AOP引入:
我们首先通过下面这一段代码看看传统编程存在的问题
package com.hnu.service;
public class SomeServiceImpl implements SomeService {
@Override
public void doFirst() {
SystemService.doTx(); //系统级事务服务
System.out.println("执行doFirst()方法");
SystemService.doLog(); //系统级日志服务
}
@Override
public void doSecond() {
SystemService.doTx();
System.out.println("执行doSecond()方法");
SystemService.doLog();
}
}
可以看到,我们在主业务逻辑中穿插了系统级服务,这样会使得主业务逻辑过于复杂,编码时需要考虑的东西太多,不利于我们编码。
使用代理可以有效解决上面存在的问题,我们还是通过代码说明
代理通常有两种:
(1)若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
优点:因为有接口,所以使系统更加松耦合
缺点:为每一个目标类创建接口
(2)若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。
本例中使用JDK的java.lang.reflect.Proxy类代理:
package com.hnu.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyTest {
public static void main(String[] args) {
final SomeService target = new SomeServiceImpl();
SomeService service = (SomeService)Proxy.newProxyInstance(
target.getClass().getClassLoader(), //这里target.getClass()拿到的是SomeServiceImpl.Class对象
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
SystemService.doTx(); //系统级事务服务
Object result = method.invoke(target, args);
SystemService.doLog(); //系统级日志服务
return result;
}
});
service.doFirst();
System.out.println("=======================");
service.doSecond();
}
}
AOP简介:
AOP(Aspect Orient Programming),面向切面编程,是面向对象编程OOP的一种补充。面向对象编程是从静态角度考虑程序结构,而面向切面编程是从动态角度考虑程序运行过程。
AOP是一种编程思想,而Spring实现了这种思想,Spring的 AOP编程底层就是采用动态代理模式实现的。采用了两种代理:JDK的Proxy动态代理与CGLIB的动态代理。
Spring的AOP编程,就是将交叉业务逻辑封装层切面,利用AOP容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指通用的、与主业务逻辑无关的代码,如安全检查、事务、日志等。
若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样会使主业务逻辑变得混杂不清。
例如,转账,在真正转账业务逻辑前后,需要权限控制,日志记录,加载事务,结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑--转账。
Spring的AOP编程术语:
(1)切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务,日志就可以理解为切面,实际就是对主业务逻辑的一种增强。常用的切面有通知与顾问。
(2)织入(Weaving)
织入是指将切面代码插入到目标对象的过程。上例中InvocationHandler类中的invoke()方法完成的工作就可以称为织入。
(3)连接点(Join Point)
连接点指可以被切面织入的方法。通常业务接口中的方法均为连接点。
(4)切入点(Pointcut)
切入点指被切面真正织入的方法。假如在SomeServiceImpl中doFirst()被增强而doSecond()不被增强,则doFirst()为切入点,而doSecond()仅为连接点。
注意:被标记为final的方法是不能作为连接点或切入点的,因为最终的是不能被修改的,也就是不能被增强的。
(5)目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象,如上例中的SomeServiceImpl类的对象如果被增强了,那么它就是目标对象。
(6)通知(Advice)
通知是切面的一种实现,可以完成简单的织入功能。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是在目标方法之前执行还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
(7)顾问(Advisor)
顾问是切面的另一种实现,能够将通知以更复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。
通知Advice:
搭建AOP编程环境:
导入AOP联盟和Spring关于AOP的两个jar包
AOP联盟的jar包是一套关于AOP编程的规范(类似于JDBC规范),是一组接口,而Spring的AOP jar包是对这套规范的实现。
1.前置通知
定义前置通知,需要实现MethodBeforeAdvice接口。该接口中有一个方法before()(返回值为void),会在目标方法执行之前执行。
前置通知的特点:
(1)在目标方法之前先执行
(2)不改变目标方法的执行流程,前置通知不能阻止目标方法执行
(3)不改变目标方法的执行结果
2.后置通知
定义后置通知,需要实现AfterReturningAdvice接口。该接口中有一个方法afterReturing()(返回值为void),会在目标方法执行之后执行。
后置通知的特点:
(1)在目标方法之后执行
(2)不改变目标方法的执行流程,前置通知不能阻止目标方法执行
(3)不改变目标方法的执行结果(虽然可以获取到目标方法的返回值,但因为afterReturing()的返回值为void,所以对目标方法的返回值的修改不能传递回去)
3.环绕通知
定义环绕通知,需要实现MethodInterceptor接口。环绕通知,也叫方法拦截器,可以在目标方法调用之前及之后做处理,可以改变目标方法的返回值,也可以改变程序执行流程。
注意:org.aopalliance.intercept.MethodInterceptor才是需要的包。
4.异常通知
定义异常通知,需要实现ThrowsAdvice接口。需要自己实现public void afterThrowing(Exception ex)方法。当目标方法抛出与指定类型的异常具有is-a关系的异常时,执行当前方法。
省略知识点:
为目标对象织入多个通知,只需要在配置ProxyFactoryBean时注入多个通知即可。
有接口默认使用Proxy,无接口自动切换CGLIB。如果有接口时想使用CGLIB只需要做一点配置即可。
顾问Advisor:
通知是Spring提供的一种切面,但是其功能过于简单,只能将切面织入到目标类的所有目标方法中,无法完成将切面织入到指定目标的方法中。
顾问是Spring提供的另一种切面,它将通知进行了包装,其可以完成更为复杂的切面织入功能。
PointcutAdvisor是顾问的一种,PointcutAdvisor接口有两个较为常用的实现类:
(1)NameMatchMathodPointcutAdvisor 名称匹配方法切入点顾问
(2)RegexpMethodPointcutAdvisor 正则表达式匹配法切入点顾问
使用顾问,只需要在applicationContext.xml里面稍作配置即可:
名称匹配方法切入点顾问:匹配的是简单方法名
正则表达式匹配法切入点顾问:匹配的是全限定性方法名
自动代理生成器:
ProxyFactoryBean类的功能太过简单,导致以下两个问题:
1.若有多个目标对象需要增强,就需要使用多次ProxyFactoryBean来创建多个代理对象,这会使得配置文件过于庞大。
2.用户真正想要调用的是目标对象,而真正可以调用的却是代理对象,这不符合正常逻辑。
使用自动代理生成器可以解决上面的问题,常用的自动代理生成器有两个:
1.默认Advisor自动代理生成器 DefaultAdvisorAutoProxyCreator
2.Bean名称自动代理生成器 BeanNameAutoProxyCreator
自动代理生成器底层是使用Bean后处理器对Bean进行了增强
默认Advisor自动代理生成器:只要一行配置就可以了
DefaultAdvisorAutoProxyCreator存在三个问题:
1.不能选择目标对象(默认作用于全部目标对象)
2.不能选择切面类型,即切面只能是Advisor而不能是Advice
3.不能指定Advisor,所以所有的Advisor都会被作为切面织入到目标方法
Bean名称自动代理生成器:可以解决上面三个问题: