一、概述
1.1、何为AOP
- AOP是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 应用范围:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
1.2、AOP 基本概念
1、切面的基本属性
- 切面(aspect):用来切插业务方法的类。
- 连接点(joinpoint):是切面类和业务类的连接点,其实就是封装了业务方法的一些基本属性,作为通知的参数来解析。
- 通知(advice):在切面类中,声明对业务方法做额外处理的方法。
- 切入点(pointcut):业务类中指定的方法,作为切面切入的点。其实就是指定某个方法作为切面切的地方。
- 目标对象(target object):被代理对象。
- AOP代理(aop proxy):代理对象。
- 前置通知(before advice):在切入点之前执行。
- 后置通知(after returning advice):在切入点执行完成后,执行通知。
- 环绕通知(around advice):包围切入点,调用方法前后完成自定义行为。
- 异常通知(after throwing advice):在切入点抛出异常后,执行通知。
2、切入点表达式
二、AOP实例
2.1、基于xml配置文件实现
1、定义切面类HomeAspect
public class HomeAspect {
public void before(){
System.out.println("homeAspect before --- 业务执行前调用 before 通知" );
}
public void afterReturning(String name){
System.out.println("homeAspect afterReturning --- 业务执行完成后调用 afterReturning 通知并且可以获得方法的返回值,如果应用编译报错此方法不执行");
System.out.println("homeAspect afterReturning --- 对有返回值业务方法进行返回值大写功能加强"+name.toUpperCase());
}
public void after(){
System.out.println("homeAspect after(or finally advice) --- 业务执行完毕后时调用 after 通知,此通知无论是否报错最终都会执行,不能获取方法的返回值");
}
public Object around(ProceedingJoinPoint joinPoint){
Object proceed = null;
try {
System.out.println("homeAspect around ...1");
proceed = joinPoint.proceed();
System.out.println("homeAspect around ...2");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
public void afterThrowing(){
System.out.println("homeAspect afterThrowing --- 业务逻辑编译报错时执行此通知");
}
public Object aroundInit(ProceedingJoinPoint joinPoint,String name,int age){
System.out.println("增强并大写应用方法的参数 "+name.toUpperCase()+" "+age);
Object proceed = null;
try {
System.out.println("homeAspect aroundInit ...1");
proceed = joinPoint.proceed();
System.out.println("homeAspect aroundInit ...2");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
2、xml配置
<!--aop依赖包:aspectjrt.jar,aspectjweaver.jar以及aopalliance_1.0.jar-->
<!--注入主业务类-->
<bean id="homeService" class="com.charge.aopdemo.basedaop.HomeService"></bean>
<!--注入自定义切面-->
<bean id="homeAspect" class="com.charge.aopdemo.basedaop.HomeAspect"></bean>
<!--aop配置:CGLIB代理-->
<aop:config >
<!--配置切面,指明切面是哪一个类,里面包含了切入点-->
<aop:aspect id="myAspect" ref="homeAspect" order="1">
<aop:pointcut id="homePointcut" expression="execution(* com.charge.aopdemo.basedaop.HomeService.*(..))" />
<!--配置应用业务执行前通知-->
<aop:before method="before" pointcut-ref="homePointcut"></aop:before>
<!--配置应用执行之后的通知并得到业务方法返回值并加强它,只有有返回值的方法才会执行此通知j-->
<aop:after-returning method="afterReturning" pointcut-ref="homePointcut" returning="name"></aop:after-returning>
<!--此通知又名finally通知,主要是在切入点目标对象的应用方法完全执行完毕以后执行-->
<aop:after method="after" pointcut-ref="homePointcut"></aop:after>
<!--此通知执行后抛异常通知,如果业务方法在执行的过程中出现异常就会执行这个通知业务-->
<aop:after-throwing method="afterThrowing" pointcut-ref="homePointcut"></aop:after-throwing>
<!--环绕通知,在运行前后均会执行,最重要的一个通知-->
<aop:around method="around" pointcut-ref="homePointcut"></aop:around>
<!--增强型环绕通知-->
<aop:around method="aroundInit" pointcut="execution(* com.charge.aopdemo.basedaop.HomeService.doThird(String,int)) and args(name,age)"></aop:around>
</aop:aspect>
</aop:config>
- 注意:定义切面、定义切入点、定义通知三者的顺序一定不能变。
- order:定义切面的优先级
3、自定义HomeService类
public class HomeService {
public void doFirst(){
System.out.println("homeService主业务类执行了 doFirst方法。。。");
throw new RuntimeException();
}
public String doSecond(String name){
System.out.println("homeService主业务类执行了 doSecond方法。。。");
return name;
}
public void doThird(String name,int age){
System.out.println("homeService主业务类执行了 doThird方法。。。");
}
}
4、测试类
@Test
public void test01(){
// homeService.doFirst();
// homeService.doSecond("mark jack");
homeService.doThird("mark",23);
}
- 测试结果
homeAspect before --- 业务执行前调用 before 通知
homeAspect around ...1
增强并大写应用方法的参数 MARK 23
homeAspect aroundInit ...1
homeService主业务类执行了 doThird方法。。。
homeAspect aroundInit ...2
homeAspect around ...2
homeAspect after(or finally advice) --- 业务执行完毕后时调用 after 通知,此通知无论是否报错最终都会执行,不能获取方法的返回值
- 注意:业务执行完成后调用 afterReturning 通知并且可以获得方法的返回值,如果应用编译报错此方法不执行
- 注释掉doThird方法,调试doFirst方法,doFirst方法中抛出了一个运行时异常,结果如下:
homeAspect before --- 业务执行前调用 before 通知
homeAspect around ...1
homeService主业务类执行了 doFirst方法。。。
java.lang.RuntimeException
at com.charge.aopdemo.basedaop.HomeService.doFirst(HomeService.java:13)
at com.charge.aopdemo.basedaop.HomeService$$FastClassBySpringCGLIB$$afd28d2d.invoke(<generated>)
homeAspect after(or finally advice) --- 业务执行完毕后时调用 after 通知,此通知无论是否报错最终都会执行,不能获取方法的返回值
如上所示:afterReturning、aroundInit方法均未执行
2.2、基于注解实现
1、xml配置
<context:component-scan base-package="com.charge.aopdemo.aspecj"/>
<!--配置aspectj的xml配置,声明要使用aspectj-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- aop:aspectj-autoproxy:声明使用aspectj的标签,只有实现了这一步,MyAspect中的切面标签才会生效。
2、自定义切面MyAspect
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.charge.aopdemo.aspecj.service.*Biz.do*(..))")
public void bizPoint(){
}
@Pointcut("execution(* com.charge.aopdemo.aspecj.service.MyService.*(..))")
public void servicePoint(){
}
//注意:||是交集两个表达式都包含,&& 主要作用于两个表达式的公共部分
@Before(value = "bizPoint() || servicePoint() ")
public void before(){
System.out.println("执行了 before 通知");
}
//如果主业务方法中含参数,则执行此通知并将arg参数传入此方法
@Before("bizPoint() && args(arg)")
public void beforeWithParam(String arg){
System.out.println("bizPoint切入点 ===>> 执行了 beforeWithParam 带参的before通知,业务方法传入的参数是: "+ arg);
}
@Before("bizPoint() && @annotation(homeMethod)")
public void beforeWithAnnotation(HomeMethod homeMethod){
System.out.println("bizPoint和servicePoint切入点 ===>> 执行了 beforeWithAnnotation 带自定义注解参数的before通知,业务方法传入的参数是: "+homeMethod.value());
}
//注意:如果主业务方法没有返回值,则此通知不会执行
@AfterReturning(pointcut = "bizPoint() || servicePoint()",returning = "returnValue")
public void afterReturning(String returnValue){
System.out.println("执行了 afterReturning 通知,主业务方法返回的参数是: " + returnValue);
}
@After("bizPoint() || servicePoint()")
public void after(){
System.out.println("执行了 after(finally) 通知");
}
@AfterThrowing(value = "bizPoint() || servicePoint()",throwing = "ex")
public void afterThrowing(Exception ex){
System.out.println("执行了 afterThrowing 通知,返回的错误参数是: "+ex.getMessage());
}
@Around("bizPoint() || servicePoint()")
public Object around(ProceedingJoinPoint joinPoint) {
Object proceed = null;
try {
System.out.println("执行了 around 通知01.。。。。。。");
proceed = joinPoint.proceed();
System.out.println("执行了 around 通知02.。。。。。。");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
- 注意:一定要使用**@Aspect**申明
2.3、jdk相关接口实现AOP
1、自定义HomeAfterReturningAdvice后置通知
+继承 org.springframework.aop.AfterReturningAdvice接口
public class HomeAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object target) throws Throwable {
System.out.println("我是后置通知,执行了 "+method.getName()+"方法的目标对象是: " + target.getClass().getName());
}
}
2、自定义HomeBeforeAdvice前置通知
- 继承org.springframework.aop.MethodBeforeAdvice接口
public class HomeBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object target) throws Throwable {
System.out.println("我是前置通知,拦截的方法名是: "+method.getName()+" 目标对象是: " + target.getClass().getName());
}
}
3、自定义HomeInterceptorAdvice环绕通知
- 继承org.aopalliance.intercept.MethodInterceptor接口
public class HomeInterceptorAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("执行环绕通知 ==== HomeInterceptorAdvice 01,拦截的方法:"+methodInvocation.getMethod().getName());
Object proceed = methodInvocation.proceed();
System.out.println("执行环绕通知 ==== HomeInterceptorAdvice 02");
return proceed;
}
}
4、自定义HomeThrowingAdvice异常通知
- 继承org.springframework.aop.ThrowsAdvice接口
public class HomeThrowingAdvice implements ThrowsAdvice {
//异常通知有四个方法,每一个方法的作用均不同
/*
public void afterThrowing(Exception ex)
public void afterThrowing(RemoteException ex)
public void afterThrowing(Method method,Object[] args,Object target,Exception ex)
public void afterThrowing(Method method,Object[] args,Object target,ServletException ex)
*/
public void afterThrowing(Method method, Object[] objects, Object target,Exception ex){
System.out.println(target.getClass().getName()+"目标对象的"+method.getName()+"方法遇到了"+ex+"异常,");
}
}
5、自定义切点类HomePointcut:根据目标对象的方法名进行匹配
- 继承org.springframework.aop.support.NameMatchMethodPointcut接口
public class HomePointcut extends NameMatchMethodPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
//设置一个方法名匹配
this.setMappedName("doFirst");
//设置多个方法名匹配
String[] methods = {"doSecond","doThird"};
this.setMappedNames(methods);
return super.matches(method, targetClass);
}
}
6、定义业务类IndexServiceImpl
public class IndexServiceImpl implements IndexService{
public void doFirst(){
System.out.println("执行了doFirst方法。。。");
}
public void doSecond(){
System.out.println("执行了doSecond方法。。。");
}
public void doThird(){
System.out.println("执行了doThird方法。。。");
}
}
7、配置XML文件
- 配置方式一:spring AOP API方式配置
<bean id="indexService" class="com.charge.aopdemo.jdkaop.service.IndexServiceImpl"></bean>
<!--注册advice-->
<bean id="homeBeforeAdvice" class="com.charge.aopdemo.jdkaop.advice.HomeBeforeAdvice"></bean>
<bean id="homeAfterAdvice" class="com.charge.aopdemo.jdkaop.advice.HomeAfterReturningAdvice"></bean>
<bean id="homeInterceptorAdvice" class="com.charge.aopdemo.jdkaop.advice.HomeInterceptorAdvice"></bean>
<bean id="homeThrowingAdvice" class="com.charge.aopdemo.jdkaop.advice.HomeThrowingAdvice"></bean>
<!--配置切入点-->
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut" >
<property name="mappedNames">
<list>
<value>do*</value>
</list>
</property>
</bean>
<!--配置advisor-->
<bean id="homeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" >
<property name="advice" ref="homeBeforeAdvice"></property>
<property name="pointcut" ref="pointcutBean"></property>
</bean>
- 配置方式二:配置proxy代理bean
<!--配置proxy代理bean-->
<bean id="homeProxyBean" class="org.springframework.aop.framework.ProxyFactoryBean" >
<property name="target" ref="indexService"></property>
<property name="interfaces" value="com.charge.aopdemo.jdkaop.service.IndexService"></property>
<!--配置要执行的切面-->
<property name="interceptorNames">
<list>
<value>homeBeforeAdvice</value>
<value>homeAfterAdvice</value>
<value>homeInterceptorAdvice</value>
<value>homeThrowingAdvice</value>
</list>
</property>
</bean>
- 测试结果
我是前置通知,拦截的方法名是: doFirst 目标对象是: com.charge.aopdemo.jdkaop.service.IndexServiceImpl
执行环绕通知 ==== HomeInterceptorAdvice 01,拦截的方法:doFirst
执行了doFirst方法。。。
执行环绕通知 ==== HomeInterceptorAdvice 02
我是后置通知,执行了 doFirst方法的目标对象是: com.charge.aopdemo.jdkaop.service.IndexServiceImpl