Container Extention Points

1.8.2 Customizing Configuration Metadata with a BeanFactoryPostProcessor

使用 BeanFactoryPostProcessor 来定制化配置元数据 (包含了 Bean 的定义)

BeanFactoryPostProcessor 操作的是 bean 的配置元数据,而不是 bean 实例对象,Spring IoC 容器允许 BeanFactoryPostProcessor 读取配置元数据并且有可能会在实例化任何 bean (除了它本身)之前改变这些 bean 的定义。

可以配置多个 BeanFactoryPostProcessor 实例, 并且通过实现 Ordered 接口,设置 order 属性来设置这些实例应用的顺序。

While it is technically possible to work with bean instances within a BeanFactoryPostProcessor (for example, by using BeanFactory.getBean()), doing so causes premature bean instantiation, violating the standard container lifecycle. This may cause negative side effects, such as bypassing bean post processing.

虽然在 BeanFactoryPostProcessor 中可以通过 BeanFactory.getBean() 获取 bean 实例,然后对其操作,但是这回导致 bean 的预实例化,违反了标准的容器生命周期。这可能带来一些副作用,如绕过 bean 的后置处理。

Implementation Example:

PropertySourcesPlaceholderConfigurer : 实现 bean 属性的值,可以定义在外部 properties 文件中

PropertyOverrideConfigurer : 事项 bean 的属性如果没有值的话,可以通过外部 properties 文件定义一个默认值

1
PropertySource

Scheduling

Cron Expressions

Cron expressions are comprised of 6 required fields and one optional field separated by white space. The fields respectively are described as follows:

Field Name Allowed Values Allowed Special Characters
Seconds 0-59 , - * /
Minutes 0-59 , - * /
Hours 0-23 , - * /
Day-of-month 1-31 , - * ? / L W
Month 0-11 or JAN-DEC , - * /
Day-of-Week 1-7 or SUN-SAT , - * ? / L #
Year (Optional) empty, 1970-2199 , - * /
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
The '*' character is used to specify all values. For example, "*" in the minute field means "every minute".

The '?' character is allowed for the day-of-month and day-of-week fields. It is used to specify 'no specific value'. This is useful when you need to specify something in one of the two fields, but not the other.

The '-' character is used to specify ranges For example "10-12" in the hour field means "the hours 10, 11 and 12".

The ',' character is used to specify additional values. For example "MON,WED,FRI" in the day-of-week field means "the days Monday, Wednesday, and Friday".

The '/' character is used to specify increments. For example "0/15" in the seconds field means "the seconds 0, 15, 30, and 45". And "5/15" in the seconds field means "the seconds 5, 20, 35, and 50". Specifying '*' before the '/' is equivalent to specifying 0 is the value to start with. Essentially, for each field in the expression, there is a set of numbers that can be turned on or off. For seconds and minutes, the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn on every "nth" value in the given set. Thus "7/6" in the month field only turns on month "7", it does NOT mean every 6th month, please note that subtlety.

The 'L' character is allowed for the day-of-month and day-of-week fields. This character is short-hand for "last", but it has different meaning in each of the two fields. For example, the value "L" in the day-of-month field means "the last day of the month" - day 31 for January, day 28 for February on non-leap years. If used in the day-of-week field by itself, it simply means "7" or "SAT". But if used in the day-of-week field after another value, it means "the last xxx day of the month" - for example "6L" means "the last friday of the month". You can also specify an offset from the last day of the month, such as "L-3" which would mean the third-to-last day of the calendar month. When using the 'L' option, it is important not to specify lists, or ranges of values, as you'll get confusing/unexpected results.

The 'W' character is allowed for the day-of-month field. This character is used to specify the weekday (Monday-Friday) nearest the given day. As an example, if you were to specify "15W" as the value for the day-of-month field, the meaning is: "the nearest weekday to the 15th of the month". So if the 15th is a Saturday, the trigger will fire on Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you specify "1W" as the value for day-of-month, and the 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not 'jump' over the boundary of a month's days. The 'W' character can only be specified when the day-of-month is a single day, not a range or list of days.

The 'L' and 'W' characters can also be combined for the day-of-month expression to yield 'LW', which translates to "last weekday of the month".

The '#' character is allowed for the day-of-week field. This character is used to specify "the nth" XXX day of the month. For example, the value of "6#3" in the day-of-week field means the third Friday of the month (day 6 = Friday and "#3" = the 3rd one in the month). Other examples: "2#1" = the first Monday of the month and "4#5" = the fifth Wednesday of the month. Note that if you specify "#5" and there is not 5 of the given day-of-week in the month, then no firing will occur that month. If the '#' character is used, there can only be one expression in the day-of-week field ("3#1,6#3" is not valid, since there are two expressions).

The legal characters and the names of months and days of the week are not case sensitive.
NOTES:
Support for specifying both a day-of-week and a day-of-month value is not complete (you'll need to use the '?' character in one of these fields).
Overflowing ranges is supported - that is, having a larger number on the left hand side than the right. You might do 22-2 to catch 10 o'clock at night until 2 o'clock in the morning, or you might have NOV-FEB. It is very important to note that overuse of overflowing ranges creates ranges that don't make sense and no effort has been made to determine which interpretation CronExpression chooses. An example would be "0 0 14-6 ? * FRI-MON".

MVC Note1

Root Application Context
As such, it typically contains middle-tier services, data sources, etc.

Servlet Application Context
As such, it typically contains controllers, view resolvers, locale resolvers, and other web-related beans.

Root Application Context 用来初始化 ContextLoaderListener 对象,并将该对象注册到 Servlet 容器中

Servlet Application Context 用来初始化 DispatcherServlet 对象,并将该对象注册到 Servlet 容器中,随后添加 Filters, 添加 Mapping 映射, 当然用户还可以通过 customizeRegistration(ServletRegistration.Dynamic registration) 来进行自定义之后的操作,如配置 Multipart 处理的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Delegate the ServletContext to any WebApplicationInitializer implementations present on the application classpath.
Because this class declares @HandlesTypes(WebApplicationInitializer.class), Servlet 3.0+ containers will automatically scan the classpath for implementations of Spring's WebApplicationInitializer interface and provide the set of all such types to the webAppInitializerClasses parameter of this method.

Assuming that one or more WebApplicationInitializer types are detected, they will be instantiated (and sorted if the @@Order annotation is present or the Ordered interface has been implemented). Then the WebApplicationInitializer.onStartup(ServletContext) method will be invoked on each instance, delegating the ServletContext such that each instance may register and configure servlets such as Spring's DispatcherServlet, listeners such as Spring's ContextLoaderListener, or any other Servlet API componentry such as filters.

/* Servlet 3.0+ 会自动在应用的 classpath 中寻找 WebApplicationInitializer 的实现类,并对每一个实现类进行实例化,将 ServletContext 委托给他们,对每一个实例调用 WebApplicationInitializer.onStartup(ServletContext), 加载和注册所有的 ServletListenerFilter */

If no WebApplicationInitializer implementations are found on the classpath, this method is effectively a no-op. An INFO-level log message will be issued notifying the user that the ServletContainerInitializer has indeed been invoked but that no WebApplicationInitializer implementations were found.


// org.springframework.web.SpringServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, @NotNull ServletContext servletContext) throws ServletException {

}
}
1
2
3
4
5
ServletContainerInitializer

SpringServletContainerInitializer

WebApplicationInitializer

Spring AOP

Spring提供了4种类型的AOP支持:

  • 基于代理的经典Spring AOP;
  • 纯POJO切面;
  • @AspectJ注解驱动的切面;
  • 注入式AspectJ切面(适用于Spring各版本)。

Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。

引入了简单的声明式AOP和基于注解的AOP之后,Spring经典的AOP看起来就显得非常笨重和过于复杂,直接使用 ProxyFactory Bean会让人感觉厌烦

Spring借鉴了AspectJ的切面,以提供注解驱动的AOP

方法拦截可以满足绝大部分的需求。如果需要方法拦截之外的连接点拦截功能,那么我们可以利用Aspect来补充Spring AOP的功能。

在Spring AOP中,要使用AspectJ的切点表达式语言来定义切点

《AspectJ in Action》第二版(Manning,2009,www.manning.com/laddad2/)。

<aop:aspectj-autoproxy>元素,它能够自动代理AspectJ注解的通知类

如下的XML代码片段与之前基于AspectJ的引入功能是相同:

1
2
3
4
5
6
7
<aop:aspect>
<aop:declare-parents
types-matching="concert.Performance+"
implement-interface="concert.Encoreable"
default-impl="concert.DefaultEncoreable"
/>
</aop:aspect>

<aop:declare-parents>声明了此切面所通知的bean要在它的对象层次结构中拥有新的父类型。具体到本例中,类型匹配Performance接口(由types-matching属性指定)的那些bean在父类结构中会增加Encoreable接口(由implement-interface属性指定)