AOP了解

AOP:面向切面编程。把公共的模块提取出来,作为切面,在程序运行期织入到对应的业务中。AOP是通过动态代理实现的。

spring的AOP是使用哪种代理方式?
spring默认会判断被代理的类是否实现类接口,如果实现了接口,就使用JDK的代理方式,如果没有实现接口,就使用CGLib代理方式,当然我们也可以通过配置强制要求spring全部使用CGLib代理方式。

实现一个aop例子:

  • 添加依赖
  • 实现目标类
  • 创建切面类
    • 添加响应的处理方法,如前置通知方法
  • 配置aop(目前通过xml配置文件进行配置)
  • 测试
    1. <!-- spring的依赖 -->
    2. <dependency>
    3. <groupId>org.springframework</groupId>
    4. <artifactId>spring-context</artifactId>
    5. <version>5.2.9.RELEASE</version>
    6. </dependency>
    7. <dependency>
    8. <groupId>org.springframework</groupId>
    9. <artifactId>spring-aspects</artifactId>
    10. <version>5.2.9.RELEASE</version>
    11. </dependency>
    ```java public interface UserDao { int save(String name); }

public class UserDaoImpl implements UserDao { public int save(String name) { System.out.println(“我的名字是:”+name); return 1; } }


```java
public class StAspacts { 
    //前置通知的方法 
    public void before(){ System.out.println("---前置通知方法---"); } 
}
<?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 
                https://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop 
                http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 业务类 -->
    <bean id="userService" class="com.st.service.impl.UserServiceImpl"/>
    <!-- 切面类 --> 
    <bean id="aspects" class="com.st.aspects.StAspacts"/> 
    <!-- 添加AOP的配置 -->
    <!--- proxy-target-class:默认是false,如果修改为true,则表示要求spring所有的 动态代理都使用CGLib --> 
    <aop:config proxy-target-class="false"> 
        <!-- 配置切面 --> 
        <!-- ref 执行我们上面配置的切面bean -->
        <aop:aspect id="stAspacts" ref="aspects"> 
            <!-- 配置一个前置通知:被拦截方法的之前执行的方法 -->
            <!-- method:配置切面类中的对应的要执行的方法 --> 
            <!-- pointcut:切入点。 描述这个增强的方法,应该执行的位置。--> 
            <aop:before method="before" pointcut="execution(* com.st.service.impl.UserServiceImpl.*(..))"/> 
        </aop:aspect> 
    </aop:config>
</beans>

测试:

    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao bean = context.getBean(UserDao.class);
        bean.save("quail");
    }

测试结果:image.png

AOP的五种通知

  • 前置通知(before)
  • 后置通知(after-returning)
  • 环绕通知(round)被拦截方法的前后都会通知
  • 异常通知(after-throwing)出现异常就执行
  • 最终tongz(after)无论是否异常都会执行的通知

tips:所有通知都可以多次添加。

package com.quail.aspects;

import org.aspectj.lang.ProceedingJoinPoint;

public class QuailAspects {
    // 1前置通知
    public void before(){
        System.out.println("--前置通知方法--");
    }

    // 6后置通知
    public void afterReturning(){
        System.out.println("--后置通知方法--");
    }

    // 234环绕通知,参数是切入点对象
    public Object round(ProceedingJoinPoint joinPoint){
        System.out.println("--2环绕前置通知--");
        // 执行目标方法
        Object proceed = null;
        try {
            // 通过切入点执行目标方法
            proceed = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("--环绕后置通知--");
        return proceed;
    }

    // 异常通知
    public void afterThrowing(){
        System.out.println("--异常通知--");
    }

    // 5最终通知
    public void after(){
        System.out.println("--最终通知--");
    }
}
<!-- 添加aop配置 -->
    <!-- proxy-target-class:默认是false,为true时,spring的动态代理方式为CGLib-->
    <aop:config proxy-target-class="false">
        <!-- 配置切面 -->
        <aop:aspect id="myAspects" ref="aspects">
            <!-- 配置一个前置通知:被拦截方法的之前执行的方法 -->
            <!--method:配置切面类中的对应的要执行的方法-->
            <!--pointcut:切入点。描述这个增强的方法,应该执行的位置。-->
            <aop:before method="before" 
                        pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
            <aop:after-returning method="afterReturning" 
                        pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
            <aop:after-throwing method="afterThrowing" 
                        pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
            <aop:after method="after" 
                        pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
            <aop:around method="round" 
                        pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
        </aop:aspect>
    </aop:config>

Spring通知方法中的参数

  • 所有的通知方法都可以设置参数JoinPoint。这个参数叫连接点。包含了连接点的所有信息。这个参数必须写第一个。
    • toString() //连接点的字符串表达形式,其中包含方法名字
    • getThis() //目标对象
    • getTarget() //被代理的对象
    • getArgs() //获取被增强的方法的参数数组
  • 在通知方法中使用目标方法的参数

    • public void before(String name){System.out.print(name);}
    • 需要在配置文件中指明。
      • 配置参数名,用来通知spring要传入的参数是哪个。
      • 配置一个切点的要求,只有拥有这个参数的方法才会被增强。
        <aop:before method="before" arg-names="name"
            pointcut="args(name) execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
        
        image.png
  • 后置通知中获取方法的返回值

    • 在后置通知方法中设置一个参数,用来获取目标方法的返回值。
      // 6后置通知
      public void afterReturning(int returnVal){
         System.out.println("--后置通知方法--"+returnVal);
      }
      
      <aop:after-returning method="afterReturning" returning="returnVal"
                      pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
      
      image.png
  • 在异常通知中获取异常对象

    • 添加表示异常的参数,如:RuntimeException e
    • 配置文件中添加属性:throwing=”e”

      SpringAOP切点的配置方法

      定义一个切点描述:
      <aop:config proxy-target-class="false">
      <aop:pointcut id="allMethod" 
           expression="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
      <!-- 配置切面 -->
      <aop:aspect id="myAspects" ref="aspects">
                     <aop:after-returning method="afterReturning" pointcut-ref="allMethod"/>
      </aop:aspect>
      </aop:config>
      

      切点描述的配置方式:

  • 表示所有的public方法

    • execution(public * *(..))
  • 所有的set开头的方法
    • execution(* set*(..))
  • AccountService接口中的所有方法
    • execution(* com.xyz.service.AccountService.*(..))
  • 在service包下的所有类的所有方法(不含子孙包)
    • execution(* com.xyz.service.*.*(..))
  • service包下及子孙包下所有类的所有方法
    • execution(* com.xyz.service..*.*(..))
  • service包中的任何连接点(仅SpringAOP中执行方法)
    • within(com.xyz.service.*)
  • this中配置的都是具体类型,也就是全限定类名。this中不支持通配符
    • this(com.xyz.service.AccountService)
  • target和this的主要不同点:target是按照目标类型进行匹配,this按照调用类型进行匹配
    • target(com.xyz.service.AccountService)
  • 匹配指定参数的方法
    • args(java.io.Serializable)
  • 按照指定bean的beanName匹配
    • bean(userService)
  • 匹配所有的beanName以及service结尾的bean
    • bean(*Service)

Advisor方式配置AOP

  • 前置通知

自定义一个类实现MethodBeforeAdvice接口。

package com.quail.aspects;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;


public class BeforeAdvice implements MethodBeforeAdvice {
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("--前置通知--");
    }
}
<bean id="beforeAdvice" class="com.quail.aspects.BeforeAdvice"/>
<aop:config proxy-target-class="false">
  <aop:pointcut id="allMethod" 
                expression="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
  <!-- Spring会自己判断通知的类型 -->
  <aop:advisor advice-ref="beforeAdvice" pointcut-ref="allMethod"/>
</aop:config>