很多框架的技术原理都涉及到java动态链模式和责任链模式,比如spring和mybatis。
代理,顾名思义,代为处理。先来看看名词解释:
代理就是定义一个行为和某类(class)的对象相似,而又潜在地表示了所有继承自该类的对象的东西,我们把这种类的对象叫做代理(surrogate)
来源代理(百度百科)。
来看一下最常用的代理技术:
jdk代理技术是java.lang.reflect
包提供的方式,它必须通过一个接口才能产生代理对象,所以必须先创建对象,所以先对接口进行定义。
public interface HelloWorld{
void sayHelloWorld();
}
然后我们来创建一个是实现类:
public class HelloWorldImpl implements HelloWorld{
@override
public void sayHelloWorld(){
System.out.println("Hello World!");
}
}
实现动态代理,分成两步走,第一步,实现接口java.lang.reflect.InvocationHandler
,它有一个invoke方法,用于提供接口数组用于下挂代理对象
public class JdkProxyExample implements InvocationHandler{
//真实对象
private object target = null;
/**
*建立代理对象和真实对象的代理关系,并返回对象
*@param target 真实对象
*@return 代理对象
*/
public object bind(Object target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterface(),this);
/**
*代理对象逻辑
*@param proxy 代理对象
*@param method 当前调度方法
*@param args 当前方法参数
*@return 代理结果返回
*@throws Throwable 异常
*/
@override
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
System.out.println("进入代理逻辑方法");
System.out.println("在调度真实对象之前的服务");
Object object = method.invoke(target,args);
System.out.println("在调度真实对象之后的服务");
return object;
}
}
第一步建立对象和代理对象之间的联系,也就是使用了bind方法用如下语句
Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterface(),this);
生成代理对象,通过目标类的类加载器,和目标类的挂在接口。用当前对象this作为代理逻辑方法的现实方法,它必须实现InvocationHandler接口的invoke方法。
第二步实现代理逻辑方法。invoke方法可以实现代理逻辑。
这样我们进行HelloWorld proxy= (HelloWorld)jdk . bind(new HelloWorldImpl());
生成的就是proxy代理对象,调用方法proxy . sayHelloWorld();
就会输出:
进入代理逻辑方法
在调度真实对象之前的服务
Hello World
在调度真实对象之后的服务
这样通过代理,我们做到了什么事情呢?
你可以看到,我们并没有修改HelloWorldImpl类的sayHello()方法,但是执行出来的代理方法增添了新的内容!如果把目标对象比喻成棒棒糖,那么,代理操作就是在糖外面再裹了一层糖浆!
下面来讲代理的另一种实现方式。
上面讲的JDK代理,必须要有挂在的接口,也就是说目标的被代理对象必须要有实现的接口。
如果目标对象没有接口,是一个普通类怎么办呢?
我们可以用CGLB技术。CGLB使用抽象类来实现代理。来个简单的例子:
public class SampleClass {
public void test(){
System.out.println("hello world");
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before method run...");
Object result = proxy.invokeSuper(obj, args);
System.out.println("after method run...");
return result;
}
});
SampleClass sample = (SampleClass) enhancer.create();
sample.test();
}
}
如果把两个类实现同一个接口的这种行为,看作是当兄弟,那么一个类继承另一个类的行为可以被看作当儿子所以JDK的代理和CGLB的差别就是当兄弟和当儿子的区别(都是针对目标对象而言)。
CGLB的代理实际上是用Enhance对象调用setSuperclass()设置目标类为父类,setCallback()为实际执行的逻辑方法,然后返回代理对象。
动态代理比较难理解,所以设计者设计了一个拦截器,实现代理,这样开发人员就不需要关心动态代理是怎么实现的。
一般来说,拦截器是这样的
public interfa ce Interceptor {
public boolean before (Obje ct pr , oxy , Object target , Method method , Object []
args) ;
public void around(Object proxy , Object target , Method method , Object[]
args) ;
public void after(Object proxy, Object target, Method method , Object []
args) ;
}
拦截器的作用就是他的名字,拦截。把代理看作一种拦截行为。
回头看代理的逻辑实现,可以分为,原方法执行前,原方法执行,原方法执行后。三个部分。当然原方法实际上是可以不执行的。
而拦截器实际上就是把这几个部分拿出来并且加工了一下。
before()在原方法执行前执行,返回boolean,为true是调用原方法,false调用around()方法,之后调用after()方法。
这样依赖,我们就只能看见拦截器,而看不见内部的动态代理了。
也就是说设计者提供了接口,封装了内部原理,而开发人员负责实现接口,提供实际逻辑
上面说到,设计者会用拦截器去替换动态代理,提供对应的接口给开发者,从而降低开发难度。
但是拦截器可以有很多个,比如生病报销,需要将材料一层层的上报。也像是捕鱼,有的鱼篓有好几节长。
当一个对象在一条链上被多个拦截器拦截处理(当然也可以存在拦截器,而不做任何处理)时,我们把这样的设计模式称之为责任链模式。
内部也就会有多个代理对象。
这时候你可能就知道了,联想到之前的,我们说代理就是在棒棒糖(原对象方法)上裹了一层糖,那么责任链就是按照顺序裹了一层有一层。类比请假:
而拦截的方法执行就像是从中穿过的一条线的顺序:
这样看是不是像穿过了一个裹了一层有一层的糖衣的棒棒糖呢?
意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。