引入 在springboot的启动代码中看到了下面这一句: 先不谈这段代码的作用,只说这里的写法就有很多可以借鉴的地方: 定义了有意义的常量 在set之前先get以避免覆盖用户自定义配置 允许用户在程序中配置headless项,所以有了this.headless变量 Boolean.toString这样的写法也是极好的,起到了规范统一的作用 从spring的代码中就可以看到设计者对我们这些普通Developer的友好。 忽略掉这些有价值(但有可能显得啰嗦)的内容,可以提炼出这行代码: 这行代码的作用是:声明当前应用使用headless模式。 说明 Headless模式是应用的一种配置模式。在服务器可能缺少显示设备、键盘、鼠标等外设的情况下可以使用这种模式。 在java.awt.toolkit和java.awt.graphicsenvironment类中的很多方法,除了对字体、图形和打印的操作外还需要调用显示器、键盘和鼠标等设备。但是有一些类,比如Canvas和Panel,是可以在headless模式下执行的。 在headless模式下,(awt)应用程序可以执行如下操作: 创建轻量级组件 收集关于可用的字体、字体指标和字体设置的信息 设置颜色来渲染准备图片 创造和获取图像,为渲染准备图片 使用java.awt.PrintJob,java.awt.print.*,和javax.print.*类里的打印操作 简单来说,headless模式是一种声明:现在不要指望硬件的支持了,只能使用系统的运算能力来做些非可视化的处理工作。 除此之外,应该是没有其他作用了。 重点 关于这个配置在性能上的影响: 如果应用不需要任何head,那么有无这个配置没有任何影响; 如果应用有弹出窗口之类的操作,那么在headless模式下这种操作会被阻止。 总之,这是一项提示类的配置。 参考 Using Headless Mode in the Java SE Platform What is the benefit of setting java.awt.headless=true? End!
[阅读更多...]-
SpringBoot探索04 – Java Headless Mode
-
springboot学习01 – 自定义自动配置
概述 SpringBoot提供了自动配置能力。通过自动配置我们可以非常方便地启动相关的服务。 SpringBoot自动配置有两个核心模块: 自动配置模块(autoconfigure):主要负责读取配置相关的内容,并尝试启动服务; 启动模块(starter):提供具体的服务能力以及所有相关的依赖。 通常这两个模块是分开的。比如使用Caffeine缓存,缓存自动配置在一个独立的包中,Caffine缓存支持又是一个独立的包。如果不想把配置和能力分开,这两个模块也可以放在一起。 创建 接下来尝试创建一个自启动配置组件:hello-spring-boot-starter。这个组件功能很简单,就是在服务启动后自动打印一行“Hello xxx!”。 命名 springboot官方的自动配置包和自启动包都是以“spring-boot-”开头的。但是springboot不建议第三方开发者这样命名,应该是担心和官方支持出现冲突——即使现在没有冲突,未必以后官方不会推出相同的服务。即使使用了不同的groupId也仍然不建议这么做。 官方的建议是将具体的名称放“spring-boot”在前面。比如,我们要创建一个名为hello的自动配置组件,那么自动配置模块包可以命名为“hello-spring-boot-autoconfigure”,自启动模块包可以命名为“hello-spring-boot-starter”。如果要把这两个模块合并起来,那么包名是“hello-spring-boot-starter”。 配置项 如果自定义的自动配置组件提供了配置项,那么需要为配置项提供一个独立的名称空间。注意,尽量不要和spring-boot默认提供的名称空间(server、management、spring等等)产生冲突。建议使用组件的关键字作为名称空间,比如我的组件名称是hello,那么名称空间就是hello。组件只有一个配置项name,最终yaml格式的配置就是: 然后需要为这些配置项创建一个配置描述类来表示记录配置信息,如: 配置描述类中需要包含全部配置项,以确保其生效。 下面是一些SpringBoot内部的配置类定义的准则: 不以“the”或“a”开头 boolean类型的配置项,以“weather”或“enable”开头 对于简单集合类型,尽量使用逗号分隔的字符串形式 对于毫秒级的时间,使用java.time.Duration替换long类型 如果时间不是毫秒级的,需要在meta-data中提供必要的提示,如:”If a duration suffix is not specified, seconds will be used” 提供默认值要谨慎,如果默认值不是在运行时必需的就不要设置 为了能让idea等开发工具识别我们提供的配置项,还需要提供一个meta-data文件META-INF/spring-configuration-metadata.json。 SpringBoot提供了annotationProcessor来辅助生成meta-data文件。我们只需要添加如下依赖即可: 不过annotationProcessor对集合类型支持得不是很好,使用的时候要慎重。 此外,annotationProcessor还能生成一个配置项元数据文件META-INF/spring-autoconfigure-metadata.properties。当存在这个文件的时候,就可以了用来对配置项进行初步的过滤,有助于减少启动耗时。 配置类 自动配置组件的配置类就是一个标准的配置类,所以它也需要使用@Configuration注解。下面是一个配置类的示例: 示例代码中通过@EnableConfigurationProperties注解引入了配置描述类。还提供了相应的构造器以便注入配置信息。 此外这里还装模作样的使用了条件注解@ConditionalOnClass。System.class是JRE的标配,因此这行注解实际上是没有任何作用的,在这里只是做个演示。条件注解通常多出现在自动配置中,以保证在满足设定条件后自动配置才能生效。关于条件注解前段时间写过一篇文:《SpringBoot条件注解》。这里就不重复啰嗦了。 因为自动配置组件要求放在独立的包中,而且包路径不能和应用包路径重合,所以需要提供一些帮助才能让SpringBoot识别我们提供的自动配置信息——这里是META-INF/spring.factories文件。SpringBoot会检查jar包中是否存在META-INF/spring.factories文件,并尝试读取解析文件中配置的类信息。关于读取解析spring.factories文件的过程在之前也有介绍过:《SpringBoot启动过程之getSpringFactoriesInstances》。 下面是一个spring.factories文件的示例: 应该可以看出spring.factories实际上就是一个典型的.properties文件。 注意:SpringBoot自动配置组件只能通过这种形式加载。在定义组件包路径的时候就需要注意包路径不能是Spring componentScan的目标。同时,在自定义组件类中也不能使用componentScan来获取其它的组件。如有必要,可以使用@Import注解代替(可以参考SpringBoot探索01-@Import注解)。 如果多个配置类之间存在先后顺序的话,可以使用@AutoConfigureAfter和@AutoConfigureBefore注解来确定顺序。比如,如果定义的是web相关的配置类,那么这个配置类可能就需要在WebMvcAutoConfiguration之后生效。 如果想保证多个配置类的加载顺序,又不想让配置类之间存在显式的关联,那么可以使用@AutoConfigureOrder注解。这个注解和普通的@Order注解的作用是一样的,但是只能用于自动配置类。 启动类 关于启动类的作用,根据名称就可以猜出来:主要是负责组件服务的启动。前面配置类的示例代码中就有几行启动类相关的内容: 其中的HelloStarter就是一个启动类。在配置类中创建注入了HelloStarter的实例。具体的服务逻辑还是需要在启动类HelloStarter中完成。 很多时候,启动模块和配置模块是分别放在独立的包中的,不过这里实现的功能比较简单,且无其它的依赖,所以就干脆放在一个jar中了。 看下HelloStarter的实现: 只是在HelloStarter实例注入完成后执行了一行输出语句。可以说是极为简单了。 测试 自动配置可能会被多种因素影响: 用户自定义配置(Bean定义和自定义环境参数) 条件分析(是否存在某个类或某个依赖) 其它约束 执行具体测试的时候就需要为每种情形定义一个ApplicationContext。这种情况下,使用ApplicationContextRunner事情会变得很简单。 ApplicationContextRunner主要被用来搜集基础的、通用的配置信息。通常是作为成员变量定义在测试类中,如下例: 如果定义了多个配置类,不用在测试中刻意控制声明的顺序,SpringBoot会保证它们的触发顺序和正常启动时一致。 每个测试都可以使用contextRunner执行一类测试案例。在下面的示例代码中定义了一个新的配置类HelloConfiguration ,但是在新的配置类中创建的HelloStarter Bean并不能覆盖自动配置中创建的同类的Bean: 因为没有提供配置信息,所以自动配置中创建的HelloStarter Bean的name值是null。 在测试中使用了Assert4J来进行值的比较。 还可以自定义配置参数,如下: contextRunne还可以展示ConditionEvaluationReport,即条件注解检查过程日志。日志的级别可以设置为INFO或DEBUG,下面的测试代码使用了ConditionEvaluationReportLoggingListener来打印条件注解检查过程日志: 借助于SpringBoot提供的FilteredClassLoader,我们还能够验证在某个类或某个jar不存在的情况下自动配置如何处理。在下面的代码中,我们在类加载器中排除掉了HelloStarter.class,这样自动配置就不会生效: 另外,如果我们需要的是Servlet或Reactive web应用Context,可以使用WebApplicationContextRunner或者ReactiveWebApplicationContextRunner。 其它 这里的测试代码已经上传到了GitHub,见: GitHub/zhyea。 不过这个自启动组件实现的功能太过简单,如果想深入了解下,可以参考SpringBoot官方提供的自启动配置。我自己还写过一个简易的kafka自启动组件,如果有兴趣也可以参考下。 还有一个自动配置演示项目也不错,在git:spring-boot-master-auto-configuration 参考 Creating Your Own Auto-configuration Metadata json for nested List End!
[阅读更多...] -
SpringBoot探索03 – 条件注解
SpringBoot中提供了一系列的条件注解(@Conditional)来实现对@Bean和@Configuration等实例的创建进行约束。这些注解包括: Class Conditions,类条件约束 Bean Conditions,Bean条件约束 Property Conditions,配置项条件约束 Resource Conditions,文件条件约束 Web Application Conditions,Web应用条件约束 SpEL Expression Conditions,SpEL表达式条件约束 类条件约束 类条件注解有两个:@ConditionalOnClass和@ConditionalOnMissingClass。 类条件注解通常用于自动化配置管理中,作用是根据某个(或某些)类的存在与否来决定一个@Configuration注解的类是否需要被解析并注入。 因为自动化配置类是在一个独立的jar包中,而且SpringBoot是使用ASM执行类条件注解的解析,也就是说相关的@Configuration类不一定会被加载,所以@ConditionalOnClass和@ConditionalOnMissingClass的value属性可以是一个类。 当然,如果存在顾虑的话,也可以使用全限定类名字符串设置name属性的值。尤其是在应用内部使用类条件约束注解的时候,就只能使用name属性了。 当类条件约束注解用于@Bean注解的方法时存在一种特殊的情形:即约束注解的value值就是方法的返回值的类型。因为在注解生效前,JVM就已经加载了这个类并开始尝试处理方法的引用,此时如果相关的类不存在就会执行失败。 为了解决这种问题,可以将一个@Configuration类拆分开来,如下例所示: 类条件约束注解会影响@Configuration类的创建。 Bean条件约束 Bean条件注解也有两个:@ConditionalOnBean和@ConditionalOnMissingBean,作用是根据当前容器中是否存在指定的Bean来决定是否进行注入。 可以使用注解的value属性来按类指定Bean,也可以使用name属性按名称指定Bean。search属性可以用来限制在容器的哪些层级上搜索Bean。 当把Bean条件约束注解用于@Bean方法上时,type属性的值默认就是方法返回值的类型,看个例子: 如上面的示例代码:如果如果容器中目前没有MyService类的实例,这里就会创建一个myService 实例并注入。 因为是根据当前已经加载过的类进行判断的,所以在使用Bean条件约束注解时,需要注意相关Bean的加载次序。 在自动化配置类中使用时,则无需考虑这些事情,因为自动化配置类中的Bean一定会在用户自定义的Bean都注入完成后才会被加载。 @ConditionalOnBean和@ConditionalOnMissingBean并不会阻止@Configuration类的创建,只会对@Configuration类实例的注入产生影响。 配置项条件约束 配置项条件注解@ConditionalOnProperty会根据SpringEnvironment的属性(或者说是配置信息)进行约束。 使用注解的prefix和name属性可以指定要检查的配置项。 可以根据havingValue和matchIfMissing属性来设置配置项的预期值。 如果不做任何设置的话,只要配置项的值存在且不为false就一定能通过校验。 另外,如果将配置视为是树结构的话,那么指定的配置项一定是叶子结点,如下例: 如果指定的配置项是kafka.common或kafka.consumers,校验的返回结果一定是false,只有指定kafka.common.bootstrap_servers或kafka.consumers[0].topics才有意义。 资源条件约束 资源条件注解@ConditionalOnResource会根据指定的资源文件是否存在来进行校验。资源文件名如:file:/home/user/test.dat。 Web应用条件约束 Web应用条件注解同样有两个:@ConditionalOnWebApplication和@ConditionalOnNotWebApplication。 这类注解会校验当前应用是否是web应用。 如当前应用是一个servlet类型的web应用,那么判断依据就会是是否存在WebApplicationContext容器,有没有定义session scope,或者是否存在ConfigurableWebEnvironment实例。 而react类型的的web应用判断依据则是是否有使用ReactiveWebApplicationContext容器,或者是否存在ConfigurableReactiveWebEnvironment实例。 SpEL表达式条件约束 SpEL表达式条件注解@ConditionalOnExpression会根据SpEL表达式进行校验。 关于SpEL表达式可以参考前文《使用SpEL表达式》。 End!
[阅读更多...] -
SpringBoot探索02 – 启动过程之getSpringFactoriesInstances
最近计划整理下SpringBoot的启动过程以及API请求返回过程,以便支持后续的部分内容,也方便实现一周一文的计划。 先做SpringBoot启动流程分析。 初见 在SpringBoot启动代码的开头部分可以看到如下的内容: 比较显眼的是getSpringFactoriesInstances方法——这个方法出现了两次,而且以后还会继续出现。 从代码上看来,这个方法的作用应该就是获取指定类型的实例集合。 具体如何,还得继续走走看。从这里一步步走下去,可以看到一个重载的方法,这个方法的内容比较实在: 关键是方法体中的三四两行。第三行应该是获取了名称集合,第四行则应是获取了名称对应的实例集合。 问名 首先看看SpringFactoriesLoader.loadFactoryNames方法做了哪些事情。这个方法的核心内容在于它调用的loadSpringFactories方法。loadSpringFactories的方法体稍稍长了一些,沾出来显得太啰嗦,所以仅在下面介绍下这个方法具体做了那些事情。 这个类的作用是根据类加载器获取类名称集合(map结构,接口->实现类集合),具体流程如下: 首先尝试从内存缓存中获取,如获取到就立即返回,没有则继续下面内容; 从类加载路径(的jar文件)中获取全部的spring.factories文件路径; 循环遍历读取这些文件中的键值对(K->List[V]); 将读取内容放入内存缓存,下次再调用这个方法时会优先从缓存中获取; 返回读取到的全部键值对集合(Map[K,List[V]]结构)。 简单做些解释: spring.factories文件本质上是一个.properties文件,也就是说,它的内容是键值对集合,下面是一段示例: 这个片段中有三个键值对。根据后面的代码可知spring.factories文件中每个键值对的key是一个接口的名称,value则是实现类名称的集合(以“,”分隔)。 spring.factories文件中的内容会被读取到一个(K->List[V])结构的Map中。最终全部这些内容又会以类加载器实例为key保存在内存缓存中。 然后,SpringFactoriesLoader.loadFactoryNames方法就可以根据接口的名称获取到接口的实现类的名称集合了。 得意 在得到类名称集合后就是根据类名构建类对应的实例了。这是createSpringFactoriesInstances方法做的事情。这个方法不长,可以粘出来看看: 方法的内容很清晰也很简单: 根据方法名获取Class实例; 通过Class实例获取构造器; 使用构造器构建类实例。 很普通的一个反射过程。期间有一个校验是判断Class是不是指定接口的子类;还有就是在BeanUtils.instantiateClass中创建实例的时候修改了构造器的可见性范围,并提供了对Kotlin的支持。 这块儿内容大体上就这样了。至于这里获取到了哪些ApplicationContextInitializer和ApplicationListener的实例,以及它们的作用,在后续的部分会陆续提到。 End!
[阅读更多...] -
springboot入门08 – 创建非web项目
概述 从开始使用SpringBoot到现在,一直都是在用SpringBoot开发web服务(API服务)。直到前段时间,需要帮其他组的同事写一个非web的简单服务时,才想到Springboot是不是也支持非web项目。 答案是肯定的:spring诞生之初就不是为web项目定制的,springBoot无非是在spring核心项目的基础上添加了一些方便开发者使用的组件,所以使用springboot开发非web项目也是可行的。 首先我们要弄明白常用的web项目和非web项目的区别在哪儿?私以为是服务启动和执行逻辑触发的方式: web项目需要依赖web容器来启动,通过http请求来触发相关的服务; 非web项目则不需要依赖web容器来启动,它可以是自启动的; 非web项的服务通常是主动触发的或者通过非http的方式被动触发的。 接下来详细介绍下如何使用springboot构建非web项目。 依赖 创建web项目通常需要使用的依赖是spring-boot-starter-web: 这个依赖间接引用了tomcat,spring-webmvc,spring-context,json-starter等依赖。这些在非web项目里基本上都用不到,非web项目可以直接使用spring-boot-starter: spring-boot-starter间接引用了spring-core、yaml、auto-configure等依赖,已经足够用来创建一个普通的spring项目了。 如果有特殊情况(说实话,我强烈怀疑)非要使用spring-boot-starter-web依赖来构建非web项目也不是不行,只需要加一些配置来避免启动web容器就行: 或者在启动类中添加web配置并设置为NONE: 启动 springboot非web项目的启动类定义和web项目并无不同。如下: 但是执行启动类中的main方法以后呢?如果是web项目,在启动后等待HTTP请求调用就行了。不过这里是非web项目,我们得想办法执行我们定义好的业务逻辑。接下来按常见的业务逻辑特征分别介绍下。 1. 任务需要定时执行 这种情况需要配置定时任务。SpringBoot对定时任务的支持还算可以。前段时间我写过关于《SpringBoot定时任务配置》这样的文章,有需要可以参考下,这里就不重复介绍了。 2. 在某个类的实例注入后就立即执行 这种场景说实话不多见。通常是需要该类实现InitializingBean接口,并在afterPropertiesSet方法中实现相关的逻辑。如下: 还有一种方式是使用java提供的@PostConstruct注解: 建议最好使用前者。 3. 使用SpringBoot的提供的主动执行接口 springboot提供了两个接口ApplicationRunner和CommandLineRunner来支持主动执行业务逻辑。 比如我们可以直接实现ApplicationRunner接口,并在run方法中添加业务逻辑: CommandLineRunner接口和ApplicationRunner接口的差别不大。从执行时机还是调用过程上来着,这两者几乎都是一样的。这唯一的区别在于他们提供的run方法的args参数类型上: ApplicationRunner的args参数是ApplicationArguments类型,对原始参数做了一层封装; CommandLineRunner的args参数是字符串类型,取的是启动类收到的原始参数。 这种差别源于SpringBoot对二者的定位上: ApplicationRunner适用于启动即执行的场景,只需要读取一次参数信息即可。它的参数通常是“option=value”这种结构的,如:“–foo=bar –foo=baz” 。ApplicationArguments中封装了一些对这种参数进行处理的方法,以便开发使用。 CommandLineRunner从名字上看就是用来做命令行交互用的,所以它这里直接取了原始参数,看一个使用示例: 如果在执行命令行交互之前也需要读取解析传入的参数,那么这里的MyRunner3类完全也可以实现ApplicationRunner接口。二者的差别几乎可以忽略。 在一个应用里面可以有多个ApplicationRunner或CommandLineRunner的实现。要调整两者的实现类之间的执行顺序可以使用@Order注解。 就这样。这一节虽然介绍的是如何创建启动非web项目,但是在web项目中也会有主动触发一些执行逻辑的需要,上面介绍的这些方案也是完全可用的。 示例代码已上传到了 GitHub/Zhyea ,有需要可自行查阅。 End!
[阅读更多...] -
spring feign https配置
前两天需要通过springboot-feign来调用一个https的外部服务接口,因此要实现feign-client的SSL设置。 feign执行http请求通常会调用feign.Client接口的实现。这个接口的默认实现类Default提供了添加SSL配置的构造器: 简单介绍下构造器参数中用到的SSL相关的接口: SSLSocketFactory:SSLSocket工厂;SSLSocket扩展了Socket并提供使用SSL或TLS协议的安全套接字。这种套接字是正常的流套接字,但是它们在基础网络传输协议(如TCP)上添加了安全保护层。 HostnameVerifier:实现主机名验证功能;在握手期间,如果URL的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口实现程序来确定是否应该允许此连接,如果回调内实现不恰当,默认接受所有域名,则有安全风险。 默认使用的Client实例的两个构造参数的值都是null。我们需要在配置类中创建使用SSL的Client实例,这里参考了默认的配置类FeignClientsConfiguration: 代码中使用了SSLContext的getSocketFactory方法来创建 SSLSocketFactory对象。在创建SSLContext对象时,通过(chain, authType) -> true指定了SSL信任策略,即:不做任何处理,信任全部请求。 另一个HostnameVerifier类型的参数使用了一个实现类NoopHostnameVerifier的实例,作用是关闭主机名验证功能,并且永远不会抛出SSLException。 最后将配置添加到FeignClient注解上即可: 如代码中所示可以为configuration项一次指定多个配置类。这里的第一个配置类是spring默认提供的配置类。 另外,如需要创建Client的Bean实例,在使用Eureka的情况下,应选择LoadBalancerFeignClient的对象,不然会产生诸如无法解析host之类的问题。 就这样了。
[阅读更多...] -
springboot入门07 – 配置文件详解
概述 这一篇主要介绍下SpringBoot配置相关的内容。 通过配置文件,我们可以做到如下事情: 修改springboot的默认配置项 添加自定义配置项 SpringBoot使用全局的配置文件,主配置文件名是固定的:application.xxx springboot的配置文件有多种格式可选,如:.properties,.xml,.yml。 我个人比较喜欢yaml的简洁,所以推荐使用yaml。 yaml的基本语法 下面介绍些yaml的语法 使用空格缩进来表示结构,相同层级的数据其缩进的长度必定相同: 支持使用注解,但只支持使用单行注解,注解以“#”号开头: 支持直接类型或字符串的列表或数组结构,有两种表达形式: 每个列表成员占一行,以横杠和空格(- )开头 所有列表成员在同一行,被方括号([])包围,列表成员间以逗号和空格分隔 也支持Map结构,同样也有两种表达形式: 每个KV对占一行,键值用冒号分隔 所有KV对在同一行,被大括号({})包围, KV对之间用逗号和空格分隔,键值用冒号分隔 通常字符串不需要使用引号,但必要时可以使用单引号(’)或双引号(”),比如需要使用转义字符时 可以使用两个叹号(!!)来强制指定格式(这个感觉用处不大,但是万一用到了呢) 内容可以重复使用,原始位置的内容用(&标记名)来标记,在引用位置用(*标记名)来引用 如果想在一个文件里面配置不同的profile,可以使用三个横杠“—”将yaml文件分割成多个独立区域,每个区域被视为一个独立的文件。三个点号“…”用来标识文件结尾。 如示例代码,在一个yaml文件中存在多个独立区域时,使用spring.profiles属性来标识配置区域的profile信息。 多环境配置 在主配置文件application.yml之外,还可以有多个外部配置文件,其名称格式固定为application-{profile}.yml。 SpringBoot会根据主配置文件中的spring.profiles.active参数来决定加载哪个配置文件。 通过这种方式SpringBoot实现了支持多环境配置,从而能够在不同环境下(开发、测试、生产)进行配置参数的切换。 主配置文件中的spring.profiles.active属性还可以有多个值,每个值以逗号进行分割: 这种方式允许我们为不同需求创建不同的配置文件。SpringBoot会依序加载每个值指向的配置文件。 配置文件的加载顺序是主配置文件application.yml,spring.profiles.active指定的外部配置文件。如有冲突项,后面加载的配置会覆盖前面的。 如果多个外部配置文件有相同的配置项,建议在主配置文件中进行配置。 如spring.profiles.active指定的值无法与任何外部文件匹配,SpringBoot会尝试加载application-default.yml文件。 结合@Profile实现内部实例的动态选择 spring.profiles.active参数不仅可以指明要使用哪个外部配置文件,也可以控制内部实例的选择。这需要通过配合@Profile注解实现。 如下例: 如果没有@Profile注解,在启动的时候SpringBoot就会报错,因为为一个Worker类提供了两个实例。但是现在,SpringBoot可以根据spring.profiles.active指明的profile和@Profile注解来选择注入哪个实例。 @Profile注解可以用于含有@Component、@Configuration、@Bean等注解的类及其方法。 @Profile注解也可以有多个值,如下: 这样,在profile是dev或test时,这个Bean都可以被成功注入。 配置项获取 使用SpringBoot的配置项有两种方式:通过@Value注解或通过@ConfigurationProperties注解。这两种方式各有长处,可根据进行选择。 通过@Value注解获取 @Value注解适用于单个配置项的获取,比如我们可以获取当前的外部配置文件的profile: @Value注解的一个优势是可以使用SPEL表达式(关于SPEL表达式,我有整理过:《SpringBoot SpEL表达式》),如下是一个非常粗略的SPEL表达式: @Value注解的不足之处在于只能获取直接类型的值或字符串,不能映射为复杂类型。 通过@PropertiesConfiguration注解获取 @PropertiesConfiguration注解的主要特点是可以将配置映射为对象。因此平时还是通过这种方式获取SpringBoot配置为主。 @PropertiesConfiguration注解可应用于类或成员方法。 如下是将应用于类的示例: 从示例代码中可以看到配置类需要有@Component注解或@Configuration注解,以便完成注入。 此外,在使用@ConfigurationProperties注解的时候需要传入注入配置属性的前缀,以便获得配置项的范围。 在成员方法上使用@PropertiesConfiguration注解则是一种更为灵活的方式,能够充分弥补@Value注解的不足,示例如下: 注意这里还需要用到@Bean注解,以便将读取到的配置信息注入到实例上。不过不能通过getWorker()方法调用,否则获取的还是一个只有默认值的对象。要使用该实例还是需要通过@Autowired注解来注入。 最后,可以看下我在GITHUB上的示例应用,在里面我尝试了SpringBoot配置的各种应用实践。 就这样。
[阅读更多...] -
springboot入门06 – 接口单元测试方案
以前写过关于springboot Controller层单元测试的系列文章(Spring Controller层测试)。但是那几篇文章还是更偏方法论一些,不能直接拿来使用。所以有了这偏内容,目的主要是记录下平时使用的Controller层单元测试方案。 在这里先定义一个普通的api接口类WorkerApi: 这个接口里的4个方法覆盖了平时常用的四种Http请求方案,并将请求结果用一个统一的ResultWrapper类进行了封装(关于如何封装请求结果请参考上一篇文章SpringBoot Controller返回值封装)。 然后是单元测试方案。这里有两个超类:TestBase和ApiTestBase。前者用来对普通的注入实例进行测试,后者主要用来对Api接口进行测试。 TestCase类内容如下: 通过类内容可以看到,在测试中会启动一个内嵌的WebServer来加载Spring Context。这样的测试比较重一些,不过也和实际使用场景更一致一些。 ApiTestBase类继承了TestBase类。在这个类里引用了TestRestTemplate的实例执行具体的接口调用,此外还定义了一些公用方法来减少使用时的代码量: 这里为每种请求都定义了两个方法,以根据需要返回不同形式的返回值。 看下是怎样使用的: 有时候还会有在执行测试前添加虚拟机环境参数的需要,此时可以视情况在TaseBase或ApiTestBase或测试类中添加静态代码块并设置虚拟机参数: 就这样。代码已经传到了GitHub上,有需要请自行查阅: GitHub / zhyea
[阅读更多...] -
springboot入门05 – 包装SpringBoot Controller返回值
一个项目使用了SpringBoot,需要对Controller的返回值进行二次包装。包装类结构大致如下: 通过查找资料,找到了两种封装方式。 方法一 第一种方式是替换掉RequestResponseBodyMethodProcessor,这需要使用一个MethodReturnValueHandler的装饰类: 在装饰类中使用一个Result类的实例替换了returnValue。而后在InitializingBean中基于原来的RequestResponseBodyMethodProcessor的实例创建一个ResponseBodyWrapHandler的实例来完成替换: 使用ResponseBodyWrapFactoryBean,完成afterProperties方法的调用,只需要创建一个ResponseBodyWrapFactoryBean的实例即可: 这行代码可以放在启动类中。 方法二 第二种方式基于ControllerAdvice和HttpMessageConverter实现。 首先用一个ResponseBodyAdvice类的实现包装Controller的返回值: 如果Controller类的返回值没有String类型的,仅有上面这个类就够了。如果有String类型的返回值,就有可能遇到类型不匹配的问题。HttpMessageConverter是根据Controller的原始返回值类型进行处理的,而我们在ResponseAdvisor中改变了返回值的类型。如果HttpMessageConverter处理的目标类型是Object还好说,如果是其它类型就会出现问题,其中最容易出现问题的就是String类型,因为在所有的HttpMessageConverter实例集合中,StringHttpMessageConverter要比其它的Converter排得靠前一些。我们需要尝试将处理Object类型的HttpMessageConverter放得靠前一些,这可以在一个Configuration类中完成: 在这个方案中,如需要对异常做些特别处理,还可以创建一个ExceptionAdvisor类来完成: 这样还可以根据异常类型来设置返回时的HttpStatus。 就这样。 有朋友在评论区指出问题了,做了些调整,也写了一份示例程序上传到CSDN。有兴趣的可以下载来看看。下载地址如下:点击此处下载。 这是许久之前刚用springboot时写的,现在适应springboot的最新版本存在一些问题。所以稍稍重新调整了下,并将之加入到了最近正在尝试进行的一个系列里面。 新版本的代码可以在GitHub / zhyea上看到。 ##########
[阅读更多...] -
springboot入门04 – 使用SpEL表达式
概述 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表达式
[阅读更多...]