众所周知,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:首先定义一个注解
/**
* 作用在方法上,在运行时通过反射获取信息、将注解加到javadoc中、允许子类继承
* @author
* @date
* */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserAccess {
String authId() default "111111111111";
}
2:定义一个切面类
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@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.authId());
try {
//先拿到Request请求体
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
System.out.println("此次请求的路径:"+request.getRequestURL());
System.out.println("此次业务操作的权限ID:"+userAccess.authId());
//真实环境下应该是从session或Redis中拿到登录的用户信息,再去与权限ID做匹配
System.out.println("模拟查询数据库或其他存储介质,验证当前用户是否有权限");
//如果传的参数值不是admin,则驳回请求
if (!request.getParameter("message").equals("admin")){
return JSON.parseObject("{\"message\":\"no auth\",\"code\":403}");
}
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
3:写一个接口测试
@RestController
public class UserController {
@RequestMapping("/second")
@UserAccess(authId = "admin")
public Object second() {
List<Map<String , Object>> mapList = new ArrayList<>();
Map<String , Object> dataMapOne = new HashMap<>();
dataMapOne.put("name","张三");
dataMapOne.put("age",25);
dataMapOne.put("sex",0);
Map<String , Object> dataMapTwo = new HashMap<>();
dataMapTwo.put("name","李四");
dataMapTwo.put("age",23);
dataMapTwo.put("sex",1);
mapList.add(dataMapOne);
mapList.add(dataMapTwo);
return JSON.toJSON(mapList);
}
}
运行结果
当我们访问对应的API时,如果给的参数message的值是admin:
后台打印:
当我们的message的值不是admin:
总结
以上只是模拟场景,真实场景下一般从session中获取用户信息,与注解中的权限ID作为参数,去数据库查询是否有权限。
可以看得出来,这种方式非常简洁,很灵活,而且代码的侵入性很低,我们只是在需要拦截的方法上增加了一个注解,就实现了对权限的控制,完全不需要大量的代码穿插在业务逻辑中,这也是AOP的理念之一。
SpringBoot使用AOP实现自定义接口缓存:
1、新建两个注解
(1)该注解表示需要缓存
-
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; /** * 自定义缓存注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LzcCache { String cacheName() default ""; String key() default ""; long timeOut() default 0; TimeUnit timeUnit() default TimeUnit.HOURS; }
(2)该注解表示需要清除缓存
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义清除缓存注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LzcCacheEvict {
String cacheName() default "";
String key() default "";
}
二、新建两个切面
(1)缓存切面
import com.xncoding.aop.util.JsonUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class LzcCacheAspect {
@Pointcut("execution(public * com.xncoding.aop.controller.*.*(..))")
public void webLog(){}
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Around("webLog()")
public Object lzcCacheAspect(ProceedingJoinPoint jp) throws Throwable {
Class<?> cls = jp.getTarget().getClass();
String methodName = jp.getSignature().getName();
Map<String, Object> map = isChache(cls, methodName);
boolean isCache = (boolean)map.get("isCache");
if (isCache) {
String cacheName = (String) map.get("cacheName"); // 缓存名字
String key = (String) map.get("key"); // 自定义缓存key
long timeOut = (long) map.get("timeOut"); // 过期时间, 0代表永久有效
TimeUnit timeUnit = (TimeUnit) map.get("timeUnit"); // 过期时间单位
Class<?> methodReturnType = (Class<?>)map.get("methodReturnType"); // 方法的返回类型
Method method = (Method)map.get("method"); // 方法
String realCacheName = "";
// 判断cacheName是否为空,如果cacheName为空则使用默认的cacheName
if(cacheName.equals("")) {
realCacheName = cls.getName() + "." + methodName;
} else {
realCacheName = cacheName;
}
String realKey = "";
// 判断key是否为空, 如果为空则使用默认的key
if (key.equals("")) {
realKey = realCacheName + "::" + defaultKeyGenerator(jp);
} else {
realKey = realCacheName + "::" + parseKey(key, method, jp.getArgs());
}
// 判断缓存中是否存在该key, 如果存在则直接从缓存中获取数据并返回
if (stringRedisTemplate.hasKey(realKey)) {
String value = stringRedisTemplate.opsForValue().get(realKey);
return JsonUtil.toBean(methodReturnType, value);
} else {
Object result = jp.proceed();
// 将返回结果保存到缓存中
if (timeOut == 0) {
stringRedisTemplate.opsForValue().set(realKey, JsonUtil.toJson(result));
} else {
stringRedisTemplate.opsForValue().set(realKey, JsonUtil.toJson(result), timeOut, timeUnit);
}
return result;
}
}
return jp.proceed();
}
/**
* 自定义生成key,使用方法中的参数作为key
*/
private String defaultKeyGenerator(ProceedingJoinPoint jp) {
// 获取所有参数的值
List<String> list = new ArrayList<>();
Object[] args = jp.getArgs();
for (Object object : args) {
list.add(object.toString());
}
return list.toString();
}
/**
* 获取缓存的key
* key 定义在注解上,支持SPEL表达式
*/
private String parseKey(String key,Method method,Object [] args){
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String [] paraNameArr = u.getParameterNames(method);
//使用SPEL进行key的解析
ExpressionParser parser = new SpelExpressionParser();
//SPEL上下文
StandardEvaluationContext context = new StandardEvaluationContext();
//把方法参数放入SPEL上下文中
for(int i=0;i<paraNameArr.length;i++){
context.setVariable(paraNameArr[i], args[i]);
}
return parser.parseExpression(key).getValue(context, String.class);
}
private Map<String, Object> isChache(Class<?> cls, String methodName) {
boolean isCache = false;
Map<String, Object> map = new HashMap<>();
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && method.isAnnotationPresent(LzcCache.class)) {
LzcCache lzcCache = method.getAnnotation(LzcCache.class); // 获取方法上的注解
Class<?> methodReturnType = method.getReturnType(); // 获取方法的返回类型
map.put("cacheName", lzcCache.cacheName());
map.put("key", lzcCache.key());
map.put("timeOut", lzcCache.timeOut());
map.put("timeUnit", lzcCache.timeUnit());
map.put("methodReturnType", methodReturnType);
map.put("method", method);
isCache = true;
break;
}
}
map.put("isCache", isCache);
return map;
}
}
(2)清除缓存切面
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Aspect
@Component
public class LzcCacheEvictAspect {
@Pointcut("execution(public * com.xncoding.aop.controller.*.*(..))")
public void webLog(){}
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Around("webLog()")
public Object lzcCacheAspect(ProceedingJoinPoint jp) throws Throwable {
Class<?> cls = jp.getTarget().getClass();
String methodName = jp.getSignature().getName();
Map<String, Object> map = isChacheEvict(cls, methodName);
boolean isCacheEvict = (boolean)map.get("isCacheEvict");
if (isCacheEvict) {
String cacheName = (String) map.get("cacheName"); // 缓存名字
String key = (String) map.get("key"); // 自定义缓存key
Method method = (Method)map.get("method"); // 方法
String realCacheName = "";
// 判断cacheName是否为空,如果cacheName为空则使用默认的cacheName
if(cacheName.equals("")) {
realCacheName = cls.getName() + "." + methodName;
} else {
realCacheName = cacheName;
}
String realKey = "";
// 判断key是否为空, 如果为空则使用默认的key
if (key.equals("")) {
realKey = realCacheName + "::" + defaultKeyGenerator(jp);
} else {
realKey = realCacheName + "::" + parseKey(key, method, jp.getArgs());
}
// 判断缓存中是否存在该key, 如果存在则直接从缓存中删除该数据
if (stringRedisTemplate.hasKey(realKey)) {
stringRedisTemplate.delete(realKey);
}
}
return jp.proceed();
}
/**
* 自定义生成key,使用方法中的参数作为key
*/
private String defaultKeyGenerator(ProceedingJoinPoint jp) {
// 获取所有参数的值
List<String> list = new ArrayList<>();
Object[] args = jp.getArgs();
for (Object object : args) {
list.add(object.toString());
}
return list.toString();
}
/**
* 获取缓存的key
* key 定义在注解上,支持SPEL表达式
*/
private String parseKey(String key,Method method,Object [] args){
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String [] paraNameArr = u.getParameterNames(method);
//使用SPEL进行key的解析
ExpressionParser parser = new SpelExpressionParser();
//SPEL上下文
StandardEvaluationContext context = new StandardEvaluationContext();
//把方法参数放入SPEL上下文中
for(int i=0;i<paraNameArr.length;i++){
context.setVariable(paraNameArr[i], args[i]);
}
return parser.parseExpression(key).getValue(context, String.class);
}
private Map<String, Object> isChacheEvict(Class<?> cls, String methodName) {
boolean isCacheEvict = false;
Map<String, Object> map = new HashMap<>();
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && method.isAnnotationPresent(LzcCacheEvict.class)) {
LzcCacheEvict lzcCacheEvict = method.getAnnotation(LzcCacheEvict.class); // 获取方法上的注解
map.put("cacheName", lzcCacheEvict.cacheName());
map.put("key", lzcCacheEvict.key());
map.put("method", method);
isCacheEvict = true;
break;
}
}
map.put("isCacheEvict", isCacheEvict);
return map;
}
}
三:测试运行结果:
@GetMapping("/cache")
@LzcCache(cacheName = "user", key = "#user.username", timeOut = 10, timeUnit = TimeUnit.MINUTES)
@ResponseBody
public ResultVO cahce() {
System.out.println("进来了");
User user1 = new User(1, "张三");
return ResultVOUtil.success(user1);
}
@GetMapping("/cache1")
@LzcCacheEvict(cacheName = "user", key = "#user.username")
public ResultVO cahce1(User user) {
return ResultVOUtil.success("清除成功");
}
添加到redis内存
删除redis内存:
springboot使用aop进行全局事务管理:
在 JavaEE 的开发过程中,service 方法用于处理主要的业务逻辑,而业务逻辑的处理往往伴随着对数据库的多个操作,我们并不能保证业务逻辑可以完全正确地执行,我们需要在业务逻辑中加入事务管理,一旦程序出错,数据库可以回滚,保证数据的准确性
1、创建 TransactionConfig.java 文件
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
/**
* @ClassName TransactionConfig
* 事务管理配置,拦截service包下所有方法
* @Author
* @Date 2
* @Version 1.0
**/
@Aspect
@Configuration
public class TransactionConfig {
/**
* 切面地址
*/
private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.xncoding.aop.service.*.*(..))";
/**
* 事务失效时间
*/
private static final int TX_METHOD_TIMEOUT = 5;
@Autowired
private PlatformTransactionManager transactionManager;
/**
* 事务配置
*
* @return
*/
@Bean
public TransactionInterceptor txAdvice() {
DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
/**
* PROPAGATION_REQUIRED:事务隔离性为 1
* 若当前存在事务,则加入该事务
* 如果当前没有事务,则创建一个新的事务,这是默认值
*/
txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
/**
* 设置事务失效时间,如果超过5秒,则回滚事务
*/
txAttr_REQUIRED.setTimeout(TX_METHOD_TIMEOUT);
DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
/**
* transactiondefinition 定义事务的隔离级别;
* PROPAGATION_NOT_SUPPORTED 事务传播级别5,以非事务运行,如果当前存在事务,则把当前事务挂起
*/
txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
/**
* 设置当前事务是否为只读事务
* true为只读
*/
txAttr_REQUIRED_READONLY.setReadOnly(true);
/**
* 事务管理规则,声明具备事务管理的方法名
*/
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
source.addTransactionalMethod("save*", txAttr_REQUIRED);
source.addTransactionalMethod("add*", txAttr_REQUIRED);
source.addTransactionalMethod("delete*", txAttr_REQUIRED);
source.addTransactionalMethod("update*", txAttr_REQUIRED);
source.addTransactionalMethod("get*", txAttr_REQUIRED_READONLY);
source.addTransactionalMethod("query*", txAttr_REQUIRED_READONLY);
source.addTransactionalMethod("find*", txAttr_REQUIRED_READONLY);
source.addTransactionalMethod("list*", txAttr_REQUIRED_READONLY);
source.addTransactionalMethod("count*", txAttr_REQUIRED_READONLY);
return new TransactionInterceptor(transactionManager, source);
}
/**
* 利用AspectJExpressionPointcut设置切面=切点+通知(写成内部bean的方式)
*
* @return
*/
@Bean
public Advisor txAdviceAdvisor() {
/**
* 声明切点的面
* 切面(Aspect):切面就是通知和切入点的结合。
* 通知和切入点共同定义了关于切面的全部内容——它的功能、在何时和何地完成其功能
*/
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
/**
* 声明和设置需要拦截的方法,用切点语言描写
*/
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
/**
* 设置切面=切点pointcut+通知TxAdvice
*/
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
2、解析
通过 @Aspect 定义一个切面,使用 @Configuration 声明当前类是一个配置类
我们业务逻辑一般都放在 service 层进行处理,所以我们定义的切入点为:com.lzzy.meet.service..(…),即 service 下的所有方法
我们设置了事务失效时间为:5秒,超过五秒,则回滚事务,并定义了事务的隔离性和隔离级别,并声明该事务为只读
最后我们利用 AspectJExpressionPointcut 设置切面 = 切点 + 通知(写成内部bean的方式)
我们一般使用AOP主要来做:
场景一: 记录日志
场景二: 监控方法运行时间 (监控性能)
场景三: 权限控制
场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
场景五: 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )