声明:本文仅为个人观点,如有不当还请指出。
简介
本文采用spring事件监听机制,来进行项目启动时的部分数据缓存,并处理redis和数据库的一致性问题。
环境构建
练习采用Gradle构建,如果使用Maven仅限参考。
plugins {
id 'org.springframework.boot' version '2.3.7.RELEASE'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}
dependencies {
//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.apache.commons:commons-pool2'
//web依赖
implementation 'org.springframework.boot:spring-boot-starter-web'
//springBoot
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
正文
自定义一个CacheEvent继承自ApplicationEvent(所有应用程序事件扩展类)。
我们可以在CacheEvent中,定义一些字段通过构造器的方式进行传递信息或一些拓展实现。
public class CacheEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
//信息
private String msg;
//加载顺序
private int loadOrder;
public RedisEvent(Object source, String msg,int LoadOrder) {
super(source);
this.msg = msg;
this.loadOrder = LoadOrder;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getLoadOrder() {
return loadOrder;
}
public void setLoadOrder(int loadOrder) {
this.loadOrder = loadOrder;
}
}
自定义一个CacheListener实现ApplicationListener(基于观察者设计模式的标准,应用程序事件监听器)接口。
我们将缓存逻辑写在这里
@Slf4j
@Component
public class CacheListener implements ApplicationListener<CacheEvent> {
@Autowired
private RedisTemplate redisTemplate;
@Override
public void onApplicationEvent(CacheEvent event) {
/**
* 缓存逻辑代码....
*/
redisTemplate.opsForValue().set("cache:key1", "cache1");
log.info("监听到事件:" + event.getMsg());
}
}
这样我们的事件和监听器都有了,最后我们需要定义ApplicationInitialize实现ApplicationRunner接口来完成SpringBoot的初始化操作。
@Component
@Order(100)
public class ApplicationInitialize implements ApplicationRunner {
@Autowired
private ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) throws Exception {
applicationContext.publishEvent(new CacheEvent(this,"项目初始化完成!!!!",1));
}
}
我们使用容器上下文的方式来发送事件,它之所以可以具备这个能力,我们看源码注释可以得知,此能力继承自ApplicationEventPublisher接口。
/**
* Central interface to provide configuration for an application.
* This is read-only while the application is running, but may be
* reloaded if the implementation supports this.
*
* <p>An ApplicationContext provides:
* <ul>
* <li>Bean factory methods for accessing application components.
* Inherited from {@link org.springframework.beans.factory.ListableBeanFactory}.
* <li>The ability to load file resources in a generic fashion.
* Inherited from the {@link org.springframework.core.io.ResourceLoader} interface.
* <li>The ability to publish events to registered listeners.
* Inherited from the {@link ApplicationEventPublisher} interface.
* <li>The ability to resolve messages, supporting internationalization.
* Inherited from the {@link MessageSource} interface.
* <li>Inheritance from a parent context. Definitions in a descendant context
* will always take priority. This means, for example, that a single parent
* context can be used by an entire web application, while each servlet has
* its own child context that is independent of that of any other servlet.
* </ul>
*
* <p>In addition to standard {@link org.springframework.beans.factory.BeanFactory}
* lifecycle capabilities, ApplicationContext implementations detect and invoke
* {@link ApplicationContextAware} beans as well as {@link ResourceLoaderAware},
* {@link ApplicationEventPublisherAware} and {@link MessageSourceAware} beans.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see ConfigurableApplicationContext
* @see org.springframework.beans.factory.BeanFactory
* @see org.springframework.core.io.ResourceLoader
*/
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
/**
* Return the unique id of this application context.
* @return the unique id of the context, or {@code null} if none
*/
@Nullable
String getId();
/**
* Return a name for the deployed application that this context belongs to.
* @return a name for the deployed application, or the empty String by default
*/
String getApplicationName();
/**
* Return a friendly name for this context.
* @return a display name for this context (never {@code null})
*/
String getDisplayName();
/**
* Return the timestamp when this context was first loaded.
* @return the timestamp (ms) when this context was first loaded
*/
long getStartupDate();
/**
* Return the parent context, or {@code null} if there is no parent
* and this is the root of the context hierarchy.
* @return the parent context, or {@code null} if there is no parent
*/
@Nullable
ApplicationContext getParent();
/**
* Expose AutowireCapableBeanFactory functionality for this context.
* <p>This is not typically used by application code, except for the purpose of
* initializing bean instances that live outside of the application context,
* applying the Spring bean lifecycle (fully or partly) to them.
* <p>Alternatively, the internal BeanFactory exposed by the
* {@link ConfigurableApplicationContext} interface offers access to the
* {@link AutowireCapableBeanFactory} interface too. The present method mainly
* serves as a convenient, specific facility on the ApplicationContext interface.
* <p><b>NOTE: As of 4.2, this method will consistently throw IllegalStateException
* after the application context has been closed.</b> In current Spring Framework
* versions, only refreshable application contexts behave that way; as of 4.2,
* all application context implementations will be required to comply.
* @return the AutowireCapableBeanFactory for this context
* @throws IllegalStateException if the context does not support the
* {@link AutowireCapableBeanFactory} interface, or does not hold an
* autowire-capable bean factory yet (e.g. if {@code refresh()} has
* never been called), or if the context has been closed already
* @see ConfigurableApplicationContext#refresh()
* @see ConfigurableApplicationContext#getBeanFactory()
*/
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
最后我们来看效果
项目启动完毕我们发现日志打印,监听器监听到了事件。
数据也被存储在了redis当中
一致性问题
采用事件监听的方式解决一致性问题也很简单,这里需要对监听器进行一点小更改。
监听器监听到之前将缓存进行删除。
@Slf4j
@Component
public class CacheListener implements ApplicationListener<CacheEvent> {
@Autowired
private RedisTemplate redisTemplate;
@Override
public void onApplicationEvent(CacheEvent event) {
//清空缓存(解决一致性问题)
Set keys = redisTemplate.keys("cache:*");
keys.stream().forEach(item -> redisTemplate.delete(item));
/**
* 缓存逻辑代码....
*/
log.info("监听到事件:" + event.getMsg());
}
}
我们对增删改操作进行判断如果成功,触发缓存更改事件,将redis缓存删除,将更新好的数据,再次缓存到redis当中。
@PostMapping("/add")
public String add(@RequestBody(required = false) User user) {
boolean save = UserService.save(user);
if (save) {
applicationContext.publishEvent(new CacheEvent(this, "缓存被更改!!!!", 2));
}
return save ? "添加成功" : "添加异常";
}
如果想要提高效率我们可以选择局部更新,可以在事件的loadOrder进行控制。