目录
1. AOP介绍
2. 静态代理:AspectJ介绍
3.反射
4.动态代理(JDK动态代理、CGLIB)介绍
5.小结
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代理,前者基于接口,后者基于子类
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();
}
}
测试结果:
静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理。再者,如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本。那么要如何解决呢?答案是使用动态代理。
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.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 框架的优势:
可调用普通类,不需要实现接口;
高性能;
AspectJ在编译时就增强了目标对象,Spring AOP的动态代理则是在每次运行时动态的增强,生成AOP代理对象。区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。