文章目录
AOP引入:动态代理处理公共代码
重复代码问题
拿我上一个博客那个例子来说,需要事务处理的不仅仅只有一个方法,如果每个方法都使用同样的流程进行事务处理,大量的重复代码就会出现。
就像下面这样:
public boolean transfer(String fromUsername, String toUsername, double money){
transactionManager.openTranscation();
try {
User from = userDao.getUserByUsername(fromUsername);
User to = userDao.getUserByUsername(toUsername);
from.setAccount(from.getAccount() - money);
to.setAccount(to.getAccount() + money);
userDao.updateUser(from);
userDao.updateUser(to);
transactionManager.commit();
}catch (Exception e){
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
transactionManager.closeTransaction();
}
return true;
}
如果需要事务处理,就会出现和上面代码流程及其类似的代码,不同的地方只有业务部分的代码。
所以,可以提取出如下的公共代码
public Object function(...){
transactionManager.openTranscation();
try {
/******业务代码******/
transactionManager.commit();
}catch (Exception e){
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
transactionManager.closeTransaction();
}
}
到这里,可以想到Java的动态代理。使用动态代理对象调用方法时,每个方法都会调用InvocationHandler.invoke()方法,因此,公共代码可以放到invoke()方法中
在这里,使用代理模式,对于需要进行事务处理的方法时,通过代理对象进行调用。对于不需要事务处理的方法,就可以使用被代理对象直接调用。先看一下类图:
思路是这样的,Servcice
通过一个工厂方法创建一个动态代理对象,如果使用该代理方法调用,则会加入事务处理,如果使用被代理对象本身的方法,则不会加入事务处理。
实现代码如下:
@Service("userService")
public class UserServiceImpl implements IUserService {
@Autowired
private UserDaoImpl userDao;
@Autowired
private TransactionManager transactionManager;
/**
* 没有实现事务处理,使用代理模式对该方法进行增强
*/
@Override
public boolean transfer(String fromUsername, String toUsername, double money) {
User from = userDao.getUserByUsername(fromUsername);
User to = userDao.getUserByUsername(toUsername);
if(from.getAccount() < money)
return false;
from.setAccount(from.getAccount() - money);
to.setAccount(to.getAccount() + money);
userDao.updateUser(from);
userDao.updateUser(to);
return true;
}
//创建一个有事务处理的动态代理对象
@Override
public IUserService getTransactionProxy() {
class InvocationHandlerImpl implements InvocationHandler {
IUserService userService;
public InvocationHandlerImpl(IUserService userService) {
this.userService = userService;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
transactionManager.openTranscation();
try{
Object retvalue = method.invoke(userService, objects);
transactionManager.commit();
return retvalue;
}catch (Exception e){
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
transactionManager.closeTransaction();
}
}
}
return (IUserService) Proxy.newProxyInstance(this.getClass().getClassLoader(),
this.getClass().getInterfaces(),
new InvocationHandlerImpl(this));
}
}
AOP相关概念
AOP(Aspect Orientend Programming)
AOP,面向切面编程。通过预编译或运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续。
定义中提到的统一维护,指的就是对公共代码进行维护。
连接点(JoinPoint)
连接点是指被代理对象的方法。这些方法可以通过AOP进行功能性的增强。
切入点(PointCut)
被AOP进行功能性增强的连接点,被称作切入点。
目标对象(Target)
被代理的对象,上面例子中的Servcie
对象,他需要被代理,实现功能性的增强。
通知(Advice)
对切入点进行功能性增强的部分。
通知的分类:
- 前置通知
- 后置通知
- 异常通知
- 最终通知
- 环绕通知
举例:
public Object function(...){
transactionManager.openTranscation();
try {
/******切入点方法调用******/
transactionManager.commit();
}catch (Exception e){
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
transactionManager.closeTransaction();
}
}
在切入点方法调用之前的通知transactionManager.openTransaction()
,就称为前置通知。
在切入点方法调用之后的通知trasactionManager.commit()
,称为后置通知。
在异常处理中执行的通知trasactionManager.rollback()
,称为异常通知。
进行资源回收等操作的通知transactionManager.closeTransaction()
,称为最终通知(在finally代码块中)。
对于整个function()
方法,被称作环绕通知。对于环绕通知的处理,可以像上面一样,当拦截切入点方法后,对切入点方法进行功能性增强;也可以不调用切入点方法,这样就相当于使用环绕通知修改切入点功能。
可以看到,在实现时,需要一个“通知对象”(trasactionManager),对被代理对象的方法进行功能性增强
织入(Weaving)
把功能上的增强应用到被代理对象的过程。就像上面的例子,使用各种通知对切入点进行功能增强的过程。
代理(Proxy)
一个类被AOP植入后,产生一个代理类。通过代理对象调用切入点方法,会执行通知对象的通知及被代理对象的切入点方法(如果使用环绕通知,可能不会执行切入点方法)。(在上面的例子中,采用动态代理的方式,代理的类型和被代理对象的类型是相同的)
切面(Aspect)
切面 = 切入点 + 通知。
对一个切面进行定义时:定义切入点&&定义通知