场景分析
public User gerUser(String id) {
//1.查询缓存
boolean cache = CacheUtil.isCache(id);
//2.缓存命中,返回数据
if (cache) {
System.out.println("缓存命中了-----------------");
return CacheUtil.getCache(id);
}
//3.缓存没有命中,查询数据库
User user = UserMapper.getUserById(id);
//4.写入缓存
CacheUtil.cache(id, user);
//5.写入日志
LogUtils.log(user);
return user;
}
存在问题:代码臃肿。违背了业务接口的单一职责原理。代码复用性很差。
使用注解和切面改进
1.pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
1.application.properties
server.port=8900
2.App.java
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
3.User.java
@Data
public class User {
private String id;
private String name;
private Integer age;
}
4.UserMapper.java
public class UserMapper {
//模拟数据库,实际可以通过mybatis整合查询数据库
public static User getUserById(String id){
System.out.println("查询数据库");
User user = new User();
user.setId(id);
user.setName("tom-"+ id);
user.setAge(22);
return user;
}
}
5.UserService.java
@Service
public class UserService {
//原始代码
public User gerUser(String id) {
//1.查询缓存
boolean cache = CacheUtil.isCache(id);
//2.缓存命中,返回数据
if (cache) {
System.out.println("缓存命中了-----------------");
return CacheUtil.getCache(id);
}
//3.缓存没有命中,查询数据库
User user = UserMapper.getUserById(id);
//4.写入缓存
CacheUtil.cache(id, user);
//5.写入日志
LogUtils.log(user);
return user;
}
//该进后的代码 添加了该注解,查询缓存写日志的工作在切面中实现了
//注意这里使用了el表达式,通过 #id获取传入的参数。
@LogCache(key = "#id")
public User gerUserAnno(String id) {
User user = UserMapper.getUserById(id);
return user;
}
}
6.CacheUtil.java
public class CacheUtil {
//模拟redis。实际可以整合redis
public static Map<String, User> map = new HashMap<>();
static {
User user = new User();
user.setId("1");
user.setName("tom");
user.setAge(22);
map.put("1", user);
}
public static boolean isCache(String key) {
return map.containsKey(key);
}
public static User getCache(String key) {
return map.get(key);
}
public static void cache(String key, User user) {
System.out.println("写入缓存--------");
map.put(key, user);
}
}
7.核心:LogCacheAspect.java
@Aspect
@Component
public class LogCacheAspect {
// 代表ai.test.service包下所有的类(第2个.),所有方法(第3个.),参数不限(括号内两个..),返回值不限(第1个.)
@Pointcut("execution( * ai.test.service.*.*(..))")
public void pointCut() {
}
// 匹配所有带有 @LogCache的注解
@Pointcut("@annotation(ai.test.annotation.LogCache)")
public void pointCut2() {
}
//环绕通知 执行方法之前做什么,执行方法之后做什么
@Around(value = "pointCut2()")
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
//拦截的是一个方法,这个proceedingJoinPoint就代表了整个方法。
Object proceed = null;
try {
System.out.println("环绕通知----------前--------------");
//获取注解上的参数
MethodSignature methodSignature = (MethodSignature) (proceedingJoinPoint.getSignature()); //这是一个方法的标识
//获取到拦截的方法
Method method = methodSignature.getMethod();
//从方法上获取到注解(这个注解也可以直接通过参数传递进来)
LogCache annotation = method.getAnnotation(LogCache.class);
String id = annotation.key();
System.out.println("注解上的参数是:" + id); //直接获取到的值是: #id (没有被el表达式解析过)
System.out.println("需要经过el表达式解析id,具体如下");
//获取方法的形参
Parameter[] parameters = method.getParameters();
String[] parameterNames = new String[parameters.length];
for (int i = 0; i < parameters.length; i++) {
parameterNames[i] = parameters[i].getName();
}
//获取方法的实参
Object[] args = proceedingJoinPoint.getArgs();
id = SpelParser.getKey(id, parameterNames, args);
System.out.println("el表达式处理后的id:" + id);
boolean cache = CacheUtil.isCache(id);
//2.缓存命中,返回数据
if (cache) {
System.out.println("缓存命中了-----------------");
return CacheUtil.getCache(id);
}
proceed = proceedingJoinPoint.proceed(); //执行主方法
System.out.println("环绕通知-----------后-------------");
User user = (User) proceed;
//4.写入缓存
CacheUtil.cache(id, user);
//5.写入日志
LogUtils.log(user);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
8.LogCache.java
@Target({ElementType.METHOD}) //作用在方法上
@Retention(RetentionPolicy.RUNTIME) //运行时
public @interface LogCache {
String key();
}
9.SpelParser.java
//EL表达式中 占位符的解析
public class SpelParser {
private static ExpressionParser parser = new SpelExpressionParser();
/**
*
* @param key el表达式字符串,站位符以#开头
* @param parameterNames 形参名称,方法中传入的名称,占位符名称
* @param args 实参
* @return
*/
public static String getKey(String key,String[] parameterNames,Object[] args){
//1.把字符串转变为el表达式
Expression exp = parser.parseExpression(key);
//2.将形参和形参值以配对的方式配置到赋值上下文中。就是一个map
EvaluationContext context = new StandardEvaluationContext();
if(args.length <=0){
return null;
}
for (int i = 0; i < args.length; i++) {
context.setVariable(parameterNames[i],args[i]);
}
//3.根据赋值上下文运算el表达式
return exp.getValue(context,String.class);
}
public static void main(String[] args) {
String key = "#name+' '+#age"; //el表达式 一定要写成这样
//形参名称
String name = "name";
String age = "age";
String[] parameterNames = new String[]{name,age};
//实参 args赋值
Object[] argsarr = new Object[2];
argsarr[0] = "tom";
argsarr[1] = 22;
System.out.println(SpelParser.getKey(key,parameterNames,argsarr));
}
}
10.AppTest.java(test/java目录下)
@SpringBootTest(classes = App.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class AppTest {
@Test
public void testAnno() {
// 1.获取一个类
Class<UserService> aClass = UserService.class;
Method[] methods = aClass.getMethods();
for (Method method : methods) {
LogCache annotation = method.getAnnotation(LogCache.class);
if (annotation != null) {
//有这个注解
String key = annotation.key();
System.out.println(key);
}
}
}
@Autowired
private UserService userService;
//参数值为2 回去查询数据库。参数值为1,会直接命中缓存。
@Test
public void testuser(){
userService.gerUserAnno("2");
}
}