zoukankan      html  css  js  c++  java
  • springIOC的那些事

       springIOC动态代理的那些事儿

    1.发现问题

    今天在使用spring的IOC容器时发现了这样的一个问题:
    首先有一个接口定义如下:

    public interface BookShopService {
                void purchase(String username, Integer isbn) throws Exception;
    }
    

    它的实现类如下:

    package cn.ccsu.service.impl;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import cn.ccsu.dao.BookMapper;
    import cn.ccsu.dao.StockMapper;
    import cn.ccsu.dao.UserMapper;
    import cn.ccsu.exception.BookStockException;
    import cn.ccsu.exception.UserAccountException;
    import cn.ccsu.service.BookShopService;
    
    @Service("bookShopServiceImpl")
    public class BookShopServiceImpl implements BookShopService {
    
    	@Autowired
    	private UserMapper userMapper;
    
    	@Autowired
    	private BookMapper bookMapper;
    
    	@Autowired
    	private StockMapper stockMapper;
    
    	public BookShopServiceImpl() {
    
    	}
    
    	@Transactional
    	@Override
    	public void purchase(String userName, Integer id) throws Exception {
    
    		// 1. 获取书的单价
    		Integer price = bookMapper.queryPrice(id);
    
    		// 2. 更新书的库存
    		if (stockMapper.queryStock(id) == 0) {
    			throw new BookStockException("库存不足!");
    		}
    		System.out.println("更新书的库存:" + stockMapper.updateStock(id));
    
    		// 3. 更新用户余额
    		if (userMapper.queryBalance(userName) < price) {
    			throw new UserAccountException("余额不足!");
    		}
    		System.out.println("
    更新用户余额:" + userMapper.updateBalance(userName, price));
    
    	}
    
    }
    

    这个类主要是完成对图书的销售工作,这个不是重点。接着我创建spring应用上下文,视图从中获取这个类的实例,这个时候出错了。代码如下:

    ClassPathXmlApplicationContext ctxt = new ClassPathXmlApplicationContext("applicationContext.xml");
    		BookShopService service = (BookShopService) ctxt.getBean("bookShopServiceImpl");
    

    报错信息如下:

    com.sun.proxy.$Proxy21 cannot be cast to BookShopServiceImpl 
    

    怎么样?是不是懵逼了?且听我细细道来。

    2.动态代理

      看到com.sun.proxy.$Proxy21没?这就是突破口:proxy--->代理,这说明spring创建了一个代理对象。为什么是代理对象而不是BookShopServiceImpl类的对象呢?这个后面再说,先看看下面的。
    不知道你有没有听说过java的动态代理(不知道的请自行谷歌),java有2种动态代理机制:JDK动态代理和cglib动态代理。前者是基于接口实现的,而后者是基于类实现的。听不懂?行,我简单说下吧!!
    比如我刚刚的这个例子,BookShopServiceImpl类实现了BookShopService接口,此时就可以用JDK代理,JDK会创建一个代理对象,暂且叫它$Proxy21吧。$Proxy21和BookShopServiceImpl类没有任何继承关系,但是$Proxy21是BookShopService接口的实现类的对象。也就是说JDK代理创建的是该类的父接口的一个实现对象。
    接下来说说cglib代理,cglib代理是基于类的代理。比如有一个基类A,B继承了这个基类A。如果此时创建一个代理对象,该代理对象是可以用B指向的。因为该对象是B的一个实现类的对象。也就是说cglib代理会创建原来的类的一个子类,也就是代理类是原有类的一个子类。
    综上所述:JDK代理会创建原有接口的一个实现类,而cglib代理会创建原有类的一个子类。

    3.解开谜团

    这下明白没?再来看看报错信息:
    
    com.sun.proxy.$Proxy21 cannot be cast to BookShopServiceImpl 
    

    这里使用了代理,而且还是JDK代理-->即基于接口的代理,所以不能将该代理对象强转为BookShopServiceImpl类型,因为该代理对象是BookShopService接口的子类型。这就完了吗?还早着呢,继续往下看。
    我之前说过:为什么spring IOC容器创建代理对象而不是创建BookShopServiceImpl类的对象呢?仔细看这个类的purchase方法:

    	@Transactional
    	@Override
    	public void purchase(String userName, Integer id) throws Exception {
    
    		// 1. 获取书的单价
    		Integer price = bookMapper.queryPrice(id);
    
    		// 2. 更新书的库存
    		if (stockMapper.queryStock(id) == 0) {
    			throw new BookStockException("库存不足!");
    		}
    		System.out.println("更新书的库存:" + stockMapper.updateStock(id));
    
    		// 3. 更新用户余额
    		if (userMapper.queryBalance(userName) < price) {
    			throw new UserAccountException("余额不足!");
    		}
    		System.out.println("
    更新用户余额:" + userMapper.updateBalance(userName, price));
    
    	}
    

    这里用了事务。在spring中如果使用事务或者AOP,都会创建代理对象,让这个代理对象去完成。而spring默认的代理机制是JDK代理,所以这里使用了JDK代理,创建的对象是BookShopService的子类型,和BookShopServiceImpl 没有半点关系,所以不能强转为BookShopServiceImpl。还有一种是cglib代理,之前说了,它是基于类的方式。你可以在配置文件中修改代理方式,如下:

    <tx:annotation-driven transaction-manager="transactionManager"  mode="aspectj" />
    

    修改代理方式为cglib代理。(注:aspectj代理方式即cglib代理)

    4.验证

    接着我写了一个小demo,验证了下,代码如下:
    A.java:
    
    package cn.ccsu;
    
    public abstract class A {
    
    	public A() {
    	
    	}
    
    }
    
    B.java:
    
    package cn.ccsu;
    
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.annotation.Transactional;
    
    @Component
    public class B extends A {
    
    	public B() {
    
    	}
    
    	@Transactional
    	public void testAnno() {
    	}
    }
    
    测试:
    
    ClassPathXmlApplicationContext ctxt = new ClassPathXmlApplicationContext("applicationContext.xml");
    B b = (B) ctxt.getBean("b"); b.testAnno();
    

    虽然我在在这里用了事务,但是因为没有牵涉到接口,所以会使用cglib代理,也就是创建B类的一个子类型的对象。即代理类是B类的子类。所以在这里无论使用A指向还是B指向都没问题。
    接着又写了一个接口以及它的一个实现类,代码如下:

    package cn.ccsu.service;
    
    public interface IUserService {
    
    	void addUser();
    }
    
    package cn.ccsu.service.impl;
    
    import org.springframework.stereotype.Repository;
    import org.springframework.transaction.annotation.Transactional;
    
    import cn.ccsu.service.IUserService;
    
    @Repository("iUserServiceImpl")
    public class IUserServiceImpl implements IUserService {
    
    	public IUserServiceImpl() {
    
    	}
    
    	@Transactional
    	@Override
    	public void addUser() {
    
    	}
    
    }
    
        测试如下:
    
    ClassPathXmlApplicationContext ctxt = new ClassPathXmlApplicationContext("applicationContext.xml");
    IUserServiceImpl service =(IUserServiceImpl) ctxt.getBean("iUserServiceImpl");
    service.addUser();
    

    此时会报错:

     java.lang.ClassCastException: com.sun.proxy.$Proxy8 cannot be cast to cn.ccsu.service.impl.IUserServiceImpl
    

    如果我去掉@Transactional注解,程序可以正常后运行。当你使用事务(或者AOP)时,spring会自动创建一个代理对象,让这个代理对象去完成。但如果没有使用事务,spring的IOC容器会正常创建该类的一个对象,所以程序可以正常跑起来。

    5.总结

    敲黑板ing..划重点啦!!划重点啦!!
    1.JDK代理是基于接口的,它会创建被代理类的父接口的一个子类型;cglib代理是基于类的,它会创建被代理类的一个子类型。
    2.spring有2中代理机制:JDK代理和cglib代理,默认使用前者。
    3.当使用AOP或者事务时会自动创建一个代理对象,让它来完成需要处理的事。

    这个问题也让我想起了之前的一个bug,同样的问题,只不过是在使用AOP时遇到的,链接如下:
    AOP bug

  • 相关阅读:
    js正则
    【zookeeper】zookeeper 集群搭建
    【zookeeper】linux zookeeper的安装步骤
    【ActiveMQ】ActiveMQ之JDBC消息存储安装配置
    【数据库】Cannot create PoolableConnectionFactory (null, message from server: "Host 'xxxxx' isnot allow
    【ActiveMQ】Failed to bind to server socket: nio://0.0.0.0:61616 due to: java.net.BindException:
    【微服务】Springboot和ActiveMQ整合出现 Could not resolve placeholder 'xxx' in value "${xxx}"
    【ActiveMQ】记录一次activemq与jdk版本冲突问题
    【ActiveMq】linux ActiveMq安装
    【springcloud】Could not resolve type alias 'Dept'. Cause: java.lang.ClassNotFoundException
  • 原文地址:https://www.cnblogs.com/yansiju/p/7859931.html
Copyright © 2011-2022 走看看