【Spring】AOP的原理以及实现方式

一、闲话

今天人有点困,不过还是坚持学习了一会,今天我们看一下Spring的另外一大特性,AOP

二、基本要点

1、基本概念
AOP意为面向切面编程,它通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术
你可以理解为,我们在不改变原有的业务逻辑的情况下,对功能进行增强

2、学习AOP之前,我们还需要了解代理模式
可以参考一下我之前的博客 【结构型设计模式】代理模式

3、AOP的重点概念

  • Aspect(切面):横切关注点被模块化的类,比如我们会将日志功能写成一个LogUtils类
  • 横切关注点:和我们业务逻辑无关,但是我们需要关注的部分,比如我们想在业务代码实现之前加上日志打印功能或者安全校验等,这些功能的增强就是横切关注点
  • Advice(通知):切面必须要实现的功能,可以理解为切面类中的某个方法
  • Target(目标):被通知的对象,也就是想要对哪个对象实现功能的增强
  • Proxy(代理):向目标对象应用通知之后创建的对象,也就是我们的代理类
  • PointCut(切入点):切面通知执行的地点,也就是你想在哪里对业务逻辑功能进行增强
  • JointPoint(连接点):与切入点匹配的执行点

三、AOP的三种实现方式

1、使用Spring的API接口

首先我们需要导入AOP相关依赖

<dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.8</version>
        </dependency>
    </dependencies>

接着我们写一个支持增删查改的接口和对应实现类

package com.decade.service;
public interface UserService {
    void add();
    void delete();
    void update();
    void query();
}

import com.decade.service.UserService;

public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("用户增加");
    }

    @Override
    public void delete() {
        System.out.println("用户删除");
    }

    @Override
    public void update() {
        System.out.println("用户更新");
    }

    @Override
    public void query() {
        System.out.println("用户查询");
    }
}

假如我们想在执行业务逻辑代码前后分别加上一段日志输出
在不改变原有代码逻辑的情况下,我们就需要使用AOP了
我们新建2个Aspect(切面),分别负责业务执行前和执行后的日志输出
注意:后置增强多了一个参数,含义为执行目标对象方法的返回值

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class BeforeLogUtil implements MethodBeforeAdvice {

    /**
     * 前置增强
     * @param method 要执行的目标对象的方法
     * @param args 参数
     * @param target 目标对象
     * @throws Throwable 抛出异常
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
    }
}

import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class AfterLogUtil implements AfterReturningAdvice {

    /**
     * 后置增强
     * @param returnValue 执行目标对象方法的返回值
     * @param method 要执行的目标对象的方法
     * @param args 参数
     * @param target 目标对象
     * @throws Throwable 抛出异常
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + method.getName() + "返回结果为" + returnValue);
    }
}

此外,我们还需要进行相关配置,我们在resource文件夹下新建applicationContext.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 注册bean -->
    <bean id="userService" class="com.decade.service.impl.UserServiceImpl"/>
    <bean id="beforeLog" class="com.decade.util.BeforeLogUtil"/>
    <bean id="afterLog" class="com.decade.util.AfterLogUtil"/>

    <!-- 方式一:使用原生spring API接口 -->
    <!-- 配置AOP:需要导入AOP的约束 -->
    <aop:config>
        <!-- 切入点设置,这里的execution表达式的作用是表明要执行的位置
         execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
         除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
         这里参数含义为返回值类型 方法名 参数(这里的两个省略号表示可以是任意的参数)
         com.decade.service.impl.UserServiceImpl.*表示UserServiceImpl下的所有方法
         -->
        <aop:pointcut id="point" expression="execution(* com.decade.service.impl.UserServiceImpl.*(..))"/>
        <!-- 执行环绕增加 -->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="point"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="point"/>
    </aop:config>
</beans>

最后,我们写一个测试类来进行测试

import com.decade.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 因为动态代理模式代理的是接口,所以我们需要使用代理对象,即接口
        UserService userService = context.getBean("userService", UserService.class);

        userService.add();
    }
}

运行结果如下
【Spring】AOP的原理以及实现方式

2、使用自定义类来实现

与方式一不同的是,我们不需要继承Spring 的API接口,可以通过一个自定义的切面来实现功能的增强
我们新建一个自定义类来替代上面的BeforeLogUtil和AfterLogUtil

package com.decade.util;

// 自定义一个切面,将通知放在其中
public class LogUtils {

    public void before() {
        System.out.println("=========方法执行前=============");
    }

    public void after() {
        System.out.println("=========方法执行后=============");
    }
}

然后调整一下xml中的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 注册bean -->
    <bean id="userService" class="com.decade.service.impl.UserServiceImpl"/>
    
    <!-- 方式二:自定义类 -->
    <bean id="logUtil" class="com.decade.util.LogUtils"/>
    <aop:config>
        <!-- 自定义切面,也就是存放通知的类 -->
        <aop:aspect ref="logUtil">
            <!-- 配置切入点,即你想在哪里对业务逻辑功能进行增强 -->
            <aop:pointcut id="point" expression="execution(* com.decade.service.impl.UserServiceImpl.*(..))"/>
            <!-- 通知,也就是切面必须要实现的功能,可以理解为切面类中的某个方法 -->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
</beans>

运行方式一中的测试类,得到结果如下
【Spring】AOP的原理以及实现方式

3、使用注解实现

关键注解:@Aspect、@Before、@After、@Around

package com.decade.util;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

// 此注解表示这个类是一个切面
@Aspect
public class AnnotationPointCut {

    // 注意,@Before注解后,括号中的值填execution表达式,表明要执行的位置
    @Before("execution(* com.decade.service.impl.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("=========方法执行前=============");
    }

    // @After注解括号内的值也是execution表达式
    @After("execution(* com.decade.service.impl.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("=========方法执行后=============");
    }

    /**
     * 环绕通知
     * @param joinPoint 代表切入点的执行点
     * @throws Throwable 抛出异常
     */
    @Around("execution(* com.decade.service.impl.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("=========环绕前=============");
        // 方法执行,proceed很重要,这个是aop代理链执行的方法
        Object proceed = joinPoint.proceed();
        System.out.println("=========环绕后=============");

        // 打印签名,修饰符+ 包名+组件名(类名) +方法名
        System.out.println("Signature:" + joinPoint.getSignature());
        System.out.println("proceed:" + proceed);
    }
}

然后我们在xml文件中开启注解扫描相关配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 注册bean -->
    <bean id="userService" class="com.decade.service.impl.UserServiceImpl"/>
    <bean id="annotationPoint" class="com.decade.util.AnnotationPointCut"/>
    <!-- 开启注解,自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面
     它有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强
     当配为true时,表示使用CGLib动态代理技术织入增强。
     不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理
     -->
    <aop:aspectj-autoproxy/>
</beans>

运行结果如下
【Spring】AOP的原理以及实现方式
如有错误,欢迎指正

上一篇:Spring学习笔记之依赖注入


下一篇:(转)Spring Boot 上传文件(带进度条)