通常我们注册 Spring Bean 是通过像 @Named, @Bean, @Component, @Service 这样的注解来注册的,或者用更为古老的 XML 配置文件的方式。难免有时候要根据实际业务需求在 Spring 运行期间动态注册 Spring Bean, 比如基本某种形式的配置文件或系统属性来注册相应的 Bean, 这好像又回到了 XML 文件注册方式,也不尽然。
那为什么在运行期还要去注册 Spring Bean 呢,直接 new 对象不行吗?当然行得通,不过这样的话就不能更好的使用到 Spring IOC 的好处了。像待注册的 Bean 构造函数可以直接用到其他的 Spring 对象,或 @Value 引入环境变量,还有 @PostContruct 这样的行为。
最初思考如何注册 Spring Bean 时还是费了不少周折,如今清晰了许多。了当的说,不管是 Spring 初始时还是运行时,注册 Bean 的关键(应是唯一) 入口就是 BeanDefinitionRegistry 接口的方法 Read More
Java 1.5(Tiger) 个人认为最为激动人心的两个特性是泛型与注解(Java Versions, Features and History)。泛型自然是不必说了,注解对 Java 世界的改变比泛型伟大的多(现在框架的注解配置),在 Java 1.5 之前我们只能在 Javadoc 注释中做文章,于是只能用 XDoclet 那样不伦不类的东西。Java 的注解发展到现在几乎可以使用在书写代码时的任何地方,见
java.lang.annotation.ElementType中的类型,囊括了 TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER(since 1.8), TYPE_USE(since 1.8)。Java 1.5 基本确定了注解的基本框架,包括元注解(meta-annotation); 直到 Java 8 又扩展了注解的使用范围,列举如下:
创建类实例
new@Interned MyObject();类型映射
myString = (@NonNull String) str;implements 语句中
class UnmodifiableList<T> implements@Readonly List<@Readonly T> { ... }throw exception声明
void monitorTemperature() throws@Critical TemperatureException { ... }解析前面 ElementType Java 8 增加的 TYPE_PARAMETER和 TYPE_USE 注解使用新场合。ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中。ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中(如: 声明语句、泛型和强制转换语句中的类型) Read More

在 Spring 中应用 ServiceLocator 方式来获取 Spring Bean 的介绍参考了那么多,其实还是数官方的 ServiceLocatorFactoryBean JavaDoc 文档最言简意该了。Spring 的 ServiceLocator 仿佛用处不大,说到底就是类似于下方找寻某个 Spring Bean 一样:
ApplicationContext context = ...; Service service = context.getBean(ServiceImpl.class); Service service = context.getBean("myService");
只是有了 ServiceLocatorFactoryBean(它本质上就是一个 FactoryBean) 后我们不需要直接与 ApplicationContext 打交道,且多个的 Spring Bean 可以从相关的一个 FactoryBean 获得。下面用两个例子来演示(代码中刨去了 package 和 import 部分的代码)
一:实现类只有一个 Spring Bean 时
接口类 Parser(我们要定位就是它的实现类) Read More

许久没记录笔记了,这回来重新熟悉一下 Spring 中 FactoryBean 的使用,顾名思义,它是用来获得相应 Bean 的工厂的。它与另一个 Spring 中的接口 BeanFactory 的作用不一样的,不能多说了。FactoryBean 和 BeanFactory 都是在
org.springframework.beans.factory包中,谁能一看类名搞清楚它们的差别?- FactoryBean: 用于创建某个特定的 Spring bean 的工厂类
- BeanFactory: Spring 上下文的最顶层接口,如
ApplicationContext就继承了该接口,它可称之为所有 Spring bean 的工厂
这儿说的是第一个 FactoryBean, 它的接口声明是
1public interface FactoryBean<T> { 2 T getObject() throws Exception; 3 Class<?> getObjectType(); 4 boolean isSingleton(); 5}它最终的效果是,Spring 容器中注册一个名称为 abcFactoryBean 的
AbcFactoryBean实例,通后名称abcFactoryBean获得的实际上是相应AbcFactoryBean.getObject()返回的对象,类型为getObjectType(),isSingleton()是否是单例。 Read More
本文记录 SpringBoot 与 Logback 是如何工作的,即观察 SpringBoot 中 Logback 是怎么一步一步初始化的。用以测试的 SpringBoot 版本是 1.5.16, 而非最新的 SpringBoot 2。关于 SpringBoot 日志的官方文档在 Logging, 但不太详细或透彻。本文也不承诺说就理解得更有深度,只是为官方文档提供更多方面的参考。
SpringBoot 默认使用 Slf4J + Logback 来记录日志,对于一个基本的依赖于
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>的 Spring Boot 项目,上面组件依赖了 spring-boot-starter-logging 组件,而该组件又引入了以下几个依赖
- logback-classic: 依赖了 Slf4J
- jcl-over-slf4j
- jul-to-slf4j
- log4j-over-slf4j
相当于把其他的日志框架全桥接到了 Slf4J + Logback 上去了。 Read More
在命名本文的标题都敲打了几分钟时间,问题很简单,然而用简短的一个标题完全描述出来却有点费事。在 Spring MVC 项目结合 Springfox 来生成 Swagger API 文档时,如果一个资源操作因为请求参数的不同而映射到多个 controller 方法,那么 Swagger 可能只能生成某一个 API 条目,其余都被忽略。至于为什么说是 "可能", 可能正好未遵循命名规范而躲过了这一劫。由此引出我们的问题
我们这里用了资源操作一词,它包含了两部分信息: 资源与操作,比如/users/{userId}是资源,而发生在其上的 HTTP 各种方法,如 POST, GET, PUT, DELETE 等就是操作。而 Spring MVC 中允许我们针对不同的查询参数把相同的资源操作映射到不同的 controller 方法上,也是为了保持逻辑上更为清晰。
比如下面的例子路由配置的例子GET /users/{userId} UserController.getUserInfo //默认
看到上面资源与操作完全相同,仅仅因为 source 查询参数的不同而映射到三个 controller 方法。用代码体现如下图 Read More
GET /users/{userId} UserController.getUserInfo //当有 ?source=file 时
GET /users/{userId} CloudUserController.getUserInfoFromCloud //当有 ?source=cloud 时
这是一个最基本的 Spring 4 MVC 的 Maven 项目,非 SpringBoot 的,SpringBoot 由于有许多自动化配置特性,会更简单些。本例全部用 Java 代码作为配置,免除了创建web.xml和如dispatcher-servlet.xml这样的文件。本人更倾向于 Java 配置,它的优势在于能进行编译期检查,逻辑性也强,配置文件只是改动无需重新编译,都是要重启服务的; 关于使用 XML 配置文件的方式可参考文后的链接。
本文侧重于 Spring MVC 项目提供 RESTful JSON API, 因而静态 Web 内容提及较少。创建一个 Maven 项目的方式,可以直接创建一个pom.xml文件,然后编辑它的内容,使用 IntelliJ IDEA 的话只需要选择导入为一个 Maven 项目就成,Eclipse 的话可能还要事先运行mvn eclipse:eclipse初始化一下。项目结构布局
就是一个普通的 Maven 项目,稍稍不同的是 src/main 目录中除了 java 和 resources 之外,还有 webapp 目录,用于存放 web 静态文件或模板文件的。
Read More
Spring 相关代码分析
本文通过对 Spring 的源代码来理解它是如何扫描 Bean 与资源的,因为自己有一个类似的需求,想把一堆的配置文件丢到 resources 下某个目录中,在程序启动的时候能加载它们。因为文件名是不一定的,所以不能直接指定文件名来加载,通过对 Spring 扫描资源的理解后,可以在自己的代码中手工扫描那些配置文件,以后有任何新的配置文件只需要扔到相应的配置目录即可。
下面以一个最简单的 Spring Boot 项目为例,调试并观察源代码1@SpringBootApplication 2@ComponentScan(basePackages = "cc.unmi") 3public class DemoApplication { 4 public static void main(String[] args) { 5 SpringApplication.run(DemoApplication.class, args); 6 } 7}
还是直奔主题吧,不一步一步的去探寻到底是哪个实现类去扫描资源的,用 Google 找到的是ClassPathScanningCandidateComponentProvider, 因此直接在这个类的敏感位置上打上断点,比如它的构造函数 Read More
其实很多年前就做过如此的实验,一翻开自己的日志有关于 aspectj site:yanbin.blog, 可以找到 2008 年写的日志。真是流光容易把人抛,红了樱桃,绿了巴蕉。只是那时候 Spring 刚步入 2.0, 才翻开强大 AOP 的篇章,还记得彼时只要是直接使用 AspectJ 就要写 *.aj 文件。而如今 Spring 都到 5.0 了,也就是一年前才重拾起 Spring, 这期间 AspectJ 早就可以不用 *.aj 文件,只需普通 Java 文件,加上 @Aspect 和 @Pointcut 之类的注解就行。
本文内容与几年前写过的日志大体相差不大,再缀上一篇纯粹是个人笔记。这里不以 Spring 5.0 为例,仍然是最新的 4.3.11.RELEASE, 并且直接用 Spring, 而非选择 Spring Boot, 因为用了 Spring Boot 常常搞不清楚哪些是自动配置了的。原生的 Spring 可以使自己掌握一个 Spring AOP 的基本要素。
需求:@LogStartTime 注解的方法,在每次进入该方法时把当前时间写入 ThreadLocal 中去,被 @LogStartTime 注解的方法中随时可以获得进入方法的时间 Read More
用过 Spring 的 @EnableScheduling 的都知道,我们用三种形式来部署计划任务,即 @Scheduled 注解的 fixedRate(fixedRateString), fixedDelay(fixedDelayString), 以及 cron. cron 不在这里讨论的范畴。我们着重在如何理解 fixedRate 和 fixedDelay 的区别。
在 Spring 的 Scheduled 注解的 JavaDoc 对此的解释很简单
public abstract long fixedRate
Execute the annotated method with a fixed period in milliseconds between invocations.
public abstract long fixedDelay
Execute the annotated method with a fixed period in milliseconds between the end of the last invocation and the start of the next.只是说是 fixedRate 任务两次执行时间间隔是任务的开始点,而 fixedDelay 的间隔是前次任务的结束与下次任务的开始。
大致用示意字符串来表示如下(每个 T1, 或 T2 代表任务执行秒数(每次任务执行时间不定),假定 fixedRate 或 fixedDelay 的值是 5 秒,用 W 表示等待的数)
fixedRate: T1.T1WWWT2.T2.T2WW.T3.T3.T3.T3.T3.T4.T4.T4.T4.T4.T4.T4T5T5WWWT6.T6........
fixedDelay: T1.T1.WWWWW.T2.T2.T2WWWWW.T3.T3.T3.T3.T3.WWWWW.T4.T4.T4.T4.T4.T4.T4.WWWWWT6.T6......
一般来说能理解到上面两个场景已经差不多了,相比而言 fixedDelay 简单些,盯着上一次任务的屁股就行。 Read More