调查管理系统 -(6)自定义Struts2的拦截器&自定义UserAware接口&Action中模型赋值问题&Hibernate懒加载问题
1、对于一些功能,如我的调查或新建调查等,只有用户登录后才能进行操作,因此必须对用户是否登录进行判断。当用户登录后才能使用相应的功能,如果没有登录则需为用户导航到登录页面让其进行登录。这个功能可以通过自定义Struts2的拦截器来完成。
2、当用户登录之后,由于是将用户的信息保存在session中的。这样当一些Action中需要用到当前登录的用户的信息时需要手动的从session中获取,不太方便,因此我们声明了一个UserAware接口(即用户关注,类似于Struts2中的SessionAware等),当Action实现了该接口后,我们可以自动的为Action注入用户对象。
3、UserAware接口定义如下,在其中声明了一个setUser()方法:
1 package com.atguigu.surveypark.struts2; 2 import com.atguigu.surveypark.model.User; 3 /** 4 * 用户关注 5 */ 6 public interface UserAware { 7 public void setUser(User user); 8 }
4、定义SurveyAction并让其实现UserAware接口:
1 package com.atguigu.surveypark.struts2.action; 2 import java.util.List; 3 import javax.annotation.Resource; 4 import org.springframework.context.annotation.Scope; 5 import org.springframework.stereotype.Controller; 6 import com.atguigu.surveypark.model.Survey; 7 import com.atguigu.surveypark.model.User; 8 import com.atguigu.surveypark.service.SurveyService; 9 import com.atguigu.surveypark.struts2.UserAware; 10 /** 11 * SurveyAction 12 */ 13 @Controller 14 @Scope("prototype") 15 public class SurveyAction extends BaseAction<Survey> implements UserAware{ 16 @Resource //注入SurveyService 17 private SurveyService surveyService ; 18 //调查集合 19 private List<Survey> mySurveys ; 20 //接受user对象 21 private User user; 22 //接受sid参数 23 private Integer sid ; 24 public Integer getSid() { 25 return sid; 26 } 27 public void setSid(Integer sid) { 28 this.sid = sid; 29 } 30 public List<Survey> getMySurveys() { 31 return mySurveys; 32 } 33 public void setMySurveys(List<Survey> mySurveys) { 34 this.mySurveys = mySurveys; 35 } 36 //查询我的调查列表 37 public String mySurveys(){ 38 this.mySurveys = surveyService.findMySurveys(user); 39 return "mySurveyListPage" ; 40 } 41 42 //新建调查 43 public String newSurvey(){ 44 this.model = surveyService.newSurvey(user); 45 return "designSurveyPage" ; 46 } 47 48 //设计调查 49 public String designSurvey(){ 50 this.model = surveyService.getSurveyWithChildren(sid); 51 return "designSurveyPage" ; 52 } 53 54 //该方法只在designSurvey之前调用 55 // public void prepareDesignSurvey(){ 56 // this.model = surveyService.getSurveyWithChildren(sid); 57 // } 58 //注入User对象 59 public void setUser(User user) { 60 this.user = user ; 61 } 62 }
在SurveyAction中实现了UserAware接口,并声明了一个user对象来接收注入的user对象。接下来就可以通过登录拦截器来为实现了UserAware接口的Action注入当前登录用户。
5、登录拦截器实现如下:
1 package com.atguigu.surveypark.struts2.interceptor; 2 import com.atguigu.surveypark.model.User; 3 import com.atguigu.surveypark.struts2.UserAware; 4 import com.atguigu.surveypark.struts2.action.BaseAction; 5 import com.atguigu.surveypark.struts2.action.LoginAction; 6 import com.atguigu.surveypark.struts2.action.RegAction; 7 import com.opensymphony.xwork2.ActionInvocation; 8 import com.opensymphony.xwork2.interceptor.Interceptor; 9 /** 10 * 登陆拦截器 11 */ 12 public class LoginInterceptor implements Interceptor { 13 public void destroy() {} 14 public void init() {} 15 public String intercept(ActionInvocation arg0) throws Exception { 16 BaseAction action = (BaseAction) arg0.getAction(); 17 if(action instanceof LoginAction 18 || action instanceof RegAction){ //判断Action是否为LoginAction或RegAction,即用户是否是去登录或注册,如果是的话则放行,否则判断用户是否登录了 19 return arg0.invoke(); 20 }else{ 21 User user = (User) arg0.getInvocationContext().getSession().get("user"); 22 if(user == null){ //判断用户是否登录了,未登录则跳转到登录页面 23 //去登陆 24 return "login" ; 25 }else{ 26 //放行 27 if(action instanceof UserAware){ //判断action是否实现了UserAware接口,如果实现了则通过setUser()方法为其注入当前登录用户对象 28 //注入user给action 29 ((UserAware)action).setUser(user); 30 } 31 return arg0.invoke(); 32 } 33 } 34 } 35 }
可以看到,在登录拦截器中我们通过判断Action是否实现了UserAware接口可以自动的为Action注入user对象。如果在程序中不使用UserAware接口,则需要让Action实现SessionAware接口或通过ActionContext等方法获取到session,然后再从session中得到登录用户的信息。
6、另外,我们还注意到,在SurveyAction的实现中,在新建调查和设计调查的方法中我们都直接使用this.model=XXX将获取到的新对象直接赋值给model对象了。我们知道,在一般情况下,Struts2的执行流程是通过在ActionProxy[DefaultActionProxy]的prepare()方法中调用ActionInvocation[DefaultActionInvocation]的init()方法完成Action的实例化工作并将其压入到值栈ValueStack的栈顶,如果Action实现了ModelDriven接口,则在执行到ModelDriven拦截器的时候,该拦截器会通过Action的getModel()方法获得到model对象,并将其压入到值栈的栈顶,之后在执行params拦截器时,会完成为model的赋值工作。因此,如果在执行Action的业务方法时,如果直接使用this.model=XXX为model重新赋值的话,会改变Action中model的引用,其与值栈栈顶的model将不再是同一个对象,因此在页面将无法正确的显示对象的内容。这里我们之所以可以这样做是因为我们对拦截器做了新的配置。相关内容总结如下:
Struts2的action中为model赋值的问题总结-四种处理方法:
1)手动压栈。
ActionContext.getContext().getValueStack().push(newModel);
缺点:
Ⅰ. 耦合度高.(指Struts2的代码与自己的代码)
Ⅱ. 不推荐直接操作值栈vs
Ⅲ. 栈中的模型对象过多
2)通过手动将新模型的属性全部赋值给旧模型。
oldModel.setXxxx(newModel.getXxxx());
...
缺点:性能比较差
3)使用paramsPrepareParamsStack + preparable拦截器配合使用。
prepare拦截器先执行,先为model赋值,后调用modelDriven,在栈顶压入的model是新模型,
但是需要主要在prepare拦截器还要进行传递参数,defaultstack在prepare不能完成传参,
因此可以改换成paramsPrepareParamsStack来达到此目的。
下面是paramsPrepareParamsStack(即参数预处理参数拦截器栈)的相关配置:
<!-- 配置使用 paramsPrepareParamsStack 作为默认的拦截器栈 --> <!-- 修改 PrepareInterceptor 拦截器的 alwaysInvokePrepare 属性值为 false(注:这是为了在调用prepare拦截器时,使Preparable接口的prepare方法不被调用,可根据情况选择是否配置该参数) --> <interceptors> <!-- 注册登陆拦截器 --> <interceptor name="loginInterceptor" class="com.atguigu.surveypark.struts2.interceptor.LoginInterceptor" /> <!-- 定义拦截器栈 --> <interceptor-stack name="surveyparkStack"> <interceptor-ref name="loginInterceptor" /> <interceptor-ref name="paramsPrepareParamsStack"> <param name="prepare.alwaysInvokePrepare">false</param> </interceptor-ref> </interceptor-stack> </interceptors> <!-- 定义默认栈 --> <default-interceptor-ref name="surveyparkStack" />
当使用了paramsPrepareParamsStack拦截器栈,并且Action实现了Preparable接口时,可以定义以prepare或prepareDo开头加上action方法的名称的方法,这样,在执行prepare拦截器时,就会完成model赋值等预处理工作,之后执行modelDriven拦截器时将以完成赋值的model压入值栈栈顶,之后可以正确的在页面进行显示。例如在SurveyAction中的prepareDesignSurvey()方法就是在designSurvey()之前调用,完成为model赋值的工作。
缺点:会导致方法的膨胀,由于很多方法中都会为model赋值,因此几乎导致了方法的翻倍。
4)使用刷新机制进行赋值:直接在action中为model赋值,使用刷新机制即可★★★★★。
<interceptors> <!-- 注册登陆拦截器 --> <interceptor name="loginInterceptor" class="com.atguigu.surveypark.struts2.interceptor.LoginInterceptor" /> <!-- 定义拦截器栈 --><interceptor-stack name="surveyparkStack"> <interceptor-ref name="loginInterceptor" /> <interceptor-ref name="defaultStack"> <param name="modelDriven.refreshModelBeforeResult">true</param> </interceptor-ref> </interceptor-stack> </interceptors>
这种方法依然是使用默认的拦截器栈defaultStack,只是将modelDriven拦截器的属性refreshModelBeforeResult设置为了true。这样modelDriven拦截器会添加一个监听器PreResultListener(即结果处理之前执行的监听器)到invocation中,之后在invocation调用结果处理方法之前会执行该监听器来判断是否model是否改变,是否需要刷新。该监听器的处理过程实际上就是将旧的model从栈顶移除,然后将新的model压入栈顶。
7、在SurveyAction中可以看到,其设计调查的方法中使用了“surveyService.getSurveyWithChildren(sid)”,之所以使用getSurveyWithChildren()是因为存在懒加载的问题。下面总结了关于Hibernate懒加载问题的常见处理方法:
1)不用懒加载:关闭Hibernate的懒加载
缺点:Hibernate提供懒加载机制是为了提高程序的性能的,完全关闭后会降低程序性能。
2)一劳永逸解决懒加载问题.使用spring的openSessionInViewFilter.
<!-- openSessionInViewFilter,该过滤器在view(视图)渲染时始终开启session,一劳永逸解决hibernate的懒加载问题,该过滤器必须配置在struts2过滤器之前,不推荐使用(性能问题) --> <filter> <filter-name>openSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> </filter> <filter-mapping> <filter-name>openSessionInViewFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
缺点:由于该过滤器在view(视图)渲染时始终开启session,也就是该过程中始终保持数据库的连接,由于数据库连接是比较宝贵的资源,因此在高并发时会导致严重的性能问题,因此不推荐使用该方法。另一方面,其可能会导致一些莫名的问题,如事务的传播行为方面的问题等。
3)强行在service层面是初始化代理对象.
1 /** 2 * 按照id查询Survey,同时携带所有的孩子 3 */ 4 public Survey getSurveyWithChildren(Integer sid){ 5 //Survey s = surveyDao.getEntity(sid); 6 //降低耦合度,这里之所以使用第7行的代码而不使用第5行的代码,是其体现了高内聚(组件/类的内部),低耦合(组件/类之间)的特点 7 Survey s = this.getSurvey(sid); 8 //强行初始化pages和questions集合 9 for(Page p : s.getPages()){ 10 p.getQuestions().size(); 11 } 12 return s; 13 }
由于Hibernate的懒加载返回的是一个代理对象,因此我们需要手动调用懒加载的集合或对象的相关方法强行将其初始化。(注:必须是调用除了getId()和getClass()方法以外的方法,否则不能被初始化)