Spring学习(五):动态代理的两种实现方式(全网最容易懂)

Spring学习(五):动态代理的两种实现方式(全网最容易懂)

前言

要学习SpringAOP之前,肯定要弄清楚什么是动态代理,动态代理是怎样实现的,以及动态代理能解决什么问题。

一、什么是动态代理

1、字面意思,代理就是代替别人去做一些事情,如线下店代替工厂去卖电脑、代理工厂做售后工作,线下店就是代理商,从卖给工厂的获得的钱提取分成就是增强的方法。

2、Java中就是在不改变别别的类,对类做增强处理,如打印日志、事物的控制,权限的管理,后续我们都会介绍。

二、两种实现动态代理的方法

1、基于JDK的动态代理

基于接口的动态代理,用到的类是Proxy的newProxyInstance静态方法创建,要求被代理对象至少实现一个接口,如果没有,则不能创建代理对象。

2、基于cglib的动态代理

要导入cglib第三方库,使用的类是Enhancer的create静态方法创建,要求被代理类不能是最终类,即不能用final修饰,如String类。

三、代码演示

1、首先创建一个IProduct接口,并创建被代理类,实现这个接口

IProduct

public interface IProduct {
    String sell(Float money);
    void afterSell();
}

Product

public class Product implements IProduct {
    @Override
    public String sell(Float money) {
        System.out.println("代理员交给工厂:"+money);
        return "aaa";
    }
    @Override
    public void afterSell() {
        System.out.println("代理员做售后。。");
    }
}
2、通过JDK来实现动态代理,创建一个消费者Consumer

这里我们直接通过匿名内部类来实现,当然不是必须的

Consumer类

public class Consumer {
    public static void main(String[] args) {
    	// 创建一个被代理对象
        final Product product = new Product();
        // 创建一个代理对象,并在InvocationHandler的invoke方法里面,对被代理类的方法做增强
        IProduct proxyProduct = (IProduct) Proxy.newProxyInstance(product.getClass().getClassLoader(), product.getClass().getInterfaces(), new InvocationHandler() {
			// 实现具体的增强操作           
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 获取方法在运行中可能产生的返回值
                Object returnValue = null;
                Float money = (Float) args[0];
                if("sell".equals(method.getName())){
                	// 执行具体的方法
                    returnValue = method.invoke(product, money*0.8F);
                }
                return returnValue;
            }
        });
        System.out.println(proxyProduct.sell(1000F));
    }
}

代码分析

1、Proxy.newProxyInstance的三个参数

IProduct proxyProduct = (IProduct) Proxy.newProxyInstance(product.getClass().getClassLoader(), product.getClass().getInterfaces(), new InvocationHandler() {
}
  • ClassLoader loader获取被代理类的类加载器。
  • Class<?>[] interfaces获取被代理类的实现接口的数组。
  • InvocationHandler h在invok方法中对方法做增强处理。

2、invoke方法的三个参数

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
}
  • Object proxy 当前代理对象
  • Method method 当前方法
  • Object[] args方法传递的参数
3、通过cglib来实现动态代理,创建一个消费者Consumer
public class Consumer {
    public static void main(final String[] args) {
    	// 创建一个被代理对象,这里要求必须是final
        final Product product = new Product();
        Product proxyProduct =(Product) Enhancer.create(product.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Float money = (Float) objects[0];
                Object returnValue = null;
                if("sell".equals(method.getName())){
                    returnValue = method.invoke(product, 0.8f * money);
                }
                return returnValue;
            }
        });
        System.out.println(proxyProduct.sell(1000f));
    }
}

代码分析
1、Enhancer.create的2个参数

Product proxyProduct =(Product) Enhancer.create(product.getClass(), new MethodInterceptor() {
}
  • Class type被代理类的class文件
  • Callback callback一个Callback接口,我们通常使用MethodInterceptor接口,继承了Callback接口

2、intercept方法的参数

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
}
  • Method method当前方法
  • Object[] objects方法用到的参数数组
4、测试结果

代理员交给工厂:800.0
aaa

代理商收取了200块提成。

四、结合BeanFactory创建Bean的方式来控制事务

学完动态代理,可以结合BeanFactory创建Bean的方式来控制事务

1、改造事务分析

Spring学习(四):事务的学习之银行转账案例
原来的事务控制我们是写在Service层,现在我们要把重复代码抽取出来,统一交给代理对象去管理事务。
原Service代码

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
	@Autowired
    TransactionManager transactionManager;
    @Autowired
    IAccountDao accountDao;
    @Autowired
    private ConnectionUtils connectionUtils;
	
	@Override
    public void updateAccount(Account account) {
        try {
            transactionManager.beginTransaction();
            accountDao.updateAccount(account);
            int a = 1/0; // 模拟业务层出错
            transactionManager.commitTransaction();
        }catch (Exception e){
            transactionManager.rollbackTransaction();
            e.printStackTrace();
        }finally {
            transactionManager.release();
        }
    }
}

现在我们只留一行代码

accountDao.updateAccount(account);

2、代码编写思路分析

  • 创建一个BeanFactory,里面注入一个AccountService。
  • 在get方法中返回一个代理对象。
  • 选择一种动态代理的实现方法,编写代理详细实现代码。
  • 配置bean.xml配置文件

3、代码的实现

BeanFactory类

public class BeanFactory {

    @Autowired
    /**
     * 由于配置文件有2个AccountService实现类的bean配置,所以要指定beanId才可以自动注入
     * proxyAccountService、accountService
     */
    @Qualifier("accountService")
    private IAccountService iAccountService;

    @Autowired
    TransactionManager transactionManager;

	// 通过JDK动态代理实现
    public IAccountService getAccountService() {
        IAccountService proxyIaccountService = (IAccountService) Proxy.newProxyInstance(iAccountService.getClass().getClassLoader(), iAccountService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object returnValue = null;
                try {
                    transactionManager.beginTransaction();
                    System.out.println("开启事务。。。");
                    System.out.println("执行【"+method.getName()+"】方法。。。");
                    returnValue = method.invoke(iAccountService, args);
                    System.out.println(5/0);
                    transactionManager.commitTransaction();
                    System.out.println("COMMIT事务。。。");
                }catch (Exception e){
                    System.out.println("ROLLBACK事务。。。");
                    transactionManager.rollbackTransaction();
                    e.printStackTrace();
                }finally {
                    transactionManager.release();
                }
                return returnValue;
            }
        });
        return proxyIaccountService;
    }

	// 通过Cglib动态代理实现
	public IAccountService getAccountServiceByCglib() {
        IAccountService proxyAccountServiceByCglib = (IAccountService) Enhancer.create(IAccountService.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object returnValue = null;
                try {
                    transactionManager.beginTransaction();
                    System.out.println("开启事务。。。");
                    System.out.println("执行【"+method.getName()+"】方法。。。");
                    returnValue = method.invoke(iAccountService, objects);
                    System.out.println(5/0);
                    transactionManager.commitTransaction();
                    System.out.println("COMMIT事务。。。");
                }catch (Exception e){
                    System.out.println("ROLLBACK事务。。。");
                    transactionManager.rollbackTransaction();
                    e.printStackTrace();
                }finally {
                    transactionManager.release();
                }
                return returnValue;            }
        });
        return proxyAccountServiceByCglib;
    }
    
    public void setIAccountService(IAccountService iAccountService) {
        this.iAccountService = iAccountService;
    }
}

xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!--找到对应的XML头,和打开包扫描-->
    <context:component-scan base-package="com"/>

    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
        <property name="user" value="root"/>
        <property name="password" value="123456" />
    </bean>

    <bean id="connectionUtils" class="com.utils.ConnectionUtils">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <bean id="transationManager" class="com.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils" />
    </bean>

	<!-- 配置BeanFactory类,用工厂创建我们的代理AccountService -->
    <bean id="beanFactory" class="com.utils.BeanFactory"></bean>
    <!-- 通过JDK动态代理实现 -->
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
	<!-- 通过Cglib动态代理实现 -->
	<bean id="proxyAccountServiceByCglib" factory-bean="beanFactory" factory-method="getAccountServiceByCglib"></bean>

</beans>

测试类

public void testFindAccountAll(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService accountService = (IAccountService) context.getBean("proxyAccountService");
        Account account = new Account();
        account.setId(1);
        account.setMoney(500D);
        account.setName("aaa");
        accountService.updateAccount(account);
}

可以看到代理类实现了事务,当代码报错,数据正常回滚了。
在这里插入图片描述

五、总结

1、JDK动态代理,自带的,方便使用,但是要要求必须实现接口,有一定的约束。
2、cglib,需要导入第三方jar包,使用的时候没有什么约束。
3、SpringAOP以上2种方法都用到了。
4、学完动态代理,可以结合BeanFactory创建Bean的方式来控制事务。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值