概述 SpEL即Spring表达式语言(Spring Expression Language)。 从我通常的使用场景(API开发)来说,SpEL提供的大部分能力都可以划到奇技淫巧的范畴内。但是在一些场景下如缓存配置、ThymeLeaf取值等,SpEL还是大有可为的。 SpEL表达式的默认格式为:#{expression}。SpEL表达式以“#”开头,表达式主体包围在花括号中。 我们通常使用的属性取值表达式(也可称为属性占位符,格式${expression})不可以嵌套SpEL表达式。不过SpEL表达式可以嵌套属性取值表达式,如下: 在上面的这个表达式里面,如果属性“someProperty”的值是2,这个表达式的值就是4。 运算符 下表列出了SpEL支持的运算符: Type Operators Arithmetic +, -, *, /, %, ^, div, mod Relational <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge Logical and, or, not, &&, Conditional ?: Regex matches 接下来我们主要以@Value注解的形式介绍并演示这些运算符在SpEL中的应用。 算数运算符(Arithmetic) 如下是算数运算符的使用示例: 除运算和取模运算都有字母形式的别名(除运算“div”,取模运算“mod”)。“+”运算符还可以用来执行字符串连接。 关系运算符(Relational) 如下是关系运算符的使用示例: 所有的关系运算都有字母形式的别名。主要是为了适配使用xml配置文件的场景。在xml中使用带有三角符号的运算符(如小于“<”,大于“>”等)是不被允许的,此时我们可以使用字母形式的别名(lt,gt等)来进行运算。 逻辑运算符(Logical) 如下是逻辑运算符的使用示例: 同关系运算符一样,每个逻辑运算符也都有字母形式的别名。 条件运算符(Conditional) 条件运算符,顾名思义是用来根据不同的情况来注入不同的值。实际上,就是一个三目运算符: 三目运算符主要被用来处理“if-then-else”这样的判定。 三目运算符通常的使用场景式是判断一个属性是否为null,如果是的话就返回一个默认值,如下: 此外还有一种“Elvis”运算符,简化了上面这种“判定是否为空,为空则返回默认值”的场景: 如上,“Elvis”运算符的符号是“?:”,我们可以在Groovy语言中见到它。现在SpEL也引入了这个运算符。 正则运算符(Regex) 正则运算符被用来校验字符串是否匹配某个指定的正则表达式。如下: 访问List或Map对象 使用SpEL,我们还可以访问容器中的任何Map或List对象。看个例子: 这里我们创建了一个List对象来存放工人名字,一个Map对象来存放每个工人的工资。 接下来我们使用SpEL来访问这两个集合对象里面的元素: 使用程序解析SpEL表达式 有时候会需要编程处理SpEL表达式。Spring也为我们提供了相关的工具类。这些类都位于“spring-expression”包下。下面是一个封装好的解析SpEL表达式的方法: 调用ExpressionParser.parseExpression()后获得的值是Object类型的,这里会通过强制类型转换转为需要的类型。 现在我们将字符串(’zhyea’)作为SpEL表达式传入并执行,毫无疑问,执行结果也应当返回字符串“zhyea”: 在SpEL中还可以调用方法,访问属性,使用构造器。代码大致如下: 代码都上传到了github上,我就不一一贴执行结果了。 之前我们获取解析后的值是通过了一次强制类型转换的。Spring也提供了一个传入泛型类型来获取目标类型结果的方法,简单做了下封装: 程序处理SpEL这块儿还有很多的内容,不过从我个人的角度来说,到目前的程度已经够了。如果想继续多了解一些的话可以查阅下方的参考文档。话说,spring官方文档中对SpEL的说明完全是基于程序处理来进行的。 相关代码已上传到了GITHUB/zhyea,如有需要请直接查看。 参考文档 Spring Expression Language Spring Expression Language Guide SpEL表达式
[阅读更多...]-
springboot入门04 – 使用SpEL表达式
-
springboot入门03 – 配置定时任务
概述 在Java环境下创建定时任务有多种方式: 使用while循环配合Thread.sleep(),虽然稍嫌粗陋但也勉强可用 使用Timer和TimerTask 使用ScheduledExecutorService 定时任务框架,如Quartz 在SpringBoot下执行定时任务无非也就这几种方式(主要还是后两种)。只不过SpringBoot做了许多底层的工作,我们只需要做些简单的配置就行了。 通过注解实现定时任务 在SpringBoot中仅通过注解就可以实现常用的定时任务。步骤就两步: 在启动类中添加@EnableScheduling注解 在目标方法中添加@Scheduled注解,同时在@Scheduled注解中添加触发定时任务的元数据。 注意: 目标方法需要没有任何参数,并且返回类型为void 。 这里的定时任务元数据是“fixRate=1000”,意思是固定间隔每1000毫秒即执行一次该任务。 再来看几个@Schedule注解的参数: fixedRate:设置定时任务执行的时间间隔,该值为当前任务启动时间与下次任务启动时间之差; fixedDelay:设置定时任务执行的时间间隔,该值为当前任务结束时间与下次任务启动时间之差; cron:通过cron表达式来设置定时任务启动时间,在Cron Generator网站可以直接生成cron表达式。 这样创建的定时任务存在一个问题:如存在多个定时任务,这些任务会同步执行,也就是说所有的定时任务都是在一个线程中执行。 再添几个定时任务来执行下看看: 代码中一共创建了三个定时任务,每个定时任务的执行间隔都是1000毫秒,在任务体中输出了执行任务的线程ID和执行时间。 看下执行结果: 可以看到这三个定时任务的执行有如下的特点: 所有的定时任务每次都是在同一个线程上执行; 虽然未必是job1第一个开始执行,但是每批任务的执行次序是固定的——这是由fixRate参数决定的 这样的定时任务已经能够覆盖绝大部分的使用场景了,但是它的缺点也很明显:前面的任务执行时间过长必然会影响之后的任务的执行。为了解决这个问题,我们需要异步执行定时任务。接下来的部分我们将主要着眼于如何实现异步执行定时任务。 通过@Async注解实现异步定时任务 最常用的方式是使用@Async注解来实现异步执行定时任务。启用@Async注解的步骤如下: 在启动类中添加@EnableAsync注解: 在定时任务方法上添加@Async注解 我们为前面的三个定时任务都加上@Async注解再运行看看: 通过输出信息可以看到每个定时任务都在不同的线程上执行,彼此的执行次序和执行时间也互不影响,说明配置为异步执行已经成功。 通过配置实现异步定时任务 现在我们有必要稍稍深入了解下springboot定时任务的执行机制了。 springboot的定时任务主要涉及到两个接口:TaskScheduler和TaskExecutor。在springboot的默认定时任务实现中,这两个接口的实现类是ThreadPoolTaskScheduler和ThreadPoolTaskExecutor。 ThreadPoolTaskScheduler负责实现任务的定时执行机制,而ThreadPoolTaskExecutor则负责实现任务的异步执行机制。二者中,ThreadPoolTaskScheduler执行栈更偏底层一些。 尽管在职责上有些区别,但是两者在底层上都是依赖java的线程池机制实现的:ThreadPoolTaskScheduler依赖的底层线程池是ScheduledExecutorService,springboot默认为其提供的coreSize是1,所以默认的定时任务都是在一个线程中执行;ThreadPoolTaskExecutor依赖的底层线程池是ThreadPoolExecutor,springboot默认为其提供的corePoolSize是8。 说到这里应该清楚了:我们可以不添加@Async注解,仅通过调整ThreadPoolTaskScheduler依赖的线程池的coreSize也能实现多线程异步执行;同样的,即使添加了@Async注解,将ThreadPoolTaskExecutor依赖的线程池的corePoolSize设置为1,那定时任务还是只能在一个线程上同步执行。看下springboot的相关配置项: 其中spring.task.scheduling是ThreadPoolTaskScheduler的线程池配置项,spring.task.execution是ThreadPoolExecutor的线程池配置项。 再稍稍扩展下:@Async注解的value属性就是用来指明使用的TaskExecutor实例的。默认值是空字符串,表示使用的是springboot自启动的TaskExecutor实例。如有需要,也可以使用自定义的TaskExecutor实例,如下: 此外,还有一种做法是通过提供自定义的TaskScheduler Bean实例来实现异步执行。要提供提供自定义的TaskScheduler 实例,可以直接通过@Bean注解声明创建,也可以在SchedulingConfigurer接口中配置。这些在后面我们会提到。 调用SpringBoot接口实现定时任务 有时候会需要将定时任务的定时元数据写在数据库或其他配置中心以便统一维护。这种情况就不是通过注解能够搞定的了,此时我们需要使用springboot定时任务一些组件来自行编程实现。常用的组件包括TaskScheduler、 Triger接口和SchedulingConfigurer接口。 注意:因为我们用到了springboot的定时任务组件,所以仍然需要在启动类上添加@EnableScheduling注解。 Trigger接口 Trigger接口主要用来设置定时元数据。要通过程序实现定时任务就不能不用到这个接口。这个接口有两个实现类: PeriodicTrigger用来配置固定时长的定时元数据 CronTrigger用来配置cron表达式定时元数据 使用TaskScheduler接口 TaskScheduler接口前面我们提过,这个接口需要配合Trigger接口一起使用来实现定时任务,看个例子: 在上面的代码里,我们使用@Autowired注解获取了springbootr容器里默认的TaskScheduler实例,然后通过PeriodicTrigger设置了定时元数据,定时任务的任务体则是一个Runable接口的实现(在这里只是输出一行信息)。 因为默认的TaskScheduler实例的线程池coreSize是1,所以如有多个并发任务,这些任务的执行仍然是同步的。要调整为异步可以在配置文件中配置,也可以通过提供一个自定义的TaskScheduler实例来设置: 使用SchedulingConfigurer接口 SchedulingConfigurer接口的主要用处是注册基于Trigger接口自定义实现的定时任务。 在实现SchedulingConfigurer接口后,通常还需要使用@Configuration注解(当然启动类上的@EnableScheduling注解也不能少)来声明它实现类。 这个接口唯一的一个方法就是configureTasks,字面意思是配置定时任务。这个方法最重要的参数是一个ScheduledTaskRegistrar定时任务注册类实例,该类有8个方法,允许我们以不同的方式注册定时任务。 简单做了个实现: 这里我们只使用了三种注册任务的方法,分别尝试注册了fixDelay、fixRate以及cron触发的定时任务。 springboot会自动启动注册的定时任务。看下执行结果: 在执行结果中可以看到这里的任务也是在单一线程同步执行的。要设置为异步执行也简单,因为SchedulingConfigurer接口的另一个作用就是为定时任务提供自定义的TaskScheduler实例。来看下: 在这里,我将之前注册的定时任务去掉了,目的是想验证下这里的配置是否对注解实现的定时任务有效。经检验是可行的。当然对在configureTasks方法中配置的定时任务肯定也是有效的。我就不一一贴结果了。 另外,需要注意:如SchedulingConfigurer接口实例已经注入,将无法再获取到springboot默认提供的TaskScheduler接口实例。 通过Quartz实现定时任务 Quartz是一个非常强大的定时任务管理框架。短短的一篇文章未必能介绍清楚Quartz的全部用法。所以这里只是简单地演示下如何在springboot中是如何使用Quartz的。更多的用法建议优先参考Quartz官方文档。 在spring-boot-web 2.0及之后的版本,已经自动集成了quartz,如果不使用spring-boot-web或使用较早的版本的话我们还需要加一些依赖: 添加完成这些依赖后,springboot服务在启动时也会自启动内部的quartz。事实上springboot已经为我们准备好了几乎全部的quartz的配置。我们要做的只是把自定义的任务填进去。 首先我们需要创建一个Job实例,来实现Job的具体行为。 QuartzJobBean是Spring提供的Quartz Job抽象类。在实现这个类的时候我们可以获取注入到spring中的其他Bean。 配置Job 在创建QuartzConfig类的时候实现了InitializingBean接口,目的是在QuartzConfig实例及依赖类都完成注入后可以立即执行配置组装操作。 这里面有几个关键接口需要说明下: SchedulerFactoryBean,Quartz Scheduler工厂类,springboot自动化配置实现; Scheduer,负责Quartz Job调度,可从工厂类实例获取; JobDetail,执行Quartz Job封装; Trigger,完成Quartz Job启动。 还可以在配置文件中添加Quartz的配置: 这里配置了让Quartz默认延迟启动3分钟。 看下执行结果: 好了,就这些内容了。前面用到的程序都上传到了GITHUB,有需要可以参考下。 参考文档 Spring Task Execution and Scheduling Scheduling Tasks SpringBoot Quartz Scheduler Spring Boot Quartz Scheduler Example: Building an Email Scheduling app Quartz Scheduler Tutorials
[阅读更多...]