设计模式之代理模式

在java的项目中经常用到代理模式,它不是什么高深的技术,只是一种解决问题的思路。

代理模式的由来

       程序来源于生活,前贤的总结总是那么精辟。在生活中处处可以见到代理模式的影子。如明星和经纪人与业务方(广告商,导演),租房客和中介与房东,大姑娘和媒婆与小伙子。发现它们的规律了吗?参与方总是有三方,通过中间商赚差价...呸,通过中间商提供沟通的桥梁,不用亲力亲为,还能享受额外的服务,例如经纪人除了告诉明星关于导演广告商提供的信息,还能提前筛选收集资料,分析可行性。中介除了可以给租客介绍房子带看房,还能给介绍房子的相关朝向,过户流程之类的额外服务。

其实总结下来就是两点

  1.不用为一些事情投入过多精力,亲力亲为

  2.通过中间人,能获得额外的服务和信息

转到程序里面就是

 1.有业务需要调用外部接口,内部或者外部可能经常变更的时候,可以采用代理模式,内部业务不需要动,只改代理类就行了。这样代码无侵入性,无耦合

 2.代理能在接口调用前后做额外工作,如记录调用时间,次数,能数据校验,拦截等

代理模式的实现方式

 1.静态代理

 2.动态代理

    a. cglib方式

    b.jdk方式

静态代理是比较容易理解的,其结构不外乎,创建一个类,里面持有被用者的引用对象,并提供一个方法,供使用者调用。需要调用的时候,找代理类就行,你不用关心他怎么实现的

动态代理其实就是静态代理类,只不过是由jvm通过反射(asm)动态生成一个匿名代理类.class文件.接下来可以了解一下它们的使用与要点。

前置需求

有一个下班接口,有方法go,需要在下班前下班后做点事情

interface OffWork{
void go();
}

 有一个实现类

public class Employee implements OffWork {
    public  Employee(){}
    private String name;

    public Employee(String name) {
        this.name = name;
    }

    @Override
    public void go() {
        System.out.println("员工:"+name+"下班了");
    }
}

 


 

静态代理

优点:结构简单,实现也简单,一目了然

缺点:项目中有大量代理类,结构一致,代码重复,导致项目包文件过大。新增接口麻烦,接口变动代理类也得跟着变动,就算有工具可以生成代理类,也得替换和检查

 1 public class StaticProxy {
 2     public StaticProxy(OffWork e){
 3         this.work=e;
 4     }
 5    private OffWork work;
 6     public void go(){
 7         System.out.println("下班前准备,好像需要关电脑...");
 8         work.go();
 9         System.out.println("下班路上顺便买个菜");
10     }
11 }

 


动态代理-cglib方式

步骤:

 1.创建代理类并实现MethodInterceptor 接口

2.持有调用者引用,并提供一个create方法,返回匿名代理类

3.在调用方法前后做额外工作before,after

 1 public class CglibProxy implements MethodInterceptor {
 2     private Object target;
 3 
 4     public Object create(Object target) {
 5         this.target = target;
 6         Enhancer enhancer = new Enhancer();
 7         enhancer.setSuperclass(target.getClass());
 8         enhancer.setCallback(this);
 9         return enhancer.create();
10     }
11 
12     @Override
13     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
14         before();
15         Object res=   method.invoke(target, objects);
16         after();
17         return res;
18     }
19     private void before(){
20         System.out.println("执行之前找点事情干干");
21     }
22     private void after(){
23         System.out.println("完成之后收工");
24     }
25 }

 

 注意!划重点

以上cglib方式只能用于无参构造,。

下面是有参构造,注意他们之前的区别

 1 public class CglibProxy implements MethodInterceptor {
 2     private Object target;
 3 
 4     public Object create(Object target) {
 5         this.target = target;
 6         Enhancer enhancer = new Enhancer();
 7         enhancer.setSuperclass(target.getClass());
 8         enhancer.setCallback(this);
 9         return enhancer.create();
10     }
11     public <T> T getInstance(T target,Class[] args,Object[] argsValue){
12         Enhancer enhancer = new Enhancer();
13         enhancer.setSuperclass(target.getClass());
14         enhancer.setCallback(this);
15         return (T) enhancer.create(args,argsValue);
16     }
17     @Override
18     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
19         before();
20         Object res=    methodProxy.invokeSuper(o, objects);
21         /**被注释的是无参构造用法**/
22        //   Object res=method.invoke(target, objects);
23         after();
24         return res;
25     }
26     private void before(){
27         System.out.println("执行之前找点事情干干");
28     }
29     private void after(){
30         System.out.println("完成之后收工");
31     }
32 }

 

 

动态代理-jdk方式

在java.lang.reflect包下面InvocationHandler接口 

步骤:

1.创建代理类并实现InvocationHandler接口

2.创建成员变量,用来存储调用者的引用

3.提供create接口,通过方法注入,给成员变量赋值,返回匿名代理类

4.在invoke方法中调用接口,并添加额外业务

 1 public class DynamicProxy implements InvocationHandler {
 2     private Object target;
 3 
 4 
 5     @Override
 6     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 7         before();
 8         Object res=  method.invoke(target,args);
 9         after();
10         return res;
11     }
12     public Object create(Object t ){
13         target=t;
14         return    Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
15     }
16     private void before(){
17         System.out.println("执行之前找点事情干干");
18     }
19     private void after(){
20         System.out.println("完成之后收工");
21     }
22 }

 

调用

 1 public class MainTest {
 2     public static void main(String[] args) {
 3         /**静态代理方式**/
 4         OffWork employee=  new Employee("张一山");
 5         new StaticProxy(employee).go();
 6         System.out.println("==========================");
 7         /**jdk动态代理方式**/
 8         DynamicProxy   dynamicProxy=   new DynamicProxy();
 9         OffWork employee1=(OffWork) dynamicProxy.create(employee);
10         employee1.go();
11         System.out.println("==========================");
12         /**cglib动态代理方式**/
13         CglibProxy cglibProxy=new CglibProxy();
14         /**无参构造方式**/
15         OffWork offWork= (OffWork)cglibProxy.create(employee);
16         offWork.go();
17         System.out.println("==========================");
18         /**有参构造方式**/
19         OffWork s=  cglibProxy.getInstance(employee,new Class[]{String.class},new Object[]{"张一山"});
20        s.go();
21     }
22 }

 

 

 

 

 输出结果

下班前准备,好像需要关电脑...
员工:张一山下班了
下班路上顺便买个菜
==========================
执行之前找点事情干干
员工:张一山下班了
完成之后收工
==========================
执行之前找点事情干干
员工:null下班了
完成之后收工
==========================
执行之前找点事情干干
员工:张一山下班了
完成之后收工

 

 分析

我们常用的动态代理jdk方式和cglib 有什么不同呢,如何选择?

这就要聊到它们的实现方式上了。jdk动态代理和cglib动态代理。两种方法的存在,各有各自的优势。

jdk方式 InvocationHandler

jdk动态代理是由java内部的反射机制来实现的

特点:

    jdk反射机制在生成类的过程中比较高效,在执行时效率较低

优点:无依赖,直接利用jdk反射

缺点:被代理的类必须实现接口方法,否则无法编译通过,无法使用

 

cglib方式 MethodInterceptor

cglib动态代理底层则是借助asm来实现。

特点:生成代理类的过程中比较慢,但生成后使用速度快

优点:

    被代理类无需实现接口,普通类就可以了,没有局限性

 缺点:有外部依赖,不过目前已经被封装到spring-core.jar中

 

相比较来说,cglib的使用范围更广泛,更通用一点

 总结

    动态代理给我们程序添加了无数的可能,或者可以这样说,动态编译+反射+动态代理 给java程序带来了很多奇思妙想,spring的核心也是在这里,利用它实现了容器的加载,方法的增强,ioc 与aop 都是通过它们实现的。

posted @ 2019-03-08 15:36  井传红  阅读(266)  评论(0编辑  收藏  举报