Java 元注解及 Spring 组合注解应用

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 表示该注解能写在使用类型的任何语句中(如: 声明语句、泛型和强制转换语句中的类型) 阅读全文 >>

Spring ServiceLocator 介绍及应用

在 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(我们要定位就是它的实现类) 阅读全文 >>

Spring 中 FactoryBean 的使用

许久没记录笔记了,这回来重新熟悉一下 Spring 中 FactoryBean 的使用,顾名思义,它是用来获得相应 Bean 的工厂的。它与另一个 Spring 中的接口 BeanFactory 的作用不一样的,不能多说了。FactoryBean 和 BeanFactory 都是在 org.springframework.beans.factory 包中,谁能一看类名搞清楚它们的差别?

  1. FactoryBean: 用于创建某个特定的 Spring bean 的工厂类
  2. BeanFactory: Spring 上下文的最顶层接口,如 ApplicationContext 就继承了该接口,它可称之为所有 Spring bean 的工厂

这儿说的是第一个 FactoryBean, 它的接口声明是

它最终的效果是,Spring 容器中注册一个名称为 abcFactoryBean 的 AbcFactoryBean 实例,通后名称 abcFactoryBean 获得的实际上是相应 AbcFactoryBean.getObject() 返回的对象,类型为 getObjectType(), isSingleton() 是否是单例。 阅读全文 >>

Spring Boot 与 Logback 日志配置

本文记录 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 组件,而该组件又引入了以下几个依赖

  1. logback-classic:   依赖了 Slf4J
  2. jcl-over-slf4j
  3. jul-to-slf4j
  4. log4j-over-slf4j

相当于把其他的日志框架全桥接到了 Slf4J + Logback 上去了。 阅读全文 >>

Springfox 解决在单一资源操作多个方法实现时生成 Swagger 文档的问题

在命名本文的标题都敲打了几分钟时间,问题很简单,然而用简短的一个标题完全描述出来却有点费事。在 Spring MVC 项目结合 Springfox 来生成 Swagger API 文档时,如果一个资源操作因为请求参数的不同而映射到多个 controller 方法,那么 Swagger 可能只能生成某一个 API 条目,其余都被忽略。至于为什么说是 "可能", 可能正好未遵循命名规范而躲过了这一劫。由此引出

我们的问题

我们这里用了资源操作一词,它包含了两部分信息: 资源与操作,比如 /users/{userId} 是资源,而发生在其上的 HTTP 各种方法,如 POST, GET, PUT, DELETE 等就是操作。而 Spring MVC 中允许我们针对不同的查询参数把相同的资源操作映射到不同的 controller 方法上,也是为了保持逻辑上更为清晰。

比如下面的例子路由配置的例子

GET /users/{userId}     UserController.getUserInfo                                    //默认
GET /users/{userId}     UserController.getUserInfo                                   //当有 ?source=file 时
GET /users/{userId}     CloudUserController.getUserInfoFromCloud       //当有 ?source=cloud 时

看到上面资源与操作完全相同,仅仅因为 source 查询参数的不同而映射到三个 controller 方法。用代码体现如下图 阅读全文 >>

Spring MVC 项目中用 SpringFox 生成 Swagger2 API 文档

前面写过一篇如何建立 一个最基础的 Spring 4 MVC Maven 项目,现在是要在此基础上使用 SpringFox 来生成 Swagger 文档以及 SwaggerUI。Swagger 2 是基于 OpenAPI3.0 的实现,SpringFox 是用于在 Spring 项目中生成 Swagger 文档及 UI, 主要包括两个组件 springfox-swagger2 和  springfox-swagger-ui

本文是基于普通的 Spring MVC 项目的实例,而非针对 SpringBoot 项目,实际运用应该大同小异,无外乎 SpringBoot 中有许多自动配置项。并且本文也不涉及到 Swagger 众多注解的具体用法,具体请查看源代码或相关文档。

开始本文的示例,首先需要搭建起 Spring MVC 项目,具体做法请参考  一个最基础的 Spring 4 MVC Maven 项目

对应于两个组件 springfox-swagger2 和 springfox-swaager-ui 的应用,从大尺度上讲有两方面的内容,分别是

一:生成 Swagger 2 的 JSON API 文档

使用第一个组件 springfox-swagger2 能够让项目运行期自动生成 Swagger 2 的 JSON API 文档,分两步走

添加 springfox-swagger2 依赖

在项目的 pom.xml 文件中加入依赖配置

截止目前,springfox 官方已释放出 springfox-swagger2:2.9.0, 但是在 Maven 的中央仓库中只有 2.8.0 版,所就采用 2.8.0 版本 阅读全文 >>

一个最基础的 Spring 4 MVC Maven 项目

这是一个最基本的 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 静态文件或模板文件的。 阅读全文 >>

记录一下 Spring 如何扫描注解的 Bean 与资源

Spring 相关代码分析

本文通过对 Spring 的源代码来理解它是如何扫描 Bean 与资源的,因为自己有一个类似的需求,想把一堆的配置文件丢到 resources 下某个目录中,在程序启动的时候能加载它们。因为文件名是不一定的,所以不能直接指定文件名来加载,通过对 Spring 扫描资源的理解后,可以在自己的代码中手工扫描那些配置文件,以后有任何新的配置文件只需要扔到相应的配置目录即可。

下面以一个最简单的 Spring Boot 项目为例,调试并观察源代码

还是直奔主题吧,不一步一步的去探寻到底是哪个实现类去扫描资源的,用 Google 找到的是 ClassPathScanningCandidateComponentProvider, 因此直接在这个类的敏感位置上打上断点,比如它的构造函数 阅读全文 >>

Spring 下基于自定义注解拦截方法调用

其实很多年前就做过如此的实验,一翻开自己的日志有关于 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 注解的方法中随时可以获得进入方法的时间 阅读全文 >>

理解 Spring 定时任务的 fixedRate 和 fixedDelay 的区别

用过  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 简单些,盯着上一次任务的屁股就行。 阅读全文 >>