Spring中必备的自定义扩展点,结合工作中的案例,你一定用得到

news2024/11/25 15:25:30

文章目录

    • 简单介绍Spring流程
    • 从问题入手介绍扩展点
      • 如何在static方法中从Spring容器中获取bean对象
      • 如何在bean实例化前后做一些自定义操作
      • 如何移除,修改一些BeanDefination
      • 怎样用相同的线程从Spring容器中获取同一个bean对象
      • 如何在Spring容器初始化或销毁时做一些自定义操作
      • 如何利用Spring内部的Listenr机制,实现自定义事件发布通知

简单介绍Spring流程

在介绍自定义扩展点之前,首先大概回顾一下Spring的IOC流程,主要做了哪些事, 理清楚这个对我们实现自定义扩展点非常有帮助,理解更加透彻。

主要有以下几点:

  1. 从不同配置文件加载配置并解析
  2. 扫描包路径并结合配置形成BeanDefination, 这里面存储了bean的相关属性
  3. 根据BeanDefination中的信息实例化对象,将对象存储到Spring容器,即BeanFactory中
  4. 进行对象和容器的销毁
    在这里插入图片描述

在Spring中有很多的PostProcessor(后置处理器),主要作用就是增强器,自定义扩展实现

还有很多的Aware,顾名思义就是知道的,可获取的,比如说BeanNameAware,实现这个接口我们可以获取BeanName, ApplicationContextAware实现这个接口,我们可以获取ApplicationContext

而Spring中的自定义扩展点主要是针对这两类接口实现的,当然还有其他的操作,我们一起结合案例来看一下。

从问题入手介绍扩展点

如何在static方法中从Spring容器中获取bean对象

在Spring中,如果一个方法是static方法, 是无法在方法中使用@Resource或者@Autowired注解注入的对象的

如下:会直接编译报错。

在这里插入图片描述

那怎样做呢,我们可以封装一个BeanUtil,该类实现ApplicationContextAware,获取到ApplicationContext,然后就可以从中获取对象。如下:

@Service
public class BeanUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 通过名称获取bean
     * @param name
     * @return
     */
    public static Object getBean(String name) {
        if(StringUtils.isEmpty(name)){
            return null;
        }
        return applicationContext.getBean(name);
    }

    /**
     * 通过类型获取bean
     * @param requiredType
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> requiredType) {
        if(requiredType == null){
            return null;
        }
        return applicationContext.getBean(requiredType);
    }
}

使用也很简单,直接调用方法即可:

在这里插入图片描述

举一反三:

前面已经提到以Aware结尾的接口,我们可以据此获取到对应的对象,从而做一些参数获取或其他操作,这里再总结一些其他的。

  1. EnvironmentAware:用于获取Enviroment的一个扩展类,这个变量非常有用, 可以获得系统内的所有参数。包括配置文件中我们自定义的配置参数。

  2. EmbeddedValueResolverAware:用于获取StringValueResolver的一个扩展类, StringValueResolver用于获取基于String类型的properties的变量,一般我们都用@Value的方式去获取,如果实现了这个Aware接口,把StringValueResolver缓存起来,通过这个类去获取String类型的变量,效果是一样的。

  3. ResourceLoaderAware:用于获取ResourceLoader的一个扩展类,ResourceLoader可以用于获取classpath内所有的资源对象,可以扩展此类来拿到ResourceLoader对象。

  4. ApplicationEventPublisherAware:用于获取ApplicationEventPublisher的一个扩展类,ApplicationEventPublisher可以用来发布事件,结合ApplicationListener来共同使用

  5. ApplicationContextAware:用来获取ApplicationContext的一个扩展类,ApplicationContext应该是很多人非常熟悉的一个类了,就是spring上下文管理器,可以手动的获取任何在spring上下文注册的bean,我们经常扩展这个接口来缓存spring上下文,包装成静态方法。同时ApplicationContext也实现了BeanFactory,MessageSource,ApplicationEventPublisher等接口,也可以用来做相关接口的事情。

如何在bean实例化前后做一些自定义操作

我们可以实现BeanPostProcessor这个接口,重写以下两个方法。这两个方法都在bean实例化,属性赋值之后执行。
区别是一个在执行初始化方法之前执行,一个执行初始化方法之后执行。


注意事项:
这两个方法默认重写会返回null,但是一定不能返回null, 当不想操作修改某些对象时,直接返回bean即可。

案例:

在项目中自定义了一个注解, 这个注解标识在一些类上, 那我怎么识别到哪些类上有这个注解,从而做一些特殊操作呢?

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SelfDefine {
  String value() default "";
}

一个是直接使用反射框架,扫描某个路径下的所有类,循环判断即可,但是比较麻烦,更简单的我们可以实现BeanPostProcessor,重写方法, 在方法中对每个bean判断即可。

@Service
public class Test implements BeanPostProcessor {


    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        SelfDefine selfDefine = bean.getClass().getAnnotation(SelfDefine.class);
        if(selfDefine!=null){
            //实现一些自定义逻辑
            String value = selfDefine.value();
            System.out.println(value);
        }
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

如何移除,修改一些BeanDefination

在Spring中, 基本上所有的bean都是根据BeanDefination生成的,这里面存储了每个bean的相关属性,比如说作用域,接口等等。

所以如果我们想修改某个bean,可以直接修改BeanDefination. 甚至比如说删除, 不注入某个bean。

具体我们可以通过实现BeanDefinitionRegistryPostProcessor接口来操作。

如下案例:

在项目中的一个maven依赖中向Spring容器注入了某个bean, 但其实当前项目并不需要这个bean, 而且这个bean还需要额外增加一些配置,否则启动会报错。 所以在当前项目中需要移除该bean, 不让其报错。

@Service
public class Test implements BeanDefinitionRegistryPostProcessor {


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //移除beanName为service
        registry.removeBeanDefinition("service");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}


怎样用相同的线程从Spring容器中获取同一个bean对象

我们都知道spring默认支持的Scope只有两种:

singleton 单例,每次从spring容器中获取到的bean都是同一个对象。
prototype 多例,每次从spring容器中获取到的bean都是不同的对象。

但是有些情况我们需要相同的线程获取到同一个对象,进行对象的复用,而不同的线程因为线程安全问题需要获取不同的对象,这个怎么实现呢?此时需要自定义Scope了。

  1. 实现Scope接口:
public class ThreadScope implements Scope {
    private static final ThreadLocal THREAD_LOCAL_SCOPE = new ThreadLocal();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Object value = THREAD_LOCAL_SCOPE.get();
        if (value != null) {
            return value;
        }

        Object object = objectFactory.getObject();
        THREAD_LOCAL_SCOPE.set(object);
        return object;
    }

    @Override
    public Object remove(String name) {
        THREAD_LOCAL_SCOPE.remove();
        return null;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return null;
    }
}

  1. 将新定义的Scope注入到spring容器中:
@Component
public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope("threadScope", new ThreadScope());
    }
}

  1. 使用新定义的Scope:
@Scope("threadScope")
@Service
public class Test {
    public void test() {
    }
}

注意:
以上说的作用域无论是单例,多例,还是相同线程共享一个对象都指的是从容器中获取的时候,这里很容易产生误解。

举个例子,比如一个对象你定义成多例的

@Scope("prototype")
@Component
@Slf4j
public class Service {    
}

然后在Controller中,每次用请求获取该service对象,这个service每次会是不一样的吗?

@RestController
@Slf4j
public class Controller {
    
    @Resource
    Service service;

 
    @GetMapping("/test2")
    public String test(){
        log.info("运行线程:{}",Thread.currentThread().getName());
        return service.toString();
    }


}

答案是no, 每次都是一样的,因为对于Controller这个对象来说,它是单例的,所以对于service这个属性也只从Spring容器中获取了一次。自然就都是一样的。

那怎么处理呢?

  1. 可以将Controller也搞成多例的,这样就会从容器中获取多次,达到效果。
  2. 可以用本篇文章最开始提到的自己封装的BeanUtil,每次使用的时候直接从Spring容器中获取,也可以达到效果。

如何在Spring容器初始化或销毁时做一些自定义操作

有时候,我们需要在spring容器初始化或关闭前,做一些额外的工作,比如:关闭资源文件等。
我们可以实现InitializingBean接口,来进行一些初始化工作
可以实现DisposableBean接口,来进行一些关闭资源文件等等工作

@Service
public class Test implements InitializingBean, DisposableBean {


    @Override
    public void afterPropertiesSet() throws Exception {
        //进行一些初始化操作
        System.out.println("Spring容器已启动......");
    }

    @Override
    public void destroy() throws Exception {
        //进行一些关闭资源文件等操作
        System.out.println("Spring容器准备关闭......");
    }
}

如何利用Spring内部的Listenr机制,实现自定义事件发布通知

要实现自定义事件发布通知,我们可以利用ApplicationListener

主要有三步:

  1. 增加事件,继承ApplicationEvent类
public class TestEvent extends ApplicationEvent {

    private String name;
    /**
     * Create a new ApplicationEvent.
     *
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public TestEvent(Object source,String name) {
        super(source);
        this.name = name;
    }

    public void test(){
        System.out.println(name);
    }

}

  1. 增加监听者,实现ApplicationListener接口
@Component
public class TestListener implements ApplicationListener<TestEvent>{

    @Override
    public void onApplicationEvent(TestEvent event) {
        //执行相应事件的一些逻辑
        event.test();
    }
}

  1. 发布事件通知

@RestController
@Slf4j
public class Controller {

    @Resource
    ApplicationContext applicationContext;

    @GetMapping("/test2")
    public void test(){
        //执行一些本身的逻辑
        //......
        //发布事件,也就是当执行到此处的时候,监听者可以监听到变化,从而执行对应逻辑
        applicationContext.publishEvent(new TestEvent(this,"test"));
    }



}

这个其实就典型的运用了观察者模式,只不过做了一个小升级,额外定义封装了Event接口,其实也就是为了区分通知的不同行为,这个做法可以让我们明确监听某个事件。

因为在Spring中,还有很多的内部通知、事件,比如

  1. ContextRefreshedEvent
    ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext接口中使用refresh() 方法来发生。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用。

  2. ContextStartedEvent
    当使用ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。

  3. ContextStoppedEvent
    当使用ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作

  4. ContextClosedEvent
    当使用ConfigurableApplicationContext接口中的 close()方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启

  5. RequestHandledEvent
    这是一个web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件

如果我们自己想监听这些事件,也非常简单,只需要增加一个监听者就好了,如下:

@Component
public class TestRequestHandledEvent implements ApplicationListener<RequestHandledEvent> {
    @Override
    public void onApplicationEvent(RequestHandledEvent event) {
        System.out.println("一个请求处理完毕......");
    }
}

今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/760504.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年软件测试方向大厂招人和面试要求趋势

当前软件测试的行业现状是什么&#xff1f;2022年后半年经常能够在知乎、小红书、抖音等互联网平台上看到有人在抱怨软件测试行业就业困难。抱怨的一个主要内容是相比2021年测试岗位数下跌&#xff0c;几个月都找不到工作。另外一个测试岗位是有限的&#xff0c;但是应聘者在变…

Python读取excle文件,插入到数据库

一、需求背景 最近项目实践过程中遇到了一个问题&#xff1a;在使用Navicat将数据导入到PostgreSQL数据库时&#xff0c;发现时间格式的字段中的时间数值发生了变化&#xff0c;导致部分数据的时间不正确&#xff0c;故数据手动导入数据库报错。为了解决这个问题&#xff0c;决…

集群基础6——keepalived+lvs+apache

文章目录 一、环境说明二、安装apache三、配置keepalivedlvs3.1 配置lvs规则3.2 配置keepalived规则&#xff08;主&#xff09;3.3 配置keepalived规则&#xff08;备&#xff09; 四、验证 一、环境说明 先对两台后端服务器的httpd服务进行负载均衡&#xff0c;再对负载均衡服…

如何二次封装一个el-table组件并二次复用

*注:示例使用的是vue3和element进行二次封装的 首先我们来看效果图&#xff08;总共可以分为以下几个模块&#xff09;&#xff1a; 表格数据操作按钮区域表格信息提示区域表格主体内容展示区域表格分页区域 表单搜索没有封装在这里是为了降低代码的耦合性(有兴趣的可以查看我…

ChatGPT开发【一】:打造与ChatGPT默契互动的绝佳输入格式

点击加入->【OpenAI-API开发】技术交流群 文章目录 1. 导入openai库2.示例聊天API调用3.GPT-3.5-Turbo-0301的使用技巧系统消息Few-show prompt 4.计数Token数 Chatgpt由Openai最先进的型号 gpt-3.5-Turbo和 gpt-4提供支持。我们可以使用OpenAI API使用 GPT-3.5-Turbo或…

谈谈在Bitcask中用读写锁实现并发控制的性能表现

背景 最近被问了几次nutsdb事务是怎么实现的&#xff0c;也就是并发控制是怎么做的。我说&#xff0c;用一把大的读写锁&#xff0c;写事务拿到写锁&#xff0c;读事务拿读锁&#xff0c;这样子做的。提问者先是震惊&#xff0c;接着说是有一点鄙夷&#xff0c;我感觉大概心里…

【踩坑指南】Django+channels WebSocket配置

目前我搜到网上所有配置Djangochannels的教程/博客中&#xff0c;都没有提及这一点。希望能帮助你 踩的坑必须写在最前面&#xff1a; 根据文档的步骤去配置&#xff0c;每次到执行python manage.py 的时,使用的是默认的development server&#xff0c;而不是我们想要的Star…

解决:.prettierrc 配置完后,自动保存并没有格式化代码

如果你也碰到了同样的问题&#xff0c;请先确保&#xff1a; .prettierrc 文件已正确配置&#xff0c;例如我的&#xff1a; {"semi": false,"singleQuote": true,"arrowParens": "always","trailingComma": "all&qu…

卷积神经网络参数量和计算量的计算方法

本文总结了标准卷积、分组卷积和全连接层参数量和计算量的计算方法&#xff0c;如有错误&#xff0c;麻烦大家指正 一、标准卷积 假设输入特征的shape为[, , ]&#xff0c;卷积核的shape为[, , , ]&#xff0c;输出特征的shape为[, , ]&#xff0c;则&#xff0c; 标准卷积运…

C++特殊类设计及类型转换

目录 一、特殊类的设计 1.不能被拷贝的类 2.只能在堆区构建对象的类 3.只能在栈区构建对象的类 4.不能被继承的类 二、单例模式 1.饿汉模式 2.懒汉模式 3.线程安全 4.单例的释放 三、C类型转换 1.C语言的类型转换 2.static_cast 3.reinterpret_cast 4.const_cas…

Python补充笔记1-字符串

目录 1.字符串的驻留机制​编辑 2.字符串查找 2.1字符串查询操作方法 3.字符串大小写转换 3.1字符串的大小写转换方法 4.字符串内容对齐 4.1字符串内容对齐操作方法 5.字符串的劈分 5.1字符串劈分操作的方法​编辑 6.字符串判断 6.1判断字符串操作的方法​编辑 6.2字符串替换和…

虚拟化技术及实时虚拟化概述

版权声明&#xff1a;本文为本文为博主原创文章&#xff0c;未经本人同意&#xff0c;禁止转载。如有问题&#xff0c;欢迎指正。博客地址&#xff1a;https://www.cnblogs.com/wsg1100/ 实时虚拟化技术是一种针对实时应用场景的虚拟化技术&#xff0c;它要求在保证虚拟化优势…

STM32 ws2812b 最快点灯cubemx

文章目录 前言一、cubemx配置二、代码1.ws2812b.c/ws2812b.h2.主函数 前言 吐槽 想用stm32控制一下ws2812b的灯珠&#xff0c;结果发下没有一个好用的。 emmm&#xff01;&#xff01;&#xff01; 自己来吧&#xff01;&#xff01;&#xff01;&#xff01; 本篇基本不讲原理…

6、传输层TCP28

TCP协议&#xff1a;传输控制协议 1、协议实现 16位源端端口&16位对端端口&#xff1a;描述通信俩端进程32位序号&#xff1a;告诉接收端&#xff0c;这条数据在整体数据中的排序&#xff0c;接收端根据序号进行排序32位确认序号&#xff1a;向发送端进行回复确定&#xff…

pytest-html报告修改与汉化

目录 前言 生成报告 测试代码 原始报告 修改Environment 修改后的效果 修改Summary 修改后的效果 修改Results 优化Test 解决中文乱码 删除多余部分 修改后的效果 删除Links 修改后的效果 增加失败截图与用例描述 完整的conftest.py代码 汉化报告 修改plugin…

ClickHouse进阶

一、Explain查看执行计划 在 clickhouse 20.6 版本之前要查看 SQL 语句的执行计划需要设置日志级别为 trace 才能可以看到&#xff0c;并且只能真正执行 sql&#xff0c;在执行日志里面查看。 在 20.6 版本引入了原生的执行计划的语法。在 20.6.3 版本成为正式版本的功能。 …

常见的JS内置对象——字符串、数学、日期

二、字符串&#xff08;string&#xff09; 创建 一般使用第一种方式 2&#xff09;字符串的遍历 注意&#xff1a;没有foreach方法 3&#xff09;字符串的常见方法 substr()和substring()&#xff1a; substr()参数是从哪个位置开始&#xff0c;截多长 substring()参数是从…

完美匹配:一种简单的神经网络反事实推理学习表示方法

英文题目&#xff1a;Perfect Match: A Simple Method for Learning Representations For Counterfactual Inference With Neural Networks 翻译&#xff1a;完美匹配&#xff1a;一种简单的神经网络反事实推理学习表示方法 单位&#xff1a; 论文链接&#xff1a;https://a…

【状态估计】基于FOMIAUKF、分数阶模块、模型估计、多新息系数的电池SOC估计研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

C++ 创建共享内存

共享内存用于实现进程间大量的数据传输&#xff0c;共享内存是在内存中单独开辟一段内存空间&#xff0c;这段内存空间有自己特有的数据结构&#xff0c;包括访问权限、大小和最近访问时间等。 1、shmget函数 #include <sys/ipc.h> #include <sys/shm.h> int shm…