1. web.xml
关于Servlet
Servlet是一个特殊的Java类,Servlet可以输出HTML标签作为表现层使用,但自从MVC规范出来以后,明确了Servlet的指责仅作为控制器使用,不再生成页面标签,也不再作为视图层角色使用。
Servlet类继承自HttpServlet类,包含了常用处理浏览器请求的方法如doGet, doPost, doPut, doDelete, init, destroy等。
一个典型Servlet接受客户端请求的场景如
1 @WebServlet(name="firstServlet", urlPatterns={"/firstServlet"}) 2 public class FirstServlet extends HttpServlet 3 { 4 public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, java.io.IOException 5 { 6 String name = request.getParameterValues("name"); 7 PrintStream out = new PrintStream(response.getOutputStream); 8 out.println("name="+name+"<br/); 9 } 10 }
1.1配置文件介绍
web.xml是servlet2.5以前java web必须包含的配置文件,web.xml作为一个web应用的入口,将被web容器(例如tomcat的Context容器)解析并初始化。
web.xml主要用来配置以下信息
- 配置JSP,以及JSP属性
- 配置Servlet
- 配置Listener
- 配置Filter
- 配置标签库
- 配置和管理JAAS授权认证
- 配置和管理资源引用
- 配置应用首页:<welcom-file-list>
1.2.web.xml 配置文件内容
1.2.1 配置Servlet
一个Servlet配置项由<servlet>和<servlet-mapping>两个子元素组成。
其中<servlet>用来配置Servlet的名称,处理Servlet的类,初始化参数等。
<servlet-mapping>用来配置Servlet的URL,这项为可选。
下面这个例子是在J2EE WEB中集成SSH时的web.xml配置,其中配置了一个
1 <servlet> 2 <servlet-name>dispatcher</servlet-name> 3 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 4 <init-param> 5 <param-name>contextConfigLocation</param-name> 6 <param-value>/WEB-INF/dispatcher-servlet.xml</param-value> 7 </init-param> 8 <load-on-startup>2</load-on-startup> 9 </servlet> 10 11 <servlet-mapping> 12 <servlet-name>dispatcher</servlet-name> 13 <url-pattern>/*</url-pattern> 14 </servlet-mapping>
1.3.web容器与Servlet的关系及Servlet工作原理
1.3.1 web容器与Servlet的关系
Tomcat 的容器等级中,Servlet将被包装成Wrapper容器,而Tomcat的 Context 容器是直接管理Wrapper,如下图。
一个 Context 对应一个 Web 工程,在 Tomcat 的配置文件中可以很容易发现这一点,如下:<Context path="/projectOne " docBase="D:projectsprojectOne" reloadable="true" />
1.3.2 Servlet工作原理
a)Tomcat容器启动过程
- 在Tomcat中新增一个Context容器
- 关键一步:使用ContextConfig解析web应用的配置文件,包括创建ClassLoader加载web应用 class字节码文件,获取ServletContext并设置参数,初始化“load on startup”的 Servlet等等。
- 最后将Context容器添加到Tomcat Host中,并启动Tomcat
b)Web应用初始化过程
Web 应用的初始化工作是在 ContextConfig 的 configureStart 方法中实现的,WebXml 对象中的属性设置到 Context 容器中,这里包括创建 Servlet 对象、filter、listener 等等
初始化web应用的过程就是一个将 Servlet 包装成 Context 容器中的 StandardWrapper的过程。
c)创建Servlet实例
1)创建Servlet对象
前面已经完成了 Servlet 的解析工作,并且被包装成 StandardWrapper 添加在 Context 容器中,但是它仍然不能为我们工作,它还没有被实例化。下面我们将介绍 Servlet 对象是如何创建的,以及如何被初始化的.
首先是对于load-on-startup类型的Servlet,在Context 容器启动的时候就会被实例化。
对于非load-on-startup的创建 Servlet 实例的方法是从 Wrapper. loadServlet 开始的。loadServlet 方法要完成的就是获取 servletClass 然后把它交给 InstanceManager 去创建一个基于 servletClass.class 的对象。
2)初始化Servlet对象
初始化 Servlet 在 StandardWrapper 的 initServlet 方法中,这个方法很简单就是调用 Servlet 的 init 的方法,同时把包装了 StandardWrapper 对象的 StandardWrapperFacade 作为 ServletConfig 传给 Servlet
如果该 Servlet 关联的是一个 jsp 文件,那么前面初始化的就是 JspServlet,接下去会模拟一次简单请求,请求调用这个 jsp 文件,以便编译这个 jsp 文件为 class,并初始化这个 class。这样 Servlet 对象就初始化完成了
d)Servlet工作原理
我们已经清楚了 Servlet 是如何被加载的、Servlet 是如何被初始化的,以及 Servlet 的体系结构,现在的问题就是它是如何被调用的
当用户从浏览器向服务器发起一个请求,通常会包含如下信息:http://hostname: port /contextpath/servletpath,hostname 和 port 是用来与服务器建立 TCP 连接,而后面的 URL 才是用来选择服务器中那个子容器服务用户的请求。那服务器是如何根据这个 URL 来达到正确的 Servlet 容器中的呢?
Tomcat7.0 中这件事很容易解决,因为这种映射工作有专门一个类来完成的,这个就是 org.apache.tomcat.util.http.mapper,这个类保存了 Tomcat 的 Container 容器中的所有子容器的信息,当 org.apache.catalina.connector. Request 类在进入 Container 容器之前,mapper 将会根据这次请求的 hostnane 和 contextpath 将 host 和 context 容器设置到 Request 的 mappingData 属性中。所以当 Request 进入 Container 容器之前,它要访问那个子容器这时就已经确定了。
1.4 Servlet生命周期
前面已经说了创建Servlet有两个时机,对于load-on-startip这种Servlet,在web容器(例如tomcat里的context容器)被初始化候就会创建一个Servlet实例,而其他类型的Servlet要等到客户端第一次请求时候才创建。
Servlet生命周期如下
1)创建Servlet实例
2)Web容器调用Servet的init方法初始化Servlet实例
3)被初始化后的Servlet实例将一直存在于Web容器中,用来响应客户端请求
4)当Web容器决定销毁Servlet实例时,会先调用Servlet的destroy方法,通常在关闭web容器前会先销毁Servlet。
1.5 Servlet中的filter
Filter有点类似加强版的Servlet,Servlet只是响应请求,而Filter会在响应请求前后添加新功能。
javax.servlet.Filter中定义了一个重要的方法doFilter,因此重写这个方法可以对每个请求前后添加额外功能。例如添加日志,或者权限检查(根据参数 HttpSession)等。
通过Filter,可以在请求前后进行处理,添加新功能,甚至决定阻止请求等。
下面是一个典型例子,用来检查用户权限,例子中一个Filter可以对应多个URL
1 <filter> 2 <filter-name>LoginFilter</filter-name> 3 <filter-class>com.cathaypacific.member.controller.filter.MemberLoginFilter</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>LoginFilter</filter-name> 7 <url-pattern>/profile/*</url-pattern> 8 <dispatcher>INCLUDE</dispatcher> 9 <dispatcher>FORWARD</dispatcher> 10 <dispatcher>REQUEST</dispatcher> 11 </filter-mapping> 12 <filter-mapping> 13 <filter-name>LoginFilter</filter-name> 14 <url-pattern>/request/*</url-pattern> 15 <dispatcher>INCLUDE</dispatcher> 16 <dispatcher>FORWARD</dispatcher> 17 <dispatcher>REQUEST</dispatcher> 18 </filter-mapping> 19 <filter-mapping> 20 <filter-name>LoginFilter</filter-name> 21 <url-pattern>/account/*</url-pattern> 22 <dispatcher>INCLUDE</dispatcher> 23 <dispatcher>FORWARD</dispatcher> 24 <dispatcher>REQUEST</dispatcher> 25 </filter-mapping> 26 <filter-mapping> 27 <filter-name>LoginFilter</filter-name> 28 <url-pattern>/miles/*</url-pattern> 29 <dispatcher>INCLUDE</dispatcher> 30 <dispatcher>FORWARD</dispatcher> 31 <dispatcher>REQUEST</dispatcher> 32 </filter-mapping>
1.6 Servlet中的listener
Listener是Servlet API中用来监听各种事件(例如Web应用启动、停止,用户Session开始,用户Session结束,用户请求到达等),
通过实现各种Listener的方法,可以在特定事件发生时进行特定操作,例如用户登录session产生时候,可以将session存入application范围内的map中,从而实现统计当前用户数的功能。
通过自定义一个Listener实现类,并在web.xml中配置,就可以监听各种事件。
常见配置在web.xml中的监听器,
1 <listener> 2 <listener-class>com.cathaypacific.framework.session.SessionListener</listener-class> 3 </listener> 4 <listener> 5 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 6 </listener> 7 <listener> 8 <listener-class>com.cathaypacific.member.controller.listener.TransactionManagerBootstrapListener</listener-class> 9 </listener>
常用Web事件监听接口:
ServletContextListener:用来监听Web应用的启动和关闭
ServletContextAttributeListener:用来监听ServletContext范围内(即application范围)的属性的改变
ServletRequestAttributeListener:用来监听ServletRequest范围内(即request范围)的属性的改变
ServletRequestListener:监听用户请求
HttpSessionListener:监听用户session开始和结束
目前 Servlet 中提供了 5 种两类事件的观察者接口,它们分别是:4 个 EventListeners 类型的,ServletContextAttributeListener、ServletRequestAttributeListener、 ServletRequestListener、HttpSessionAttributeListener 和 2 个 LifecycleListeners 类型的,ServletContextListener、HttpSessionListener。如下图所示:
1.7 Servlet容器的类加载器加载顺序
我们注意到Tomcat的安装目录下也有一个lib目录,这个与Web应用中的lib目录的区别在于:
Tomcat的lib子目录:存放的JAR文件不仅能被Tomcat访问,还能被所有在Tomcat中发布的JavaWeb应用访问。
JavaWeb应用的lib子目录:存放的JAR文件只能被当前JavaWeb应用访问。
假如Tomcat类加载器要加载一个MyClass的类,它会按照以下先后顺序到各个目录中去查找MyClass的class文件,直到找到为止,如果所有目录中都不存在MyClass.class的文件,则会抛出异常:
1、在JavaWeb应用的WEB-INF/classes中查找MyClass.class文件。
2、在JavaWeb应用的 WEB-INF/lib目录下的JAR文件中查找MyClass.class文件。
3、在Tomcat的lib子目录下直接查找MyClass.class文件。
4、在Tomcat的lib子目录下JAR的文件中查找MyClass.class文件。
1.8 Servlet中各种配置项的加载顺序
- 启动web项目后,web容器首先回去找web.xml文件,读取这个文件。
- 容器会创建一个 ServletContext ( servlet 上下文),整个 web 项目的所有部分都将共享这个上下文。
- 容器将web.xml转换为键值对,并交给 servletContext
- 容器创建web.xml中的类实例,创建Listener监听器。
- 容器加载filter,创建过滤器, 要注意对应的filter-mapping一定要放在filter的后面。
- 容器加载servlet,加载顺序按照 Load-on-startup 来执行,Load-on-startup大于0的servlet将在web容器创建之后立即实例化,否则要在用到servlet时候才实例化
因此它的完整加载顺序就是 :ServletContext -> context-param -> listener-> filter -> servlet
不过有一点需要注意的是: spring容器的加载要在servlet之后,因此在有些过滤器当中需要提前用到spring bean的时候,就需要改成 Listener 的方式
有关Servlet的内容参考自 https://www.ibm.com/developerworks/cn/java/j-lo-servlet/
1.9 Servlet线程安全问题
Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet 容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有 多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行
这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。
例如下面这个典型的例子,
1 public class ConcurrentTest extends HttpServlet { 2 PrintWriter output; 3 @Override 4 protected void service(HttpServletRequest request, HttpServletResponse response) 5 throws ServletException, IOException { 6 String username; 7 response.setContentType("text/html;charset=gb2312"); 8 username=request.getParameter("username"); 9 output=response.getWriter(); 10 try { 11 //为了突出并发问题,在这设置一个延时 12 Thread.sleep(5000); 13 output.println("用户名:"+username+"<BR>"); 14 } catch (Exception e) { 15 e.printStackTrace(); 16 } 17 } 18 }
如果同时有两个访客(线程)恰好使用的是Servlet线程池中的同一个Servlet实例, 如果时间足够短, 第二个访客很可能覆盖第一个访客的output变量,导致第一个访客对应线程,将数据输入到第二个访客的页面中去,
具体例子参考自:http://www.cnblogs.com/gw811/archive/2012/09/07/2674859.html
要解决这个问题, 通常是建议尽量避免使用实例变量。
2.struts.xml
此文件用来配置Action和request之间的映射关系,通常放在类加载目录,即WEB-INF/classes下。
另外,struts.xml还可以用来配置常量,配置bean,导入其他配置等。
2.1配置Action
通常Action总是跟interceptor一起配置到一个package中,interceptor将会在action前后执行,顺序由配置的位置决定?
一个典型的action+interceptor组合配置如下,自定义的interceptor中可以指定实现类,action中也可以指定处理类和处理方法,以及结果(可以是数据或者页面)
1 <struts> 2 <package name="test"extends="struts-default"> 3 <interceptors> 4 <interceptor name="abc"class ="ceshi.AuthorizationInterceptor"/> 5 </interceptors> 6 <action name="TestLogger"class="vaannila.TestLoggerAction"> 7 <interceptor-refnameinterceptor-refname="abc"/> 8 <result name="success">/success.jsp</result> 9 </action> 10 </package> 11 </struts>
2.1配置常量
例如
struts.local, 默认值en_US
struts.i18n.encoding,默认值UTF-8
struts.objectFactory.spring.autoWire, 默认是name
2.2配置包含其他文件
struts2默认加载class路径下的struts.xml, default-struts.xml, struts-plugin.xml 文件。
也可以在struts.xml中使用include导入其他配置文件,例如<include file="struts-part1.xml"/>
3.