美文网首页
设计模式---代理模式

设计模式---代理模式

作者: 皮多堡 | 来源:发表于2020-08-12 15:19 被阅读0次

    代理(Proxy)是一种常用的行为型设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。比如日志,统计操作,常用框架中Mybaits中的Mapper就是通过动态代理实现方法的调用的。

    如图所示代理模式是对目标对象的封装和隐藏,通过关联目标对象,并向外暴露于目标接口同样的行为方法,从而通过内部完成对目标对象的增强。


    一、静态代理

    静态代理要求被代理对象与代理对象一起实现相同的接口或者是继承相同父类,一下以打印日志为例实现静态代理

      1. 公共接口,定义用户相关的业务逻辑
    /**
     * @author haopeng
     * @date 2020-08-11 15:14
     */
    public interface BizService {
    
        void doSomething();
    }
    
    • 2.具体实现类,也就是被代理的目标对象
    public class UserServiceImpl implements BizService {
        @Override
        public void doSomething() {
            System.out.println("执行用户相关业务逻辑");
        }
    }
    
    • 3.代理类,对目标对象进行增强
    public class UserLogProxy implements BizService {
    
        private final BizService service = new UserServiceImpl();
    
        @Override
        public void doSomething() {
            System.out.println("静态代理执行前。。。打印日志 。。。。" );
            service.doSomething();
            System.out.println("静态代理执行后。。。 打印日志 。。。。" );
        }
    }
    
    • 4.测试
    public class Client {
    
        public static void main(String[] args) {
            UserLogProxy userLogProxy = new UserLogProxy();
            userLogProxy.doSomething();
        }
    }
    

    程序运行结果:

    静态代理执行前。。。打印日志 。。。。
    执行用户相关业务逻辑
    静态代理执行后。。。 打印日志 。。。。
    

    可以看到完成了对用户业务方法的日志打印功能,但是动态代理明显的区别就是:需要再编译器确定被代理对象,试想一下,如果还有订单、商品等业务方法也需要打印日志的话,那么我们就需要对每一个业务类编写对应的代理类实现。

    二、动态代理

    利用反射机制在运行时创建代理类。动态代理的实现方式有两种分别是jdk动态代理cglib动态代理

    1.jdk动态代理

    jdk动态代理的实现步骤:

    1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
    2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
    3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
    4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
    • 代码实现
    public interface UserBizService {
    
        void doUserSomething();
    }
    
    public class UserServiceImpl implements UserBizService {
        @Override
        public void doUserSomething() {
            System.out.println("执行商用户相关的业务逻辑。。。");
        }
    }
    
    • 通过实现 InvocationHandler 接口创建自己的调用处理器
    public class LogProxy implements InvocationHandler {
    
        private Object object;
    
        public LogProxy(Object object) {
            this.object = object;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            System.out.println("动态代理执行前。。。打印日志");
            System.out.println( "执行方法" + method.getName());
            Object invoke = method.invoke(object, args);
            System.out.println("动态代理执行后。。。打印日志");
            return invoke;
        }
    }
    
    public class Client {
    
        public static void main(String[] args) {
            UserBizService hello = new UserServiceImpl();
            InvocationHandler invocationHandler = new LogProxy(hello);
            UserBizService userBizService = (UserBizService) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), invocationHandler);
            userBizService.doUserSomething();
    
    
        }
    }
    

    执行结果

    动态代理执行前。。。打印日志
    执行方法doUserSomething
    执行商用户相关的业务逻辑。。。
    动态代理执行后。。。打印日志
    

    可以看到符合预期效果,现在如果想新增一个代理功能,比如为商品业务方法打印日志,我们只需要新增几行代码就可以搞定,不需要额外的为商品服务单独创建代理类的实现

    • 用到的基础服务类
    public interface GoodService {
    
        public void doGoodThing();
    }
    
    public class GoodServiceImpl implements GoodService {
    
        @Override
        public void doGoodThing() {
            System.out.println("执行商品相关的业务逻辑。。。");
        }
    }
    
    • 测试类中通过构造函数创建动态代理类实例
    public class Client {
    
        public static void main(String[] args) {
            UserBizService userService = new UserServiceImpl();
            InvocationHandler invocationHandler = new LogProxy(userService);
            // 以下代码执行会直接报错,因为jdk的动态代理是能代理接口类
            //HelloInterface helloInterface = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), new Class[]{hello.getClass()}, invocationHandler);
            UserBizService userBizService = (UserBizService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), invocationHandler);
            userBizService.doUserSomething();
    
            GoodService goodservice = new GoodServiceImpl();
            InvocationHandler invocationHandlerBye = new LogProxy(goodservice);
            GoodService goodService = (GoodService) Proxy.newProxyInstance(goodservice.getClass().getClassLoader(), goodservice.getClass().getInterfaces(), invocationHandlerBye);
    
            goodService.doGoodThing();
    
        }
    }
    
    • 运行结果
    动态代理执行前。。。打印日志
    执行方法doUserSomething
    执行商用户相关的业务逻辑。。。
    动态代理执行后。。。打印日志
    动态代理执行前。。。打印日志
    执行方法doGoodThing
    执行商品相关的业务逻辑。。。
    动态代理执行后。。。打印日志
    

    总结: jdk动态代理要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种动态代理就叫Cglib

    2.cglib动态代理

    Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

    示例代码:

    • 代理类实现MethodInterceptor接口
    public class CglibLogProxy implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");
            System.out.println(method.getName());
            Object o1 = methodProxy.invokeSuper(o, args);
            System.out.println("++++++after " + methodProxy.getSuperName() + "++++++");
            return o1;
        }
    }
    
    • 业务类(被代理类),UserBiz不用实现任何接口
    public class UserBiz {
    
        public String getInfo(String msg) {
            System.out.println("=======执行业务方法======");
            System.out.println("=======方法参数为: <" + msg + ">=======");
            return "=======返回业务方法执行完毕的返回值======";
        }
    }
    
    • 测试类
    public class Client {
    
        public static void main(String[] args) {
            CglibLogProxy cglibProxy = new CglibLogProxy();
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(UserBiz.class);
            enhancer.setCallback(cglibProxy);
            UserBiz o = (UserBiz)enhancer.create();
            String info = o.getInfo("路漫漫其修远兮");
            System.out.println(info);
    
        }
    
    }
    
    • 执行结果
    ++++++before CGLIB$getInfo$0++++++
    getInfo
    =======执行业务方法======
    =======方法参数为: <路漫漫其修远兮>=======
    ++++++after CGLIB$getInfo$0++++++
    =======返回业务方法执行完毕的返回值======
    

    可以看到同样完成了代理功能,其实Spring框架中AOP就是根据目标类进行判断,从而决定使用哪种代理方式,如果目标类实现了接口就通过jdk方式来代理,否则通过cglib方式来代理。

    优化改进

    以上的写法可以结合工厂模式和泛型的思想再优化一下,这样没增加一个代理类就不必写重复的代码

    /**
     * @author haopeng
     * @date 2020-08-12 10:47
     */
    public class ProxyFactory implements MethodInterceptor {
    
        @SuppressWarnings("unchecked")
        public <T> T getProxyInstance(Class<?> clz) {
            Enhancer en = new Enhancer();
            en.setSuperclass(clz);
            en.setCallback(this);
            return (T)en.create();
        }
    
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("方法开始了 .....");
            //Object invoke = method.invoke(target, args);
            Object invoke = methodProxy.invokeSuper(obj, args);
            System.out.println("方法结束了 .....");
            return invoke;
        }
    
    
    }
    
    • 测试
    public class Client {
    
        public static void main(String[] args) {
            ProxyFactory proxyFactory = new ProxyFactory();
            UserBiz userBizProxy = proxyFactory.getProxyInstance(UserBiz.class);
            userBizProxy.getInfo("好玩的cglib动态代理");
        }
    }
    
    • 结果
    方法开始了 .....
    =======执行业务方法======
    =======方法参数为: <好玩的cglib动态代理>=======
    方法结束了 .....
    

    总结:

    • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
    • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP,为他们提供方法的interception(拦截)
    • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉
      补充一点ASM技术在SpringMVC中也有用到,SpringMVC中填充Controller参数的时候获取参数的名称就是通过ASM技术,因为反射是不能拿到参数名的

    三、Mybatis中的动态代理

    在使用Mybatis时我们只需要声明Mapper接口,但是并没有编写具体的实现类,那么Mybaits最终是怎样执行Mapper中的方法的,其实就是通过JDK动态代理,生成代理对象。
    源码示例:
    通过SqlSession获取UserMapper代理对象

     SqlSession session = new DefaultSqlSession(null, null, true);
     session.getMapper(UserMapper.class);
    
        public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
          //   获取代理工厂
            MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
            if (mapperProxyFactory == null) {
                throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
            } else {
                try {
                  // 通过Mapper代理工厂生成代理类
                    return mapperProxyFactory.newInstance(sqlSession);
                } catch (Exception var5) {
                    throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
                }
            }
        }
    
    
    • MapperProxyFactory#newInstance
    // 通过jdk动态代理实现生成代理对象
     protected T newInstance(MapperProxy<T> mapperProxy) {
            return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
        }
    
    // 先生成MapperProxy代理类
        public T newInstance(SqlSession sqlSession) {
            MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
            return this.newInstance(mapperProxy);
        }
    

    相关文章

      网友评论

          本文标题:设计模式---代理模式

          本文链接:https://www.haomeiwen.com/subject/jbwpdktx.html