zoukankan      html  css  js  c++  java
  • 设计模式--代理模式

    转:https://blog.csdn.net/cmavb/article/details/90720386

    代理模式是Java常见的设计模式之一。所谓代理模式是指通过代理对象来访问被代理对象 ,可以在被代理对象的基础之上,增强额外的功能,而不需要修改别人已经写好的代码。
    为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
    在现实生活中,这种情形非常的常见,比如请一个律师代理来打官司、车票代售点代售车票等。

    代理模式的实现

    代理模式可以有两种实现的方式,一种是静态代理类,另一种是各大框架都喜欢的动态代理。下面我们主要讲解一下这两种代理模式

    1.静态代理

    静态代理在使用时,需要定义接口或者父类,目标对象与代理对象一起实现相同的接口或者是继承相同父类。

    /**
     * 接口
     */
    public interface IUserDao {
     
        void save();
    }
    /**
     * 接口实现
     * 被代理对象(目标对象)
     */
    public class UserDao implements IUserDao {
        public void save() {
            System.out.println("----已经保存数据!----");
        }
    }
    /**
     * 代理对象,静态代理
     */
    public class UserDaoProxy implements IUserDao{
        //接收保存目标对象
        private IUserDao target;
        public UserDaoProxy(IUserDao target){
            this.target=target;
        }
     
        public void save() {
            System.out.println("开始事务...");
            target.save(); //代理对象的方法中包含了被代理对象的方法,执行被代理对象的方法
            System.out.println("提交事务...");
        }
    }
    /**
     * 测试类
     */
    public class App {
        public static void main(String[] args) {
            //目标对象
            UserDao target = new UserDao();
     
            //代理对象,把目标对象传给代理对象,建立代理关系
            UserDaoProxy proxy = new UserDaoProxy(target);
     
            proxy.save();//执行的是代理对象的方法
        }
    }

    总结:代理对象特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。这里的包含是关键。

    静态代理的缺点:

    1、需增加的代理类太多

    2、被实现的接口如果增加了方法,被代理对象与代理对象都需要修改

    2.动态代理

    在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,换言之就是Srping的AOP(切面编程)。可以非常灵活地在某个类,某个方法,某个代码点上切入我们想要的内容,就是动态代理其中的内容。

    2.1 基于接口的动态代理(JDK官方)

    动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)。代理对象不需要实现接口,需要指定接口类型。但是目标对象一定要实现接口,否则不能用动态代理。

    代理类所在包:java.lang.reflect.Proxy

    JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

    static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

    三个参数的意义是:

    ClassLoader loader:指定当前目标对象使用的类加载器,也就是代理对象和被代理对象使用相同的类加载器

    Class<?>[] interfaces:目标对象实现的接口的类型。也就是代理对象和被代理对象具有相同的行为。实现相同的接口。

    InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

    public interface IActor {
        /**
         * 基本演出
         * @param time 表演时长
         */
        public void basicAct(float time);
     
        /**
         * 危险演出
         * @param time 表演时长
         */
        public void dangerousAct(float time);
    }
    /**
      * 目标对象
    */
    public class Actor implements IActor {
     
        /**
         * 基本演出
         *
         * @param time 表演时长
         */
        @Override
        public void basicAct(float time) {
            System.out.println("基本表演,时长为:" + time);
        }
     
        /**
         * 危险演出
         *
         * @param time 表演时长
         */
        @Override
        public void dangerousAct(float time) {
            System.out.println("危险表演,时长为:" + time);
        }
    }
    /**
      *测试类,在本类中实现了代理对象
        */
    public class ClientTest {
     
        public static void main(String[] args){
            // 生成目标对象,此处和静态代理的目的不一样,所以不是生成IActor的实例
            final Actor actor = new Actor();  // 内部类调用外部成员变量,必须为final
     
            // 获取代理对象
            IActor proxy = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(),
                                                          actor.getClass().getInterfaces(),
                                                          new InvocationHandler() {
               /**
                *   执行目标对象的任何方法,都会经过该方法。
                *   此方法有拦截的功能。
                * @param proxy 代理对象的引用。不一定每次都用得到
                * @param method 当前执行的方法对象
                * @param args  执行方法所需的参数
                * @return 前执行方法的返回值
                * @throws Throwable
                */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    String name = method.getName(); // 目标对象执行了接口声明的任意方法都会调用本invoke方法,此处获得是调用了哪个方法名return method.invoke(actor, time);//执行目标对象的方法
          } }); 

        // 开始测试
        proxy.basicAct(100);
      }
    }

    上面的测试类中,明明代理对象已经是IActor类型的,为什么还要使用第二个getInterfaces()参数呢?如果看下面这个例子就能明白了:

    /**
     * 创建动态代理对象
     * 动态代理不需要实现接口,但是需要指定接口类型
     */
    public class ProxyFactory{
     
        //维护一个目标对象
        private Object target;
        public ProxyFactory(Object target){
            this.target=target;
        }
     
       //给目标对象生成代理对象
        public Object getProxyInstance(){
            return Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), // 此处不知道目标对象的接口
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("开始事务2");
                            //执行目标对象方法
                            Object returnValue = method.invoke(target, args);
                            System.out.println("提交事务2");
                            return returnValue;
                        }
                    }
            );
        }
     
    }

    2.2 基于子类的动态代理(第三方CGLib)

    有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

    Cglib子类代理实现步骤:

    1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可。

    2.引入功能包后,就可以在内存中动态构建子类

    3.目标类不能为final,否则报错。(因为final修饰的类不能被继承)

    4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

    //还是之前的目标对象,但是没有实现接口
    public class Actor {
     
        /**
         * 基本演出
         *
         * @param time 表演时长
         */
        @Override
        public void basicAct(float time) {
            System.out.println("基本表演,时长为:" + time);
        }
     
        /**
         * 危险演出
         *
         * @param time 表演时长
         */
        @Override
        public void dangerousAct(float time) {
            System.out.println("危险表演,时长为:" + time);
        }
    }
    //测试类,包含了生成的代理对象
    public class Client {
        /**
         * 基于子类的动态代理
         * 要求:
         * 目标类不能用final修饰
         * 用到的类:
         * Enhancer
         * 用到的方法:
         * create(Class, Callback):创建参数Class的子类对象
         * 方法的参数:
         * Class:被代理对象的字节码
         * Callback:如何代理
         *
         * @param args
         */
        public static void main(String[] args) {
            final Actor actor = new Actor();
            Actor cglibActor = (Actor) Enhancer.create(actor.getClass(),
                    new MethodInterceptor() {
                        /**
                         * 执行目标对象的任何方法,都会经过该方法。在此方法内部就可以对目标对象的任何
                         方法进行增强。
                            在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自            
                            己的代理逻辑,类似于JDK中的InvocationHandler接口
                         *
                         * 参数:
                         * 前三个和基于接口的动态代理是一样的。
                         * proxy为:由CGLib动态生成的代理类实例
                         * MethodProxy:当前执行方法的代理对象,也就是生成的代理类对方法的代理引用。
                         * 更通俗的说,MethodProxy是cglib生成用来代替Method对象的一个对象,使用 
                         * MethodProxy比调用JDK自身的Method直接执行方法效率会有提升。
                         * 返回值:
                         * 当前执行方法的返回值
                         */
                        @Override
                        public Object intercept(Object proxy, Method method, Object[] args,
                                                MethodProxy methodProxy) throws Throwable {
                            String name = method.getName();
                            Float time = (Float) args[0];
                            Object rtValue = null;
                            if ("basicAct".equals(name)) {
                                //基本演出
                                if (time > 2000) {
                                    rtValue = method.invoke(actor, time / 2);
                                }
                            }
                            return rtValue;
                        }
                    });
            cglibActor.basicAct(10000);
        }
    }

    Spring AOP 怎么应用的动态代理

    在Spring的AOP编程中:
    如果加入容器的目标对象有实现接口,用JDK代理。
    如果目标对象没有实现接口,用Cglib代理。

  • 相关阅读:
    阿里--面经 搜集
    阿里一面经验总结
    System对象
    JDBC-oracle(登陆)
    博客静态页面
    设计模式(1)---Factory Pattern
    软件设计师备考经验(含新旧版本对比)
    第九课,ROS仿真1
    参数服务器相关的问题
    3.空域图像处理的洪荒之力
  • 原文地址:https://www.cnblogs.com/jvStarBlog/p/11042647.html
Copyright © 2011-2022 走看看