1. 什么是 AOP
概念
AOP是Aspect Oriented Programming的缩写,意思是:面向切面编程,一种在运行过程中通过预编译方式和动态代理实现程序功能统一维护的技术。 AOP是OOP的延续,是软件开发的热点,是Spring框架的重要内容,是函数式编程的衍生范式。 AOP可以隔离各部分业务逻辑,减少各部分业务逻辑之间的耦合,提高程序的复用性,提高开发效率。
通常来说:
AOP就是在原代码的基础上,在不改变原代码的情况下,增加附加功能。
优点
为什么AOP会降低耦合? 例如:
开发代码时,如果需要添加日志功能怎么办? 一般来说,我们可以在需要添加日志的界面或者函数上添加日志的功能。 这样就需要修改原来的代码了。 如果数量少,可以接受。 但如果数量大,工作量就会很大。 另外,如果需要升级或删除日志,必须修改源代码,非常麻烦且容易出错。 但是如果使用AOP,只需要一个入口点。
2. 代码实现
Redis配置
@Component
@DependsOn("mybatisConfig")
public class RedisService{
@Resource
private ISystemConfigService systemConfigService;
private JedisPool jedisPool;
private final Logger log = LoggerFactory.getLogger(RedisService.class);
public void setJedis(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
public JedisPool instance() {
try {
if (this.jedisPool != null) return this.jedisPool;
// 获取redis的连接
// host
SystemConfig systemConfigHost = systemConfigService.selectByKey("redis_host");
String host = systemConfigHost.getValue();
// port
SystemConfig systemConfigPort = systemConfigService.selectByKey("redis_port");
String port = systemConfigPort.getValue();
// password
SystemConfig systemConfigPassword = systemConfigService.selectByKey("redis_password");
String password = systemConfigPassword.getValue();
password = StringUtils.isEmpty(password) ? null : password;
// database
SystemConfig systemConfigDatabase = systemConfigService.selectByKey("redis_database");
String database = systemConfigDatabase.getValue();
// timeout
SystemConfig systemConfigTimeout = systemConfigService.selectByKey("redis_timeout");
String timeout = systemConfigTimeout.getValue();
if (!this.isRedisConfig()) {
log.info("redis配置信息不全或没有配置...");
return null;
}
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 配置jedis连接池最多空闲多少个实例,源码默认 8
jedisPoolConfig.setMaxIdle(7);
// 配置jedis连接池最多创建多少个实例,源码默认 8
jedisPoolConfig.setMaxTotal(20);
//在borrow(引入)一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
jedisPoolConfig.setTestOnBorrow(true);
//return 一个jedis实例给pool时,是否检查连接可用性(ping())
jedisPoolConfig.setTestOnReturn(true);
jedisPool = new JedisPool(jedisPoolConfig, host, Integer.parseInt(port), Integer.parseInt(timeout), password,
Integer.parseInt(database));
log.info("redis连接对象获取成功...");
return this.jedisPool;
} catch (Exception e) {
log.error("配置redis连接池报错,错误信息: {}", e.getMessage());
return null;
}
}
// 判断redis是否配置了
public boolean isRedisConfig() {
SystemConfig systemConfigHost = systemConfigService.selectByKey("redis_host");
String host = systemConfigHost.getValue();
// port
SystemConfig systemConfigPort = systemConfigService.selectByKey("redis_port");
String port = systemConfigPort.getValue();
// database
SystemConfig systemConfigDatabase = systemConfigService.selectByKey("redis_database");
String database = systemConfigDatabase.getValue();
// timeout
SystemConfig systemConfigTimeout = systemConfigService.selectByKey("redis_timeout");
String timeout = systemConfigTimeout.getValue();
return !StringUtils.isEmpty(host) && !StringUtils.isEmpty(port) && !StringUtils.isEmpty(database) && !StringUtils
.isEmpty(timeout);
}
// 获取String值
public String getString(String key) {
JedisPool instance = this.instance();
if (StringUtils.isEmpty(key) || instance == null) return null;
Jedis jedis = instance.getResource();
String value = jedis.get(key);
jedis.close();
return value;
}
public void setString(String key, String value) {
this.setString(key, value, 300); // 如果不指定过时时间,默认为5分钟
}
/**
* 带有过期时间的保存数据到redis,到期自动删除
*
* @param key
* @param value
* @param expireTime 单位 秒
*/
public void setString(String key, String value, int expireTime) {
JedisPool instance = this.instance();
if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value) || instance == null) return;
Jedis jedis = instance.getResource();
SetParams params = new SetParams();
params.px(expireTime * 1000);
jedis.set(key, value, params);
jedis.close();
}
public void delString(String key) {
JedisPool instance = this.instance();
if (StringUtils.isEmpty(key) || instance == null) return;
Jedis jedis = instance.getResource();
jedis.del(key); // 返回值成功是 1
jedis.close();
}
// TODO list, map 等方法
}
定义切点方法
package co.abc.demo.hook;
import org.aspectj.lang.annotation.Pointcut;
public class UserServiceHook {
@Pointcut("execution(public * co.abc.demo.service.IUserService.selectByUsername(..))")
public void selectByUsername() {
}
@Pointcut("execution(public * co.abc.demo.service.IUserService.selectByToken(..))")
public void selectByToken() {
}
@Pointcut("execution(public * co.abc.demo.service.IUserService.selectById(..))")
public void selectById() {
}
@Pointcut("execution(public * co.abc.demo.service.IUserService.delRedisUser(..))")
public void delRedisUser() {
}
}
package co.abc.demo.hook;
import org.aspectj.lang.annotation.Pointcut;
public class TopicServiceHook {
@Pointcut("execution(public * co.abc.demo.service.ITopicService.search(..))")
public void search() {
}
@Pointcut("execution(public * co.abc.demo.service.ITopicService.selectById(..))")
public void selectById() {
}
@Pointcut("execution(public * co.abc.demo.service.ITopicService.update(..))")
public void update() {
}
@Pointcut("execution(public * co.abc.demo.service.ITopicService.vote(..))")
public void vote() {
}
@Pointcut("execution(public * co.abc.demo.service.ITopicService.downVote(..))")
public void downVote() {
}
@Pointcut("execution(public * co.abc.demo.service.ITopicService.updateViewCount(..))")
public void updateViewCount() {
}
}
实现aop切点处需要执行的方法以及逻辑
@Component
@Aspect
public class RedisCachePlugin {
@Resource
private RedisService redisService;
@Resource
private ITopicService topicService;
@Resource
private ISystemConfigService systemConfigService;
@Around("co.abc.demo.hook.TopicServiceHook.selectById()")
public Object topicSelectById(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String topicJson = redisService.getString(String.format(RedisKeys.REDIS_TOPIC_KEY, proceedingJoinPoint.getArgs()[0]));
if (topicJson == null) {
Object topic = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
// 缓存在redis里
redisService.setString(String.format(RedisKeys.REDIS_TOPIC_KEY, proceedingJoinPoint.getArgs()[0]), JsonUtil.objectToJson(topic));
return topic;
} else {
return JsonUtil.jsonToObject(topicJson, Topic.class);
}
}
@Around("co.abc.demo.hook.TopicServiceHook.vote()")
public Object voteTopic(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 点赞后删除redis内缓存的topic数据
Topic topic = (Topic) proceedingJoinPoint.getArgs()[0];
redisService.delString(String.format(RedisKeys.REDIS_TOPIC_KEY, topic.getId()));
return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
}
@Around("co.abc.demo.hook.TopicServiceHook.downVote()")
public Object downVoteTopic(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 点赞后删除redis内缓存的topic数据
Topic topic = (Topic) proceedingJoinPoint.getArgs()[0];
redisService.delString(String.format(RedisKeys.REDIS_TOPIC_KEY, topic.getId()));
return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
}
@After("co.abc.demo.hook.TopicServiceHook.update()")
public void topicUpdate(JoinPoint joinPoint) {
Topic topic = (Topic) joinPoint.getArgs()[0];
// 缓存到redis里
redisService.setString(String.format(RedisKeys.REDIS_TOPIC_KEY, topic.getId()), JsonUtil.objectToJson(topic));
}
@Around("co.abc.demo.hook.UserServiceHook.selectByUsername()")
public Object userSelectByUsername(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String username = (String) proceedingJoinPoint.getArgs()[0];
String userJson = redisService.getString(String.format(RedisKeys.REDIS_USER_USERNAME_KEY, username));
if (userJson != null) {
// 带泛型转换, 这里如果不带泛型转换,会报错
return JsonUtil.jsonToObject(userJson, User.class);
} else {
Object returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
if (returnValue != null) {
redisService.setString(String.format(RedisKeys.REDIS_USER_USERNAME_KEY, username), JsonUtil.objectToJson(returnValue));
}
return returnValue;
}
}
@Around("co.abc.demo.hook.UserServiceHook.selectByToken()")
public Object userSelectByToken(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String token = (String) proceedingJoinPoint.getArgs()[0];
String userJson = redisService.getString(String.format(RedisKeys.REDIS_USER_TOKEN_KEY, token));
if (userJson != null) {
// 带泛型转换, 这里如果不带泛型转换,会报错
return JsonUtil.jsonToObject(userJson, User.class);
} else {
Object returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
if (returnValue != null) {
redisService.setString(String.format(RedisKeys.REDIS_USER_TOKEN_KEY, token), JsonUtil.objectToJson(returnValue));
}
return returnValue;
}
}
@Around("co.abc.demo.hook.UserServiceHook.selectById()")
public Object userSelectById(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Integer id = (Integer) proceedingJoinPoint.getArgs()[0];
String userJson = redisService.getString(String.format(RedisKeys.REDIS_USER_ID_KEY, id));
if (userJson != null) {
// 带泛型转换, 这里如果不带泛型转换,会报错
return JsonUtil.jsonToObject(userJson, User.class);
} else {
Object returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
if (returnValue != null) {
redisService.setString(String.format(RedisKeys.REDIS_USER_ID_KEY, id), JsonUtil.objectToJson(returnValue));
}
return returnValue;
}
}
@After("co.abc.demo.hook.UserServiceHook.delRedisUser()")
public void userDelRedisUser(JoinPoint joinPoint) {
User user = (User) joinPoint.getArgs()[0];
redisService.delString(String.format(RedisKeys.REDIS_USER_ID_KEY, user.getId()));
redisService.delString(String.format(RedisKeys.REDIS_USER_USERNAME_KEY, user.getUsername()));
redisService.delString(String.format(RedisKeys.REDIS_USER_TOKEN_KEY, user.getToken()));
}
static class RedisKeys {
public static final String REDIS_TOPIC_KEY = "demo_topic_%s"; // 后面还要拼上话题的id
public static final String REDIS_TOPIC_VIEW_IP_ID_KEY = "demo_topic_view_ip_%s_topic_%s"; // 需要格式化字符串填充上ip地址跟话题id
public static final String REDIS_COMMENTS_KEY = "demo_comments_%s"; // 后面还要拼上话题的id
public static final String REDIS_USER_ID_KEY = "demo_user_id_%s"; // 后面还要拼上用户的id
public static final String REDIS_USER_USERNAME_KEY = "demo_user_username_%s"; // 后面还要拼上用户名
public static final String REDIS_USER_TOKEN_KEY = "demo_user_token_%s"; // 后面还要拼上用户的token
}
}