Java的代理在很多方面都有所应用,比较有名的就是Spring AOP,底层的实现就是使用的动态代理,Java中动态代理的实现由两种,分别是JDK动态代理和CGlib动态代理,下面就介绍下这两种代理的区别和优缺点。
介绍动态代理之前,先介绍下无代理和静态代理。
无代理
//定义接口
public interface person {
void eat(String food);
}
//实现类
public class PersonImpl implements Person {
@Override
public void eat(String food) {
System.out.println("I eat:"+food);
}
}
//调用
public static void main(String[] args) {
Person person = new PersonImpl();
person.eat("dinner");
}
//打印结果 I eat:dinner
在上面的例子中,如果在吃东西前后加上一些逻辑该怎么办呢?比如吃法前打扫,吃饭后收拾下,当然可以在eat方法中把这些逻辑加上,不过这样牺牲了许多灵活性和拓展性。此外,如果要在其他地方打扫,代码也无法做到复用。
因此,在这里把eat和打扫的代码给分开,不要耦合到一块,这样就是静态代理。
静态代理
看下静态代理的例子
//定义接口
public interface person {
void eat(String food);
}
//实现类
public class PersonImpl implements Person {
@Override
public void eat(String food) {
System.out.println("I eat:"+food);
}
}
//静态代理类
public class staticProxy implements Person{
private Person person;
public staticProxy(Person person){
this.person = person;
}
@Override
public void eat(String food) {
before();
person.eat(food);
after();
}
private void after() {
System.out.println("打扫");
}
private void before() {
System.out.println("收拾");
}
}
//调用
public static void main(String[] args) {
StaticProxy staticProxy = new StaticProxy(new PersonImpl());
staticProxy.eat("dinner");
}
//打印结果
收拾
I eat:dinner
打扫
上面的静态代理中,通过把真正的对象new PersonImpl()委托给同样实现了Person接口的静态代理对象staticProxy,因为实现了同样的接口,所以可以直接调用staticProxy的方法,但是在staticProxy的方法内部实际上调用的是被代理对象的方法,然后代理对象为我们加上了before()和after()的逻辑代码。如果以后要修改逻辑代码,只需要修改下before和after方法即可,此外,如果在其他方法中也需要复用before()和after()方法的话,可以让staticProxy实现其他的接口,然后和这里的eat逻辑一样调用即可。这样就不会影响到eat()方法。
但是也存在着问题,如上面所讲,如果要复用before和after逻辑时,总要实现新的接口,然后再实现该接口的代理方法,但是代理类的调用逻辑总是相似的,为了实现相似的功能而添加了很多包装代码,此外,如果接口发生了改变,不光是被代理对象,代理对象也要跟着修改,这时就引出了下面的动态代理
动态代理
JDK动态代理
public class JDKproxy implements InvocationHandler {
private Object target;
public JDKproxy(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target,args);
after();
return result;
}
private void after() {
System.out.println("JDK打扫");
}
private void before() {
System.out.println("JDK收拾");
}
}
//调用
public static void main(String[] args) {
Person person = new PersonImpl();
JDKproxy jdKproxy = new JDKproxy(person);
Person personproxy = (Person) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
jdKproxy
);
personproxy.eat("dinner");
}
//打印结果
JDK收拾
I eat:dinner
JDK打扫
上面的代码中,通过JDKproxy包装person,然后用JDK的代理工厂方法实例了了一个具体的代理类,最后调用代理的方法。
在这里,因为代理类JDKproxy中保存的是Object,因此无论是什么类,只要实现了特定的接口,都能够使用JDKproxy进行包装,从而使before()和after()方法能够得到复用。
但是JDK的动态代理还是有一个致命的缺点,那就是要代理的类需要实现一个特定的接口,如果被代理的方法不是通过某个接口实现的话,就无法实现代理,下面就引出了CGLib动态代理。
CGLib动态代理
CGLib动态代理是一个类库,可以在运行期间动态生成字节码,从而生成代理类
public class CGLibProxy implements MethodInterceptor {
private static CGLibProxy instance = new CGLibProxy();
private CGLibProxy(){}
public static CGLibProxy getInstance(){
return instance;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o,objects);
after();
return result;
}
public <T> T getProxy(Class<T> tclass){
return (T) Enhancer.create(tclass,this);
}
private void after() {
System.out.println("CGL打扫");
}
private void before() {
System.out.println("CGL收拾");
}
}
//调用
public static void main(String[] args) {
Person person = CGLibProxy.getInstance().getProxy(PersonImpl.class);
person.eat("dinner");
}
//结果
CGL收拾
I eat:dinner
CGL打扫
CGLib动态代理的优点很明显,就是可以为没有实现接口的类的方法进行包装。
但是,JDK的动态代理会拦截并包装实现了接口的方法,但是CGLib的包装是无须提供接口的,因此CGLib是对所有的方法进行包装的。
CGLib的背后是生成了一个被代理对象类的子类,因此被代理对象的final方法无法被包装
Spring AOP实现原理
SpringAOP的底层就是使用了动态代理,JDK动态代理和CGLib动态代理都有使用到。如果被代理类有实现接口,那么使用JDK动态代理,否则使用CGLib动态代理。