论 Spring+SpringBoot+Aop 以及应用场景
作者:互联网
众所周知,spring最核心的两个功能是aop和ioc,即面向切面,控制反转。这里我们探讨一下如何使用spring aop。
1.何为aop
aop全称Aspect Oriented Programming,面向切面,AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。其与设计模式完成的任务差不多,是提供另一种角度来思考程序的结构,来弥补面向对象编程的不足。
通俗点讲就是提供一个为一个业务实现提供切面注入的机制,通过这种方式,在业务运行中将定义好的切面通过切入点绑定到业务中,以实现将一些特殊的逻辑绑定到此业务中。
比如,若是需要一个记录日志的功能,首先想到的是在方法中通过log4j或其他框架来进行记录日志,但写下来发现一个问题,在整个业务中其实核心的业务代码并没有多少,都是一些记录日志或其他辅助性的一些代码。而且很多业务有需要相同的功能,比如都需要记录日志,这时候又需要将这些记录日志的功能复制一遍,即使是封装成框架,也是需要调用之类的。在此处使用复杂的设计模式又得不偿失。
所以就需要面向切面出场了。
3.搭建aop
本来spring就自带一套aop实现,我们直接使用此实现即可,本来使用aop还需要定义一些xml文件,但由于我们使用的是spring-boot框架,这一步就省略掉了。也就是说,在spring-boot中,我们可以直接使用aop而不需要任何的配置
具体如何搭建spring-boot请参考:http://www.cnblogs.com/lic309/p/4073307.html
4.aop名称
先介绍一些aop的名词,其实这些名词对使用aop没什么影响,但为了更好的理解最好知道一些
-
切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。
-
连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
-
通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
-
切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
-
引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。
-
目标对象(Target Object):被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
-
AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
-
织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
其中重要的名词有:切面,切入点
5.简单例子:
maven:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xncoding</groupId>
<artifactId>springboot-aop</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-aop</name>
<description>SpringBoot AOP演示</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
</executions>
</plugin>
</plugins>
</build>
</project>
可能直接说会很模糊,这里我先做了一个小例子:直接上代码
/**
* 日志切面
*/
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(public * com.xncoding.aop.controller.*.*(..))")
public void webLog(){}
@Before("webLog()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
System.out.println("URL : " + request.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + request.getMethod());
System.out.println("IP : " + request.getRemoteAddr());
System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
System.out.println("方法的返回值 : " + ret);
}
//后置异常通知
@AfterThrowing("webLog()")
public void throwss(JoinPoint jp){
System.out.println("方法异常时执行.....");
}
//后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
@After("webLog()")
public void after(JoinPoint jp){
System.out.println("方法最后执行.....");
}
//环绕通知,环绕增强,相当于MethodInterceptor
@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("方法环绕start.....");
try {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object obj = pjp.proceed(pjp.getArgs());
stopWatch.stop();
long cost = stopWatch.getTotalTimeMillis();
MethodSignature signature = (MethodSignature) pjp.getSignature();
String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
System.out.println("----------- 执行" + methodName + "方法, 用时: " + cost + "ms -----------");
System.out.println("方法环绕proceed,结果是 :" + obj);
return obj;
} catch (Throwable e) {
throw e;
}
}
}
看上面的例子就是实现了一个切面,其中有一些特殊的东西,下面一一解释:
6.使用的注解:
@Aspect:描述一个切面类,定义切面类的时候需要打上这个注解
1. @Pointcut:声明一个切入点,切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。Spring AOP只支持Spring bean的方法执行连接点。所以你可以把切入点看做是Spring bean上方法执行的匹配。一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了我们关注那个方法的执行。
注:作为切入点签名的方法必须返回void 类型
Spring AOP支持在切入点表达式中使用如下的切入点指示符:
-
-
execution - 匹配方法执行的连接点,这是你将会用到的Spring的最主要的切入点指示符。
-
within - 限定匹配特定类型的连接点(在使用Spring AOP的时候,在匹配的类型中定义的方法的执行)。
-
this - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中bean reference(Spring AOP 代理)是指定类型的实例。
-
target - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中目标对象(被代理的应用对象)是指定类型的实例。
-
args - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中参数是指定类型的实例。
-
@target - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中正执行对象的类持有指定类型的注解。
-
@args - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中实际传入参数的运行时类型持有指定类型的注解。
-
@within - 限定匹配特定的连接点,其中连接点所在类型已指定注解(在使用Spring AOP的时候,所执行的方法所在类型已指定注解)。
-
@annotation - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中连接点的主题持有指定的注解。
-
其中execution使用最频繁,即某方法执行时进行切入。定义切入点中有一个重要的知识,即切入点表达式,我们一会在解释怎么写切入点表达式。
切入点意思就是在什么时候切入什么方法,定义一个切入点就相当于定义了一个“变量”,具体什么时间使用这个变量就需要一个通知。
即将切面与目标对象连接起来。
如例子中所示,通知均可以通过注解进行定义,注解中的参数为切入点。
spring aop支持的通知:
@Before:前置通知:在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
@AfterReturning :后置通知:在某连接点正常完成后执行的通知,通常在一个匹配的方法返回的时候执行。
@AfterThrowing:异常通知:在方法抛出异常退出时执行的通知。
@After 最终通知。当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
@Around:环绕通知:包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
环绕通知最麻烦,也最强大,其是一个对方法的环绕,具体方法会通过代理传递到切面中去,切面中可选择执行方法与否,执行方法几次等。
环绕通知使用一个代理ProceedingJoinPoint类型的对象来管理目标对象,所以此通知的第一个参数必须是ProceedingJoinPoint类型,在通知体内,调用ProceedingJoinPoint的proceed()方法会导致后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个Object[]对象-该数组中的值将被作为方法执行时的参数。
下面写上测试列子:
/**
* Description:
*/
@RestController
public class UserController {
@RequestMapping("/first")
public Object first() {
return "first controller";
}
@RequestMapping("/doError")
public Object error() {
return 1 / 0;
}
}
返回:
方法环绕start.....
URL : http://localhost:8092/second
HTTP_METHOD : POST
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.xncoding.aop.controller.UserController.second
ARGS : []
second around:老铁们来咯
second before
----------- 执行com.xncoding.aop.controller.UserController.second方法, 用时: 6ms -----------
方法环绕proceed,结果是 :second controller
方法最后执行.....
方法的返回值 : second controller
接下来介绍自定义式注解式日志
直接上代码:
1:首先定义一个注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {
String desc() default "无信息";
}
2:定义一个切面类
@Component
@Aspect
public class UserAccessAspect {
@Pointcut(value = "@annotation(com.xncoding.aop.aspect.UserAccess)")
public void access() {
}
@Before("access()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
System.out.println("second before");
}
@Around("@annotation(userAccess)")
public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {
//获取注解里的值
System.out.println("second around:" + userAccess.desc());
try {
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
3:写一个接口测试
@RestController
public class UserController {
@RequestMapping("/second")
@UserAccess(desc = "老铁们来咯")
public Object second() {
return "second controller";
}
}
返回:
方法环绕start.....
URL : http://localhost:8092/second
HTTP_METHOD : POST
IP : 0:0:0:0:0:0:0:1
CLASS_METHOD : com.xncoding.aop.controller.UserController.second
ARGS : []
second around:老铁们来咯
second before
方法环绕proceed,结果是 :second controller
方法最后执行.....
方法的返回值 : second controller
我们一般使用AOP主要来做:
场景一: 记录日志
场景二: 监控方法运行时间 (监控性能)
场景三: 权限控制
场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
场景五: 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )
标签:SpringBoot,aop,连接点,Spring,切入点,Aop,AOP,方法 来源: https://blog.csdn.net/xyzx123456/article/details/117258509