代理的好处
代码可以专心业务代码编写,与非业务代码区分开且避免编写过多重复代码,简化代码。比如事务管理、日志打印、接口监控、数据加密解密等。
静态代理
即编译期的代理。即创建一个代理类,代理类中创建被代理类的对象,代理类对外提供方法a调用,方法a中代理类调用被代理类的方法b,并在方法b前后加一些操作,实现代理增强效果。在编译后生成的class文件就已经是代理后的内容。
缺点:不灵活,每加一个方法都要加一个实现。经典的静态代理框架aspectj可以避免这个问题,可以指定切入点的规则,但是需要使用AspectJ 编译器才能编译。
优点:代理代码只需要织入一次,动态代理在每次重新获取新对象需要重新织入新对象。
关于AspectJ的注解,spring也支持,但是是通过动态代理的方式去解析注解的。
字节码增强
字节码增强即在运行阶段对已生成的字节码对象从内存中读取出来,通过一些技术修改字节码对象增强其功能。
原理:
1.在内存中获取原始的字节码,然后通过一些开源项目(ASM,CGBLIb,javassist)等修改它的byte[]数组,得到一个新的byte[]数组。 2.将这个新的byte[]数组写到PermGen区域,加载它或者替换原来的Class字节码。
动态代理
在程序运行时,通过字节码增强技术,为一个类生成代理对象,这个代理对象实现了在被代理对象执行方法前后添加一些操作实现增加,比如为一个对象的方法统一在方法访问前增加权限校验、打日志等。
动态代理的方式:
1. jdk自带的动态代理实现。
通过创建一个代理处理类实现InvocationHandler,在invoke方法中实现方法增强,通过Proxy.newProxyInstance并传入代理处理类的方式返回代理对象,实现动态代理。这种形式jdk原生支持,不用引入外部jar包,且jdk8以上效率比cglib高。缺点是被代理类必须是实现了接口,必须实现接口与jdk动态代理的实现方式有关,由于返回的代理对象要可以强转成被代理类,所以代理对象要么是成为被代理类的子类,要么被代理类改成用接口接收代理类,代理类实现接口从而达到可以强转。jdk自带的动态代理就是通过实现接口的形式,代理类实现接口,因为java只能单继承,代理类本身需要继承Proxy,需要接收invokeHandler和使用proxy中的invoke方法,所以只能通过实现接口获取被代理类的方法信息和实现强转。mybatis就是通过jdk动态代理方式方式实现字节码增强。如下展示了个简单实例。
public interface Say {
Integer say(String a);
}
public class SayHello implements Say {
public Integer say(String a) {
System.out.println(a+" hello");
return 2;
}
}
public class MyHandler implements InvocationHandler {
Object o;
Object bind(Object o){
this.o = o;
return java.lang.reflect.Proxy.newProxyInstance(o.getClass().getClassLoader(),o.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("之前---");
method.invoke(o,args);
System.out.println("之后---");
return 1;
}
}
public class Test {
public static void main(String[] args) {
Say s = (Say) new MyHandler().bind(new SayHello());
s.say("name");
}
}
缺点:如果被代理类实现了多个接口,虽然都会被代理增强,但是返回代理类时只能指定一个接口接收。
2. cglib动态代理
开源的动态代理框架,基于asm框架实现(ASM是一个轻量级的类库,用于动态修改字节码文件,但需要涉及到JVM的操作和指令比较复杂),spring aop采用的代理方式之一。原理通过从内存中加载出被代理类的字节码信息,继承为被代理类的子类实现,不需要被代理类实现了接口。spring通过判断被代理类是否有实现接口,决定使用jdk动态代理还是cglib。
public class CglibProxy implements MethodInterceptor {
private Object object;
public Object bind(Object object){
this.object = object;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib之前--");
Object result = method.invoke(object,objects);
System.out.println("cglib之后--");
return result;
}
}
JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。
缺点:类和方法如果实现了final则无法被继承重写,导致无法代理。
3.Javassist动态代理
Javassist是一个开源的分析、编辑和创建Java字节码的类库,dubbo的动态代理默认就是通过javassist实现的。