一、AOP核心思想
AOP是Aspect-Oriented Programming的缩写,翻译为面向切面编程。我个人理解切面就是一个方面。
例子,一个接口里面有增删改查四个方法:
package com.stuspring.aop.impl;
/**
* Created by bee on 17/5/15.
*/
public interface ArithmeticCalculator {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
实现类:
package com.stuspring.aop.impl;
import org.springframework.stereotype.Component;
/**
* Created by bee on 17/5/15.
*/
@Component("arithmeticCalculator")
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result=i+j;
return result;
}
@Override
public int sub(int i, int j) {
int result=i-j;
return result;
}
@Override
public int mul(int i, int j) {
int result=i*j;
return result;
}
@Override
public int div(int i, int j) {
int result=i/j;
return result;
}
}
如果想给这四个方法分别加上前置日志功能,以其中一个为例,add方法变这样:
@Override
public int add(int i, int j) {
System.out.println("The method add begins with " +i+","+j);
int result=i+j;
return result;
}
手动给每个方法都加上固然可行,但是维护起来过于麻烦,面向切面编程就是为了解决这一问题。AOP的好处就是每个逻辑位于一个位置,代码不分散便于维护和升级,业务模块更简洁。
加一个日志切面:
package com.stuspring.aop.impl;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* Created by bee on 17/5/15.
*/
@Aspect
@Component
public class LoggingAspect {
/**
* 前置通知 方法开始之前执行
* @param joinPoint
*/
@Before("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> agrs = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " begins with " + agrs);
}
/**
* 后置通知,方法执行完之后执行,不论方法是否出现异常
* 后置通知中不能访问目标方法的执行结果
*/
@After("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))")
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " ends with " + args);
}
}
新建spring配置文件beans-aspect.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:content="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">
<content:component-scan base-package="com.stuspring.aop.impl"/>
<aop:aspectj-autoproxy/>
</beans>
附maven依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
二、五种类型通知
@Before前置通知:方法开始之前执行。
/** * 前置通知 方法开始之前执行 * @param joinPoint */ @Before("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> agrs = Arrays.asList(joinPoint.getArgs()); System.out.println("The method " + methodName + " begins with " + agrs); }
@After后置通知:方法开始之后执行,不论方法是否出现异常。
/** * 后置通知,方法执行完之后执行,不论方法是否出现异常 * 后置通知中不能访问目标方法的执行结果 */ @After("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))") public void afterMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("The method " + methodName + " ends with " + args); }
@AfterRunning:返回通知,在方法返回结果之后执行。
/** * 返回通知,在方法正常结束之后执行的代码 * 返回通知可以访问方法的返回值 */ @AfterReturning(value="execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))",returning = "result") public void afterReturning(JoinPoint joinPoint,Object result){ String methodName=joinPoint.getSignature().getName(); System.out.println("The method "+methodName+" ends with "+result); }
@AfterThrowing:异常通知,在方法抛出异常之后。
/** * 异常通知:在方法抛出异常之后执行 * @param joinPoint * @param e */ @AfterThrowing(value="execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))",throwing = "e") public void afterThrowing(JoinPoint joinPoint,Exception e){ String methodName=joinPoint.getSignature().getName(); System.out.println("The method "+methodName+" occurs execution: "+e); }
@Around:环绕通知,围绕方法执行。
/** * 环绕通知需要携带ProceedingJoinPoint类型的参数 * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法 * 环绕通知必须有返回值,返回值即为目标方法的返回值 * * @param pjd */ @Around("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(..))") public Object aroundMethod(ProceedingJoinPoint pjd) { Object result = null; String methodName = pjd.getSignature().getName(); try { //前置通知 System.out.println("--->The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs())); //执行目标方法 result = pjd.proceed(); //后置通知 System.out.println("--->The method " + methodName + " ends with " + result); } catch (Throwable e) { //异常通知 System.out.println("The method "+methodName+" occurs exception : "+e); } //后置通知 System.out.println("The Method "+methodName+" ends!"); return result; }
三、指定切面的优先级
切面的优先级可以用@Order注解指定,传入的整数值越小,优先级越高。
四、复用切点表达式
/**
* 定义一个方法用于声明切入点表达式。一般地,该方法不需要再写其它代码。
*/
@Pointcut("execution(public int com.stuspring.aop.impl.ArithmeticCalculatorImpl.*(int,int))")
public void declareJoinPointExpression(){
}
/**
* 前置通知 方法开始之前执行
* @param joinPoint
*/
@Before("declareJoinPointExpression()")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> agrs = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " begins with " + agrs);
}
外部类引用可以使用报名加方法名的方法。
五、配置文件方式配置AOP
接口:
package com.stuspring.aop.fileimpl;
/**
* Created by bee on 17/5/16.
*/
public interface ArithmeticCalculator {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
实现类:
package com.stuspring.aop.fileimpl;
/**
* Created by bee on 17/5/16.
*/
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result=i+j;
return result;
}
@Override
public int sub(int i, int j) {
int result=i-j;
return result;
}
@Override
public int mul(int i, int j) {
int result=i*j;
return result;
}
@Override
public int div(int i, int j) {
int result=i/j;
return result;
}
}
日志切面:
package com.stuspring.aop.fileimpl;
import org.aspectj.lang.JoinPoint;
/**
* Created by bee on 17/5/16.
*/
public class LoggingAspect {
public void beforeMethod(JoinPoint joinPoint){
String methodName=joinPoint.getSignature().getName();
System.out.println("The method begins with "+methodName);
}
}
参数验证切面:
package com.stuspring.aop.fileimpl;
import org.aspectj.lang.JoinPoint;
/**
* Created by bee on 17/5/16.
*/
public class ValidationAspect {
public void validateArgs(JoinPoint joinPoint){
System.out.println("validationMethod......");
}
}
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"
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.3.xsd">
<!--配置bean-->
<bean id="arithmeticCalculator" class="com.stuspring.aop.fileimpl.ArithmeticCalculatorImpl"/>
<bean id="loggingAspect" class="com.stuspring.aop.fileimpl.LoggingAspect"/>
<bean id="validationAspect" class="com.stuspring.aop.fileimpl.ValidationAspect"/>
<aop:config>
<!--配置切面表达式-->
<aop:pointcut id="pointcut" expression="execution(public int com.stuspring.aop.fileimpl.ArithmeticCalculatorImpl.*(int,int))"/>
<!--配置切面通知-->
<aop:aspect ref="loggingAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
</aop:aspect>
<aop:aspect ref="validationAspect" order="1">
<aop:before method="validateArgs" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
Main测试方法:
package com.stuspring.aop.fileimpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by bee on 17/5/16.
*/
public class Main {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("beans-aspectfile.xml");
ArithmeticCalculator calculator= (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
System.out.println(calculator.add(2,4));
}
}