当前位置 博文首页 > 韩超的博客 (hanchao5272):设计模式-代理模式-以购房中介为例

    韩超的博客 (hanchao5272):设计模式-代理模式-以购房中介为例

    作者:[db:作者] 时间:2021-09-05 16:12

    超级链接: Java常用设计模式的实例学习系列-绪论

    参考:《HeadFirst设计模式》


    1.关于代理模式

    代理模式是一种结构型模式。

    代理模式:为其他对象提供一个代理以控制对这个对象的访问。

    本文以购房中介为场景来学习代理模式

    • 购房者可以直接找房主买房。如此做较累,因为买房之前要多次房屋筛选,买房之后要签订合同等等。
    • 购房者可以找房屋中介买房,购房者只需进行少量看房即可,中介将代劳筛选房屋和签订合同等事宜。
    • 房屋中介其实最终还是通过房主完成买房。

    2.房主

    房子最终属于房主,首先我们先定义房主接口和房主类。

    被代理对象:接口:IHouseOwner

    /**
     * <p>房主接口</P>
     *
     * @author hanchao
     */
    public interface IHouseOwner {
        /**
         * 房屋交易
         */
        void tradeHouse();
    }
    

    被代理对象:类:HouseOwner

    /**
     * <p>房主</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class HouseOwner implements IHouseOwner {
        /**
         * 房屋交易
         */
        @Override
        public void tradeHouse() {
            log.info("房屋交易");
        }
    }
    

    3.无代理:直接买房

    /**
     * <p>购房者</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class HouseBuyer {
        /**
         * 直接找房主购房
         */
        public void buyHouseDirectly() {
            log.info("购房者花费大量时间用于筛选房屋...最终选定了一间房。");
            new HouseOwner().tradeHouse();
            log.info("购房者与房主签订售房合同。");
        }
    }
    

    直接买房是在太麻烦了,因为购房者花费大量时间用于筛选房屋,在决定买房之后,购房者还需与房主签订一系列的售房合同。

    为了省事省心,购房者决定通过售房中介进行买房。

    4.静态代理

    4.1.静态代理:代理对象:类

    这种方式,通过定义售房中介,直接代理房主类进行售房。

    /**
     * <p>房屋中介:静态代理-代理对象是类</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class HouseAgentByStaticForClass {
    
        /**
         * 被代理的对象:房主
         */
        private HouseOwner owner;
    
        public HouseAgentByStaticForClass(HouseOwner owner) {
            this.owner = owner;
        }
    
        /**
         * 房屋交易
         */
        public void sellHouse() {
            //前置操作
            log.info("中介替购房者完成筛选房屋...最终选定了一间房。");
    
            //售房
            owner.tradeHouse();
    
            //后置操作
            log.info("中介替购房者完成与房主签订的售房合同。");
        }
    }
    

    下面是通过中介买房的流程。

    /**
     * <p>购房者</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class HouseBuyer {
        /**
         * 找中介买房:静态代理-代理对象:类
         */
        public void buyHouseByStaticProxyForClass() {
            //被代理对象:房主类
            HouseOwner owner = new HouseOwner();
    
            //代理对象
            HouseAgentByStaticForClass houseAgent = new HouseAgentByStaticForClass(owner);
    
            //买房
            houseAgent.sellHouse();
        }
    }
    

    4.2.静态代理:代理对象:接口

    这种方式,通过定义售房中介,直接代理房主接口进行售房。

    /**
     * <p>房屋中介:静态代理-代理对象是接口</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class HouseAgentByStaticForInterface implements IHouseOwner {
    
        /**
         * 被代理的对象:房主
         */
        private IHouseOwner owner;
    
        public HouseAgentByStaticForInterface(IHouseOwner owner) {
            this.owner = owner;
        }
    
        /**
         * 房屋交易
         */
        @Override
        public void tradeHouse() {
            //前置操作
            log.info("中介替购房者完成筛选房屋...最终选定了一间房。");
    
            //售房
            owner.tradeHouse();
    
            //后置操作
            log.info("中介替购房者完成与房主签订的售房合同。");
        }
    }
    

    下面是通过中介买房的流程。

    /**
     * <p>购房者</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class HouseBuyer {
        /**
         * 找中介买房:静态代理-代理对象:接口
         */
        public void buyHouseByStaticProxyForInterface() {
            //被代理对象:房主接口
            IHouseOwner iHouseOwner = new HouseOwner();
    
            //定义代理对象
            IHouseOwner houseAgent = new HouseAgentByStaticForInterface(iHouseOwner);
    
            //买房
            houseAgent.tradeHouse();
        }
    }
    

    4.3.静态代理的不足

    可维护性差:

    • 被代理对象与代理类一一对应,如果新增被代理对象,则需要新增代理类。
    • 被代理方法与代理方法一一对应,如果新增被代理方法,则需要新增代理方法。

    5.动态代理

    5.1.JDK动态代理:代理对象:接口

    自定义调用处理器:实现InvocationHandler:HouseAgentByDynamicForJdk

    /**
     * <p>动态代理:房屋中介</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class HouseAgentByDynamicForJdk implements InvocationHandler {
        /**
         * 被代理的对象:房主
         */
        private IHouseOwner owner;
    
        public HouseAgentByDynamicForJdk(IHouseOwner owner) {
            this.owner = owner;
        }
    
        /**
         * 对售房进行代理
         *
         * @param proxy  代理对象实例
         * @param method 被代理的方法
         * @param args   参数
         * @return 返回值
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //前置操作
            log.info("中介替购房者完成筛选房屋...最终选定了一间房。");
    
            //售房
            Object result = method.invoke(owner, args);
    
            //后置操作
            log.info("中介替购房者完成与房主签订的售房合同。");
    
            return result;
        }
    }
    

    JDK动态代理实现步骤:

    /**
     * <p>购房者</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class HouseBuyer {
    		/**
         * 找中介买房:JDK动态代理-代理对象:接口
         */
        public void buyHouseByDynamicProxyForJdk() {
            //被代理的对象:房主接口
            IHouseOwner owner = new HouseOwner();
    
            //被代理对象的接口加载器
            ClassLoader classLoader = IHouseOwner.class.getClassLoader();
    
            //被代理的接口的类型
            Class[] classes = {IHouseOwner.class};
    
            //代理时的调用处理器:房租中介
            InvocationHandler proxyHandler = new HouseAgentByDynamicForJdk(owner);
    
            //代理对象
            IHouseOwner proxy = (IHouseOwner) Proxy.newProxyInstance(classLoader, classes, proxyHandler);
    
            //代理售房
            proxy.tradeHouse();
        }
    }
    

    JDK通过java.lang.reflect.Proxy类能够实现动态代理,其主要流程如下:

    • 实现InvocationHandler接口,自定义自己需要的调用处理器:invocationHandler
    • 获取被代理对象的接口加载器:classLoader
    • 获取被代理的接口的类型:clazz[]
    • 通过java.lang.reflect.Proxy的静态方法newProxyInstance(),以invocationHandlerclassLoaderclazz[]为参数,生成代理对象。
    • 调用代理对象的代理方法。

    **注意:**根据上述流程,可以确定JDK动态代理针对的是接口,而非

    如何实现对类的动态代理呢?这就需要CGLib了。

    5.2.CGLIB动态代理:代理对象:类

    自定义方法解释器:实现MethodInterceptor:HouseAgentByDynamicForJdk

    /**
     * <p></P>
     *
     * @author hanchao
     */
    @Slf4j
    public class HouseAgentByDynamicForCglib implements MethodInterceptor {
        /**
         * 被代理的对象:房主
         */
        private HouseOwner owner;
    
        public HouseAgentByDynamicForCglib(HouseOwner owner) {
            this.owner = owner;
        }
    
        /**
         * 对售房进行代理
         *
         * @param obj         代理对象实例
         * @param method      被代理的方法
         * @param args        参数
         * @param methodProxy 方法代理
         * @return 返回值
         */
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws InvocationTargetException, IllegalAccessException {
            //前置操作
            log.info("中介替购房者完成筛选房屋...最终选定了一间房。");
    
            //售房
            Object result = method.invoke(owner, args);
    
            //后置操作
            log.info("中介替购房者完成与房主签订的售房合同。");
    
            return result;
        }
    }
    

    CGLIB动态代理实现步骤:

    /**
     * <p>购房者</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class HouseBuyer {
        /**
         * 找中介买房:CGLIB动态代理-代理对象:类
         */
        public void buyHouseByDynamicProxyForCglib() {
            //被代理的对象:房主
            HouseOwner owner = new HouseOwner();
    
            //CGLIB增强的代理类
            Enhancer enhancer = new Enhancer();
    
            //被代理对象的类型
            enhancer.setSuperclass(HouseOwner.class);
    
            //定义代理时的方法解释器
            MethodInterceptor methodInterceptor = new HouseAgentByDynamicForCglib(owner);
    
            //以回调方式设置代理行为
            enhancer.setCallback(methodInterceptor);
    
            //创建代理对象
            HouseOwner roomowner = (HouseOwner) enhancer.create();
    
            //代理售房
            roomowner.tradeHouse();
        }
    }
    

    JDK通过org.springframework.cglib.proxy.Enhancer类能够实现动态代理,其主要流程如下:

    • 实现MethodInterceptor接口,自定义自己需要的方法解释器:methodInterceptor
    • 创建CGLIB增强型代理类:enhancer
    • enhancer设置被代理对象的类型:clazz
    • methodInterceptor为参数,为enhancer设置回调方法。
    • 调用Enhancer#create()方法创建代理对象。
    • 调用代理对象的代理方法。

    注意:CGLIB动态代理针对的是

    使用CGLIB可以直接引用其依赖,也可以使用springframework的内置CGLIB。

    CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

    6.实际应用场景

    • Struts2中的拦截器Interceptor
    • Spring中的AOP
    • AspectJ的实现。

    7.总结

    最后以UML类图来总结本文的售房中介场景以及代理模式
    在这里插入图片描述

    cs