代理模式
代理模式:在对已有方法进行改进时,不直接修改原有方法或代码,而是提供第三方。该第三方含有代理角色的引用,并根据需求增加相应的功能。
代理模式在程序设计中有非常重要的应用,AOP就是针对代理的一种应用。在生活中代理也无处不在,比如翻墙,比如开代理打韩服等等。
学习代理模式,先从简单的代码开始。
先定义一个简单的接口
public interface Hello{
void sayHello();
}
再实现一个基本的Hello实现
public class HelloImpl implements Hello{
@override
public void sayHello{
System.out.println("Hello");
}
}
如果要在println方法前分别需要一些处理逻辑,该怎么么做呢?如果是以前的我,我会直接将处理逻辑写死在在HelloImpl的sayHello方法中。但是这样的写法在大多数情况下是不妥的,就好像没学面向对象以前,将所有的方法逻辑写在一个类里面。这样的写法,会使得一个方法越来越长,处理逻辑的实现也会将类变得越来越庞大,而且这样处理逻辑可能与该类的其他方法无关。所以最好的方法是将这些处理逻辑交于代理处理。
对于我们的HelloImpl类,我们写一个HelloProxy类,让该类调用HelloImpl的sayHello方法,并在调用的前后进行相应的逻辑处理。
public class HelloProxy implements Hello{
Hello helloImpl;
HelloProxy(){
helloImpl=new HelloImpl();
}
@Override
public void sayHello(){
before();
helloImpl.sayHello();
after();
}
private void before(){
System.out.println("before");
}
public void after(){
System.out.println("after");
}
}
在这段代码中,我们用HelloProxy实现Hello接口(和HelloImpl实现相同的接口),并且在构造方法中new出HelloImpl实例,则我们可以在HelloProxy的sayHello()方法中调用HelloImpl的sayHello()方法。这样的话,我们就可以在调用前后添加相应的before和after方法,并在这两个方法中去实现相应的前后处理逻辑。
我们再用main方法测试一下
public static void main(String args[]){
HelloProxy helloProxy=new HelloProxy();
helloProxy.sayHello();
}
结果如下
before
hello
after
这个HelloProxy就是简单的代理。这样的代理称为静态代理。所谓静态,就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
这样的静态代理几个缺点:
1. 如果要代理的方法很多,那么静态代理类的规模也势必很大。
2. 如果接口增加一个方法,那么所有代理类也必须实现此方法,如此就增加了代码维护的复杂度
为了解决静态代理的问题,我们使用JDK提供的动态代理方案DynamicProxy:
public class DynamicProxy implements InvocationHandler{
private Object object;
public DynamicProxy(Object object) {
this.object=object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
before();
Object result=method.invoke(object, args);
after();
return result;
}
private void before(){
System.out.println("before");
}
private void after(){
System.out.println("after");
}
}
DynamicProxy实现了InvocationHandler接口,那么必须实现invoke方法,在invoke方法中,直接通过反射去调用被包装类的方法,在调用前后实现 分别处理before和after,最后将result返回。
在main中测试:
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);
helloProxy.sayHello();
}
结果如下:
before
hello
after
在上述代码中,我们用这个通用的DynamicProxy类去包装HelloImpl实例,然后调用JDK给我们提供的Proxy类工厂方法newProxyInstance去动态的创建一个Hello接口的代理类,最后调用这个代理类的sayHello方法。结果和静态代理结果一样。
其实,动态代理就是动态生成XxxProxy。和静态代理相比,动态代理类的源码是在程序运行期间由JVM根据反射等机制动态生成的。
代理类和委托类的关系是在程序运行时确定的,所有如果需要在不同类的不同方法增加相同的处理逻辑,我们只需要一个动态代理类就可以实现,这就是动态代理的优点。
在main方法中我们可以看到,Proxy.newProxyInstance方法要有三个参数:
1. ClassLoader;
2. 该实现类的所有接口
3. 动态代理对象
在调用结束后还需要类型的强制转换。如果我们的动态代理需要代理多个对象,那么上述代码需要重复多次。
为了避免Proxy.newInstance方法出现多次(减少代码量和简化动态代理类的使用),我们将动态代理对象实例化部分代码封装起来(使用泛型):
@SuppressWarnings("unchecked")
public <T> T getProxy(){
return (T)Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(), this);
}
如此一来,我们的DynamicProxy的使用将更加方便,代码也看起来简洁清晰多了:
public static void main(String args[]){
DynamicProxy dynamicProxy=new DynamicProxy(new HelloImpl());
Hello helloProxy=dynamicProxy.getProxy();
helloProxy.sayHello();
}
除了减少代理类这个优点,相对于静态代理,动态代理在接口变化的时候,代理类不需要变化,而静态代理类则需要相对应的改动。
但是,我们发现,上述JDK动态代理只能代理有接口的类,如果需要代理的类没有接口,那么JDK动态代理就无用武之地。
为了解决代理没有接口的类的问题,我们使用开源的CGLIB类库。
public class CGLIBProxy implements MethodInterceptor{
private static CGLIBProxy instance=new CGLIBProxy();
private CGLIBProxy() {
}
public static CGLIBProxy getInstance(){
return instance;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T>cls){
return (T)Enhancer.create(cls, this);
}
@Override
public Object intercept(Object target, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
before();
Object result=proxy.invokeSuper(target, args);
after();
return result;
}
private void before(){
System.out.println("before");
}
private void after(){
System.out.println("after");
}
}
从上述代码可以看出,CGLIB类库的使用和JDK动态代理类似。但是CGLIB类库可以代理没有接口的类,且速度相较于JDK DynamicProxy更快。
测试使用及运行结果如下:
public class Greet {
public void say(String str){
System.out.println("hello"+str);
}
public static void main(String args[]){
Greet greetProxy=CGLIBProxy.getInstance().getProxy(Greet.class);
greetProxy.say("shan");
}
}
运行结果:
before
helloshan
after