在看狂神讲Spring AOP的时候,又顺便讲到了代理模式。
静态代理
静态代理模式还是比较好理解的。就是在客户
和服务商
之间,架设一个中间商
。原先的逻辑是:客户
直接调用服务商
。现在是:客户
调用中间商
,间接调用服务商
。这样的改动,中间商
赚了差价,但是,好处是中间商
可以帮我们做一些额外的工作。
动态代理
如果这样去理解:JVM在运行期间动态生成代理类。所以,就叫动态代理,所以就好,那就差点意思了。
我们在这里从结果去理解动态代理,就容易不明就里。我们不妨从静态代理的问题开始推演。
-
客户
调用中间商
,这一步貌似必不可少,跑不掉了,也貌似没有可以优化的余地,先按下不表。 -
中间商
持有服务商
属性,并且调用服务商
的方法,这一步,其实分为两块。
1.中间商
持有服务商
属性,这是中间商
起作用的必要步骤,貌似也不能省。
2.中间商
根据客户
具体调用,做额外的工作,间接调用服务商
。如果这里的额外工作特别通用,比如打印日志,那么,就会非常繁琐,而且还不利于复用。这里,我们的直觉是,可不可以把这些特别通用的代码提取出来,封装在一个方法中,类似于一个拦截器,每次中间商的调用,都要经过这个拦截器,处理完我们的额外工作之后,再调用服务商
。
package com.keqing.staticproxy;
public class ServiceProxy implements Service{
private ServiceProvider serviceProvider;
public ServiceProxy(ServiceProvider serviceProvider) {
this.serviceProvider = serviceProvider;
}
@Override
public void add(Object obj) {
System.out.println("日志-调用了方法:"+"add");
serviceProvider.add(obj);
}
@Override
public void delete(Integer id) {
System.out.println("日志-调用了方法:"+"delete");
serviceProvider.delete(id);
}
@Override
public void update(Object obj) {
System.out.println("日志-调用了方法:"+"update");
serviceProvider.update(obj);
}
@Override
public void query() {
System.out.println("日志-调用了方法:"+"query");
serviceProvider.query();
}
}
经过上面的分析。问题就水落石出了。
静态代理面临的问题:我们需要在
中间商
调用服务商
这个过程中,切入一个拦截器方法,处理通用业务逻辑。
jdk给我们提供了一整套方案。这套方案是这样的。
- 首先定义一个拦截器,并实现拦截方法。这个拦截器需要持有
服务商
属性,用来在拦截方法中真正的去调用服务商
提供的功能。
public class ServiceProxy implements InvocationHandler{
private Service service;
public ServiceProxy(Service service) {
this.service = service;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("此处打印日志。。。。。");
return method.invoke(service, args);
}
}
- 其次,我们该如何切入到原来的调用链条中呢?在java语言中都是对象调用方法,原来的
中间商
已经没有修改的余地了。所以,我们不得不改造中间商
这个对象。为了保证原来中间商
调用服务商
的方法的逻辑不变。中间商
就必须实现服务商
同样的接口才行,或者继承服务商
。然后,中间商
要做的事情就是:进入一个统一的入口:拦截器,然后再由拦截器分发调用请求。到这里,中间商
的作用已经高度抽象化了。
唯一需要变化的就是中间商
需要实现我们的自定义接口,或者继承服务商
,这是为了客户
方便调用。否则在客户
调用的过程中,是没办法调用到具体的方法的。这里,jdk选用了实现接口的方案,也是符合面向接口的编程思想。当然,也是可以继承服务商
,只是jdk没有这么去做而已,并不是什么好多人说的java是单继承多实现这个原因造成的。这不过是不同的方案选择而已。说到后面,大家应该会更深刻的理解。
高度抽象化了的中间商
的结构是这样的:继承服务商
或者实现接口,每个方法中,都是先调用我们的拦截器,然后调用服务商
。高度抽象的中间商
自然就不用我们去开发了,jdk就帮我们代劳了。就像下面这样:
Service serviceProvider = new ServiceProvider();
ServiceProxy serviceProxy = new ServiceProxy(serviceProvider);
//jdk的动态代理,实现动态创建代理类。
Object proxyObj = Proxy.newProxyInstance(
serviceProvider.getClass().getClassLoader(),
serviceProvider.getClass().getInterfaces(),
serviceProxy
);
Service proxy = (Service)proxyObj;
proxy.query();
jdk通过我们传入的接口
,拦截器
这两个参数,就动态创建出来了我们需要的中间商
。与静态代理相比。我们少了自己创建中间商
这一步,这还不是最关键的。这只是我们解决问题路上的一个附带结果,我们不是真的为了省略中间商
而来,回头看,我们为啥要改造静态代理。我们需要切入一个实现通用业务逻辑的拦截器,然而这个拦截器的切入又需要改造中间商
。改造之后的中间商
又是高度抽象化的,可以由jdk来代劳。
所以,最终,我们解决了我们的问题,也砍掉了中间商
。
我想再说一下,jdk生成的动态代理类,这个类,如果我们去看jdk源码的话,底层是要用到字节码的技术,动态的先按照固定模板拼凑出来实现了接口的代理类,然后,编译为class文件,加载到内存中。了解到这一步,我们自然而然的就想到了,我们可以动态的拼凑一个继承了服务商
的代理类。这里根本就没有要让服务商
去继承的任何动机,就不会违反java单继承的原则。所以,我才说,jdk动态代理实现必须拥有接口,是方案选择的不同而已,是面向接口编程思想的体现而已。根本不是某些人说的java是单继承多实现造成的。cglib库的存在也很好的说明了这个问题。
总结
上面的代理模式,我们作为开发者需要做什么工作,我们处于什么位置。如果在学习设计模式的过程中,我们很容易昏了头。往往感觉很多设计模式都是雷同的。
所以,我慢慢感觉到,在学习涉及模式的时候,我们在利用这个设计模式的时候,是处于怎样的一个位置,我们需要做哪些开发工作。
就拿代理模式来说
- 在日常的使用中,
服务商
是现成的。我们需要利用动态代理需要实现拦截器,需要在客户
调用的地方,改造代码。 - 而在Spring框架中,我们是配置AOP注解,实现我们的拦截器的功能。但是,我们不需要改造
客户
调用的代码,Spring帮我们完成了。所以,Spring是帮我们封装了。我们明明是在使用动态代理,但是确实无感的。
你看,同一个设计模式,我们所处的位置不同,需要做的开发工作也是不同的,感受也是不同的。
我们在学习涉及模式的时候,不要总认为,我们就是那个client
。其实不是这样的,我们在这个设计模式中的角色是不固定的。另外,client
也不是最关键的。设计模式的核心思想就是以不变应万变。我们要关注的是那些要时常变化的部分。有些场景,client
变化很重要也很频繁,所以会形成一个模式;有些场景,我们根本不关心client
,关心的是,调用链条中间的那一部分,所以会形成一个模式;有些场景,我们关心的是最终的结果部分,所以会形成一个模式。
这整个调用链条的业务没有变,所以很多设计模式看起来很相似。但是,我们关心的,或者说时常变化的节点不同,所以说每个设计模式都不同。
网友评论