SpringAOP(面向切面编程)
AOP,即面向切面编程。AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制、异常处理等,封装起来,便于减少系统重复的代码,降低模块之间的耦合度。它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用拦截方法的方式,对该方法进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“切面”,从而使得编译器可以在编译期间织入有关“切面”的代码。
SpringAOP组成
切面(Aspect)
横切关注点的模块化(跨越应用程序多个模块的功能,比如 日志功能),这个关注点实现可能另外横切多个对象。
连接点(Join point)
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是类的某个方法调用前、调用后、方法抛出异常后等。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。
通知(Advice)
-
前置通知(Before):在目标方法被调用之前调用通知功能。
-
后置通知(After):在目标方法完成之后调用通知,无论该方法是否发生异常。
-
后置返回通知(After-returning):在目标方法成功执行之后调用通知。
-
后置异常通知(After-throwing):在目标方法抛出异常后调用通知。
-
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
切点(Pointcut)
指定一个通知将被引发的一系列连接点的集合。AOP 通过切点定位到特定的连接点。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。每个类都拥有多个连接点,例如 ArithmethicCalculator类的所有方法实际上都是连接点。
引入(Introduction)
添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
目标对象(Target Object)
包含连接点的对象。也被称作被通知或被代理对象。
AOP代理(AOP Proxy)
AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving)
织入描述的是把切面应用到目标对象来创建新的代理对象的过程。 Spring AOP 的切面是在运行时被织入,原理是使用了动态代理技术。Spring支持两种方式生成代理对象:JDK动态代理和CGLib,默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。
我的理解:SpringAOP就是规定一段代码什么时候在哪里执行
Spring AOP使用
基于注解配置
1、定义目标对象
package com.bytebeats.spring4.aop.annotation.service;
/**
* 业务逻辑接口
*/
public interface BankService {
boolean transfer(String from, String to, int amount);
}
业务逻辑层实现类
package com.bytebeats.spring4.aop.annotation.service;
import org.springframework.stereotype.Service;
/**
* 业务逻辑层实现类
*/
@Service
public class BankServiceImpl implements BankService {
@Override
public boolean transfer(String from, String to, int amount) {
if(amount<1){
throw new IllegalArgumentException("transfer amount must be a positive number");
}
System.out.println("["+from+"]向["+to+ "]转账金额"+amount);
return false;
}
}
2、定义切面
package com.bytebeats.spring4.aop.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Aspect //表明该类是一个切面
@Component //Aspect切面首先必须是一个普通的bean组件
public class TransferLogAdvice {
/**
* 切入点表达式1
*
* 通过@Pointcut注解定义切入点表达式
* 此处表达式含义:拦截com.bytebeats.spring4.aop.annotation.service.BankServiceImpl包下所 * 有类(包括子包中所有类)中的所有方法
*/
@Pointcut("execution(* com.bytebeats.spring4.aop.annotation.service.BankServiceImpl.*(..))")
public void pointcut1() {}
/**
* 切入点表达式2
*/
@Pointcut("execution(* com.bytebeats.spring4.aop.annotation.service.*ServiceImpl.*(..))")
public void myPointcut() {}
/**
* 前置通知:在方法执行前执行的代码
* @param joinPoint
*/
@Before(value = "pointcut1() || myPointcut()")
//@Before("execution(* com.bytebeats.spring4.aop.annotation.service.BankServiceImpl.*(..))")
public void beforeExecute(JoinPoint joinPoint){
//该方法的含义就是在切入点表达式1或者切入点表达式2配置的
//路径下的方法执行前先执行下面的代码,下面代码的含义就是获取目标方法的名字和参数列表打印出来
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(this.getClass().getSimpleName()+ " before execute:"+methodName+ " begin with "+args);
}
/**
* 后置通知:在方法执行后执行的代码(无论该方法是否发生异常),注意后置通知拿不到执行的结果
* @param joinPoint
*/
@After(value = "pointcut1()")
public void afterExecute(JoinPoint joinPoint){
//该方法的含义就是在切入点表达式1配置的
//路径下的方法执行后执行下面的代码,下面代码的含义就是获取目标方法的名字和参数列表打印出来
String methodName = joinPoint.getSignature().getName();
System.out.println(this.getClass().getSimpleName()+ " after execute:"+methodName+" end!");
}
/**
* 后置返回通知:在方法正常执行后执行的代码,可以获取到方法的返回值
* @param joinPoint
*/
@AfterReturning(value = "pointcut1()",
returning="result")
public void afterReturning(JoinPoint joinPoint, Object result){
//该方法的含义就是在切入点表达式2配置的
//路径下的方法执行后执行下面的代码,下面代码的含义就是获取目标方法的类名,方法名和返回值打印
String methodName = joinPoint.getSignature().getName();
System.out.println(this.getClass().getSimpleName()+ " afterReturning execute:"+methodName+" end with result:"+result);
}
/**
* 后置异常通知:在方法抛出异常之后执行,可以访问到异常信息,且可以指定出现特定异常信息时执行代码
* @param joinPoint
*/
@AfterThrowing(value = "pointcut1()",
throwing="exception")
public void afterThrowing(JoinPoint joinPoint, Exception /**NullPointerException*/ exception){
//该方法的含义就是在切入点表达式1配置的
//路径下的方法抛出异常后执行下面的代码,下面代码的含义就是获取目标方法的类名,方法名和异常信息打印
String methodName = joinPoint.getSignature().getName();
System.out.println(this.getClass().getSimpleName()+ " afterThrowing execute:"+methodName+" occurs exception:"+exception);
}
/**
* 环绕通知, 围绕着方法执行
* 通知注解的参数代表引用一个切入点表达式
*/
@Around(value = "pointcut1()")
public Object around(ProceedingJoinPoint joinPoint){
//该方法的含义就是在切入点表达式1配置的
//路径下的方法执行前后执行下面的代码
String methodName = joinPoint.getSignature().getName();
System.out.println(this.getClass().getSimpleName()+ " around:"+methodName+" execute start");
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println(this.getClass().getSimpleName()+ " around:"+methodName+" execute end");
return result;
}
}
把代码抽离出来分析:
Spring AOP提供了 @Before,@After、@AfterReturning、@AfterThrowing、@Around注解来指定 通知类型。
/**
* 前置通知:在方法执行前执行的代码
* @param joinPoint
*/
@Before("execution(* com.bytebeats.spring4.aop.annotation.service.BankServiceImpl.*(..))")
public void beforeExecute(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(this.getClass().getSimpleName()+ " before execute:"+methodName+ " begin with "+args);
}
但是,为了复用切点,推荐使用@Pointcut来定义切点,然后在 @Before、@After等注解中引用定义好的切点,代码如下:
@Pointcut("execution(* com.bytebeats.spring4.aop.annotation.service.BankServiceImpl.*(..))")
public void pointcut1() {
}
/**
* 前置通知:在方法执行前执行的代码
* @param joinPoint
*/
@Before(value = "pointcut1()")
public void beforeExecute(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(this.getClass().getSimpleName()+ " before execute:"+methodName+ " begin with "+args);
}
/**
* 后置通知:在方法执行后执行的代码(无论该方法是否发生异常),注意后置通知拿不到执行的结果
* @param joinPoint
*/
@After(value = "pointcut1()")
public void afterExecute(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println(this.getClass().getSimpleName()+ " after execute:"+methodName+" end!");
}
3、启用注解扫描
开启注解扫描后,基于注解方式配置AOP已经结束了,接下来可以验证一下。
package com.bytebeats.spring4.aop.annotation;
import com.bytebeats.spring4.aop.annotation.service.BankService;
import com.bytebeats.spring4.aop.xml.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 纯注解方式入口类
*/
@Configuration
@ComponentScan(basePackages = com.bytebeats.spring4.aop.annotation.UserService")
@EnableAspectJAutoProxy //开启对AOP相关注解的处理
public class SpringAopAnnotationApp {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringAopAnnotationApp.class);
BankService bankService = ctx.getBean(BankService.class);
bankService.transfer("jordan", "kobe", 2000);
System.out.println("*********************");
bankService.transfer("jordan", "kobe", 0);
ctx.close();
}
}
拓展,@Pointcut 定义切点时,还可以使用 &&、|| 和 ! 。