前言
Spring是目前比较常用的框架,其核心是控制反转(IoC)和面向切面编程(AOP)。通过IOC容器管理POJO或JavaBean对象,以及它们之间的耦合关系;通过AOP以动态非入侵的方式增强服务。这篇主要浅谈一下AOP~
一、AOP概念
面向切面的技术,开发者可以在不改变原有方法逻辑的情况下,对方法逻辑进行加强,或者校验操作。
实现:动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层动态代理机制,对特定的方法进行编程。
二、AOP的简单应用
1.我这边使用的是maven项目,如果没有相应的包,需要先导入依赖到maven仓库。
<!--AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.创建一个切面类,用来统计方法的执行时间和返回结果。其中
@Aspect 注解用来告知该类属于切面类
@Component 注解将该类声明为IOC容器的been对象
@Pointcut用于定义切入点,切入点表达式中用execution定义方法切点保重包含的方法(
com.example.testdemo.service.*.*(..)对应的就是下面第二张图中的方法。
)
@Aspect
@Component
public class AspectTest {
// 切点定义
@Pointcut("execution(* com.example.testdemo.service.*.*(..))")
public void servicePointcut() {}
// 前置通知,记录方法调用入参和当前时间
@Around("servicePointcut()")
public void logMethodEntry(ProceedingJoinPoint joinPoint) throws Throwable {
String method = joinPoint.getSignature().getName();
System.out.println("调用 " + method + " 方法");
System.out.println("调用开始时间 " + new Date() + " -----------------------------------------------");
Object result = joinPoint.proceed();
System.out.println("调用结果 " + result);
System.out.println("调用结束时间 " + new Date() + " -----------------------------------------------");
}
}
@Service
public class TestDemoServiceImpl implements TestDemoService {
@Override
public String helloWord() {
int n=0;
for(int i=0; i<1000 ; i++){
n = n+i;
}
return String.valueOf(n);
}
}
执行结果
其中有几个注意的点
@Around:环绕通知,使用此注解标注的通知方法在目标前、后都被执行。
@Before:前置通知,会在通知方法使用之前被执行。
@After:后置通知,会在通知方法使用之后被执行,无论是否有异常都会被执行。
@AfterReturning:返回后通知,会在方法完成后被执行,有异常不会执行。
@AfterThrowing:异常后通知,会在通知方法发生异常后执行。
三、自定义注解的使用
我们接着以上的实例进行一个自定义注解话的改造。
1.首先创建一个注解类
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestTimeDate {
}
@Target 注解——指明可以被注解修饰的类型
ElementType.TYPE 可以用于类、接口和枚举类型
ElementType.FIELD 可以用于字段(包括枚举常量)
ElementType.METHOD 可以用于方法(controller上面的接口里它也是方法)
ElementType.PARAMETER 可以用于方法的参数
ElementType.CONSTRUCTOR 可以用于构造函数
ElementType.LOCAL_VARIABLE 可以用于局部变量
ElementType.ANNOTATION_TYPE 可以用于注解类型
ElementType.PACKAGE 可以用于包
ElementType.TYPE_PARAMETER 可以用于类型参数声明(Java 8新增)
ElementType.TYPE_USE 可以用于使用类型的任何语句中(Java 8新增)
@Rerention 注解——指明被注解的类的生命周期(一般都会使用RUNTIME)
RetentionPolicy.SOURCE | 在源文件中有效(即源文件保留) |
RetentionPolicy.CLASS | 在class文件中有效(即class保留),不会被加载到JVM中 |
RetentionPolicy.RUNTIME | 在运行时有效(即运行时保留),会被加载到JVM中 |
@Documented 注解——表示被注解的类将被javadoc 工具提取成文档。
@Inherited 注解——表示被它修饰的注解具有继承性,即如果一个类声明了被 @Inherited 修饰的注解,那么它的子类也将具有这个注解。
2.此处的切点指向的是刚刚创建的注解类。
@Aspect
@Component
public class AspectTest {
// 切点定义
@Pointcut(value="@annotation(com.example.testdemo.until.TestTimeDate)")
public void servicePointcut() {}
// 前置通知,记录方法调用入参和当前时间
@Around("servicePointcut()")
public void logMethodEntry(ProceedingJoinPoint joinPoint) throws Throwable {
String method = joinPoint.getSignature().getName();
System.out.println("调用 " + method + " 方法");
System.out.println("调用开始时间 " + new Date() + " -----------------------------------------------");
Object result = joinPoint.proceed();
System.out.println("调用结果 " + result);
System.out.println("调用结束时间 " + new Date() + " -----------------------------------------------");
}
}
3.在运行的方法上添加注解 @TestTimeDate
@Override
@TestTimeDate
public String helloWord() {
int n=0;
for(int i=0; i<1000 ; i++){
n = n+i;
}
return String.valueOf(n);
}
我们先看一下没有加注解时候的运行结果。可以看到,并没有进入我们的切面方法。
接下来看下加了注解时候的运行结果。可以看出,由注解进入了我们的切面方法。