浅谈AOP

前言

在JAVA编程中,AOP技术被广泛运用。其中较为熟悉的Spring AOP被大家广泛运用。

AOP底层技术

下面是我们常用AOP技术的。

技术方式控制泛围性能工作原理
直接改写class文件无明显性能代价对 class 文件结构和 Java 字节码《class文件和字节码解析
JDK Instrument(探针)无论是否改写,每个类装入时都要执行 hook 程序通过JVM 提供hook程序拦截每个类装载过程,并对目标类修改
JDK Proxy接口反射引入性能代价通过JVM提供方式,查找到JAVA对象对应的方法的执行地址《Java对象内存表示机制
ASM无明显性能代价通过类操作的方式,对应生成JAVA字节码
Javassist无明显性能代价通过编写类的方式,然后编译成字节码,达到字节码修改的目的

对于上述几种方式的DEMO如下:

  1. 直接改写class文件(略)
  2. JDK Instrument(待续)
  3. JDK Proxy (略)
  4. ASM
public class ASMTest extends ClassLoader {

    public Class<?> defineMyClass( byte[] b, int off, int len)
    {
        return super.defineClass(b, off, len);
    }
    public static void main(String[] args) throws Exception {

        ClassWriter classWriter = new ClassWriter(0);
        // 通过visit方法确定类的头部信息
        classWriter.visit(Opcodes.V1_7,// java版本
                Opcodes.ACC_PUBLIC,// 类修饰符
                "Programmer", // 类的全限定名
                null, "java/lang/Object", null);

        //创建构造函数
        MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();

        // 定义code方法
        MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",
                null, null);
        methodVisitor.visitCode();
        methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
                "Ljava/io/PrintStream;");
        methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");
        methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
                "(Ljava/lang/String;)V");
        methodVisitor.visitInsn(Opcodes.RETURN);
        methodVisitor.visitMaxs(2, 2);
        methodVisitor.visitEnd();
        classWriter.visitEnd();
        // 使classWriter类已经完成
        // 将classWriter转换成字节数组写到文件里面去
        byte[] data = classWriter.toByteArray();
        File file = new File("./target/classes/Programmer.class");
        FileOutputStream fout = new FileOutputStream(file);
        fout.write(data);
        fout.close();
        Class clazz = new ASMTest().defineMyClass(data,0, data.length);
        System.out.println(clazz.getSimpleName());
        Object object = clazz.newInstance();
        Method method1 =  clazz.getDeclaredMethod("code");
        method1.invoke(object);
    }
}
  1. Javassist
public class JavassistTest extends ClassLoader{

    public Class<?> defineMyClass( byte[] b, int off, int len)
    {
        return super.defineClass(b, off, len);
    }

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        //创建Programmer类
        CtClass cc= pool.makeClass("Programmer");
        //定义code方法
        CtMethod method = CtNewMethod.make("public void code(){}", cc);
        //插入方法代码
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
        cc.addMethod(method);
        //保存生成的字节码
        cc.writeFile("./stream/target/classes");


        InputStream input = new FileInputStream("./stream/target/classes/Programmer.class");
        byte[] data = new byte[1024];

        int count = input.read(data);

        Class clazz = new JavassistTest().defineMyClass(data,0, count);
        System.out.println(clazz.getSimpleName());

        Object object = clazz.newInstance();

        Method method1 =  clazz.getDeclaredMethod("code");

        method1.invoke(object);
    }
}

cglib代理技术

从上面分析得。ASM技术,Javassist技术运行速度上明显优于JDK proxy。但操作性远远不如。因此有人整理封装ASM,得到我们的cglib.简单看其通过继承方式的DEMO.

public class CglibTest  {

    static class MyMethodInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
            System.out.println("Before: "+ method);
            Object object = proxy.invokeSuper(obj, arg);
            System.out.println("After: "+method);
            return object;
        }
    }

    static class ElectricCar  {
        public void drive() {
            System.out.println("Electric Car is Moving silently...");
        }

        public void recharge() {
            System.out.println("Electric Car is Recharging...");
        }
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ElectricCar.class);
        enhancer.setCallback(new MyMethodInterceptor());
        ElectricCar student = (ElectricCar)enhancer.create();
        student.drive();
        student.recharge();
    }
}

ps: spring 在 3.20之后,把asm,cglib收于 spring-core中。

aop alliance

aop和java有浓厚兴趣的软件开发人员联合成立的开源项目,主要定义AOP的统一规范。主要提出2概念

  1. Joinpoint 程序在执行某个方法时的连接点
    1. ConstructorInvocation 构造执行点
    2. MethodInvocation 方法执行点
  2. Advice 连接点(Joinpoint)监听器,可以对任意连接点进行增强处理
    1. ConstructorInterceptor 构造执行监听
    2. MethodInterceptor 方法执行监听

ps: aop alliance项目, 被移入 spring-aop中

AspectJ

细化了AopAlliance对Advice的定义。

  1. CutPoint 切入点,指符合要求的连接点
  2. Advice 切入点监听器,可以对切入接点进行增强处理
  3. Aspect 切面,汇聚相关业务切入点形成面

并进一步对Advice细分:

//除了Advice原生的Around事件以外,另加入如下几个切点
try{
    try{
        //@Before
        method.invoke(..);
    }finally{
        //@After
    }
    //@AfterReturning
}catch(){
    //@AfterThrowing
}
AspectJ技术原理

AspectJ对类代理的原理是直接操作字节。但操作字节码分别有以下几种情况。

编译
加载
运行
x.java
x.class
oop
D
  1. 编译阶段
  2. 加载阶段, “classLoader” 和 “Instrument ‘, 又称 加载时编入(Load-Time-Weaving),简称LTW.
  3. 运行阶段, ”Instrument“

针对1阶段给出例子如下

<!--maven 编译插件-->
<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>aspectj-maven-plugin</artifactId>
	<version>1.10</version>
</plugin>
<!--maven 依赖-->
<dependency>
	 <groupId>org.aspectj</groupId>
	 <artifactId>aspectjweaver</artifactId>
	 <version>1.8.1</version>
</dependency>
//引出 aj文件
public aspect MyAspect{
    /**
     * 切入点:SampleServiceImpl所有 public 且以 add 开头的方法
     */
    public pointcut serviceAddMethods(): execution(public * com.aspectJ.aspectJDemo.SampleServiceImpl.add*(..));
 
    Object around(): serviceAddMethods(){
        Object oldValue = proceed();
        System.out.println("add方法执行结果:" + oldValue);
        return Integer.MIN_VALUE;
    }
}

public class SampleServiceImpl {
	public int add(int x, int y) {
		return x + y;
	}
}

public class AppTest {
	public static void main(String[] args) {
		SampleServiceImpl service = new SampleServiceImpl();
		service.add(2, 4);
	}
}

控制台输出结果为:
add方法执行结果:6

查看target/classes会发现如下结果:

//编译aj为class文件
@Aspect
public class MyAspect {
    static {
        try {
            ajc$postClinit();
        } catch (Throwable var1) {
            ajc$initFailureCause = var1;
        }

    }

    public MyAspect() {
    }

    @Around(
        value = "serviceAddMethods()",
        argNames = "ajc$aroundClosure"
    )
    public Object ajc$around$com_aspectJ_aspectJDemo_MyAspect$1$f211abe7(AroundClosure ajc$aroundClosure) {
        Object oldValue = ajc$around$com_aspectJ_aspectJDemo_MyAspect$1$f211abe7proceed(ajc$aroundClosure);
        System.out.println("add方法执行结果:" + oldValue);
        return -2147483648;
    }

    public static MyAspect aspectOf() {
        if (ajc$perSingletonInstance == null) {
            throw new NoAspectBoundException("com_aspectJ_aspectJDemo_MyAspect", ajc$initFailureCause);
        } else {
            return ajc$perSingletonInstance;
        }
    }

    public static boolean hasAspect() {
        return ajc$perSingletonInstance != null;
    }
}

//将代码切入SampleServiceImpl中实现代理
public class SampleServiceImpl {
    public SampleServiceImpl() {
    }

    public int add(int x, int y) {
        return Conversions.intValue(add_aroundBody1$advice(this, x, y, MyAspect.aspectOf(), (AroundClosure)null));
    }
}

针对2阶段给出例子。

<!--maven 依赖-->
<dependency>
	 <groupId>org.aspectj</groupId>
	 <artifactId>aspectjweaver</artifactId>
	 <version>1.8.1</version>
</dependency>
public class SampleServiceImpl {
	public int add(int x, int y) {
		return x + y;
	}
}

public class ClassLoaderTest extends WeavingURLClassLoader {

    public ClassLoaderTest(ClassLoader parent) {
        super(parent);
    }

    public Class defineClass(String name, byte[] b, CodeSource cs) throws IOException {
        return super.defineClass(name,b,cs);
    }

    public static void main(String[] args) throws Exception {
        //例子1中,编译出来MyAspect存入的路径
        System.setProperty("aj.aspect.path", "/Users/moyao/IdeaProjects/AspectJDemo/src/test/java");

        ClassLoaderTest weavingURLClassLoader = new ClassLoaderTest(ClassLoaderTest.class.getClassLoader());

        //取出现在已编译好的原生的SampleServiceImpl class文件
        InputStream input = new FileInputStream("./target/classes/com/aspectJ/aspectJDemo/SampleServiceImpl.class");
        int count = input.available();
        byte[] data = new byte[count];
        input.read(data);

        //向class文件织入代理代码
        Class clazz = weavingURLClassLoader.defineClass("com.aspectJ.aspectJDemo.SampleServiceImpl", data, null);

        //执行add方法
        Object object = clazz.newInstance();
        Method method1 = clazz.getDeclaredMethod("add", int.class, int.class);
        method1.invoke(object, new Object[]{4, 4});
    }
}

控制台输出结果为:
add方法执行结果:8

可见AspectJ可分为2步骤:

  1. 编译aj文件,生成Aspect class文件
  2. 根据Aspect class文件切入代理类

针对”加载“和”运行“阶段的探针(Instrument)例子:
Java SE 6 新特性Instrumentation

主要参考

AOP 的利器:ASM 3.0 介绍
JAVA instrument简单使用
Java动态代理机制详解
javassist使用全解析
Java SE 6 新特性Instrumentation
Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving
Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细讲
AspectJ简单Demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值