AOP、AspectJ、JDK动态代理、CGLIB

AOP、AspectJ、JDK动态代理、CGLIB

AOP

  1. AOP(Aspect Orient Programming),作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、日志记录管理等。
  2. AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类。
  3. 其中静态代理是指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成AOP代理类,因此也称为编译时增强;代表框架如:AspectJ
  4. 动态代理则在运行时借助于JDK动态代理、CGLIB 代码生成类库。在内存中“临时”生成AOP动态代理类,因此也被称为运行时增强。

AspectJ

  1. AspectJ其实是一套独立的面向切面编程的解决方案,与Spring没有关联关系。
  2. AspectJ 下载地址(http://www.eclipse.org/aspectj/downloads.php)
  3. 简单demo如下:
public class SayHelloService {
    public void say(){
        System.out.print("Hello  AspectJ");
    }
}

public aspect LogAspect {
    pointcut logPointcut():execution(void SayHelloService.say());
    after():logPointcut(){
         System.out.println("记录日志 ...");
    }
}

执行命令   ajc -d . SayHelloService.java LogAspect.java
生成 SayHelloService.class
执行命令    java SayHelloService
输出:
Hello AspectJ  
记录日志
  1. ajc.exe 可以理解为 javac.exe 命令,都用于编译 Java 程序,区别是 ajc.exe 命令可识别 AspectJ 的语法;我们可以将 ajc.exe 当成一个增强版的 javac.exe 命令;
  2. 执行ajc命令后的 SayHelloService.class 文件不是由原来的 SayHelloService.java 文件编译得到的,该 SayHelloService.class 里新增了打印日志的内容——这表明 AspectJ 在编译时“自动”编译得到了一个新类,这个新类增强了原有的 SayHelloService.java 类的功能,因此 AspectJ 通常被称为编译时增强的 AOP 框架。

JDK动态代理

  1. JDK的动态代理的写法比较固定,需要先定义一个接口和接口的实现类,然后再定义一个实现了InvocationHandler接口的实现类。然后调用Proxy类的newInstance()方法即可。示例代码如下:
//先定义一个接口:UserService,接口中有两个方法
public interface UserService {

    int insert();

    String query();
}

//再定义一个UserService接口的实现类:UserServiceImpl
public class UserServiceImpl implements UserService{

    @Override
    public int insert() {
        System.out.println("insert");
        return 0;
    }

    @Override
    public String query() {
        System.out.println("query");
        return null;
    }
}

//再定义一个InvocationHandler接口的实现类:UserServiceInvocationHandler。
//在自定义的InvocationHandler中,定义了一个属性:target
//定义这个属性的目的是为了在InvocationHandler中持有对目标对象的引用,target属性的初始化是在构造器中进行初始化的。
public class UserServiceInvocationHandler implements InvocationHandler {

    // 持有目标对象
    private Object target;

    public UserServiceInvocationHandler(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invocation handler");
        // 通过反射调用目标对象的方法
        return method.invoke(target,args);
    }
}

//通过Proxy.newProxyInstance()方法创建代理对象
public class MainApplication {
    public static void main(String[] args) {
        // 指明一个类加载器,要操作class文件,怎么少得了类加载器呢
        ClassLoader classLoader = MainApplication.class.getClassLoader();
        // 为代理对象指定要是实现哪些接口,这里我们要为UserServiceImpl这个目标对象创建动态代理,所以需要为代理对象指定实现UserService接口
        Class[] classes = new Class[]{UserService.class};
        // 初始化一个InvocationHandler,并初始化InvocationHandler中的目标对象
        InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl());
        // 创建动态代理
        UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler);
        // 执行代理对象的方法,通过观察控制台的结果,判断我们是否对目标对象(UserServiceImpl)的方法进行了增强
        userService.insert();
    }
}

//输出:
invocation handler
insert
  1. 如何获取这个增强后的代理对象源码呢?通过在main方法增加一行代码:
//jdk8亲测可以
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

//高版本jdk请使用这个
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
  1. 然后运行程序,就会在项目的根目录下生成一个包:com.sun.proxy。包下有一个文件$Proxy0.class。在idea打开,发现就是所产生代理类的源代码,源代码如下:
public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int insert() throws  {
        try {
            return (Integer)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String query() throws  {
        try {
            return (String)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.tiantang.study.UserService").getMethod("insert");
            m4 = Class.forName("com.tiantang.study.UserService").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
  1. 通过源码我们发现,$Proxy0类继承了Proxy类,同时实现了UserService接口,所以JDK只能通过接口去实现动态代理。因为Java中不支持多继承,而JDK的动态代理在创建代理对象时,默认让代理对象继承了Proxy类。
  2. $Proxy0实现了UserService接口,所以重写了接口中的两个方法($Proxy0同时还重写了Object类中的几个方法)。所以当我们调用query()方法时,先是调用到$Proxy0.query()方法,在这个方法中,直接调用了super.h.invoke()方法,父类是Proxy,父类中的h就是我们定义的InvocationHandler,所以这儿会调用到UserServiceInvocationHandler.invoke()方法。因此当我们通过代理对象去执行目标对象的方法时,会先经过InvocationHandler的invoke()方法,然后在通过反射method.invoke()去调用目标对象的方法,因此每次都会先打印invocation handler这句话。
  3. Spring中有挺多功能都是使用的JDK动态代理增强来实现的,如声明式事务@Transactional,声明异步方法@Async。但如果我们用的是this对象来调用相应的方法,没有用代理对象去调用方法。那其实方法是没有得到增强处理的,也就是我们常见的事务失效等场景。

CGLIB

  1. CGLIB(Code Generation Library)它是一个代码生成类库。它可以在运行时候动态是生成某个类的子类。
  2. Jdk代理生成的代理类只有一个,因而其编译速度是非常快的;而由于被代理的目标类是动态传入代理类中的,在执行完代理方法后再通过反射去调用目标对象的方法。所以Jdk代理的执行效率相对来说低一点;
  3. Cglib代理需要为每个目标类生成相应的子类,因而在实际运行过程中,其可能会生成非常多的子类,过多的子类始终不是太好的,因为这影响了虚拟机编译类的效率;但由于在调用过程中,代理类的方法是已经静态编译生成了的(已经在子类中把目标对象的方法进行织入了),因而Cglib代理的执行效率相对来说高一些。
  4. CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM(Java字节码操控框架),来转换字节码并生成新的类。
  5. 具体实现可以参考Spring中ConfigurationClassPostProcessor对带有@Configuration注解的类的增强处理。具体方法路径参考此文章文末:https://blog.csdn.net/ab1024249403/article/details/127329871
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值