Spring学习笔记【part06】动态代理

导读:本篇文章讲解 Spring学习笔记【part06】动态代理,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

Spring 学习笔记 Part06

1. 动态代理概念

​ 动态代理得类的字节码随用随创建,随用随加载,作用是不修改源码的基础上对方法进行增强。分为两类:① 基于接口的动态代理 ② 基于子类的动态代理 。

2. 基于接口的动态代理

使用的类:Proxy (由 JDK 官方提供的)

创建代理对象的要求:被代理类最少实现了一个接口,如果没有则不能使用

使用的方法 :Proxy.newProxyInstance( )

​ 参数:

​ ClassLoader :类加载器类型。用于加载代理对象的字节码的 ( 被代理的是谁,就写谁的类加载器 )// producer.getClass( ).getClassLoader( )

​ Class[ ] :字节码数组。用于让代理对象和被代理对象有相同的方法( 被代理的是谁,就写谁的接口集合 ) // producer.getClass( ).getInterface( )

​ InvocationHandler :用于提供增强的代码 ( 由我们编写如何代理 ) // new InvocationHandler( )

//经销商类
public class Producer implements ProducerInter{
    @Override
    public void saleProduct(double money) {
        System.out.println("卖出商品了,价格是"+money+"元");
    }
}
//未增强代理的客户类
public class Client {
    public static void main(String[] args) {
        
        final Producer producer; //匿名内部类访问外部变量时,外部变量必须被final修饰

        ProducerInter proxyProducer = 
            (ProducerInter) Proxy.newProxyInstance(Producer.class.getClassLoader(), 
                                                   Producer.class.getInterfaces(), 
         										   new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    
                    /*  这里没有写东西时,是没有意义的,即对方法没有增强,与原方法一模一样  */
                    
                    return method.invoke(producer,args);//传入被代理对象,传入方法的参数
                }
        });
        
        proxyProducer.saleProduct(10000);//使用代理对象的方法
        
    }
}

invoke( )方法:执行被代理对象的任何接口方法都会经过该方法

​ 参数:

​ proxy:代理对象的引用(用不上)

​ method:当前执行的方法

​ args:执行方法所需的参数

​ 返回值:和被代理对象方法具有相同的返回值

增强代理代码段可以总结为 3 个步骤 :

1. 获取方法执行的参数
2. 判断当前执行的方法是不是需要增强的
3. return一个返回值,类型和原方法返回值一样
//增强代理的客户类
public class Client {
    public static void main(String[] args) {
        
        final Producer producer; //匿名内部类访问外部变量时,外部变量必须被final修饰

        ProducerInter proxyProducer = 
            (ProducerInter) Proxy.newProxyInstance(Producer.class.getClassLoader(), 
                                                   Producer.class.getInterfaces(), 
         										   new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    
                   /*  在这里编写代码来对方法进行增强  */
                    
                    Object returnValue = null;
                    
                    Double money = (Double)args[0];   //1.获取方法提供的参数
                   
                    if("saleProduct".equals(method.getName())){   //2.判断当前方法是不是销售
                        returnValue = method.invoke(producer,money*0.8);
                    }
                    
                    return returnValue;         
                    
              }
        });
        
         proxyProducer.saleProduct(10000);//使用代理对象的方法
        
    }
}

method.invoke( ) 用于反射调用方法

​ 参数:

​ Object obj:指定调用该方法的对象

​ Object… args:指定该方法的参数

3. 基于子类的动态代理

​ 当我们的类不实现任何接口时,基于接口的动态代理是不能用的。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.1_3</version>
</dependency>

使用基于子类的动态代理,需要导入cglib的坐标

使用的类:Enhancer(由 第三方cglib库 提供的)

创建代理对象的要求:被代理类不能是 final 修饰的最终类(如果是最终类,就没有子类了,也就不能创建代理对象了)

使用的方法 :Enhancer.create( )

​ 参数:

​ Class :字节码类型。用于指定被代理对象的字节码 ( 被代理的是谁,就写谁的类加载器 )// producer.getClass( )

​ Callback :用于提供增强的代码 ( 由我们编写如何代理 ) 。但一般我们都会使用它的子接口实现类:MethodInterceptor(方法拦截器)// new MethodInterceptor( )

//不再是接口,而是一个类,但是这个类不能是final修饰的最终类
public class Producer { 
    public void saleProduct(double money) {
        System.out.println("卖出商品了,价格是"+money+"元");
    }
}

public class Client {
    public static void main(String[] args) {
        
        final Producer producer = new Producer();
        
        Producer superProducer = (Producer) Enhancer.create(Producer.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                
                //增强方法代码
                
            }
        });
        
        superProducer.saleProduct(12000);
        
    }
}

intercept( )方法:执行被代理对象的任何接口方法都会经过该方法

​ 参数:

​ proxy:代理对象的引用(用不上)

​ method:当前执行的方法

​ args:执行方法所需的参数

​ //以上三个参数和基于接口的动态代理中 invoke( ) 方法的参数是一样的

​ methodProxy:当前执行方法的代理对象(更用不上)

​ 返回值:和被代理对象方法具有相同的返回值

//增强代理代码段同基于接口的代理

    Object returnValue = null;
    Double money = (Double) objects[0];
    if("saleProduct".equals(method.getName())){
        returnValue = method.invoke(producer,money*0.8);
    }
    return returnValue;

1. 获取方法执行的参数
2. 判断当前执行的方法是不是需要增强的
3. return一个返回值,类型和原方法返回值一样

4. 动态代理实现事务控制

​ 建立一个BeanFactory类来生产代理对象。

4.1 注解方式

使用 @Autowired 后,代理对象就不需要加 final 了,因为 Spring 已经为我们做好了这个工作。


@Component("beanFactory")
public class BeanFactory {

    @Autowired
    private AccountService accountService;   //本来这个被代理对象要加final属性的,但是使用@Autowired后,Spring已经为我们做好了这个工作

    @Autowired
    private TransactionManager transactionManager;

    @Bean(name = "proxyService")  //需要把代理对象加入IoC容器中
    public AccountService getAccountService() {
        return (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
            
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object returnValue=null;
                //事务控制
                try {
                    transactionManager.begin();
                    returnValue = method.invoke(accountService, args); //业务层方法调用
                    transactionManager.commit();
                    return returnValue;
                } catch (Exception e) {
                    transactionManager.rollback();
                    throw new RuntimeException(e);
                } finally {
                    transactionManager.release();
                }
            }
        });
    }
}

Service层则不加入事务控制代码,直接和最开始一样各自实现业务即可。

4.2 xml配置方式

xml配置方式注入时,final需要自行添加。

public class BeanFactory{
    
    private AccountService accountService;
    
    public final void setAccountService(AccountService accountService){
        this.accountService=accountService;
    }

}

xml里的配置和最开始一样。

<bean id="beanFactory" class="com.itheima.factory.BeanFactory">
	<property name="accountService" ref="accountService"/>
</bean>

<bean id="accountService" class="com.itheima.service.AccountServiceImpl">
	<property name="accountDao" ref="accountDao"/>
</bean>

通过工厂类获取bean对象的回顾。

<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"/>

工厂创建时,一行解决的方式必须用静态工厂(即工厂类里使用静态方法),两行解决的可以不用。以上例子是使用一行解决的,所以在工厂类中的 getAccountService( ) 方法必须被 static 关键字修饰。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/84499.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!