Spring事件监听机制(实现启动缓存处理)

本文探讨如何利用Spring事件监听机制在项目启动时处理数据缓存,并解决Redis和数据库的一致性问题。通过自定义CacheEvent、CacheListener和ApplicationInitialize,实现了启动时的缓存逻辑和事件发布。在一致性问题部分,通过监听器删除旧缓存并在操作成功后更新Redis,以确保数据同步。
摘要由CSDN通过智能技术生成

声明:本文仅为个人观点,如有不当还请指出。

简介

本文采用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进行控制。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值