代理模式总结及入职第一天感受
入职心情
题图是笔者的工位,今天是入职第一天,很轻松的氛围,心情也很好。
果然大公司有大公司的气魄。规范的流程还有完善的文档,尤其是每一个实习生都有一个指导人,亦师亦友亦同事,给人一种很沉稳乐观的感觉。
没有很好奇也没有紧张,或许是因为之前已经有过类似的经历吧,所以这次就以一种踏实的心态面对工作生活。
第一天没有很多活儿,所以就又研究了一下设计模式,这里就对今天学的代理模式做一个小结。
什么是代理模式
代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。现实世界的代理人被授权执行当事人的一些事宜,
无需当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信。而事实上代理人是要有当事人的授权,
并且在核心问题上还需要请示当事人。
就好像打官司的时候请一个律师,起草文案发言都由他来代理,但最终的发言权还是属于原告。
在软件开发中,代理分为静态代理和动态代理两类。
静态代理
首先定义一个接口
public interface Hello {
public void say(String name);
}
接着定义这个接口的实现类
public class HelloImpl implements Hello {
@Override
public void say(String name) {
System.out.println("hello " + name);
}
}
为了在执行具体操作之前或者之后进行一些其他的操作,我们定义一个代理类并实现上述接口
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("after");
}
private void before() {
System.out.println("before");
}
}
解释
我们用HelloProxy实现了Hello接口并在构造方法中new出一个HelloImpl的实例。
这样就可以在HelloProxy的say方法中去调用HelloImpl的say方法了。
更重要的是我们可以在调用的前后分别添加before和after方法,这两个方法去实现那些前后逻辑。
用一个main方法测试一下
public class Main {
public static void main(String[] args) {
Hello helloProxy = new HelloProxy();
helloProxy.say("snowalker");
}
}
运行结果
before
hello,snowalker
after
上述代码即是代理模式的静态实现。之所以称之为静态是因为前置代码和后置代码都“写死”在代理类中了,这样每当需求更改的时候,
代理类都需要修改。因此我们就产生了一个疑问,能否动态的生成代理而不必每次都修改代码生成新的代理类呢?
这个问题早已被Java解决了。
在Java中有一个InvocationHandler接口即是用来解决我们的问题的。
编写一个动态代理类DynamicProxy
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
public DynamicProxy() {
}
@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("after");
}
private void before() {
System.out.println("before");
}
//获取代理对象
@SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
}
在DynamicProxy中,定义了一个Object类型的target变量,这就是被代理的目标实例,通过构造方法注入。
实现接口的invoke方法,接受三个参数,
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调用真实对象的某个方法的Method对象
args: 指代的是调用真实对象某个方法时接受的参数
这里将target对象的方法进行执行
为了方便,定义了一个泛型方法,该方法返回代理对象,接受三个参数
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException
loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,
如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
编写main方法
public class Main {
public static void main(String[] args) {
DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl());
Hello helloProxy = dynamicProxy.getProxy();
helloProxy.say("snowalker1");
}
}
这里,我们将需要代理的类HelloImpl通过构造方法注入到DynamicProxy中,并通过newProxyInstance生成了代理对象实体,
最后调用say方法时,执行了invoke方法并将实际的参数注入其中,输出的结果和上述相同。
这就是所谓的动态代理。
不过在实际开发中,我们一般不使用JDK的动态代理而是使用第三方的类库去做这项工作,比如CGLIB。
原因之一在于JDK动态代理只能代理有接口的类,而遇到没有任何接口的类就束手无策了,
而CGLIB可以在运行时动态生成字节码,亦即动态生成类。包括Spring和Hibernate在内的框架都使用了CGLIB这个工具。
更多
我们在上文提到了代理模式可以在执行被代理对象本身的方法的同时添加新的逻辑,比如在之前做准备,
在运行之后做清理等,这就引出一个新的概念–AOP。
实际上,在AOP中提到的前置增强,就类似于我们的before方法,后置增强就类似于after方法,
所谓的环绕增强就是同时具备before和after两种。
这么一说就好理解多了,这个话题在之后的文章里会专门去说。本文就不展开论述了。
小结
不管怎样,紧张也好期盼也好,工作生活正式开始了。