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("\n更新用户余额:" + 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("\n更新用户余额:" + 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

springIOC的那些事的更多相关文章

  1. 数据交换格式与SpringIOC底层实现

    1.数据交换格式 1.1 有哪些数据交换格式 客户端与服务器常用数据交换格式xml.json.html 1.2 数据交换格式应用场景 1.2.1 移动端(安卓.iOS)通讯方式采用http协议+JSO ...

  2. 【腾讯Bugly干货分享】H5 视频直播那些事

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57a42ee6503dfcb22007ede8 Dev Club 是一个交流移动 ...

  3. CSharpGL(31)[译]OpenGL渲染管道那些事

    CSharpGL(31)[译]OpenGL渲染管道那些事 +BIT祝威+悄悄在此留下版了个权的信息说: 开始 自认为对OpenGL的掌握到了一个小瓶颈,现在回头细细地捋一遍OpenGL渲染管道应当是一 ...

  4. TODO:字节的那点事Go篇

    TODO:字节的那点事Go篇 (本文go version go1.7.3 darwin/amd64) 在Golang中string底层是由byte数组组成的. fmt.Println(len(&quo ...

  5. Microsoft Visual Studio 2013 — Project搭载IIS配置的那些事

    前段时间在改Bug打开一个project时,发生了一件奇怪的事,好好的一直不能加载solution底下的这个project,错误如下图所示:大致的意思就是这个project的web server被配置 ...

  6. OpenNLP:驾驭文本,分词那些事

    OpenNLP:驾驭文本,分词那些事 作者 白宁超 2016年3月27日19:55:03 摘要:字符串.字符数组以及其他文本表示的处理库构成大部分文本处理程序的基础.大部分语言都包括基本的处理库,这也 ...

  7. 深入理解Spring--动手实现一个简单的SpringIOC容器

    接触Spring快半年了,前段时间刚用Spring4+S2H4做完了自己的毕设,但是很明显感觉对Spring尤其是IOC容器的实现原理理解的不到位,说白了,就是仅仅停留在会用的阶段,有一颗想读源码的心 ...

  8. HTTPS那些事(一)HTTPS原理

    转载来自:http://www.guokr.com/post/114121/ 谣言粉碎机前些日子发布的<用公共WiFi上网会危害银行账户安全吗?>,文中介绍了在使用HTTPS进行网络加密传 ...

  9. 做一个 App 前需要考虑的几件事

    做一个 App 前需要考虑的几件事  来源:limboy的博客   随着工具链的完善,语言的升级以及各种优质教程的涌现,做一个 App 的成本也越来越低了.尽管如此,有些事情最好前期就做起来,避免当 ...

随机推荐

  1. 正则表达式中模式修正符作用详解(i、g、m、s、x、e)

    下面的转:http://www.cnblogs.com/shunyao8210/archive/2008/11/13/1332591.html 总结1:附件参数g的用法 表达式加上参数g之后,表明可以 ...

  2. Scala构建工具sbt的配置

    时间是17年12月24日.初学Scala,想使用它的标配构建工具sbt,结果好大一轮折腾,因为公司隔离外网,需要内部代理,所以尤其折腾.下面的配置参考了好多篇不同的文章,已经没法一一留下出处了.而且还 ...

  3. python 函数的递归

    递归:简单来说就是自己调用自己 这里我们又要举个例子来说明递归能做的事情. 例一: 现在你们问我,alex老师多大了?我说我不告诉你,但alex比 egon 大两岁. 你想知道alex多大,你是不是还 ...

  4. 墨菲定律&吉德林法则&吉尔伯特定律&沃尔森法则&福克兰定律

    一.墨菲定律:越害怕什么,就越会发生什么 二.吉德林法则:把问题清楚地写下来,就已经解决一半了 三.吉尔伯特定律:工作中的最大问题就是没人跟你说该如何去做 四.沃尔森法则:把信息和情报排在第一位,金钱 ...

  5. matplotlib中绘图配色

    Python中绘图配色(参照博文: Python-画图(散点图scatter.保存savefig)及颜色大全) # 可以直接使用配色编码 c=["#A52A2A" if tag = ...

  6. CentOS6 克 隆

    原始机子关机 自己设置名字 保存地址 开机 配置hosts   后面的为你要设置的名字不配置可能xshell链接上不了网 更改名字: 配置网卡 删除物理地址 mac 和  uuid 删除网卡 重启

  7. Nagios 利用NSClient++的check_nrpe方式使用自定义脚本监控windows

    分类 NsClient++来监控windows主机有三种方式:check_nt.check_nrpe.nsca.check_nt自带很多功能,但是扩展性差,check_nrpe可以通过执行自己定义的脚 ...

  8. VRRP协议介绍--转

    http://www.cnblogs.com/jony413/articles/2697404.html VRRP协议介绍 参考资料: RFC 3768 1. 前言 VRRP(Virtual Rout ...

  9. 【Linux】Linux系统启动过程

    1.Linux系统的启动过程并不是大家想象中的那么复杂,其过程可以分为5个阶段: 内核的引导. 运行 init. 系统初始化. 建立终端 . 用户登录系统. 1.Linux系统的启动过程并不是大家想象 ...

  10. phpstorm一些简单配置

    1.字体大小和行间距 2.设置编码:包括编辑工具编码和项目编码