问题的引出
一些伪代码:
public class AccountServiceImpl implements IAccountService{
/* 转账操作 */
public void transfer() {
try{
// 开启事务
// 具体的转账业务操作
// 提交事务
}catch(Exception e){
// 事务回滚
}finally{
// 关闭资源
}
}
}
若上述类中还存在转入、转出的方法。那么,每个方法总都必须写事务处理的代码。
在设计上存在的问题:
1、责任不分离。
业务方法只需要关心如何完成该业务功能,不需要去关系事务管理/日志管理/权限管理等等。
2、代码结构重复。
在开发中不要重复代码,重复就意味着维护成本增大。
装饰设计模式
在不改变源代码基础上,动态地扩展一个对象的功能。通过包裹真实的对象,对已有对象进行功能增强。
特点:
1、装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
2、装饰对象包含一个真实对象的引用(reference)
3、装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
4、装饰对象可以在转发这些请求以前或以后增加一些附加功能。
这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。
在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
一些伪代码
public class AccountSerivceImplWapper implements IAccountService{
// 事务对象的引用
public AccountSerivceImplWapper(AccountServiceImpl target) {
// 传入真实对象的引用
}
public void transfer() {
try{
// 开启事务
// 真实对象调用转账方法
// 提交事务
}catch(Exception e){
// 关闭资源
}finally{
}
}
public void withdraw() {
try{
// 开启事务
// 真实对象调用取钱方法
// 提交事务
}catch(Exception e){
// 事务回滚
}finally{
// 关闭资源
}
}
}
小结:
可以看出,使用装饰设计模式让责任分离了。真实对象可以专注于完成业务逻辑。
但是,还是存在着代码结构重复的问题。而且,若存在多个需要增强的类,每个类都要定义一个增强类。
此外,还是暴露了真实对象,客户端可直接使用真实对象,该真实对象无事务相关的代码,仅有业务操作,很不安全。
静态代理模式
客户端直接使用的是代理对象,不知道真实对象是谁。代理对象在客户端和真实对象之间其中介作用。
类比现实中的房屋中介模式:租客,中介,房东。
租客不知道房东是谁,签合同、交租金都是直接与中介公司打交道。
特点:
1、代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系
2、代理模式的职责:把不是真实对象该做的事情从真实对象上撇开——职责清晰
public class AccountSerivceImplWapper implements IAccountService{
// 事务对象的引用
// 真实对象的引用target
public void transfer() {
try{
// 开启事务
// 真实对象调用转账方法
// 提交事务
}catch(Exception e){
// 事务回滚
}finally{
// 关闭资源
}
}
public void withdraw() {
try{
// 开启事务
// 真实对象调用取钱方法
// 提交事务
}catch(Exception e){
// 事务回滚
}finally{
// 关闭资源
}
}
}
小结:
静态代理模式让真实对象安全了,不会暴露在客户端。但是除此之外,仍旧存在装饰设计模式一样的问题。
JDK动态代理
· 静态代理与动态代理对比
静态代理:在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在程序运行前就确定了。
动态代理:动态代理类是在程序运行期间由JVM通过反射等机制动态的生成的,所以不存在代理类的字节码文件。
代理对象和真实对象的关系是在程序运行时期才确定的。
· JDK动态代理API
1、java.lang.reflect.Proxy类
Java动态代理机制生成的所有动态代理类的父类。提供了一组静态方法,来为一组接口动态地生成代理类及其对象。
真实对象必须实现至少一个接口。
主要方法:
newProxyInstance
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
参数:
loader
- 定义代理类的类加载器
interfaces
- 代理类要实现的接口列表
h
- 指派方法调用的调用处理程序返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
2、java.lang.reflect.InvocationHandler接口
定义一个实现类实现该接口,并在实现类中编写增强的代码。
invoke
Object invoke(Object proxy, Method method,Object[] args)throws Throwable
参数:
proxy-代理对象
method-真实对象中要增强的方法
arg-真实对象中要增强的方法的参数
· 使用思路
1、创建增强程序类,该类实现InvocationHandler接口。
实现invoke方法,在该方法中编写对真实方法的增强代码。这个方法我们不会直接调用。
2、在增强程序类中,提供获取代理对象的方法。
通过Proxy类的newProxyInstance方法创建代理对象。
增强对象中存有对真实对象的引用,通过Spring注入,即可不暴露真实对象。
其中InvocationHandler类型的参数就是当前增强程序类的实例。
3、通过增强程序实例获取代理对象,通过代理对象增强的操作真实对象的方法。
一些代码
public class TranscationManagerHandler // 增强程序类必须实现InvocationHandler接口 implements java.lang.reflect.InvocationHandler { /* 真实对象的引用 */ @Setter private Object target; private TransactionManager tx = new TransactionManager(); /* 增强程序中的具体操作 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = null; try{ tx.begin(); ret = method.invoke(target, args); tx.commit(); }catch(Exception e){ tx.rollback(); e.printStackTrace(); }finally{ tx.close(); } return ret; } /* 创建代理对象 */ @SuppressWarnings("unchecked") public <T>T getProxyInstance(){ Object ret= Proxy.newProxyInstance(target.getClass().getClassLoader(), // 真实对象的类加载器 target.getClass().getInterfaces(), // 真实对象的类实现的接口 this); // 增强程序对象 return (T)ret; } }
Spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="transationManager" class="com.hanaii.common.TransactionManager"/> <!-- 隐藏真实对象 --> <bean id="accountServiceImpl" class="com.hanaii.jdk_proxy.TranscationManagerHandler"> <property name="target" > <bean class="com.hanaii.common.AccountServiceImpl" /> </property> </bean> </beans>
测试代码
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class JdkProxyTest { @Autowired @Qualifier("accountServiceImpl") private TranscationManagerHandler handler; @Test public void test() throws Exception { IAccountService service= handler.getProxyInstance(); service.transfer(); } }
测试结果
Transcation begin ... service: 转账操作 ... Transcation commit ... Transcation close ...
· JDK动态代理存在的问题
1、代理的对象必须要实现一个接口。
2、需要为每个对象创建代理对象
3、动态代理的最小单位是类,类中的所有实现于接口的方法都会被增强。(有时候我们想要有些方法不要被增强)
Spring提供的动态代理:CGLIB
CGLIB提供了和JDK动态代理类似的API接口。
其原理是对指定的目标类生产一个子类,并覆写其中方法进行增强。
(类必须可继承,不能是final修饰。)
· API
1、org.springframework.cglib.proxy.InvocationHandler接口
该接口也存在invoke方法,使用基本同JDK动态代理相同。
另外,该接口继承了 org.springframework.cglib.proxy.Callback 接口。
2、org.springframework.cglib.proxy.Enhancer类
其主要方法用于创建代理对象。其使用案例如下:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置代理对象父类
enhancer.setCallback(this); // 设置增强程序
return (T)enhancer.create(); // 创建代理对象
· 使用思路
基本同JDK动态代理
一些代码:
public class TranscationManagerHandler // 增强程序类必须实现InvocationHandler接口
implements org.springframework.cglib.proxy.InvocationHandler {
/* 真实对象的引用 */
@Setter
private Object target;
private TransactionManager tx = new TransactionManager();
/* 创建代理对象 */
@SuppressWarnings("unchecked")
public <T>T getProxyInstance(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return (T)enhancer.create();
}
/* 增强程序中的具体操作 */
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("-d-");
Object ret = null;
try{
tx.begin();
ret = method.invoke(target, args);
}catch(Exception e){
tx.rollback();
e.printStackTrace();
}finally{
tx.commit();
}
return ret;
}
}
· 小结
1、CGLIB原理生成目标类的子类,所以目标类可以不实现接口。
2、要求类不能是final的(可继承),要拦截的方法要是非final、非static、非private的(可覆写)。
3、动态代理的最小单位是类(所有类中的方法都会被处理)。
Spring中的动态代理机制
若目标对象实现了若干接口,Spring就会使用JDK动态代理。
若目标对象没有实现任何接口,Spring就使用CGLIB库生成目标对象的子类。
对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规则。
cglib和javassist代理的机制都是一样的,都是通过继承实现的。