AOP概念
- 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- AOP是Spring框架面向切面的编程思想,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。
引用项目JAR包
- aopalliance-1.0.jar
- aspectjweaver-1.5.4.jar
- 其他版本可以使用如下地址,搜索下载:
- https://mvnrepository.com/
配置切点时expression中“execution”表达式的书写
利用实现接口的方式实现AOP
- 前置通知
- 定义普通方法作为切点
import org.dsl.service.AspectService;
public class AspectServiceImpl implements AspectService {
@Override
public void show() {
System.out.println("普通方法被执行---");
}
}
- 定义切面方法,实现MethodBeforeAdvice接口,成为前置通知
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class BeforeLog implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("前置通知被执行---");
}
}
- 将普通方法和前置通知方法,添加在IOC容器中,在applicationContext.xml文件中配置如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
default-autowire="byName"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<context:component-scan base-package="org.dsl.entity"></context:component-scan>
<!--将普通方法配置到容器中(作为切点使用)-->
<bean id="aspectServiceImpl" class="org.dsl.serviceImpl.AspectServiceImpl"></bean>
<!--将前置通知配置到容器中(作为切面)-->
<bean id="beforeLog" class="org.dsl.log.BeforeLog"></bean>
<!--配置定义切点,并将切点和切面进行关联-->
<aop:config>
<!--配置定义切点-->
<aop:pointcut expression="execution(* org.dsl.serviceImpl..*.*(..))" id="pointPram"/>
<!--实现切点和切面的关联-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointPram"/>
</aop:config>
</beans>
- 测试代码:
package org.dsl.test;
import org.dsl.service.AspectService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AspectService pram = (AspectService)context.getBean("aspectServiceImpl");
pram.show();
}
}
- 测试结果:
- 后置通知
- 普通方法如上所示
- 通过实现AfterReturningAdvice接口,定义后置通知
import java.lang.reflect.Method;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
public class AfterLog implements AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("后置通知被执行---");
}
}
- 在IOC容器中,在applicationContext.xml文件中配置如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
default-autowire="byName"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<context:component-scan base-package="org.dsl.entity"></context:component-scan>
<bean id="aspectServiceImpl" class="org.dsl.serviceImpl.AspectServiceImpl"></bean>
<bean id="beforeLog" class="org.dsl.log.BeforeLog"></bean>
<aop:config>
<aop:pointcut expression="execution(* org.dsl.serviceImpl..*.*(..))" id="pointPram"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointPram"/>
</aop:config>
<!--将后置通知配置到容器中-->
<bean id="afterLog" class="org.dsl.log.AfterLog"></bean>
<!--进行切点的配置,并将切点和切面进行关联-->
<aop:config>
<!--配置切点-->
<aop:pointcut expression="execution(* org.dsl.serviceImpl..*.*(..))" id="pointcut"/>
<!--将切点和切面进行关联-->
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 测试代码同上所示
- 测试结果如下:
- 异常通知
- 带有异常的普通方法
import org.dsl.service.AspectService;
public class AspectServiceImpl implements AspectService {
@Override
public void show() {
int a = 3;
int b = 0;
a=a/b;
System.out.println("普通方法被执行---");
}
}
- 通过实现ThrowsAdvice接口,定义异常通知
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
public class AbnormalLog implements ThrowsAdvice{
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
System.out.println("异常通知被执行---");
System.out.println("目标方法:"+method+"\n当前目标:"+target+"\n参数个数:"+args.length+"\n异常信息:"+ex.toString());
}
}
- 注:异常通知在实现ThrowsAdvice接口后,没有提供需要重写的方法,但是必须要定义如下方法,才能正常执行,源码中定义如下:
- 在IOC容器中,在applicationContext.xml文件中配置如下:
<!--将切面配置到IOC容器中-->
<bean id="abnormalLog" class="org.dsl.log.AbnormalLog"></bean>
<aop:config>
<!--配置切点-->
<aop:pointcut expression="execution(* org.dsl.serviceImpl..*.*(..))" id="pointcut2"/>
<!--将切点和切面进行关联-->
<aop:advisor advice-ref="abnormalLog" pointcut-ref="pointcut2"/>
</aop:config>
- 测试代码同上所示
- 测试结果如下(方法中参数解释):
- 环绕通知
- 普通方法如上所示
- 非异常普通代码
import org.dsl.service.AspectService;
public class AspectServiceImpl implements AspectService {
@Override
public void show() {
System.out.println("普通方法被执行---");
}
}
- 带有异常普通代码
import org.dsl.service.AspectService;
public class AspectServiceImpl implements AspectService {
@Override
public void show() {
int a = 3;
int b = 0;
a=a/b;
System.out.println("普通方法被执行---");
}
}
- 通过实现MethodInterceptor接口,定义环绕通知
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class RoundLog implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String result= null;
try {
System.out.println("环绕方法的前置通知---");
result=(String) invocation.proceed();//控制目标方法的执行
System.out.println("环绕方法的后置通知---");
} catch (Exception e) {
System.out.println("环绕方法的异常通知---");
}
return result;
}
}
- 在IOC容器中,在applicationContext.xml文件中配置如下:
</aop:config>
<bean id="roundLog" class="org.dsl.log.RoundLog"></bean>
<aop:config>
<aop:pointcut expression="execution(* org.dsl.serviceImpl..*.*(..))" id="pointcut3"/>
<aop:advisor advice-ref="roundLog" pointcut-ref="pointcut3"/>
</aop:config>
- 非异常环绕通知结果
- 异常环绕通知结果
- 注:环绕通知中invocation.proceed()方法,是控制着目标方法的执行,如果不写,目前方法是不会执行的;另环绕通知的底层是拦截器,环绕通知的返回结果执行影响着单个通知的结果(前置通知、后置通知、异常通知)。
利用注解的方式实现AOP
- 利用注解的方式实现AOP,首先要在Spring IOC容器(applicationContext.xml)中添加对注解的支持和扫描器;
- 添加如下代码,进行相应包体的扫描,查找该包路径下是否存在注解
<context:component-scan base-package="org.dsl.log"></context:component-scan>
- 添加如下代码实现注解对AOP的支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 普通方法:
import org.dsl.service.AspectService;
public class AspectServiceImpl implements AspectService {
@Override
public void show() {
System.out.println("普通方法被执行---");
}
}
- 通知方法:
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("annotionLog")
@Aspect
public class AnnotionLog {
@Before("execution(public * show(..))")
public void myBefore(JoinPoint jp) {
System.out.println("【注解模式前置通知】:"+"目标对象:"+jp.getTarget()+",目标方法参数:"+jp.getArgs().length+",目标方法:"+jp.getThis());
}
@AfterReturning("execution(public * show(..))")
public void myAfter(JoinPoint jp) {
System.out.println("【注解模式后置通知】:"+"目标对象:"+jp.getTarget()+",目标方法参数:"+jp.getArgs().length+",目标方法:"+jp.getThis());
}
}
- 注其中@Component(“annotionLog”)注解和是等价的,如果在applicationContext.xml中配置,则可以不需要再添加注释@Component(“annotionLog”),两者都是将相应的类注册到IOC容器中
- 测试代码:
import org.dsl.service.AspectService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AspectService pram = (AspectService)context.getBean("aspectServiceImpl");
pram.show();
}
}
-
测试结果
-
注解方式环绕通知
-
环绕通知的参数与前置、后置不一样,是JoinPoint的子类ProceedingJoinPoint
-
通知方法:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("annotionLog")
@Aspect
public class AnnotionLog {
@Around("execution(public * show(..))")
public void aroundLog(ProceedingJoinPoint jp) throws Throwable {
try {
System.out.println("---环绕通知---前置通知---");
jp.proceed();//执行切点方法,如果不写,则不会执行切点方法
System.out.println("---环绕通知---后置通知---");
} catch (Exception e) {
System.out.println("---环绕通知---异常通知---");
}
}
}
-
不存在异常的结果:
-
存在异常的结果:
-
注解方式最终通知(任何情况下都能够执行最终通知)
-
通知代码:
@After("execution(public * show(..))")
public void aroundLog() throws Throwable {
System.out.println("---注解方式---最终通知---");
}
- 测试结果:
- 注解方式实现异常通知
- 通知方法:
@AfterThrowing(pointcut = "execution(public * show(..))",throwing = "e")
public void aroundLog(ArithmeticException e) throws Throwable {
System.out.println("---注解方式---异常通知---");
}
- 注:注解方式的异常通知必须定义pointcut(切点)和异常参数throwing,并在下面的方法体中,定义具体的异常类型(例如算数异常、空指针异常等)
- 测试结果如下:
通过Schema配置实现AOP
- 前置通知和后置通知
- 切面代码:
import org.aspectj.lang.JoinPoint;
public class SchmaLog {
public void before() {
System.out.println("<<<<<<<<<<<<<基于配置---前置通知");
}
public void after(JoinPoint jp,Object returnValue) {
System.out.println("<<<<<<<<<<<<<基于配置---后置通知");
}
}
- 注:如果要拿到切点的信息必须使用JoinPoint,返回值需要单独定义,例如Object returnValue
- 在applicationContext.xml中配置如下:
<!--将切面配置到容器中-->
<bean id="schmaLog" class="org.dsl.log.SchmaLog"></bean>
<!--进行切面和切点的配置-->
<aop:config>
<!--定义切点-->
<aop:pointcut expression="execution(* org.dsl.serviceImpl..*.*(..))" id="pointcut4"/>
<!--指定切面所在哪个类中-->
<aop:aspect ref="schmaLog">
<!--定义通知类型,指定调用类中哪个方法,并将两者关联-->
<aop:before method="before" pointcut-ref="pointcut4"/>
<!--定义通知类型,指定调用类中哪个方法,并将两者关联,returning="returnValue"是用于切点中方法返回值的接收-->
<aop:after-returning method="after" returning="returnValue" pointcut-ref="pointcut4"/>
</aop:aspect>
</aop:config>
- 测试结果:
- 异常通知
- 切面代码:
import org.aspectj.lang.JoinPoint;
public class SchmaLog {
public void myException(JoinPoint jp,ArithmeticException e) {
System.out.println("<<<<<<<<<<<<<基于配置---异常通知");
}
}
-
注:在异常通知中,要指定具体的异常类型,并且在xml配置中配置接收才可以。
-
在applicationContext.xml中配置如下:
<aop:config>
<aop:pointcut expression="execution(* org.dsl.serviceImpl..*.*(..))" id="pointcut4"/>
<aop:aspect ref="schmaLog">
<aop:before method="before" pointcut-ref="pointcut4"/>
<aop:after-returning method="after" returning="returnValue" pointcut-ref="pointcut4"/>
<!--异常通知,定义throwing="e"用于接收由切点返回到切面中的异常信息-->
<aop:after-throwing method="myException" pointcut-ref="pointcut4" throwing="e"/>
</aop:aspect>
</aop:config>
-
测试结果:
-
环绕通知
-
切面代码:
public void around(ProceedingJoinPoint jp) throws Throwable {
try {
System.out.println("----基于配置---前置通知");
jp.proceed();//执行目标方法
System.out.println("----基于配置---后置通知");
} catch (Exception e) {
System.out.println("----基于配置---异常通知");
}
}
- 在applicationContext.xml中配置如下:
<aop:pointcut expression="execution(* org.dsl.serviceImpl..*.*(..))" id="pointcut4"/>
<aop:aspect ref="schmaLog">
<aop:before method="before" pointcut-ref="pointcut4"/>
<aop:after-returning method="after" returning="returnValue" pointcut-ref="pointcut4"/>
<aop:after-throwing method="myException" pointcut-ref="pointcut4" throwing="e"/>
<!--环绕通知-->
<aop:around method="around" pointcut-ref="pointcut4"/>
</aop:aspect>
</aop:config>
- 测试结果(带有异常的)
- 测试结果(没有异常的)
总结
-
基于接口实现的AOP方式:
- 前置通知实现MethodBeforeAdvice接口,必须重写public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {} - 后置通知实现AfterReturningAdvice接口,必须重写public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable{} - 异常通知实现ThrowsAdvice接口,无需重写方法,但是必须定义方法public void afterThrowing(Method method, Object[] args, Object target, Exception ex){} - 环绕通知实现MethodInterceptor接口,需要重写public Object invoke(MethodInvocation invocation) throws Throwable{}
-
基于注解方式实现AOP(切面中的方法参数使用JoinPoint):
- @Before实现前置通知,其中execution表达式@Before("execution(public * show(..))")定义切点方法所在位置; - @AfterReturning实现后置通知,其中execution表达式含义同上; - @Around实现环绕通知,其中execution表达式含义同上;环绕通知的参数与前置、后置不一样,是JoinPoint的子类ProceedingJoinPoint - @After("execution(public * show(..))")实现环绕通知,其中execution表达式含义同上; - @AfterThrowing(pointcut = "execution(public * show(..))",throwing = "e")实现异常通知,其中execution表达式含义同上;但是需要定义接收异常的参数,例如throwing = "e"
-
基于Schema配置实现AOP:
- <aop:before method="before" pointcut-ref="pointcut4"/>实现前置通知配置 - <aop:after-returning method="after" returning="returnValue" pointcut-ref="pointcut4"/>实现后置通知配置 - <aop:after-throwing method="myException" pointcut-ref="pointcut4" throwing="e"/>实现异常通知配置 - <aop:around method="around" pointcut-ref="pointcut4"/>实现环绕通知配置