Spring4笔记12--SSH整合3--Spring与Struts2整合

SSH 框架整合技术:
  3. Spring与Struts2整合(对比SpringWeb):

    Spring 与 Struts2 整合的目的有两个:
      (1)在 Struts2 的 Action 中,即 View 层,获取到 Service。然后就实现了在 Struts2 项目中 View 层、Service 层、Dao 层的联通。
      (2)将 Action 实例交由 Spring 容器管理。

    代码详解:

    (1) 将 Servlet 修改成 Action:

 package com.tongji.actions;

 import com.tongji.beans.Student;
import com.tongji.service.IStudentService; public class RegisterAction {
private String name;
private int age; //声明Service对象
private IStudentService service; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public IStudentService getService() {
return service;
} public void setService(IStudentService service) {
this.service = service;
} public String execute() {
Student student = new Student(name, age);
service.addStudent(student);
return "success";
}
}

    注意:直接将Service作为Action的属性

    (2) 在 Spring 配置文件中注册 Action 的 Bean
      在 Spring 的配置文件中将 Action 作为一个普通 Bean 进行定义,并在配置文件中完成 Service 的注入。即添加如下代码:

          <!-- 注册Action:必须指定Action的scope为prototype,因为Action是多例的 -->
          <bean id="registerAction" class="com.tongji.actions.RegisterAction" scope="prototype">
              <property name="service" ref="studentService"/>
          </bean>

    (3) Struts2 配置文件中的 class 使用伪类名
      Struts2 配置文件中的<action/>标签的 class 属性值不再是 Action 的全类名了,而是个伪类名:Spring 配置文件中该 Bean 的 id 名。

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd"> <struts>
<package name="ssh" namespace="/test" extends="struts-default">
<!-- class的值是Spring容器中Action的Bean的id值,此时的class的属性值称为 伪类 -->
<action name="register" class="registerAction">
<result>/welcome.jsp</result>
</action>
</package>
</struts>

    (4) 修改前端页面:

 <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html>
<head>
<title>index</title>
</head> <body>
<form action="test/register.action" method="POST">
name:<input type="text" name="name"/><br>
age:<input type="text" name="age"/><br>
<input type="submit" value="submit"/>
</form>
</body>
</html>
 <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html>
<head>
<title>welcome</title>
</head> <body>
welcome you!
</body>
</html>

    (5) 其余代码都不变,发布运行即可。

  补充问题:当将事务织入到 Service 层后,在 Dao 层通过 load()等具有延迟加载功能的方法加载实体时,会出现异常。

  View 层要读取一个对象的某属性值,所以在 View 层调用了 Service 层的对象获取方法。此时 Service 层就会去调用 Dao 层,欲从 DB 中读取数据。但由于 Service 层并非是真正需要数据的一方,即 Servcie 层并非真正需要数据的详情。又由于 Dao 层使用了延迟加载技术,所以返回给 Service 层一个数据为空的代理对象。当 Service 获取到了这个对象后,马上将这个对象返回给了 View 层。Service 层认为任务完成,所以将事务进行了关闭。由于 Session 必须在事务环境下运行,且事务在提交或回滚后,会首先将Session 关闭,而后关闭事务。所以事务关闭前,Session 就已经消失。当 View 层收到 Service 层发送来的对象,并使用其详情时,发现为空。此时 View 层向 Service 层发出进行真正查询请求时,Service 层的 Session 已经不存在,所以,会报出 Session 为空的异常。

  解决办法:

  问题的主要原因是事务应用在业务层,而延迟加载使得对 Hibernate 的 Session 的连续使用放在了 View 层。若要 Session 不进行自动关闭,必须使其所在的事务环境不能在 Service 层就结束,所以可以将 Session 的开启放在 View 层。  
    只需在 web.xml 中注册一个过滤器 OpenSessionInViewFilter(开启 Session 在 View),只要用户提交请求,就会先执行该过滤器,将 Session 开启。

 <?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
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_3_0.xsd">
<!-- 指定Spring配置文件的名称及位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param> <!-- 注册监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <!-- 注册OpenSessionInViewFilter,要求其必须在Struts启动项这个Filter的前面注册 -->
<filter>
<filter-name>OpenSessionInView</filter-name>
<filter-class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</filter-class>
<!-- 默认OpenSessionInViewFilter加载的SessionFactory名称为sessionFactory
这里指定自己定义的SessionFactory的名称,让OpenSessionInViewFilter加载 -->
<!-- <init-param>
<param-name>sessionFactoryBeanName</param-name>
<param-value>mySessionFactory</param-value>
</init-param> -->
</filter>
<filter-mapping>
<filter-name>OpenSessionInView</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> <!-- 注册Struts2启动项 -->
<filter>
<filter-name>Struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> <welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list> </web-app>

    注册过滤器时需要注意:
    (1)该过滤器会自动从 Spring 配置文件中查找名称为 sessionFactory 的 Bean。若定义的不是这个名称,则需通过初始化参数<init-param/>进行指定。

    Spring4笔记12--SSH整合3--Spring与Struts2整合
    (2)该过滤器应注册在 StrutsPrepareAndExecuteFilter 的前面,即在进入 Struts2 之前先执行过滤,否则还会出现noSession的问题。其注册顺序可以从 Struts2 的流程图中看出。

    Spring4笔记12--SSH整合3--Spring与Struts2整合
    过滤器的执行都是通过过滤器链串起来执行的,即当前的 Filter 通过在自己的 doFilter() 方法中执行 chain.doFilter()来调用执行下一下过滤器。
      查看 StrutsPrepareAndExecuteFilter 的源码可以看出,只有当没有 Action 要执行时,才可能会执行其它的过滤器,即调用 chain.doFilter()。否则,只要有 Action 能够匹配上执行,那么就没有 chain.doFilter()方法要执行了,即 Struts2 的 StrutsPrepareAndExecuteFilter 将成为最后一个被执行的 Filter。所以,只有将 OpenSessionInViewFilter 放到 Struts2 的过滤器StrutsPrepareAndExecuteFilter 的前面才会被执行到。
    Spring4笔记12--SSH整合3--Spring与Struts2整合

    至此,SSH整合就结束了。

上一篇:qt creator 源代码中含有中文编译报错


下一篇:Python 第二课笔记