设计模式 - 代理模式:静态代理和动态代理

基础:需要具备面向对象设计思想,多态的思想,反射的思想;

1 什么是代理模式

代理模式:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
抽象角色:通过接口抽象类声明需要实现的业务方法method1。
真实角色:实现抽象角色,并实现其所有声明的方法method1。
代理角色:实现抽象角色,并实现其所有声明的方法method1,method1则是通过调用真实角色实现的方法method1,除此之外,还可以添加自定义方法method2增强method1

代理又分为静态代理动态代理,我们先从静态代理开始了解代理模式的工作流程。

2 静态代理

 一个场景:客户要去银行取钱,此时,委托柜员代理自己从账户中取钱,柜员不仅代他取出钱给他,还生成了取钱业务的记录

/**
 * 抽象角色
 */
public interface UserService {
    void getMoney();
}

/**
 * 真实角色---实现抽象角色
 */
public class UserServiceImpl implements UserService{
    public void getMoney(){
        System.out.println("客户取钱");
    }
}

/**
 * 代理角色---实现抽象角色,调用真实角色的方法,并增强该行为
 */
public class ProxyActor implements UserService {
    public UserServiceImpl userService;
    public void getMoney(){
        userService=new UserServiceImpl();
        userService.getMoney();  //调用真实角色的具体方法
        generateDetail();        //自定义的增强方法
    }
    public void generateDetail(){
        System.out.println("生成业务记录");
    }
}

       由上面代码可以看出,原本由真实角色UserService执行的getMoney()方法,现在由代理角色ProxyActor执行,而且还封装了自定义的generateDetail()方法对其增强。但是,这样的设计模式也带来了缺点。

       缺点:当功能叠加的时候,只能臃肿的扩展代理类,为每一个真实角色扩展一个代理角色类,在实际开发中,大量使用静态代理会导致类的急剧增加。

       究其原因:是因为静态代理所需要的实现类在编译时期就要加载检查。

       如何改善:使用动态代理设计方法,在运行期需要的时候在加载对应的类,减少类的急剧增加。

3 动态代理

3.1 JDK动态代理

3.1.1 设计实现

       抽象角色和真实角色仍然不变,动态构建新的代理类:通过实现InvocationHandler接口的反射方法invoke(),这个方法在代理类中动态实现。(InvocationHandler位于java.lang.reflect包)

public class MyInvocationHandler implements InvocationHandler {
    private Object target;   //角色属性和构造方法
    MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
        Object result = method.invoke(target, args);
        generateDetail();
        return result;
    }
    public void generateDetail(){
        System.out.println("生成业务记录");
    }
}

使用测试:

 @Test
    public void test(){
        UserService userService=new UserServiceImpl();
        MyInvocationHandler invocationHandler=new MyInvocationHandler(userService);
        UserService proxyInstance = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                invocationHandler); //需要的时候在动态生成代理类
        proxyInstance.getMoney(); //这一步:通过动态代理类执行业务方法时,会通过反射invoke()执行代理方法
    }

      Proxy代理类有静态方法:Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler invocationHandler):返回代理类的一个实例,返回后的代理类可以被当作真实角色使用,可使用真实角色中申明的方法。

                   

      其实动态代理就是在运行时,指定所需接口生成class文件,自然就能指向class已实现的接口,相当于生成了一个代理类实例,但是这个代理类的具体操作封装在InvocationHandler的invoke(Object obj, Method method, Object[] args)中实现,所以在生成代理类的时候还需提供一个handler由它接管实际操作
      通过这种方式,抽象角色、真实角色都能在运行时动态改变,从而实现灵活的动态代理关系。

3.1.2 总结

       所为的DynamicProxy是这样一种class:它是在运行时生成的class,该class需要实现一组interface,使用动态代理类的时候,必须实现InvocationHandler接口。

JDK动态代理的一般步骤

1. 创建被代理的接口(抽象角色)和类(真实角色)

2. 创建一个实现接口InvocationHandler的类(必须实现invoke方法),传入真实角色对象,用于封装代理任务

3. 调用Proxy的静态方法,创建一个代理类newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandlerh)

4.调用代理类的业务方法即可

                    

3.2 CGlib动态代理

3.1.1 代理实现

1.引入cglib-node-2.2.jar包

2.用CglibProxy拦截类实现接口MethodInterceptor:重写intercept拦截方法

/**
 * 真实角色,被代理类,不用抽象角色
 */
public class User{
    public void getMoney(){
        System.out.println("客户取钱");
    }
}

/**
 * CGlib代理类
 */
public class CGlibProxy implements MethodInterceptor {
    private Enhancer enhancer=new Enhancer();
    public Object getProxy(Class cl){
        //设置创建子类的类
        enhancer.setSuperclass(cl);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 
throws Throwable{
        Object result=methodProxy.invokeSuper(o, objects);
        generateDetail();
        return result;
    }
    private void generateDetail(){
        System.out.println("生成业务记录");
    }
}

CGlib测试:

/**
* CGlib动态代理测试
*/
    @Test
    public void test3(){
        CGlibProxy proxy=new CGlibProxy();
        User user=(User )proxy.getProxy(User.class);
        user.getMoney();
    }

3.2.2 总结

1.创建被代理类

2.创建代理类实现接口MethodInterceptor,并重写intercept方法

3.调用代理类自定义的方法,得到一个代理实例

使用:执行代理实例的业务方法即可

比较总结

3.2.3 比较总结

JDK动态代理

1、  只能代理实现了接口的类,即实现了抽象角色的真是角色。

2、  没有实现接口的类不能实现JDK的动态代理

CGlib动态代理

1、  针对类来实现代理

2、  对执行目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。

3 模拟代理产生步骤

思路:
实现功能:通过Proxy的newProxyInstance返回代理对象
1、  声明一段源码(动态产生代理)
2、  编译源码(JDK Compiler API)产生新的类(代理类)
3、  将这个类load到内存当中,产生一个新的对象(代理对象)
4、  返回代理对象完善动态代理实现
      首先得到系统编译器,通过编译器得到文件管理者,然后获取文件,然后编译器执行编译任务,完成编译之后,将class文件加载到类加载器中,通过构造方法得到实例,然后调用newInstance()接收一个对象的实例。

(1)拿到编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
(2)文件管理者 StandardJavaFileManager fileMgr = Compiler.getStandardFileManager(null,null,null);

(3)获取文件 Iterable units = fileMgr.getJavaFileObjects(filename);

(4)编译任务 CompilationTask t =compiler.getTask(null,fileMgr,null,null,null,units);

(5)load到内存
            ClassLoader cl = ClassLoader.getSystemClassLoader();
            Class c = cl.loadClass(”com.imooc.proxy.$Proxy0”);

(6)通过代理对象的构造器构造实例
            Constructor ctr = c.getConstructor(infce);
            ctr.newInstance(new Car());

上说所说,内部的业务逻辑是硬编码的,如何实现真正的动态代理,动态的指定业务逻辑呢?
     1、需要创建一个事务处理器,首先创建一个接口也就是InvocationHandler,为了模拟JDK,这里把接口的名字和JDK事务处理器名称一样,同样写一个方法叫做invoke(),用来表示对某个对象的某个方法进行业务处理,所以需要把某个对象以及对象的方法作为invoke()方法的参数传递进来,invoke(Object obj,Method method),方法作为参数使用到了java反射,需要把此包引入。这样InvocationHandler接口就完成了。
     2、创建事务处理实现类比如说时间代理TimerProxy,实现了InvocationHandler接口,这样结构就成了

TimerProxy implements InvocationHandler{
@override
void invoke(Object obj,Method method){
    //业务逻辑<br>——method.invoke(目标对象,参数);
    //业务逻辑<br>
   }
}

      需要将目标对象传入,没有参数可以不写参数,创建代理对象的构造方法,初始化目标对象
      3、在Proxy类的newProxyInstance()方法中,除了要把目标Class接口作为参数外,还需要把事务处理器InvocationHandler 
传进去,然后更改创建实例对象中硬编码的部分用事务处理器方法替代即可。难点在于字符串的拼接。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值