spring · 2019-08-24 0

Spring中使用AOP的两种方式(基于注解和XML配置)

一、添加依赖

在maven中添加spring依赖、aop依赖和spring测试依赖

    <!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

    <!--spring aop依赖-->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

    <!--spring test-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

    <!--junit依赖-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>

二、配置文件

编写配置文件spring-conf.xml

扫描com.exmple.service和com.exemple.utils包下注解,把声明bean的注解,加入到ioc容器中

:用于开启基于注解的AOP功能

<?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:context="http://www.springframework.org/schema/context"
       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/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--包扫描-->
    <context:component-scan base-package="com.example.service"/>
    <context:component-scan base-package="com.example.utils"/>

    <!--开启基于注解的AOP功能-->
    <aop:aspectj-autoproxy/>

</beans>

三、目标类

Calculator.java

public interface Calculator {

    public int add(int i, int j);
    public int sub(int i, int j);
    public int mul(int i, int j);
    public int div(int i, int j);

}

@Service:声明CalculatorImpl 是一个spring bean组件

CaculatorImpl.java

@Service
public class CalculatorImpl implements Calculator {

    @Override
    public int add(int i, int j) {
        return i+j;
    }

    @Override
    public int sub(int i, int j) {
        return i-j;
    }

    @Override
    public int mul(int i, int j) {
        return i*j;
    }

    @Override
    public int div(int i, int j) {
        return i/j;
    }
}

四、切面类

@Componet:声明LogUtils是一个spring bean组件

@Aspect:告诉spring容器,LogUtils是一个切面类

LogUtils.java

@Aspect
@Component
//@Order(1)   //使用Order改变切面顺序,数值越小优先级越高
public class LogUtils {

    /**
     * 切入点
     */
    @Pointcut("execution(public int com.example.service.CalculatorImpl.*(int,int))")
    public void executePackage(){};

    /**
     * 前置通知,目标方法调用前被调用
     */
    @Before("executePackage()")
    public void beforeAdvice(JoinPoint joinPoint){
        System.out.println("- - - - - 前置通知 - - - - -");
        Signature signature = joinPoint.getSignature();
        System.out.println("返回目标方法的签名:"+signature);
        System.out.println("代理的是哪一个方法:"+signature.getName());
        Object[] obj = joinPoint.getArgs();
        System.out.println("获取目标方法的参数信息:"+Arrays.asList(obj));
    }

    /**
     * 后置通知,目标方法执行完执行
     */
    @After("execution(public int com.example.service.CalculatorImpl.*(int,int))")
    public void afterAdvice(){
        System.out.println("- - - - - 后置通知- - - - -");
    }

    /**
     * 后置返回通知
     * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
     * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
     * returning 只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行
     */
   @AfterReturning(value = "executePackage()",returning = "res")
    public void afterReturningAdvice(JoinPoint joinPoint, Object res){
        System.out.println("- - - - - 后置返回通知- - - - -");
        System.out.println("后置返回通知 返回值:"+res);
    }

    /**
     * 后置异常通知
     *  定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
     *  throwing 只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
     */
    @AfterThrowing(value = "executePackage()",throwing = "exception")
    public void afterThrowingAdvice(JoinPoint joinPoint,NullPointerException exception){
        System.out.println("- - - - - 后置异常通知 - - - - -");
    }

    /**
     * 环绕通知:
     * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     */
    @Around("executePackage()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();  //参数
        try {
            System.out.println("- - - - - 环绕前置通知 - - - -");
            Object obj = proceedingJoinPoint.proceed(args);//调用执行目标方法
            System.out.println("- - - - - 环绕后置返回通知 - - - -");
            return obj;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("- - - - - 环绕异常通知 - - - -");
        }finally {
            System.out.println("- - - - - 环绕后置通知 - - - -");
        }
        return null;
    }
}

1.@Pointcut是创建切入点

切入点方法不用写代码,返回类型为void

execution:用于匹配表达式

execution(访问权限符 返回值类型 方法签名)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?) 
  • 修饰符匹配(modifier-pattern?)
  • 返回值匹配(ret-type-pattern)可以为*表示任何返回值,全路径的类名等
  • 类路径匹配(declaring-type-pattern?)
  • 方法名匹配(name-pattern)可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法
  • 参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“*”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(..)表示零个或多个任意参数
  • 异常类型匹配(throws-pattern?) 其中后面跟 着“?”的是可选项
1)execution(* *(..))
//表示匹配所有方法
2)execution(public * com.example.service.*(..))
//表示匹配com.example.service中所有的public方法
3)execution(* com. example.service..*.*(..))
//表示匹配com.example.service包及其子包下的所有方法

2.JoinPoint

除@Around外,每个方法里都可以加或者不加参数JoinPoint。JoinPoint包含了类名、被切面的方法名、参数等属性。@Around参数必须为ProceedingJoinPoint。

五、测试

MyTest.java

@ContextConfiguration(locations = "classpath:spring-conf.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class MyTest {

    @Autowired
    private Calculator calculator;

    @Test
    public void test(){
        int res = calculator.add(1,2);
        System.out.println("- - - - - - - - - - - - - - - -");
        System.out.println("calculator:"+calculator.getClass());
        System.out.println("res:"+res);
    }
}

六、结果

执行顺序:

环绕前置- - -前置---目标方法执行---环绕后置返回/异常---环绕后置---后置---后置返回/异常

spring注册的bean是代理的对象,不是实现类

- - - - - 环绕前置通知 - - - -
- - - - - 前置通知 - - - - -
返回目标方法的签名:int com.example.service.Calculator.add(int,int)
代理的是哪一个方法:add
获取目标方法的参数信息:[1, 2]
- - - - - 环绕后置返回通知 - - - -
- - - - - 环绕后置通知 - - - -
- - - - - 后置通知- - - - -
- - - - - 后置返回通知- - - - -
后置返回通知 返回值:3
- - - - - - - - - - - - - - - -
calculator:class com.sun.proxy.$Proxy25
res:3

七、基于XML配置的AOP

可以只使用XML完成对AOP的配置

<?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:context="http://www.springframework.org/schema/context"
       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/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="calculator" class="com.example.service.CalculatorImpl"/>
    <bean id="logUtils" class="com.example.utils.LogUtils"/>

    <aop:config>
        <aop:pointcut id="mypoint" expression="execution(public int com.example.service.CalculatorImpl.*(int,int))"/>
        <aop:aspect ref="logUtils" order="1">
            <aop:before method="beforeAdvice" pointcut="execution(public int com.example.service.CalculatorImpl.*(int,int))"/>
            <aop:after-returning method="afterReturningAdvice" pointcut-ref="mypoint" returning="res" />
        </aop:aspect>
    </aop:config>

</beans>