有所耳闻的反射、AOP、AspectJ、JDK Proxy、CGLIB到底是什么?之间有什么关系?

目录

1. AOP介绍

2. 静态代理:AspectJ介绍

3.反射

4.动态代理(JDK动态代理、CGLIB)介绍

5.小结


1. AOP介绍

AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。

 AOP的基本概念:

(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知

(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用

(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around

(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式

(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类


AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。


2. 静态代理:AspectJ介绍

AspectJ是静态代理的增强,所谓的静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。

静态代理模式:静态代理说白了就是在程序运行前就已经存在代理类的字节码文件,代理类和原始类的关系在运行前就已经确定。废话不多说,我们看一下代码,为了方便阅读,博主把单独的class文件合并到接口中,读者可以直接复制代码运行:


package test.staticProxy;

// 接口

public interface IUserDao {

void save();

void find();

}

//目标对象

class UserDao implements IUserDao{

@Override

public void save() {

System.out.println("模拟:保存用户!");

}

@Override

public void find() {

System.out.println("模拟:查询用户");

}

}

/**

静态代理

特点:

1. 目标对象必须要实现接口

2. 代理对象,要实现与目标对象一样的接口

*/

class UserDaoProxy implements IUserDao{

// 代理对象,需要维护一个目标对象

private IUserDao target = new UserDao();

@Override

public void save() {

System.out.println("代理操作: 开启事务...");

target.save(); // 执行目标对象的方法

System.out.println("代理操作:提交事务...");

}

@Override

public void find() {

target.find();

}

}

测试结果:

       

静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理。再者,如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本。那么要如何解决呢?答案是使用动态代理。

3.反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。

1、获取类(Class)对象

获取类对象有三种方法:

  • 通过forName() -> 示例:Class.forName("PeopleImpl")

  • 通过getClass() -> 示例:new PeopleImpl().getClass()

  • 直接获取.class -> 示例:PeopleImpl.class

2、类的常用方法

  • getName():获取类完整方法;

  • getSuperclass():获取类的父类;

  • newInstance():创建实例对象;

  • getFields():获取当前类和父类的public修饰的所有属性;

  • getDeclaredFields():获取当前类(不包含父类)的声明的所有属性;

  • getMethod():获取当前类和父类的public修饰的所有方法;

  • getDeclaredMethods():获取当前类(不包含父类)的声明的所有方法;

3、类方法调用

反射要调用类中的方法,需要通过关键方法“invoke()”实现的,方法调用也分为三种:

  • 静态(static)方法调用

  • 普通方法调用

  • 私有方法调用

3.1 静态方法调用

// 核心代码(省略了抛出异常的声明)

public static void main(String[] args) {

   Class myClass = Class.forName("example.PeopleImpl");

   // 调用静态(static)方法

   Method getSex = myClass.getMethod("getSex");

   getSex.invoke(myClass);

}

静态方法的调用比较简单,使用 getMethod(xx) 获取到对应的方法,直接使用 invoke(xx)就可以了。

3.2 普通方法调用

普通非静态方法调用,需要先获取类示例,通过“newInstance()”方法获取,核心代码如下:

Class myClass = Class.forName("example.PeopleImpl");

Object object = myClass.newInstance();

Method method = myClass.getMethod("sayHi",String.class);

method.invoke(object,"老王");

getMethod 获取方法,可以声明需要传递的参数的类型。

3.3 调用私有方法

调用私有方法,必须使用“getDeclaredMethod(xx)”获取本类所有什么的方法,代码如下:

Class myClass = Class.forName("example.PeopleImpl");

Object object = myClass.newInstance();

Method privSayHi = myClass.getDeclaredMethod("privSayHi");

privSayHi.setAccessible(true); // 修改访问限制

privSayHi.invoke(object);

除了“getDeclaredMethod(xx)”可以看出,调用私有方法的关键是设置 setAccessible(true) 属性,修改访问限制,这样设置之后就可以进行调用了。

4.动态代理(JDK动态代理、CGLIB)介绍

4.1 JDK动态代理

JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

4.2 CGLIB动态代理

如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类。注意:CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

4.3 JDK Proxy VS Cglib

JDK Proxy 的优势:

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,更加可靠;

  • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版上能够使用;

Cglib 框架的优势:

  • 可调用普通类,不需要实现接口;

  • 高性能;

5.小结

AspectJ在编译时就增强了目标对象,Spring AOP的动态代理则是在每次运行时动态的增强,生成AOP代理对象。区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

猜你喜欢

转载自blog.csdn.net/m0_38109046/article/details/89449474