Spring09_动态代理

Posted CodeAction的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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是代理工厂类的对象,运行测试方法,测试。

以上是关于Spring09_动态代理的主要内容,如果未能解决你的问题,请参考以下文章

Spring5学习笔记 — “Spring AOP底层原理(动态代理)”

Spring5学习笔记 — “Spring AOP底层原理(动态代理)”

阶段3 2.Spring_07.银行转账案例_9 基于子类的动态代理

spring_03AOP编程

Spring 从入门到精通系列 09——转账方法的事务问题与动态代理

Spring框架_代理模式(静态代理,动态代理,cglib代理)