• Java核心(四)——动态代理


    iwehdio的博客园:https://www.cnblogs.com/iwehdio/

    学习自:

    1、静态代理

    • 代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象。如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求。
    • 需要在业务代码前后打印日志,如果直接添加的话:
      1. 直接修改源程序,不符合开闭原则。应该对扩展开放,对修改关闭
      2. 如果有几十个、上百个方法,修改量太大
      3. 存在重复代码(都是在核心代码前后打印日志)
      4. 日志打印硬编码在类中,不利于后期维护
    • 静态代理的实现:
      • 编写一个代理类,实现与目标对象相同的接口,并在内部维护一个目标对象的引用。
      • 通过构造器塞入目标对象,在代理对象中调用目标对象的同名方法,并添加前拦截,后拦截等所需的业务功能。
      • 静态代理只解决了上面四个缺点中的第一点。
      • 如果需要对多个类进行改造,就需要对每个类都实现一个代理。
      • 我们其实想要的并不是代理类,而是代理对象!那么,能否让JVM根据接口自动生成代理对象呢?

    2、动态代理

    • 思路:通过获得被代理对象的接口的Class对象,获取被代理对象的方法信息。

    • 代理Class其实就是附有构造器的接口Class,一样的类结构信息,却能创建实例。

      image-20210120203829657

    • java.lang.reflect.Proxy.getProxyClass():返回代理类的Class对象。

    • 也就说,只要传入目标类实现的接口的Class对象,getProxyClass()方法即可返回代理Class对象,而不用实际编写代理类。

    image-20210120204035393

    image-20210120204331951

    • 动态代理使用流程:

      image-20210120204606915

      • 每次调用代理对象的方法都会调用invoke(),且invoke()的返回值就是代理方法的返回值。

      image-20210120204707762

    • 动态代理底层调用逻辑:

      • 静态代理:往代理对象的构造器传入目标对象,然后代理对象调用目标对象的同名方法。
      • 动态代理:constructor反射创建代理对象时,需要传入InvocationHandler。

      image-20210120205030879

      • 引入InvocationHandler的好处是:

        • JVM创建代理对象时不必考虑方法实现,只要造一个空壳的代理对象
        • 后期代理对象想要什么样的方法实现,我写在invocationHandler对象的invoke()方法里送进来便是
      • invocationHandler的作用,像是把“方法”和“方法体”分离。

      • 代理对象中有个成员变量invocationHandler,每一个方法里只有一句话:handler.invoke()。所以调任何一个代理方法,最终都会跑去调用invoke()方法。

      • invoke()方法是代理对象和目标对象的桥梁:

        image-20210120205350139

    • 但是我们真正想要的结果是:调用代理对象的方法时,去调用目标对象的方法。

    • 所以,接下来努力的方向就是:设法在invoke()方法得到目标对象,并调用目标对象的同名方法。

    • invoke()方法的形参:

      • Object proxy:很遗憾,是代理对象本身,而不是目标对象(不要调用,会无限递归)
      • Method method:本次被调用的代理对象的方法
      • Obeject[] args:本次被调用的代理对象的方法参数
    • 这样传入目标对象即可:

      public class ProxyTest {
      	public static void main(String[] args) throws Throwable {
      		CalculatorImpl target = new CalculatorImpl();
      		Calculator calculatorProxy = (Calculator) getProxy(target);
      		calculatorProxy.add(1, 2);
      		calculatorProxy.subtract(2, 1);
      	}
      
      	private static Object getProxy(final Object target) throws Exception {
      		Object proxy = Proxy.newProxyInstance(
      				target.getClass().getClassLoader(),/*类加载器*/
      				target.getClass().getInterfaces(),/*让代理对象和目标对象实现相同接口*/
      				new InvocationHandler(){/*代理对象的方法最终都会被JVM导向它的invoke方法*/
      					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      						System.out.println(method.getName() + "方法开始执行...");
      						Object result = method.invoke(target, args);
      						System.out.println(result);
      						System.out.println(method.getName() + "方法执行结束...");
      						return result;
      					}
      				}
      		);
      		return proxy;
      	}
      }
      

    3、AOP

    • 自己写了一个UserController,以及UserServiceImpl implements UserService,并且在UserController中注入Service层对象:

      @Autowired
      private UserService userService;
      

      那么,这个userService一定是我们写UserServiceImpl的实例吗?

      • 给UserServiceImpl加了@Transactional 注解,那么注入的就不是我们写UserServiceImpl,而是其的动态代理。
      • 要用动态代理完成事务管理,还需要编写一个通知类,并把通知对象传入代理对象,通知负责事务的开启和提交,并在代理对象内部调用目标对象同名方法完成业务功能。

      image-20210122182814925

      • Spring为了实现事务,也编写了一个通知类,TransactionManager。利用动态代理创建代理对象时,Spring会把transactionManager织入代理对象,然后将代理对象注入。
    • 不用AOP时,交叉业务直接写在方法内部的前后,用了AOP交叉业务写在方法调用前后。这与AOP的底层实现方式有关:动态代理其实就是代理对象调用目标对象的同名方法,并在调用前后加增强代码。不过这两种最终运行效果是一样的。

    • AOP的实现:

      • 需要一个通知类(TransactionManager)执行事务,一个代理工厂帮助生成代理对象,然后利用动态代理将事务代码织入代理对象的各个方法中。

      • 加了个@MyTransactional后,代理工厂给我返回一个代理对象:

        image-20210122183331475

      • 代理对象方法 = 事务 + 目标对象方法。

        image-20210122183349373

      • 事务操作,必须使用同一个Connection对象。如何保证?

        • 第一次从数据源获取Connection对象并开启事务后,将它存入当前线程的ThreadLocal中,等到了DAO层,还是从ThreadLocal中取。

        image-20210122183530618

    • AOP事务的实现:

      • ConnectionUtils工具类

        public class ConnectionUtils {
        
            private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
        
            private static BasicDataSource dataSource = new BasicDataSource();
        
            //静态代码块,设置连接数据库的参数
            static{
                dataSource.setDriverClassName("com.mysql.jdbc.Driver");
                dataSource.setUrl("jdbc:mysql://localhost:3306/test");
                dataSource.setUsername("root");
                dataSource.setPassword("123456");
            }
        
        
            /**
             * 获取当前线程上的连接
             * @return
             */
            public Connection getThreadConnection() {
                try{
                    //1.先从ThreadLocal上获取
                    Connection conn = tl.get();
                    //2.判断当前线程上是否有连接
                    if (conn == null) {
                        //3.从数据源中获取一个连接,并且存入ThreadLocal中
                        conn = dataSource.getConnection();
                        tl.set(conn);
                    }
                    //4.返回当前线程上的连接
                    return conn;
                }catch (Exception e){
                    throw new RuntimeException(e);
                }
            }
        
            /**
             * 把连接和线程解绑
             */
            public void removeConnection(){
                tl.remove();
            }
        }
        
      • AOP通知(事务管理器)

        /**
         * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
         */
        public class TransactionManager {
        
            private ConnectionUtils connectionUtils;
        
            public void setConnectionUtils(ConnectionUtils connectionUtils) {
                this.connectionUtils = connectionUtils;
            }
        
            /**
             * 开启事务
             */
            public  void beginTransaction(){
                try {
                    connectionUtils.getThreadConnection().setAutoCommit(false);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        
            /**
             * 提交事务
             */
            public  void commit(){
                try {
                    connectionUtils.getThreadConnection().commit();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        
            /**
             * 回滚事务
             */
            public  void rollback(){
                try {
                    connectionUtils.getThreadConnection().rollback();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        
        
            /**
             * 释放连接
             */
            public  void release(){
                try {
                    connectionUtils.getThreadConnection().close();//还回连接池中
                    connectionUtils.removeConnection();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        
      • 自定义注解

        @Target({ElementType.TYPE, ElementType.METHOD})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface MyTransactional {
        }
        
      • Service

        public interface UserService {
        	void getUser();
        }
        
         
        public class UserServiceImpl implements UserService {
        	@Override
        	public void getUser() {
        		System.out.println("service执行...");
        	}
        }
        
      • 实例工厂

        public class BeanFactory {
        
        	public Object getBean(String name) throws Exception {
        		//得到目标类的Class对象
        		Class<?> clazz = Class.forName(name);
        		//得到目标对象
        		Object bean = clazz.newInstance();
        		//得到目标类上的@MyTransactional注解
        		MyTransactional myTransactional = clazz.getAnnotation(MyTransactional.class);
        		//如果打了@MyTransactional注解,返回代理对象,否则返回目标对象
        		if (null != myTransactional) {
        			ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        			TransactionManager txManager = new TransactionManager();
        			txManager.setConnectionUtils(new ConnectionUtils());
        			//装配通知和目标对象
        			proxyFactoryBean.setTxManager(txManager);
        			proxyFactoryBean.setTarget(bean);
        			Object proxyBean = proxyFactoryBean.getProxy();
        			//返回代理对象
        			return proxyBean;
        		}
        		//返回目标对象
        		return bean;
        	}
        }
        
      • 代理工厂

        public class ProxyFactoryBean {
        	//通知
        	private TransactionManager txManager;
        	//目标对象
        	private Object target;
        
        	public void setTxManager(TransactionManager txManager) {
        		this.txManager = txManager;
        	}
        
        	public void setTarget(Object target) {
        		this.target = target;
        	}
        
        	//传入目标对象target,为它装配好通知,返回代理对象
        	public Object getProxy() {
        		Object proxy = Proxy.newProxyInstance(
        				target.getClass().getClassLoader(),/*1.类加载器*/
        				target.getClass().getInterfaces(), /*2.目标对象实现的接口*/
        				new InvocationHandler() {/*3.InvocationHandler*/
        					@Override
        					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        						try {
        							//1.开启事务
        							txManager.beginTransaction();
        							//2.执行操作
        							Object retVal = method.invoke(target, args);
        							//3.提交事务
        							txManager.commit();
        							//4.返回结果
        							return retVal;
        						} catch (Exception e) {
        							//5.回滚事务
        							txManager.rollback();
        							throw new RuntimeException(e);
        						} finally {
        							//6.释放连接
        							txManager.release();
        						}
        
        					}
        				}
        		);
        		return proxy;
        	}
        
        }
        

    iwehdio的博客园:https://www.cnblogs.com/iwehdio/
    来源与结束于否定之否定。
  • 相关阅读:
    cmcc_simplerop
    WeiFenLuo.winFormsUI.Docking.dll的使用
    MySQL转换Oracle的七大注意事项
    icsharpcode
    详细介绍IIS7基于WAS 部署WCF服务《收藏》
    Win2008 IIS7日期格式更改方法 《转》
    SVCUtil使用说明(生成代理类)《收藏》
    Oracle中的高效语句
    WCF配置文件全攻略《收藏》
    设计高效合理的MySQL查询语句
  • 原文地址:https://www.cnblogs.com/iwehdio/p/14314930.html
Copyright © 2020-2023  润新知