Ivan's Blog

虽日暮途远,仍梦想诗和远方


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

渔舟唱晚

发表于 2014-02-13 | 更新于 2020-11-14 | 分类于 山水古韵 | 评论数:
本文字数: 122

斜阳,氤氲了满脸的红光,恋恋地下山。

水湄边,圆晕,被斜打了正着。船上人家,在两岸“青山”中被烟笼着,羞涩地拉开了帷幕。

斜阳,抹红了天际。
远远有座小村庄,烟袅袅升起。外婆,就在那里面的灶头做饭,等着我。

渔舟唱晚,余音绕梁。

upload successful

阅读全文 »

源码解读——Jetty

发表于 2012-04-23 | 更新于 2020-11-14 | 分类于 源码解读 | 评论数:
本文字数: 2.4k

引言

Jetty相较于Tomcat更加轻便,虽然架构更加简单,但是看起来可并不轻松。Spring是设计初衷是用来管理应用中的实例Bean,因而是基于Bean的架构;Jetty则更倾向于流程和组件的管理,采用了基于handler的架构。handler的嵌套和链式结构,LifeCycle和doStart、doHandler模式无不印证了这点。

本文主要从基本架构、LifeCycle结构、Handler体系结构、Jetty启动过程、接受并处理请求的流程和与Tomcat的比较来简要介绍下Jetty,细节部分后面的博文会有分析。

阅读全文 »

关于 SELECT INTO

发表于 2012-04-17 | 更新于 2020-11-14 | 评论数:
本文字数: 674

跟 SQLServer 不同,MySQL不支持 SELECT INTO 语句直接备份表结构和数据,由于工作中的需要在网上找到一种方法可以代替, 也有其它方法可以处理,总结如下。

阅读全文 »

Spring AOP 切点表达式

发表于 2012-03-28 | 更新于 2020-11-14 | 评论数:
本文字数: 3.7k

1.概述

本文主要记录一下 Spring AOP 中 Pointcut 切点表达式。

关于 AOP 及切点相关的概念可以参考 Spring源码解读——AOP。

阅读全文 »

Spring源码解读——JDBC

发表于 2012-03-07 | 更新于 2020-11-14 | 分类于 源码解读 | 评论数:
本文字数: 9.7k

下面我们看看Spring JDBC相关的实现,在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数据库,在JdbcTemplate为用户程序提供了许多便利的数据库操作方法,比如查询,更新等,而且在Spring中,有许多类似 JdbcTemplate的模板,比如HibernateTemplate等等。

阅读全文 »

Spring源码解读——MVC

发表于 2012-03-02 | 更新于 2020-11-14 | 分类于 源码解读 | 评论数:
本文字数: 23k

下面我们对Spring MVC框架代码进行分析,对于webApplicationContext的相关分析可以参见以前的文档,我们这里着重分析Spring Web MVC框架的实现.我们从分析DispatcherServlet入手:

1
2
3
4
5
6
7
8
9
10
11
//这里是对DispatcherServlet的初始化方法,根据名字我们很方面的看到对各个Spring MVC主要元素的初始化  
protected void initFrameworkServlet() throws ServletException, BeansException {
initMultipartResolver();
initLocaleResolver();
initThemeResolver();
initHandlerMappings();
initHandlerAdapters();
initHandlerExceptionResolvers();
initRequestToViewNameTranslator();
initViewResolvers();
}

看到注解我们知道,这是DispatcherSerlvet的初始化过程,它是在WebApplicationContext已经存在的情况下进行的,也就意味着在初始化它的时候,IOC容器应该已经工作了,这也是我们在web.xml中配置Spring的时候,需要把DispatcherServlet的 load-on-startup的属性配置为2的原因。
对于具体的初始化过程,很容易理解,我们拿initHandlerMappings()来看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void initHandlerMappings() throws BeansException {  
if (this.detectAllHandlerMappings) {
// 这里找到所有在上下文中定义的HandlerMapping,同时把他们排序
// 因为在同一个上下文中可以有不止一个handlerMapping,所以我们把他们都载入到一个链里进行维护和管理
Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
getWebApplicationContext(), HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList(matchingBeans.values());
// 这里通过order属性来对handlerMapping来在list中排序
Collections.sort(this.handlerMappings, new OrderComparator());
}
}
else {
try {
Object hm = getWebApplicationContext().getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}

//如果在上下文中没有定义的话,那么我们使用默认的BeanNameUrlHandlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(HandlerMapping.class);
........
}
}

怎样获得上下文环境,可以参见我们前面的对IOC容器在web环境中加载的分析。 DispatcherServlet把定义了的所有HandlerMapping都加载了放在一个List里待以后进行使用,这个链的每一个元素都是一个handlerMapping的配置,而一般每一个handlerMapping可以持有一系列从URL请求到 Spring Controller的映射,比如SimpleUrl
HandlerMaaping中就定义了一个map来持有这一系列的映射关系。
DisptcherServlet通过HandlerMapping使得Web应用程序确定一个执行路径,就像我们在HanderMapping中看到的那样,HandlerMapping只是一个借口:

1
2
3
4
5
6
public interface HandlerMapping {  
public static final String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE =
Conventions.getQualifiedAttributeName(HandlerMapping.class, "pathWithinHandlerMapping");
//实际上维护一个HandlerExecutionChain,这是典型的Command的模式的使用,这个执行链里面维护handler和拦截器
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

他的具体实现只需要实现一个接口方法,而这个接口方法返回的是一个HandlerExecutionChain,实际上就是一个执行链,就像在Command模式描述的那样,这个类很简单,就是一个持有一个Interceptor链和一个Controller:

1
2
3
4
5
6
7
8
public class HandlerExecutionChain {  

private Object handler;

private HandlerInterceptor[] interceptors;

........
}

而这些Handler和Interceptor需要我们定义HandlerMapping的时候配置好,比如对具体的 SimpleURLHandlerMapping,他要做的就是根据URL映射的方式注册Handler和Interceptor,自己维护一个放映映射的handlerMap,当需要匹配Http请求的时候需要使用这个表里的信息来得到执行链。这个注册的过程在IOC容器初始化 SimpleUrlHandlerMapping的时候就被完成了,这样以后的解析才可以用到map里的映射信息,这里的信息和bean文件的信息是等价的,下面是具体的注册过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected void registerHandlers(Map urlMap) throws BeansException {  
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
//这里迭代在SimpleUrlHandlerMapping中定义的所有映射元素
Iterator it = urlMap.keySet().iterator();
while (it.hasNext()) {
//这里取得配置的url
String url = (String) it.next();
//这里根据url在bean定义中取得对应的handler
Object handler = urlMap.get(url);
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
//这里调用AbstractHandlerMapping中的注册过程
registerHandler(url, handler);
}
}
}

在AbstractMappingHandler中的注册代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {  
//试图从handlerMap中取handler,看看是否已经存在同样的Url映射关系
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
........
}

//如果是直接用bean名做映射那就直接从容器中取handler
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
handler = getApplicationContext().getBean(handlerName);
}
}
//或者使用默认的handler.
if (urlPath.equals("/*")) {
setDefaultHandler(handler);
}
else {
//把url和handler的对应关系放到handlerMap中去
this.handlerMap.put(urlPath, handler);
........
}
}

handlerMap是持有的一个HashMap,里面就保存了具体的映射信息:

1
private final Map handlerMap = new HashMap();

而SimpleUrlHandlerMapping对接口HandlerMapping的实现是这样的,这个getHandler根据在初始化的时候就得到的映射表来生成DispatcherServlet需要的执行链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {  
//这里根据request中的参数得到其对应的handler,具体处理在AbstractUrlHandlerMapping中
Object handler = getHandlerInternal(request);
//如果找不到对应的,就使用缺省的handler
if (handler == null) {
handler = this.defaultHandler;
}
//如果缺省的也没有,那就没办法了
if (handler == null) {
return null;
}
// 如果handler不是一个具体的handler,那我们还要到上下文中取
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
//生成一个HandlerExecutionChain,其中放了我们匹配上的handler和定义好的拦截器,就像我们在HandlerExecutionChain中看到的那样,它持有一个handler和一个拦截器组。
return new HandlerExecutionChain(handler, this.adaptedInterceptors);
}

我们看看具体的handler查找过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {  
//这里的HTTP Request传进来的参数进行分析,得到具体的路径信息。
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
.......//下面是根据请求信息的查找
return lookupHandler(lookupPath, request);
}

protected Object lookupHandler(String urlPath, HttpServletRequest request) {
// 如果能够直接能在SimpleUrlHandlerMapping的映射表中找到,那最好。
Object handler = this.handlerMap.get(urlPath);
if (handler == null) {
// 这里使用模式来对map中的所有handler进行匹配,调用了Jre中的Matcher类来完成匹配处理。
String bestPathMatch = null;
for (Iterator it = this.handlerMap.keySet().iterator(); it.hasNext();) {
String registeredPath = (String) it.next();
if (this.pathMatcher.match(registeredPath, urlPath) &&
(bestPathMatch == null || bestPathMatch.length() <= registeredPath.length())) {
//这里根据匹配路径找到最象的一个
handler = this.handlerMap.get(registeredPath);
bestPathMatch = registeredPath;
}
}

if (handler != null) {
exposePathWithinMapping(this.pathMatcher.extractPathWithinPattern(bestPathMatch, urlPath), request);
}
}
else {
exposePathWithinMapping(urlPath, request);
}
//
return handler;
}

我们可以看到,总是在handlerMap这个HashMap中找,当然如果直接找到最好,如果找不到,就看看是不是能通过Match Pattern的模式找,我们一定还记得在配置HnaderMapping的时候是可以通过ANT语法进行配置的,其中的处理就在这里。
这样可以清楚地看到整个HandlerMapping的初始化过程 - 同时,我们也看到了一个具体的handler映射是怎样被存储和查找的 - 这里生成一个ExecutionChain来储存我们找到的handler和在定义bean的时候定义的Interceptors.
让我们回到DispatcherServlet,初始化完成以后,实际的对web请求是在doService()方法中处理的,我们知道DispatcherServlet只是一个普通的Servlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {  
.......
//这里把属性信息进行保存
Map attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
logger.debug("Taking snapshot of request attributes before include");
attributesSnapshot = new HashMap();
Enumeration attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DispatcherServlet.class.getName())) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}

// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

try {
//这里使实际的处理入口
doDispatch(request, response);
}
finally {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}

我们看到,对于请求的处理实际上是让doDispatch()来完成的 - 这个方法很长,但是过程很简单明了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception {  
HttpServletRequest processedRequest = request;
//这是从handlerMapping中得到的执行链
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;

........
try {
//我们熟悉的ModelAndView开始出现了。
ModelAndView mv = null;
try {
processedRequest = checkMultipart(request);

// 这是我们得到handler的过程
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}

// 这里取出执行链中的Interceptor进行前处理
if (mappedHandler.getInterceptors() != null) {
for (int i = 0; i < mappedHandler.getInterceptors().length; i++) {
HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}

//在执行handler之前,用HandlerAdapter先检查一下handler的合法性:是不是按Spring的要求编写的。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

// 这里取出执行链中的Interceptor进行后处理
if (mappedHandler.getInterceptors() != null) {
for (int i = mappedHandler.getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
}

........

// Did the handler return a view to render?
//这里对视图生成进行处理
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
}
.......
}

我们很清楚的看到和MVC框架紧密相关的代码,比如如何得到和http请求相对应的执行链,怎样执行执行链和怎样把模型数据展现到视图中去。
先看怎样取得Command对象,对我们来说就是Handler - 下面是getHandler的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {  
//在ServletContext取得执行链 - 实际上第一次得到它的时候,我们把它放在ServletContext进行了缓存。
HandlerExecutionChain handler =
(HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
if (handler != null) {
if (!cache) {
request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
}
return handler;
}
//这里的迭代器迭代的时在initHandlerMapping中载入的上下文所有的HandlerMapping
Iterator it = this.handlerMappings.iterator();
while (it.hasNext()) {
HandlerMapping hm = (HandlerMapping) it.next();
.......
//这里是实际取得handler的过程,在每个HandlerMapping中建立的映射表进行检索得到请求对应的handler
handler = hm.getHandler(request);

//然后把handler存到ServletContext中去进行缓存
if (handler != null) {
if (cache) {
request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
}
return handler;
}
}
return null;
}

如果在ServletContext中可以取得handler则直接返回,实际上这个handler是缓冲了上次处理的结果 - 总要有第一次把这个handler放到ServletContext中去:
如果在ServletContext中找不到handler,那就通过持有的handlerMapping生成一个,我们看到它会迭代当前持有的所有的 handlerMapping,因为可以定义不止一个,他们在定义的时候也可以指定顺序,直到找到第一个,然后返回。先找到一个 handlerMapping,然后通过这个handlerMapping返回一个执行链,里面包含了最终的Handler和我们定义的一连串的 Interceptor。具体的我们可以参考上面的SimpleUrlHandlerMapping的代码分析知道getHandler是怎样得到一个 HandlerExecutionChain的。
得到HandlerExecutionChain以后,我们通过HandlerAdapter对这个Handler的合法性进行判断:

1
2
3
4
5
6
7
8
9
10
11
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {  
Iterator it = this.handlerAdapters.iterator();
while (it.hasNext()) {
//同样对持有的所有adapter进行匹配
HandlerAdapter ha = (HandlerAdapter) it.next();
if (ha.supports(handler)) {
return ha;
}
}
........
}

通过判断,我们知道这个handler是不是一个Controller接口的实现,比如对于具体的HandlerAdapter - SimpleControllerHandlerAdapter:

1
2
3
4
5
6
7
public class SimpleControllerHandlerAdapter implements HandlerAdapter {  

public boolean supports(Object handler) {
return (handler instanceof Controller);
}
.......
}

简单的判断一下handler是不是实现了Controller接口。这也体现了一种对配置文件进行验证的机制。
让我们再回到DispatcherServlet看到代码:

1
2
3
4
5
6
7
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  

这个就是对handle的具体调用!相当于Command模式里的Command.execute();理所当然的返回一个ModelAndView,下面就是一个对View进行处理的过程:
```java
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
}

调用的是render方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)  
throws Exception {response.setLocale(locale);

View view = null;
//这里把默认的视图放到ModelAndView中去。
if (!mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}

if (mv.isReference()) {
// 这里对视图名字进行解析
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
.......
}
else {
// 有可能在ModelAndView里已经直接包含了View对象,那我们就直接使用。
view = mv.getView();
........
}

//得到具体的View对象以后,我们用它来生成视图。
view.render(mv.getModelInternal(), request, response);
}

从整个过程我们看到先在ModelAndView中寻找视图的逻辑名,如果找不到那就使用缺省的视图,如果能够找到视图的名字,那就对他进行解析得到实际的需要使用的视图对象。还有一种可能就是在ModelAndView中已经包含了实际的视图对象,这个视图对象是可以直接使用的。
不管怎样,得到一个视图对象以后,通过调用视图对象的render来完成数据的显示过程,我们可以看看具体的JstlView是怎样实现的,我们在JstlView的抽象父类 AbstractView中找到render方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {  
......
// 这里把所有的相关信息都收集到一个Map里
Map mergedModel = new HashMap(this.staticAttributes.size() + (model != null ? model.size() : 0));
mergedModel.putAll(this.staticAttributes);
if (model != null) {
mergedModel.putAll(model);
}

// Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, mergedModel));
}
//这是实际的展现模型数据到视图的调用。
renderMergedOutputModel(mergedModel, request, response);
}

注解写的很清楚了,先把所有的数据模型进行整合放到一个Map - mergedModel里,然后调用renderMergedOutputModel();这个renderMergedOutputModel是一个模板方法,他的实现在InternalResourceView也就是JstlView的父类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected void renderMergedOutputModel(  
Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {

// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);

// Expose helpers as request attributes, if any.
exposeHelpers(request);

// 这里得到InternalResource定义的内部资源路径。
String dispatcherPath = prepareForRendering(request, response);

//这里把请求转发到前面得到的内部资源路径中去。
RequestDispatcher rd = request.getRequestDispatcher(dispatcherPath);
if (rd == null) {
throw new ServletException(
"Could not get RequestDispatcher for [" + getUrl() + "]: check that this file exists within your WAR");
}
.......

首先对模型数据进行处理,exposeModelAsRequestAttributes是在AbstractView中实现的,这个方法把 ModelAndView中的模型数据和其他request数据统统放到ServletContext当中去,这样整个模型数据就通过 ServletContext暴露并得到共享使用了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void exposeModelAsRequestAttributes(Map model, HttpServletRequest request) throws Exception {  
Iterator it = model.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
..........
String modelName = (String) entry.getKey();
Object modelValue = entry.getValue();
if (modelValue != null) {
request.setAttribute(modelName, modelValue);
...........
}
else {
request.removeAttribute(modelName);
.......
}
}
}

让我们回到数据处理部分的exposeHelper();这是一个模板方法,其实现在JstlView中实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class JstlView extends InternalResourceView {  

private MessageSource jstlAwareMessageSource;


protected void initApplicationContext() {
super.initApplicationContext();
this.jstlAwareMessageSource =
JstlUtils.getJstlAwareMessageSource(getServletContext(), getApplicationContext());
}

protected void exposeHelpers(HttpServletRequest request) throws Exception {
JstlUtils.exposeLocalizationContext(request, this.jstlAwareMessageSource);
}

}

在JstlUtils中包含了对于其他而言jstl特殊的数据处理和设置。
过程是不是很长?我们现在在哪里了?呵呵,我们刚刚完成的事MVC中View的render,对于InternalResourceView的render 过程比较简单只是完成一个资源的重定向处理。需要做的就是得到实际view的internalResource路径,然后转发到那个资源中去。怎样得到资源的路径呢通过调用:

1
2
3
4
protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)  
throws Exception {

return getUrl();

那这个url在哪里生成呢?我们在View相关的代码中没有找到,实际上,他在ViewRosolve的时候就生成了,在UrlBasedViewResolver中:

1
2
3
4
5
6
7
8
9
10
11
protected AbstractUrlBasedView buildView(String viewName) throws Exception {  
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
return view;
}

这里是生成View的地方,自然也把生成的url和其他一些和view相关的属性也配置好了。
那这个ViewResolve是什么时候被调用的呢?哈哈,我们这样又要回到DispatcherServlet中去看看究竟,在DispatcherServlet中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)  
throws Exception {

........
View view = null;

// 这里设置视图名为默认的名字
if (!mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}

if (mv.isReference()) {
//这里对视图名进行解析,在解析的过程中根据需要生成实际需要的视图对象。
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
..........
}
......
}

下面是对视图名进行解析的具体过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected View resolveViewName(String viewName, Map model, Locale locale, HttpServletRequest request)  
throws Exception {
//我们有可能不止一个视图解析器
for (Iterator it = this.viewResolvers.iterator(); it.hasNext();) {
ViewResolver viewResolver = (ViewResolver) it.next();
//这里是视图解析器进行解析并生成视图的过程。
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}

这里调用具体的ViewResolver对视图的名字进行解析 - 除了单纯的解析之外,它还根据我们的要求生成了我们实际需要的视图对象。具体的viewResolver在bean定义文件中进行定义同时在 initViewResolver()方法中被初始化到viewResolver变量中,我们看看具体的 InternalResourceViewResolver是怎样对视图名进行处理的并生成V视图对象的:对resolveViewName的调用模板在 AbstractCachingViewResolver中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public View resolveViewName(String viewName, Locale locale) throws Exception {  
//如果没有打开缓存设置,那创建需要的视图
if (!isCache()) {
logger.warn("View caching is SWITCHED OFF -- DEVELOPMENT SETTING ONLY: This can severely impair performance");
return createView(viewName, locale);
}
else {
Object cacheKey = getCacheKey(viewName, locale);
// No synchronization, as we can live with occasional double caching.
synchronized (this.viewCache) {
//这里查找缓存里的视图对象
View view = (View) this.viewCache.get(cacheKey);
if (view == null) {
//如果在缓存中没有找到,创建一个并把创建的放到缓存中去
view = createView(viewName, locale);
this.viewCache.put(cacheKey, view);
........
}
return view;
}
}
}

关于这些createView(),loadView(),buildView()的关系,我们看看Eclipse里的call hiearchy
然后我们回到view.render中完成数据的最终对httpResponse的写入,比如在AbstractExcelView中的实现:

1
2
3
4
5
6
7
8
9
protected final void renderMergedOutputModel(  
Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
.........
// response.setContentLength(workbook.getBytes().length);
response.setContentType(getContentType());
ServletOutputStream out = response.getOutputStream();
workbook.write(out);
out.flush();
}

这样就和我们前面的分析一致起来了:DispatcherServlet在解析视图名的时候就根据要求生成了视图对象,包括在InternalResourceView中需要使用的url和其他各种和HTTP response相关的属性都会写保持在生成的视图对象中,然后就直接调用视图对象的render来完成数据的展示。
这就是整个Spring Web MVC框架的大致流程,整个MVC流程由DispatcherServlet来控制。MVC的关键过程包括:
配置到handler的映射关系和怎样根据请求参数得到对应的handler,在Spring中,这是由handlerMapping通过执行链来完成的,而具体的映射关系我们在bean定义文件中定义并在HandlerMapping载入上下文的时候就被配置好了。然后 DispatcherServlet调用HandlerMapping来得到对应的执行链,最后通过视图来展现模型数据,但我们要注意的是视图对象是在解析视图名的时候生成配置好的。这些作为核心类的HanderMapping,ViewResolver,View,Handler的紧密协作实现了MVC的功能。

Spring源码解读——声明式事务处理

发表于 2012-02-26 | 更新于 2020-11-14 | 分类于 源码解读 | 评论数:
本文字数: 19k

我们看看Spring中的事务处理的代码,使用Spring管理事务有声明式和编程式两种方式,声明式事务处理通过AOP的实现把事物管理代码作为方面封装来横向插入到业务代码中,使得事务管理代码和业务代码解藕。在这种方式我们结合IoC容器和Spirng已有的FactoryBean来对事务管理进行属性配置,比如传播行为,隔离级别等。其中最简单的方式就是通过配置TransactionProxyFactoryBean来实现声明式事物;

阅读全文 »

Spring源码解读——AOP

发表于 2012-02-25 | 更新于 2020-11-14 | 分类于 源码解读 | 评论数:
本文字数: 9.3k

一、基本知识

AOP基本概念

Aspect-Oriented Programming 面向切面编程的简称,Aspect是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting concern),从关注点中分离出横切关注点是面向方面程序设计的核心所在。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过方面来封装、维护,这样原本分散在整个应用程序中的变动就可以很好地管理起来。

AOP和OOP的区别

面向方面编程AOP和面向对象编程OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。

OOP针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。

而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

AOP常用的技术以及实现

常用的AOP技术有:

  • AspectJ:源代码和字节码级别的方面编织器,用户需要使用不同于Java的新语言。
  • AspectWerkz:AOP框架,使用字节码动态编织器和XML配置。
  • JBoss-AOP:基于拦截器和元数据的AOP框架,运行在JBoss应用服务器上。

AOP中使用的一些实现技术有:

  • BCEL:Byte-Code Engineering Library,Java字节码操作类库。
  • CGLIB:CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
  • Javassist:Java字节码操作类库,JBoss的一个子项目。

面向方面编程(AOP)的常用术语

  • 切面Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些切入点Pointcut 以及对切入点进行相应的操作的通知Advice。
  • 连接点Joint point:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它连接点jointpoint。
  • 切入点Pointcut:表示一组连接点jointpoint,这些连接点或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的操作处理通知Advice将要发生的地方。
  • 通知Advice:Advice 定义了在切入点pointcut 里面定义的程序点具体要做的操作和处理,它通过 before、after 和 around 来区别是在每个切入点之前、之后还是代替执行的代码。
  • 目标对象Target:代理的目标对象,即切面要被应用到的目标对象。
  • 织入Weave:指将切面应用到目标对象,并导致代理对象创建的过程。

二、Advice通知

Advice通知是AOP联盟定义的一个接口,定义当拦截到连接点做相应的处理操作,为切面增强提供织入接口。在spring AOP中,通知主要描述Spring AOP围绕方法调用而注入切面的行为,Spring AOP的通知扩展了AOP联盟的通知接口,提供了前置通知 BeforeAdvice、后置通知 AfterReturningAdvice 、最终通知 AfterAdvice 和例外通知 ThrowsAdvice等。

AOP联盟定义的接口都只是类似一个标记,的接口方法还是要视具体的连接点的情况而定,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Advice {

}

public interface BeforeAdvice extends Advice {

}

public interface AfterAdvice extends Advice {

}

public interface ThrowsAdvice extends AfterAdvice {

}

可以看到,都只有接口,而没有具体方法的定义,而真正要实现的方法都在具体的连接器指定后才有,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 前置增强接口,使用这个前置接口需要实现一个回调函数
public interface MethodBeforeAdvice extends BeforeAdvice {

/**
* 作为回调函数,该方法的实现在Advice中被配置到目标方法后,会调用目标方法时被回调
* @param method Method对象,是目标方法的反射对象
* @param args 对象数组,包含目标方法的输入参数
* @param target
* @throws Throwable
*/
void before(Method method, Object[] args, Object target) throws Throwable;

}

public interface AfterReturningAdvice extends AfterAdvice {
/**
* 作为回调函数,该方法的实现在Advice中被配置到目标方法返回后,对目标返回结果进行增强
* @param returnValue 返回值
* @param method Method对象,是目标方法的反射对象
* @param args 对象数组,包含目标方法的输入参数
* @param target
* @throws Throwable
*/
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;

}

三、Pointcut切点

Pointcut切入点决定通知Advice应该作用于哪个连接点,即通过Pointcut切入点来定义目标对象中需要使用AOP增强的方法集合,这些集合的选取可以按照一定的规则来完成。

Pointcut通常意味着标识方法,这些需要增强的方法可以被某个正则表达式进行标识,或者根据指定方法名进行匹配等。下面是Pointcut设计:

Pointcut

Pointcut切入点源码

1
2
3
4
5
6
7
8
9
10
public interface Pointcut {  
//获取类过滤器
ClassFilter getClassFilter();

//获取匹配切入点的方法
MethodMatcher getMethodMatcher();

//总匹配的标准切入点实例
Pointcut TRUE = TruePointcut.INSTANCE;
}

在Pointcut的基本接口定义中可以看到,需要返回一个MethodMatcher。对于Point的匹配判断功能,具体是由这个返回的MethodMatcher来完成的,也就是说,由这个MethodMatcher来判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好Advice通知。

实现原理

在Pointcut的类继承关系中,以正则表达式切点JdkRegexpMethodPointcut的实现原理为例,来具体了解切点Pointcut的工作原理。该类完成通过正则表达式对方法名进行匹配的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut {  
//要编译的正则表达式模式
private Pattern[] compiledPatterns = new Pattern[0];
//编译时要排除的正则表达式模式
private Pattern[] compiledExclusionPatterns = new Pattern[0];

//将给定的模式字符串数组初始化为编译的正则表达式模式
protected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException {
this.compiledPatterns = compilePatterns(patterns);
}

//将给定的模式字符串数组初始化为编译时要排除的正则表达式模式
protected void initExcludedPatternRepresentation(String[] excludedPatterns) throws PatternSyntaxException {
this.compiledExclusionPatterns = compilePatterns(excludedPatterns);
}

//使用正则表达式匹配给定的名称
protected boolean matches(String pattern, int patternIndex) {
Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
return matcher.matches();
}

//使用要排除的正则表达式匹配给定的名称
protected boolean matchesExclusion(String candidate, int patternIndex) {
Matcher matcher = this.compiledExclusionPatterns[patternIndex].matcher(candidate);
return matcher.matches();
}

//将给定的字符串数组编译为正则表达的模式
private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException {
Pattern[] destination = new Pattern[source.length];
for (int i = 0; i < source.length; i++) {
destination[i] = Pattern.compile(source[i]);
}
return destination;
}
}

从上面的源码分析中,我们可以看到,最简单的使用正则表达式匹配的Pointcut切入点基本功能就是根据正则表达式判断方法名等是否匹配。

四、Advisor通知器:

当完成对目标对象方法的增强行为操作 Advice 和切入点 Point 的设计开发之后,需要一个对象将目标对象、增强行为和切入点三者结合起来,通知器Advisor就是一个实现这个功能的对象,即通过Advisor通知器,可以定义那些目标对象的那些方法在什么地方使用这些增加的行为。

Advisor通知器定义

Advisor通知器的源码如下:

1
2
3
4
5
6
7
public interface Advisor {  
//获取切面的通知Advice
Advice getAdvice();

//判断这个通知是否和某个特定的实例对象相关
boolean isPerInstance();
}

实现原理

查看Advisor通知器的继承体系,发现Advisor的实现类很多,我们以最常用的DefaultPointcutAdvisor为例,分析通知器的工作原理。

  1. DefaultPointcutAdvisor源码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {  
    //默认切入点 — - - ——>
    //Pointcut.TRUE在切入点中的定义为:Pointcut TRUE = TruePointcut.INSTANCE;
    private Pointcut pointcut = Pointcut.TRUE;

    //无参构造方法,创建一个空的通知器
    public DefaultPointcutAdvisor() {
    }

    //创建一个匹配所有方法的通知器
    public DefaultPointcutAdvisor(Advice advice) {
    this(Pointcut.TRUE, advice);
    }

    //创建一个指定切入点和通知的通知器
    public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
    this.pointcut = pointcut;
    setAdvice(advice);
    }

    //为通知设置切入点
    public void setPointcut(Pointcut pointcut) {
    this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
    }

    //获取切入点
    public Pointcut getPointcut() {
    return this.pointcut;
    }
    public String toString() {
    return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
    }
    }
  1. 其中默认切入点为TruePointcut,主要功能是配置默认的类过滤器和方法匹配器,即定义Spring AOP对于哪些类的哪些方法其作用,源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    class TruePointcut implements Pointcut, Serializable {  
    //INSTANCE是TruePointcut类的一个常量单件,即整个应用中只有这个一个,
    //不会创建第二个实例对象,确保该实例对象的唯一性,单例模型
    public static final TruePointcut INSTANCE = new TruePointcut();

    //单态模式构造方法
    private TruePointcut() {
    }

    //获取切入点的类过滤器
    public ClassFilter getClassFilter() {
    return ClassFilter.TRUE;
    }
    //获取切入点的方法匹配器
    public MethodMatcher getMethodMatcher() {
    return MethodMatcher.TRUE; // ————>
    }

    //获取单态模式对象的方法
    private Object readResolve() {
    return INSTANCE;
    }
    public String toString() {
    return "Pointcut.TRUE";
    }
    }

    //———————————————
    public interface MethodMatcher {

    boolean matches(Method method, Class<?> targetClass);

    boolean isRuntime();

    boolean matches(Method method, Class<?> targetClass, Object... args);

    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

    }

    从TruePointcut的源码我们看到,切入点使用TrueClassFilter作为类过滤器,匹配任意的类; 使用TrueMethodMatcher作为方法匹配器,匹配任意的方法。下面我们继续分析TrueClassFilter类过滤器和TrueMethodMatcher方法匹配器。

  2. TrueClassFilter作为默认切入点的默认类过滤器,主要告诉切入点对哪些类进行增强,源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class TrueClassFilter implements ClassFilter, Serializable {  
    //单态模式
    public static final TrueClassFilter INSTANCE = new TrueClassFilter();

    //单态模式的构造方法
    private TrueClassFilter() {
    }

    //切入点过滤匹配类的方法,默认对所有的类都增强
    public boolean matches(Class clazz) {
    return true;
    }

    //获取单态模式对象的方法
    private Object readResolve() {
    return INSTANCE;
    }
    public String toString() {
    return "ClassFilter.TRUE";
    }
    }
  1. TrueMethodMatcher作为默认切入点的默认方法匹配器,主要告诉切入点对哪些方法进行增强,源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    class TrueMethodMatcher implements MethodMatcher, Serializable {  
    //单态模式
    public static final TrueMethodMatcher INSTANCE = new TrueMethodMatcher();
    //单态模式构造方法
    private TrueMethodMatcher() {
    }

    //不支持运行时调用
    public boolean isRuntime() {
    return false;
    }

    //切入点匹配方法时调用,默认匹配所有的方法
    public boolean matches(Method method, Class targetClass) {
    return true;
    }

    //运行时调用将抛出异常
    public boolean matches(Method method, Class targetClass, Object[] args) {
    throw new UnsupportedOperationException();
    }

    //获取单态模式对象的方法
    private Object readResolve() {
    return INSTANCE;
    }
    public String toString() {
    return "MethodMatcher.TRUE";
    }
    }

    从上面方法匹配器的源码,我们可以看出,切入点对方法进行匹配时不支持运行时的匹配,如果在运行时进行匹配将抛出异常。

Spring源码解读——BeanFactory

发表于 2012-02-24 | 更新于 2020-11-14 | 评论数:
本文字数: 18k

在Spring中,IOC容器的重要地位我们就不多说了,对于Spring的使用者而言,IOC容器实际上是什么呢?我们可以说BeanFactory就是我们看到的IoC容器,当然了Spring为我们准备了许多种IoC容器来使用,这样可以方便我们从不同的层面,不同的资源位置,不同的形式的定义信息来建立我们需要的IoC容器。
在Spring中,最基本的IOC容器接口是BeanFactory - 这个接口为具体的IOC容器的实现作了最基本的功能规定 - 不管怎么着,作为IOC容器,这些接口你必须要满足应用程序的最基本要求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface BeanFactory {  

//这里是对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象,
//如果需要得到工厂本身,需要转义
String FACTORY_BEAN_PREFIX = "&";


//这里根据bean的名字,在IOC容器中得到bean实例,这个IOC容器就是一个大的抽象工厂。
Object getBean(String name) throws BeansException;

//这里根据bean的名字和Class类型来得到bean实例,和上面的方法不同在于它会抛出异常:如果根据名字取得的bean实例的Class类型和需要的不同的话。
Object getBean(String name, Class requiredType) throws BeansException;

//这里提供对bean的检索,看看是否在IOC容器有这个名字的bean
boolean containsBean(String name);

//这里根据bean名字得到bean实例,并同时判断这个bean是不是单件
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

//这里对得到bean实例的Class类型
Class getType(String name) throws NoSuchBeanDefinitionException;

//这里得到bean的别名,如果根据别名检索,那么其原名也会被检索出来
String[] getAliases(String name);

}

在BeanFactory里只对IOC容器的基本行为作了定义,根本不关心你的bean是怎样定义怎样加载的 - 就像我们只关心从这个工厂里我们得到到什么产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心这些。如果要关心工厂是怎样产生对象的,应用程序需要使用具体的IOC容器实现- 当然你可以自己根据这个BeanFactory来实现自己的IOC容器,但这个没有必要,因为Spring已经为我们准备好了一系列工厂来让我们使用。比如XmlBeanFactory就是针对最基础的BeanFactory的IOC容器的实现 - 这个实现使用xml来定义IOC容器中的bean。
Spring提供了一个BeanFactory的基本实现,XmlBeanFactory同样的通过使用模板模式来得到对IOC容器的抽象- AbstractBeanFactory,DefaultListableBeanFactory这些抽象类为其提供模板服务。其中通过resource 接口来抽象bean定义数据,对Xml定义文件的解析通过委托给XmlBeanDefinitionReader来完成。下面我们根据书上的例子,简单的演示IOC容器的创建过程:

1
2
3
4
ClassPathResource res = new ClassPathResource("beans.xml");  
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);

这些代码演示了以下几个步骤:

  1. 创建IOC配置文件的抽象资源
  2. 创建一个BeanFactory
  3. 把读取配置信息的BeanDefinitionReader,这里是XmlBeanDefinitionReader配置给BeanFactory
  4. 从定义好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader来完成,这样完成整个载入bean定义的过程。我们的IoC容器就建立起来了。在BeanFactory的源代码中我们可以看到:
1
2
3
4
5
6
7
8
9
10
11
public class XmlBeanFactory extends DefaultListableBeanFactory {  
//这里为容器定义了一个默认使用的bean定义读取器
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
//在初始化函数中使用读取器来对资源进行读取,得到bean定义信息。
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}

我们在后面会看到读取器读取资源和注册bean定义信息的整个过程,基本上是和上下文的处理是一样的,从这里我们可以看到上下文和 XmlBeanFactory这两种IOC容器的区别,BeanFactory往往不具备对资源定义的能力,而上下文可以自己完成资源定义,从这个角度上看上下文更好用一些。
仔细分析Spring BeanFactory的结构,我们来看看在BeanFactory基础上扩展出的ApplicationContext - 我们最常使用的上下文。除了具备BeanFactory的全部能力,上下文为应用程序又增添了许多便利:

1
2
3
* 可以支持不同的信息源,我们看到ApplicationContext扩展了MessageSource 
* 访问资源 , 体现在对ResourceLoader和Resource的支持上面,这样我们可以从不同地方得到bean定义资源
* 支持应用事件,继承了接口ApplicationEventPublisher,这样在上下文中引入了事件机制而BeanFactory是没有的。

ApplicationContext允许上下文嵌套 - 通过保持父上下文可以维持一个上下文体系 - 这个体系我们在以后对Web容器中的上下文环境的分析中可以清楚地看到。对于bean的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的Spring应用提供了一个共享的bean定义环境。这个我们在分析Web容器中的上下文环境时也能看到。
ApplicationContext提供IoC容器的主要接口,在其体系中有许多抽象子类比如AbstractApplicationContext为具体的BeanFactory的实现,比如FileSystemXmlApplicationContext和 ClassPathXmlApplicationContext提供上下文的模板,使得他们只需要关心具体的资源定位问题。当应用程序代码实例化 FileSystemXmlApplicationContext的时候,得到IoC容器的一种具体表现 - ApplicationContext,从而应用程序通过ApplicationContext来管理对bean的操作。
BeanFactory 是一个接口,在实际应用中我们一般使用ApplicationContext来使用IOC容器,它们也是IOC容器展现给应用开发者的使用接口。对应用程序开发者来说,可以认为BeanFactory和ApplicationFactory在不同的使用层面上代表了SPRING提供的IOC容器服务。
下面我们具体看看通过FileSystemXmlApplicationContext是怎样建立起IOC容器的, 显而易见我们可以通过new来得到IoC容器:

1
ApplicationContext = new FileSystemXmlApplicationContext(xmlPath);

调用的是它初始化代码:

1
2
3
4
5
6
7
8
9
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)  
throws BeansException {
super(parent);
this.configLocations = configLocations;
if (refresh) {
 //这里是IoC容器的初始化过程,其初始化过程的大致步骤由AbstractApplicationContext来定义
refresh();
}
}

refresh的模板在AbstractApplicationContext:

1
2
3
4
5
6
7
8
9
10
public void refresh() throws BeansException, IllegalStateException {  
synchronized (this.startupShutdownMonitor) {
synchronized (this.activeMonitor) {
this.active = true;
}

// 这里需要子类来协助完成资源位置定义,bean载入和向IOC容器注册的过程
refreshBeanFactory();
............
}

这个方法包含了整个BeanFactory初始化的过程,对于特定的FileSystemXmlBeanFactory,我们看到定位资源位置由refreshBeanFactory()来实现:
在AbstractXmlApplicationContext中定义了对资源的读取过程,默认由XmlBeanDefinitionReader来读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {  
// 这里使用XMLBeanDefinitionReader来载入bean定义信息的XML文件
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

//这里配置reader的环境,其中ResourceLoader是我们用来定位bean定义信息资源位置的
///因为上下文本身实现了ResourceLoader接口,所以可以直接把上下文作为ResourceLoader传递给XmlBeanDefinitionReader
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

initBeanDefinitionReader(beanDefinitionReader);
//这里转到定义好的XmlBeanDefinitionReader中对载入bean信息进行处理
loadBeanDefinitions(beanDefinitionReader);
}

转到beanDefinitionReader中进行处理:

1
2
3
4
5
6
7
8
9
10
11
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {  
Resource[] configResources = getConfigResources();
if (configResources != null) {
//调用XmlBeanDefinitionReader来载入bean定义信息。
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}

而在作为其抽象父类的AbstractBeanDefinitionReader中来定义载入过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {  
//这里得到当前定义的ResourceLoader,默认的我们使用DefaultResourceLoader
ResourceLoader resourceLoader = getResourceLoader();
.........//如果没有找到我们需要的ResourceLoader,直接抛出异常
if (resourceLoader instanceof ResourcePatternResolver) {
// 这里处理我们在定义位置时使用的各种pattern,需要ResourcePatternResolver来完成
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
return loadCount;
}
........
}
else {
// 这里通过ResourceLoader来完成位置定位
Resource resource = resourceLoader.getResource(location);
// 这里已经把一个位置定义转化为Resource接口,可以供XmlBeanDefinitionReader来使用了
int loadCount = loadBeanDefinitions(resource);
return loadCount;
}
}

当我们通过ResourceLoader来载入资源,别忘了了我们的GenericApplicationContext也实现了ResourceLoader接口:

1
2
3
4
5
6
7
8
9
10
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {  
public Resource getResource(String location) {
//这里调用当前的loader也就是DefaultResourceLoader来完成载入
if (this.resourceLoader != null) {
return this.resourceLoader.getResource(location);
}
return super.getResource(location);
}
.......
}

而我们的FileSystemXmlApplicationContext就是一个DefaultResourceLoader - GenericApplicationContext()通过DefaultResourceLoader:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Resource getResource(String location) {  
//如果是类路径的方式,那需要使用ClassPathResource来得到bean文件的资源对象
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 如果是URL方式,使用UrlResource作为bean文件的资源对象
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// 如果都不是,那我们只能委托给子类由子类来决定使用什么样的资源对象了
return getResourceByPath(location);
}
}
}

我们的FileSystemXmlApplicationContext本身就是是DefaultResourceLoader的实现类,他实现了以下的接口:

1
2
3
4
5
6
7
protected Resource getResourceByPath(String path) {  
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
//这里使用文件系统资源对象来定义bean文件
return new FileSystemResource(path);
}

这样代码就回到了FileSystemXmlApplicationContext中来,他提供了FileSystemResource来完成从文件系统得到配置文件的资源定义。这样,就可以从文件系统路径上对IOC配置文件进行加载 - 当然我们可以按照这个逻辑从任何地方加载,在Spring中我们看到它提供的各种资源抽象,比如ClassPathResource, URLResource,FileSystemResource等来供我们使用。上面我们看到的是定位Resource的一个过程,而这只是加载过程的一部分 - 我们回到AbstractBeanDefinitionReaderz中的loadDefinitions(resource)来看看得到代表bean文件的资源定义以后的载入过程,默认的我们使用XmlBeanDefinitionReader:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {  
.......
try {
//这里通过Resource得到InputStream的IO流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//从InputStream中得到XML的解析源
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//这里是具体的解析和注册过程
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
//关闭从Resource中得到的IO流
inputStream.close();
}
}
.........
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
//通过解析得到DOM,然后完成bean在IOC容器中的注册
Document doc = this.documentLoader.loadDocument(
inputSource, this.entityResolver, this.errorHandler, validationMode, this.namespaceAware);
return registerBeanDefinitions(doc, resource);
}
.......
}

我们看到先把定义文件解析为DOM对象,然后进行具体的注册过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {  
// 这里定义解析器,使用XmlBeanDefinitionParser来解析xml方式的bean定义文件 - 现在的版本不用这个解析器了,使用的是XmlBeanDefinitionReader
if (this.parserClass != null) {
XmlBeanDefinitionParser parser =
(XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass);
return parser.registerBeanDefinitions(this, doc, resource);
}
// 具体的注册过程,首先得到XmlBeanDefinitionReader,来处理xml的bean定义文件
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getBeanFactory().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getBeanFactory().getBeanDefinitionCount() - countBefore;
}

具体的在BeanDefinitionDocumentReader中完成对,下面是一个简要的注册过程来完成bean定义文件的解析和IOC容器中bean的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {  
this.readerContext = readerContext;

logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();

BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);

preProcessXml(root);
parseBeanDefinitions(root, delegate);
postProcessXml(root);
}

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root.getNamespaceURI())) {
//这里得到xml文件的子节点,比如各个bean节点
NodeList nl = root.getChildNodes();

//这里对每个节点进行分析处理
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
String namespaceUri = ele.getNamespaceURI();
if (delegate.isDefaultNamespace(namespaceUri)) {
//这里是解析过程的调用,对缺省的元素进行分析比如bean元素
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//这里对元素Import进行处理
if (DomUtils.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (DomUtils.nodeNameEquals(ele, ALIAS_ELEMENT)) {
String name = ele.getAttribute(NAME_ATTRIBUTE);
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
getReaderContext().getReader().getBeanFactory().registerAlias(name, alias);
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
//这里对我们最熟悉的bean元素进行处理
else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) {
//委托给BeanDefinitionParserDelegate来完成对bean元素的处理,这个类包含了具体的bean解析的过程。
// 把解析bean文件得到的信息放到BeanDefinition里,他是bean信息的主要载体,也是IOC容器的管理对象。
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
// 这里是向IOC容器注册,实际上是放到IOC容器的一个map里
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());

// 这里向IOC容器发送事件,表示解析和注册完成。
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
}

我们看到在parseBeanDefinition中对具体bean元素的解析式交给BeanDefinitionParserDelegate来完成的,下面我们看看解析完的bean是怎样在IOC容器中注册的:
在BeanDefinitionReaderUtils调用的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void registerBeanDefinition(  
BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory) throws BeansException {

// 这里得到需要注册bean的名字;
String beanName = bdHolder.getBeanName();
//这是调用IOC来注册的bean的过程,需要得到BeanDefinition
beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition());

// 别名也是可以通过IOC容器和bean联系起来的进行注册
String[] aliases = bdHolder.getAliases();
if (aliases != null) {
for (int i = 0; i < aliases.length; i++) {
beanFactory.registerAlias(beanName, aliases[i]);
}
}
}

我们看看XmlBeanFactory中的注册实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//---------------------------------------------------------------------  
// 这里是IOC容器对BeanDefinitionRegistry接口的实现
//---------------------------------------------------------------------

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {

.....//这里省略了对BeanDefinition的验证过程
//先看看在容器里是不是已经有了同名的bean,如果有抛出异常。
Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
...........
}
else {
//把bean的名字加到IOC容器中去
this.beanDefinitionNames.add(beanName);
}
//这里把bean的名字和Bean定义联系起来放到一个HashMap中去,IOC容器通过这个Map来维护容器里的Bean定义信息。
this.beanDefinitionMap.put(beanName, beanDefinition);
removeSingleton(beanName);
}

这样就完成了Bean定义在IOC容器中的注册,就可被IOC容器进行管理和使用了。
从上面的代码来看,我们总结一下IOC容器初始化的基本步骤:

  • 初始化的入口在容器实现中的refresh()调用来完成
  • 对bean 定义载入IOC容器使用的方法是loadBeanDefinition,其中的大致过程如下:通过ResourceLoader来完成资源文件位置的定位,DefaultResourceLoader是默认的实现,同时上下文本身就给出了ResourceLoader的实现,可以从类路径,文件系统, URL等方式来定为资源位置。如果是XmlBeanFactory作为IOC容器,那么需要为它指定bean定义的资源,也就是说bean定义文件时通过抽象成Resource来被IOC容器处理的,容器通过BeanDefinitionReader来完成定义信息的解析和Bean信息的注册,往往使用的是XmlBeanDefinitionReader来解析bean的xml定义文件 - 实际的处理过程是委托给BeanDefinitionParserDelegate来完成的,从而得到bean的定义信息,这些信息在Spring中使用BeanDefinition对象来表示 - 这个名字可以让我们想到loadBeanDefinition,RegisterBeanDefinition这些相关的方法 - 他们都是为处理BeanDefinitin服务的,IoC容器解析得到BeanDefinition以后,需要把它在IOC容器中注册,这由IOC实现 BeanDefinitionRegistry接口来实现。注册过程就是在IOC容器内部维护的一个HashMap来保存得到的 BeanDefinition的过程。这个HashMap是IoC容器持有bean信息的场所,以后对bean的操作都是围绕这个HashMap来实现的。
  • 然后我们就可以通过BeanFactory和ApplicationContext来享受到Spring IOC的服务了.

在使用IOC容器的时候,我们注意到除了少量粘合代码,绝大多数以正确IoC风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。 Spring本身提供了对声明式载入web应用程序用法的应用程序上下文,并将其存储在ServletContext中的框架实现。具体可以参见以后的文章。
在使用Spring IOC容器的时候我们还需要区别两个概念:
Beanfactory 和Factory bean,其中BeanFactory指的是IOC容器的编程抽象,比如ApplicationContext, XmlBeanFactory等,这些都是IOC容器的具体表现,需要使用什么样的容器由客户决定但Spring为我们提供了丰富的选择。而 FactoryBean只是一个可以在IOC容器中被管理的一个bean,是对各种处理过程和资源使用的抽象,Factory bean在需要时产生另一个对象,而不返回FactoryBean本省,我们可以把它看成是一个抽象工厂,对它的调用返回的是工厂生产的产品。所有的 Factory bean都实现特殊的org.springframework.beans.factory.FactoryBean接口,当使用容器中factory bean的时候,该容器不会返回factory bean本身,而是返回其生成的对象。Spring包括了大部分的通用资源和服务访问抽象的Factory bean的实现,其中包括:
对JNDI查询的处理,对代理对象的处理,对事务性代理的处理,对RMI代理的处理等,这些我们都可以看成是具体的工厂,看成是SPRING为我们建立好的工厂。也就是说Spring通过使用抽象工厂模式为我们准备了一系列工厂来生产一些特定的对象,免除我们手工重复的工作,我们要使用时只需要在IOC容器里配置好就能很方便的使用了。
现在我们来看看在Spring的事件机制,Spring中有3个标准事件,ContextRefreshEvent, ContextCloseEvent,RequestHandledEvent他们通过ApplicationEvent接口,同样的如果需要自定义时间也只需要实现ApplicationEvent接口,参照ContextCloseEvent的实现可以定制自己的事件实现:

1
2
3
4
5
6
7
8
9
10
public class ContextClosedEvent extends ApplicationEvent {  

public ContextClosedEvent(ApplicationContext source) {
super(source);
}

public ApplicationContext getApplicationContext() {
return (ApplicationContext) getSource();
}
}

可以通过显现ApplicationEventPublishAware接口,将事件发布器耦合到ApplicationContext这样可以使用 ApplicationContext框架来传递和消费消息,然后在ApplicationContext中配置好bean就可以了,在消费消息的过程中,接受者通过实现ApplicationListener接收消息。

Spring源码解读——容器体系

发表于 2012-02-22 | 更新于 2020-11-14 | 评论数:
本文字数: 2.8k

IoC和AOP是 Spring 提供的两个非常核心的概念,特别是 IoC 是建立起整个 Spring 架构的基础,Spring 提供了一个非常强大完整的容器体系,今天我们先看看整体体系结构的架构图:

容器体系结构

可以看到最顶层的是 BeanFactory ,往下有许多的针对不同场景的实现,分别是:

容器 说明
BeanFactory Spring的Bean容器最顶层的接口,定义了Ioc容器的基本规范。实现这个接口的Ioc容器都会持有一些BeanDefinition和一个唯一的字符串形式的名字。
HierarchicalBeanFactory 继承BeanFactory并扩展使其支持层级结构。getParentBeanFactory()方法或者父级BeanFactory,containsLocalBean(String name)方法查看当前BeanFactory是否包含给定名字的Bean,不会递归想父级查找。
ListableBeanFactory 同样扩展BeanFactory使其支持迭代Ioc容器持有的Bean对象。注意如果ListableBeanFactory同时也是HierarchicalBeanFactory,那么大多数情况下,只迭代当前Ioc容器持有的Bean对象,不会在体系结构中想父级递归迭代。具体情况请看API说明。
ResourceLoader Spring提供资源的根接口。在Spring Ioc中,资源被Resource引用,获得Resource对象,说明获得了资源的访问。Resource提供资源的抽象,具体资源可是从URL,classpath,file等地方获得。
ResourcePatternResolver ResourcePatternResolver是对ResourceLoader的扩展,其支持模式匹配的资源。如:classpath*:表示匹配路径下所有的资源。
DefaultResourceLoader ResourceLoader的默认实现,可以单独使用,也可以通过扩展使其支持特殊的资源,如:FileSystemResourceLoader,ClassPathXmlApplicationContext等。
ApplicationEventPublisher 封装事件发布,通知事件监听者此Application的事件。 MessageSource:处理Spring 中的消息,支持i18n和参数化消息。另外其子类ReloadableResourceBundleMessageSource支持不重启JVM刷新消息。
EnvironmentCapable 实现此接口的容器将支持上下文环境。在Spring Ioc容器中,都是支持上下文环境的。
ApplicationContext 从上图来看,ApplicationContext继承了上面描述的所有接口,因此ApplicationContext是一个接口集合,提供所继承接口的功能。另外,ApplicationContext在启动后是只读的,但是如果ApplicationContext实现类支持reload,也可以刷新这个ApplicationContext。 Lifecycle:对BeanFactory提供生命周期支持。另外其他任何对象都可以实现Lifecycle接口开支持开始/结束控制。注意Lifecycle接口只支持顶层对象,其他的Lifecycle将被忽略。
DisposableBean DisposableBean提供了在销毁Ioc容器的时候释放资源。
ConfigurableApplicationContext 提供对Ioc容器的配置的支持。包括设置父级容器,设置上下文环境,刷新容器,注册关闭容器钩子等。
AbstractApplicationContext AbstractApplicationContext是Ioc容器的抽象实现,这里实现了大部分的功能:消息,事件,刷新容器,生命周期等。AbstractApplicationContext采用模板方法模式,把一部分实现推迟到子类。
AbstractRefreshableApplicationContext 提供多线程同时刷新容器支持,每次刷新都会产生一个内部BeanFactory(DefaultListableBeanFactory)。另外,子类要实现loadBeanDefinitions方法来正确加载Bean定义。
Aware 标记接口,实现这个接口的对象提供通知Spring容器功能。具体个通知动作来子类中定义。
BeanNameAware Aware的子接口,当设置BeanName的时候,创建通知。
InitializingBean 这个接口作用是当Bean对象的属性都被设置完成或,可以立即做一些自定义的动作。令一个替代方案是设置init-method。
AbstractRefreshableConfigApplicationContext 提供对容器的一些特殊设置:setConfigLocation,setBeanName,setId等。
AbstractXmlApplicationContext 从XML读取Bean定义的容器,这个容器实现了loadBeanDefinitions方法,从XML资源中获得Bean定义。
FileSystemXmlApplicationContext 标准的从文件系统读XML的Bean定义容器。getResourceByPath方法返回文件系统资源。

还有一个重量级的实现:DefaultListableBeanFactory

DefaultListableBeanFactory包含了Ioc容器的重要内容,很多容器都会用的它。如AbstractApplicationContext.refersh()方法就会销毁内部的容器并重新创建一个DefaultListableBeanFactory作为起内部表示。DefaultListableBeanFactory则直接继承它成为从XML读取资源的Ioc容器。

在DefaultListableBeanFactory有一个ConcurrentHashMap保存了Bean的定义。

1
2
// Map of bean definition objects, keyed by bean name
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();

DefaultListableBeanFactory的层级结构图如下所示:

DefaultListableBeanFactory的层级结构图如下所示

1…678…11
Ivan

Ivan

104 日志
7 分类
99 标签
RSS
GitHub E-Mail Weibo Google Twitter FB Page
© 2020 Ivan
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Gemini v7.0.1