设计模式(二)代理模式
博客专区 > centrald 的博客 > 博客详情
设计模式(二)代理模式
centrald 发表于5个月前
设计模式(二)代理模式
  • 发表于 5个月前
  • 阅读 32
  • 收藏 0
  • 点赞 0
  • 评论 0

标题:腾讯云 新注册用户域名抢购1元起>>>   

摘要: 代理简单讲就是代替被代理对象去执行一系列操作。代理在服务架构中是一个常见的机制,通过squid,varnish,nginx等开源软件可以很方便的提供给web一个反向代理,极大的提高web网站的访问效率。同样的,在java spring框架中的aop也是通过动态代理的方式去实现的。

静态代理:

大家经常会使用代理服务器去翻墙,这就是一个常见的正向代理过程。先以翻墙的过程为例,给出一个java例子。

(1) AccessInterface.java

public interface AccessInterface {

    void accessGoogle();


}

(2)PersonalComputer.java

/**
* 被代理类
*/
public class PersonalComputer implements AccessInterface {
    @Override
    public void accessGoogle() {
        System.out.println("access google.com");
    }
}

(3)HongkongService.java

public class HongkongService implements AccessInterface {

    private AccessInterface accessInterface;

    public HongkongService(AccessInterface accessInterfase) {
        this.accessInterface = accessInterfase;
    }

    public void accessGoogle(){

        //控制真实行为
        accessInterface.accessGoogle();
    }

}

(4)GoogleService.java

public class GoogleService {

    public static void main(String[] args) {
        AccessInterface myComputer = new PersonalComputer();

        HongkongService hongkongService = new HongkongService(myComputer);
        //通过代理对象去访问google
        hongkongService.accessGoogle();
    }
}

结果:access google.com

由以上demo结果可以看出模拟出了通过香港的服务器去访问google,实际上自己的个人电脑也访问到了google.   
    通过代理的小例子可以总结出java中实现代理的特点:

  • 代理类与被代理类要实现同一个接口.
  • 代理类中需持有被代理对象.
  • 在代理类中调用被代理的行为。

        通过以上几点总结可以看出代理方式也是有局限性的。比如现在我的个人电脑需要去访问某个学校的内网,这个时候就需要能访问学校内网的服务器去做代理了(不使用vpn的方式)。按照以上总结出的实现代理的特点,这个时候就需要再去写一个schoolservice的代理类,并且在访问时需要将myCompute作为构造方法的参数传入等。假如需要多个代理类,通过java的代理方式去操作,是极为繁琐与重复的。
      而动态代理恰好可以解决这个问题,它不再是对单一的类型进行代理, 而是可以对任意的一个实现了接口的类的对象做代理。实际上实现aop的动态代理方式有两种,JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)。

JDK动态代理:

先继续看一个实现demo:

(1)Computer.java

public interface Computer {

    void accessSchool();

    void accessGoogle();
}

(2)StudentComputer.java

public class StudentComputer implements Computer {
    @Override
    public void accessSchool() {
        System.out.println("access school");
    }

    @Override
    public void accessGoogle() {

        System.out.println("access google");
    }
}

(3)AccessUtil.java

public class AccessUtil {

    public void method1() {
        System.out.println("===拦截代理1===");
    }

    public void method2() {
        System.out.println("===拦截代理2==");
    }
}

(4)MyInvocationHandler.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    //需要被代理的对象
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //执行动态代理类的所有方法时,都会被替换成执行如下的invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        AccessUtil accessUtil = new AccessUtil();

        accessUtil.method1();

        //以target作为主调执行method方法
        Object result =  method.invoke(target, args);

        accessUtil.method2();

        return result;
    }
}

(5)MyProxyFactory.java

public class MyProxyFactory {

    //为指定的traget生成动态代理对象
    public static Object getProxy(Object target) {

        MyInvocationHandler handler = new MyInvocationHandler();

        handler.setTarget(target);

        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                handler);

    }
}

(6)Client.java

public class Client {

    public static void main(String[] args) {

        Computer target = new StudentComputer();

        Computer proxy = (Computer) MyProxyFactory.getProxy(target);

        proxy.accessSchool();
        proxy.accessGoogle();

    }
}
/**
 * ===拦截代理1===
 access school
 ===拦截代理2==
 ===拦截代理1===
 access google
 ===拦截代理2==
 */

    由以上demo可以看出,通过动态代理的方式,不需要单独去实现一个代理类,通过动态代理的方式可以去代理任意符合一定规范的被代理类。当然实现机制是通过反射的方式去实现的。
总结动态代理最重要的要求是:被代理类必须实现接口。
简单分析一下实现动态代理方式的Proxy类。

Proxy类中有一个方法newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h);
loader: 要求,传递的是被代理类的类加载器ClassLoader
类加载器怎样获取:通过反射机制得到其Class对象。在Class类中提供一个方法  getClassLoader();
interfaces:
要求:得到被代理对象所实现的接口的所有Class对象。
所有Class对象获得方法:得到其Class对象,在Class类中提供一个方法  getInterfaces();
它返回的是Class[],就代表所实现接口的所有Class对象。
h:
它的类型是InvocationHandler,这是一个接口。
InvocationHandler 是代理实例的调用处理程序 实现的接口。
InvocationHandler接口中有一个方法invoke;
public Object invoke(Object proxy, Method method, Object[] args);
// 参数 proxy就是代理对象
// 参数method就是调用方法
// 参数args就是调用的方法的参数
// 返回值,就是真实行为执行后返回的结果,会传递给代理对象调用的方法.

以上就是JDK提供的动态代理技术。

cglib动态代理:

cglib动态代理用到了第三方类库,需要在项目中引入两个jar包:

asm-6.0.jar

cglib-3.2.5.jar

1.StudentComputer.java(同上)

2.AccessUtil.java(同上)

3.CglibProxy.java

/**
 * 拦截器
 */
public class CglibProxy implements MethodInterceptor {

    /**
     * 重写方法拦截,在方法前和方法后加入业务
     * @param o 目标对象
     * @param method 目标方法
     * @param objects 参数
     * @param methodProxy  代理对象
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        AccessUtil accessUtil = new AccessUtil();

        accessUtil.method1();

        //调用代理类实例上的proxy方法的父类方法(即实体类TargetObject中对应的方法)
        methodProxy.invokeSuper(o, objects);

        accessUtil.method2();

        return null;
    }
}

4.MyProxyFactory.java

public class MyProxyFactory {

    //为指定的traget生成动态代理对象
    public static StudentComputer getProxy(CglibProxy cglibProxy) {

        //Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展
        Enhancer enhancer = new Enhancer();

        //首先将被代理类TargetObject设置成父类
        enhancer.setSuperclass(StudentComputer.class);
        //然后设置拦截器TargetInterceptor
        enhancer.setCallback(cglibProxy);

        //最后执行enhancer.create()动态生成一个代理类,并从Object强制转型成父类型TargetObject
        StudentComputer studentComputer = (StudentComputer) enhancer.create();

        return studentComputer;
    }
}

5.client.java

public class Client {

    public static void main(String[] args) {

        CglibProxy proxy = new CglibProxy();

        StudentComputer studentComputer = MyProxyFactory.getProxy(proxy);

        studentComputer.accessGoogle();
        studentComputer.accessSchool();
    }
}

/**

 ===拦截代理1===
 access google
 ===拦截代理2==
 ===拦截代理1===
 access school
 ===拦截代理2==

 */

优缺点:   

  1. 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
    优点:因为有接口,所以使系统更加松耦合
    缺点:为每一个目标类创建接口
  2. 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
    优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
    缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。

     AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。

    jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。

    还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

 

共有 人打赏支持
粉丝 10
博文 67
码字总数 113149
×
centrald
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: