前言
之前学习aop的使用,虽然学会了使用,但是还是很别扭,提到那些概念什么切点,切面,Advice乱七八糟的东西;最近看了一些书和博客,打算系统整理下AOP的思路。
业务场景
最近医疗项目中有个简单业务场景,我们需要统计一个地区的高血压,糖尿病,冠心病,肺结核的人数。做高血压模块的同学可能将患者做高血压登记后,然后就将一条操作日志新增到数据库中;同理做糖尿病模块的同学可能将患者做糖尿病登记后,然后就将一条操作日志新增到数据库中,等等。。。
高血压模块同学的代码:
public interface HighBp {
public void regHighBp();
}
public class HighBpImpl implements HighBp {
@Override
public void regHighBp() {
System.out.println("登记高血压患者");
System.out.println("操作日志表插入一条操作记录");
}
}
问题:上面这种代码有问题,将登记高血压患者的业务代码和向操作日志表中插入记录业务代码耦合在了一起;因为添加了操作日志功能,就修改登记高血压患者的功能方法,那么也违反了开闭原则,很糟糕。而且如果糖尿病患者、肺结核患者都要插入日志,那么到处加插入操作日志业务的代码明显不合理,不能复用。
改造1:装饰器模式
如果我们想增强当前方法的功能,首先想到了装饰器模式。
public interface HighBp {
public void regHighBp();
}
public class HighBpImpl implements HighBp {
@Override
public void regHighBp() {
System.out.println("登记高血压患者");
}
}
public class DecoratorBpImpl implements HighBp {
private HighBp highBp;
public DecoratorBpImpl(HighBp highBp) {
this.highBp = highBp;
}
@Override
public void regHighBp() {
highBp.regHighBp();
System.out.println("操作日志表插入一条操作记录");
}
}
public class MyTest {
public static void main(String[] args) {
HighBp hbp = new HighBpImpl();
HighBp dbp = new DecoratorBpImpl(hbp);
dbp.regHighBp();
}
}
说明:使用了装饰器模式,开始没有修改登记高血压患者功能方法的代码了,遵守了开闭原则,只是增加了一个装饰器类扩展了之前的功能。原类的代码虽然没有膨胀,但是却有个问题,依旧需要为高血压、糖尿病等患者登记写多个装饰器类;那就是业务代码还是没有真正分离,只是在没修改原来的基础上,只是装饰了一层。
改造2:代理模式
如果我们不光单纯登记高血压时候插入一条操作记录,如果我们删除登记的高血压记录时,同样也要向日志表插入一条操作记录,对于这种接口级别代理将多个方法进行增强。
Jdk代理:
public interface HighBp {
/** 登记高血压患者 */
public void regHighBp();
/** 删除登记的高血压患者 */
public void delHighBp();
}
public class HighBpImpl implements HighBp {
@Override
public void regHighBp() {
System.out.println("登记高血压患者");
}
@Override
public void delHighBp() {
System.out.println("删除登记的高血压患者");
}
}
public class JdkProxy implements InvocationHandler {
private Object target;
public JdkProxy(Object target) {
this.target = target;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = method.invoke(target, args);
System.out.println("操作日志表插入一条操作记录");
return result;
}
}
public class MyTest {
public static void main(String[] args) {
HighBp hbp = new HighBpImpl();
JdkProxy jdkProxy = new JdkProxy(hbp);
HighBp hbpPorxy = (HighBp) jdkProxy.getProxyInstance();
hbpPorxy.regHighBp();
hbpPorxy.delHighBp();
}
}
说明:使用jdk代理,这样我们只需要传入高血压登记的接口。我们就可以将这个接口的多个方法进行增强操作。而Jdk代理的弊端也在于只能代理接口不能代理实现类。而日志操作和业务操作没有真正分离。
Cglib代理:
public class CglibProxy implements MethodInterceptor{
@SuppressWarnings("unchecked")
public <T> T getProxyInstance(Class<T> clazz) {
return (T) Enhancer.create(clazz, this);
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
Object result = proxy.invokeSuper(obj, args);
System.out.println("操作日志表插入一条操作记录");
return result;
}
}
public class MyTest {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
HighBp highBp = cglibProxy.getProxyInstance(HighBpImpl.class);
highBp.regHighBp();
highBp.delHighBp();
}
}
说明:使用Cglib代理实现了对实现类的代理。但是其本身还是没有实现日志操作和业务类分离。
代理模式传送门: 结构型模式之代理模式
改造3:Spring AOP的增强策略
- 硬编码实现
public class AfterAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println("操作日志表插入一条操作记录");
}
}
public class MyTest {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new HighBpImpl());
proxyFactory.addAdvice(new AfterAdvice());
HighBp highBp = (HighBp) proxyFactory.getProxy();
highBp.regHighBp();
highBp.delHighBp();
}
}
说明:可以发现AOP的作用同样是为了增强(Advice)功能方法。而切点的概念就是一个基于表达式的拦截条件,使通过的方法进行增强。而将切点和增强方法统称为切面。对方法的增强叫做织入;对类的增强叫做引入。
为什么要提出切面这个东西?
aop会使用拦截器,来保证过滤之后通过的类进行增强,但是实际上我们大多都是方法级别的,而为了保证拦截方法,就使用切面,切面本身是由切点和增强封装的。而通过切面我们将增强方法和拦截器匹配的条件组合起来生成代理。
- 配置方式实现
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.0.xsd">
<context:component-scan base-package="cn.spy.aop"></context:component-scan>
<beans:bean id="highBpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<beans:property name="advice" ref="highBpAdvice"></beans:property>
<beans:property name="pattern" value="cn.spy.aop.proxy.HighBpImpl.regHighBp"></beans:property>
</beans:bean>
<beans:bean id="highBpProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<beans:property name="target" ref="highBpImpl"></beans:property>
<beans:property name="interceptorNames" value="highBpAdvisor"></beans:property>
<beans:property name="proxyTargetClass" value="true"></beans:property>
</beans:bean>
</beans>
@Component("highBpAdvice")
public class HighBpAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println("操作日志表插入一条操作记录");
}
}
public interface HighBp {
/** 登记高血压患者 */
public void regHighBp();
/** 删除登记的高血压患者 */
public void delHighBp();
}
@Component("highBpImpl")
public class HighBpImpl implements HighBp {
@Override
public void regHighBp() {
System.out.println("登记高血压患者");
}
@Override
public void delHighBp() {
System.out.println("删除登记的高血压患者");
}
}
测试:
public class MyTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
HighBpImpl highBp = (HighBpImpl) ac.getBean("highBpProxy");
highBp.regHighBp();
highBp.delHighBp();
}
}
结果:
登记高血压患者
操作日志表插入一条操作记录
删除登记的高血压患者
说明:可以看到如果我们想增强方法功能,可以使用Aop的增强和切点表达式配合将指定的方法进行增强。而二者结合在一起叫切面,aop通过切面生成代理来执行。