一、静态代理
代理概念:
目标对象类型的变量指向代理对象,调用方法的时候执行代理对象的方法。
代理对象的方法里面调用了目标对象的方法并在方法前后添加了一些功能。
代码部分:
创建一个Animal接口,写一个call方法
public interface Animal {
void call();
}
创建一个Cat类去实现Animal接口的方法
public class Cat implements Animal{
@Override
public void call() {
System.out.println("喵喵喵");
}
}
创建代理类对象,和被代理对象一样继承Animal接口
public class ProxyCat implements Animal{
//被代理类对象(cat)
private Animal animal;
public ProxyCat(Animal animal) {
this.animal = animal;
}
@Override
public void call() {
begin();
animal.call();
end();
}
public void begin(){
System.out.println("猫饿了");
}
public void end(){
System.out.println("猫吃饭");
}
主函数入口
public static void main(String[] args) {
ProxyCat proxy = new ProxyCat(new Cat());
proxy.call();
}
执行结果
猫饿了
喵喵喵
猫吃饭
Process finished with exit code 0
静态代理的缺点:
代理类和被代理类都需要去继承Animal接口,代理类通过调用被代理类的方法进行静态代理的实现(期间可以添加一些功能),但静态代理也有比较明显的不足之处:
1.当我们在 Animal 接口中增加方法,这时不仅实现类 Cat 需要新增该方法的实现,同时,由于代理类实现了 Animal 接口,所以代理类也必须实现 Animal 新增的方法,这对项目规模较大时,在维护上就不太友好了。
2.这个代理类是针对Cat目标类的对象进行设置的,如果再需要添加 Dog 目标类的代理,那就必须再针对 Dog 类实现一个对应的代理类,这样就使得代理类的重用型不友好,并且过多的代理类对维护上也是比较繁琐。
二、为什么要是用动态代理
动态代理类与静态代理类最主要不同的是,代理类的字节码不是在程序运行前生成的,而是在程序运行时在虚拟机中程序自动创建的。其实动态代理与静态代理的本质一样,最终程序运行时都需要生成一个代理对象实例,通过它来完成相关增强以及业务逻辑,只不过静态代理需要硬编码的方式指定,而动态代理则是以动态方式生成代理(有编译时操作字节码生成的方式、运行时通过反射、字节码生成的式)。
动态生成的好处很明显,代理逻辑与业务逻辑是互相独立的,没有耦合,代理1个类或100个类要做的事情没有任何区别。
三、JDK动态代理
Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
代码部分:
创建一个Animal接口,写一个call方法
public interface Animal {
void call();
}
创建一个Cat类去实现Animal接口的方法
public class Cat implements Animal{
@Override
public void call() {
System.out.println("喵喵喵");
}
}
被代理类作为参数传给代理对象
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyCat implements InvocationHandler {
//被代理对象
private Object obj;
public ProxyCat(Object obj) {
this.obj = obj;
}
public Object getProxy(){
//获取被代理对象的类加载器
ClassLoader classLoader = obj.getClass().getClassLoader();
//获取被代理对象实现的接口
Class<?>[] interfaces = obj.getClass().getInterfaces();
//第一个参数:被代理对象的类加载器
//第二个参数:被代理对象实现的接口
//第三个参数:使用产生代理对象调用方法时,用于拦截方法执行的处理器
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, this);
return proxy;
}
public void begin(){
System.out.println("JDK猫饿了");
}
public void end(){
System.out.println("JDK猫吃饭");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
begin();
//执行method方法(被代理对象的call方法)
Object invoke = method.invoke(obj);
end();
return invoke;
}
主函数入口:
public static void main(String[] args) {
//创建被代理对象
Cat cat = new Cat();
//创建动态代理对象,并将被代理对象传递到代理对象类中赋值给obj
ProxyCat proxyCat = new ProxyCat(cat);
//proxy就是产生的代理对象,产生的代理对象可以强转成被代理对象实现的接口类型
Animal proxy = (Animal) proxyCat.getProxy();
//使用代理对象调用方法,并不会执行调用的方法
//而是进入到代理对象指定的InvocationHandler类中的invoke方法
//调用的方法作为一个method参数传给invoke方法
proxy.call();
}
执行结果:
JDK猫饿了
喵喵喵
JDK猫吃饭
Process finished with exit code 0
四、CJLIB动态代理
CGLIB 动态代理的实现机制是生成目标类的子类,通过调用父类(目标代理类)的方法实现,在调用父类方法时再代理中进行增强。
实现 MethodInterceptor 接口
相比于 JDK 动态代理的实现,CGLIB 动态代理不需要实现与目标类一样的接口,而是通过方法拦截的方式实现代理。
public class CgLibCat implements MethodInterceptor {
//传入被代理对象
private Object obj;
public CgLibCat(Object obj) {
this.obj = obj;
}
public Object getProxy(){
Enhancer enhancer = new Enhancer();
//设置被代理类作为父类
enhancer.setSuperclass(obj.getClass());
//设置拦截器
enhancer.setCallback(this);
//创建被代理类
Object proxy = enhancer.create();
return proxy;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
begin();
//这个objects是代理类调用方法(call方法)的时候传入进来的参数
//有参数就写objects没有就可以不写
Object result = method.invoke(obj,objects);
end();
return result;
}
public void begin(){
System.out.println("CJLIB猫饿了");
}
public void end(){
System.out.println("CJLIB猫吃饭");
}
public static void main(String[] args) {
Cat cat = new Cat();
CgLibCat cgLibCat = new CgLibCat(cat);
//proxyCat是Cat的子类
Cat proxyCat =(Cat) cgLibCat.getProxy();
proxyCat.call();
}
运行结果:
CJLIB猫饿了
喵喵喵
CJLIB猫吃饭
Process finished with exit code 0
五、JDK动态代理和CJLIB动态代理的比较
JDK动态代理和CJLIB动态代理的区别
1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类
2、Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的。
什么时候用JDK动态代理,什么时候用CJLIB动态代理
1、目标对象生成了接口 默认用JDK动态代理
2、如果目标对象使用了接口,可以强制使用cglib
3、如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换
Cglib和JDK的效率比较
1、cglib底层是ASM字节码生成框架,但是字节码技术生成代理类,在JDL1.8之后比使用java反射的效率要低
2、Cglib不能对声明final的方法进行代理,因为cglib是动态生成代理对象,final关键字修饰的类不可变只能被引用不能被修改
Spring如何选择是用JDK还是cglib
1、当bean实现接口时,会用JDK代理模式
2、当bean没有实现接口,用cglib实现
3、可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class=”true”/>)