什么是代理?
代理是使用代理类去调用被代理类的一种方式。代理分为静态代理和动态代理。
动态代理在java中的实现有两种方式,后面讲到。
通俗点讲,就像蓝绿大厂的手机销售店!我们通过销售店去买手机,而销售店的手机也是来自于厂商的。
为什么要使用代理
在不改变目标对象方法的情况下对方法进行增强,比如,我们希望对方法的调用增加日志记录,或者对方法的调用进行拦截(AOP也是如此),
等等...(例如我们对于第三方jar包的功能进行增强和添加日志信息)
静态代理
有一个Game接口,有三个方法
public interface Game {
public void play();
}
有一个GameImpl类实现Game接口(被代理的类)
public class GameImpl implements Game{
@Override
public void play() {
System.out.println("play");
}
}
设计静态代理类
public class GameProxy implements Game{
private GameImpl gameImpl;
public GameProxy(GameImpl gameImpl) {
this.gameImpl = gameImpl;
}
@Override
public void play() {
System.out.println("调用前的操作");
gameImpl.play();
}
}
测试类
GameImpl gameImpl = new GameImpl();
GameProxy gameProxy = new
GameProxy(gameImpl);
gameProxy.play();
结果:
调用前的操作
play
综上可以看出静态代理就是代理类的内部调用被代理类的相关操作,并且可以添加更多的操作(如“调用前的操作”)可以很明显看出是对于现有的类的方法进行增强。
但是实际的操作中,会出现要是存在更多的类需要做相同的功能,就比如所有实现Game接口的类都必须在调用play方法前调用System.out.println("调用前的操作")。那么静态代理就需要产生多个实现类的代理才能满足相对应的实现类。岂不是很麻烦
这个时候我们就需要了解动态代理了:
动态代理
还是针对刚才的接口和被代理的类,我们现在需要实现代理类,还是直接看代码:
实现动态代理的类iPorxyGame继承JDK的InvocationHandler接口
public class DynPorxyGame implements InvocationHandler{
//
private Game game;
public DynPorxyGame(Game game) {
this.game = game;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke" + method.getName());
return method.invoke(game, args);
}
}
测试类:
//动态代理
DynPorxyGame dynPorxyGame = new DynPorxyGame(gameImpl);
Game game = (Game)Proxy.newProxyInstance(gameImpl.getClass().getClassLoader(),
gameImpl.getClass().getInterfaces(), dynPorxyGame);
System.out.println(game.getClass());
//调用方法时dynPorxyGame会执行invoke方法
game.play();
结果:
class com.sun.proxy.$Proxy0
invokeplay
play
如此,一个简单的动态代理就实现了。但是看起来在动态代理类中,没有对接口的方法统一添加前提操作。
所以和静态代理的想法相同,对于扩充操作放在invok里面。
如需要对于不同的方法进行不同的操作也可以通过反射实现。
修改后的动态代理类的:
public class DynPorxyGame implements InvocationHandler{
private Game game;
public DynPorxyGame(Game game) {
this.game = game;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke " + method.getName());
if("play".equals(method.getName())) {
System.out.println("检查play");
}
if("view".equals(method.getName())) {
System.out.println("检查view");
}
return method.invoke(game, args);
}
}
结果:
class com.sun.proxy.$Proxy0
invoke play
检查play
play
invoke view
检查view
view
所以看到现在我们就明白了,动态代理就是利用了反射机制,让我们对于接口的方法在执行的前后可以进行个性化的操作。(这块就是和spring的AOP是一样的,其实AOP利用的就是动态代理的基础,这里的切面就是统一的切入口)
那么对于动态代理,我们只能够去使用接口定义的方法,如果一个类没有使用接口,就不能使用动态代理的模式。那么当然有解决之道。那就是CGlib
两者的相同和不同点
JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
具体的实现
1.还是像JDK代理一样,CGlib需要继承提供的接口MethodInterceptor,同样的需要实现一个方法,这个方法是在每一次的函数调用的时候执行,而我们就可以使用这个特性对不同的方法进行相关的切面编程。在这个里面的方法是intercept。代理生成类CGlibPorxy.Java
public class CGlibPorxy implements
MethodInterceptor{
private Object targetObject;
public CGlibPorxy(Object targetObject) {
this.targetObject = targetObject;
}
//给目标对象获取代理对象
public Object getPorxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetObject.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object var1, Method
method, Object[] var3, MethodProxy var4) throws
Throwable {
if("htxeian".equals(method.getName())){
System.out.println("123");
}
if("play".equals(method.getName())){
System.out.println("456");
}
return null;
}
}
2.测试
public static void main(String[] args) {
//被代理类
TTT2 t = new TTT2();
GameImpl gameImpl = new GameImpl();
CGlibPorxy cGlibPorxy = new
CGlibPorxy(t);
gameImpl =
(GameImpl)cGlibPorxy.getPorxyInstance();
System.out.println(gameImpl.getClass());
//生成的代理对象和原本对象不是一个实例,那么对于代理对象的方法实现其实都是执行的invoke方法。
//如果invoke里面没有操作,那就是对于动态代理什么也不做。
gameImpl.play();
}
结果:
class com.htxeian.example.TTT2$$EnhancerByCGLIBffae2e52
456
可以看出实际生成的代理实现类的类型还是TTT2类,也就是说对于传入对象生成的代理对象是同一类型。而对于JDK的动态代理生成的则是com.sun.proxy这个相当于是接口的实现类,跟实现是同一个等级,如果此时使用实现类强制转换,肯定会报错(com.sun.proxy.$Proxy0 cannot be cast to),这里就不展示了。
总结
1.JDK代理使用的是反射机制实现aop的动态代理,CGLIB代理使用字节码处理框架asm,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,cglib创建效率较低,执行效率高;
2.JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLIB则使用的继承机制,具体说被代理类和代理类是继承关系
3.对于生成动态代理的两种方式,旨在生成代理类,我们根据代理类去进行操作。代理类执行方法会走想对应的处理方法invoke或者intercept