Spring系列
- Spring — Spring简介、入门、配置 , IoC和DI思想
- Spring — IoC核心(基于XML)、DI核心(基于XML)
- Spring — 使用IoC和DI模拟注册案例、注解配置IoC和DI
- Spring — 静态代理、动态代理、拦截器思想
- Spring — AOP思想、AOP开发、Pointcut语法、注解配置AOP
- Spring — DAO层、Spring JDBC、Spring事务控制
- Spring — XML配置事务、注解+XML、纯注解的配置方式
- Spring整合MyBatis
- Spring Java Config — 组件注册相关注解
- Spring Java Config — 常用注解
跳转到目录
之前我们在Service中写的业务逻辑方法, 对有些方法都要做事务管理,比如save和update操作距离,没有加上事务控制的代码如下:
public class EmployeeServiceImpl implement EmployeeService{
public void save(){
// 保存操作
}
}
加上事务控制之后
public class EmployeeServiceImpl implements EmployeeService{
public void save(){
// 打开资源
// 开启事务
try{
// 保存操作
// 提交事务
} catch (Exception e) {
// 回滚事务
} finally {
// 释放资源
}
}
}
上述问题: 我们在业务层的方法都得处理事务(繁琐的try-catch)
设计上存在的问题:
跳转到目录
客户端直接使用的都是代理对象,不知道真实对象是谁, 此时代理对象可以在客户端和真实对象之间起到中介的作用;
跳转到目录
在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了. 我们成为静态代理
跳转到目录
代码
// domain
public class Employee {
}
// ---------------------------------------------
// dao层
public interface EmployeeDao {
void save(Employee emp);
void update(Employee emp);
}
public class EmployeeDaoImpl implements EmployeeDao {
public void save(Employee emp) {
System.out.println("保存员工");
}
public void update(Employee emp) {
System.out.println("修改员工信息");;
}
}
// ---------------------------------------------
// service层
public interface EmployeeService {
void save(Employee emp);
void update(Employee emp);
}
public class EmployeeServiceImpl implements EmployeeService {
private EmployeeDao dao;
public void setDao(EmployeeDao dao) {
this.dao = dao;
}
public void save(Employee emp) {
dao.save(emp);
System.out.println("保存成功");
}
public void update(Employee emp) {
dao.update(emp);
throw new RuntimeException("故意出错");
}
}
// ---------------------------------------------
// tx包,模拟事务管理器
public class TransactionManager {
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
public void rollback(){
System.out.println("回滚事务");
}
}
// ---------------------------------------------
// proxy类
public class EmployeeServiceProxy implements EmployeeService {
private TransactionManager tx; // 事务管理器
private EmployeeService target; // 真实对象/委托对象
public void setTarget(EmployeeService target) {
this.target = target;
}
public void setTx(TransactionManager tx) {
this.tx = tx;
}
// 被增强的方法
public void save(Employee emp) {
// 开启事务_对save方法的增强
tx.begin();
try {
target.save(emp);
tx.commit();
} catch (Exception e){
e.printStackTrace();
tx.rollback();
}
}
// 被增强的方法
public void update(Employee emp) {
tx.begin();
try {
target.update(emp);
tx.commit();
} catch (Exception e){
e.printStackTrace();
tx.rollback();
}
}
}
staticProxy.xml
<bean id="employeeDao" class="com.sunny._01_static_proxy.dao.impl.EmployeeDaoImpl"/>
<bean id="transactionManager" class="com.sunny._01_static_proxy.tx.TransactionManager"/>
<bean id="employeeServiceProxy" class="com.sunny._01_static_proxy.proxy.EmployeeServiceProxy">
<property name="tx" ref="transactionManager"/>
<property name="target">
<bean class="com.sunny._01_static_proxy.service.impl.EmployeeServiceImpl">
<property name="dao" ref="employeeDao"/>
bean>
property>
bean>
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class StaticProxyTest {
// class com.sunny._01_static_proxy.proxy.EmployeeServiceProxy
@Autowired // 注入代理对象
private EmployeeServiceProxy service;
@Test
public void testSave(){
System.out.println(service.getClass());
service.save(new Employee());
}
@Test
public void testUpdate1(){
service.update(new Employee());
}
}
跳转到目录
静态代理:在程序运行前就已经存在代理类的字节码文件
,代理对象和真实对象的关系在运行前就确定了。
优点:
缺点:
这样就需要使用动态代理了
跳转到目录
已经存在代理类的字节码文件
,代理对象和真实对象的关系在运行前就确定了。运行期间由JVM通过反射等机制动态的生成
的,所以不存在代理类的字节码文件,代理对象和真实对象的关系是在程序运行时期才确定的。有接口
:使用JDK动态代理无接口
:使用CGLIB或Javassist组件.class
文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类。如此就完成了动态创建一个类的能力;// dao包同上
// service包
public interface EmployeeService1 {
void save(Employee1 emp);
void update(Employee1 emp);
// 测试增加功能不需要对增强类做操作
void delete(Long l);
// JDK动态反射,最小单位是类,类中的方法都会被拦截
void queryAll();
}
public class EmployeeServiceImpl1 implements EmployeeService1 {
private EmployeeDao1 dao1;
public void setDao1(EmployeeDao1 dao1) {
this.dao1 = dao1;
}
public void save(Employee1 emp) {
dao1.save(emp);
System.out.println("保存成功");
}
public void update(Employee1 emp) {
dao1.update(emp);
throw new RuntimeException("故意出错");
}
public void delete(Long l) {
System.out.println("删除成功");
}
public void queryAll() {
System.out.println("查询全部");
}
}
// ---------------------------------------------
// tx包,模拟事务管理器
public class TransactionManager1 {
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
public void rollback(){
System.out.println("回滚事务");
}
}
// ---------------------------------------------
// tx包, 事务管理器增强
// 事务的增强操作
@SuppressWarnings("all")
public class TransactionManagerAdvice implements java.lang.reflect.InvocationHandler{
// 代理对象中包含的真实对象(对谁做增强)
private Object target; // 这里是对EmployeeService作增强
private TransactionManager1 tx;
public void setTarget(Object target) {
this.target = target;
}
public void setTx(TransactionManager1 tx) {
this.tx = tx;
}
// 创建一个代理对象
public <T> T getProxyObject() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),// 类加载器,一般跟上真实对象的类加载器
target.getClass().getInterfaces(),// 真实对象所实现的接口(JDK动态代理必须要求真实对象有接口)
this); // 如何做事务增强的对象
}
// 如何为真实对象的方法做增强的具体操作
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 过滤某些方法(对某些方法不增强)
if (method.getName().equals("queryAll")){
return method.invoke(target, args);
}
Object ret = null;
tx.begin(); // 开启事务_ 增强操作
try {
//------------------------
ret = method.invoke(target, args); //调用真实对象的方法
//------------------------
tx.commit(); // 增强操作
} catch (Exception e){
e.printStackTrace();
tx.rollback(); // 增强操作
}
return ret;
}
/*class Xx implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}*/
}
dynamicProxy.xml
<bean id="employeeDao1" class="com.sunny._02_dynamic_proxy.dao.impl.EmployeeDaoImpl1"/>
<bean id="transactionManager1" class="com.sunny._02_dynamic_proxy.tx.TransactionManager1"/>
<bean id="employeeService1" class="com.sunny._02_dynamic_proxy.service.impl.EmployeeServiceImpl1">
<property name="dao1" ref="employeeDao1"/>
bean>
<bean id="transactionManagerAdvice1" class="com.sunny._02_dynamic_proxy.tx.TransactionManagerAdvice">
<property name="target" ref="employeeService1"/>
<property name="tx" ref="transactionManager1"/>
bean>
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdkDynamicProxyTest {
// com.sun.proxy.$Proxy13
@Autowired
private TransactionManagerAdvice advice;
@Test
public void testSave(){
// 获取到代理对象
EmployeeService1 proxy = advice.getProxyObject();
//System.out.println(proxy.getClass());
proxy.save(new Employee1());
}
@Test
public void testUpdate(){
// 获取到代理对象
EmployeeService1 proxy = advice.getProxyObject();
proxy.update(new Employee1());
}
@Test
public void testDelete(){
EmployeeService1 proxy = advice.getProxyObject();
proxy.delete(1L);
}
@Test
public void testQueryAll1(){
EmployeeService1 proxy = advice.getProxyObject();
proxy.queryAll(); // 没有增强该方法,作了判断过滤
}
}
注意: 在接口方法Invoke中打印代理对象会造成死循环,实际上打印代理对象就是打印其对象的toString方法,通过上面反编译的代码,toString方法中仍会调用Invoke方法,造成了死循环.
跳转到目录
使用JDK的动态代理,只能针对于目标对象存在接口的情况,如果目标对象没有接口
, 此时可以考虑使用CGLIB的动态代理方式;
代码:
public class TransactionManagerAdvice2 implements org.springframework.cglib.proxy.InvocationHandler {
// 代理对象中包含的真实对象(对谁做增强)
private Object target; // 这里是对EmployeeService作增强
private TransactionManager2 tx;
public void setTarget(Object target) {
this.target = target;
}
public void setTx(TransactionManager2 tx) {
this.tx = tx;
}
// 创建一个代理对象
public <T> T getProxyObject() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 将继承于哪一个类,来做增强
enhancer.setCallback(this); // 设置增强的对象
return (T) enhancer.create(); // 创建代理对象
}
// 如何为真实对象的方法做增强的具体操作
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = null;
tx.begin(); // 开启事务_ 增强操作
try {
//------------------------
ret = method.invoke(target, args); //调用真实对象的方法
//------------------------
tx.commit(); // 增强操作
} catch (Exception e){
e.printStackTrace();
tx.rollback(); // 增强操作
}
return ret;
}
}
// CGLIB代理对象
com.sunny._03cglib_dynamic_proxy.service.impl.EmployeeServiceImpl2$$EnhancerByCGLIB$$676c99e7
CGLIB原理:
通过生成代理类,然后继承目标类,再对目标类中可以继承的方法做覆盖,并在该方法中做功能增强,实则调用的是子类中的方法;
两种动态代理底层:
跳转到目录
// 在service方法调用之前,做日志记录
public class LogUtil {
public void writeLog(String methodClass,String methodName){
System.out.println(new Date().toLocaleString() + "调用了"+methodClass+"类中的" +methodName + "方法");
}
}
// 日志增强
public class LogAdvice implements org.springframework.cglib.proxy.MethodInterceptor{
private Object target; // 真实对象
private LogUtil logUtil; // 日志工具类
public void setTarget(Object target) {
this.target = target;
}
public void setLogUtil(LogUtil logUtil) {
this.logUtil = logUtil;
}
// 创建代理对象
public <T> T getProxyObject() {
/*Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 将继承于哪一个类,来做增强
enhancer.setCallback(this); // 设置增强的对象
return (T) enhancer.create(); // 创建代理对象*/
return (T) Enhancer.create(target.getClass(), this);
}
/**
* 如何对方法作增强
* @param proxy 代理对象
* @param method 要作增强的方法
* @param objects 要作增强方法的参数
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
logUtil.writeLog(method.getDeclaringClass().getName(),method.getName());
Object ret = method.invoke(target, objects); // 调用真实对象的方法
return ret;
}
}
cglibMethodInterceptor.xml
<bean id="employeeDao3" class="com.sunny._04cglib_methodinterceptor.dao.impl.EmployeeDaoImpl3"/>
<bean id="logUtil" class="com.sunny._04cglib_methodinterceptor.log.LogUtil"/>
<bean id="employeeServiceImpl3" class="com.sunny._04cglib_methodinterceptor.service.impl.EmployeeServiceImpl3">
<property name="dao" ref="employeeDao3"/>
bean>
<bean id="logAdvice" class="com.sunny._04cglib_methodinterceptor.log.LogAdvice">
<property name="target" ref="employeeServiceImpl3"/>
<property name="logUtil" ref="logUtil"/>
bean>
测试类
// com.sunny._04cglib_methodinterceptor.service.impl.EmployeeServiceImpl3$$EnhancerByCGLIB$$b1d8ba01
@Autowired
private LogAdvice advice;
@Test
public void testSave(){
EmployeeServiceImpl3 proxy = advice.getProxyObject();
// System.out.println(proxy.getClass());
proxy.save(new Employee3());
}
@Test
public void testUpdate(){
EmployeeServiceImpl3 proxy = advice.getProxyObject();
proxy.update(new Employee3());
}
JAVA动态代理是使用java.lang.reflect包中的Proxy类
与InvocationHandler接口
这两个来完成的。
要使用JDK动态代理,代理类必须要实现接口。
JDK动态代理将会拦截所有pubic的方法(因为只能调用接口中定义的方法) ,这样即使在接口中增加了新的方法,不用修改代码也会被拦截。
动态代理的最小单位是类(所有类中的方法都会被处理) ,如果只想拦截一部分方法 ,可以在invoke方法中对要执行的方法名进行判断
。
CGLIB代理总结:
CGLIB可以生成委托类的子类,并重写父类非final修饰符的方法。
要求类不能是final 的,要拦截的方法要是非final、非static、非private的。
动态代理的最小单位是类(所有类中的方法都会被处理);
性能和选择:
JDK动态代理是基于实现接口的, CGUB和Javassit是基于继承委托类的。
从性能上考虑: Javassit > CGLIB > JDK
Struts2的拦截器和Hibernate延迟加载对象,采用的是Javassit的方式
对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规范。若委托对象实现了干接口,优先选用JDK动态代理。
若委托对象没有实现任何接口, 使用Javassit和CGLIB动态代理。