AOP
概述
AOP(Aspect Oriented Programming):面向切面编程
面向切面编程,指在程序运行期间,将某段代码动态地切入到指定方法的指定位置进行运行的编程方式
常见场景:日志记录,编写一个计算器,需要在目标方法开始,返回,出现异常,结束时记录信息
第一种方法:直接编写在方法内部
缺点:维护修改困难,核心功能和辅助功能耦合
第二种方法:使用动态代理
public class CalculatorProxy {
public static Calculator getProxy(Calculator calculator){
ClassLoader classLoader = calculator.getClass().getClassLoader();
Class<?>[] interfaces = calculator.getClass().getInterfaces();
//方法执行器,用于执行目标方法
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
Object proxy:代理对象,提供给JDK使用
Method method:当前将要执行的目标对象的方法
Object[] args:目标对象的方法执行所需要的参数
*/
Object result = null;
try {
LogUtils.logStart(method,args);
result = method.invoke(calculator, args);
LogUtils.logReturn(method,result);
} catch (Exception e) {
LogUtils.logException(method,e);
} finally {
LogUtils.logEnd(method);
}
return result;
}
};
//如果目标对象没有实现任何接口,即interfaces为空,则无法创建代理对象
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, handler);
return (Calculator) proxy;
}
}
缺点:编写困难,且jdk的动态代理,若目标对象没有实现任何接口,则无法创建代理对象
第三种方法:利用Spring框架实现AOP功能
AOP专业术语

步骤
-
导包
commons-logging-1.1.1.jar
spring-aop-5.2.6.RELEASE.jar
spring-beans-5.2.6.RELEASE.jar
spring-context-5.2.6.RELEASE.jar
spring-core-5.2.6.RELEASE.jar
spring-expression-5.2.6.RELEASE.jarSpring支持面向切面编程的包是
spring-aspects-5.2.6.RELEASE.jar
扩展功能包
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar -
写配置
- 将目标类和切面类(封装了通知方法)加入到ioc容器中
- 切面类添加注解@Aspect
- 添加通知方法注解
/* 五个通知注解 @Before:前置通知,在目标方法调用之前运行 @After:后置通知,在目标方法结束之后运行 @AfterReturning:返回通知,在目标方法正常返回之后运行 @AfterThrowing:异常通知,在目标方法抛出异常之后通知 @Around:环绕通知,在目标方法前后执行 try{ @Before method.invoke(obj,args); @AfterReturning }catch(e){ @AfterThrowing }finally{ @After } */@Aspect @Component public class LogUtils { //在方法开始之前执行 @Before(value = "execution(public int com.th1024.bean.impl.MyMathCalculator.*(..))") public static void logStart(){ System.out.println("方法开始执行"); } //在方法正常返回之后执行 @AfterReturning(value = "execution(public int com.th1024.bean.impl.MyMathCalculator.*(..))") public static void logReturn(){ System.out.println("方法执行完成"); } //在方法出现异常时执行 @AfterThrowing(value = "execution(public int com.th1024.bean.impl.MyMathCalculator.*(..))") public static void logException(){ System.out.println("方法出现异常"); } //在方法最终结束后运行 @After(value = "execution(public int com.th1024.bean.impl.MyMathCalculator.*(..))") public static void logEnd(){ System.out.println("方法最终执行完成"); } } -
测试
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Calculator bean = context.getBean(Calculator.class);
bean.add(1,2);
}
细节点
- IOC容器中保存的是组件的代理对象
@Test
public void test4(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//从ioc容器中获取目标对象,一定要使用接口类型,AOP底层使用动态代理,创建的是目标对象的代理对象
// Calculator bean = context.getBean(Calculator.class);
//没有接口即使用本类型获取
MyMathCalculator bean = (MyMathCalculator) context.getBean("myMathCalculator");
bean.add(1,2);
System.out.println(bean);//com.th1024.bean.impl.MyMathCalculator@53fb3dab
System.out.println(bean.getClass());//com.th1024.bean.impl.MyMathCalculator$$EnhancerBySpringCGLIB$$97cc1b67,CGLIB创建的代理对象
}
补充:CGLIB可以为没有实现接口的对象创建代理对象
- 切入点表达式的写法,通配符
/*
切入点表达式
固定写法:execution(访问权限符 返回值类型 方法全类名(参数列表))
其中访问权限符可省略不写,默认为public
通配符:
*:
1. 匹配一个或多个字符:execution(public int com.th1024.bean.impl.MyMath*r.*(..))
2. 匹配任意一个参数:execution(public int com.th1024.bean.impl.MyMath*r.*(int,*)),第二个参数可为任意类型
3. 匹配一层路径
4. 匹配所有返回值类型
..:
1. 匹配任意多个参数,任意类型参数
2. 匹配任意多层路径:execution(public int com.th1024..MyMath*r.*(int,*))
最精确的:execution(public int com.th1024.bean.impl.MyMathCalculator.add(int,int))
最模糊的:execution( * *.*(..))
*/
- 通知方法的执行顺序
/*
通知方法执行顺序
正常执行:@Before(前置通知)-->@After(后置通知)-->@AfterReturning(正常返回)
异常执行:@Before(前置通知)-->@After(后置通知)-->@AfterThrowing(抛出异常)
*/
@Test
public void test5(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
MyMathCalculator bean = (MyMathCalculator) context.getBean("myMathCalculator");
bean.add(1,2);
System.out.println("=============");
// bean.div(2,1);
// bean.div(1,0);
}
- JoinPoint获取目标方法的信息
@Before(value = "execution(public int com.th1024.bean.impl.MyMathCalculator.*(..))")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行时使用的参数
Object[] args = joinPoint.getArgs();
//获取到目标方法签名
Signature signature = joinPoint.getSignature();
System.out.println("logUtils:【"+signature.getName()+"】方法开始执行,参数列表为"+ Arrays.asList(args));
}
- @AfterReturning、@AfterThrowing设置参数接收返回值和异常
//returning="result",设置result接收方法返回值
@AfterReturning(value = "execution(public int com.th1024.bean.impl.MyMathCalculator.*(..))",returning = "result")
public static void logReturn(JoinPoint joinPoint,Object result){
System.out.println("logUtils:【"+joinPoint.getSignature().getName()+"】方法执行完成,返回结果为"+ result);
}
//throwing="e",设置e接收方法抛出的异常
@AfterThrowing(value = "execution(public int com.th1024.bean.impl.MyMathCalculator.*(..))",throwing = "e")
public static void logException(JoinPoint joinPoint,Exception e){
System.out.println("logUtils:【"+joinPoint.getSignature().getName()+"】方法出现异常,异常信息为"+ e);
}
Spring对通知方法的要求为参数一定要正确,不然无法识别为通知方法或者无法正确接收值
- 抽取可重用的切入点表达式
//抽取相同的切入点
@Pointcut(value = "execution(public int com.th1024.bean.impl.MyMathCalculator.*(..))")
public void pointDemo() {}
@Before(value = "pointDemo()")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行时使用的参数
Object[] args = joinPoint.getArgs();
//获取到目标方法签名
Signature signature = joinPoint.getSignature();
System.out.println("logUtils:【"+signature.getName()+"】方法开始执行,参数列表为"+ Arrays.asList(args));
}
- 环绕通知
/*
@Around:环绕通知,是Spring中强大的通知方法
类似动态代理,集合了其它四种通知的功能
环绕通知优先于普通通知执行,顺序:
普通前置
{
环绕前置
目标方法执行
环绕方法返回/异常
环绕后置
}
普通后置
普通方法返回/异常
环绕前置-->普通前置-->环绕方法返回/异常-->环绕后置-->普通后置-->普通返回
*/
@Around(value = "pointDemo()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object[] args = proceedingJoinPoint.getArgs();
String name = proceedingJoinPoint.getSignature().getName();
Object proceed = null;
try {
//@Before()
System.out.println("环绕前置:【"+name+"】方法开始执行,参数列表为"+ Arrays.asList(args));
//利用反射调用目标方法--method.invoke(obj,args)
proceed = proceedingJoinPoint.proceed(args);
//@AfterReturning
System.out.println("环绕返回:【"+name+"】方法执行完成,返回结果为" + proceed);
} catch (Exception e) {
//@AfterThrowing
System.out.println("环绕异常:【"+name+"】方法出现异常,异常信息为" + e);
//为避免其他通知方法接收不到异常信息,将异常抛出
throw new RuntimeException(e);
} finally {
//@After
System.out.println("环绕后置:【"+name+"】方法最终执行完成");
}
return proceed;
}
- 多切面运行顺序
创建另一个切面类,ValidateAspect
@Aspect
@Component
@Order(2)
public class ValidateAspect {
@Before(value = "com.th1024.utils.LogUtils.pointDemo()")
public void vaStart(JoinPoint joinPoint){
//获取到目标方法运行时使用的参数
Object[] args = joinPoint.getArgs();
//获取到目标方法签名
Signature signature = joinPoint.getSignature();
System.out.println("validate:【"+signature.getName()+"】方法开始执行,参数列表为"+ Arrays.asList(args));
}
//returning="result",设置result接收方法返回值
@AfterReturning(value = "com.th1024.utils.LogUtils.pointDemo()",returning = "result")
public void vaReturn(JoinPoint joinPoint, Object result){
System.out.println("validate:【"+joinPoint.getSignature().getName()+"】方法执行完成,返回结果为"+ result);
}
//throwing="e",设置e接收方法抛出的异常
@AfterThrowing(value = "com.th1024.utils.LogUtils.pointDemo()",throwing = "e")
public void vaException(JoinPoint joinPoint,Exception e){
System.out.println("validate:【"+joinPoint.getSignature().getName()+"】方法出现异常,异常信息为"+ e);
}
@After(value = "com.th1024.utils.LogUtils.pointDemo()")
public void vaEnd(JoinPoint joinPoint){
System.out.println("validate:【"+joinPoint.getSignature().getName()+"】方法最终执行完成");
}
}
执行测试
@Test
public void test5(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
MyMathCalculator bean = (MyMathCalculator) context.getBean("myMathCalculator");
bean.add(1,2);
System.out.println("=============");
// bean.div(2,1);
// bean.div(1,0);
}
输出结果
环绕前置:【add】方法开始执行,参数列表为[1, 2]
logUtils:【add】方法开始执行,参数列表为[1, 2]
validate:【add】方法开始执行,参数列表为[1, 2]
validate:【add】方法最终执行完成
validate:【add】方法执行完成,返回结果为3
环绕返回:【add】方法执行完成,返回结果为3
环绕后置:【add】方法最终执行完成
logUtils:【add】方法最终执行完成
logUtils:【add】方法执行完成,返回结果为3
图示

AOP的应用
- AOP添加日志记录保存到数据库
- AOP做权限检查
- AOP做安全检查
- AOP做事务控制
基于配置的AOP
<!--基于配置的AOP-->
<bean id="myMathCalculator_xml" class="com.th1024.bean.impl.MyMathCalculator_xml"></bean>
<bean id="validateAspect_xml" class="com.th1024.proxy.ValidateAspect_xml"></bean>
<bean id="logUtils_xml" class="com.th1024.utils.LogUtils_xml"></bean>
<!--AOP名称空间-->
<aop:config>
<!--提取切入点-->
<aop:pointcut id="pointDemo" expression="execution(* com.th1024.bean.impl.MyMathCalculator_xml.*(..))"/>
<!--指定切面-->
<aop:aspect ref="logUtils_xml" order="1">
<aop:before method="logStart" pointcut-ref="pointDemo"/>
<aop:after-returning method="logReturn" pointcut-ref="pointDemo" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="pointDemo" throwing="e"/>
<aop:after method="logEnd" pointcut-ref="pointDemo"/>
<aop:around method="myAround" pointcut-ref="pointDemo"/>
</aop:aspect>
<aop:aspect ref="validateAspect_xml" order="0">
<aop:before method="vaStart" pointcut-ref="pointDemo"/>
<aop:after-returning method="vaReturn" pointcut-ref="pointDemo" returning="result"/>
<aop:after-throwing method="vaException" pointcut-ref="pointDemo" throwing="e"/>
<aop:after method="vaEnd" pointcut-ref="pointDemo"/>
</aop:aspect>
</aop:config>
<!--
注解:快速方便
配置:功能完善
重要的切面用配置,不重要的用注解
-->
浙公网安备 33010602011771号