Java AOP的底层原理

为了避免文章过长,针对AOP的理解分为两篇,第一篇介绍Spring AOP的具体实现,本篇将从AOP在Java中的具体实现记录。


Java实现AOP的底层原理

AOP的实现方式主要有三种:

第一种,JVM本身提供了动态代理组件,可以通过它任意对象的代理模式,在处理代理的过程中可以插入切面的逻辑1

首先编写Operator和OperatorImpl

package zmqc.iceyung.aoptool;

public interface Operator {
    int getAllStudentNum();
}
package zmqc.iceyung.aoptool;
public class OperatorImpl implements Operator {
    @Override
    public int getAllStudentNum() {
        return 2;
    }
}

整个程序的运行过程中,原始为:

Operator operator = new OperatorImpl();
operator.getAllStudentNum()

若想将该类的操作添加我们想要的内容,不去改变其编码,而是采用动态代理的方式,在运行该业务代码的时候执行我们想要的操作。
首先创建我们自己的InvocationHandler,我们想添加的具体操作,在该类的invoke中进行:

package zmqc.iceyung.aoptool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyHandler implements InvocationHandler {

    private Object targetObject;
    public Object getTargetObject() {
        return targetObject;
    }
    public void setTargetObject(Object targetObject) {
        this.targetObject = targetObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(targetObject ,args);
        System.out.println("invoke " + targetObject.getClass());
        return result;
    }

}

创建代理工厂:

package zmqc.iceyung.aoptool;
import java.lang.reflect.Proxy;

public class ProxyFactory {
    public static Object getProxy(Object targetObject,
                                  MyHandler handlers) {
        Object proxyObject = null;
        if (handlers != null) {
        
            //传入目标object
            proxyObject = targetObject;
            handlers.setTargetObject(proxyObject);
            
            proxyObject = Proxy.newProxyInstance(targetObject.getClass()
                    .getClassLoader(), targetObject.getClass()
                        .getInterfaces(), handlers);
            return proxyObject;
        } else {
            return targetObject;
        }
    }
}

测试:

package zmqc.iceyung.aoptool;

public class ProxyTest {

    public static void main(String[] args){
        //声明业务类实例
        Operator operator = new OperatorImpl();
        //使用代理工厂生成业务类的代理实例
        Operator operatorProxy = (Operator)ProxyFactory.getProxy(operator,new MyHandler());
        //运行
        int num = operatorProxy.getAllStudentNum();
        System.out.println("student num is "+ num);
    }
}

运行结果为:

invoke class zmqc.iceyung.aoptool.OperatorImpl
student num is 2

若要实现doAfer,doBefor等参考1

第二种,对Java字节码进行重新编译,将切面插入字节码的某些点和面上,可以使用cglib库来实现

package zmqc.iceyung.aoptool;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CglibTest implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    public Object getProxy(Class clazz){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("前置代理");
        //通过代理类调用父类中的方法
        Object result = methodProxy.invokeSuper(o, objects);

        System.out.println("后置代理");
        return result;
    }
    public static void main(String[] args) {
        CglibTest proxy = new CglibTest();
        //通过生成子类的方式创建代理类
        OperatorImpl operator = (OperatorImpl)proxy.getProxy(OperatorImpl.class);
        int num = operator.getAllStudentNum();
        System.out.println("学生人数为:"+ num);
    }

}

输出为:

前置代理
后置代理
学生人数为:2
operator:class zmqc.iceyung.aoptool.OperatorImpl$$EnhancerByCGLIB$$268278e0

可以看到,此时的operator实例为EnhancerByCGLIB,OperatorImpl为其父类,等于是增强了OperatorImpl的功能,添加了一些功能代码。2

第三种,定制类加载器,在类加载的时,对字节码进行补充,在字节码中插入切面。Java的agent就是在加载类字节码的时候,通过增加切面来实现AOP的3 4

根据引文中的代码进行编写:
下面为测试类,用来使用我们自定义的加载器加载,注意将该类放到某个目录中,用javac命令编译,切乎直接放到项目中,这样项目若是编译了该类,默认可能直接使用的是ClassLoader,即已经加载过了,不会再重新加载

public class Test {
    public int getAllStudentNum(){
        System.out.println("getAllStudentNum");
        return 2;
    }
}

自定义加载器:

package zmqc.iceyung.aoptool;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {
    //加载类的目录
    private String classpath;

    public MyClassLoader(String classpath) {

        this.classpath = classpath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            //获取类的字节码
            byte [] classDate = getDate(name);

            if(classDate!=null){
                //defineClass方法将字节码转化为类
                return defineClass(name,classDate,0,classDate.length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }
    //返回类的字节码
    private byte[] getDate(String className) throws IOException{
        InputStream in = null;
        ByteArrayOutputStream out = null;
        //拼接字节码的地址和名称
        String path = classpath + File.separatorChar +
                className.replace('.',File.separatorChar)+".class";

        try {
            in = new FileInputStream(path);
            out = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int len = 0;
            while((len=in.read(buffer))!=-1){
                out.write(buffer,0,len);
            }
            return out.toByteArray();
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        finally{
            in.close();
            out.close();
        }
        return null;
    }
}

测试加载器的加载效果

package zmqc.iceyung.aoptool;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException,
            SecurityException, IllegalArgumentException, InvocationTargetException {
        //自定义类加载器的加载路径
        MyClassLoader myClassLoader = new MyClassLoader("E:\\CodeLibrary\\Java\\iceyung");
        //包名+类名
        Class c = myClassLoader.loadClass("Test");

        if(c!=null){
            Object obj = c.newInstance();
            Method method = c.getMethod("getAllStudentNum", null);

            method.invoke(obj, null);
            System.out.println(c.getClassLoader().toString());
        }
    }
}

最终的运行结果:

getAllStudentNum
zmqc.iceyung.aoptool.MyClassLoader@27c170f0

可以看出加载器已经改成我们自己的了,那么如何在加载这个类的时候,去自定义一些我们的切面程序呢?

可以通过在字节码加载的时候进行添加,Java的agent也是类似的技术,本文已经比较多了,若有时间将继续探讨关于字节码增强的内容。详情可以看引文35


此外还有AspectJ,其是静态代理的增强。所谓的静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强
例如:ajc -d . Hello.java TxAspect.aj 详细见引文6

本文的代码在Spring AOP的具体实现中进行扩充,地址为:
https://gitee.com/iceyung/SpringAOPDemo.git /aoptool


  1. 手把手教你用Java实现AOP, https://blog.csdn.net/GarfieldEr007/article/details/80615537,https://github.com/debjava/aopusingjdkdynamicproxy ↩︎ ↩︎

  2. Java实现AOP的两种方式, https://blog.csdn.net/feigeswjtu/article/details/78741245 ↩︎

  3. Java探针-Java Agent技术,https://www.cnblogs.com/aspirant/p/8796974.html ↩︎ ↩︎

  4. Java类加载机制及自定义加载器,https://www.cnblogs.com/gdpuzxs/p/7044963.html ↩︎

  5. Java学习之javassist,https://www.cnblogs.com/sunfie/p/5154246.html ↩︎

  6. Java AOP的底层原理,https://blog.csdn.net/spring_lws/article/details/81031564 ↩︎

发布了89 篇原创文章 · 获赞 28 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/iceyung/article/details/88568038