嗨,你知道吗,Spring还有这些高级特性
liebian365 2024-10-16 13:00 28 浏览 0 评论
日常开发使用非常多的Spring,它的设计理念是什么呢?有哪些核心的组件呢?为啥又需要这些组件呢?在Spring中用到了哪些设计模式呢?Spring有哪些高级特性,该如何使用呢?本文将对这些做简要的介绍,希望看完本文您能了解这些知识!
Spring介绍
Spring是一个Java轻量级的IOC容器,实现了AOP的特性,非侵入性框架。提供了对持久层、事务、Web层等各个方面组件集成与一致性封装。涉及到的组件非常丰富,但核心仍然是Spring Framework。Spring Framework真正的核心组件只有几个。 下面看下Spring框架的总体架构图:
可以看到,Spring提供的功能非常多,但核心组件只有三个:Core、Context、Beans;它们构建起了整个Spring的骨骼架构,没有它们就不可能有AOP、Web等上层的特性功能。
设计理念
Spring的设计理念:构建一个数据结构,然后根据这个数据结构设计它的生存环境
就像开发一个系统一样,比如电商系统,需要有用户User,这个User需要一张表,然后根据这个用户去设计他的生存环境,比如用户的订单、购物车等,这些就是这个用户在这个系统中的生存环境,那么Spring的生存环境又是什么呢?
上面说到,Spring的设计理念是构建一个数据结构,那么什么是Spring的数据结构呢?
Spring的三个核心组件中最核心的是Beans组件,Bean则是Spring构建的数据结构
- 在Spring中,Bean才是真正的主角,或者说Spring是面向Bean的编程,Bean在Spring中的作用就像Object对OOP的作用一样,在java中是面向对象的编程,在Spring中是面向Bean的编程,包括Bean的创建、定义、解析等,这些会在后续的文章中说到
- 通过IOC容器完成依赖注入机制,构建Bean的生存环境;IOC容器就是被Bean包裹的对象,Spring正是通过把对象包装在Bean中,从而达到对这些对象的管理以及一些列额外操作的目的
- Spring框架的设计目标:依赖注入机制,把对象之间的依赖关系用配置文件或者注解来管理
核心组件的协同工作
从上面可以知道,Bean是Spring的关键因素,那么Context和Core又有什么作用呢? 如果把Bean比作舞台中的演员的话,那么Context就是这个舞台背景,而Core就是演出的道具
Context、Core、Beans关系图:
知道了Bean是Spring的核心,Bean里面包装的是对象,那么Context组件解决了Bean的生存环境问题,就比如没有舞台,演员还怎么演出呢;Context也会去发现每个Bean之间的关系,然后为它们建立维护好Bean关系;所以可以说,Context就是一个Bean关系的集合,这个关系集合又叫做IOC容器,一旦建立起这个IOC容器后Spring就可以工作了
Core组件就是发现、建立和维护Bean关系需要的一系列的工具,从这个角度来看的话,Core组件叫做Util更容易理解
设计模式的应用
代理模式
Spring AOP中CGLIB、JDK动态代理就是利用代理模式设计实现的
从上图可以看到,Spring除了实现被代理对象的接口,还有SpringProxy和Advised两个接口
$Proxy就是创建的代理对象,Subject是抽象主题,代理对象是通过InvocationHandler来持有对目标对象的引用的
在Spring中一个真实的代理对象结构如下:
策略模式
在Spring中,代理对象的创建就是通过策略模式来实现的
Spring中的代理方式有两个,一个JDK动态代理,一个CGLIB代理。两个代理方式都使用了策略模式,结构图如下:
AopProxy接口表示抽象策略
- CglibAopProxy和JdkDynamicAopProxy分别代表两种策略的实现方式
- ProxyFactoryBean就是代表Context角色,它会根据条件选用JDK动态代理方式还是CGLIB方式
- 另外的三个类主要是负责创建具体策略对象
- ProxyFactoryBean通过依赖关联具体策略对象,通过调用策略对象getProxy(ClassLoader classLoader)方法来完成操作
特性应用
事件驱动编程
事件驱动编程,是基于发布-订阅模式的编程模型,即观察者模式
事件驱动模型的核心构建通常包含了一下几个:
- 事件源:负责产生事件的对象,比如页面中常见的按钮,按钮就是一个事件源,可以产生“点击”这个事件
- 事件监听器:也叫做事件处理器,负责处理事件的对象
- 事件:也可以称作事件对象,是事件源和事件监听器之间的信息桥梁,是整个事件模型驱动的核心
事件驱动模型的实现包含以下几种:
- 观察者模式
- JDK观察者模式
- JavaBean事件驱动
- Spring事件驱动
下面主要是Spring事件驱动的示例,由于Spring事件驱动模型原理比较复杂,涉及到的类比较多,下面从一个简单的例子入手,了解Spring事件驱动模型
在日常购物中,当下了一个订单的时候,这个订单的支付状态会发生变化,然后能够通知到库存服务、短信服务、邮件服务等
- PaymentEntity类:
/**
* @ClassName PaymentEntity
* @Description: 支付的实体。作为事件实体
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
public class PaymentEntity {
/** 订单id */
private int id;
/** 订单状态 */
private String status;
public PaymentEntity(int id, String status) {
this.id = id;
this.status = status;
}
@Override
public String toString() {
return "PaymentEntity{" +
"id=" + id +
", status='" + status + '\'' +
'}';
}
}
复制代码
- PaymentUpdateStatusEvent类
/**
* @ClassName 支付状态更新的事件,以PaymentEntity作为传输的载体
* @Description: TODO
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
public class PaymentUpdateStatusEvent extends ApplicationEvent {
public PaymentUpdateStatusEvent(Object source) {
super(source);
}
}
复制代码
- PaymentService类,主要用来发布事件
/**
* @ClassName PaymentService
* @Description: TODO
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
@Service
public class PaymentService {
@Autowired
private ApplicationContext applicationContext;
public void pay(int id, String status) {
//TODO 省略的业务代码
PaymentEntity entity = new PaymentEntity(id, status);
// 发布事件
applicationContext.publishEvent(new PaymentUpdateStatusEvent(entity));
}
}
复制代码
- StockPaymentListener事件监听器
/**
* @ClassName StockPaymantListener
* @Description: 无序事件监听器,库存服务监听器
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
@Service
public class StockPaymentListener implements ApplicationListener<PaymentUpdateStatusEvent> {
@Override
@Async
public void onApplicationEvent(PaymentUpdateStatusEvent event) {
System.out.println(Thread.currentThread().getName() +
":库存服务,收到了支付状态的更新:" + event);
}
}
复制代码
5.AbstractPaymentListener抽象类,有序监听器
/**
* @ClassName SmsPaymentListener
* @Description: 有序监听器,抽象类实现事件源以及事件的通用判断
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
public abstract class AbstractPaymentListener implements SmartApplicationListener {
/** 支持的事件类型 */
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return eventType == PaymentUpdateStatusEvent.class;
}
/** 事件发生的目标类 */
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return sourceType == PaymentEntity.class;
}
}
复制代码
- SmsPaymentListener事件监听器
/**
* @ClassName SmsPaymentListener
* @Description: 短信监听器
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
@Service
public class SmsPaymentListener extends AbstractPaymentListener implements SmartApplicationListener {
/** 排序,数字越小执行的优先级越高 */
@Override
public int getOrder() {
return 1;
}
@Override
@Async
public void onApplicationEvent(ApplicationEvent event) {
System.out.println(Thread.currentThread().getName() +
":短信服务,收到了支付状态的更新:" + event);
}
}
复制代码
- MailPaymentListener事件监听器
/**
* @ClassName MailPaymentListener
* @Description: 邮件监听器
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
@Service
public class MailPaymentListener extends AbstractPaymentListener implements SmartApplicationListener {
/** 排序,数字越小执行的优先级越高 */
@Override
public int getOrder() {
return 2;
}
@Override
@Async
public void onApplicationEvent(ApplicationEvent event) {
System.out.println(Thread.currentThread().getName() +
":邮件服务,收到了支付状态的更新:" + event);
}
}
复制代码
- 测试类
/**
* @ClassName EventTest
* @Description: 测试类
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
@SpringBootTest
public class EventTest {
@Autowired
PaymentService paymentService;
@Test
void pay() {
paymentService.pay(1, "支付成功");
}
}
复制代码
运行之后的结果:
涉及到的类:
- ApplicationEvent
- ApplicationListener
- SmartApplicationListener
- ApplicationContext
可以看到,有序监听器执行是按照优先级执行的,也可以看到,上面执行的线程全部是main线程,当订单很多的时候,只有一个线程来执行,效率会很低,所以引出了下面的内容,异步执行
异步执行
Spring有两种异步执行方式:全局异步、注解式配置异步
一. 全局异步实现
/**
* @ClassName GlobalAsyncConfig
* @Description: TODO
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
@Configuration
public class GlobalAsyncConfig {
/** 线程池维护线程的最小数量 */
private int minPoolSize = 2;
/** 线程池维护线程的最大数量 */
private int maxPoolSize = 2;
/** 线程池队列的长度 */
private int queueCapacity = 100;
/** 获取异步线程池的执行对象 */
@Bean("asyncExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(minPoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
//用来调试
executor.setThreadNamePrefix("GlobalAsyncConfig:");
executor.setWaitForTasksToCompleteOnShutdown(true);
//拒绝策略 CallerRunsPolicy 由调用线程处理该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
/** 名称必须是applicationEventMulticaster,Spring内部通过这个名字来获取Bean */
@Bean("applicationEventMulticaster")
public SimpleApplicationEventMulticaster simpleApplicationEventMulticaster(Executor executor) {
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(executor);
return eventMulticaster;
}
}
复制代码
思考一下,当用到了异步之后,上面的有序监听器还会按照优先级执行吗?下面看下执行结果:
可以看到,当使用异步之后,有序监听器并没有按照优先级执行,具体原因就是不同的线程去执行导致的
全局异步的执行步骤:
- 定义并且配置Executor Bean
- 配置名为applicationEventMulticaster的SimpleApplicationEventMulticaster Bean
- 设置applicationEventMulticaster执行器为第一步的Executor
二. 注解式配置异步实现
/**
* @ClassName AnnotationAsyncConfig
* @Description: TODO
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
@Configuration
@EnableAsync
public class AnnotationAsyncConfig implements AsyncConfigurer {
/** 线程池维护线程的最小数量 */
private int minPoolSize = 2;
/** 线程池维护线程的最大数量 */
private int maxPoolSize = 2;
/** 线程池队列的长度 */
private int queueCapacity = 100;
/** 获取异步线程池的执行对象 */
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(minPoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
//用来调试
executor.setThreadNamePrefix("AnnotationAsyncConfig:");
executor.setWaitForTasksToCompleteOnShutdown(true);
//拒绝策略 CallerRunsPolicy 由调用线程处理该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
复制代码
//加上@Async注解
@Override
@Async
public void onApplicationEvent(PaymentUpdateStatusEvent event) {
System.out.println(Thread.currentThread().getName() +
":库存服务,收到了支付状态的更新:" + event);
}
复制代码
执行结果如下:
注解式配置异步的执行步骤:
- 开启异步执行:@EnableAsync
- 配置线程池,这个是非必要的,没有的话则使用默认线程池
- 在Bean方法上指定为异步:@Async
异步执行的原理本质上是AOP,具体步骤如下:
- 初始化线程池和异步处理器
- 创建异步方法所在的Bean后,执行Async对应的BeanPostProcessor,创建AOP代理类,代理对象替换原来的对象
- 代理对象中,异步方法被动态植入了异步执行方法
- 执行异步方法,其实执行的是代理对象里面的方法,从而实现异步,除了Async这个注解,没有任何的入侵
定时任务
一. SpringTask
- fixedRate:上一次开始执行时间点之后再执行
- fixedDelay:上一次执行完毕时间点之后再执行
- initialDelay:第一次延迟后执行,之后按照上面指定的规则执行
- 默认的是上一次执行完毕时间点之后再执行
Cron表达式,一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素,按照顺序依次是:
- 秒(0~59)
- 分钟(0~59)
- 小时(0~23)
- 天(0~31)
- 月(0~11)
- 星期(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
- 年份(1970-2099)
可以使用工具生成,如下图:
Corn表达式生成工具:点击链接
简单示例:
- 使用cron表达式
@Component
@EnableScheduling
public class AlarmSpringTask {
/** 默认的是fixedDelay,上一次执行完毕时间点之后再执行 */
@Scheduled(cron = "0/5 * * * * *")
public void run() throws InterruptedException {
Thread.sleep(6000);
System.out.println(Thread.currentThread().getName() +
"使用cron表达式:" +(System.currentTimeMillis()/1000));
}
}
复制代码
运行结果:
- 使用fixedRate
@Component
@EnableScheduling
public class AlarmSpringTask {
/** fixedRate,上一次开始执行时间点之后5秒再执行 */
@Scheduled(fixedRate = 5000)
public void run() throws InterruptedException {
Thread.sleep(6000);
System.out.println(Thread.currentThread().getName() +
"使用fixedRate:" +(System.currentTimeMillis()/1000));
}
}
复制代码
运行结果:
- 使用fixedDelay
@Component
@EnableScheduling
public class AlarmSpringTask {
/** fixedDelay,上一次执行完毕时间点之后5秒再执行 */
@Scheduled(fixedDelay = 5000)
public void run() throws InterruptedException {
Thread.sleep(6000);
System.out.println(Thread.currentThread().getName() +
"使用fixedDelay:" +(System.currentTimeMillis()/1000));
}
}
复制代码
运行结果:
- 使用initialDelay
@Component
@EnableScheduling
public class AlarmSpringTask {
/** initialDelay,第一次延迟2s后执行,之后按fixedDelay的规则每5秒执行 一次*/
@Scheduled(initialDelay = 2000, fixedDelay = 5000)
public void run() throws InterruptedException {
Thread.sleep(6000);
System.out.println(Thread.currentThread().getName() +
"使用使用initialDelay:" +(System.currentTimeMillis()/1000));
}
}
复制代码
运行结果:
二. Spring集成Quartz
/**
* @ClassName TestQuartz
* @Description: 创建任务类TestQuartz,该类主要是继承了QuartzJobBean
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
public class TestQuartz extends QuartzJobBean {
/** 执行定时任务 */
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("quartz task "+(System.currentTimeMillis()/1000));
}
}
复制代码
/**
* @ClassName QuartzConfig
* @Description: 创建配置类QuartzConfig
* @Author TR
* @Date 2021/3/21
* @Version V1.0
*/
@Configuration
public class QuartzConfig {
@Bean
public JobDetail teatQuartzDetail() {
return JobBuilder.newJob(TestQuartz.class).withIdentity("testQuartz").storeDurably().build();
}
@Bean
public Trigger testQuartzTrigger() {
SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(6) //设置时间周期单位:秒
.repeatForever();
return TriggerBuilder.newTrigger().forJob(teatQuartzDetail())
.withIdentity("testQuartz")
.withSchedule(builder)
.build();
}
}
复制代码
运行结果:
相关推荐
- “版本末期”了?下周平衡补丁!国服最强5套牌!上分首选
-
明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...
- VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符"
-
首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...
- 东营交警实名曝光一批酒驾人员名单 88人受处罚
-
齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...
- Qt界面——搭配QCustomPlot(qt platform)
-
这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...
- 大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写
-
老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...
- 测试谷歌VS Code AI 编程插件 Gemini Code Assist
-
用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...
- 顾爷想知道第4.5期 国服便利性到底需优化啥?
-
前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...
- 掌握Visual Studio项目配置【基础篇】
-
1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...
- 还嫌LED驱动设计套路深?那就来看看这篇文章吧
-
随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...
- Visual Studio Community 2022(VS2022)安装图文方法
-
直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...
- Qt添加MSVC构建套件的方法(qt添加c++11)
-
前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...
- Qt为什么站稳c++GUI的top1(qt c)
-
为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...
- qt开发IDE应该选择VS还是qt creator
-
如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...
- Qt 5.14.2超详细安装教程,不会来打我
-
Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...
- Cygwin配置与使用(四)——VI字体和颜色的配置
-
简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- “版本末期”了?下周平衡补丁!国服最强5套牌!上分首选
- VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符"
- 东营交警实名曝光一批酒驾人员名单 88人受处罚
- Qt界面——搭配QCustomPlot(qt platform)
- 大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写
- 测试谷歌VS Code AI 编程插件 Gemini Code Assist
- 顾爷想知道第4.5期 国服便利性到底需优化啥?
- 掌握Visual Studio项目配置【基础篇】
- 还嫌LED驱动设计套路深?那就来看看这篇文章吧
- Visual Studio Community 2022(VS2022)安装图文方法
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)