zoukankan      html  css  js  c++  java
  • Spring09_动态代理

    本教程源码请访问:tutorial_demo

    一、什么是动态代理

    1.1、概念

    动态代理的用途与装饰模式很相似,就是为了对某个对象进行增强。所有使用装饰者模式的案例都可以使用动态代理来替换。

    特点:字节码随用随创建,随用随加载;

    作用:不修改源码的基础上对方法增强;

    学习目的:为了学习AOP的原理做准备。

    1.2、实现方式

    两种方式

    1. 基于接口的动态代理,JDK官方提供,被代理类最少实现一个接口,如果没有则不能使用
    2. 基于子类的动态代理,第三方cglib库提供。

    我们这篇教程使用基于接口的动态代理方式讲解,所有案例都使用这种方式。

    1.3、需要明确的几个概念

    目标对象:被增强的对象。

    代理对象:需要目标对象,然后在目标对象上添加了增强后的对象。

    目标方法:被增强的方法。

    代理对象 = 目标对象 + 增强
    

    到现在为止,我们需要知道有一种方式可以在不改变目标对象方法的前提下,对方法进行增强,这个方式就是动态代理。使用它,我们需要提供目标对象增强生成代理对象

    得到了代理对象就相当于有了一个强化版的目标对象,运行相关方法,除了运行方法本身,增强的内容也会被运行,从而实现了在不改变源码的前提下,对方法进行增强。

    1.4、基于接口的动态代理方式详解

    1.4.1、如何生成代理对象

    使用Proxy类中的newProxyInstance方法。

    1.4.2、newProxyInstance方法参数详解

    ClassLoader loader

    类加载器类型,你不用去理睬它,你只需要知道怎么可以获得它就可以了,获取方法:

    this.class.getClassLoader();
    

    只要你有一个Class对象就可以获取到ClassLoader对象。

    Class[] interfaces

    指定newProxyInstance()方法返回的对象要实现哪些接口,因为是数组,可以指定多个接口。

    InvocationHandler h

    三个参数中最重要的一个参数,是一个接口,叫调用处理器。这个接口只有一个方法,即invoke()方法。它是对代理对象所有方法的唯一实现。也就是说,无论你调用代理对象上的哪个方法,其实都是在调用InvocationHandler的invoke()方法。

    1.4.3、invoke()方法参数详解

    执行被代理对象的任何接口方法都会经过该方法。

    Object proxy:代理对象,也就是Proxy.newProxyInstance()方法返回的对象,通常我们用不上它。

    Method method:表示当前被调用方法的反射对象,例如m.fun(),那m么method就是fun()方法的反射对象;

    Object[] args:表示当前被调用方法的参数,当然m.fun()这个调用是没有参数的,所以args是一个长度为0的数组。

    二、动态代理案例

    下面通过一个案例,说明动态代理的用途。

    2.1、创建Maven工程并添加坐标

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.codeaction</groupId>
        <artifactId>proxy</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </project>
    

    2.2、创建一个IWaiter接口

    package org.codeaction.proxy;
    
    //表示服务员的接口
    public interface IWaiter {
        //提供服务的方法
        void serve();
    }
    

    2.3、创建一个IWaiter接口的实现类

    package org.codeaction.proxy;
    
    //表示男服务员
    public class ManWaiter implements IWaiter {
        @Override
        public void serve() {
            System.out.println("服务...");
        }
    }
    

    目前存在的问题,我希望让ManWaiter提供服务的时候(调用serve方法)打印如下信息:

    你好...
    服务...
    再见...
    

    我们可以这样做:

    package org.codeaction.proxy;
    
    //表示男服务员
    public class ManWaiter implements IWaiter {
        @Override
        public void serve() {
            System.out.println("你好...");
            System.out.println("服务...");
            System.out.println("再见...");
        }
    }
    

    但是这样我们修改了serve方法,如果将来有其他需求,我们还要再修改serve方法,这显然很繁琐,是不可取的,我们可以使用动态代理的方式在不修改源码的基础上对serve方法进行增强。

    2.4、创建测试类使用动态代理

    package org.codeaction.test;
    
    
    import org.codeaction.proxy.IWaiter;
    import org.codeaction.proxy.ManWaiter;
    import org.junit.Test;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class MyTest {
        @Test
        public void TestProxy() {
            //目标对象
            IWaiter manWaiter = new ManWaiter();
    
            /**
             * 三个参数,用来创建代理对象
             */
            ClassLoader loader = this.getClass().getClassLoader();
            Class[] interfaces = {IWaiter.class};
            InvocationHandler handler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object resultValue = null;
    
                    System.out.println("你好...");
                    resultValue = method.invoke(manWaiter, args);//调用目标对象的目标方法
                    System.out.println("再见...");
                    return resultValue;
                }
            };
            //得到代理对象,代理对象就是在目标对象的基础上进行了增强的对象
            IWaiter waiter = (IWaiter) Proxy.newProxyInstance(loader, interfaces, handler);
            //前面添加“您好”,后面添加“再见”
            waiter.serve();
        }
    }
    
    

    运行测试方法,输出如下:

    你好...
    服务...
    再见...
    

    通过上面的代码及运行结果我们发现:

    1. 使用动态代理需要提供:目标对象、三大参数;
    2. 生成的代理对象是实现了三大参数中第二个参数的所有接口的对象;
    3. 运行代理对象的方法,就是运行invoke方法;
    4. 在invoke方法中实现增强。

    三、动态代理使用代理工厂实现

    上面的案例中,目标对象和增强绑定在了一起,无法自由切换,不灵活,接下来我们创建一个代理工厂来实现动态代理。

    3.1、创建前置增强接口

    package org.codeaction.proxy;
    
    //前置增强
    public interface BeforeAdvice {
        void before();
    }
    

    3.2、创建后置增强接口

    package org.codeaction.proxy;
    
    //后置增强
    public interface AfterAdvice {
        void after();
    }
    

    3.3、创建代理工厂

    package org.codeaction.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 这个类用来生成代理对象
     * 需要的参数:
     *  * 目标对象
     *  * 增强
     * 怎么用?
     *  1.创建代理工厂
     *  2.给工厂设置三样东西:
     *      * 目标对象:setTargetObject(xxx);
     *      * 前置增强:setBeforeAdvice(该接口的实现)
     *      * 后置增强:setAfterAdvice(该接口的实现)
     *  3.调用createProxy()得到代理对象
     *      * 执行代理对象方法时:
     *          > 执行BeforeAdvice的before()
     *          > 目标对象的目标方法
     *          > 执行AfterAdvice的after()
     */
    public class ProxyFactory {
        private Object targetObject;//目标对象
        private BeforeAdvice beforeAdvice;//前置增强
        private AfterAdvice afterAdvice;//后置增强
    
        public Object getTargetObject() {
            return targetObject;
        }
    
        public void setTargetObject(Object targetObject) {
            this.targetObject = targetObject;
        }
    
        public BeforeAdvice getBeforeAdvice() {
            return beforeAdvice;
        }
    
        public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
            this.beforeAdvice = beforeAdvice;
        }
    
        public AfterAdvice getAfterAdvice() {
            return afterAdvice;
        }
    
        public void setAfterAdvice(AfterAdvice afterAdvice) {
            this.afterAdvice = afterAdvice;
        }
    
        //用来生成代理对象
        public Object createProxyObject() {
            //三大参数
            ClassLoader classLoader = this.getClass().getClassLoader();
            Class[] interfaces = this.targetObject.getClass().getInterfaces();
            InvocationHandler handler = new InvocationHandler() {
                //在调用代理对象的方法时会执行这里的内容
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object resultValue = null;
    
                    if(beforeAdvice != null) {
                        //执行前置增强
                        beforeAdvice.before();
                    }
                    //执行目标对象的目标方法
                    resultValue = method.invoke(targetObject, args);
                    if(afterAdvice != null) {
                        //执行后置增强
                        afterAdvice.after();
                    }
                    //返回目标对象的返回值
                    return resultValue;
                }
            };
    
            //得到代理对象
            return Proxy.newProxyInstance(classLoader, interfaces, handler);
        }
    }
    

    3.4、在测试类中添加测试方法

    @Test
    public void testProxyFactory() {
        //创建工厂
        ProxyFactory factory = new ProxyFactory();
        //设置目标对象
        factory.setTargetObject(new ManWaiter());
        //设置前置增强
        factory.setBeforeAdvice(new BeforeAdvice() {
            @Override
            public void before() {
                System.out.println("你好...");
            }
        });
        //设置后置增强
        factory.setAfterAdvice(new AfterAdvice() {
            @Override
            public void after() {
                System.out.println("再见...");
            }
        });
        //创建代理对象
        IWaiter waiter = (IWaiter) factory.createProxyObject();
        //执行代理对象方法
        waiter.serve();
    }
    

    运行测试方法,输出如下:

    你好...
    服务...
    再见...
    

    四、使用代理工厂的方式修改上一节的代码

    在上一篇文章我们将纯注解方式结合Apache Commons DbUtils实现单表的CRUD操作的代码修改成了支持事务的版本,每一个Service方法都要开启事务,提交事务,回滚事务代码冗余,如果JdbcUtils中相关方法的方法名修改,那么Service中每个调用位置都有修改,为了解决上面的问题,我们使用动态代理的方式修改上一节的代码。

    4.1、创建前置增强接口

    package org.codeaction.proxy;
    
    public interface BeforeAdvice {
        void before() throws Exception;
    }
    

    4.2、创建后置增强接口

    package org.codeaction.proxy;
    
    public interface AfterAdvice {
        void after() throws Exception;
    }
    

    4.3、创建特殊增强接口

    这个是用来进行回滚的,就教他特殊增强吧。

    package org.codeaction.proxy;
    
    public interface ActAdvice {
        void act() throws Exception;
    }
    

    4.4、创建代理工厂

    package org.codeaction.proxy;
    
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    @Component
    public class ProxyFactory {
        private Object targetObject;
        private BeforeAdvice beforeAdvice;
        private AfterAdvice afterAdvice;
        private ActAdvice actAdvice;
    
        public ActAdvice getActAdvice() {
            return actAdvice;
        }
    
        public void setActAdvice(ActAdvice actAdvice) {
            this.actAdvice = actAdvice;
        }
    
        public Object getTargetObject() {
            return targetObject;
        }
    
        public void setTargetObject(Object targetObject) {
            this.targetObject = targetObject;
        }
    
        public BeforeAdvice getBeforeAdvice() {
            return beforeAdvice;
        }
    
        public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
            this.beforeAdvice = beforeAdvice;
        }
    
        public AfterAdvice getAfterAdvice() {
            return afterAdvice;
        }
    
        public void setAfterAdvice(AfterAdvice afterAdvice) {
            this.afterAdvice = afterAdvice;
        }
    
        public Object createProxyObject() {
    
            ClassLoader classLoader = this.getClass().getClassLoader();
            Class[] interfaces = this.targetObject.getClass().getInterfaces();
            InvocationHandler handler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object resultValue = null;
    
                    try {
                        if(beforeAdvice != null) {
                            beforeAdvice.before();
                        }
                        resultValue = method.invoke(targetObject, args);
                        if(afterAdvice != null) {
                            afterAdvice.after();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        if(actAdvice != null) {
                            actAdvice.act();
                        }
                    }
                    return resultValue;
                }
            };
    
            return Proxy.newProxyInstance(classLoader, interfaces, handler);
        }
    }
    

    4.5、修改Service接口的实现类AccountServiceImpl

    去掉所有的和事务相关的代码,让Service只关注业务

    package org.codeaction.service.impl;
    
    import org.codeaction.dao.IAccountDao;
    import org.codeaction.domain.Account;
    import org.codeaction.service.IAccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService {
        @Autowired
        private IAccountDao accountDao;
    
        @Override
        public List<Account> findAll() throws Exception {
            return accountDao.findAll();
        }
    
        @Override
        public Account findById(Integer id) throws Exception {
            return accountDao.findById(id);
        }
    
        @Override
        public void save(Account account) throws Exception {
            accountDao.save(account);
        }
    
        @Override
        public void update(Account account) throws Exception {
            accountDao.update(account);
        }
    
        @Override
        public void delete(Integer id) throws Exception {
            accountDao.delete(id);
        }
    
        @Override
        public void transfer(Integer srcId, Integer dstId, Float money) throws Exception {
            Account src = accountDao.findById(srcId);
            Account dst = accountDao.findById(dstId);
    
            if(src == null) {
                throw new RuntimeException("转出用户不存在");
            }
    
            if(dst == null) {
                throw new RuntimeException("转入用户不存在");
            }
    
            if(src.getMoney() < money) {
                throw new RuntimeException("转出账户余额不足");
            }
    
            src.setMoney(src.getMoney() - money);
            dst.setMoney(dst.getMoney() + money);
    
            accountDao.update(src);
    
           //int x = 1/0;
    
            accountDao.update(dst);
        }
    }
    

    4.6、修改主配置类

    package org.codeaction.config;
    
    import org.codeaction.proxy.ActAdvice;
    import org.codeaction.proxy.AfterAdvice;
    import org.codeaction.proxy.BeforeAdvice;
    import org.codeaction.proxy.ProxyFactory;
    import org.codeaction.service.IAccountService;
    import org.codeaction.util.JdbcUtils;
    import org.springframework.context.annotation.*;
    
    import java.sql.SQLException;
    
    @Configuration
    @ComponentScan(basePackages = "org.codeaction")
    @PropertySource("classpath:jdbc.properties")
    @Import(JdbcConfig.class)
    public class MyConfig {
        /**
         *
         * @param factory 代理工厂
         * @param accountService 目标对象
         * @return
         */
        @Bean("proxyAccountService")
        public IAccountService createProxyAccountService(ProxyFactory factory, IAccountService accountService) {
            factory.setTargetObject(accountService);
            factory.setBeforeAdvice(new BeforeAdvice() {
                @Override
                public void before() throws Exception {
                    //开启事务
                    JdbcUtils.beginTransaction();
                }
            });
    
            factory.setAfterAdvice(new AfterAdvice() {
                @Override
                public void after() throws Exception {
                    //提交事务
                    JdbcUtils.commitTransaction();
                }
            });
    
            factory.setActAdvice(new ActAdvice() {
                @Override
                public void act() throws Exception {
                    //回滚
                    JdbcUtils.rollbackTransaction();
                }
            });
    		//生成代理对象
            return (IAccountService)factory.createProxyObject();
        }
    }
    

    4.7、修改测试类

    package org.codeaction.test;
    
    import org.codeaction.config.MyConfig;
    import org.codeaction.domain.Account;
    import org.codeaction.service.IAccountService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import java.util.List;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = MyConfig.class)
    public class MyTest {
    
        //注入代理工厂对象
        @Autowired
        @Qualifier("proxyAccountService")
        private IAccountService accountService;
    
        @Test
        public void testFindAll() throws Exception {
            List<Account> accounts = accountService.findAll();
            for (Account account : accounts) {
                System.out.println(account);
            }
        }
    
        @Test
        public void testFindById() throws Exception {
            Account account = accountService.findById(3);
            System.out.println(account);
        }
    
        @Test
        public void testSave() throws Exception {
            Account account = new Account();
            account.setName("abc");
            account.setMoney(10000F);
    
            accountService.save(account);
    
            System.out.println(account);
        }
    
        @Test
        public void testDelete() throws Exception {
            accountService.delete(4);
        }
    
        @Test
        public void testUpdate() throws Exception {
            Account account = new Account();
            account.setId(5);
            account.setName("ab111111111c111");
            account.setMoney(10000F);
            accountService.update(account);
        }
    
        @Test
        public void testTrans() throws Exception {
            accountService.transfer(1, 2, 10F);
        }
    }
    

    注意这里注入的accountServie是代理工厂类的对象,运行测试方法,测试。

  • 相关阅读:
    C 习题
    gcc
    几何视角看线性方程组解的情况
    JAVA设计模式之工厂模式(简单工厂模式+工厂方法模式)
    为什么重写了equals(),还要重写hashCode()?
    关于ArrayList的越界问题?
    通过实例聊聊Java中的多态
    java异常处理实例分析
    Java: Integer用==比较时127相等128不相等的原因
    Java并发编程:Lock
  • 原文地址:https://www.cnblogs.com/codeaction/p/13027243.html
Copyright © 2011-2022 走看看