上篇文章中,准备好了jar包,接下来就是写配置文件了.(说明一下,使用的IDE是eclipse,而建立的项目是动态web项目).其实不一定要准备好所有的jar,才开始写配置文件,如准备好hibernate的jar之后,就可以写hibernate的配置文件,并写测试代码,测试hibernate是否可用了.
这配置文件共4个,分别是,web.xml,spring-mvc.xml以及applicationContext.xml,hibernate.cfg.xml.一般是放在项目的WEB-INF文件中,比较安全.其中,web.xml中配置一些监听器和过滤器,在tomcat启动时,读取web.xml,再根据配置在web.xml的配置,去读取spring-mvc.xml,applicationContext.xml.
而applicationContext.xml是spring的配置文件,用来配置sessionFactory以及事务,这里用的是声明式事务.还有spring-mvc.xml配置的spring-mvc的,主要配置视图解析器,还有扫描包,设置注解驱动,以及配置解除mvc对资源的拦截.
现在依次介绍4个xml文件.
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- 上下文初始化参数,找到spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 找到spring-mvc的配置文件和spring的配置文件 -->
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml,/WEB-INF/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置编码过滤器,防止乱码 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
该配置文件,从上到下,依次配置了4个点,context-param上下文参数节点,listener监听器节点,servlet节点,和filter过滤节点.从比较简单的开始介绍.
1.filter过滤器节点
<!-- 配置编码过滤器,防止乱码 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
filter过滤器节点,在这里是配置了一个编码的过滤器,将所有的(/*)的POST请求都拦截下来,强制设置为utf-8的编码,防止编码不一致的乱码,不过只对post请求起作用. 而这个编码的类,是spring-web自己就提供的一个类.
反编译出来的代码,我给添加了注释.看了源码之后,就更加明白,为什么要那么配置characterEncodingFilter了.
package org.springframework.web.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CharacterEncodingFilter extends OncePerRequestFilter
{
//属性,接收编码和是否强制编码
private String encoding;
private boolean forceEncoding = false;
//设置编码
public void setEncoding(String encoding)
{
this.encoding = encoding;
}
//设置强制编码
public void setForceEncoding(boolean forceEncoding)
{
this.forceEncoding = forceEncoding;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException
{
//只要请求中设置了编码,或者只要设置了编码,并且强制编码为ture,就执行下面的
if ((this.encoding != null) && ((this.forceEncoding) || (request.getCharacterEncoding() == null))) {
//请求就设置编码
request.setCharacterEncoding(this.encoding);
//如果是强制编码
if (this.forceEncoding) {
//则响应也设置编码
response.setCharacterEncoding(this.encoding);
}
}
//到过滤器链的下一个过滤器
filterChain.doFilter(request, response);
}
}
2.listener
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
监听器节点也是用的spring-web中的一个类
反编译出来是这样的效果,本类中的代码,很少,基本上都是去调用父类中的方法.
package org.springframework.web.context;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//ContextLoaderListener监听器,继承ContextLoader,实现了ServletContextListener的两个接口
//contextInitialized和contextDestroyed
public class ContextLoaderListener extends ContextLoader
implements ServletContextListener
{
//构造方式
public ContextLoaderListener()
{
}
//带参数的构造方法,传递参数WebApplicationContext,web应用的上下文
public ContextLoaderListener(WebApplicationContext context)
{
super(context);
}
//上下文初始化
public void contextInitialized(ServletContextEvent event)
{
//调用父类的initWebApplicationContext,初始化web应用上下文方法
initWebApplicationContext(event.getServletContext());
}
//销毁上下文
public void contextDestroyed(ServletContextEvent event)
{
//调用父类的关闭wen应用上下文
closeWebApplicationContext(event.getServletContext());
//以及另一个监听器的清除属性的方法
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
但是从代码中还是可以看出,主要功能就是接收到web应用中的上下文,将其初始化,或者销毁.而WebApplicationContext ,则是context-param中配置的,所以context-param和listener节点应该是配合使用的.
下面是我从网上找到的,可能更方便理解.
context-param的作用是声明应用范围内的上下文初始化参数,范围是整个WEB项目。
初始化过程介绍:
1.在启动Web项目时,容器(比如Tomcat)会读web.xml配置文件中的两个节点<listener>和<contex-param>。
2.接着容器会创建一个ServletContext(上下文),应用范围内即整个WEB项目都能使用这个上下文。
3.接着容器会将读取到<context-param>转化为键值对,并交给ServletContext。
4.容器创建<listener></listener>中的类实例,即创建监听(备注:listener定义的类可以是自定义的类但必须需要继承ServletContextListener)。
5.在监听的类中会有一个contextInitialized(ServletContextEvent event)初始化方法,在这个方法中可以通过event.getServletContext().getInitParameter("contextConfigLocation")来得到context-param 设定的值。在这个类中还必须有一个contextDestroyed(ServletContextEvent event)销毁方法.用于关闭应用前释放资源,比如说数据库连接的关闭。
6.得到这个context-param的值之后,你就可以做一些操作了.注意,这个时候你的WEB项目还没有完全启动完成.这个动作会比所有的Servlet都要早。
7.由上面的初始化过程可知容器对于web.xml的加载过程是context-param>> listener >> fileter >> servlet.
3.context-param节点
<!-- 上下文初始化参数,找到spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
所以根据上面的说法,这个上下文参数的配置的作用,就是将参数名为contextConfigLocation的,参数值为/WEB-INF/applicationContext.xml初始化到上下文参数中,并且该参数的作用范围为整个应用程序.
那段初始化的代码,是这样子的。
public class ContextLoader
{ //感觉父类中没用上
public static final String CONTEXT_ID_PARAM = "contextId";
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies; //这个是map
private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread = new ConcurrentHashMap(1); //这个是多线程时的web应用上下文,具体不懂
private static volatile WebApplicationContext currentContext; //initWebApplicationContext方法,就是在给这个属性,赋值;
private WebApplicationContext context;
private BeanFactoryReference parentContextRef;
public ContextLoader()
{
}
public ContextLoader(WebApplicationContext context)
{
this.context = context;
}
//初始化web应用上下文,根据servlet上下文
public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
{
//如果上下文中"根web应用上下文属性"不为空,则抛错
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
}
//记录日志
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
//记录当前时间,用来放在日志中,记录时间
long startTime = System.currentTimeMillis();
//作用,给context(web应用上下文)赋上值
try
{
//如果web应用上下文属性为空,就创建一个web应用上下文
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
//如果是可配置的web应用上下文类型的
if ((this.context instanceof ConfigurableWebApplicationContext)) {
//就强转成可配置的web应用上下文类型
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
//判断若不是活动状态的
if (!cwac.isActive())
{
//并且可配置的web应用上下文的parent属性是空的
if (cwac.getParent() == null)
{
//加载父上下文,应用上下文
ApplicationContext parent = loadParentContext(servletContext);
//并且给可配置的web应用上下文的parent属性赋上值
cwac.setParent(parent);
}
//然后配置并且刷新一下web应用上下文,根据可配置的web应用上下文和servlet上下文
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//给servlet上下文的"根web应用上下文属性"设置值,值就是context
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
//从当前线程中获取类加载器
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
//若该类加载和上下文加载器的类加载器是一样的
if (ccl == ContextLoader.class.getClassLoader()) {
//就将web应用上下文,给当前上下文
currentContext = this.context;
}
//若当前线程中,没有类加载器,
else if (ccl != null) {
//则将类加载器和web应用上下文,作为key,value放到当前上下文之前的线程的map中
currentContextPerThread.put(ccl, this.context);
}
//记录bug级别的日志
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
//记录info级别的日志,并记录时间
if (logger.isInfoEnabled()k) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
//该初始化的方法,最后会返回准备好的web应用上下文context
return this.context;
}
catch (RuntimeException ex) {
//出错的话,就记录下来
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
//出错的话,就记录下来
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
//配置并刷新web应用上下文
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId()))
{
//获取servlet上下文中的初始化参数contextId
String idParam = sc.getInitParameter("contextId");
//不为null的话,这就是可配置的web应用上下文对象的id了
if (idParam != null) {
wac.setId(idParam);
}
else
{
//否则,就用路径来做id
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc
.getContextPath()));
}
}
//设置可配置的web应用上下文对象的servletContext
wac.setServletContext(sc);
//获取servlet上下文中的初始化参数contextConfigLocation,这个就是xml中配置的
String configLocationParam = sc.getInitParameter("contextConfigLocation");
//有值的话,这就是可配置的web应用上下文对象的ConfigLocation了
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
//获取可配置环境
ConfigurableEnvironment env = wac.getEnvironment();
//初始化属性资源
if ((env instanceof ConfigurableWebEnvironment)) {
((ConfigurableWebEnvironment)env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
//可配置的web应用上下文对象,刷新一下
wac.refresh();
} //其他代码都省略
}
其实写完注释,我也没大懂,就只知道大概的东西.还有contextConfigLocation,是上下文的初始化参数.
4.servlet节点
<!-- 找到spring-mvc的配置文件和spring的配置文件 -->
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml,/WEB-INF/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在servlet节点中配置了servlet-name为action,其实改为springmvc会更好理解,action是struts中的,而我们的框架是springmvc.
DispatcherServlet是spring-webmvc下的一个类.
DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。
DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:
1、文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
2、通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
3、 通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);
4、通过ViewResolver解析逻辑视图名到具体视图实现;
5、本地化解析;
6、渲染具体的视图等;
7、如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。
从配置中可以看出,主要是拦截(/)所有请求,然后用DispatcherServlet进行职责调度.
在容器(Tomcat)启动时,启动自己配置的初始化参数/WEB-INF/spring-mvc.xml,/WEB-INF/applicationContext.xml作为初始化的上下文,而这个上下文的作用范围是只对spring web mvc的bean有效,如controller.而在context-param的init-param的作用范围是整个应用程序共享的.
以上就是web.xml中的配置的一些解释,以前也没这么研究过,还是有很多不明白的,自己也没懂. 不过觉得看源码是很有用的.