spring源码

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

2018-09-09  本文已影响82人  Coding小聪

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

1.例子

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

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

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上的目标对象自动创建代理对象。

<!--
    AutoProxyCreator
    AnnotationAwareAspectJAutoProxyCreator会自动搜集IOC容器中注册的Aspect,并应用到
    Pointcut定义的各个目标对象上。
 -->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">
    <property name="proxyTargetClass" value="true"></property>
</bean>

<!-- aspect -->
<bean id="authAspect" class="cn.zgc.aop.aspect.AuthAspect"/>

<!-- 目标对象 -->
<bean id="target" class="cn.zgc.aop.aspect.UserServiceImpl"/>

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

<?xml version="1.0" encoding="GBK"?>
<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
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 启动@AspectJ支持 -->
    <aop:aspectj-autoproxy/>

    <!-- aspect -->
    <bean id="authAspect" class="cn.zgc.aop.aspect.AuthAspect"/>

    <!-- 目标对象 -->
    <bean id="target" class="cn.zgc.aop.aspect.UserServiceImpl"/>
</beans>

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

<?xml version="1.0" encoding="GBK"?>
<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组件、自动搜索切面类 -->
    <context:component-scan base-package="cn.zgc.aop.aspect">
        <context:include-filter type="annotation"
            expression="org.aspectj.lang.annotation.Aspect"/>
    </context:component-scan>
    <!-- 启动@AspectJ支持 -->
    <aop:aspectj-autoproxy/>
</beans>

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种类型的表达式,分别如下。

// 匹配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..*)")
@Pointcut("this(cn.zgc.aop.UserviceImpl)")
@Pointcut("target(cn.zgc.aop.UserviceImpl)")
// 匹配任何不带参数的方法
@Pointcut("args()")
// 匹配任何只带一个参数,而且这个参数的类型是String的方法
@Pointcut("args(java.lang.String)")
// 匹配带任意参数的方法
@Pointcut("args(..)")
// 匹配带任意个参数,但是第一个参数的类型是String的方法
@Pointcut("args(java.lang.String,..)")
// 匹配带任意个参数,但是最后一个参数的类型是String的方法
@Pointcut("args(.., java.lang.String)")
@target(MyAnnotation)
// 匹配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的表达式中可以使用两种通配符:*和..

我们基于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的注解有以下几种:

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值来表示。

上一篇 下一篇

猜你喜欢

热点阅读