1、什么是代理
代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。
例如:假设有一组对象都实现同一个接口,实现同样的方法,但这组对象中有一部分对象需要有单独的方法,传统的笨办法是在每一个应用端都加上这个单独的方法,但是代码重用性低,耦合性高。
如果用代理的方法则很好的解决了这个问题。
我们来看一个例子。
2、无代理
public interface Hello {
void say(String name);
}
上面是Hello接口,下面是实现类
public class HelloImpl implements Hello {
@Override
public void say(String name) {
System.out.println("Hello "+name);
}
}
如果要在println方法前面和后面分别处理一些逻辑,怎么做呢?
在没有代理的模式下,我们只能将这些逻辑写死在say方法里面。
public class HelloImpl implements Hello {
@Override
public void say(String name) {
//println前逻辑
System.out.println("Hello "+name);
//println后逻辑
}
}
3、静态代理
使用静态代理模式,写一个HelloProxy类,让他去调用HelloImpl的say方法,在调用前和调用后分别进行逻辑处理。
public class HelloProxy implements Hello {
private Hello hello;
public HelloProxy() {
hello = new HelloImpl();
}
@Override
public void say(String name) {
before();
hello.say(name);
after();
}
private void after() {
System.out.println("调用方法之前");
}
private void before() {
System.out.println("调用方法之后");
}
}
用HelloProxy实现了Hello接口,并且在构造方法中new了一个HelloImpl的实例。这样我们就可以在HelloProxy的say方法中去调用HelloImpl的say方法。更重要的是,我们还可以在调用前后分别加上before和after方法,在这两个方法里面去实现前后的逻辑。
public static void main(String[] args) {
Hello hello = new HelloProxy();
hello.say("Ron.Zheng");
}
运行结果如下:
调用方法之后
Hello Ron.Zheng
调用方法之前
以上就是静态代理的简单实现方法。
但是这有一个缺陷,在代理用的比较少的项目中,静态代理基本就够用了,但是如果项目中,要用到代理的地方非常多,很明显就会产生非常非常多的代理类。用什么办法可以减少代理类呢,办法就是动态代理。
4、JDK动态代理方案
写一个DynamicProxy。
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target=target;
}
@SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@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("调用方法之前");
}
private void before() {
System.out.println("调用方法之后");
}
}
在DynamicProxy中,定义了一个Object类型的target对象,它就是被代理的目标对象,通过构造函数来初始化。
DynamicProxy类实现了InvocationHandler接口,那么必须实现该接口的invoke方法,在该方法中,直接通过反射去invoke方法,在invoke方法的前后分别处理before和after,最后将result返回。
在DynamicProxy里添加了一个getProxy方法,用于构造并返回代理实例,无需添加任何参数,返回一个泛型类型。
public static void main(String[] args) {
Hello hello = new HelloImpl();
DynamicProxy dynamicProxy = new DynamicProxy(hello);
/*Hello helloProxy = (Hello) Proxy.newProxyInstance(hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(), dynamicProxy);*/
Hello helloProxy = dynamicProxy.getProxy();
helloProxy.say("Ron.Zheng");
}
运行结果
调用方法之后
Hello Ron.Zheng
调用方法之前
当然,如果在DynamicProxy里没有添加getProxy方法,也可以通过以上代码中注释的部分获取代理对象。
用了DynamicProxy以后,接口如果发生变化,动态代理类可以不做任何修改,而静态代理就不一样了,接口变,实现类要动,代理类也要动。
但是在我们需要代理一个没有接口的类时JDK动态代理方案就不能满足需求了,但是CGLib动态代理能解决这个问题。
5、CGLib动态代理
代理为控制要访问的目标对象提供了一种途径。当访问对象时,它引入了一个间接的层。JDK自从1.3版本开始,就引入了动态代理,并且经常被用来动态地创建代理。JDK的动态代理用起来非常简单,但它有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?现在我们可以使用CGLIB包。
CGLIB是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
写一个CGLibSingleProxy
public class CGLibSingleProxy implements MethodInterceptor {
private static CGLibSingleProxy instance = new CGLibSingleProxy();
private CGLibSingleProxy() {
}
public static CGLibSingleProxy getInstance() {
return instance;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> cls) {
return (T) Enhancer.create(cls, this);
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy proxy) throws Throwable {
before();
Object result = proxy.invokeSuper(arg0, arg2);
after();
return result;
}
private void after() {
System.out.println("调用方法之前");
}
private void before() {
System.out.println("调用方法之后");
}
}
CGLibSingleProxy需要实现CGLib给我们提供的MethodInterceptor接口,并实现intercept方法。CGLib给我们提供的是方法级别的代理,也可以理解成方法拦截器。
在intercept方法中,我们需要注意MethodProxy类型的参数,我们直接调用proxy的invokeSuper方法,将被代理的对象arg0以及方法参数arg2传入即可。
public static void main(String[] args) {
Hello helloProxy = CGLibSingleProxy.getInstance().getProxy(HelloImpl.class);
helloProxy.say("Ron.zheng");
}
执行结果:
调用方法之后
Hello Ron.zheng
调用方法之前
注意:使用CGLib动态代理时,需要引入CGLib的jar包,本文使用的是cglib-nodep-2.1_3.jar