什么是AOP:
AOP是面向切面编程,是一种新的方法论,是对传统面向对象编程的补充。
AOP的主要变成对象是切面,而切面模块化横切还煮点。
在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样一来横切关注点就被模块化到特殊的对象(切面)里
一些AOP相关的概念:
切面(Aspect):横切关注点(跨越应用程序多个模块的功能),被模块化的特殊对象。
通知(Advice):切面必须要完成的工作。
目标(Target):被通知的对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
连接点(Joinpoint):程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。
切点(pointcut):每个类都拥有多个连接点,例如一个类中的所有方法实际上都是连接点,即连接点是程序类中客观存在的食物。AOP通过切点定位到特定的连接点。连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
基础:
当我们在实现一个需求的时候,往往伴随着一些重复的注解,日志或者一些除了核心功能代码之外的合理需求的代码。而我们不可能每次都从核心功能代码块里去改动,这样会导致代码量的膨胀和代码的分散,从而导致代码的维护变得困难。
针对这些问题,我们需要使用AOP,也就是面向切面编程。
首先来看看AOP的实质,动态代理:
使用代理来将一个对象包起来,这样在使用对象之前可以在代理中随意增加额外的代码。
public class ArithmeticCalculatorLoggingProxy {
//要代理的对象
private ArithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
this.target = target;
}
public ArithmeticCalculator getLoggingProxy() {
ArithmeticCalculator proxy = null;
//代理对象由哪一个类加载器负责加载
ClassLoader loader = target.getClass().getClassLoader();
//代理对象的类型,即其中有哪些方法
Class[] interfaces = new Class[]{ArithmeticCalculator.class};
//当调用代理对象其中的方法时,该执行的代码
InvocationHandler handler = new InvocationHandler() {
/**
* @param :(proxy)正在返回的哪个代理对象,一般情况下,在invoke方法中都不使用该对象
* @param :(method)正在被调用的方法
* @param :(args)调用方法时,传入的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
//日志
System.out.println("The method " + methodName + " begins with" + Arrays.asList(args));
//执行方法
Object result = method.invoke(target, args);
return result;
}
};
proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, handler);
return proxy;
}
}
在Spring框架下的AOP:
在spring2.0以上的版本,可以使用基于AspectJ的AOP框架
基本的导入jar包此处略去不谈
有两种方法配置AOP:基于AspectJ注解的方式;基于XML配置文件的方式。
基于注解:
基本类:
接口
public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
}
实现
@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;
}
}
切面:
@Order(2)
@Aspect
@Component
public class LoggingAspect {
/**
* 定义一个方法,用于声明切入点表达式,该方法中再不需要添加其他代码
*/
@Pointcut("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
public void declareJoinPointExpression(){}
/**
* 在com.spring.aop.ArithmeticCalculator接口的每一个方法开始之前执行一段代码
*/
@Before("declareJoinPointExpression()")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("The method begins");
}
/**
* 在com.spring.aop.ArithmeticCalculator接口每一个方法结束之后执行一段代码
* 无论该方法是否出现异常都会执行,且无法在该方法中调用方法返回值
*/
@After("declareJoinPointExpression()")
public void afterMethod(JoinPoint joinPoint){
System.out.println("The method ends");
}
/**
* 在com.spring.aop.ArithmeticCalculator接口方法正常结束后执行的代码
* 返回通知是可以访问到方法的返回值的
*/
@AfterReturning(value = "declareJoinPointExpression()",
returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
System.out.println("return");
}
/**
* 在目标方法出现异常时回执行的代码
* 可以访问到异常对象;且可以指定在出现特定异常时再执行通知代码
*/
@AfterThrowing(value = "declareJoinPointExpression()",throwing = "ex")
public void afterThrowing(JoinPoint joinPoint,Exception ex){
System.out.println("wrong with"+ex);
}
/**
* 环绕通知需要携带ProceedingJoinPoint类型的参数
* 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法
* 且汗绕通知必须有返回值,返回值即为目标方法的返回值
*/
@Around("declareJoinPointExpression()")
public void aroundMethod(ProceedingJoinPoint pjd){
Object result=null;
String methodName=pjd.getSignature().getName();
try {
//前置通知
System.out.println("before");
//执行目标方法
result=pjd.proceed();
//返回通知
System.out.println("return");
}catch (Throwable e){
//异常通知
System.out.println("wrong with:"+e);
}
//后置通知
System.out.println("after");
}
}
按照顺序,从上至下分别为:
@Before 前置通知
@After 后置通知
@AfterReturning 返回通知
@Around 环绕通知
环绕通知就相当于一个完整的动态代理过程,我们可以通过对比上文的动态代理和环绕通知来理解几个通知实际在动态代理中的位置。
第二个切面:
/**
* 可以使用@Order指定切面的优先级,值越小,优先级越高
*/
@Component
@Aspect
@Order(1)
public class VlidationAspect {
@Before("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
public void validateArgs(JoinPoint joinPoint){
System.out.println("validate:"+ Arrays.asList(joinPoint.getArgs()));
}
}