精华内容
下载资源
问答
  • 动态代理

    千次阅读 2018-08-30 19:06:38
    1.代理模式深入学习(一)——动态代理的实现及解析 原文链接:https://blog.csdn.net/wangyy130/article/details/48828595 一、代理模式 分为静态和动态代理。静态代理,我们通常都很熟悉。有一个写好的代理类...

    1.代理模式深入学习(一)——动态代理的实现及解析

    原文链接:https://blog.csdn.net/wangyy130/article/details/48828595

    一、代理模式

        分为静态和动态代理。静态代理,我们通常都很熟悉。有一个写好的代理类,实现与要代理的类的一个共同的接口,目的是为了约束也为了安全。
        具体不再多说。
    
        这里主要想说的是关于动态代理。我们知道静态代理若想代理多个类,实现扩展功能,那么它必须具有多个代理类分别取代理不同的实现类。
        这样做的后果是造成太多的代码冗余。那么我们会思考如果做,才能既满足需求,又没有太多的冗余代码呢?——————动态代理。
        它通过在运行时创建代理类,来适应变化。主要用到的是Reflec中的Proxy和InvocationHandler类。
        先通过一段代码来理解一下动态代理模式的实现过程:
    

     

    public class LogHandler implements InvocationHandler {
    
        private Object targetObject; //将要代理的对象保存为成员变量
        //将被代理的对象传进来,通过这个方法生成代理对象
        public Object newProxyInstance(Object targetObject) {
            this.targetObject = targetObject;
            return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                                   targetObject.getClass().getInterfaces(), this);
        }
        //代理模式内部要毁掉的方法
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            System.out.println("start-->>" + method.getName());//方法执行前的操作
            for (int i=0; i<args.length; i++) {
                System.out.println(args[i]);
            }
            Object ret = null;
            try {
                //调用目标方法,如果目标方法有返回值,返回ret,如果没有抛出异常
                ret = method.invoke(targetObject, args);
                System.out.println("success-->>" + method.getName()); //方法执行后操作
            }catch(Exception e) {
                e.printStackTrace();
                System.out.println("error-->>" + method.getName());//出现异常时的操作
                throw e;
            }
            return ret;
        }
    
    }

     

    //客户端调用
    public static void main(String[] args) {
            LogHandler logHandler = new LogHandler();
            UserManager userManager = (UserManager)logHandler.newProxyInstance(new UserManagerImpl());
            //userManager.addUser("0001", "张三");
            String name = userManager.findUser("0001");
            System.out.println("Client.main() --- " + name);
        }

    代码解析

    首先我们要了解一下类的加载机制,在每创建一个Java类时,都会生产一个.class文件,在内存中对应也会生成一个class对象,来表示该类的类型信
    息,我们可以用.class来获取这个类的所有信息,也可以通过getClass()方法来读取这个类的所有信息,比如
    getClass().getInterfaces()获取类的接口信息等。在Java类加载时,要通过一个类加载器classloader来将生成的Java类加载到JVM
    中才能执行。
    
        理解了类的加载机制后,我们再看代码中的newProxyInstance方法,在这个方法中,我们将被代理对象传进来后,通过
        Proxy.newProxyInstance这个方法来动态的创建一个被代理类的一个代理类的实例。
    

    在Proxy.newProxyInstance方法中,共有三个参数:

        1、targetObject.getClass().getClassLoader()目标对象通过getClass方法获取类的所有信息后,调用getClassLoader()
        方法来获取类加载器。获取类加载器后,可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即Java虚拟机中,以便运行时需要!
        2、targetObject.getClass().getInterfaces()获取被代理类的所有接口信息,以便于生成的代理类可以具有代理类接口中的所有方法。
        3、this:我们使用动态代理是为了更好的扩展,比如在方法之前做什么,之后做什么等操作。这个时候这些公共的操作可以统一交给代理类去做。
        这个时候需要调用实现了InvocationHandler 类的一个回调方法。由于自身变实现了这个方法,所以将this传递过去。
    

    invoke方法的参数

        1、Object proxy生成的代理对象,在这里不是特别的理解这个对象,但是个人认为是已经在内存中生成的proxy对象。
        2、Method method:被代理的对象中被代理的方法的一个抽象。
        3、Object[] args:被代理方法中的参数。这里因为参数个数不定,所以用一个对象数组来表示。
    

    执行过程

        在执行过程中,由于被代理的方法可能有返回值,可能直接就是void来表示,那么为了适应于所有方法,所以定义一个返回值,将返回的值
        通过ret来接收,当然会抛出异常。ret = method.invoke(targetObject, args);就是调用被代理对象的方法,来执行最原始的方法。在执行完后,
        进行额外的处理操作。
    
        以上就是在学习代理模式中自己的一些理解。代理模式是一个很重要的模式,也是一个很实用的模式,利用它可以实现事务的封装,不用我们每次
        需要事务操作时,都要进行手动去写,直接调用代理就好了,具体见下篇博客。

    2.深入剖析ThreadLocal

    原文链接:https://www.cnblogs.com/dolphin0520/p/3920407.html

    探讨下ThreadLocal的使用方法和实现原理。首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两个应用场景。

      以下是本文目录大纲:

      一.对ThreadLocal的理解

      二.深入解析ThreadLocal类

      三.ThreadLocal的应用场景

      若有不正之处请多多谅解,并欢迎批评指正。

      请尊重作者劳动成果,转载请标明原文链接:

       http://www.cnblogs.com/dolphin0520/p/3920407.html

    一.对ThreadLocal的理解

      ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

      这句话从字面上看起来很容易理解,但是真正理解并不是那么容易。

      我们还是先来看一个例子:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    class ConnectionManager {

         

        private static Connection connect = null;

         

        public static Connection openConnection() {

            if(connect == null){

                connect = DriverManager.getConnection();

            }

            return connect;

        }

         

        public static void closeConnection() {

            if(connect!=null)

                connect.close();

        }

    }

       假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。

      所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。

      这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待。

      那么大家来仔细分析一下这个问题,这地方到底需不需要将connect变量进行共享?事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。

      到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    class ConnectionManager {

         

        private  Connection connect = null;

         

        public Connection openConnection() {

            if(connect == null){

                connect = DriverManager.getConnection();

            }

            return connect;

        }

         

        public void closeConnection() {

            if(connect!=null)

                connect.close();

        }

    }

     

     

    class Dao{

        public void insert() {

            ConnectionManager connectionManager = new ConnectionManager();

            Connection connection = connectionManager.openConnection();

             

            //使用connection进行操作

             

            connectionManager.closeConnection();

        }

    }

       这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。

      那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

      但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

    二.深入解析ThreadLocal类

      在上面谈到了对ThreadLocal的一些理解,那我们下面来看一下具体ThreadLocal是如何实现的。

      先了解一下ThreadLocal类提供的几个方法:

    1

    2

    3

    4

    public T get() { }

    public void set(T value) { }

    public void remove() { }

    protected T initialValue() { }

       get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,下面会详细说明。

      首先我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。

      先看下get方法的实现:

      

       第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是  this,而不是当前线程t。

      如果获取成功,则返回value值。

      如果map为空,则调用setInitialValue方法返回value。

      我们上面的每一句来仔细分析:

      首先看一下getMap方法中做了什么:

      

      可能大家没有想到的是,在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。

      那么我们继续取Thread类中取看一下成员变量threadLocals是什么:

      

      实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:

      

      可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。

      然后再继续看setInitialValue方法的具体实现:

      很容易了解,就是如果map不为空,就设置键值对,为空,再创建Map,看一下createMap的实现:

      

      至此,可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的:

      首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

      初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

      然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

      下面通过一个例子来证明通过ThreadLocal能达到在每个线程中创建变量副本的效果:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    public class Test {

        ThreadLocal<Long> longLocal = new ThreadLocal<Long>();

        ThreadLocal<String> stringLocal = new ThreadLocal<String>();

     

         

        public void set() {

            longLocal.set(Thread.currentThread().getId());

            stringLocal.set(Thread.currentThread().getName());

        }

         

        public long getLong() {

            return longLocal.get();

        }

         

        public String getString() {

            return stringLocal.get();

        }

         

        public static void main(String[] args) throws InterruptedException {

            final Test test = new Test();

             

             

            test.set();

            System.out.println(test.getLong());

            System.out.println(test.getString());

         

             

            Thread thread1 = new Thread(){

                public void run() {

                    test.set();

                    System.out.println(test.getLong());

                    System.out.println(test.getString());

                };

            };

            thread1.start();

            thread1.join();

             

            System.out.println(test.getLong());

            System.out.println(test.getString());

        }

    }

       这段代码的输出结果为:

      

      从这段代码的输出结果可以看出,在main线程中和thread1线程中,longLocal保存的副本值和stringLocal保存的副本值都不一样。最后一次在main线程再次打印副本值是为了证明在main线程中和thread1线程中的副本值确实是不同的。

      总结一下:

      1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;

      2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;

      3)在进行get之前,必须先set,否则会报空指针异常;

          如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

        因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。

      

      看下面这个例子:

      

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    public class Test {

        ThreadLocal<Long> longLocal = new ThreadLocal<Long>();

        ThreadLocal<String> stringLocal = new ThreadLocal<String>();

     

        public void set() {

            longLocal.set(Thread.currentThread().getId());

            stringLocal.set(Thread.currentThread().getName());

        }

         

        public long getLong() {

            return longLocal.get();

        }

         

        public String getString() {

            return stringLocal.get();

        }

         

        public static void main(String[] args) throws InterruptedException {

            final Test test = new Test();

             

            System.out.println(test.getLong());

            System.out.println(test.getString());

     

            Thread thread1 = new Thread(){

                public void run() {

                    test.set();

                    System.out.println(test.getLong());

                    System.out.println(test.getString());

                };

            };

            thread1.start();

            thread1.join();

             

            System.out.println(test.getLong());

            System.out.println(test.getString());

        }

    }

       在main线程中,没有先set,直接get的话,运行时会报空指针异常。

      但是如果改成下面这段代码,即重写了initialValue方法:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    public class Test {

        ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){

            protected Long initialValue() {

                return Thread.currentThread().getId();

            };

        };

        ThreadLocal<String> stringLocal = new ThreadLocal<String>(){;

            protected String initialValue() {

                return Thread.currentThread().getName();

            };

        };

     

         

        public void set() {

            longLocal.set(Thread.currentThread().getId());

            stringLocal.set(Thread.currentThread().getName());

        }

         

        public long getLong() {

            return longLocal.get();

        }

         

        public String getString() {

            return stringLocal.get();

        }

         

        public static void main(String[] args) throws InterruptedException {

            final Test test = new Test();

     

            test.set();

            System.out.println(test.getLong());

            System.out.println(test.getString());

         

             

            Thread thread1 = new Thread(){

                public void run() {

                    test.set();

                    System.out.println(test.getLong());

                    System.out.println(test.getString());

                };

            };

            thread1.start();

            thread1.join();

             

            System.out.println(test.getLong());

            System.out.println(test.getString());

        }

    }

       就可以直接不用先set而直接调用get了。

    三.ThreadLocal的应用场景

      最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。

      如:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    private static ThreadLocal<Connection> connectionHolder

    new ThreadLocal<Connection>() {

    public Connection initialValue() {

        return DriverManager.getConnection(DB_URL);

    }

    };

     

    public static Connection getConnection() {

    return connectionHolder.get();

    }

       下面这段代码摘自:

      http://www.iteye.com/topic/103804

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    private static final ThreadLocal threadSession = new ThreadLocal();

     

    public static Session getSession() throws InfrastructureException {

        Session s = (Session) threadSession.get();

        try {

            if (s == null) {

                s = getSessionFactory().openSession();

                threadSession.set(s);

            }

        catch (HibernateException ex) {

            throw new InfrastructureException(ex);

        }

        return s;

    }

    3.为避免多线程Connection混乱使用ThreadLocal来封装事务

    原文链接:https://blog.csdn.net/wangyy130/article/details/48952371

    上一篇博客总结了动态代理的使用及代码的含义。接下来,继续探究动态代理的实地应用——利用动态代理来封装事务。
        首先,要先来回忆一下最原始的封装好的事务的代码,这里在连接数据库时用到了TheadLocal这个类,通过它可以来保证在执行业务逻辑过程中来
        保证每一次使用的connection的连接对象都执行的是同一个线程内的connection。
    

    事务的封装

    /**
     * 采用ThreadLocal封装Connection
     * @author yanyan
     *
     */
    public class ConnectionManager {
        //声明一个Connection类型的ThreadLocal
        private static ThreadLocal<Connection> connectionHolder=new ThreadLocal<Connection>();
    
        public static Connection getConnection(){
        //通过TheadLocal的get方法来获取当前线程连接对象的实例
            Connection conn=connectionHolder.get();
            if(conn==null){
                try{
                //获取数据库驱动
                    JdbcConfig jdbcConfig=XmlConfigReader.getInstance().getJdbcConfig();
                    Class.forName(jdbcConfig.getDriverName());
                    conn=DriverManager.getConnection(jdbcConfig.getUrl(),jdbcConfig.getUserName(),jdbcConfig.getPassword());
                //设置数据库连接对象
                    connectionHolder.set(conn);
                }catch (Exception e) {
                    throw new ApplicationException("系统错误,请联系管理员!");
                }
            }
    
            return conn;
        }
    
        //关闭数据库连接
        public static void closeConnection(){
            Connection conn=connectionHolder.get();
    
            if(conn!=null){
                try{
                    conn.close();
                    //从集合中清除
                    connectionHolder.remove();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 手动提交事务
         * @param conn
         */
        public static void beginTransaction(Connection conn){
            try{
                if(conn !=null){
                    if(conn.getAutoCommit()){
                        conn.setAutoCommit(false);
                    }
                }
            }catch(SQLException e){}
        }
    
        /**
         * 
         * 提交事务
         * @param conn
         */
        public static void commitTransaction(Connection conn){
            try{
                if(conn !=null){
                    if(!conn.getAutoCommit()){
                        conn.commit();
                    }
                }
            }catch(SQLException e){}
        }
    
        /**
         * 回滚事务
         * @param conn
         */
        public static void rollbackTransaction(Connection conn){
            try{
                if(conn!=null){
                    if(!conn.getAutoCommit()){
                        conn.rollback();
                    }
                }
            }catch(SQLException e){}
        }
    
        /**
         * 重置connection状态
         * @param conn
         */
        public static void resetConnection(Connection conn){
            try{
                if(conn!=null){
                    if(conn.getAutoCommit()){
                        conn.setAutoCommit(false);
                    }else{
                        conn.setAutoCommit(true);
                    }
                }
            }catch(SQLException e){}
        }
    
    
        //关闭preparedStatement
        public static void close(Statement pstmt){
            if(pstmt !=null){
                try {
                    pstmt.close();
                } catch (SQLException e) {
    
                    e.printStackTrace();
                }
            }
        }
        //关闭ResultSet
        public static void close(ResultSet rs){
            if(rs !=null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    
    
    
        通过利用ThreadLocal这个类来封装Connection,这样在业务逻辑层获取connection的时候,就会避免了很多麻烦。在遇到多重业务逻辑调用
        多种方法时,调用到的connection会在当前线程中查找获取到connection这个对象进行数据库操作。
        接下来,就该介绍如何利用动态代理来封装事务了,请见下篇博客!

    4.代理模式深入学习(二)——实现动态代理对事务的封装

    原文链接:https://blog.csdn.net/wangyy130/article/details/49228495

    从动态代理的实现衍生原理到threadLocal来封装事务,到最后真正的利用动态代理来封装事务。缺少每一
        步都似乎显得有些冒进了!现在剩下的就只是把先前封装好的事务加进到写好的动态代理类中就好了!
    

    动态代理与事务结合

    package com.bjpowernode.drp.util;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    
    /**
     * 事务的封装,利用动态代理封装事务
     * @author yanyan
     *
     */
    public class TransactionHandler implements InvocationHandler {
        private Object targetObject;
    
        /**
         * 将要代理的类传递给该类,同时在该类中保存这个类的对象
         * @param targetObject
         * @return
         */
        public Object newProxyInstance(Object targetObject){
            this.targetObject=targetObject;
            /**
             * 1/targetObject:要代理的类的对象
             * 2/代理的类所实现的接口,便于生成的代理类去实现这个接口
             * 3/每个生成的代理类要回调一个实现了InvocationHandler的内部函数,这个函数放在这个类里面就是invoke函数,为了适应多种类,要写活
             */
            return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
        }
    
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            Connection conn=null;
            Object ret=null; //定义代理的方法返回对象
            try{
                //从ThreadLocal中取得connection
                conn=ConnectionManager.getConnection();//打开连接
                if(method.getName().startsWith("add") || method.getName().startsWith("del") || method.getName().startsWith("modify")){
                    //手动控制事务提交
                    ConnectionManager.beginTransaction(conn);
                }
                //调用目标对象的业务逻辑方法
                ret=method.invoke(targetObject, args);
                if(!conn.getAutoCommit()){
                    //提交事务
                    ConnectionManager.commitTransaction(conn);
                }
    
            }catch(ApplicationException e){
                //回滚事务
                ConnectionManager.rollbackTransaction(conn);
                throw e;
            }catch(Exception e){
                e.printStackTrace();
                if(e instanceof InvocationTargetException){
                    InvocationTargetException ete=(InvocationTargetException)e;
                    throw ete.getTargetException();
                }
                //回滚事务
                ConnectionManager.rollbackTransaction(conn);
                throw new ApplicationException("操作失败!");
            }finally{
                ConnectionManager.closeConnection();
            }
    
            return ret;
        }
    }
    
    

    将动态代理封装的事务嵌进原始调用对象中

    package com.bjpowernode.drp.util;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.dom4j.Document;
    import org.dom4j.DocumentException;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    import com.bjpowernode.drp.flowcard.manager.FlowCardManager;
    
    /**
     * 抽象工厂,主要创建两个系列的产品
     * 1、manager系列
     * 2、dao系列
     * @author yanyan
     *
     */
    public class BeanFactory {
    
        private final String beansConfigFile="beans-config.xml";
        private Document doc;
        private static BeanFactory instance=new BeanFactory();
    
        private Map serviceMap=new HashMap();
    
        private Map daoMap=new HashMap();
        private BeanFactory(){
            //装载配置文件
            try {
                doc=new SAXReader().read(Thread.currentThread().getContextClassLoader().getResourceAsStream(beansConfigFile));
            } catch (DocumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                throw new RuntimeException();
            }
    
        }
    
        public static BeanFactory getInstance(){
    
            return instance;
        }
    
        /**
         * 根据产品编号取得service系列产品
         * @param beanId
         * @return
         */
        public synchronized Object getServiceObject(Class c){
            //首先判断是否存在该对象
                    if(serviceMap.containsKey(c.getName())){
                        return serviceMap.get(c.getName());
                    }
                    //如果不存在,则创建一个
                    //  //代表以bean路径结尾的所有元素,用“//” 表示所有路径以"//"后指定的子路径结尾的元素 
                    Element beanElt=(Element) doc.selectSingleNode("//service[@id=\""+c.getName()+"\"]");
                    /*String className=beanElt.getTextTrim();*/
                    String className=beanElt.attributeValue("class");
                    Object service=null;
                    try {
    
                        service=Class.forName(className).newInstance();
                        TransactionHandler transactionHandler=new TransactionHandler();
                        service=transactionHandler.newProxyInstance(service);
                        //将创建好的对象放到map中
                        serviceMap.put(c.getName(), service);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    } 
                    return service;
        }
    
        /**
         * 根据产品编号取得dao系列产品
         * @param beanId
         * @return
         */
        public synchronized Object getDaoObject(Class c){
            //首先判断是否存在该对象
                    if(daoMap.containsKey(c.getName())){
                        return daoMap.get(c.getName());
                    }
                    //如果不存在,则创建一个
                    //  //代表以bean路径结尾的所有元素,用“//” 表示所有路径以"//"后指定的子路径结尾的元素 
                    Element beanElt=(Element) doc.selectSingleNode("//dao[@id=\""+c.getName()+"\"]");
                    /*String className=beanElt.getTextTrim();*/
                    String className=beanElt.attributeValue("class");
                    Object dao=null;
                    try {
                        dao=Class.forName(className).newInstance();
                        TransactionHandler transactionHandler=new TransactionHandler();
                        dao=transactionHandler.newProxyInstance(dao);
                        //将创建好的对象放到map中
                        daoMap.put(c.getName(), dao);
                    } catch (Exception e) {
                        throw new RuntimeException("创建失败");
                    } 
                    return dao;
        }
    }
    

    调用

    private FlowCardDao flowCardDao;
    
    //这里通过构造函数调用的beanFactory实现代码中已经启用了动态代理封装的事务
            this.flowCardDao=(FlowCardDao)BeanFactory.getInstance().getDaoObject(FlowCardDao.class);
        }
    
        public void addFlowCard(FlowCard flowCard) throws ApplicationException {
            //Connection conn=null;
            try{
                //conn=ConnectionManager.getConnection();
                //开始事务
                //ConnectionManager.beginTransaction(conn);
                //生成流向单单号
                String flowCardVouNo=flowCardDao.generateVouNo();
                //添加流向单主信息
                flowCardDao.addFlowCardMaster(flowCardVouNo, flowCard);
                //添加明细信息
                flowCardDao.addFlowCardDetail(flowCardVouNo, flowCard.getFlowCardDetailList());
                //提交事务
                //ConnectionManager.commitTransaction(conn);
            }catch(DaoException e){
                //回滚事务
                //ConnectionManager.rollbackTransaction(conn);
                throw new ApplicationException(e);
    
            }/*finally{
                ConnectionManager.closeConnection();
            }
            */
    
    
        }
        从注释的代码中可以看到,我们利用动态代理封装的事务可以大大减少代码的重复。

     

    展开全文
  • 需求大致如下:通过url传参的方式,让Nginx代理到不同的服务器 浏览器输入:http://127.0.0.1/remote?port=8081被代理到:http://192.168.108.2:8081
  • 动态代理的两种实现方式

    千次阅读 多人点赞 2019-10-15 18:41:48
    1.动态代理概述 代理模式:在不修改目标对象的情况下,对目标对象进行功能增强 动态代理的代理类在物理上是不存在的,代理类是在程序运行的时候产生的. 目标对象:被增强功能的对象 代理对象:用来对目标对象进行...

    1.动态代理概述

    代理模式:在不修改目标对象的情况下,对目标对象进行功能增强

    动态代理的代理类在物理上是不存在的,代理类是在程序运行的时候产生的.
    目标对象:被增强功能的对象
    代理对象:用来对目标对象进行功能增强
    代理模式:动态代理需要使用专门的API来产生代理对象.
    动态代理应用场景:在不想或不能够修改目标对象的情况下,对其进行功能增强

    2. 常用的动态代理技术

    2.1 jdk动态代理

    jdk本身提供的动态代理:要求目标对象必须实现相应接口
    jdk产生的动态代理对象和目标对象实现了相同的接口。代理对象和目标对象是兄弟关系.

    //目标类接口
    public interface SaleComputer {
        /**
         * 卖电脑方法
         * @param money 电脑原价
         * @return 得到的物品
         */
        String sale(double money);
    
    }
    
    //相当于现实生活中的厂家直销
    //目标类
    public class Vender implements SaleComputer{
        @Override
        public String sale(double money) {
            return "一台电脑卖了"+money+"元";
        }
    
    }
    
    public class ProxyTest {
        public static void main(String[] args) {
            //创建真实对象
            Vender vender=new Vender();
            //动态代理增强vender对象
            /*
                三个参数:
                1.类加载器:真实对象.getClass().getClassLoader()
                2.接口数组:真实对象.getClass().getInterfaces()
                3.处理器:new InvocationHandler()
             */
    
            SaleComputer proxy_computer = (SaleComputer) Proxy.newProxyInstance(vender.getClass().getClassLoader(), 
            vender.getClass().getInterfaces(), new InvocationHandler() {
                /*
                    代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
                    参数:
                        1.代理对象
                        2.method:代理对象调用的方法,被封装为对象
                        3.args:代理对象调用方法时,传递的实际参数
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                    //判断是否是sale方法
                    if(method.getName().equals("sale")){
                        //1.获得电脑价格,对价格进行增强(相当于代理商提高价格)
                        double money=(double)args[0];
                        //代理商提高电脑价格
                        money*=1.1;
                        //通过反射的方式使用真实对象调用方法
                        String obj =(String) method.invoke(vender, money);
                        //代理商提高电脑价格的同时会赠送一些礼品
                        return obj+",赠送机械键盘和耳机";
                    }else {
                        Object obj = method.invoke(vender, args);
                        return obj;
                    }
                }
            });
    
            //厂家直销 一台电脑卖了8000.0元
            String vener_sale = vender.sale(8000);
            System.out.println("厂家直销,"+vener_sale);
    
            //代理销售 一台电脑卖了8800.0元,赠送机械键盘和耳机
            String proxy_sale = proxy_computer.sale(8000);
            System.out.println("代理销售,"+proxy_sale);
        }
    }
    

    在这里插入图片描述

    2.2 cglib动态代理

    cglib:对jdk动态代理的封装和延伸,它可以对未实现接口的目标对象产生代理,也可以对实现接口的目标对象产生代理。

    1.目标类

    public class Target {
        public void method() {
            System.out.println("Target running....");
        }
    }
    

    2.动态代理代码

    public static void main(String[] args) {
            final Target target = new Target(); //创建目标对象
            Enhancer enhancer = new Enhancer();   //创建增强器
            enhancer.setSuperclass(Target.class);//设置父类
            enhancer.setCallback(new MethodInterceptor() { //设置回调
            public Object intercept(Object o, Method method, Object[] objects,MethodProxy methodProxy) throws Throwable {
                System.out.println("前置代码增强....");
                Object invoke = method.invoke(target, objects);
                System.out.println("后置代码增强....");
                return invoke;
                }
            });
            Target proxy = (Target) enhancer.create(); //创建代理对象
            proxy.method();
        }
    
    

    在这里插入图片描述

    展开全文
  • AOP之JDK动态代理和CGLib动态代理 ,具体效果和过程看博文 http://blog.csdn.net/evankaka/article/details/45195383
  • Java两种动态代理JDK动态代理和CGLIB动态代理

    万次阅读 多人点赞 2018-08-07 15:33:35
    JDK动态代理 cglib动态代理 测试 代理模式 代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是...

    目录

    代理模式

    JDK动态代理

    cglib动态代理

    测试


    代理模式

    代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。

    使用代理好处还不止这些,一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。代理其他的应用可能还有很多。

    上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,jdk动态代理和cglib动态代理。

    JDK动态代理

    jdk动态代理是jre提供给我们的类库,可以直接使用,不依赖第三方。先看下jdk动态代理的使用代码,再理解原理。

    首先有个“明星”接口类,有唱、跳两个功能:

    package proxy;
    
    public interface Star
    {
        String sing(String name);
        
        String dance(String name);
    }
    

    再有个明星实现类“刘德华”:

    package proxy;
    
    public class LiuDeHua implements Star
    {   
        @Override
        public String sing(String name)
        {
             System.out.println("给我一杯忘情水");
    
            return "唱完" ;
        }
        
        @Override
        public String dance(String name)
        {
            System.out.println("开心的马骝");
    
            return "跳完" ;
        }
    }
    

    明星演出前需要有人收钱,由于要准备演出,自己不做这个工作,一般交给一个经纪人。便于理解,它的名字以Proxy结尾,但他不是代理类,原因是它没有实现我们的明星接口,无法对外服务,它仅仅是一个wrapper。

    package proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class StarProxy implements InvocationHandler
    {
        // 目标类,也就是被代理对象
        private Object target;
        
        public void setTarget(Object target)
        {
            this.target = target;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            // 这里可以做增强
            System.out.println("收钱");
            
            Object result = method.invoke(target, args);
            
            return result;
        }
        
        // 生成代理类
        public Object CreatProxyedObj()
        {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }  
       
    }
    

    上述例子中,方法CreatProxyedObj返回的对象才是我们的代理类,它需要三个参数,前两个参数的意思是在同一个classloader下通过接口创建出一个对象,该对象需要一个属性,也就是第三个参数,它是一个InvocationHandler。需要注意的是这个CreatProxyedObj方法不一定非得在我们的StarProxy类中,往往放在一个工厂类中。上述代理的代码使用过程一般如下:

    1、new一个目标对象

    2、new一个InvocationHandler,将目标对象set进去

    3、通过CreatProxyedObj创建代理对象,强转为目标对象的接口类型即可使用,实际上生成的代理对象实现了目标接口。

            Star ldh = new LiuDeHua();
    
            StarProxy proxy = new StarProxy();
    
            proxy.setTarget(ldh); 
      
            Object obj = proxy.CreatProxyedObj();
            
            Star star = (Star)obj;

    Proxy(jdk类库提供)根据B的接口生成一个实现类,我们成为C,它就是动态代理类(该类型是 $Proxy+数字 的“新的类型”)。生成过程是:由于拿到了接口,便可以获知接口的所有信息(主要是方法的定义),也就能声明一个新的类型去实现该接口的所有方法,这些方法显然都是“虚”的,它调用另一个对象的方法。当然这个被调用的对象不能是对象B,如果是对象B,我们就没法增强了,等于饶了一圈又回来了。

    所以它调用的是B的包装类,这个包装类需要我们来实现,但是jdk给出了约束,它必须实现InvocationHandler,上述例子中就是StarProxy, 这个接口里面有个方法,它是所有Target的所有方法的调用入口(invoke),调用之前我们可以加自己的代码增强。

    看下我们的实现,我们在InvocationHandler里调用了对象B(target)的方法,调用之前增强了B的方法。

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            // 这里增强
            System.out.println("收钱");
            
            Object result = method.invoke(target, args);
            
            return result;
        }

    所以可以这么认为C代理了InvocationHandler,InvocationHandler代理了我们的类B,两级代理。

    整个JDK动态代理的秘密也就这些,简单一句话,动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。由于我们需要增强,这个增强是需要留给开发人员开发代码的,因此代理类不能直接包含被代理对象,而是一个InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。从原理可以看出,JDK动态代理是“对象”的代理。

    下面看下动态代理类到底如何调用的InvocationHandler的,为什么InvocationHandler的一个invoke方法能为分发target的所有方法。C中的部分代码示例如下,通过反编译生成后的代码查看,摘自链接地址。Proxy创造的C是自己(Proxy)的子类,且实现了B的接口,一般都是这么修饰的:

    public final class XXX extends Proxy implements XXX
    

    一个方法代码如下:

    
      public final String SayHello(String paramString)
      {
        try
        {
          return (String)this.h.invoke(this, m4, new Object[] { paramString });
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
    

    可以看到,C中的方法全部通过调用h实现,其中h就是InvocationHandler,是我们在生成C时传递的第三个参数。这里还有个关键就是SayHello方法(业务方法)跟调用invoke方法时传递的参数m4一定要是一一对应的,但是这些对我们来说都是透明的,由Proxy在newProxyInstance时保证的。留心看到C在invoke时把自己this传递了过去,InvocationHandler的invoke的第一个方法也就是我们的动态代理实例类,业务上有需要就可以使用它。(所以千万不要在invoke方法里把请求分发给第一个参数,否则很明显就死循环了)

    C类中有B中所有方法的成员变量

      private static Method m1;
      private static Method m3;
      private static Method m4;
      private static Method m2;
      private static Method m0;
    

    这些变量在static静态代码块初始化,这些变量是在调用invocationhander时必要的入参,也让我们依稀看到Proxy在生成C时留下的痕迹。

    static
      {
        try
        {
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m3 = Class.forName("jiankunking.Subject").getMethod("SayGoodBye", new Class[0]);
          m4 = Class.forName("jiankunking.Subject").getMethod("SayHello", new Class[] { Class.forName("java.lang.String") });
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          return;
        }
        catch (NoSuchMethodException localNoSuchMethodException)
        {
          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException localClassNotFoundException)
        {
          throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
      }
    

    从以上分析来看,要想彻底理解一个东西,再多的理论不如看源码,底层的原理非常重要。

    jdk动态代理类图如下

    cglib动态代理

    我们了解到,“代理”的目的是构造一个和被代理的对象有同样行为的对象,一个对象的行为是在类中定义的,对象只是类的实例。所以构造代理,不一定非得通过持有、包装对象这一种方式。

    通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。根据里氏代换原则(LSP),父类需要出现的地方,子类可以出现,所以cglib实现的代理也是可以被正常使用的。

    先看下代码

    package proxy;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class CglibProxy implements MethodInterceptor
    {
        // 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
        public Object CreatProxyedObj(Class<?> clazz)
        {
            Enhancer enhancer = new Enhancer();
            
            enhancer.setSuperclass(clazz);
            
            enhancer.setCallback(this);
            
            return enhancer.create();
        }
        
        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
        {
            // 这里增强
            System.out.println("收钱");
            
            return arg3.invokeSuper(arg0, arg2);
        } 
    }
    

    从代码可以看出,它和jdk动态代理有所不同,对外表现上看CreatProxyedObj,它只需要一个类型clazz就可以产生一个代理对象, 所以说是“类的代理”,且创造的对象通过打印类型发现也是一个新的类型。不同于jdk动态代理,jdk动态代理要求对象必须实现接口(三个参数的第二个参数),cglib对此没有要求。

    cglib的原理是这样,它生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“$父类方法名$”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。

    这样的话,C中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。

    C的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。

    这里有个疑问就是intercept的四个参数,为什么我们使用的是arg3而不是arg1?

        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
        {
            System.out.println("收钱");
            
            return arg3.invokeSuper(arg0, arg2);
        }

     因为如果我们通过反射 arg1.invoke(arg0, ...)这种方式是无法调用到父类的方法的,子类有方法重写,隐藏了父类的方法,父类的方法已经不可见,如果硬调arg1.invoke(arg0, ...)很明显会死循环。

    所以调用的是cglib开头的方法,但是,我们使用arg3也不是简单的invoke,而是用的invokeSuper方法,这是因为cglib采用了fastclass机制,不仅巧妙的避开了调不到父类方法的问题,还加速了方法的调用。

    fastclass基本原理是,给每个方法编号,通过编号找到方法执行避免了通过反射调用。

    对比JDK动态代理,cglib依然需要一个第三者分发请求,只不过jdk动态代理分发给了目标对象,cglib最终分发给了自己,通过给method编号完成调用。cglib是继承的极致发挥,本身还是很简单的,只是fastclass需要另行理解。

    参考

    https://blog.csdn.net/jiankunking/article/details/52143504

    http://www.php.cn/java-article-407212.html

    https://www.cnblogs.com/chinajava/p/5880887.html

    https://rejoy.iteye.com/blog/1627405

    测试

       public static void main(String[] args)
        {
            int times = 1000000;
            
            Star ldh = new LiuDeHua();
            StarProxy proxy = new StarProxy();
            proxy.setTarget(ldh);
            
            long time1 = System.currentTimeMillis();
            Star star = (Star)proxy.CreatProxyedObj();
            long time2 = System.currentTimeMillis();
            System.out.println("jdk创建时间:" + (time2 - time1));
            
            CglibProxy proxy2 = new CglibProxy();
            long time5 = System.currentTimeMillis();
            Star star2 = (Star)proxy2.CreatProxyedObj(LiuDeHua.class);
            long time6 = System.currentTimeMillis();
            System.out.println("cglib创建时间:" + (time6 - time5));
            
            long time3 = System.currentTimeMillis();
            for (int i = 1; i <= times; i++)
            {
                star.sing("ss");
                
                star.dance("ss");
            }
            long time4 = System.currentTimeMillis();
            System.out.println("jdk执行时间" + (time4 - time3));
            
            long time7 = System.currentTimeMillis();
            for (int i = 1; i <= times; i++)
            {
                star2.sing("ss");
                
                star2.dance("ss");
            }
            
            long time8 = System.currentTimeMillis();
            
            System.out.println("cglib执行时间" + (time8 - time7));   
        }

    经测试,jdk创建对象的速度远大于cglib,这是由于cglib创建对象时需要操作字节码。cglib执行速度略大于jdk,所以比较适合单例模式。另外由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。spring默认使用jdk动态代理,如果类没有接口,则使用cglib。

    展开全文
  • 静态代理,JDK动态代理,Cglib动态代理详解

    千次阅读 热门讨论 2021-02-12 13:21:39
    文章目录一、代理模式二、静态代理三、动态代理 一、代理模式 代理模式(Proxy Pattern)是程序设计中的一种设计模式,他的特征是代理类和委托类实现有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、...


    一、代理模式

    代理模式(Proxy Pattern)是程序设计中的一种设计模式,他的特征是代理类和委托类实现有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

    代理类与委托类之间通常会存在关联关系,一个代理类对象与一个委托类对象(目标对象)关联,代理类对象本身并不真正实现服务,而是通过调用委托类对象的相关方法,来提供特定的服务。

    即通过代理对象访问目标对象,这样我们可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

    以最近很火的贵州茅台为例子,假如我们要买1箱茅台,我们不是直接跟茅台公司买的,而是通过代理商(例如沃尔玛,京东等)进行购买。茅台公司就是一个目标对象,它只负责生产茅台,而其他如何销售,寄快递等琐碎的事交由代理商(代理对象)来处理。

    在这里插入图片描述

    二、静态代理

    代理对象与目标对象一起实现相同的接口或者继承相同父类,由程序员创建或特定工具自动生成源代码,即在编译时就已经确定了接口,目标类,代理类等。在程序运行之前,代理类的 .class 文件就已经生成。

    你可以简单认为代理对象写死持有目标对象。

    package com.nobody.staticproxy;
    
    /**
     * @Description 白酒厂商
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public interface WhileWineCompany {
        // 生产酒 (演示才写一个方法,实际多个方法都是能被代理的)
        void product();
    }
    
    package com.nobody.staticproxy;
    
    /**
     * @Description 委托类,贵州茅台
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class Moutai implements WhileWineCompany {
        public void product() {
            System.out.println("生产贵州茅台...");
        }
    }
    
    package com.nobody.staticproxy;
    
    /**
     * @Description 代理类,京东代理商
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class JDProxy implements WhileWineCompany {
    
        // 被代理的贵州茅台公司
        private Moutai moutai;
    
        public JDProxy(Moutai moutai) {
            this.moutai = moutai;
        }
    
        public void product() {
            System.out.println("京东商城下订单购买");
            // 实际调用目标对象的方法
            moutai.product();
            System.out.println("京东商城发快递");
        }
    }
    
    package com.nobody.staticproxy;
    
    /**
     * @Description
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class Main {
        public static void main(String[] args) {
            // 生成代理对象,并传入被代理对象
            WhileWineCompany proxy = new JDProxy(new Moutai());
            proxy.product();
        }
    }
    
    // 输出结果
    京东商城下订单购买
    生产贵州茅台...
    京东商城发快递
    

    静态代理优缺点:

    • 优点:在不修改目标对象的功能前提下,可以对目标功能扩展。
    • 缺点:假如又有一个目标类,也要做增强,则还需要新增相对应的代理类,导致我们要手动编写很多代理类。同时,一旦接口增加方法,目标对象与代理对象都要维护。

    三、动态代理

    代理类在程序运行时才创建的代理方式被称为动态代理。

    静态代理中,代理类(JDProxy)是我们程序员定义的,在程序运行之前就已经编译完成。而动态代理中的代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。

    在 Java 中,有2种动态代理实现方式,JDK动态代理CGLIB动态代理

    Spring 中的 AOP 是依靠动态代理来实现切面编程的。

    3.1 JDK动态代理

    JDK动态代理是基于反射机制,生成一个实现代理接口的匿名类,然后重写方法进行方法增强。在调用具体方法前通过调用 InvokeHandlerinvoke 方法来处理。

    它的特点是生成代理类速度很快,但是运行时调用方法操作会比较慢,因为是基于反射机制的,而且只能针对接口编程,即目标对象要实现接口。

    如果目标对象实现了接口,默认情况下会采用JDK的动态代理。

    实现JDK动态代理,我们需要借助 java.lang.reflect 包下的 Proxy 类InvocationHandler接口

    package com.nobody.jdkproxy;
    
    /**
     * @Description 白酒厂商
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public interface WhileWineCompany {
        // 生产酒 (演示才写一个方法,实际多个方法都是能被代理的)
        void product();
    }
    
    package com.nobody.jdkproxy;
    
    /**
     * @Description 委托类,贵州茅台
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class Moutai implements WhileWineCompany {
        public void product() {
            System.out.println("生产贵州茅台...");
        }
    }
    
    package com.nobody.jdkproxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * @Description JDK动态代理实现InvocationHandler接口
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class JDKProxy implements InvocationHandler {
    
        // 被代理的目标对象
        private Object target;
    
        /**
         * 所有执行代理对象的方法都会被替换成执行invoke方法
         * 
         * @param proxy 代理对象
         * @param method 将要执行的方法信息
         * @param args 执行方法需要的参数
         * @return 方法执行后的返回值
         * @throws Throwable
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("京东商城下订单");
            // 通过反射,调用目标对象的方法并传入参数
            Object result = method.invoke(target, args);
            System.out.println("京东商城发快递");
            return result;
        }
    
        /**
         * 获取代理对象
         * 
         * @param target 被代理的对象
         * @return 代理对象
         */
        public Object getJDKProxy(Object target) {
            this.target = target;
            // loader:ClassLoader对象,定义哪个ClassLoader对象加载生成代理对象
            // interfaces:一个Interface对象的数组,即给代理的对象提供一组接口,代理对象就会实现了这些接口(多态),这样我们就能调用这些接口中的方法
            // h::InvocationHandler对象,当动态代理对象在调用方法的时候,会调用InvocationHandler对象的invoke方法
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
        }
    }
    
    package com.nobody.jdkproxy;
    
    /**
     * @Description
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class Main {
        public static void main(String[] args) {
            JDKProxy jdkProxy = new JDKProxy();
            // 获取贵州茅台的代理对象
            WhileWineCompany proxy = (WhileWineCompany) jdkProxy.getJDKProxy(new Moutai());
            // 执行方法
            proxy.product();
        }
    }
    
    // 输出结果
    京东商城下订单
    生产贵州茅台...
    京东商城发快递
    

    如果此时我们又增加了一个目标对象,也要进行代理,我们只需要定义一个委托类实现接口即可。

    package com.nobody.jdkproxy;
    
    /**
     * @Description 委托类,酒鬼酒
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class JiuGuiJiu implements WhileWineCompany {
    
        public void product() {
            System.out.println("生产酒鬼酒...");
        }
    }
    
    package com.nobody.jdkproxy;
    
    /**
     * @Description
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class Main {
        public static void main(String[] args) {
            JDKProxy jdkProxy = new JDKProxy();
            // 获取贵州茅台的代理对象
            WhileWineCompany proxy = (WhileWineCompany) jdkProxy.getJDKProxy(new Moutai());
            // 执行方法
            proxy.product();
    
            System.out.println("----------------------");
    
            // 获取酒鬼酒的代理对象
            proxy = (WhileWineCompany) jdkProxy.getJDKProxy(new JiuGuiJiu());
            // 执行方法
            proxy.product();
        }
    }
    
    // 输出结果
    京东商城下订单
    生产贵州茅台...
    京东商城发快递
    ----------------------
    京东商城下订单
    生产酒鬼酒...
    京东商城发快递
    

    动态代理让我们方便对代理类的方法进行统一处理,而不用修改每个代理类中的方法。因为所有被代理执行的方法,最终都是调用 InvocationHandler 中的 invoke 方法,所以我们可以在 invoke 方法中统一进行增强处理。

    在JDK动态代理的过程中,没有看到实际的代理类,代理对象又是如何通过调用 InvocationHandler 的
    invoke方法来完成代理过程的?通过JDK源码分析其实是 Proxy 类的 newProxyInstance
    方法在运行时动态生成字节码生成代理类(缓存在Java虚拟机内存中),从而创建了一个动态代理对象。

    我们通过以下方法将生成的代理类打印到本地查看:

    byte[] classFile =
            ProxyGenerator.generateProxyClass("$Proxy0", Moutai.class.getInterfaces());
    String path = "D:/$Proxy0.class";
    try (FileOutputStream fos = new FileOutputStream(path)) {
        fos.write(classFile);
        fos.flush();
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    最终,生成的class文件通过反编译如下,可以看出代理类继承了 Proxy,并且实现了与目标对象同样的 WhileWineCompany 接口,所以不难理解为什么 newProxyInstance 方法生成的代理对象能赋值给 WhileWineCompany 接口。而且从中也看出代理类的 product 方法实际调用了 InvocationHandler 的 invoke 方法,从而最终实现对目标对象的方法代理。

    简单可总结为,代理对象($Proxy0)持有 InvocationHandler 对象(我们定义的JDKProxy),InvocationHandler 对象持有目标对象(target),InvocationHandler 中的 invoke 方法对目标对象的方法进行增强处理。从而达到调用代理对象的方法,代理对象调用 InvocationHandler 的 invoke方法,invoke 方法中又调用了目标对象的方法。

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    import com.nobody.staticproxy.WhileWineCompany;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements WhileWineCompany {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final void product() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m3 = Class.forName("com.nobody.staticproxy.WhileWineCompany").getMethod("product");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    JDK为我们的生成了一个叫$Proxy0(0是编号,有多个代理类会依次递增)的代理类,这个类文件信息存放在内存中,我们在创建代理对象时,是通过反射获得这个类的构造方法,然后创建的代理实例。

    代理类继承了 Proxy 类,因为在Java中是单继承的,所以这就是为什么JDK动态代理中,目标对象一定要实现接口。

    3.2 Cglib动态代理

    JDK动态代理要求目标对象要实现接口,那如果一个类没有实现接口呢?那可以用 Cglib 实现动态代理。

    Cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库,它是开源的。动态代理是利用 asm 开源包,将目标对象类的 class 文件加载进来,然后修改其字节码生成新的子类来进行扩展处理。即可以在运行期扩展Java类和实现Java接口。它广泛的被许多AOP的框架使用中,例如 Spring AOP,为他们提供方法的 interception (拦截)。

    Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类。不推荐直接使用ASM,除非你对JVM内部结构包括class文件的格式和指令集都了如指掌。

    总结为,cglib继承被代理的类,重写方法,织入通知,动态生成字节码并运行,要求被代理的类不能final修饰符修饰。

    <!-- 引入cglib依赖 -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
    
    package com.nobody.cglib;
    
    /**
     * @Description 委托类,贵州茅台
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class Moutai {
    
        public void product() {
            System.out.println("生产贵州茅台...");
        }
    }
    
    package com.nobody.cglib;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * @Description 动态代理类,实现MethodInterceptor方法拦截器接口
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class CglibProxy implements MethodInterceptor {
    
        // 被代理的目标对象
        private Object target;
    
        // 动态生成一个新的类,使用父类的无参构造方法创建一个指定了特定回调的代理实例
    
        /**
         * 动态生成一个新的类
         * 
         * @param target
         * @return
         */
        public Object getProxyObject(Object target) {
            this.target = target;
            // 增强器,动态代码生成器
            Enhancer enhancer = new Enhancer();
            // 回调方法
            enhancer.setCallback(this);
            // 设置生成代理类的父类类型
            enhancer.setSuperclass(target.getClass());
            // 动态生成字节码并返回代理对象
            return enhancer.create();
        }
    
        /**
         * 拦截方法
         * 
         * @param o CGLib动态生成的代理类实例
         * @param method 上文中实体类所调用的被代理的方法引用
         * @param objects 方法参数值列表
         * @param methodProxy 生成的代理类对方法的代理引用
         * @return 代理对象
         * @throws Throwable
         */
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
                throws Throwable {
            System.out.println("京东商城下订单");
            Object result = methodProxy.invoke(target, objects);
            System.out.println("京东商城发快递");
            return result;
        }
    }
    
    package com.nobody.cglib;
    
    /**
     * @Description
     * @Author Mr.nobody
     * @Date 2021/2/12
     * @Version 1.0
     */
    public class Main {
        public static void main(String[] args) {
            // 目标对象
            Moutai moutai = new Moutai();
            // 代理对象
            Moutai proxy = (Moutai) new CglibProxy().getProxyObject(moutai);
            // 执行代理对象的方法
            proxy.product();
        }
    }
    
    //输出结果
    京东商城下订单
    生产贵州茅台...
    京东商城发快递
    

    Cglib动态代理注意的2点:

    1. 被代理类不能是 final 修饰的。
    2. 需要扩展的方法不能有 final 或 static 关键字修饰,不然不会被拦截,即执行方法只会执行目标对象的方法,不会执行方法扩展的内容。

    四、两种动态代理区别

    JDK动态代理是基于反射机制,生成一个实现代理接口的匿名类。而Cglib动态代理是基于继承机制,继承被代理类,底层是基于asm第三方框架对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

    JDK动态代理是生成类的速度快,后续执行类的方法操作慢;Cglib动态代理是生成类的速度慢,后续执行类的方法操作快。

    JDK只能针对接口编程,Cglib可以针对类和接口。在Springboot项目中,在配置文件中增加 spring.aop.proxy-target-class=true 即可强制使用Cglib动态代理实现AOP。

    如果目标对象实现了接口,默认情况下是采用JDK动态实现AOP,如果目标对象没有实现接口,必须采用CGLIB库动态实现AOP。

    此演示项目已上传到Github,如有需要可自行下载,欢迎 Star

    https://github.com/LucioChn/dynamic-proxy-demo

    展开全文
  • 上篇介绍了一下静态代理:Java中的代理模式——静态代理以及分析静态代理的缺点 也分析了一下静态代理的缺点: 1、由于静态代理中的代理类是针对某一个类去做代理的,那么假设一个系统中有100个Service,则需要...
  • 实例理解JDK动态代理和Cglib动态代理及其区别 深入理解设计模式之代理模式 代理商代理化妆品生产理解JDK动态代理 代理商代理汽车制造理解Cglib动态代理
  • Java JDK 动态代理(AOP)使用及实现原理分析

    万次阅读 多人点赞 2019-05-08 21:28:06
    二、Java 动态代理类 三、JDK的动态代理怎么使用? 四、动态代理怎么实现的? 五、结论 一、什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为...
  • 什么是Java动态代理,如何实现一个动态代理例子

    千次阅读 多人点赞 2019-09-04 11:06:15
    Java动态代理 一、概述 1. 什么是代理 我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说...
  • 轻松学,Java 中的代理模式及动态代理

    万次阅读 多人点赞 2017-06-29 22:08:55
    前几天我写了《秒懂,Java 注解 (Annotation)你可以这样学》,因为注解其实算反射技术中的一部分,然后我想了一下,反射技术中还有个常见的概念就是动态代理,于是索性再写一篇关于动态代理的博文好了。...
  • 简介 JDK动态代理就是在程序运行时,运用反射机制...动态代理又分为JDK动态代理和CGLIB动态代理,一个通过反射生成代理类,一个通过asm开源包,修改字节码生成子类。区别在于JDK只能代理接口,所以有需要代理的类...
  • jdk动态代理和cglib动态代理详解

    千次阅读 多人点赞 2018-08-22 15:14:11
    本文内容概括: 静态代理概述 基于继承方式实现静态代理 基于聚合方式实现静态代理 ...如上图,代理模式可分为动态代理和静态代理,我们比较常用的有动态代理中的jdk动态代理和Cglib代理,像spr...
  • 两万+的吐血总结。代理模式(Proxy Pattern)是一个使用频率非常高的设计模式,其定义如下:Provide a surrogate or placeholder for another object to ...以及结合spring和jdk源码分析aop原理,手写jdk动态代理方式。
  • CGlib动态代理类的jar包

    千次下载 热门讨论 2014-01-14 23:50:28
    CGlib动态代理类jar包,一共四个jar包,模拟CGlib动态代理用得到。
  • 动态代理是 AOP(Aspect Orient Programming)编程思想,理解动态代理原理,对学习AOP框架至关重要。 JDK动态代理不需要任何外部依赖,但是只能基于接口进行代理;CGLIB通过继承的方式进行代理,无论目标对象有没有...
  • 秒懂Java代理与动态代理模式

    万次阅读 多人点赞 2018-06-30 17:08:23
    什么是动态代理模式?二者什么关系?具体如何实现?什么原理?如何改进?这即为我们学习一项新知识的正确打开方式,我们接下来会以此展开,让你秒懂。 概念 什么是代理模式 定义:为其他对象提供一种代理以控制...
  • 为其他对象提供一种代理以控制这个对象的访问,在某些情况下一个对象不能直接访问那个对象时,代理就起到了客户端和被代理对象(委托类)中介作用。 代理类和委托类都有同样接口。 好处:可以不用动原来类的逻辑,...
  • 动态代理与静态代理区别

    万次阅读 多人点赞 2018-09-18 09:26:55
    一、代理概念  为某个对象提供一个代理,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托...
  • Java笔试面试-反射和动态代理

    万次阅读 多人点赞 2019-09-19 09:28:41
    A:cglib 的性能更高 B:Spring 中有使用 cglib 来实现动态代理 C:Spring 中有使用 JDK 原生的动态代理 D:JDK 原生动态代理性能更高 答:D 题目解析:Spring 动态代理的实现方式有两种:cglib 和 JDK 原生动态...
  • 深入理解代理模式:静态代理与JDK动态代理

    万次阅读 多人点赞 2018-03-01 00:22:11
    Java动态代理机制的出现使得Java开发人员只需要简单地指定一组接口及委托类对象便能动态地获得代理类,并且其所生成的代理类在将所有的方法调用分派到委托对象上反射执行的同时,还可以对方法进行增强,这也正是...
  • Android 动态代理以及利用动态代理实现 ServiceHook

    万次阅读 多人点赞 2017-02-25 20:44:15
     首先我们要介绍的就是 Java 动态代理,Java 的动态代理涉及到两个类:InvocationHandler 接口和 Proxy 类,下面我们会着重介绍一下这两个类,并且结合实例来着重分析一下使用的正确姿势等。在这之前简单介绍一下 ...
  • java动态代理实现与原理详细分析

    千次阅读 多人点赞 2019-02-08 23:22:43
    一、代理 (1)、什么是代理? 大道理上讲代理是一种软件设计模式,目的地希望能做到代码重用。具体上讲,代理这种设计模式是通过不直接访问被代理对象的方式,而访问被代理对象的方法。这个就好比 商户----&gt;...
  • cglib动态代理和jdk动态代理的区别与应用

    万次阅读 多人点赞 2018-06-13 15:46:52
    在Java中一般是2种,静态代理和动态代理动态代理又分为CGLIB和jdk自带。   3,如何使用? 3.1静态代理: public class StaticProxy { public static void main(String[] args) { Singer singer = new Agent(new ...
  • 超全MyBatis动态代理详解!(绝对干货)

    千次阅读 多人点赞 2021-01-20 09:30:23
    说 Mybatis 动态代理之前,先来看一下平常我们写的动态代理 Demo,抛砖引玉 一般来说定义 JDK 动态代理分为三个步骤,如下所示 定义代理接口 定义代理接口实现类 定义动态代理调用处理器 三步代码如下所示,玩过动态...
  • 静态代理和动态代理的区别和联系

    千次阅读 2019-06-13 22:46:58
    静态代理和动态代理的区别和联系 代理Proxy: Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个...
  • Spring的静态代理和动态代理

    千次阅读 多人点赞 2019-06-02 21:14:06
    动态代理可以分为两种,一种是基于JDk的动态代理一种是基于CGLIB的动态代理。在spring中使用他们的区别是:当目标对象有实现接口时,默认使用jdk动态代理的方式,当目标类没有实现接口时使用cglib的动态代理方式,...
  • Java动态代理的实现原理

    万次阅读 多人点赞 2018-09-25 11:20:57
    静态代理 动态代理 基于JDK的动态代理 基于继承的动态代理

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 456,390
精华内容 182,556
关键字:

动态代理