Servlet的生命周期是“初始化->init->service->destroy->卸载”。
这里大家都知道,我们在web.xml里面定义一个servlet的时候,我们可以给他们设置一个“load-on-startup” 的值,如果 Servlet 的 load-on-startup 配置项大于 0,那么在 Context 容器启动的时候就会被实例化,并且tomcat给每一个servlet加载并且实例化一个对象(注解:也就是说,我们用户在web.xml里面配置的每一个servlet都会被实例成一个servlet对象)
a, 下面的配置表示会有两个servlet对象被实例化,即使他们对应的是同一个servlet class
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="2.5" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 6 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> 7 <servlet> 8 <servlet-name>ServletTest1</servlet-name> 9 <servlet-class>web.servlet.ServletTest1</servlet-class> 10 </servlet> 11 12 <servlet-mapping> 13 <servlet-name>ServletTest1</servlet-name> 14 <url-pattern>/servlet/ServletTest1</url-pattern> 15 </servlet-mapping> 16 17 <servlet> 18 <servlet-name>ServletTest2</servlet-name> 19 <servlet-class>web.servlet.ServletTest1</servlet-class> 20 </servlet> 21 22 <servlet-mapping> 23 <servlet-name>ServletTest2</servlet-name> 24 <url-pattern>/servlet/ServletTest1</url-pattern> 25 </servlet-mapping> 26 </web-app>
b, 下面的配置表示只会有一个servlet被实例化
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="2.5" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 6 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> 7 <servlet> 8 <servlet-name>ServletTest1</servlet-name> 9 <servlet-class>web.servlet.ServletTest1</servlet-class> 10 </servlet> 11 12 <servlet-mapping> 13 <servlet-name>ServletTest1</servlet-name> 14 <url-pattern>/servlet/ServletTest1</url-pattern> 15 </servlet-mapping> 16 17 <servlet-mapping> 18 <servlet-name>ServletTest1</servlet-name> 19 <url-pattern>/servlet/ServletTest1</url-pattern> 20 </servlet-mapping> 21 </web-app>
Tomcat容器对Servlet的实现采用的是单例模式,对于一个Servlet类,永远只有一个servlet对象存在
1. Struts1
Struts1是对Java web servlet接口的直接实现,即继承了tomcat对Servlet的实现,每一个struts1里面的action都对应一个的是一个servlet class,所以这里的action在被Tomcat实例化之后也是单例的。这就是struts1产生多线程问题的原因
2. Struts2
上面我们了解了Struts1里面的多线程问题,那Struts2是怎么解决这个问题的呢?其实道理非常简单,原因就是Strtus2会获取到用户的http请求,然后负责给每个请求实例化一个Action 对象,但是大家注意,这里的action对象和Struts1里面的action对象完全不是一个概念,struts1里面的action类就是一个servlet类,而这里的action类只是一个普通的java class。这也就是为什么Struts1里面的action是线程不安全的,而struts2里面的action是线程安全的原因。
那我们在回头来看看struts2对servlet的处理和struts1有什么不同。看过前面分析的读者肯定知道,struts1的 action对servlet没有进行任何的包装,它是直接实现的Java WEB API 里面的servlet 接口。所以才会有线程安全的问题,但是struts2底层帮我们封装了Servlet,使开发人员不用直接接触Servlet。具体做法是:Strtus2截获servlet请求,然后给每个请求实例化一个Action对象,请求结束之后销毁Action对象。
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
在Struts1中的Action安全问题详解:
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
Action类
Action类真正实现应用程序的事务逻辑,它们负责处理请求。在收到请求后,ActionServlet会:为这个请求选择适当的Action,如果需要,创建Action的一个实例,调用Action的perform()方法,如果ActionServlet不能找到有效的映射,它会调用默认的Action类(在配置文件中定义)。如果找到了ActionServlet将适当的ActionMapping类转发给Action,这个Action使用ActionMapping找到本地转发,然后获得并设置ActionMapping属性。根据servlet的环境和被覆盖的perform()方法的签名,ActionServlet也会传送ServletRequest对象或HttpServletRequest对象。
所有Action类都扩展org.apache.struts.action.Action类,并且覆盖类中定义的某一个perform()方法。有两个perform()方法:
处理非HTTP(一般的)请求:
<span style="font-family:Microsoft YaHei;font-size:14px;">public ActionForward perform(ActionMapping action,
AcionForm form,
ServletRequest request,
ServletResponse response)
throws IOException,ServletException
</span>
处理HTTP请求:
<span style="font-family:Microsoft YaHei;font-size:14px;">public ActionForward perform(ActionMapping action,
AcionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException,ServletException </span>
Action类必须以“线程安全”的方式进行编程,因为控制器会令多个同时发生的请求共享同一个实例,相应的,在设计Action类时就需要注意以下几点:
不能使用实例或静态变量存储特定请求的状态信息,它们会在同一个操作中共享跨越请求的全局资源,如果要访问的资源(如JavaBean和会话变量)在并行访问时需要进行保护,那么访问就要进行同步,Action类的方法
除了perform()方法外,还有以下方法:
可以获得或设置与请求相关联的区域:
<span style="font-family:Microsoft YaHei;font-size:14px;">public Locale getLocale(HttpServletRequest request)
public void setLocale(HttpServletRequest request,Localelocale) </span>
为应用程序获得消息资源:public MessageResources getResources()
检查用户是否点击表单上的“取消”键,如果是,将返回true:public Boolean isCancelled(HttpServletRequest request)
当应用程序发生错误时,Action类能够使用下面方法存储错误信息:
public void saveErrors(HttpServletRequestrequest,ActionErrors errors)
ActionError实例被用来存储错误信息,这个方法在错误关键字下的请求属性列表中存储ActionError对象。通过使用在struts标记库中定义的自定义标记,JSP页能够显示这些错误信息,稍后我们就介绍。
ActionForm类
框架假设用户在应用程序中为每个表单都创建了一个ActionForm bean,对于每个在struts-config.xml文件中定义的bean,框架在调用Action类的perform()方法之前会进行以下操作:
(1)在相关联的关键字下,它检查用于适当类的bean实例的用户会话,如果在会话中没有可用的bean,它就会自动创建一个新的bean并添加到用户的会话中。
(2)对于请求中每个与bean属性名称对应的参数,Action调用相应的设置方法。
(3)当Action perform()被调用时,最新的ActionForml bean传送给它,参数值就可以立即使用了。
(4)ActionForm类扩展org.apache.struts.action.ActionForm类,程序开发人员创建的bean能够包含额外的属性,而且ActionServlet可能使用反射(允许从已加载的对象中回收信息)访问它。
ActionForm类提供了另一种处理错误的手段,提供两个方法:
<span style="font-family:Microsoft YaHei;font-size:14px;">Public ActionErrors validate(ActionMappin mapping,
ServletRequest request)
Public ActionErrors validate(ActionMappin mapping,
HttpServletRequest request) </span>
你应该在自己的bean里覆盖validate()方法,并在配置文件里设置<action>元素的validate为true。在ActionServlet调用Action类前,它会调用validate(),如果返回的ActionErrors不是null,则ActinForm会根据错误关键字将ActionErrors存储在请求属性列表中。
如果返回的不是null,而且长度大于0,则根据错误关键字将实例存储在请求的属性列表中,然后ActionServlet将响应转发到配置文件<action>元素的input属性所指向的目标。
如果需要执行特定的数据有效性检查,最好在Action类中进行这个操作,而不是在ActionForm类中进行。
方法reset()可将bean的属性恢复到默认值:
<span style="font-family:Microsoft YaHei;font-size:14px;">public void reset(ActionMapping mapping,HttpServletRequestrequest)
public void reset(ActionMapping mapping,ServletRequestrequest) </span>
典型的ActionFrom bean只有属性的设置与读取方法(getXXX),而没有实现事务逻辑的方法。只有简单的输入检查逻辑,使用的目的是为了存储用户在相关表单中输入的最新数据,以便可以将同一网页进行再生,同时提供一组错误信息,这样就可以让用户修改不正确的输入数据。而真正对数据有效性进行检查的是Action类或适当的事务逻辑bean。
3.6 ActionForward类
目的是控制器将Action类的处理结果转发至目的地。
Action类获得ActionForward实例的句柄,然后可用三种方法返回到ActionServlet,所以我们可以这样使用findForward():
ActionServlet根据名称获取一个全局转发,ActionMappin实例被传送到perform()方法,并根据名称找到一个本地转发,另一种是调用下面的一个构造器来创建它们自己的一个实例:
<span style="font-family:Microsoft YaHei;font-size:14px;">public ActionForward()
public ActionForward(String path)
public ActionForward(String path,Boolean redirect)</span>
----------------------------------------------------------------------------------------------------------------
在struts应用的生命周期中RequestProcessor只能保证一个Action实例,所有的其他请求都共享这个实例,因此Aciton是多线程进行工作,但是有一个保证正常和安全工作的前提是,Action不能有这样的实例变量,拥有状态的实例,尤其是拥有业务对象状态的实例. 如果要用到那些有状态的实例,唯一和最好的办法是在Action类中,仅在Action类的execute()方法中使用局部变量,对于每个调用 execute()方法的线程,JVM会在每个线程的堆栈中创建局部变量,因此每个线程拥有独立的局部变量,不会被其他线程共享.当线程执行完 execute()方法后,它的局部变量就会被销毁.
现在项目中的做法:Action调用一个业务用例服务,这个服务本身没有变量,这个服务来保证实现一个模型层的业务逻辑.如果Action类的实例变量是必须设定的话,需要采用JAVA同步机制对访问共享资源的代码块进行同步.
----------------------------------------------------------------------------------------------------------------------
struts的action是非线程安全的。不要在action中定义实例变量。
在spring的配置文件中,可以配置action为线程安全,即每次调用都生成一个新的实例,而不是只用一个实例。
bean中设置singleton="true"
因为控制器会令多个同时发生的请求共享同一个实例,所以最好不要在Action中使用实例或静态变量存储特定请求的状态信息,如果要访问相关的资源,最好进行同步操作。