AOP实践-Spring AOP与Aspectj

目录

一、对比

二、spring-aop

三、aspectj

1、编译期织入(无lombok)

(1)基于Intellij编译

(2)基于maven编译

2、编译后织入(解决lombok冲突)


一、对比

关于两者差异分析的博客很多,不展开分析了,直接从实践了解2个直观的差异:.class文件、运行时被代理对象的类型。

  spring-aop aspectj
实现代理的方式

动态代理:

运行时织入

静态代理:

① 编译期织入:把切面类和目标类放在一起用ajc编译;

② 编译后织入:已生成.class文件或打成jar包,再做增强处理;

③ 类加载时织入:在jvm加载目标类的时候,做字节码的替换

.class文件(idea查看)

包含ajc编译产生的内容,以Capitalist类为例:

 

AOP实践-Spring AOP与Aspectj_第1张图片

与对应的.java文件内容相同,以Capitalist类为例:
AOP实践-Spring AOP与Aspectj_第2张图片

运行时被代理对象的类型 AOP实践-Spring AOP与Aspectj_第3张图片 AOP实践-Spring AOP与Aspectj_第4张图片

以下介绍如何实现spring-aop、aspectj代理。先强调一点,并不是使用了@Aspect、@Pointcut注解就意味着使用的是aspectj代理,这只是spring框架借助aspectj的注解标识bean。

二、spring-aop

demo项目结构如下:

AOP实践-Spring AOP与Aspectj_第5张图片

切面类:

package com.yeleits.test.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MonitorAspect {

    @Pointcut("@annotation(com.yeleits.test.aspect.Monitor)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        String className = jp.getSignature().getDeclaringType().getSimpleName();
        String methodName = jp.getSignature().getName();
        long start = System.currentTimeMillis();
        try {
            return jp.proceed();
        } catch (Throwable e) {
            throw e;
        } finally {
            long duration = System.currentTimeMillis() - start;
            System.out.println(className + "." + methodName + "执行时间:" + duration + "ms");
        }
    }
}
package com.yeleits.test.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor {
}

业务相关(2个类、1个接口,分3个文件):

package com.yeleits.test.pojo;


import com.yeleits.test.aspect.Monitor;

public class Capitalist {

    private int assets = 999999999;

    @Monitor
    public void work() {
        System.out.println("$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $");
    }

    public int getAssets() {
        return assets;
    }

}

public interface Person {
    void work();
}

public class Worker implements Person {

    @Monitor
    @Override
    public void work() {
        System.out.println("* * * * * 搬砖 * * * * *");
    }
}

执行逻辑: 

package com.yeleits.test;

import com.yeleits.test.pojo.Capitalist;
import com.yeleits.test.pojo.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args){
        ApplicationContext ac = new ClassPathXmlApplicationContext("aopannotation.xml");
        Person worker = (Person) ac.getBean("worker");
        worker.work();
        Capitalist capitalist = (Capitalist) ac.getBean("capitalist");
        capitalist.work();
    }
}

spring配置文件:




	
	
	
	

pom.xml:



    4.0.0

    com.yeleits.test
    aop-aspectj
    1.0-SNAPSHOT

    
        
            org.springframework
            spring-context
            5.3.2
        
        
            org.springframework
            spring-beans
            5.3.2
        
        
            org.springframework
            spring-aop
            5.3.2
        

        
            org.aspectj
            aspectjweaver
            1.7.4
        
    

执行结果:

* * * * * 搬砖 * * * * *
Person.work执行时间:1ms
$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $
Capitalist.work执行时间:13ms
资产:999999999

demo实现的功能是:对被@Monitor标注的方法实现耗时监控。

tips:MainApp中如果使用BeanFactory而不是ApplicationContext,不会实现代理,这是因为ApplicationContext启动时会扫描实现BeanPostProcessor接口的bean(参考:spring系列4-aop的实现-二-2)

三、aspectj

1、编译期织入(无lombok)

(1)基于Intellij编译

基于上述demo代码,增加两步配置:

第1步:配置编译器 第2步:配置facets
AOP实践-Spring AOP与Aspectj_第6张图片 AOP实践-Spring AOP与Aspectj_第7张图片

清除之前编译的target文件夹,重新运行:

* * * * * 搬砖 * * * * *
Worker.work执行时间:0ms
$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $
Capitalist.work执行时间:0ms
Capitalist.work执行时间:0ms
资产:999999999

可以看出切面起作用了,而且基于aspectj的capitalist代理的性能远快于基于cglib的spring-aop。但是capitalist类的work时间统计了两次,似乎是aspectj编译器的bug(参考https://blog.csdn.net/u011116672/article/details/63685340)。更换切点表达式,用非注解方式实现,不会出现问题:

@Pointcut("execution(* *.work(..))")
public void pointCut() {
}

tips1:采用非注解、注解的切面表达式,产生的MainApp.class文件不同,所以导致2次执行。

tips2:为什么spring-aop方式织入切面,worker和capitalist实际类型不同?简而言之,因为worker实现了接口类,是基于JDK的动态代理机制实现代理;capitalist没有实现接口,采用cglib方式实现代理。(参考:spring系列4-aop的实现)

(2)基于maven编译

基于上述demo代码,增加插件aspectj-maven-plugin:


    
        
            org.codehaus.mojo
            aspectj-maven-plugin
            1.10
            
                1.8
                1.8
                1.8
            
            
                
                    
                        compile
                        test-compile
                    
                
            
        
    

执行mvn clean compile,根据执行日志、生成的.class文件可以看出,成功完成了切面织入

AOP实践-Spring AOP与Aspectj_第8张图片

2、编译后织入(解决lombok冲突)

(1)引入lombok依赖,并使用:


    org.projectlombok
    lombok
    1.16.20
package com.yeleits.test.pojo;

import com.yeleits.test.aspect.Monitor;
import lombok.Data;

@Data
public class Capitalist {

    private int assets = 999999999;

    @Monitor
    public void work() {
        System.out.println("$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $");
    }
}
package com.yeleits.test;

import com.yeleits.test.pojo.Capitalist;
import com.yeleits.test.pojo.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args){
        ApplicationContext ac = new ClassPathXmlApplicationContext("aopannotation.xml");
        Person worker = (Person) ac.getBean("worker");
        worker.work();
        Capitalist capitalist = (Capitalist) ac.getBean("capitalist");
        capitalist.work();
        System.out.println("资产:" + capitalist.getAssets());
    }
}

(2)尝试编译

基于Intellij编译报错:

Error:(16, 0) ajc: The method getAssets() is undefined for the type Capitalist

基于maven编译报错:

AOP实践-Spring AOP与Aspectj_第9张图片

(3)增加aspectjrt依赖、修改插件aspectj-maven-plugin配置:


    org.aspectj
    aspectjrt
    1.8.9

    
        
        
            org.codehaus.mojo
            aspectj-maven-plugin
            1.10

            
                1.8
                1.8
                1.8
                true
                true
                ignore
                UTF-8
                true
                
            
            
                
                    default-compile
                    process-classes
                    
                        compile
                    
                    
                        
                            ${project.build.directory}/classes
                        
                    
                
                
                    default-testCompile
                    process-test-classes
                    
                        test-compile
                    
                    
                        
                            ${project.build.directory}/test-classes
                        
                    
                
            
        
    

执行mvn clean process-classes,根据执行日志、生成的.class文件可以看出,成功完成了切面织入

AOP实践-Spring AOP与Aspectj_第10张图片

为了通过idea执行编译后的.class文件,在run或者debug前,可以先删除运行前编译,避免idea错误地编译覆盖:

AOP实践-Spring AOP与Aspectj_第11张图片

 

 

你可能感兴趣的:(随笔,aop,lombok)