理解Spring中的对象代理模式,对后续的AOP理解非常重要!
代理解决的问题:当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类(proxy)即可,而且代理的出现还可以让我们完成与另一个类(委托类,也称为目标类target)之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口(在Cglib中,代理类是委托类的子类),因为代理真正调用的还是委托类的方法。
静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经被编译存在了。
动态代理:在程序运行时运用反射机制动态创建代理对象。
一、 静态代理
通过静态代理,可以一窥代理模式的主要功能:
public interface IUserDao {
public void save();
}
public class UserDao implements IUserDao {
@Override
public void save() {
System.out.println("Save data into database!");
}
}
public class UserDaoProxy implements IUserDao {
private IUserDao target;
public UserDaoProxy(IUserDao targer){
this.target = targer;
}
@Override
//代理的核心方法:首先代理可以通过自己的成员变量(target)访问目标对象,实际是为了调用目标对象的核心方法save()
//然后,由于实现的同一个接口,可以在代理对象中对原有对象的方法进行扩展,而不是直接去修改目标对象,这样做有几个好处:
//1.符合开闭原则;2.一个目标对象可以有不同的代理,实现不同的功能扩展;3。便于维护和代码复用
public void save() {
System.out.println("***Begin transaction!");
target.save();
System.out.println("***Commit transaction!");
}
}
public class App {
@Test
public void testProxy() throws Exception {
IUserDao target = new UserDao();
IUserDao proxy = new UserDaoProxy(target);
proxy.save();
}
}
输出:
***Begin transaction!
Save data into database!
***Commit transaction!
代理对象需要实现与目标对象(也可以称为委托对象)相同的接口,以此来保证核心方法是一样的(而关注点不一样-参考下一篇AOP博客),但是代理对象可以对核心方法进行扩展。首先代理对象需要能够访问(实际上是包含关系)目标对象,这样可以在代理对象中执行目标对象的核心方法,然后将关注点(例如事物)交给代理类来编写。
这是符合面向对象的设计模式的开闭原则的(Open for Extension,Close for Modification,OCP),对类的改动通过增加代码来进行!
如前所述,所谓静态代理也就是在程序运行前就已经存在代理类的字节码文件,代理类和目标类的关系在运行前就确定了。 因此这种代理方式的缺点是显而易见的:需要手动实现目标对象的接口,造成代理类太多(实实在在的生成了代理类的字节码文件);如果接口中有多个方法,例如增删改查四个方法,需要在代理类中一一处理,即使处理内容是一样(例如,都是事务处理);一旦接口修改,目标类和代理类都要进行维护。因此,我们考虑使用代理工厂来创建代理对象,也就是动态代理。
二、 动态代理(重点理解)
利用JDK的API动态构建代理类,需要指定接口,所以动态代理也叫JDK代理或者接口代理。代理对象不需要实现接口(只需要指定),而是在运行期间通过反射动态地在内存中创建实现接口的对象,并不存在实现接口的代理类的字节码文件!
参考JDK反射包下的Proxy类的静态方法,获取代理对象
需要的参数:
目标类加载器ClassLoader;
目标对象实现的接口Class< ?>(可能不止一个);
调用处理器InvocationHandler;
具体步骤是:
a. 实现InvocationHandler接口创建自己的调用处理器
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
Object invoke(Object proxy, Method method, Object[] args)
其中调用处理器需要理解一下:执行目标对象核心方法时,会触发该调用处理器,并且核心方法会被当做参数传入,然后在调用处理器中可以灵活调用该方法(method.invoke())并完成代理对象对目标对象方法的扩展(通过反射实现,理解需要了解反射概念)。这里method默认指的是接口中所有的方法,也就是说,这个调用处理器默认对目标对象的所有核心方法进行代理。如果希望只对某个方法代理(过滤),可以对method进行判断。
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), //委托对象的类加载器
target.getClass().getInterfaces(), //委托对象实现的接口
//调用处理器,当代理对象执行委托对象的核心方法时,执行的是这个代理方法,而不是委托对象的方法!
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("***Begin transaction!***");
Object returnValue = method.invoke(target, args);
System.out.println("***Commit transaction!***");
return returnValue;
}
});
}
}
这里有个细节需要注意,在调用处理器中也需要执行目标对象的方法,因此需要在全局维护一个目标对象,或者在创建代理类的时候通过参数传入一个目标对象(当希望getProxyInstance()是一个静态方法时)
public class App {
@Test
public void testProxy() throws Exception {
IUserDao target = new UserDao();
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
proxy.save();
}
}
如果通过getClass获取代理类的类名
System.out.println(proxy.getClass());
输出:class $Proxy0
类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
动态代理依然有局限,因为目标对象必须要实现接口,因此引出了cglib代理。
参考网上的评价:
优点:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。Spring AOP配置外围业务的思想源于此。
美中不足:
诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy(其中需要实现InterfaceA B C)。Java 的继承机制注定了这些动态代理类们无法实现对一般类(class)的动态代理,原因是多继承(让代理对象再去继承一个一般类)在 Java 中本质上就行不通。
有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。
三、 Cglib代理
以子类的方式实现方法的扩展,显然,这样打破动态代理只能代理接口实现类的局限性,唯一的局限性只是类不能修饰为final(核心方法为final或者static时,代理对象不起作用,方法不会被拦截)。被广泛地应用于AOP框架,Spring中会根据目标类是否实现接口来选择是使用动态代理还是Cglib代理生成代理对象。
AOP(AspectOrientedProgramming):将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码—解耦。
1. jar包
需要引入cglib的支持jar包,spring-core中已经包含了:org.springframework.cglib。
2. 创建子类代理工厂类,实现MethodIntercept接口
public class ProxyFactory implements MethodInterceptor {
private UserDao target;
public ProxyFactory(UserDao target){
this.target = target;
}
//提供获取代理对象的方法
public Object getProxyInstance(){
//创建工具类
Enhancer enhancer = new Enhancer();
//设置需要代理的父类
enhancer.setSuperclass(target.getClass());
//设置回调函数,回调核心方法
enhancer.setCallback(this);
return enhancer.create();
}
//拦截的方法
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("Begin transaction!!!");
//调用委托对象的代理方法
Object returnValue = method.invoke(target, args);
System.out.println("Commit transaction!!!");
return returnValue;
}
}
3. 使用代理类
public class App {
@Test
public void testname() throws Exception {
UserDao target = new UserDao();
ProxyFactory fac = new ProxyFactory(target);
UserDao proxyInstance = (UserDao) fac.getProxyInstance();
proxyInstance.save();
}
}
在Spring的AOP编程中:
如果加入容器的目标对象由实现接口,用动态代理;
如果没有实现接口,用Cglib代理;