Aspectj与动态代理

  • 这是除去jdk动态代理,cglib的另外一种动态代理。spring aop就是基于jdk动态,cglib实现的,而spring aop也可以添加aspectj依赖,使用aspectj实现动态代理(spring aop貌似已经集成了aspectj)
  • spring aop的Aspectj注解实现就是参考了Aspectj,方便使用过Aspectj的快速迁移
  • Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

    • AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单
      • 而且sping aop官方也推荐使用Aspectj的实现动态代理而非使用运行时增强代理
    • 切面少时运行时增强和编译时增强性能差异不大,但是切面很多时Aspetctj比运行时增强快很多

      依赖

      1. <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
      2. <dependency>
      3. <groupId>org.aspectj</groupId>
      4. <artifactId>aspectjweaver</artifactId>
      5. <version>1.9.4</version>
      6. </dependency>
  • 还有另外一种依赖,暂不清楚区别

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>...</version>
    </dependency>
    
  • springboot中的aop依赖

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

    AOP的三种实现

    SpringAPI实现AOP

    业务接口和实现类

    interface UserService{    public void add-delete-update-serach(){}  //四个接口方法}
    class UserServiceImpl implements UserService{//实现4个方法:分别输出"增加用户","删除用户".....}
    

    增强类:

    写2个增强类,都是提供日志功能 一个实现前置增强接口,一个后置增强 也可以只写一个,同时实现2个增强接口

    public class Log implements MethodBeforeAdvice {
     //method : 要执行的目标对象的方法        objects : 被调用的方法的参数      Object : 目标对象
     public void before(Method method, Object[] objects, Object o) throws Throwable {
         System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
    }
    }
    public class AfterLog implements AfterReturningAdvice {
     //returnValue 返回值    method被调用的方法  args 被调用的方法的对象的参数   arget 被调用的目标对象
     public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
         System.out.println("执行了" + target.getClass().getName()
         +"的"+method.getName()+"方法,"
         +"返回值:"+returnValue);
    }
    }
    

    Spring配置文件

    在spring的文件中注册 , 并实现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: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">
    
     <!--注册bean-->
     <bean id="userService" class="com.kuang.service.UserServiceImpl"/>
     <bean id="log" class="com.kuang.log.Log"/>
     <bean id="afterLog" class="com.kuang.log.AfterLog"/>
    
     <!--aop的配置-->
     <aop:config>
         <!--切入点 expression:表达式匹配要执行的方法-->
         <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
         <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
         <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
         <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
     </aop:config>
    <bean id=""
    </beans>
    

    测试

    测试类没什么变化

    public class MyTest {
     @Test    public void test(){
         ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
         UserService userService = (UserService) context.getBean("userService");
         userService.search();        }    }
    

Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

自定义类实现AOP

业务类不变

写我们自己的一个切入类

public class DiyPointcut {
   public void before(){System.out.println("---------方法执行前---------");}
   public void after(){System.out.println("---------方法执行后---------");}}

配置xml

<!--注册bean-->
<bean id="diy" class="com.kuang.config.DiyPointcut"/>
<!--aop的配置-->
<aop:config>
   <!--第二种方式:使用AOP的标签实现-->
   <aop:aspect ref="diy">
       <aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
       <aop:before pointcut-ref="diyPonitcut" method="before"/>
       <aop:after pointcut-ref="diyPonitcut" method="after"/>
   </aop:aspect>
</aop:config>

测试:不变

注解实现( AspectJ )

  • 使用AspectJ实现AOP比较方便,因为它的定义比较简单。AspectJ是第三方框架。而且sping aop官方也推荐使用Aspectj的实现动态代理而非使用运行时增强代理

  • 有五种增强方式,常用的是前置增强,后置增强,环绕增强。这五种增强也叫做通知
    • 前置即执行前增加执行代码
    • 后置即执行后增加执行代码
    • 环绕则是前后都进行增强,而且还可以根据情况决定是否执行增强代码
  • @Aspect标注后在类方法执行前后都会去寻找标注了@Before,@After...的方法
  • @Pointcut创建一个切点,可理解成一个增强规则,其他具体的增强方式可以使用该规则,即这样的增强规则可以被用于前置,后置….任意一种增强
  • @EnableAspectJAutoProxy又会去寻找标注了@Aspect的类bean

  • 注解参数字符串是告诉AspectJ应该在何处执行该方法
    • before... pubic * ...UserService.*(..)表示执行UserService的每个public方法前执行a方法。无public即表示执行UserService每个方法前都执行
  • 环绕通知=前置通知+目标方法执行+后置通知
  • 环绕增强方法必须传入ProceedingJoinPoint对象,该对象及成员方法proceed()是通知目标方法执行用的,

    创建增强配置类

    ```java @Component @Aspect public class AnnotationPointcut { @Before(“execution(public com.kuang.service.UserService.(..))”) public void a(){

     System.out.println("---------方法执行前---------");
    

    }

    @After(“execution( com.kuang.service.UserService.(..))”) public void b(){

     System.out.println("---------方法执行后---------");
    

    }

    @Around(“execution( com.kuang.service.UserServiceImpl.(..))”) public void c(ProceedingJoinPoint jp) throws Throwable {

     System.out.println("环绕前");
     System.out.println("签名:"+jp.getSignature());
     //执行目标方法proceed
     Object proceed = jp.proceed();
     System.out.println("环绕后");
     System.out.println(proceed);
    

    } }

//—————————————————————————————

@Component(“logAopUtils”) @Aspect public class LogAopUtils { //还可以使用@Pointcut定义获取切入点方法。这样其他增强方法可以直接利用该方法获取增强路径 //多个路径时使用 || 连接 //路径可以定义在外部,pointcu传入字符串参数 String url= “@annotation(com.ruoyi.common.security.annotation.RequiresLogin) || “

        + "@annotation(com.ruoyi.common.security.annotation.RequiresPermissions) ";
 @Pointcut("execution(* com.spring.service..*(..))")
//@Pointcut(url)  
public void logs(){}

@Before("logs()")
public void beforePrintLog() {
    System.out.println("方法执行之前,输出日志");
}

}

<a name="Tr5s7"></a>
### 测试

- `@EnableAspectJAutoProxy`会去寻找标注了`@Aspect`的增强类
```java
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
    ...
}
  • 使用xml就是如下配置

    <bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
    <aop:aspectj-autoproxy/>
    

  • 上面的execution(...)一般就2种形式限定

    • 访问权限+类以限制
    • 方法名+[访问权限]限制
    • 这2种方法依旧是大范围杀伤,无法精准锁定某些类的某些方法,这时我们可以指定装配而不是让spring自动将所有符合的方法都进行增强。

  • 使用@Transactional用于被装配的类的方法前,spring通过execution(...)规则后还会检查方法是否有@Transactional注解,有该注解才会进行装配aop
  • 也可以直接用于整个类,表示该类所有方法都允许被装配aop ```java execution(public com.kuang.service.UserService.(..))

@Component public class UserService { //会被增强 @Transactional public User createUser(String name) { … }

//不会被增强
public boolean isValidName(String name){   ...    }

}

@Component

@Transactional //符合execution的方法都会被增强 public class UserService { … }

<a name="u60Bx"></a>
### 将增强方法自定义为注解

1. 自定义一个注解
```java
@Target(METHOD)
@Retention(RUNTIME)
public @interface MetricTime {
    String value();
}
  1. 定义增强配置类

    1. 4,5行即是把自定义注解与增强方法关联在一起,使用自定义注解即相当于调用增强方法
    2. 具体的类时使用**@annotation**,多个路径使用||连接。泛指时使用**execution**。自动接收时必须写注解入参,此时可以直接@annotation(注解对象)

      @Aspect
      @Component
      public class MetricAspect {
      @Around("@annotation(metricTime)")
      public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable {
        String name = metricTime.value();
        long start = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            long t = System.currentTimeMillis() - start;
            // 写入日志或发送至JMX:
            System.err.println("[Metrics] " + name + ": " + t + "ms");
        }
      }
      }
      
      public static final String POINTCUT_SIGN = " @annotation(com.ruoyi.common.security.annotation.RequiresLogin) || "
            + "@annotation(com.ruoyi.common.security.annotation.RequiresPermissions) || "
            + "@annotation(com.ruoyi.common.security.annotation.RequiresRoles)";
      
      /**
      * 声明AOP签名
      */
      @Pointcut(@annotation("com.xx.RequiresLogin"))
      public void pointcut(ProceedingJoinPoint joinPoint)
      {
         MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method xx=signature.method();
        //反射获取方法上的注解
        RequiresLogin requiresLogin = xx.getAnnotation(RequiresLogin.class);
      }
      
  2. 使用自定义注解

    @Component
    public class UserService {
    // 监控register()方法性能:
    @MetricTime("register")
    public User register(String email, String password, String name) {
        ...
    }
    ...
    }
    

    总结

  • springAPI实现即一个增强方法即一个增强类,不同增强类实现不同api接口,然后配置前置是哪个类,后置是哪个类
  • 自定义类即增强方法都写一个类,然后配置前置是哪个增强方法…
  • 注解即类似自定义类:增强方法都写一个类里,但是使用注解替代了xml配置 ```xml ● Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多 ```

spring Aop的两种底层实现

  • spring aop可以利用2种方案实现增强,一种是jdk动态代理,一种是使用cglib
    • jdk动态代理要求被代理对象必须有一个接口;且如果被代理的对象的体系太过于复杂,比如多级实现与继承,jdk动态代理容易出现问题
    • cglib利用了cglib包实现了动态代理。使用cglib需要额外引入cglib包(spring aop包的org.springframework.cglib里面已经包含了cglib,所以可以导入spring aop包的依赖即可)
      • cglib利用的是继承,因此可以避开无接口的问题,不过如果被代理类种有final类,那么final无法被增强
  • spring aop默认的增强实现是jdk动态代理,如果无法通过jdk动态代理实现(即无父接口),则通过cglib实现

    JointPoint,Pointcut

  • JointPoint是程序运行过程中可识别的点,这个点可以用来作为AOP切入点。JointPoint对象则包含了和切入相关的很多信息。比如切入点的对象,方法,属性等。我们可以通过反射的方式获取这些点的状态和信息,用于追踪tracing和记录logging应用信息。

    • 即方法被增强时可以将被增强的方法的一系列信息封装为一个对象
    • 重要的实现类为ProceedingJoinPoint,该类用于增强方式方法时,只能用于@Around
  • Object proceed()切入点调用该方法,貌似会返回被增强方法的返回值,同时使被增强方法继续执行。这貌似是环绕增强才有的方法。
  • 这也是环绕通知和前置、后置通知方法的一个最大区别。简单理解,环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的.

详见链接,本地demo项目的aop也有案例