1,Spring整合Struts2
虽然Spring也提供了自己的MVC组件,但一来Spring的MVC组件略显烦琐;二来Struts2的拥护者实在太多。因此,很多项目都会选择使用Spring整合Struts2框架。而且Spring完全可以无缝衔接整合Struts2框架,二者结合成一个更实际的Java EE开发平台。
jar:
1.1,启动Spring容器
对于使用Spring的Web应用,无须手动创建Spring容器,而是通过配置文件声明式地创建Spring容器。Spring提供了一个ContextLoaderListener,该监听类实现了ServletContextListener接口。该类可以作为Listener使用,它会在创建时自动查找WEB-INF/下的applicationContext.xml文件,因此如果只有一个配置文件且配置文件命名为applicationContext.xml,则只需在web.xml文件中增加如下配置片段:
<!-- 使用ContextLoaderListener初始化Spring容器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
如果有多个配置文件需要载入,则考虑使用<context-param.../>元素确定配置文件的文件名。,COntextLoaderListener加载时,会查找名为contextConfigLocation的初始化参数,因此配置<context-param.../>时应指定参数名为contextConfigLocation。
<context-param> <param-name> contectCOnfigLocation</param-name> <param-value>/WEB-INF/daocontext.xml,/WEB-INF/applicationCotext.xml</param-value> </context-param>
Spring根据配置文件创建WebApplicationContext对象,并将其保存在Web应用的ServletContext中。如果要获取应用中的ApplicationContext实例,则可以根据如下获取:
WebApplicationContext ctx=WebApplicationContextUtils.getWebApplicationContext(servletContext)
1.2,MVC框架与Spring整合的思考
对于一个基于B/S架构的Java EE而言,用户请求总是向MVC框架的控制器请求,而当控制器拦截到用户请求后,必须调用业务逻辑组件来处理用户请求。此时有个问题:控制器应该如何获得业务逻辑组件?
最容易的策略是,直接通过new关键字创建业务逻辑组件,然后调用业务逻辑组件的方法,根据业务逻辑方法的返回值确定结果。
在实际的应用中,很少采用上面的访问策略,因为这是一种非常差的策略。不这样做的原因至少有三点:
- 控制器直接创建业务逻辑组件,导致控制器和业务逻辑组件的耦合降低到代码层次,不利于高层次耦合。
- 控制器不应该负责业务逻辑组件的创建,控制器只是业务逻辑组件的使用者,无须关心业务逻辑组件的实现。
- 每次创建新的业务逻辑组件将导致性能下降。
答案是采用工厂模式,或者服务定位模式。对于采用服务定位器模式,是远程访问的场景。在这种场景下,业务逻辑组件已经在某个容器中运行,并对外提供某种服务。控制器无须理会该业务逻辑组件的创建,直接调用该服务即可,但在调用之前,必须先找到该服务——这就是服务定位器的概念。经典的Java EE应用就是这种结构的应用。
对于轻量级的Java EE应用,工厂模式则是更实际的策略。因为在轻量级的Java EE应用中,业务逻辑组件不是EJB,通常就是一个POJO,业务逻辑组件的生成通常应由工厂负责,而工厂可以保证该组件的实例只需一个就够了,可以避免重复实例化造成的系统开销。
采用工厂模式,将控制器与业务逻辑组件的实现分离,从而提供更好的解耦。在采用工厂模式访问的访问策略中,所有的业务逻辑组件的创建由工厂负责,业务逻辑组件的运行也由工厂负责,而控制器只需定位工厂实例即可。
如果系统采用Spring框架,则Spring成为最大的工厂。Spring负责业务逻辑组件的创建和生成,并可管理业务逻辑组件的生命周期。Spring是个性能非常优秀的工厂,可以生产出所有的实例,从业务逻辑组件,到持久层组件,甚至控制器组件。
现在的问题是:控制器如何访问到Spring容器中的业务逻辑呢?为了让Action访问到Spring的业务逻辑组件,有两种策略:
- Spring容器负责管理控制器Action,并利用依赖注入为控制器注入业务逻辑组件。
- 利用Spring的自动装配,Action将会自动从Spring容器中获取所需的业务逻辑组件。
1.3,让Spring管理控制器
让Spring容器来管理应用中的控制器,可以充分利用Spring的IoC特性,但需要将配置Struts2的控制器部署在Spring容器中,因此导致配置文件冗余。
Struts2的核心控制器首先拦截到用户请求,将请求转发给对应的Action处理,在此过程中,Struts2将负责创建Action实例,并调用其execute()方法。这个过程是固定的。现在的情形是:如果把Action实例交由Spring容器来管理,而不是Struts2产生,那么核心控制器如何知道调用Spring容器中的Action,而不是自行创建Action实例呢?这个工作由Struts2提供的Spring插件来完成:struts2-spring-2.5.26。
Spring插件提供了一种伪Action,在struts.xml文件中配置Action时,通常需要指定其class属性,该属性就是用于创建Action实例的实现类。但Struts2提供的Spring插件允许指定class属性时,不再指定Action的实际实现类,而是指定为Spring容器中的Bean ID,这样Struts2不再自己负责创建Action实例,而是直接通过Spring容器去获取Action对象。
通过上面的方式,不难发现这种整合策略的关键:当Struts2将请求转发给指定的Action时,Struts2中的该Action只是一个傀儡,它只是一个代号,并没有实际的实现类,当然也不可能创建Action实例,而是隐藏在该Action下的是Spring容器中的Action实例。
1,使用ContextLoaderListener初始化Spring容器
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- spring配置文件对应的参数,如果applicationContext.xml放在/WEB-INF/目录下,可以忽略;若不是,则需要配置 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/applicationContext.xml</param-value> </context-param> </web-app>
2,在Spring的配置文件中配置 Struts2 的 Action 实例
当使用Spring容器管理Struts2的Action时,由于每个Action对应一次用户请求,且封装了该次请求的状态信息,所以不应将Action配置成单例模式,因此必须指定scope属性,该属性值可指定为prototype或request。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="registerAction" class="action.LoginAction" scope="prototype" p:myService-ref="myService"/> <bean id="myService" class="service.MyService" /> </beans>
3,在 Struts 配置文件中配置 action, 但其 class 属性不再指向该 Action 的实现类, 而是指向 Spring 容器中 Action 实例的 ID
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN" "http://struts.apache.org/dtds/struts-2.5.dtd"> <struts> <package name="myFirstPackage" extends="struts-default"> <action name="register" class="registerAction"> <result name="error">/WEB-INF/error.jsp</result> <result name="success">/WEB-INF/welcome.jsp</result> </action> </package> </struts>
1.4,使用自动装配
自动装配:https://shao12138.blog.csdn.net/article/details/113058359#t6
在自动装配策略下,Action还是由Spring插件创建,Spring插件在创建Action实例时,利用Spring的自动装配策略,将对应的业务逻辑组件注入Action实例中。这种整合策略配置文件简单,但控制器和业务逻辑组件耦合又提升了代码层次,耦合较高。
如果不指定自动装配,则系统默认使用按byName自动装配。前面的整合策略并没有指定任何自动装配策略,当Web启动时,可以看到自动装配策略为byName。
自动装配:即让Spring自动管理Bean与Bean之间的依赖关系,无须使用ref显式指定依赖Bean。Spring容器会自动检查XML配置文件的内容,为主调Bean注入依赖Bean。自动装配可以减少配置文件的工作量,但会降低依赖关系的透明性和清晰性。
通过自动装配,可以让Spring插件自动将业务逻辑组件注入Struts2的Action实例中。通过设置struts.dobjectFactory.spring.autoWire常量可以改变Spring插件的自动装配策略,该常量有如下几个值:
- name:根据属性名自动装配。Spring插件会查找容器中全部Bean,找到其中id属性与Action所需的业务逻辑组件同名的Bean,将该bean实例注入到Action实例。
- type:根据属性类型自动装配。Spring插件会查找容器中全部Bean,找出其类型恰好与Action所需的业务逻辑组件相同的Bean,将该Bean实例注入到Action实例。
- auto:Spring插件会自动检测需要使用哪种自动装配方式。
- constructor:与type类似,区别是constructor使用构造器来构造注入的所需参数而不是使用设值注入方式。
1,使用ContextLoaderListener初始化Spring容器
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- spring配置文件对应的参数,如果applicationContext.xml放在/WEB-INF/目录下,可以忽略;若不是,则需要配置 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/applicationContext.xml</param-value> </context-param> </web-app>
2,正常编写struts配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN" "http://struts.apache.org/dtds/struts-2.5.dtd"> <struts> <package name="myFirstPackage" extends="struts-default"> <action name="register" class="action.LoginAction"> <result name="error">/WEB-INF/error.jsp</result> <result name="success">/WEB-INF/welcome.jsp</result> </action> </package> </struts>
3,编写 spring 配置文件, 在该配置文件中不需要配置 Action 实例
此时Bean的ID必须为Struts中MyService的实例名。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myService" class="service.MyService" /> </beans>
2,零配置
2.1,Struts2零配置
零配置省去了在struts.xml的配置,但也无法使用通配符,也无法配置Struts2的常量。
拦截器必须在struts,xml中配置,但是可以以注解的形式添加到action中。
1、添加jar
2、添加注解
package action; import com.opensymphony.xwork2.ActionSupport; import org.apache.struts2.convention.annotation.*; @ParentPackage("struts-default") //package名称 @Namespace("/") //命名空间 @InterceptorRef("simple") //拦截器 public class LoginAction extends ActionSupport { private String username; private String password; // username的setter和getter方法 public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } // password的setter和getter方法 public void setPassword(String password) { this.password = password; } public String getPassword() { return password; } @Action(value = "login", results = { //相当于配置中的name @Result(name = "success", location = "/success.jsp"), @Result(name = "error", location = "/error.jsp") }) //执行函数名称,结果,跳转路径 public String execute() throws Exception { System.out.println("进入execute方法执行体.........."); Thread.sleep(1000); if (getUsername().equals("123456") && getPassword().equals("123456")) { return SUCCESS; } return ERROR; } }
2.2,配置包
【问题】Struts2默认扫描action、actions、struts、struts2这4个包的注解。意思就是如果我的包名不是这4个,就无法使用注解达到零配置了。会出现:Action访问找不到namespace问题。
【解决】需要手动配置扩充包名称,来覆盖默认配置。struts.xml配置文件加一个配置:
<constant name="struts.convention.package.locators" value="action,actions,struts,struts2,test" />
2.3,Spring整合Struts零配置
Spring零配置:https://shao12138.blog.csdn.net/article/details/113327063#t6
1,Action
package action; import com.opensymphony.xwork2.ActionSupport; import org.apache.struts2.convention.annotation.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import service.MyService; @Controller @ParentPackage("struts-default") @Namespace("/") public class LoginAction extends ActionSupport { private String username; private String password; @Autowired private MyService myService; public void setMyService(MyService myService) { this.myService = myService; } // username的setter和getter方法 public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } // password的setter和getter方法 public void setPassword(String password) { this.password = password; } public String getPassword() { return password; } @Action(value = "register", results = { @Result(name = "success", location = "/success.jsp"), @Result(name = "error", location = "/error.jsp") }) public String execute() throws Exception { System.out.println("进入execute方法执行体.........."); if (myService.validLogin(username,password)) { return SUCCESS; } return ERROR; } }
2,Service
package service; import org.springframework.stereotype.Service; @Service public class MyService { public boolean validLogin(String username, String password) { if (username.equals("123456")&&password.equals("123456")){ return true; }else { return false; } } }
3,applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="action"/> <context:component-scan base-package="service"/> </beans>