深入理解SpringAOP-基于注解的方式

从Spring2.0开始,Spring AOP框架集成了AspectJ的部分功能,SpringAOP基于注解的方式就是基于AspectJ框架。并且注解逐渐成为SpringAOP主要的开发方式,下面我们从一个例子说起

1.例子

1.1 要想使用@Aspect形式的aop,首先需要导入aspectj相关的jar包


    org.aspectj
    aspectjweaver

1.2 定义目标类

@Service
public class UserServiceImpl implements UserService{

    private Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Override
    public void addUser(String name) {
        logger.info("添加用户,{}",name);
    }

    @Override
    public String quereyUser() {
        logger.info("查询用户信息");
        return "admin";
    }
}

1.3 定义切面(aspect)类

@Aspect
public class AuthAspect {
    // 匹配UserServiceImpl类中所有的操作、
    // 所有方法的执行作为切入点
    @Before("execution(* cn.zgc.aop.aspect.UserServiceImpl.*(..))")
    public void authority() {
        System.out.println("模拟执行权限检查");
    }
}

1.4 创建代理(织入),有以下两种方式:

  • 编程方式
  • 自动代理方式

1.4.1 编程方式就是通过ProxyFactory的API来完成代理的创建,具体如下代码所示:

/**
 * 编程方式进行织入
 */
private static void aspectUse01() {
    AspectJProxyFactory weaver = new AspectJProxyFactory();
    weaver.setTarget(new UserServiceImpl());
    weaver.addAspect(AuthAspect.class);
    UserService proxy = weaver.getProxy();
    logger.info("{}",proxy.getClass());
    proxy.quereyUser();
}

1.4.2 通过自动代理方式创建代理,需要在IoC容器中配置自动代理创建器(AutoProxyCreator),然后其会自动检索Aspect对象,然后为Pointcut上的目标对象自动创建代理对象。



    







引入xml文件中引入aop后有一种更简便的写法




    
    

    
    

    
    

如果使用引入context命名空间实现组件扫描,那么配置文件可以写成这样:



    
    
        
    
    
    

2.Pointcut

@Pointcut("execution(* transfer(..))")
private void anyOldTransfer() {
}

org.aspectj.lang.annotation.Pointcut注解用来表示@AspectJ形式的Pointcut
@Pointcut表达式的组成如下所示

Pointcut表达式

2.1表达式类型

Spring Aop支持AspectJ中的9种表达式类型,外加Spring Aop自己扩充的一种一共是10种类型的表达式,分别如下。

  • execution:一般用于指定方法的执行,用的最多。
  • within:指定某些类型的全部方法执行,也可用来指定一个包。
// 匹配Transactional类中所有的方法声明
@Pointcut("within(org.springframework.transaction.annotation.Transactional)")
// 匹配org.springframework.transaction.annotation包下所有类中的所有方法
@Pointcut("within(org.springframework.transaction.annotation.*)")
// 匹配org.springframework.transaction包及其子包下所有类中的所有方法
@Pointcut("within(org.springframework.transaction..*)")
  • this:Spring Aop是基于代理的,生成的bean是一个代理对象,this就是这个代理对象,当这个对象是指定的类型时,该Pointcut表示指定类型中的所有方法
@Pointcut("this(cn.zgc.aop.UserviceImpl)")
  • target:当被代理的对象(即目标对象)可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
@Pointcut("target(cn.zgc.aop.UserviceImpl)")
  • args:当执行的方法的参数是指定类型时生效。
// 匹配任何不带参数的方法
@Pointcut("args()")
// 匹配任何只带一个参数,而且这个参数的类型是String的方法
@Pointcut("args(java.lang.String)")
// 匹配带任意参数的方法
@Pointcut("args(..)")
// 匹配带任意个参数,但是第一个参数的类型是String的方法
@Pointcut("args(java.lang.String,..)")
// 匹配带任意个参数,但是最后一个参数的类型是String的方法
@Pointcut("args(.., java.lang.String)")
  • @target:当代理的目标对象上拥有指定的注解时生效。
@target(MyAnnotation)
  • @args:当执行的方法参数类型上拥有指定的注解时生效。
  • @within:与@target类似,看官方文档和网上的说法都是@within只需要目标对象的类或者父类上有指定的注解,则@within会生效,而@target则是必须是目标对象的类上有指定的注解。而根据笔者的测试这两者都是只要目标类或父类上有指定的注解即可。
  • @annotation:当执行的方法上拥有指定的注解时生效。
  • bean:当调用的方法是指定的bean的方法时生效。
// 匹配Spring Bean容器中id或name为user的bean的方法调用
@Pointcut(“bean(user)”)
// 匹配所有id或name为以user开头的bean的方法调用
@Pointcut("bean(user*)")

2.2 execution类型的Pointcut表达式格式

不同类型Pointcut中的表达式格式是不相同的。execution是最常用的,所以我们来看看execution表达式的格式。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
            name-pattern(param-pattern)throws-pattern?)

假如有以下类的定义:

package cn.zgc.aop.aspect;

public class UserServiceImpl {

    @Override
    public String quereyUser(String id) {
        logger.info("查询用户信息");
        return "admin";
    }
}

那么对于quereyUser而言,表达式各匹配模式的取值如下:

modifiers-pattern: public
ret-type-pattern: String
declaring-type-pattern: cn.zgc.aop.aspect.UserServiceImpl 
name-pattern: quereyUser
param-pattern: String

execution表达式中的方法的返回类型、方法名和参数部分的匹配模式是必须指定的,其他的匹配模式都可以省略。

另外,execution的表达式中可以使用两种通配符:*和..

  • *可以用于任何部分的匹配模式,表示任意字符。
  • ..可以用在两个位置:declaring-type-patternparam-pattern
    用在declaring-type-pattern表示可以指定多个层次的类型声明;如果用在param-pattern则表示该方法可以有0到多个参数,参数类型不限。

我们基于UserServiceImpl类中的quereyUser来看看几个execution的表达式。

// 所有的匹配模式都指定
execution(public String cn.zgc.aop.aspect.UserServiceImpl.quereyUser(String))
// 简化版本的,匹配所有的方法签名为String quereyUser(String)的方法
execution(String quereyUser(String))
// 匹配所有参数为一个String类型的方法
execution(* *(String))
// 全匹配
execution(* *(*))
// 匹配cn.zgc.aop包下所有类中的quereyUser(String)方法
execution(* cn.zgc.aop.*.quereyUser(String))
// 匹配cn.zgc.aop包以及其子包中所有类中的quereyUser(String)方法
execution(* cn.zgc.aop..*.quereyUser(String))
// 所有类中的quereyUser()方法,参数不限
execution(String *.quereyUser(..))
// 匹配两个参数的quereyUser方法,第一个参数类型为String,第二个参数类型不限
execution(String quereyUser(String,*))
// 匹配拥有多个参数的quereyUser方法,之前几个参数类型不限,最后一个参数类型必须是String
execution(String quereyUser(..,String))

2.3 表达式组合

可以通过逻辑运算符(&&、||、!)将多个Pointcut表达式组合在一起使用。

// 匹配id或name为userService的bean的所有无参方法。
@Pointcut(“bean(userService) && args()”)
//匹配id或name为userService的bean的方法调用,或者是方法上使用了MyAnnotation注解的方法调用。
@Pointcut(“bean(userService) || @annotation(MyAnnotation)”)
//匹配id或name为userService的bean的所有有参方法调用。
@Pointcut(“bean(userService) && !args()”)

3.Advice

注解形式的advice是在@aspect标注的类中的普通方法,不过需要在这些方法上添加相应的注解,可以用于标注为advice的注解有以下几种:

  • @Before:用于标注Before Advice所在的方法
  • @AfterReturning:用于标注After Returning Advice所在的方法
  • @AfterThrowing:用于标注Throws Advice所在的方法
  • @After:用于标注After finally advice所在的方法
  • @Around:用于标注Around advice所在的方法
  • @DeclareParents:用于标注Introduction类型的Advice,该注解用在成员变量上,而不是方法。

3.1 @Before

@Before(),括弧里面的是Pointcut的表达式(其它Advice注解也是同样的含义)。基本用法如下:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    //@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }

}

如果我们想要通过获取被拦截方法的参数,可以通过args()进行绑定,具体代码如下

@Before("execution(* cn.zgc.aop.aspect.*.*(..)) && args(argName)")
public void getTargetInfos(String argName){
    System.out.println("拦截的方法参数为:"+argName);
}

3.2 @AfterReturning

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("execution(boolen *.execute(..))")
    public void doAccessCheck() {
        // ...
    }
    // 通过returning属性来获取方法返回的参数
    @AfterReturning(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }
}

3.3 @AfterThrowing

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }

    // 通过throwing属性访问具体抛出的异常
    @AfterThrowing(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }
}

3.4 @After

@After用来声明After(Finally) Advice。对于匹配上了Joinpoint的方法,不管该方法是正常执行返回,还是执行过程中抛出异常,都会触发After(Finally) Advice的执行。所以其适合处理网络连接的释放、数据库连接的释放等资源释放型工作。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }

}

3.5 @Around

@Aspect
public class PerformanceAspect{

    private final Logger logger = LoggerFactory.getLogger(PerformanceAspect.class);

    @Around("execution(boolean *.execute(..))")
    public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        try {
            stopWatch.start();
            return joinPoint.proceed();
        } catch (Exception e){
            // do nothing
        } finally {
            stopWatch.stop();
            if (logger.isInfoEnabled()){
                logger.info(stopWatch.toString());
            }
        }
        return null;
    }
}

3.6 @DeclareParents

@DeclareParents用来声明IntroductionIntroduction是将一个接口的定义添加到目标对象上。

public class IntroductionAspect {
    /**
     * 将ITest的行为逻辑加到ICoder类型的目标实现类(CoderImpl)上
     */
    @DeclareParents(
        value="cn.zgc.aop.introduction.CoderImpl"
        defaultImpl=TestImpl.class
    )
    public ITest tester;
}

3.7 JoinPoint

我们可以将JoinPoint作为Advice方法的参数,通过JoinPoint来获取相关的信息

/**
 * 这里不单单可以使用@Before,还有@AfterReturning、
 * @AfterThrowing、@After注解也可以
 */
@Before("execution(* cn.zgc.aop.aspect.*.*(..))")
public void getTargetInfos(JoinPoint joinPoint){
    // 获取目标对象
    Object target = joinPoint.getTarget();
    // 获取当前的代理对象
    Object proxy = joinPoint.getThis();
    // 获取被拦截的方法
    String methodName = joinPoint.getSignature().getName();
    // 获取被拦截方法的参数
    Object[] args = joinPoint.getArgs();
}

需要注意:JoinPoint必须要放在第一个参数位置。

4.Aspect

在基于注解的SpringAOP中,普通的POJO加上@aspect注解就表示aspectaspect中的advice通过@Before@After等标识,pointcut则通过@Before@After等注解的value值来表示。

你可能感兴趣的:(深入理解SpringAOP-基于注解的方式)