struts2源代码分析(个人觉得非常经典)

时间:2019-05-12 02:43:00下载本文作者:会员上传
简介:写写帮文库小编为你整理了多篇相关的《struts2源代码分析(个人觉得非常经典)》,但愿对你工作学习有帮助,当然你在写写帮文库还可以找到更多《struts2源代码分析(个人觉得非常经典)》。

第一篇:struts2源代码分析(个人觉得非常经典)

本章讲述Struts2的工作原理。

读者如果曾经学习过Struts1.x或者有过Struts1.x的开发经验,那么千万不要想当然地以为这一章可以跳过。实际上Struts1.x与Struts2并无我们想象的血缘关系。虽然Struts2的开发小组极力保留Struts1.x的习惯,但因为Struts2的核心设计完全改变,从思想到设计到工作流程,都有了很大的不同。

Struts2是Struts社区和WebWork社区的共同成果,我们甚至可以说,Struts2是WebWork的升级版,他采用的正是WebWork的核心,所以,Struts2并不是一个不成熟的产品,相反,构建在WebWork基础之上的Struts2是一个运行稳定、性能优异、设计成熟的WEB框架。

本章主要对Struts的源代码进行分析,因为Struts2与WebWork的关系如此密不可分,因此,读者需要下载xwork的源代码,访问http://文件,则通过过滤器链继续往下传送,直到到达请求的资源为止。

如果getMapping()方法返回有效的ActionMapping对象,则被认为正在请求某个Action,将调用Dispatcher.serviceAction(request, response, servletContext, mapping)方法,该方法是处理Action的关键所在。上述过程的源代码如清单15所示。

代码清单15:FilterDispatcher.doFilter()方法

publicvoid doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {

HttpServletRequest request =(HttpServletRequest)req;

HttpServletResponse response =(HttpServletResponse)res;

ServletContext servletContext = getServletContext();

String timerKey = “FilterDispatcher_doFilter: ”;

try {

UtilTimerStack.push(timerKey);

request = prepareDispatcherAndWrapRequest(request, response);//重新包装request

ActionMapping mapping;

try {

mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());//得到存储Action信息的ActionMapping对象

} catch(Exception ex){

……(省略部分代码)

return;

}

if(mapping == null){//如果mapping为null,则认为不是请求Action资源

String resourcePath = RequestUtils.getServletPath(request);

if(“".equals(resourcePath)&& null!= request.getPathInfo()){

resourcePath = request.getPathInfo();

}

{

ngth());

//如果请求的资源以/struts开头,则当作静态资源处理

if(serveStatic && resourcePath.startsWith(”/struts“))

String name = resourcePath.substring(”/struts“.le

findStaticResource(name, request, response);} else {

//否则,过滤器链继续往下传递

chain.doFilter(request, response);}

// The framework did its job here

return;

}

//如果请求的资源是Action,则调用serviceAction方法。

dispatcher.serviceAction(request, response, servletContext, mapping);

} finally {

try {

ActionContextCleanUp.cleanUp(req);

}

} finally {

UtilTimerStack.pop(timerKey);

} }

这段代码的活动图如图18所示:

(图18)

在Dispatcher.serviceAction()方法中,先加载Struts2的配置文件,如果没有人为配置,则默认加载struts-default.xml、struts-plugin.xml和struts.xml,并且将配置信息保存在形如com.opensymphony.xwork2.config.entities.XxxxConfig的类中。

类com.opensymphony.xwork2.config.providers.XmlConfigurationProvider负责配置文件的读取和解析,addAction()方法负责读取标签,并将数据保存在ActionConfig中;addResultTypes()方法负责将标签转化为ResultTypeConfig对象;loadInterceptors()方法负责将标签转化为InterceptorConfig对象;loadInterceptorStack()方法负责将标签转化为InterceptorStackConfig对象;loadInterceptorStacks()方法负责将标签转化成InterceptorStackConfig对象。而上面的方法最终会被addPackage()方法调用,将所读取到的数据汇集到PackageConfig对象中,细节请参考代码清单16。

代码清单16:XmlConfigurationProvider.addPackage()方法

protected PackageConfig addPackage(Element packageElement)throws ConfigurationException {

PackageConfig newPackage = buildPackageContext(packageElement);

age

if(newPackage.isNeedsRefresh()){

return newPackage;} if(LOG.isDebugEnabled()){

LOG.debug(”Loaded “ + newPackage);} // add result types(and default result)to this package addResultTypes(newPackage, packageElement);// load the interceptors and interceptor stacks for this packloadInterceptors(newPackage, packageElement);// load the default interceptor reference for this package loadDefaultInterceptorRef(newPackage, packageElement);// load the default class ref for this package loadDefaultClassRef(newPackage, packageElement);// load the global result list for this package loadGlobalResults(newPackage, packageElement);// load the global exception handler list for this package

loadGlobalExceptionMappings(newPackage, packageElement);

// get actions

NodeList actionList = packageElement.getElementsByTagName(”action“);

for(int i = 0;i < actionList.getLength();i++){

Element actionElement =(Element)actionList.item(i);

addAction(actionElement, newPackage);

}

// load the default action reference for this package

loadDefaultActionRef(newPackage, packageElement);

configuration.addPackageConfig(newPackage.getName(), newPackage);

return newPackage;

}

活动图如图19所示:

(图19)

配置信息加载完成后,创建一个Action的代理对象——ActionProxy引用,实际上对Action的调用正是通过ActionProxy实现的,而ActionProxy又由ActionProxyFactory创建,ActionProxyFactory是创建ActionProxy的工厂。

注:ActionProxy和ActionProxyFactory都是接口,他们的默认实现类分别是DefaultActionProxy和DefaultActionProxyFactory,位于com.opensymphony.xwork2包下。

在这里,我们绝对有必要介绍一下com.opensymphony.xwork2.DefaultActionInvocation类,该类是对ActionInvocation接口的默认实现,负责Action和截拦器的执行。

在DefaultActionInvocation类中,定义了invoke()方法,该方法实现了截拦器的递归调用和执行Action的execute()方法。其中,递归调用截拦器的代码如清单17所示:

代码清单17:调用截拦器,DefaultActionInvocation.invoke()方法的部分代码

if(interceptors.hasNext()){

//从截拦器集合中取出当前的截拦器

final InterceptorMapping interceptor =(InterceptorMapping)interceptors.next();

UtilTimerStack.profile(”interceptor: “+interceptor.getName(),new UtilTimerStack.ProfilingBlock(){

public String doProfiling()throws Exception {

//执行截拦器(Interceptor)接口中定义的intercept方法

resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);

returnnull;

}

});

}

从代码中似乎看不到截拦器的递归调用,其实是否递归完全取决于程序员对程序的控制,先来看一下Interceptor接口的定义:

代码清单18:Interceptor.java publicinterface Interceptor extends Serializable {

void destroy();

void init();

String intercept(ActionInvocation invocation)throws Exception;}

所有的截拦器必须实现intercept方法,而该方法的参数恰恰又是ActionInvocation,所以,如果在intercept方法中调用invocation.invoke(),代码清单17会再次执行,从Action的Intercepor列表中找到下一个截拦器,依此递归。下面是一个自定义截拦器示例:

代码清单19:CustomIntercepter.java publicclass CustomIntercepter extends AbstractInterceptor {

@Override

public String intercept(ActionInvocation actionInvocation)throws Exception

} { actionInvocation.invoke();return”李赞红“;}

截拦器的调用活动图如图20所示:

(图20)

如果截拦器全部执行完毕,则调用invokeActionOnly()方法执行Action,invokeActionOnly()方法基本没做什么工作,只调用了invokeAction()方法。

为了执行Action,必须先创建该对象,该工作在DefaultActionInvocation的构造方法中调用init()方法早早完成。调用过程是:DefaultActionInvocation()->init()->createAction()。创建Action的代码如下:

代码清单20:DefaultActionInvocation.createAction()方法

protectedvoid createAction(Map contextMap){

try {

action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);

} catch(InstantiationException e){

……异常代码省略

}

}

Action创建好后,轮到invokeAction()大显身手了,该方法比较长,但关键语句实在很少,用心点看不会很难。

代码清单20:DefaultActionInvocation.invokeAction()方法

protected String invokeAction(Object action, ActionConfig actionConfig)throws Exception {

//获取Action中定义的execute()方法名称,实际上该方法是可以随便定义的

String methodName = proxy.getMethod();

String timerKey = ”invokeAction: “+proxy.getActionName();

try {

UtilTimerStack.push(timerKey);

Method method;

try {

//将方法名转化成Method对象

method = getAction().getClass().getMethod(methodName, new Class[0]);

} catch(NoSuchMethodException e){

// hmm--OK, try doXxx instead

try {

//如果Method出错,则尝试在方法名前加do,再转成Method对象

String altMethodName = ”do“ + methodName.substring(0, 1).toUpperCase()+ methodName.substring(1);

method = getAction().getClass().getMethod(altMethodName, new Class[0]);

} catch(NoSuchMethodException e1){

// throw the original one

throw e;

}

}

//执行方法

[0]);

}

Object methodResult = method.invoke(action, new Object

//处理跳转

if(methodResult instanceof Result){

this.result =(Result)methodResult;

returnnull;

} else {

return(String)methodResult;

} } catch(NoSuchMethodException e){

……省略异常代码 } finally {

UtilTimerStack.pop(timerKey);}

刚才使用了一段插述,我们继续回到ActionProxy类。

我们说Action的调用是通过ActionProxy实现的,其实就是调用了ActionProxy.execute()方法,而该方法又调用了ActionInvocation.invoke()方法。归根到底,最后调用的是DefaultActionInvocation.invokeAction()方法。

以下是调用关系图:

其中:

Ø

ActionProxy:管理Action的生命周期,它是设置和执行Action的起始点。

Ø

ActionInvocation:在ActionProxy层之下,它表示了Action的执行状态。它持有Action实例和所有的Interceptor

以下是serviceAction()方法的定义:

代码清单21:Dispatcher.serviceAction()方法

publicvoid serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,ActionMapping mapping)throws ServletException {

Map extraContext = createContextMap(request, response, mapping, context);

// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action

ValueStack stack =(ValueStack)request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);

if(stack!= null){

extraContext.put(ActionContext.VALUE_STACK, ValueStackFactory.getFactory().createValueStack(stack));

}

String timerKey = ”Handling request from Dispatcher“;

try {

UtilTimerStack.push(timerKey);

String namespace = mapping.getNamespace();

String name = mapping.getName();

String method = mapping.getMethod();

Configuration config = configurationManager.getConfiguration();

ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, extraContext, true, false);

proxy.setMethod(method);

request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

// if the ActionMapping says to go straight to a result, do it!

if(mapping.getResult()!= null){

Result result = mapping.getResult();

result.execute(proxy.getInvocation());

} else {

proxy.execute();

}

// If there was a previous value stack then set it back onto the request

if(stack!= null){

request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);

}

} catch(ConfigurationException e){

LOG.error(”Could not find action or result", e);

sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);

} catch(Exception e){

thrownew ServletException(e);

} finally {

UtilTimerStack.pop(timerKey);

}

}

最后,通过Result完成页面的跳转。

3.4 本小节总结

总体来讲,Struts2的工作机制比Struts1.x要复杂很多,但我们不得不佩服Struts和WebWork开发小组的功底,代码如此优雅,甚至能够感受看到两个开发小组心神相通的默契。两个字:佩服。

以下是Struts2运行时调用方法的顺序图:

(图21)

四、总结

阅读源代码是一件非常辛苦的事,对读者本身的要求也很高,一方面要有扎实的功底,另一方面要有超强的耐力和恒心。本章目的就是希望能帮助读者理清一条思路,在必要的地方作出简单的解释,达到事半功倍的效果。

当然,笔者不可能为读者解释所有类,这也不是我的初衷。Struts2+xwork一共有700余类,除了为读者做到现在的这些,已无法再做更多的事情。读者可以到Struts官方网站下载帮助文档,慢慢阅读和理解,相信会受益颇丰。

本章并不适合java语言初学者或者对java博大精深的思想理解不深的读者阅读,这其中涉及到太多的术语和类的使用,特别不要去钻牛角尖,容易使自信心受损。基本搞清楚Struts2的使用之后,再回过头来阅读本章,对一些知识点和思想也许会有更深的体会。

如果读者的java功底比较浑厚,而且对Struts2充满兴趣,但又没太多时间研究,不妨仔细阅读本章,再对照Struts的源代码,希望对您有所帮助。

第二篇:struts2代码分析

1.Struts2架构图和请求处理流程

请求首先通过Filter chain,Filter主要包括ActionContextCleanUp,它主要清理当前线程的ActionContext和Dispatcher;FilterDispatcher主要通过AcionMapper来决定需要调用哪个Action。

ActionMapper取得了ActionMapping后,在Dispatcher的serviceAction方法里创建ActionProxy,ActionProxy创建ActionInvocation,然后ActionInvocation调用Interceptors,执行Action本身,创建Result并返回,当然,如果要在返回之前做些什么,可以实现PreResultListener。

2.Struts2部分类介绍

这部分从Struts2参考文档中翻译就可以了。

ActionMapper

ActionMapper其实是HttpServletRequest和Action调用请求的一个映射,它屏蔽了Action对于Request等java Servlet类的依赖。Struts2中它的默认实现类是DefaultActionMapper,ActionMapper很大的用处可以根据自己的需要来设计url格式,它自己也有Restful的实现,具体可以参考文档的docs¥actionmapper.html。

ActionProxy&ActionInvocation

Action的一个代理,由ActionProxyFactory创建,它本身不包括Action实例,默认实现DefaultActionProxy是由ActionInvocation持有Action实例。ActionProxy作用是如何取得Action,无论是本地还是远程。而ActionInvocation的作用是如何执行Action,拦截器的功能就是在ActionInvocation中实现的。

ConfigurationProvider&Configuration

ConfigurationProvider就是Struts2中配置文件的解析器,Struts2中的配置文件主要是尤其实现类XmlConfigurationProvider及其子类StrutsXmlConfigurationProvider来解析。

3.Struts2请求流程

1、客户端发送请求

2、请求先通过ActionContextCleanUp-->FilterDispatcher

3、FilterDispatcher通过ActionMapper来决定这个Request需要调用哪个Action

4、如果ActionMapper决定调用某个Action,FilterDispatcher把请求的处理交给ActionProxy,这儿已经转到它的Delegate--Dispatcher来执行

5、ActionProxy根据ActionMapping和ConfigurationManager找到需要调用的Action类

6、ActionProxy创建一个ActionInvocation的实例

7、ActionInvocation调用真正的Action,当然这涉及到相关拦截器的调用

8、Action执行完毕,ActionInvocation创建Result并返回,当然,如果要在返回之前做些什么,可以实现PreResultListener。添加PreResultListener可以在Interceptor中实现。

首先强调一下struts2的线程程安全,在Struts2中大量采用ThreadLocal线程局部变量的方法来保证线程的安全,像Dispatcher等都是通过ThreadLocal来保存变量值,使得每个线程都有自己独立的实例变量,互不相干.接下来就从Dispatcher开始看起,先看其构造函数:

//创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方

public Dispatcher(ServletContext servletContext, Map initParams){

this.servletContext = servletContext;

//配置在web.xml中的param参数

this.initParams = initParams;

}

//创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方

public Dispatcher(ServletContext servletContext, Map initParams){

this.servletContext = servletContext;

//配置在web.xml中的param参数

this.initParams = initParams;

}

我们再看在FilterDispatcher创建Dispatcher的:

protected Dispatcher createDispatcher(FilterConfig filterConfig){

Map params = new HashMap();

for(Enumeration e = filterConfig.getInitParameterNames();e.hasMoreElements();){

String name =(String)e.nextElement();

String value = filterConfig.getInitParameter(name);

params.put(name, value);

}

都可以从FilterConfig中得到

return new Dispatcher(filterConfig.getServletContext(), params);

}

protected Dispatcher createDispatcher(FilterConfig filterConfig){

Map params = new HashMap();

for(Enumeration e = filterConfig.getInitParameterNames();e.hasMoreElements();){

String name =(String)e.nextElement();

String value = filterConfig.getInitParameter(name);

params.put(name, value);

}

都可以从FilterConfig中得到

return new Dispatcher(filterConfig.getServletContext(), params);

}

分七步载入各种配置属性,都是通过ConfigurationProvider接口进行的,这个接口提供init(),destroy(),register()等方法.将各种ConfigurationProvider初始化之后将实例添加到ConfigurationManager的List里面.最后通过循环调用List里的这些destroy(),register()等方法实现对配置文件的属性进行注册和销毁等功能.下面将分析这七层功夫是怎样一步步练成的.首先是init_DefaultProperties()

创建Dispatcher之后,来看init()方法

init()方法是用来Load用户配置文件,资源文件以及默认的配置文件.主要分七步走,看下面注释

public void init(){

if(configurationManager == null){

//设置ConfigurationManager的defaultFrameworkBeanName.//这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等

configurationManager

=

new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);

}

//读取properties信息,默认的default.properties,init_DefaultProperties();// [1]

//读取xml配置文件

init_TraditionalXmlConfigurations();// [2]

//读取用户自定义的struts.properties

init_LegacyStrutsProperties();// [3]

//自定义的configProviders

init_CustomConfigurationProviders();// [5]

//载入FilterDispatcher传进来的initParams

init_FilterInitParameters();// [6]

//将配置文件中的bean与具体的类映射

init_AliasStandardObjects();// [7]

//构建一个用于依赖注射的Container对象

//在这里面会循环调用上面七个ConfigurationProvider的register方法

//其中的重点就是DefaultConfiguration的#reload()方法

Container container = init_PreloadConfiguration();

container.inject(this);

init_CheckConfigurationReloading(container);

init_CheckWebLogicWorkaround(container);

if(!dispatcherListeners.isEmpty()){

for(DispatcherListener l : dispatcherListeners){

l.dispatcherInitialized(this);

}

}

}

public void init(){

if(configurationManager == null){

//设置ConfigurationManager的defaultFrameworkBeanName.//这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等

configurationManager

=

new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);

}

//读取properties信息,默认的default.properties,init_DefaultProperties();// [1]

//读取xml配置文件

init_TraditionalXmlConfigurations();// [2]

//读取用户自定义的struts.properties

init_LegacyStrutsProperties();// [3]

//自定义的configProviders

init_CustomConfigurationProviders();// [5]

//载入FilterDispatcher传进来的initParams

init_FilterInitParameters();// [6]

//将配置文件中的bean与具体的类映射

init_AliasStandardObjects();// [7]

//构建一个用于依赖注射的Container对象

//在这里面会循环调用上面七个ConfigurationProvider的register方法

//其中的重点就是DefaultConfiguration的#reload()方法

Container container = init_PreloadConfiguration();

container.inject(this);

init_CheckConfigurationReloading(container);

init_CheckWebLogicWorkaround(container);

if(!dispatcherListeners.isEmpty()){

for(DispatcherListener l : dispatcherListeners){

l.dispatcherInitialized(this);

}

}

}

分七步载入各种配置属性,都是通过ConfigurationProvider接口进行的,这个接口提供init(),destroy(),register()等方法.将各种ConfigurationProvider初始化之后将实例添加到ConfigurationManager的List里面.最后通过循环调用List里的这些destroy(),register()等方法实现对配置文件的属性进行注册和销毁等功能.下面将分析这七层功夫是怎样一步步练成的.首先是init_DefaultProperties()

private void init_DefaultProperties(){

configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());

}

接来看DefaultPropertiesProvider好了,DefaultPropertiesProvider实际上只是实现了register()方法

public void register(ContainerBuilder builder, LocatableProperties props)

throws ConfigurationException {

Settings defaultSettings = null;

try {

defaultSettings = new PropertiesSettings(“org/apache/struts2/default”);

} catch(Exception e){

throw

}

loadSettings(props, defaultSettings);

}

private void init_DefaultProperties(){

configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());

}

接来看DefaultPropertiesProvider好了,DefaultPropertiesProvider实际上只是实现了register()方法

public void register(ContainerBuilder builder, LocatableProperties props)

throws ConfigurationException {

Settings defaultSettings = null;

try {

defaultSettings = new PropertiesSettings(“org/apache/struts2/default”);

} catch(Exception e){

new

ConfigurationException(“Could

not

find

or

error

in org/apache/struts2/default.properties”, e);

throw

}

new ConfigurationException(“Could not find or error in org/apache/struts2/default.properties”, e);

loadSettings(props, defaultSettings);

}

//PropertiesSettings构造方法

//读取org/apache/struts2/default.properties的配置信息,如果项目中需要覆盖,可以在classpath里的struts.properties里覆写

public PropertiesSettings(String name){

URL settingsUrl = ClassLoaderUtils.getResource(name + “.properties”, getClass());

if(settingsUrl == null){

LOG.debug(name + “.properties missing”);

settings = new LocatableProperties();

return;

}

settings

// Load settings

InputStream in = null;

try {

in = settingsUrl.openStream();

settings.load(in);

} catch(IOException e){

throw new StrutsException(“Could not load ” + name + “.properties:” + e, e);

} finally {

if(in!= null){

try {

=

new

LocatableProperties(new

LocationImpl(null, settingsUrl.toString()));

in.close();

} catch(IOException io){

LOG.warn(“Unable to close input stream”, io);

}

}

}

}

//loadSettings主要是将progerty的value和Locale从上面PropertiesSettings中取得并存放到LocatableProperties props

//这个props是register的一个入参.protected void loadSettings(LocatableProperties props, final Settings settings){

// We are calling the impl methods to get around the single instance of Settings that is expected

for(Iterator i = settings.listImpl();i.hasNext();){

String name =(String)i.next();

props.setProperty(name, settings.getLocationImpl(name));

}

}

//PropertiesSettings构造方法

//读取org/apache/struts2/default.properties的配置信息,如果项目中需要覆盖,可以在classpath里的struts.properties里覆写

public PropertiesSettings(String name){

URL settingsUrl = ClassLoaderUtils.getResource(name + “.properties”, getClass());

if(settingsUrl == null){

LOG.debug(name + “.properties missing”);

settings = new LocatableProperties();

return;

}

settings =

new

LocatableProperties(new

LocationImpl(null, settingsUrl.toString()));

settings.getImpl(name),// Load settings

InputStream in = null;

try {

in = settingsUrl.openStream();

settings.load(in);

} catch(IOException e){

throw new StrutsException(“Could not load ” + name + “.properties:” + e, e);

} finally {

if(in!= null){

try {

in.close();

} catch(IOException io){

LOG.warn(“Unable to close input stream”, io);

}

}

}

}

//loadSettings主要是将progerty的value和Locale从上面PropertiesSettings中取得并存放到LocatableProperties props

//这个props是register的一个入参.protected void loadSettings(LocatableProperties props, final Settings settings){

// We are calling the impl methods to get around the single instance of Settings that is expected

for(Iterator i = settings.listImpl();i.hasNext();){

String name =(String)i.next();

props.setProperty(name, settings.getLocationImpl(name));

}

}

再来看第二步:init_TraditionalXmlConfigurations()

private void init_TraditionalXmlConfigurations(){

settings.getImpl(name), //首先读取web.xml中的config初始参数值

//如果

使

认的DEFAULT_CONFIGURATION_PATHS:“struts-default.xml,struts-plugin.xml,struts.xml”,//这儿就可以看出为什么默认的配置文件必须取名为这三个名称了

//如果不想使用默认的名称,直接在web.xml中配置config初始参数即可

String configPaths = initParams.get(“config”);

if(configPaths == null){

configPaths = DEFAULT_CONFIGURATION_PATHS;

}

String[] files = configPaths.split(“¥¥s*[,]¥¥s*”);

for(String file : files){

if(file.endsWith(“.xml”)){

if(“xwork.xml”.equals(file)){

//XmlConfigurationProvider负责解析xwork.xml

configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false));

} else {

//其它xml都是由StrutsXmlConfigurationProvider来解析

configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext));

}

} else {

throw new IllegalArgumentException(“Invalid configuration file name”);

}

}

}

private void init_TraditionalXmlConfigurations(){

//首先读取web.xml中的config初始参数值

//如果

使

认的DEFAULT_CONFIGURATION_PATHS:“struts-default.xml,struts-plugin.xml,struts.xml”,//这儿就可以看出为什么默认的配置文件必须取名为这三个名称了

//如果不想使用默认的名称,直接在web.xml中配置config初始参数即可

String configPaths = initParams.get(“config”);

if(configPaths == null){

configPaths = DEFAULT_CONFIGURATION_PATHS;

}

String[] files = configPaths.split(“¥¥s*[,]¥¥s*”);

for(String file : files){

if(file.endsWith(“.xml”)){

if(“xwork.xml”.equals(file)){

//XmlConfigurationProvider负责解析xwork.xml

configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false));

} else {

//其它xml都是由StrutsXmlConfigurationProvider来解析

configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext));

}

} else {

throw new IllegalArgumentException(“Invalid configuration file name”);

}

}

}

对于其它配置文件只用接口。

类XmlConfigurationProvider负责配置文件的读取和解析,首先通过init()中的loadDocuments(configFileName);利用DomHelper中的

public static Document parse(InputSource inputSource, Map dtdMappings)将configFileName配置文件通过SAX解析方式按照DtdMappings解析成Document对象.然后通过Provider的register()方法加载“bean”和“constant”属性,再通过loadPackages()加载package及package中的属性

addAction()方法负责读取标签,并将数据保存在ActionConfig中; addResultTypes()方法负责将标签转化为ResultTypeConfig对象; loadInterceptors()方法负责将标签转化为InterceptorConfi对象;

loadInterceptorStack()方法负责将标签转化为InterceptorStackConfig对象;

StrutsXmlConfigurationProvider,此类继承XmlConfigurationProvider,而XmlConfigurationProvider又实现ConfigurationProviderloadInterceptorStacks()方法负责将标签转化成InterceptorStackConfig对象。

而上面的方法最终会被addPackage()方法调用,addPackage又会被Provider的loadPackages()调用,将所读取到的数据汇集到PackageConfig对象中。

protected PackageConfig

addPackage(Element

packageElement)

throws ConfigurationException {

PackageConfig.Builder newPackage = buildPackageContext(packageElement);

if(newPackage.isNeedsRefresh()){

return newPackage.build();

}

// add result types(and default result)to this package

addResultTypes(newPackage, packageElement);

// load the interceptors and interceptor stacks for this package

loadInterceptors(newPackage, packageElement);

// load the default interceptor reference for this package

loadDefaultInterceptorRef(newPackage, packageElement);

// load the default class ref for this package

loadDefaultClassRef(newPackage, packageElement);

// load the global result list for this package

loadGlobalResults(newPackage, packageElement);

// load the global exception handler list for this package

loadGobalExceptionMappings(newPackage, packageElement);

// get actions

NodeList actionList = packageElement.getElementsByTagName(“action”);

for(int i = 0;i < actionList.getLength();i++){

Element actionElement =(Element)actionList.item(i);

addAction(actionElement, newPackage);

}

// load the default action reference for this package

loadDefaultActionRef(newPackage, packageElement);

PackageConfig cfg = newPackage.build();

configuration.addPackageConfig(cfg.getName(), cfg);

return cfg;

}

loadConfigurationFiles解析读取xml中的内容

private List

loadConfigurationFiles(String

fileName,Element includeElement){

...//通过DomHelper调用SAX进行解析xml

doc = DomHelper.parse(in, dtdMappings);

...Element rootElement = doc.getDocumentElement();

NodeList children = rootElement.getChildNodes();

int childSize = children.getLength();

for(int i = 0;i < childSize;i++){

Node childNode = children.item(i);

if(childNode instanceof Element){

Element child =(Element)childNode;

final String nodeName = child.getNodeName();

if(“include”.equals(nodeName)){

String includeFileName = child.getAttribute(“file”);

//解析每个action配置是,对于include文件可以使用通配符*来进行配置

//如Struts.xml中可配置成

if(includeFileName.indexOf('*')!=-1){

ClassPathFinder wildcardFinder = new ClassPathFinder();

wildcardFinder.setPattern(includeFileName);

Vector wildcardMatches = wildcardFinder.findMatches();

for(String match : wildcardMatches){

//递归Load子file中的

docs.addAll(loadConfigurationFiles(match, child));

}

} else {

docs.addAll(loadConfigurationFiles(includeFileName, child));

}

}

}

}

docs.add(doc);

loadedFileUrls.add(url.toString());

...return docs;

}

首先强调一下struts2的线程程安全,在Struts2中大量采用ThreadLocal线程局部变量的方法来保证线程的安全,像Dispatcher等都是通过ThreadLocal来保存变量值,使得每个线程都有自己独立的实例变量,互不相干.接下来就从Dispatcher开始看起,先看其构造函数:

//创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方

public Dispatcher(ServletContext servletContext, Map initParams){

this.servletContext = servletContext;

//配置在web.xml中的param参数

this.initParams = initParams;

}

//创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方

public Dispatcher(ServletContext servletContext, Map initParams){

this.servletContext = servletContext;

//配置在web.xml中的param参数

this.initParams = initParams;

}

我们再看在FilterDispatcher创建Dispatcher的:

protected Dispatcher createDispatcher(FilterConfig filterConfig){

Map params = new HashMap();

for(Enumeration e = filterConfig.getInitParameterNames();e.hasMoreElements();){

String name =(String)e.nextElement();

String value = filterConfig.getInitParameter(name);

params.put(name, value);

}

都可以从FilterConfig中得到

return new Dispatcher(filterConfig.getServletContext(), params);

}

protected Dispatcher createDispatcher(FilterConfig filterConfig){

Map params = new HashMap();

for(Enumeration e = filterConfig.getInitParameterNames();e.hasMoreElements();){

String name =(String)e.nextElement();

String value = filterConfig.getInitParameter(name);

params.put(name, value);

}

都可以从FilterConfig中得到

return new Dispatcher(filterConfig.getServletContext(), params);

}

分七步载入各种配置属性,都是通过ConfigurationProvider接口进行的,这个接口提供init(),destroy(),register()等方法.将各种ConfigurationProvider初始化之后将实例添加到ConfigurationManager的List里面.最后通过循环调用List里的这些destroy(),register()等方法实现对配置文件的属性进行注册和销毁等功能.下面将分析这七层功夫是怎样一步步练成的.首先是init_DefaultProperties()

创建Dispatcher之后,来看init()方法

init()方法是用来Load用户配置文件,资源文件以及默认的配置文件.主要分七步走,看下面注释

public void init(){

if(configurationManager == null){

//设置ConfigurationManager的defaultFrameworkBeanName.//这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等

configurationManager = ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);

}

//读取properties信息,默认的default.properties,init_DefaultProperties();// [1]

//读取xml配置文件

init_TraditionalXmlConfigurations();// [2]

//读取用户自定义的struts.properties

init_LegacyStrutsProperties();// [3]

//自定义的configProviders

init_CustomConfigurationProviders();// [5]

//载入FilterDispatcher传进来的initParams

init_FilterInitParameters();// [6]

//将配置文件中的bean与具体的类映射

init_AliasStandardObjects();// [7]

//构建一个用于依赖注射的Container对象

//在这里面会循环调用上面七个ConfigurationProvider的register方法

//其中的重点就是DefaultConfiguration的#reload()方法

Container container = init_PreloadConfiguration();

container.inject(this);

init_CheckConfigurationReloading(container);

init_CheckWebLogicWorkaround(container);

if(!dispatcherListeners.isEmpty()){

for(DispatcherListener l : dispatcherListeners){

l.dispatcherInitialized(this);

}

}

new

}

public void init(){

if(configurationManager == null){

//设置ConfigurationManager的defaultFrameworkBeanName.//这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等

configurationManager = ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);

}

//读取properties信息,默认的default.properties,init_DefaultProperties();// [1]

//读取xml配置文件

init_TraditionalXmlConfigurations();// [2]

//读取用户自定义的struts.properties

init_LegacyStrutsProperties();// [3]

//自定义的configProviders

init_CustomConfigurationProviders();// [5]

//载入FilterDispatcher传进来的initParams

init_FilterInitParameters();// [6]

//将配置文件中的bean与具体的类映射

init_AliasStandardObjects();// [7]

//构建一个用于依赖注射的Container对象

//在这里面会循环调用上面七个ConfigurationProvider的register方法

//其中的重点就是DefaultConfiguration的#reload()方法

Container container = init_PreloadConfiguration();

container.inject(this);

init_CheckConfigurationReloading(container);

init_CheckWebLogicWorkaround(container);

if(!dispatcherListeners.isEmpty()){

for(DispatcherListener l : dispatcherListeners){

l.dispatcherInitialized(this);

}

}

new

}

分七步载入各种配置属性,都是通过ConfigurationProvider接口进行的,这个接口提供init(),destroy(),register()等方法.将各种ConfigurationProvider初始化之后将实例添加到ConfigurationManager的List里面.最后通过循环调用List里的这些destroy(),register()等方法实现对配置文件的属性进行注册和销毁等功能.下面将分析这七层功夫是怎样一步步练成的.首先是init_DefaultProperties()

private void init_DefaultProperties(){

configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());

}

接来看DefaultPropertiesProvider好了,DefaultPropertiesProvider实际上只是实现了register()方法

public void register(ContainerBuilder builder, LocatableProperties props)

throws ConfigurationException {

Settings defaultSettings = null;

try {

defaultSettings = new PropertiesSettings(“org/apache/struts2/default”);

} catch(Exception e){

throw

}

loadSettings(props, defaultSettings);

}

private void init_DefaultProperties(){

configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());

}

接来看DefaultPropertiesProvider好了,DefaultPropertiesProvider实际上只是实现了new

ConfigurationException(“Could

not

find

or

error

in org/apache/struts2/default.properties”, e);

register()方法

public void register(ContainerBuilder builder, LocatableProperties props)

throws ConfigurationException {

Settings defaultSettings = null;

try {

defaultSettings = new PropertiesSettings(“org/apache/struts2/default”);

} catch(Exception e){

throw

}

loadSettings(props, defaultSettings);

}

//PropertiesSettings构造方法

//读取org/apache/struts2/default.properties的配置信息,如果项目中需要覆盖,可以在classpath里的struts.properties里覆写

public PropertiesSettings(String name){

URL settingsUrl = ClassLoaderUtils.getResource(name + “.properties”, getClass());

if(settingsUrl == null){

LOG.debug(name + “.properties missing”);

settings = new LocatableProperties();

return;

}

settings

// Load settings

InputStream in = null;

try {

=

new

LocatableProperties(new

LocationImpl(null, settingsUrl.toString()));

new

ConfigurationException(“Could

not

find

or

error

in org/apache/struts2/default.properties”, e);

in = settingsUrl.openStream();

settings.load(in);

} catch(IOException e){

throw new StrutsException(“Could not load ” + name + “.properties:” + e, e);

} finally {

if(in!= null){

try {

in.close();

} catch(IOException io){

LOG.warn(“Unable to close input stream”, io);

}

}

}

}

//loadSettings主要是将progerty的value和Locale从上面PropertiesSettings中取得并存放到LocatableProperties props

//这个props是register的一个入参.protected void loadSettings(LocatableProperties props, final Settings settings){

// We are calling the impl methods to get around the single instance of Settings that is expected

for(Iterator i = settings.listImpl();i.hasNext();){

String name =(String)i.next();

props.setProperty(name, settings.getLocationImpl(name));

}

}

//PropertiesSettings构造方法

//读取org/apache/struts2/default.properties的配置信息,如果项目中需要覆盖,可以在classpath里的struts.properties里覆写

public PropertiesSettings(String name){

URL settingsUrl = ClassLoaderUtils.getResource(name + “.properties”, getClass());

settings.getImpl(name),if(settingsUrl == null){

LOG.debug(name + “.properties missing”);

settings = new LocatableProperties();

return;

}

settings

// Load settings

InputStream in = null;

try {

in = settingsUrl.openStream();

settings.load(in);

} catch(IOException e){

throw new StrutsException(“Could not load ” + name + “.properties:” + e, e);

} finally {

if(in!= null){

try {

in.close();

} catch(IOException io){

LOG.warn(“Unable to close input stream”, io);

}

}

}

}

//loadSettings主要是将progerty的value和Locale从上面PropertiesSettings中取得并存放到LocatableProperties props

//这个props是register的一个入参.protected void loadSettings(LocatableProperties props, final Settings settings){

// We are calling the impl methods to get around the single instance of Settings that is expected

for(Iterator i = settings.listImpl();i.hasNext();){

String name =(String)i.next();

=

new

LocatableProperties(new

LocationImpl(null, settingsUrl.toString()));

props.setProperty(name, settings.getLocationImpl(name));

}

}

再来看第二步:init_TraditionalXmlConfigurations()

private void init_TraditionalXmlConfigurations(){

//首先读取web.xml中的config初始参数值

//如果

使

settings.getImpl(name),用默认的DEFAULT_CONFIGURATION_PATHS:“struts-default.xml,struts-plugin.xml,struts.xml”,//这儿就可以看出为什么默认的配置文件必须取名为这三个名称了

//如果不想使用默认的名称,直接在web.xml中配置config初始参数即可

String configPaths = initParams.get(“config”);

if(configPaths == null){

configPaths = DEFAULT_CONFIGURATION_PATHS;

}

String[] files = configPaths.split(“¥¥s*[,]¥¥s*”);

for(String file : files){

if(file.endsWith(“.xml”)){

if(“xwork.xml”.equals(file)){

//XmlConfigurationProvider负责解析xwork.xml

configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false));

} else {

//其它xml都是由StrutsXmlConfigurationProvider来解析

configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext));

}

} else {

throw new IllegalArgumentException(“Invalid configuration file name”);

}

}

}

private void init_TraditionalXmlConfigurations(){

//首先读取web.xml中的config初始参数值

//如果

使

认的DEFAULT_CONFIGURATION_PATHS:“struts-default.xml,struts-plugin.xml,struts.xml”,//这儿就可以看出为什么默认的配置文件必须取名为这三个名称了

//如果不想使用默认的名称,直接在web.xml中配置config初始参数即可

String configPaths = initParams.get(“config”);

if(configPaths == null){

configPaths = DEFAULT_CONFIGURATION_PATHS;

}

String[] files = configPaths.split(“¥¥s*[,]¥¥s*”);

for(String file : files){

if(file.endsWith(“.xml”)){

if(“xwork.xml”.equals(file)){

//XmlConfigurationProvider负责解析xwork.xml

configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false));

} else {

//其它xml都是由StrutsXmlConfigurationProvider来解析

configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext));

}

} else {

throw new IllegalArgumentException(“Invalid configuration file name”);

}

}

}

对于其它配置文件只用接口。

类XmlConfigurationProvider负责配置文件的读取和解析,首先通过init()中的loadDocuments(configFileName);利用DomHelper中的

public static Document parse(InputSource inputSource, Map dtdMappings)将configFileName配置文件通过SAX解析方式按照DtdMappings解析成StrutsXmlConfigurationProvider,此类继承XmlConfigurationProvider,而XmlConfigurationProvider又实现ConfigurationProviderDocument对象.然后通过Provider的register()方法加载“bean”和“constant”属性,再通过loadPackages()加载package及package中的属性

addAction()方法负责读取标签,并将数据保存在ActionConfig中; addResultTypes()方法负责将标签转化为ResultTypeConfig对象; loadInterceptors()方法负责将标签转化为InterceptorConfi对象;

loadInterceptorStack()方法负责将标签转化为InterceptorStackConfig对象;

loadInterceptorStacks()方法负责将标签转化成InterceptorStackConfig对象。

而上面的方法最终会被addPackage()方法调用,addPackage又会被Provider的loadPackages()调用,将所读取到的数据汇集到PackageConfig对象中。

protected PackageConfig

addPackage(Element

packageElement)

throws ConfigurationException {

PackageConfig.Builder newPackage = buildPackageContext(packageElement);

if(newPackage.isNeedsRefresh()){

return newPackage.build();

}

// add result types(and default result)to this package

addResultTypes(newPackage, packageElement);

// load the interceptors and interceptor stacks for this package

loadInterceptors(newPackage, packageElement);

// load the default interceptor reference for this package

loadDefaultInterceptorRef(newPackage, packageElement);

// load the default class ref for this package

loadDefaultClassRef(newPackage, packageElement);

// load the global result list for this package

loadGlobalResults(newPackage, packageElement);

// load the global exception handler list for this package

loadGobalExceptionMappings(newPackage, packageElement);

// get actions

NodeList actionList = packageElement.getElementsByTagName(“action”);

for(int i = 0;i < actionList.getLength();i++){

Element actionElement =(Element)actionList.item(i);

addAction(actionElement, newPackage);

}

// load the default action reference for this package

loadDefaultActionRef(newPackage, packageElement);

PackageConfig cfg = newPackage.build();

configuration.addPackageConfig(cfg.getName(), cfg);

return cfg;

}

loadConfigurationFiles解析读取xml中的内容

private List

loadConfigurationFiles(String

fileName, includeElement){

...//通过DomHelper调用SAX进行解析xml

doc = DomHelper.parse(in, dtdMappings);

...Element rootElement = doc.getDocumentElement();

NodeList children = rootElement.getChildNodes();

int childSize = children.getLength();

for(int i = 0;i < childSize;i++){

Node childNode = children.item(i);

if(childNode instanceof Element){

Element child =(Element)childNode;

final String nodeName = child.getNodeName();

if(“include”.equals(nodeName)){

String includeFileName = child.getAttribute(“file”);

//解析每个action配置是,对于include文件可以使用通配符*来进行配置

//如Struts.xml中可配置成

if(includeFileName.indexOf('*')!=-1){

ClassPathFinder wildcardFinder = new ClassPathFinder();

wildcardFinder.setPattern(includeFileName);

Element

Vector wildcardMatches = wildcardFinder.findMatches();

for(String match : wildcardMatches){

//递归Load子file中的

docs.addAll(loadConfigurationFiles(match, child));

}

} else {

docs.addAll(loadConfigurationFiles(includeFileName, child));

}

}

}

}

docs.add(doc);

loadedFileUrls.add(url.toString());

...return docs;

}

接下来第三步:init_LegacyStrutsProperties()调用的是调用的是LegacyPropertiesConfigurationProvider 通过比较前

DefaultPropertiesProvider

用的是LegacyPropertiesConfigurationProvider.发现DefaultPropertiesProvider继承自后者,但重写了register()方法,主要是生成PropertiesSetting的不同,前者是根据org/apache/struts2/default.properties 后者是根据struts.properties 我们展开register()中的Settings.getInstance(),最后是调用getDefaultInstance()

private static Settings getDefaultInstance(){

if(defaultImpl == null){

// Create bootstrap implementation

//不带参数的DefaultSettings(),区别与DefaultPropertiesProvider中直接带default.properties参数

//不带参数就是默认为struts.propertes,并且加载struts.custom.properties所定义的properties文件

defaultImpl = new DefaultSettings();

// Create default implementation

try {

//STRUTS_CONFIGURATION为:struts.configuration

//在struts.proterties中查找struts.configuration的值,这个值必须是org.apache.struts2.config.Configuration接口的实现类

//所以我有个困惑就是在下面的转换当中怎么将Configuration转换成Setting类型的...//这一点先放下了,有时间再研究

String className = get(StrutsConstants.STRUTS_CONFIGURATION);

if(!className.equals(defaultImpl.getClass().getName())){

try {

// singleton instances shouldn't be built accessing request or session-specific context data

defaultImpl oader().loadClass(className), null);

} catch(Exception e){

LOG.error(“Settings:

}

}

} catch(IllegalArgumentException ex){

// ignore

}

private static Settings getDefaultInstance(){

if(defaultImpl == null){

// Create bootstrap implementation

//不带参数的DefaultSettings(),区别与DefaultPropertiesProvider中直接带default.properties参数

//不带参数就是默认为struts.propertes,并且加载struts.custom.properties所定义的properties文件

defaultImpl = new DefaultSettings();

// Create default implementation

try {

//STRUTS_CONFIGURATION为:struts.configuration

//在struts.proterties中查找struts.configuration的值,这个值必须是

Could

not

instantiate

the struts.configuration object, substituting the default implementation.”, e);

=

(Settings)ObjectFactory.getObjectFactory().buildBean(Thread.currentThread().getContextClassLorg.apache.struts2.config.Configuration接口的实现类

//所以我有个困惑就是在下面的转换当中怎么将Configuration转换成Setting类型的...//这一点先放下了,有时间再研究

String className = get(StrutsConstants.STRUTS_CONFIGURATION);

if(!className.equals(defaultImpl.getClass().getName())){

try {

// singleton instances shouldn't be built accessing request or session-specific context data

defaultImpl oader().loadClass(className), null);

} catch(Exception e){

LOG.error(“Settings:

}

}

} catch(IllegalArgumentException ex){

// ignore

}

在2.1.6中去掉了第四步:init_ZeroConfiguration();第五步是自定义的configProviders

private void init_CustomConfigurationProviders(){

//从这里可以看到可以将自定义的Provider定义在web.xml中FilterDispatcher的param中:configProviders

String configProvs = initParams.get(”configProviders“);

if(configProvs!= null){

String[] classes = configProvs.split(”¥¥s*[,]¥¥s*“);

for(String cname : classes){

try {

Class cls = ClassLoaderUtils.loadClass(cname, this.getClass());

ConfigurationProvider(ConfigurationProvider)cls.newInstance();

configurationManager.addConfigurationProvider(prov);

prov

=

Could

not

instantiate

the struts.configuration object, substituting the default implementation.”, e);

=

(Settings)ObjectFactory.getObjectFactory().buildBean(Thread.currentThread().getContextClassL

}

...}

}

}

private void init_CustomConfigurationProviders(){

//从这里可以看到可以将自定义的Provider定义在web.xml中FilterDispatcher的param中:configProviders

String configProvs = initParams.get(“configProviders”);

if(configProvs!= null){

String[] classes = configProvs.split(“¥¥s*[,]¥¥s*”);

for(String cname : classes){

try {

Class cls = ClassLoaderUtils.loadClass(cname, this.getClass());

ConfigurationProvider(ConfigurationProvider)cls.newInstance();

configurationManager.addConfigurationProvider(prov);

}

...}

}

}

第六步:init_FilterInitParameters

//从这里可以看出struts.properties中的属性不仅可以在struts.xml中以constant形式定义,而且可以在FilterDispatcher的param中定义

private void init_FilterInitParameters(){

configurationManager.addConfigurationProvider(new ConfigurationProvider(){

public void destroy(){}

public

void

init(Configuration

configuration)

throws ConfigurationException {}

public void loadPackages()throws ConfigurationException {}

public boolean needsReload(){ return false;}

prov

=

public void register(ContainerBuilder builder, LocatableProperties props)throws ConfigurationException {

props.putAll(initParams);//在这里实现滴~

}

});

}

//从这里可以看出struts.properties中的属性不仅可以在struts.xml中以constant形式定义,而且可以在FilterDispatcher的param中定义

private void init_FilterInitParameters(){

configurationManager.addConfigurationProvider(new ConfigurationProvider(){

public void destroy(){}

public

void

init(Configuration

configuration)

throws ConfigurationException {}

public void loadPackages()throws ConfigurationException {}

public boolean needsReload(){ return false;}

public void register(ContainerBuilder builder, LocatableProperties props)throws ConfigurationException {

props.putAll(initParams);//在这里实现滴~

}

});

}

第七步:init_AliasStandardObjects,使用BeanSelectionProvider 这是将配置文件中定义的与实际的类相映射,就是注入bean的依赖关系,这部分以后有时候再研究Container

接下来是看怎样调用这些ConfigurationProviders 展开init_PreloadConfiguration()

private Container init_PreloadConfiguration(){

Configuration config = configurationManager.getConfiguration();

Container container = config.getContainer();

boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));

LocalizedTextUtil.setReloadBundles(reloadi18n);

return container;

}

//再看getConfiguration()

public synchronized Configuration getConfiguration(){

if(configuration == null){

setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName));

try {

//重点就是这个reloadContainer

configuration.reloadContainer(getContainerProviders());

} catch(ConfigurationException e){

setConfiguration(null);

throw new ConfigurationException(“Unable to load configuration.”, e);

}

} else {

conditionalReload();

}

return configuration;

}

private Container init_PreloadConfiguration(){

Configuration config = configurationManager.getConfiguration();

Container container = config.getContainer();

boolean reloadi18n

=

Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));

LocalizedTextUtil.setReloadBundles(reloadi18n);

return container;

}

//再看getConfiguration()

public synchronized Configuration getConfiguration(){

if(configuration == null){

setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName));

try {

//重点就是这个reloadContainer

configuration.reloadContainer(getContainerProviders());

} catch(ConfigurationException e){

setConfiguration(null);

throw new ConfigurationException(“Unable to load configuration.”, e);

}

} else {

conditionalReload();

}

return configuration;

}

展开DefaultConfiguration中的reloadContainer

public synchronized List

reloadContainer(List providers)throws ConfigurationException {

packageContexts.clear();

loadedFileNames.clear();

List

packageProviders = new ArrayList

();

//Struts2(xwork2)用Container来完成依赖注入的功能

//首先初始化一个ContainerBuilder,再由builder来保存接口与实现类或工厂类的对应关系

//然后通过builder.create(boolean)方法产生container

//由container.getInstance(Class);就可以得到接口的实现实例了

//这一部分比较复杂,后面研究完成了,会单独拿出来讲,这里先弄清楚Xwork依赖注入的实现步骤就可以了

ContainerProperties props = new ContainerProperties();

ContainerBuilder builder = new ContainerBuilder();

for(final ContainerProvider containerProvider : providers)

{

//循环调用ConfigurationProvider的init和register方法,明白了吧,在这里统一循环调用

containerProvider.init(this);

containerProvider.register(builder, props);

}

props.setConstants(builder);

//注入依赖关系,在这里并不产生实例

builder.factory(Configuration.class, new Factory(){

public Configuration create(Context context)throws Exception {

return DefaultConfiguration.this;

}

});

ActionContext oldContext = ActionContext.getContext();

try {

// Set the bootstrap container for the purposes of factory creation

Container bootstrap = createBootstrapContainer();

setContext(bootstrap);

//create已经注入依赖关系的Container

container = builder.create(false);

setContext(container);

objectFactory = container.getInstance(ObjectFactory.class);

// Process the configuration providers first

for(final ContainerProvider containerProvider : providers)

{

if(containerProvider instanceof PackageProvider){

container.inject(containerProvider);

//调用PackageProvider的loadPackages()方法,这里主要是针对XmlConfigurationProvider和StrutsXmlConfigurationProvider

((PackageProvider)containerProvider).loadPackages();

packageProviders.add((PackageProvider)containerProvider);

}

}

// Then process any package providers from the plugins

Set

packageProviderNames

= container.getInstanceNames(PackageProvider.class);

if(packageProviderNames!= null){

for(String name : packageProviderNames){

PackageProvider

provider.init(this);

provider.loadPackages();

packageProviders.add(provider);

}

}

rebuildRuntimeConfiguration();

} finally {

if(oldContext == null){

ActionContext.setContext(null);

}

}

return packageProviders;

}

Dispatcher已经在之前讲过,这就好办了。FilterDispatcher是Struts2的核心控制器,首先看一下init()方法。

public void init(FilterConfig filterConfig)throws ServletException {

try {

this.filterConfig = filterConfig;

initLogging();

//创建dispatcher,前面都已经讲过啰

dispatcher = createDispatcher(filterConfig);

dispatcher.init();

//注入将FilterDispatcher中的变量通过container注入,如下面的staticResourceLoader

dispatcher.getContainer().inject(this);

//StaticContentLoader在BeanSelectionProvider中已经被注入了依赖关系:DefaultStaticContentLoader

//可以在struts-default.xml中的可以找到

staticResourceLoader.setHostConfig(new FilterHostConfig(filterConfig));

} finally {

provider

= container.getInstance(PackageProvider.class, name);

ActionContext.setContext(null);

}

}

public void init(FilterConfig filterConfig)throws ServletException {

try {

this.filterConfig = filterConfig;

initLogging();

//创建dispatcher,前面都已经讲过啰

dispatcher = createDispatcher(filterConfig);

dispatcher.init();

//注入将FilterDispatcher中的变量通过container注入,如下面的staticResourceLoader

dispatcher.getContainer().inject(this);

//StaticContentLoader在BeanSelectionProvider中已经被注入了依赖关系:DefaultStaticContentLoader

//可以在struts-default.xml中的可以找到

staticResourceLoader.setHostConfig(new FilterHostConfig(filterConfig));

} finally {

ActionContext.setContext(null);

}

}

//下面来看DefaultStaticContentLoader的setHostConfig

public void setHostConfig(HostConfig filterConfig){

//读取初始参数

pakages,调用

parse(),解析成类似/org/apache/struts2/static,/template的数组

String param = filterConfig.getInitParameter(“packages”);

//“org.apache.struts2.static org.apache.struts2.interceptor.debugging static”

String packages = getAdditionalPackages();

if(param!= null){

packages = param + “ ” + packages;

}

this.pathPrefixes = parse(packages);

initLogging(filterConfig);

}

template //下面来看DefaultStaticContentLoader的setHostConfig

public void setHostConfig(HostConfig filterConfig){

//读取初始参数

pakages,调用

parse(),解析成类似/org/apache/struts2/static,/template的数组

String param = filterConfig.getInitParameter(“packages”);

//“org.apache.struts2.static org.apache.struts2.interceptor.debugging static”

String packages = getAdditionalPackages();

if(param!= null){

packages = param + “ ” + packages;

}

this.pathPrefixes = parse(packages);

initLogging(filterConfig);

}

现在回去doFilter的方法,每当有一个Request,都会调用这些Filters的doFilter方法

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {

HttpServletRequest request =(HttpServletRequest)req;

HttpServletResponse response =(HttpServletResponse)res;

ServletContext servletContext = getServletContext();

String timerKey = “FilterDispatcher_doFilter: ”;

try {

// FIXME: this should be refactored better to not duplicate work with the action invocation

//先看看ValueStackFactory所注入的实现类OgnlValueStackFactory

//new OgnlValueStack

ValueStack

stack

= dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();

ActionContext ctx = new ActionContext(stack.getContext());

ActionContext.setContext(ctx);

template

UtilTimerStack.push(timerKey);

//如果是multipart/form-data就用MultiPartRequestWrapper进行包装

//MultiPartRequestWrapper

StrutsRequestWrapper的子类,两者都是HttpServletRequest实现

//此时在MultiPartRequestWrapper中就会把Files给解析出来,用于文件上传

//所有request都会StrutsRequestWrapper进行包装,StrutsRequestWrapper是可以访问ValueStack

//下面是参见Dispatcher的wrapRequest

// String content_type = request.getContentType();

//if(content_type!= null&&content_type.indexOf(“multipart/form-data”)!=-1){

//MultiPartRequest multi =getContainer().getInstance(MultiPartRequest.class);

//request MultiPartRequestWrapper(multi,request,getSaveDir(servletContext));

//} else {

//

request = new StrutsRequestWrapper(request);

// }

request = prepareDispatcherAndWrapRequest(request, response);

ActionMapping mapping;

try {

//根据url取得对应的Action的配置信息

//看一下注入的DefaultActionMapper的getMapping()方法.Action的配置信息存储在 ActionMapping对象中

mapping

=

actionMapper.getMapping(request, dispatcher.getConfigurationManager());

} catch(Exception ex){

log.error(“error getting ActionMapping”, ex);

dispatcher.sendError(request,return;

}

//如果找不到对应的action配置,则直接返回。比如你输入***.jsp等等

//这儿有个例外,就是如果path是以“/struts”开头,则到初始参数packages配置的包路径去查找对应的静态资源并输出到页面流中,当然.class文件除外。如果再没有则跳转到

response,servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);

=new 404

if(mapping == null){

// there is no action in this request, should we look for a static resource?

String resourcePath = RequestUtils.getServletPath(request);

if(“".equals(resourcePath)&& null!= request.getPathInfo()){

resourcePath = request.getPathInfo();

}

if(staticResourceLoader.canHandle(resourcePath)){

// 在DefaultStaticContentLoader

:return

serveStatic

&&(resourcePath.startsWith(”/struts“)|| resourcePath.startsWith(”/static“));

staticResourceLoader.findStaticResource(resourcePath, response);

} else {

// this is a normal request, let it pass through

chain.doFilter(request, response);

}

// The framework did its job here

return;

}

//正式开始Action的方法

dispatcher.serviceAction(request, response, servletContext, mapping);

} finally {

try {

ActionContextCleanUp.cleanUp(req);

} finally {

UtilTimerStack.pop(timerKey);

}

}

}

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {

HttpServletRequest request =(HttpServletRequest)req;

request,HttpServletResponse response =(HttpServletResponse)res;

ServletContext servletContext = getServletContext();

String timerKey = ”FilterDispatcher_doFilter: “;

try {

// FIXME: this should be refactored better to not duplicate work with the action invocation

//先看看ValueStackFactory所注入的实现类OgnlValueStackFactory

//new OgnlValueStack

ValueStack

stack

= dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();

ActionContext ctx = new ActionContext(stack.getContext());

ActionContext.setContext(ctx);

UtilTimerStack.push(timerKey);

//如果是multipart/form-data就用MultiPartRequestWrapper进行包装

//MultiPartRequestWrapperHttpServletRequest实现

//此时在MultiPartRequestWrapper中就会把Files给解析出来,用于文件上传

//所有request都会StrutsRequestWrapper进行包装,StrutsRequestWrapper是可以访问ValueStack

//下面是参见Dispatcher的wrapRequest

// String content_type = request.getContentType();

//if(content_type!= null&&content_type.indexOf(”multipart/form-data“)!=-1){

//MultiPartRequest multi =getContainer().getInstance(MultiPartRequest.class);

//request MultiPartRequestWrapper(multi,request,getSaveDir(servletContext));

//} else {

//

request = new StrutsRequestWrapper(request);

// }

request = prepareDispatcherAndWrapRequest(request, response);

ActionMapping mapping;

try {

=new

StrutsRequestWrapper的子类,两者都是

//根据url取得对应的Action的配置信息

//看一下注入的DefaultActionMapper的getMapping()方法.Action的配置信息存储在 ActionMapping对象中

mapping

} catch(Exception ex){

log.error(”error getting ActionMapping“, ex);

dispatcher.sendError(request,return;

}

//如果找不到对应的action配置,则直接返回。比如你输入***.jsp等等

//这儿有个例外,就是如果path是以“/struts”开头,则到初始参数packages配置的包路径去查找对应的静态资源并输出到页面流中,当然.class文件除外。如果再没有则跳转到404

if(mapping == null){

// there is no action in this request, should we look for a static resource?

String resourcePath = RequestUtils.getServletPath(request);

if(”“.equals(resourcePath)&& null!= request.getPathInfo()){

resourcePath = request.getPathInfo();

}

if(staticResourceLoader.canHandle(resourcePath)){

// 在DefaultStaticContentLoader

:return

serveStatic

&&(resourcePath.startsWith(”/struts“)|| resourcePath.startsWith(”/static“));

staticResourceLoader.findStaticResource(resourcePath, response);

} else {

// this is a normal request, let it pass through

chain.doFilter(request, response);

}

// The framework did its job here

return;

}

request,response,servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);

=

actionMapper.getMapping(request, dispatcher.getConfigurationManager());

//正式开始Action的方法

dispatcher.serviceAction(request, response, servletContext, mapping);

} finally {

try {

ActionContextCleanUp.cleanUp(req);

} finally {

UtilTimerStack.pop(timerKey);

}

}

}

//下面是ActionMapper接口的实现类 DefaultActionMapper的getMapping()方法的源代码:

public ActionMapping getMapping(HttpServletRequest request,ConfigurationManager configManager){

ActionMapping mapping = new ActionMapping();

String uri = getUri(request);//得到请求路径的URI,如:testAtcion.action或testAction.do

int indexOfSemicolon = uri.indexOf(”;“);//修正url的带;jsessionid 时找不到而且的bug

uri =(indexOfSemicolon >-1)? uri.substring(0, indexOfSemicolon): uri;

uri = dropExtension(uri, mapping);//删除扩展名,默认扩展名为action

if(uri == null){

return null;

}

parseNameAndNamespace(uri, mapping, configManager);//匹配Action的name和namespace

handleSpecialParameters(request, mapping);//去掉重复参数

//如果Action的name没有解析出来,直接返回

if(mapping.getName()== null){

returnnull;

}

//下面处理形如testAction!method格式的请求路径

if(allowDynamicMethodCalls){

// handle ”name!method“ convention.String name = mapping.getName();

int exclamation = name.lastIndexOf(”!“);//!是Action名称和方法名的分隔符

if(exclamation!=-1){

mapping.setName(name.substring(0, exclamation));//提取左边为name

mapping.setMethod(name.substring(exclamation + 1));//提取右边的method

}

}

return mapping;

}

//下面是ActionMapper接口的实现类 DefaultActionMapper的getMapping()方法的源代码:

public ActionMapping getMapping(HttpServletRequest request,ConfigurationManager configManager){

ActionMapping mapping = new ActionMapping();

String uri = getUri(request);//得到请求路径的URI,如:testAtcion.action或testAction.do

int indexOfSemicolon = uri.indexOf(”;“);//修正url的带;jsessionid 时找不到而且的bug

uri =(indexOfSemicolon >-1)? uri.substring(0, indexOfSemicolon): uri;

uri = dropExtension(uri, mapping);//删除扩展名,默认扩展名为action

if(uri == null){

return null;

}

parseNameAndNamespace(uri, mapping, configManager);//匹配Action的name和namespace

handleSpecialParameters(request, mapping);//去掉重复参数

//如果Action的name没有解析出来,直接返回

if(mapping.getName()== null){

returnnull;

}

//下面处理形如testAction!method格式的请求路径

if(allowDynamicMethodCalls){

// handle ”name!method“ convention.String name = mapping.getName();

int exclamation = name.lastIndexOf(”!“);//!是Action名称和方法名的分隔符

if(exclamation!=-1){

mapping.setName(name.substring(0, exclamation));//提取左边为name

mapping.setMethod(name.substring(exclamation + 1));//提取右边的method

}

}

return mapping;

}

从代码中看出,getMapping()方法返回ActionMapping类型的对象,该对象包含三个参数:Action的name、namespace和要调用的方法method。

如果getMapping()方法返回ActionMapping对象为null,则FilterDispatcher认为用户请求不是Action,自然另当别论,FilterDispatcher会做一件非常有意思的事:如果请求以/struts开头,会自动查找在web.xml文件中配置的 packages初始化参数,就像下面这样:

struts2

org.apache.struts2.dispatcher.FilterDispatcher

packages

com.lizanhong.action

struts2

org.apache.struts2.dispatcher.FilterDispatcher

packages

com.lizanhong.action

FilterDispatcher会将com.lizanhong.action包下的文件当作静态资源处理,即直接在页面上显示文件内容,不过会忽略扩展名为class的文件。比如在com.lizanhong.action包下有一个aaa.txt的文本文件,其内容为“中华人民共和国”,访问

http://localhost:8081/Struts2Demo/struts/aaa.txt时会输出txt中的内容

FilterDispatcher.findStaticResource()方法

protectedvoid findStaticResource(String

name,HttpServletRequest

request, HttpServletResponse response)throws IOException {

if(!name.endsWith(”.class“)){//忽略class文件

//遍历packages参数

for(String pathPrefix : pathPrefixes){

InputStream is = findInputStream(name, pathPrefix);//读取请求文件流

if(is!= null){

...// set the content-type header

String contentType = getContentType(name);//读取内容类型

if(contentType!= null){

response.setContentType(contentType);//重新设置内容类型

}

...try {

//将读取到的文件流以每次复制4096个字节的方式循环输出

copy(is, response.getOutputStream());

} finally {

is.close();

}

return;

}

}

}

}

protectedvoid findStaticResource(String

name,HttpServletRequest

request, HttpServletResponse response)throws IOException {

if(!name.endsWith(”.class“)){//忽略class文件

//遍历packages参数

for(String pathPrefix : pathPrefixes){

InputStream is = findInputStream(name, pathPrefix);//读取请求文件流

if(is!= null){

...// set the content-type header

String contentType = getContentType(name);//读取内容类型

if(contentType!= null){

response.setContentType(contentType);//重新设置内容类型

}

...try {

//将读取到的文件流以每次复制4096个字节的方式循环输出

copy(is, response.getOutputStream());

} finally {

is.close();

}

return;

}

}

}

}

如果用户请求的资源不是以/struts开头——可能是.jsp文件,也可能是.html文件,则通过过滤器链继续往下传送,直到到达请求的资源为止。

如果getMapping()方法返回有效的ActionMapping对象,则被认为正在请求某个Action,将调用 Dispatcher.serviceAction(request, response, servletContext, mapping)方法,该方法是处理Action的关键所在。

下面就来看serviceAction,这又回到全局变量dispatcher中了

//Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result.public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,ActionMapping mapping)throws ServletException {

//createContextMap方法主要把Application、Session、Request的key value值拷贝到Map中

Map extraContext = createContextMap(request, response, mapping, context);

// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action

ValueStack

stack

=

(ValueStack)request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);

boolean nullStack = stack == null;

if(nullStack){

ActionContext ctx = ActionContext.getContext();

if(ctx!= null){

stack = ctx.getValueStack();

}

}

if(stack!= null){

extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));

}

String timerKey = ”Handling request from Dispatcher“;

try {

UtilTimerStack.push(timerKey);

String namespace = mapping.getNamespace();

String name = mapping.getName();

String method = mapping.getMethod();

Configuration config = configurationManager.getConfiguration();

//创建一个Action的代理对象,ActionProxyFactory是创建ActionProxy的工厂

//参考实现类:DefaultActionProxy和DefaultActionProxyFactory

ActionProxy

proxy

= config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, extraContext, true, false);

request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

// if the ActionMapping says to go straight to a result, do it!

//如果是Result,则直接转向,关于Result,ActionProxy,ActionInvocation下一讲中再分析

if(mapping.getResult()!= null){

Result result = mapping.getResult();

result.execute(proxy.getInvocation());

} else {

//执行Action

proxy.execute();

}

// If there was a previous value stack then set it back onto the request

if(!nullStack){

request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);

}

} catch(ConfigurationException e){

// WW-2874 Only log error if in devMode

if(devMode){

LOG.error(”Could not find action or result“, e);

}

else {

LOG.warn(”Could not find action or result“, e);

}

sendError(request, HttpServletResponse.SC_NOT_FOUND, e);

} catch(Exception e){

sendError(request,} finally {

UtilTimerStack.pop(timerKey);

}

} 下面开始讲一下主菜ActionProxy了.在这之前最好先去了解一下动态Proxy的基本知识.ActionProxy是Action的一个代理类,也就是说Action的调用是通过ActionProxy实现的,其实就是调用了ActionProxy.execute()方法,而该方法又调用了ActionInvocation.invoke()方法。归根到底,最后调用的是DefaultActionInvocation.invokeAction()方法。DefaultActionInvocation()->init()->createAction()。

最后

用ActionProxy.exute()-->ActionInvocation.invoke()-->Intercepter.intercept()-->ActionInvocation.invokeActionOnly()-->invokeAction()这里的步骤是先由ActionProxyFactory创建ActionInvocation和ActionProxy.public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext, boolean executeResult, boolean cleanupContext){

ActionInvocation inv = new DefaultActionInvocation(extraContext, true);

container.inject(inv);

return }

public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map extraContext, boolean executeResult, boolean cleanupContext){

createActionProxy(inv,namespace,actionName,methodName, executeResult, cleanupContext);

response,context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);

response,context,ActionInvocation inv = new DefaultActionInvocation(extraContext, true);

container.inject(inv);

return }

下面先看DefaultActionInvocation的init方法

public void init(ActionProxy proxy){

this.proxy = proxy;

Map contextMap = createContextMap();

// Setting this so that other classes, like object factories, can use the ActionProxy and other

// contextual information to operate

ActionContext actionContext = ActionContext.getContext();

if(actionContext!= null){

actionContext.setActionInvocation(this);

}

//创建Action,struts2中每一个Request都会创建一个新的Action

createAction(contextMap);

if(pushAction){

stack.push(action);

contextMap.put(”action“, action);

}

invocationContext = new ActionContext(contextMap);

invocationContext.setName(proxy.getActionName());

// get a new List so we don't get problems with the iterator if someone changes the list

List

interceptorList

=

new ArrayList(proxy.getConfig().getInterceptors());

interceptors = interceptorList.iterator();

createActionProxy(inv,namespace,actionName,methodName, executeResult, cleanupContext);

}

protected void createAction(Map contextMap){

// load action

String timerKey = ”actionCreate: “ + proxy.getActionName();

try {

UtilTimerStack.push(timerKey);

//默认为SpringObjectFactory:struts.objectFactory=spring.这里非常巧妙,在struts.properties中可以重写这个属性

//在前面BeanSelectionProvider中通过配置文件为ObjectFactory设置实现类

//这里以Spring为例,这里会调到SpringObjectFactory的buildBean方法,可以通过ApplicationContext的getBean()方法得到Spring的Bean

action

=

objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);

} catch(InstantiationException e){

throw new

XWorkException(”Unable

to

intantiate

Action!“,e, proxy.getConfig());

} catch(IllegalAccessException e){

throw new XWorkException(”Illegal access to constructor, is it public?", e, proxy.getConfig());

} catch(Exception e){

...} finally {

UtilTimerStack.pop(timerKey);

}

if(actionEventListener!= null){

action = actionEventListener.prepare(action, stack);

}

}

//SpringObjectFactory

public Object buildBean(String beanName, Map extraContext, boolean injectInternal)throws Exception {

Object o = null;

try {

//SpringObjectFactory

web.xml

中的

第三篇:Android系统启动源代码调查分析

Android系统启动调查。

目的:Android程序入口在哪里?Mainifest配置文件如何加载实例化?从系统层到应用层如何使用?

目标从系统角度来了解Android启动过程,通过下载源代码并且根据源代码从底层开始跟踪,跟着方法走一遍Android启动过程。了解Zygote进程是什么?

开机一开始:Linux启动这一层,主要包括了两块:BootLoader(嵌入式系统的引导程序)和Kernel(Linux内核层,驱动层)第二块:Android系统启动。

我们都知道,Linux系统启动,定义了一个Init.rc这个系统启动的配置文件(放在System/bin文件下面)。

Init.rc启动的时候,最开始启动了SystemManager守护进程,它的源代码是一个Java文件,守护进程是一个与界面无关,会持续运行在后台,用来接收响应,并且维持系统运营的。

在启动servicemanager的同时,再来启动Zygote,Zygote实际上启动的是:app_main.cpp的系统文件.这个文件的main()方法,会调用Android_Runtime.cpp的文件中的start()方法,这个方法通过JNI机制,来调用ZygoteInit.java孵化器初始文件,这个文件的Main()函数,将会去调用所有进程。

这个ZygoteInit文件的main()函数,这个函数通过JNI机制调用了FrameWrok中的SystemServer文件,这个文件有三个函数:main(),init1()和init2()方法。

Init1()方法会通过JNI机制再去调用com_Android__server_SystemService.java的原生态文件,去实现系统初始化的操作,(调用System_init.cpp)。

当系统初始化工作做完之后,系统反过来会调用SystemServer文件下面的init2()方法,会通过runtime方法调用ServerThread进程去调用激活其他的所有进程。

第三块:应用程序启动(下次再讲)。

使用工具:【代码分析工具】source Insight 【源代码】 Android 源代码包

操作步骤:

在下载好Android SDK 安装包之后(如果没有下载好请移步这里)

【配置代码分析工具】

打开source Insight 软件,来配置Android源代码。

“项目”→“新建项目”

在“新项目名”填写:“Android 14”(Android 第14个版本,代表Android V4.0.3)在“项目文件储存位置”填写:SDK源代码包的位置

继续进行配置,点击确定。

选中右边的所有文件夹,点击“添加所有”按钮,将这个版本的源代码全部导入。

应用级别:选中将所有的子集目录,下级子目录中的所有文件都导入查找项目。

进行检索。。。

一共找到了“213720”个文件,是否导入?选中“Yes”

导入文件,索引建立

这时候,查看正下方,项目文件(213720)已经全部导入,项目准备完毕。可以进行调查了。

这时候你看到的右边工具栏,就是我们可以用来方便查找的搜索栏,输入对应的关键字即可。

切入正题,查找Android系统启动文件

【查找Init文件】启动方法会初始化MainiFest.xml配置文件,配置文件再去调用里面的配置,但是启动方法何时启动的调查,还未找到源头,只知道一切事物的源头,从这里开始。

原代码如下:

service console /system/bin/sh(启动Linux内核)

console

disabled

user shell

group log

on property:ro.secure=0

start console

# adbd is controlled by the persist.service.adb.enable system property service adbd /sbin/adbd

disabled

# adbd on at boot in emulator on property:ro.kernel.qemu=1

start adbd

on property:persist.service.adb.enable=1

start adbd

on property:persist.service.adb.enable=0

stop adbd

service servicemanager /system/bin/servicemanager(启动服务管理进程)

user system

critical

onrestart restart zygote

onrestart restart media

service vold /system/bin/vold

socket vold stream 0660 root mount

ioprio be 2

service netd /system/bin/netd

socket netd stream 0660 root system

socket dnsproxyd stream 0660 root inet

service debuggerd /system/bin/debuggerd

service ril-daemon /system/bin/rild

socket rild stream 660 root radio

socket rild-debug stream 660 radio system

user root

group radio cache inet misc audio sdcard_rw

service zygote /system/bin/app_process-Xzygote /system/bin--zygote--start-system-server

socket zygote stream 666

onrestart write /sys/android_power/request_state wake

onrestart write /sys/power/state on

onrestart restart media

onrestart restart netd

OK,现在先调查(ServerManager)这个启动进程。

在system/core/libsysutils/src 目录下(系统级启动进程)

(启动孵化器进程)

在左侧点击start方法

这就是守护进程中的源代码之一,start()方法 ServiceManager::ServiceManager(){ } int ServiceManager::start(const char *name){ //如果进程已经启动,那么打印日志:“XX进程已经启动”

if(isRunning(name)){

SLOGW(“Service '%s' is already running”, name);

return 0;

}

SLOGD(“Starting service '%s'”, name);

property_set(“ctl.start”, name);

int count = 200;

while(count--){

sched_yield();

if(isRunning(name))

break;

}

if(!count){

SLOGW(“Timed out waiting for service '%s' to start”, name);

errno = ETIMEDOUT;

return-1;

}

SLOGD(“Sucessfully started '%s'”, name);

return 0;}

再来看同时启动的app_main的源代码,我们去查看一下它的main函数

int main(int argc, const char* const argv[]){

// These are global variables in ProcessState.cpp

mArgC = argc;

mArgV = argv;

mArgLen = 0;

for(int i=0;i

mArgLen += strlen(argv[i])+ 1;

}

mArgLen--;

AppRuntime runtime;

const char *arg;

const char *argv0;

argv0 = argv[0];

// Process command line arguments

// ignore argv[0]

argc--;

argv++;

// Everything up to '--' or first non '-' arg goes to the vm

int i = runtime.addVmArguments(argc, argv);

// Next arg is parent directory

if(i < argc){

runtime.mParentDir = argv[i++];

}

// Next arg is startup classname or “--zygote”

if(i < argc){

arg = argv[i++];

if(0 == strcmp(“--zygote”, arg)){

bool startSystemServer =(i < argc)?

strcmp(argv[i], “--start-system-server”)== 0 : false;

setArgv0(argv0, “zygote”);

//设置了一个进程名叫zygote的进程,通过runtime来启动ZygoteInit文件中的startSystemServer方法

set_process_name(“zygote”);

runtime.start(“com.android.internal.os.ZygoteInit”,startSystemServer);

} else {

set_process_name(argv0);

runtime.mClassName = arg;

// Remainder of args get passed to startup class main()

runtime.mArgC = argc-i;

runtime.mArgV = argv+i;

LOGV(“App process is starting with pid=%d, class=%s.n”,getpid(), runtime.getClassName());

runtime.start();

}

} else {

LOG_ALWAYS_FATAL(“app_process: no class name or--zygote supplied.”);

fprintf(stderr, “Error: no class name or--zygote supplied.n”);

app_usage();

return 10;

} } 调查一下runtime的类。AppRuntime,这就是android系统的运行时类,它启动了zygote孵化器进程,用来孵化Davlik虚拟机的。

runtime.start(“com.android.internal.os.ZygoteInit”,startSystemServer);所涉及到的ZygoteInit文件。

找到ZygoteInit文件(FrameWork里面的一个java类)。先去看看Main函数。

public static void main(String argv[]){

try {

VMRuntime.getRuntime().setMinimumHeapSize(5 * 1024 * 1024);

// Start profiling the zygote initialization.SamplingProfilerIntegration.start();

registerZygoteSocket();

EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,SystemClock.uptimeMillis());

preloadClasses();

//cacheRegisterMaps();

preloadResources();

EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,SystemClock.uptimeMillis());

// Finish profiling the zygote initialization.SamplingProfilerIntegration.writeZygoteSnapshot();

// Do an initial gc to clean up after startup

gc();

// If requested, start system server directly from Zygote

if(argv.length!= 2){

throw new RuntimeException(argv[0] + USAGE_STRING);

}

if(argv[1].equals(“true”)){ //如果输入参数为真,我们就启动系统服务

startSystemServer();

} else if(!argv[1].equals(“false”)){

throw new RuntimeException(argv[0] + USAGE_STRING);

}

Log.i(TAG, “Accepting command socket connections”);

if(ZYGOTE_FORK_MODE){ //如果孵化器一直是交叉模式,就启动运行交叉模式函数;否则就选择另一个循环模式

runForkMode();

} else {

runSelectLoopMode();

}

closeServerSocket();

} catch(MethodAndArgsCaller caller){

caller.run();

} catch(RuntimeException ex){

Log.e(TAG, “Zygote died with exception”, ex);

closeServerSocket();

throw ex;

}

}

我们继续查看,如果参数为真的情况下,ZygoteInit文件中的,startSystemServer()函数的源代码。

/**

* Prepare the arguments and fork for the system server process.*/

private static boolean startSystemServer()

throws MethodAndArgsCaller, RuntimeException {

/* Hardcoded command line to start the system server */

String args[] = {

“--setuid=1000”,“--setgid=1000”,“--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003”,“--capabilities=130104352,130104352”,“--runtime-init”,“--nice-name=system_server”,“com.android.server.SystemServer”,//这个虚拟机的名字叫system Server

};

ZygoteConnection.Arguments parsedArgs = null;

int pid;

try {

parsedArgs = new ZygoteConnection.Arguments(args);

/*

* Enable debugging of the system process if *either* the command line flags

* indicate it should be debuggable or the ro.debuggable system property

* is set to “1”

*/

int debugFlags = parsedArgs.debugFlags;

if(“1”.equals(SystemProperties.get(“ro.debuggable”)))

debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;

/* Request to fork the system server process */

pid = Zygote.forkSystemServer(parsedArgs.uid, parsedArgs.gid,parsedArgs.gids, debugFlags, null,parsedArgs.permittedCapabilities,parsedArgs.effectiveCapabilities);

} catch(IllegalArgumentException ex){

throw new RuntimeException(ex);

}

/* For child process */

if(pid == 0){

handleSystemServerProcess(parsedArgs);

}

return true;

}

我们继续去查看 system Server的源代码

main函数:

/**

* This method is called from Zygote to initialize the system.This will cause the native

* services(SurfaceFlinger, AudioFlinger, etc..)to be started.After that it will call back

* up into init2()to start the Android services.*/

native public static void init1(String[] args);//Init1()函数却是个空函数

public static void main(String[] args){

if(System.currentTimeMillis()< EARLIEST_SUPPORTED_TIME){

// If a device's clock is before 1970(before 0), a lot of

// APIs crash dealing with negative numbers, notably

// java.io.File#setLastModified, so instead we fake it and

// hope that time from cell towers or NTP fixes it

// shortly.Slog.w(TAG, “System clock is before 1970;setting to 1970.”);

SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);

}

if(SamplingProfilerIntegration.isEnabled()){

SamplingProfilerIntegration.start();

timer = new Timer();

timer.schedule(new TimerTask(){

@Override

public void run(){

SamplingProfilerIntegration.writeSnapshot(“system_server”);

}

}, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);

}

// The system server has to run all of the time, so it needs to be

// as efficient as possible with its memory usage.VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);

System.loadLibrary(“android_servers”);

init1(args);// main()函数中,会调用到 init1()的方法。

}

public static final void init2(){

Slog.i(TAG, “Entered the Android system server!”);

Thread thr = new ServerThread();

thr.setName(“android.server.ServerThread”);

thr.start();

}

因为通过调查发现,SystemServer文件的main()函数调用的init1()函数,是一个空方法,native public static void init1(String[] args);

但是根据JNI调用机制,我们可以在同名文件夹(framework/base/services/)下找到JNL目录,然后找到和系统相关的com_android_server_SystemServer.java文件

使用“C”的动态链接嗲用system_init 的方法。它去回调Init2的方法

我们继续看看SystemServer方法的Init2()方法是看什么用的。

去调查一下ServerThread()方法是干什么用的?这个内部类ServerThread就是启动,并且实例化每一个系统进程的线程类

class ServerThread extends Thread {

private static final String TAG = “SystemServer”;

private final static boolean INCLUDE_DEMO = false;

private static final int LOG_BOOT_PROGRESS_SYSTEM_RUN = 3010;

private ContentResolver mContentResolver;

private class AdbSettingsObserver extends ContentObserver {

public AdbSettingsObserver(){

super(null);

}

@Override

public void onChange(boolean selfChange){

boolean enableAdb =(Settings.Secure.getInt(mContentResolver,Settings.Secure.ADB_ENABLED, 0)> 0);

// setting this secure property will start or stop adbd

SystemProperties.set(“persist.service.adb.enable”, enableAdb ? “1” : “0”);

}

}

@Override

public void run(){

EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN,SystemClock.uptimeMillis());

Looper.prepare();

android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_FOREGROUND);

BinderInternal.disableBackgroundScheduling(true);

android.os.Process.setCanSelfBackground(false);

// Check whether we failed to shut down last time we tried.{

final String shutdownAction = SystemProperties.get(ShutdownThread.SHUTDOWN_ACTION_PROPERTY, “");

if(shutdownAction!= null && shutdownAction.length()> 0){

boolean reboot =(shutdownAction.charAt(0)== '1');

final String reason;

if(shutdownAction.length()> 1){

reason = shutdownAction.substring(1, shutdownAction.length());

} else {

reason = null;

}

ShutdownThread.rebootOrShutdown(reboot, reason);

}

}

String factoryTestStr = SystemProperties.get(”ro.factorytest“);

int factoryTest = ”“.equals(factoryTestStr)? SystemServer.FACTORY_TEST_OFF

: Integer.parseInt(factoryTestStr);

LightsService lights = null;

PowerManagerService power = null;

BatteryService battery = null;

ConnectivityService connectivity = null;

IPackageManager pm = null;

Context context = null;

WindowManagerService wm = null;

BluetoothService bluetooth = null;

BluetoothA2dpService bluetoothA2dp = null;

HeadsetObserver headset = null;

DockObserver dock = null;

UsbService usb = null;

UiModeManagerService uiMode = null;

RecognitionManagerService recognition = null;

ThrottleService throttle = null;

// Critical services...try {

Slog.i(TAG, ”Entropy Service“);

ServiceManager.addService(”entropy“, new EntropyService());

Slog.i(TAG, ”Power Manager“);

power = new PowerManagerService();

ServiceManager.addService(Context.POWER_SERVICE, power);

Slog.i(TAG, ”Activity Manager“);

context = ActivityManagerService.main(factoryTest);

Slog.i(TAG, ”Telephony Registry“);

ServiceManager.addService(”telephony.registry“, new TelephonyRegistry(context));

AttributeCache.init(context);

Slog.i(TAG, ”Package Manager“);

pm = PackageManagerService.main(context,factoryTest!= SystemServer.FACTORY_TEST_OFF);

ActivityManagerService.setSystemProcess();

mContentResolver = context.getContentResolver();

// The AccountManager must come before the ContentService

try {

Slog.i(TAG, ”Account Manager“);

ServiceManager.addService(Context.ACCOUNT_SERVICE,new AccountManagerService(context));

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Account Manager“, e);

}

Slog.i(TAG, ”Content Manager“);

ContentService.main(context,factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL);

Slog.i(TAG, ”System Content Providers“);

ActivityManagerService.installSystemProviders();

Slog.i(TAG, ”Battery Service“);

battery = new BatteryService(context);

ServiceManager.addService(”battery“, battery);

Slog.i(TAG, ”Lights Service“);

lights = new LightsService(context);

Slog.i(TAG, ”Vibrator Service“);

ServiceManager.addService(”vibrator“, new VibratorService(context));

// only initialize the power service after we have started the

// lights service, content providers and the battery service.power.init(context, lights, ActivityManagerService.getDefault(), battery);

Slog.i(TAG, ”Alarm Manager“);

AlarmManagerService alarm = new AlarmManagerService(context);

ServiceManager.addService(Context.ALARM_SERVICE, alarm);

Slog.i(TAG, ”Init Watchdog“);

Watchdog.getInstance().init(context, battery, power, alarm,ActivityManagerService.self());

Slog.i(TAG, ”Window Manager“);

wm = WindowManagerService.main(context, power,factoryTest!= SystemServer.FACTORY_TEST_LOW_LEVEL);

ServiceManager.addService(Context.WINDOW_SERVICE, wm);

((ActivityManagerService)ServiceManager.getService(”activity“)).setWindowManager(wm);

// Skip Bluetooth if we have an emulator kernel

// TODO: Use a more reliable check to see if this product should

// support Bluetooth-see bug 988521

if(SystemProperties.get(”ro.kernel.qemu“).equals(”1“)){

Slog.i(TAG, ”Registering null Bluetooth Service(emulator)“);

ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, null);

} else if(factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL){

Slog.i(TAG, ”Registering null Bluetooth Service(factory test)“);

ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, null);

} else {

Slog.i(TAG, ”Bluetooth Service“);

bluetooth = new BluetoothService(context);

ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, bluetooth);

bluetooth.initAfterRegistration();

bluetoothA2dp = new BluetoothA2dpService(context, bluetooth);

ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE,bluetoothA2dp);

int bluetoothOn = Settings.Secure.getInt(mContentResolver,Settings.Secure.BLUETOOTH_ON, 0);

if(bluetoothOn > 0){

bluetooth.enable();

}

}

} catch(RuntimeException e){

Slog.e(”System“, ”Failure starting core service“, e);

}

DevicePolicyManagerService devicePolicy = null;

StatusBarManagerService statusBar = null;

InputMethodManagerService imm = null;

AppWidgetService appWidget = null;

NotificationManagerService notification = null;

WallpaperManagerService wallpaper = null;

LocationManagerService location = null;

if(factoryTest!= SystemServer.FACTORY_TEST_LOW_LEVEL){

try {

Slog.i(TAG, ”Device Policy“);

devicePolicy = new DevicePolicyManagerService(context);

ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, devicePolicy);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting DevicePolicyService“, e);

}

try {

Slog.i(TAG, ”Status Bar“);

statusBar = new StatusBarManagerService(context);

ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting StatusBarManagerService“, e);

}

try {

Slog.i(TAG, ”Clipboard Service“);

ServiceManager.addService(Context.CLIPBOARD_SERVICE,new ClipboardService(context));

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Clipboard Service“, e);

}

try {

Slog.i(TAG, ”Input Method Service“);

imm = new InputMethodManagerService(context, statusBar);

ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Input Manager Service“, e);

}

try {

Slog.i(TAG, ”NetStat Service“);

ServiceManager.addService(”netstat“, new NetStatService(context));

} catch(Throwable e){

Slog.e(TAG, ”Failure starting NetStat Service“, e);

}

try {

Slog.i(TAG, ”NetworkManagement Service“);

ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE,NetworkManagementService.create(context));

} catch(Throwable e){

Slog.e(TAG, ”Failure starting NetworkManagement Service“, e);

}

try {

Slog.i(TAG, ”Connectivity Service“);

connectivity = ConnectivityService.getInstance(context);

ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Connectivity Service“, e);

}

try {

Slog.i(TAG, ”Throttle Service“);

throttle = new ThrottleService(context);

ServiceManager.addService(Context.THROTTLE_SERVICE, throttle);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting ThrottleService“, e);

}

try {

Slog.i(TAG, ”Accessibility Manager“);

ServiceManager.addService(Context.ACCESSIBILITY_SERVICE,new AccessibilityManagerService(context));

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Accessibility Manager“, e);

}

try {

/*

* NotificationManagerService is dependant on MountService,*(for media / usb notifications)so we must start MountService first.*/

Slog.i(TAG, ”Mount Service“);

ServiceManager.addService(”mount“, new MountService(context));

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Mount Service“, e);

}

try {

Slog.i(TAG, ”Notification Manager“);

notification = new NotificationManagerService(context, statusBar, lights);

ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Notification Manager“, e);

}

try {

Slog.i(TAG, ”Device Storage Monitor“);

ServiceManager.addService(DeviceStorageMonitorService.SERVICE,new DeviceStorageMonitorService(context));

} catch(Throwable e){

Slog.e(TAG, ”Failure starting DeviceStorageMonitor service“, e);

}

try {

Slog.i(TAG, ”Location Manager“);

location = new LocationManagerService(context);

ServiceManager.addService(Context.LOCATION_SERVICE, location);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Location Manager“, e);

}

try {

Slog.i(TAG, ”Search Service“);

ServiceManager.addService(Context.SEARCH_SERVICE,new SearchManagerService(context));

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Search Service“, e);

}

if(INCLUDE_DEMO){

Slog.i(TAG, ”Installing demo data...“);

(new DemoThread(context)).start();

}

try {

Slog.i(TAG, ”DropBox Service“);

ServiceManager.addService(Context.DROPBOX_SERVICE,new DropBoxManagerService(context, new File(”/data/system/dropbox“)));

} catch(Throwable e){

Slog.e(TAG, ”Failure starting DropBoxManagerService“, e);

}

try {

Slog.i(TAG, ”Wallpaper Service“);

wallpaper = new WallpaperManagerService(context);

ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Wallpaper Service“, e);

}

try {

Slog.i(TAG, ”Audio Service“);

ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));

} catch(Throwable e){

Slog.e(TAG, ”Failure starting Audio Service“, e);

}

try {

Slog.i(TAG, ”Headset Observer“);

// Listen for wired headset changes

headset = new HeadsetObserver(context);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting HeadsetObserver“, e);

}

try {

Slog.i(TAG, ”Dock Observer“);

// Listen for dock station changes

dock = new DockObserver(context, power);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting DockObserver“, e);

}

try {

Slog.i(TAG, ”USB Service“);

// Listen for USB changes

usb = new UsbService(context);

ServiceManager.addService(Context.USB_SERVICE, usb);

} catch(Throwable e){

Slog.e(TAG, ”Failure starting UsbService“, e);

}

try {

Slog.i(TAG, ”UI Mode Manager Service");

第四篇:非常完美电视节目分析

三、非常完美电视节目分析

节目类型:综艺类相亲节目 节目时常:1h30min 目标人群:适龄男女及其父母

一:片头 15s AE 二:内容

1、主持人出场、喊口号(接广告)50s 现场画面

2、主持人引入、嘉宾出场 1min50s 现场画面

3、恋爱公开课 2min30s VCR短片

4、女嘉宾1 17min25s VCR自我介绍 现场画面 人物出场 现场画面 男嘉宾表态 现场画面 摘面具 现场画面 选择心动男生 现场画面 与男嘉宾互动 现场画面+弹幕 男嘉宾进行选择 现场画面 女生告白环节 现场画面+VCR 情感专家点评 现场画面

5、女嘉宾 2 略

VCR自我介绍 现场画面 人物出场 现场画面 男嘉宾表态 现场画面 摘面具 现场画面 女生告白环节 现场画面+视频 男嘉宾反问 现场画面 四位嘉宾环节类似

6、结尾

节目总结:

节目亮点:往期节目的亮点在于男嘉宾

个人感受:此档节目属于综艺类相亲节目,节目制作的主要难点在于主持人对于现场的把控、以及情感专家对问题的点评;在拍摄过程中,没有太多复杂的镜头需要去拍,主要以固定镜头摇镜头为主。本期节目,编导对节目重心选择有误,主要的节目都在嘉宾方面,内容拖沓,主持人缺乏引入,专家露面太少,告别部分过于狗血,强行煽泪。男嘉宾一直是那几位,看过了很多期次节目之后,感觉已经没有可以吸引女性观众眼球的地方。

第五篇:我个人觉得一节语文课

我个人觉得一节语文课,如果开头导入得好,这节课也就成功了一半。这位老师做到了!以讲故事的形式开始一节课,吸引学生的兴趣,讲到兴头上时,老师突然打住,说:“想知道下面发生了什么吗?好,打开课本,看课文。”制造了悬念,让学生想学课文,激发求知的欲望。并且这位老师在让学生读课文的时候也指出了朗读要求,如:注意生字读音,大声读课文,标出自然段。

很喜欢这位老师语文课阅读的方式,多样有趣。有开火车式的,一人读一段,分自然段读;整体读,或者互相读,在学生读的过程中,老师走下讲台进行个别辅导。

在基础知识解决完了之后,直接进入课文的学习阶段。由老师读课文,帮助学生正字正音。然后提问学生,读完之后知道什么,不明白什么。学生也很配合,大胆提出了很多稀奇古怪的问题,不过这也正表明孩子们天真的想象力,他们敢于说出自己内心的想法,哪怕很简单甚至很愚蠢,作为教师都应该去鼓励,而不是批评。这位老师对于学生提出的问题都很肯定,并让学生回到课文里去找寻答案,这种做法,可以让学生通过读课文,自己独立解决先前提出的问题。之后老师运用多媒体展示船行的过程以及剑掉入江里的动态图画,帮助学生理解,以便于学生回答自己提出的诸多问题。

这位老师还注意课堂学生的交流合作,就能否捞剑这一话题让学生进行讨论并阐述理由。老师走下去个别交流,了解学生的想法。在捞得着与捞不着之间展开讨论分析,各圆其说,发散思维,只要说的有道理,老师都用一种肯定的眼神关注着这个学生。老师运用纸片展示船行的过程,解释为什么捞不到剑,也就顺理成章。

最后,由课文联系到自己的生活,学到了什么,揭示课文的主旨。这篇课文的学习也就暂告一段落了。总体上感觉符合小学生的认知结构,从趣味入手,让学生快乐学习。

有一点我不太明白,为什么要让学生把书反过来放在桌上,语文课,不是让学生直接面对课文吗?这样做难道是为了让学生对刚才的阅读进行回顾?这是一节新课,不是复习课啊!

下载struts2源代码分析(个人觉得非常经典)word格式文档
下载struts2源代码分析(个人觉得非常经典).doc
将本文档下载到自己电脑,方便修改和收藏,请勿使用迅雷等下载。
点此处下载文档

文档为doc格式


声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:645879355@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。

相关范文推荐

    个人通讯录管理系统,java源代码

    package cn.pab.manager; import java.util.List; import java.util.Scanner; import cn.pab.dao.PersonDao; import cn.pab.dao.TypeDao; import cn.pab.dao.UserDao; im......

    个人述职报告模板(非常实用)

    个人述职报告模板(非常实用)述职是管理干部依据自己的职务要求,就一定时期内的任期目标,向主管部门及本企业的全体工友,汇报自己履行岗位责任情况,是干部管理考核的一种形式。个......

    个人优缺点评价(非常实用)

    个人优缺点评价(非常实用) 1. 优点:乐于助人,善于团结,吃苦耐劳,平易近人。 缺点:工作作风过于拘谨不够大胆 2.优点:尊敬师长,团结同学,乐于助人,是老师的好帮手,同学的好朋友, 学习勤奋,积......

    个人觉得比较好的英文句子

    英语名句 1. ...And so on and forth and so fifth! 真是没完没了!2.Age before beauty! 长者优先。3.Old habits die hard! 积习难改。4.Never say die. 永不言败。5.Hang i......

    C语言面试题大汇总,个人觉得还是比较全

    4. static有什么用途?(请至少说明两种) 1.限制变量的作用域 2.设置变量的存储域 7. 引用与指针有什么区别? 1) 引用必须被初始化,指针不必。 2) 引用初始化以后不能被改变,指针可......

    就业指导ppt整理(个人觉得相对比较重要的)(推荐)

    就业指导ppt整理 派遣的有关规定: (1)毕业生的报到期限一般为一个月。 (2)毕业生上半月报到的,发给全月工资;下半月报到的,发给半月工资。 (3)三年以上固定期限和无固定期限劳动合同的......

    银行个人述职报告(非常不错)

    尊敬的各位领导、同事们,大家好! 2010年是我从事客户工作的第一年。这一年栉风沐雨、耕耘收获;这一年忙碌充实、紧张有序;这一年学习思考、感触良多。 感触之一:德行品质得到了锻......

    非常实用的个人述职报告供借鉴

    5篇非常实用的个人述职报告供借鉴尊敬的领导:今年,在各级领导的正确指引下,我始终恪尽职守、团结同事,坚决服从各项管理方针、政策,想方设法推进工作进步。以下是本人的述职报告:......