一、面向切面编程简介
面向切面编程,将需要管理的类进行统一管理。针对需要管理的类及方法,找到正确的切点,将一系列类及方法应用于一批受管理的对象上。
该功能典型应用场景包括:日志、校验、数据库事务管理等。
二、实例
1. 需求
一个计算器接口,有加减乘除四个方法,需要在每个方法前后增加日志记录的功能。
2. 首先必须有需要进行管理的对象
1)定义ArithmeticCalculator 接口
package com.spring.beans.aop.xml;
public interface ArithmeticCalculator {
public int add(int a, int b);
public int sub(int a, int b);
public int mul(int a, int b);
public int div(int a, int b);
}
2)实现接口
package com.spring.beans.aop.xml;
public class ArithmeticCalculatorImpl implements ArithmeticCalculator{
@Override
public int add(int a, int b) {
return a + b;
}@Override
public int sub(int a, int b) {
return a - b;
}@Override
public int mul(int a, int b) {
return a * b;
}@Override
public int div(int a, int b) {
return a/b;
}}
3) 定义bean,将该类的实例纳入spring容器进行管理
<bean id="arithmeticCalculator" class="com.spring.beans.aop.xml.ArithmeticCalculatorImpl"></bean>
3. 定义切面类及方法
1)增加日志切面类LoggingAspect
package com.spring.beans.aop.xml;
import java.util.Arrays;
import java.util.List;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;/**
* order标注,表示切面的优先级
*/
public class LoggingAspect {
public void beforeMethod(JoinPoint joinpoint) {
String methodName = joinpoint.getSignature().getName();
List<Object> args = Arrays.asList(joinpoint.getArgs());
System.out.println("before method " + methodName + " with args " + args);
}
public void afterMethod(JoinPoint joinpoint) {
String methodName = joinpoint.getSignature().getName();
System.out.println("after method " + methodName );
}
public void afterReturningMethod(JoinPoint joinpoint, Object result) {
String methodName = joinpoint.getSignature().getName();
System.out.println("after returning method " + methodName + " return with " + result );
}
public void afterThrowingMethod(JoinPoint joinpoint, Exception ex) {
String methodName = joinpoint.getSignature().getName();
System.out.println("after throwing method " + methodName + " return with " + ex );
}
public Object aroundMethod(ProceedingJoinPoint pjd) {
String methodName = pjd.getSignature().getName();
Object result = null;
try {
System.out.println("前置通知");
result = pjd.proceed();
System.out.println("返回通知");
} catch (Throwable e) {
System.out.println("异常通知");
throw new RuntimeException(e);
}
System.out.println("后置通知");
return result;
}
}
2)声明日志切面类为一个spring容器管理的一个bean
<bean id="loggingAspect" class="com.spring.beans.aop.xml.LoggingAspect"></bean>
4. 进行AOP配置
1)配置切点
<aop:pointcut expression="execution(public int com.spring.beans.aop.xml.ArithmeticCalculatorImpl.*(int, int))" id="pointcut"/>
2)配置切面类及应用到切点的方法
<aop:aspect ref="loggingAspect" order="2">
<!-- 定义日志切面的后置方法 -->
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<!-- 定义日志切面的前置方法 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<!-- 定义日志切面的正常返回方法,若方法正常返回,可以获取到切点的返回值 -->
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut" returning="result"/>
<!-- 定义日志切面的异常返回方法,若方法出现异常,可以获取到方法抛出的异常 -->
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"/>
<!-- 可自定义前后置、返回和异常方法全流程 -->
<!-- <aop:around method="aroundMethod" pointcut-ref="pointcut"/> -->
</aop:aspect>
3)最终形成的spring配置文件为:
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<bean id="arithmeticCalculator" class="com.spring.beans.aop.xml.ArithmeticCalculatorImpl"></bean>
<bean id="loggingAspect" class="com.spring.beans.aop.xml.LoggingAspect"></bean>
<bean id="validataAspect" class="com.spring.beans.aop.xml.ValidataAspect"></bean>
<!-- 切面配置 -->
<aop:config>
<!-- 定义切点,切点选取在类com.spring.beans.aop.xml.ArithmeticCalculatorImpl的所有带两个int参数的方法上 -->
<aop:pointcut expression="execution(public int com.spring.beans.aop.xml.ArithmeticCalculatorImpl.*(int, int))" id="pointcut"/>
<!-- 对切点应用日志切面 ,切面实际上也是一个bean。-->
<aop:aspect ref="loggingAspect" order="2">
<!-- 定义日志切面的后置方法 -->
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<!-- 定义日志切面的前置方法 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<!-- 定义日志切面的正常返回方法,若方法正常返回,可以获取到切点的返回值 -->
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut" returning="result"/>
<!-- 定义日志切面的异常返回方法,若方法出现异常,可以获取到方法抛出的异常 -->
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"/>
<!-- 可自定义前后置、返回和异常方法全流程 -->
<!-- <aop:around method="aroundMethod" pointcut-ref="pointcut"/> -->
</aop:aspect>
<!-- 对切点应用校验切面 -->
<aop:aspect ref="validataAspect" order="1">
<aop:before method="validateArgs" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
5. 测试
1)编写测试代码
package com.spring.beans.aop.xml;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
ArithmeticCalculator calc = (ArithmeticCalculator)ctx.getBean("arithmeticCalculator");
calc.add(1, 3);
calc.mul(1, 3);
calc.sub(3, 5);
calc.div(1, 0);
}
}
2)测试结果