spring源码阅读(三)-Spring AOP用法和理解 说明 Spring AOP

AOP、Spring AOP、Aspectj

AOP

  1. 在我们原来的的代码的基础上,方法执行前,方法返回后,方法出现异常时,进行拦截处理或者叫做增强

Spring AOP

  1. 基于动态代理实现,默认如果使用接口,使用jdk动态代理。如果没有实现接口,则使用CGLIB实现 用法可参考
  2. Spring 3.2 以后,spring-core 直接就把 CGLIB 和 ASM 的源码包括进来了,这也是为什么我们不需要显式引入这两个依赖
  3. Spring AOP 需要依赖于 IOC 容器来管理。
  4. Spring AOP 只能基于容器中的Bean实现代理增强
  5. Spring 提供了 AspectJ 的支持 注:这里的支持只是规范的支持 底层还是spring

Aspectj

  1. AspectJ来自于Eclipse基金会
  2. 属于静态织入 注入时机可以是:
      • Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
      • Post-compile weaving:也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
      • Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar

       3.AspectJ 能干很多 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案。

Spring AOP

Spring AOP沿用了AspectJ的相关概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。

如 @Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。

目前 Spring AOP 一共有三种配置方式,Spring 做到了很好地向下兼容

  • Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的
  • Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间 <aop />
  • Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做 @AspectJ,但是这个和 AspectJ 其实没啥关系。

Spring 1.2 中的配置

例子1

1.新增2个Advice

/**
 * @Project spring
 * @Description 记录方法入参
 */
public class LogArgAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
          System.out.println(String.format("方法名字:%s,入参:%s",method.getName(), Arrays.toString(args)));
    }
}
/**
 * @Description 方法返回拦截
 */
public class ReturnAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
      System.out.println(String.format("方法:%s,返回值:%s",method.getName(),returnValue));
    }
}

2.配置被代理类

public interface StudentService {
    public Integer  del(Long id);

    public boolean delAll();
}
public class StudentServiceImpl implements StudentService {

    @Override
    public Integer del(Long id) {
        System.out.println(String.format("执行删除id为:%s的数据",id));
        return 1;
    }

    @Override
    public boolean delAll() {
        System.out.println("删除所有数据");
        return true;
    }
}

3.xml配置

<!--代理类-->
    <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
    <!--定义2个advice-->
    <bean name="logArgAdvice"  class="org.springframework.lq.aspect.LogArgAdvice"></bean>
    <bean name="returnAdvice"  class="org.springframework.lq.aspect.ReturnAdvice"></bean>
    <!--配置实现studentServiceImpl的代理-->
    <bean name="studentServiceImplProxy"  class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--代理接口-->
        <property name="proxyInterfaces">
            <list>
                <value>org.springframework.lq.service.StudentService</value>
            </list>
        </property>
        <!--代理对象-->
        <property name="target" ref="studentServiceImpl" ></property>
        <!--配置拦截器 这里可以配置advice advisor interceptor-->
        <property name="interceptorNames">
            <list>
                <value>logArgAdvice</value>
                <value>returnAdvice</value>
            </list>
        </property>
    </bean>

4.测试

@Test
    public void lqTEST() {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                new String[] {LQCONTEXT}, getClass());
        StudentService studentService=ctx.getBean("studentServiceImplProxy",StudentService.class);
        studentService.del(1L);
        studentService.delAll();
}

输出:

spring源码阅读(三)-Spring AOP用法和理解
说明
Spring AOP

例子2

例子1有个缺点就是,粒度太大,所有方法都实现了拦截.如果只需要对delAll进行增强呢 使用Advisor

    <!--代理类-->
    <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
    <!--配置Advisor-->
    <bean name="logAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <!--指定advice-->
        <property name="advice" ref="logArgAdvice"/>
        <!--需要拦截的方法 可以配置多个,号隔开-->
        <property name="mappedNames" value="delAll"/>
    </bean>
    <!--定义advice-->
    <bean name="logArgAdvice"  class="org.springframework.lq.aspect.LogArgAdvice"></bean>
    <!--配置实现studentServiceImpl的代理-->
    <bean name="studentServiceImplProxy"  class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--代理接口-->
        <property name="proxyInterfaces">
            <list>
                <value>org.springframework.lq.service.StudentService</value>
            </list>
        </property>
        <!--代理对象-->
        <property name="target" ref="studentServiceImpl" ></property>
        <!--配置拦截器 这里可以配置advice advisor interceptor-->
        <property name="interceptorNames">
            <list>
                <value>logAdvisor</value>
            </list>
        </property>
    </bean>

例子3

Interceptor 如果是advisor和advice都是方法的增强 我理解 Interceptor更倾向于方法的控制

1.定义一个拦截器

public class OperationInterceptor  implements MethodInterceptor {
    /**
     * 拦截的方法
     */
    List<String> refuseMethod= Arrays.asList("delAll");
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        if(refuseMethod.contains(invocation.getMethod().getName())){
            throw new Exception("无权访问");
        }
        return invocation.proceed();
    }
}

2.定义xml

<!--代理类-->
    <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
    <!--配置Interceptor-->
    <bean name="operationInterceptor" class="org.springframework.lq.aspect.OperationInterceptor">
    </bean>
    <!--配置实现studentServiceImpl的代理-->
    <bean name="studentServiceImplProxy"  class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--代理接口-->
        <property name="proxyInterfaces">
            <list>
                <value>org.springframework.lq.service.StudentService</value>
            </list>
        </property>
        <!--代理对象-->
        <property name="target" ref="studentServiceImpl" ></property>
        <!--配置拦截器 这里可以配置advice advisor interceptor-->
        <property name="interceptorNames">
            <list>
                <value>operationInterceptor</value>
            </list>
        </property>
    </bean>

3.测试

spring源码阅读(三)-Spring AOP用法和理解
说明
Spring AOP

例子4

例子3的问题是,对于每个类型我们都要配置一个代理,如果我们有需求,对ServiceImpl结尾的类生成代理呢 之类我们使用BeanNameAutoProxyCreator

1.xml配置

beanNames支持正则表达式配置

    <!--代理类-->
    <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
    <!--配置Interceptor-->
    <bean name="operationInterceptor" class="org.springframework.lq.aspect.OperationInterceptor">
    </bean>
    <!--配置实现studentServiceImpl的代理-->
    <bean  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames" value="*ServiceImpl"></property>
        <!--配置拦截器 这里可以配置advice advisor interceptor-->
        <property name="interceptorNames">
            <list>
                <value>operationInterceptor</value>
            </list>
        </property>
    </bean>

2.测试

红色部分可以发现我们可以直接使用基础类型了

        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                new String[] {LQCONTEXT}, getClass());
        StudentService studentService=ctx.getBean(StudentService.class);
        studentService.del(1L);
        studentService.delAll();

spring源码阅读(三)-Spring AOP用法和理解
说明
Spring AOP

例子5

如果我们需要让del开头的方法进行方法控制呢 使用RegexpMethodPointcutAdvisor

1.xml配置

<!--代理类-->
    <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
    <!--配置Advisor-->
    <bean name="logAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!--指定advice-->
        <property name="advice" ref="logArgAdvice"/>
        <!--需要拦截的方法 可以配置多个,号隔开 全名称配置 支持正则多个,号隔开-->
        <property name="pattern" value="org.springframework.lq.service.StudentService.del*"/>
    </bean>
    <!--定义advice-->
    <bean name="logArgAdvice"  class="org.springframework.lq.aspect.LogArgAdvice"></bean>
    <!--配置实现studentServiceImpl的代理-->
    <bean  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames" value="*ServiceImpl"></property>
        <!--配置拦截器 这里可以配置advice advisor interceptor-->
        <property name="interceptorNames">
            <list>
                <value>logAdvisor</value>
            </list>
        </property>
    </bean>

例子6

是否觉得配置BeanNameAutoProxyCreator 很麻烦呢 可以使用DefaultAdvisorAutoProxyCreator

    <!--代理类-->
    <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
    <!--配置Advisor-->
    <bean name="logAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!--指定advice-->
        <property name="advice" ref="logArgAdvice"/>
        <!--需要拦截的方法 可以配置多个,号隔开 全名称配置 支持正则多个,号隔开-->
        <property name="pattern" value="org.springframework.lq.service.StudentService.del*"/>
    </bean>
    <!--定义advice-->
    <bean name="logArgAdvice"  class="org.springframework.lq.aspect.LogArgAdvice"></bean>
    <!--配置自动代理-->
    <bean  class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

Spring 2.0 @AspectJ配置

1.引入包

注:虽然引入了Aspectj包,但是还是使用的是Spring API @Aspectj跟 Aspectj没有任何关系,只是spring AOP是根据Aspectj规则实现,用到了他的注解

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

如果是使用 Spring Boot 的话,添加以下依赖即可:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.xml配置

<aop:aspectj-autoproxy/>

也可以通过注解开启

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

一旦开启了上面的配置,那么所有使用 @Aspect 注解的 bean 都会被 Spring 当做用来实现 AOP 的配置类,我们称之为一个 Aspect。

注意了,@Aspect 注解要作用在 bean 上面,不管是使用 @Component 等注解方式,还是在 xml 中配置 bean,首先它需要是一个 bean。

如:

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of aspect here as normal -->
</bean>
@Aspect
public class NotVeryUsefulAspect {

}

3.配置Pointcut 一般翻译为切点

@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

@Pointcut 中使用了 execution 来正则匹配方法签名,这也是最常用的,除了 execution,我们再看看其他的几个比较常用的匹配方式:

  • within:指定所在类或所在包下面的方法(Spring AOP 独有)

    如 @Pointcut("within(com.javadoop.springaoplearning.service..*)")

  • @annotation:方法上具有特定的注解,如 @Subscribe 用于订阅特定的事件。

    如 @Pointcut("execution(.*(..)) && @annotation(com.javadoop.annotation.Subscribe)")

  • bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)

    如 @Pointcut("bean(*Service)")

上面匹配中,通常 "." 代表一个包名,".." 代表包及其子包,方法参数任意匹配使用两个点 ".."。

对于 web 开发者,Spring 有个很好的建议,就是定义一个 SystemArchitecture:

@Aspect
public class SystemArchitecture {

    // web 层
    @Pointcut("within(com.javadoop.web..*)")
    public void inWebLayer() {}

    // service 层
    @Pointcut("within(com.javadoop.service..*)")
    public void inServiceLayer() {}

    // dao 层
    @Pointcut("within(com.javadoop.dao..*)")
    public void inDataAccessLayer() {}

    // service 实现,注意这里指的是方法实现,其实通常也可以使用 bean(*ServiceImpl)
    @Pointcut("execution(* com.javadoop..service.*.*(..))")
    public void businessService() {}

    // dao 实现
    @Pointcut("execution(* com.javadoop.dao.*.*(..))")
    public void dataAccessOperation() {}
}

上面这个 SystemArchitecture 很好理解,该 Aspect 定义了一堆的 Pointcut,随后在任何需要 Pointcut 的地方都可以直接引用(如 xml 中的 pointcut-ref="")。

配置 pointcut 就是配置我们需要拦截哪些方法。

配置

Advice配置

@Aspect
public class AdviceExample {

    // 这里会用到我们前面说的 SystemArchitecture
    // 下面方法就是写拦截 "dao层实现"
    @Before("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ... 实现代码
    }

    // 当然,我们也可以直接"内联"Pointcut,直接在这里定义 Pointcut
    // 把 Advice 和 Pointcut 合在一起了,但是这两个概念我们还是要区分清楚的
    @Before("execution(* com.javadoop.dao.*.*(..))")
    public void doAccessCheck() {
        // ... 实现代码
    }

    @AfterReturning("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

    @AfterReturning(
        pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // 这样,进来这个方法的处理时候,retVal 就是相应方法的返回值,是不是非常方便
        //  ... 实现代码
    }

    // 异常返回
    @AfterThrowing("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ... 实现代码
    }

    @AfterThrowing(
        pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ... 实现代码
    }

    // 注意理解它和 @AfterReturning 之间的区别,这里会拦截正常返回和异常的情况
    @After("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // 通常就像 finally 块一样使用,用来释放资源。
        // 无论正常返回还是异常退出,都会被拦截到
    }

    // 感觉这个很有用吧,既能做 @Before 的事情,也可以做 @AfterReturning 的事情
    @Around("com.javadoop.aop.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}
@Before("com.javadoop.springaoplearning.aop_spring_2_aspectj.SystemArchitecture.businessService()")
public void logArgs(JoinPoint joinPoint) {
    System.out.println("方法执行前,打印入参:" + Arrays.toString(joinPoint.getArgs()));
}

Spring 2.0 schema-based配置

解析<AOP/> 相关的xml在org.springframework.aop.config.AopNamespaceHandle 中

<aop:config>
    <aop:aspect ref="logArgsAspect">
        <aop:pointcut >
                expression="com.javadoop.SystemArchitecture.businessService()" />
    </aop:aspect>
</aop:config>