zoukankan      html  css  js  c++  java
  • 第十五部分_Struts2.1拦截器深度剖析、异常处理

    恢复拦截器:interceptor。

    在我们声明拦截器(这时候默认的拦截器就不起作用了)的同时,我们一定要加上struts2提供的默认拦截器(否则访问页面的返回信息可能出乎你的意料,比如提交的表单信息出现一堆乱七八糟的信息),且我们自己声明的拦截器一定要在默认的之前。

    使用拦截器的步骤:

    1. 定义相应的拦截器类,实现Interceptor或是继承AbstractInterceptor(该抽象类空实现实现了Interceptor接口的init和destroy方法)
    2. 在struts.xml中声明拦截器,在相关Action中配置,配置的最后记得一点要添加默认的拦截器

    下面我们使用拦截器帮助我们读取xml中的参数(类比与之前用过滤器实现黑名单禁止留言的例子,这里不需要用getParameter方法就可以方便的读取参数值)。

    下面举个例子:

    新建一个com.test.interceptor包,在该包下面新建一个类MyInterceptor:

    package com.test.interceptor;
    
    import com.opensymphony.xwork2.ActionInvocation;
    import com.opensymphony.xwork2.interceptor.Interceptor;
    import com.opensymphony.xwork2.interceptor.PreResultListener;
    
    public class MyInterceptor implements Interceptor
    {
    	private String hello; // 接收拦截器在struts.xml配置中的hello属性中的值
    	
    	public String getHello()
    	{
    		return hello;
    	}
    
    	public void setHello(String hello)
    	{
    		this.hello = hello; 
    	}
    
    	public void destroy()
    	{
    		System.out.println("destroy invoked");
    	}
    	
    	public void init()
    	{
    		System.out.println("init invoked");
    		System.out.println("hello :" + hello);
    	}
    	
    	public String intercept(ActionInvocation invocation) throws Exception
    	{
    		System.out.println("intercept invoked");
    		
    		String result = invocation.invoke();
    		
    		System.out.println(result);
    		
    		return result;
    	}
    }
    

    为了更加清楚地了解拦截器的运转流程,我们多写几个拦截器,MyInterceptor2:

    package com.test.interceptor;
    
    import com.opensymphony.xwork2.ActionInvocation;
    import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
    
    public class MyInterceptor2 extends AbstractInterceptor
    {
    
    	@Override
    	public String intercept(ActionInvocation invocation) throws Exception
    	{
    		System.out.println("myInterceptor2 invoked");
    		
    		String result = invocation.invoke();
    		
    		System.out.println("result2: " + result);
    		
    		return result;
    	}
    
    }
    

    再来一个MyInterceptor3(前面是基于类的拦截器,这个是基于方法的):

    package com.test.interceptor;
    
    import com.opensymphony.xwork2.ActionInvocation;
    import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
    
    public class MyInterceptor3 extends MethodFilterInterceptor
    {
    
    	@Override
    	protected String doIntercept(ActionInvocation invocation) throws Exception
    	{
    		System.out.println("myInterceptor3 invoked");
    		
    		String result = invocation.invoke();
    		
    		System.out.println("result3 : " + result);
    		
    		return result;
    	}
    
    }
    

    然后在struts.xml中原来的基础之上进行配置:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">
    	
    	<struts>
    		
    		<package name="struts2" extends="struts-default">
    		
    		<interceptors>
    		
    			<interceptor name="myInterceptor" class="com.test.interceptor.MyInterceptor">
    					<param name="hello">world</param>
    			</interceptor>
    			
    			<interceptor name="myInterceptor2" class="com.test.interceptor.MyInterceptor2">
    			</interceptor>
    			
    			<interceptor name="myInterceptor3" class="com.test.interceptor.MyInterceptor3">
    			</interceptor>
    			
    						
    	</interceptors>
    
    		
    	            <action name="register" class="com.test.action.RegisterAction">
    				<result name="success">/success.jsp</result>
    				<result name="input">/register.jsp</result>
    				
    				<interceptor-ref name="myInterceptor"></interceptor-ref>
    				<interceptor-ref name="myInterceptor2"></interceptor-ref>
     				<interceptor-ref name="myInterceptor3"></interceptor-ref>
    							</action>
    		</package>
    	
    	
    	</struts>
    

    服务器启动过程中会输出的与我们相关的信息(截取一部分):

    信息: Parsing configuration file [struts-plugin.xml]
    2015-7-23 9:21:28 com.opensymphony.xwork2.util.logging.commons.CommonsLogger info
    信息: Parsing configuration file [struts.xml]
    init invoked
    hello :world
    2015-7-23 9:21:30 org.apache.catalina.startup.HostConfig deployDescriptor
    信息: Deploying configuration descriptor host-manager.xml
    2015-7-23 9:21:30 org.apache.catalina.startup.HostConfig deployDescriptor
    信息: Deploying configuration descriptor manager.xml

    访问http://localhost:8080/struts2/register.action,成功转移到结果页面时,控制台输出如下:

    intercept invoked
    myInterceptor2 invoked
    myInterceptor3 invoked
    validate~~~~~~~~~~~~~~~~
    execute invoked
    result3: success
    result2: success
    success

    这里validate~~~~~~~~~~~~~~得到输出是因为我们在RegisterAction的validate方法中打印了改行语句,从输出中我们可以窥得拦截器的执行时机和执行流程,它和过滤器的运行机理基本一致。

    正如我们在之前MyInterceptor3中写的,它是一个基于方法的拦截器,如何让其生效呢?那就是在配置的时候给它添加额外的参数:

    <interceptor-ref name="myInterceptor3">
    					<param name="excludeMethods">execute</param>
    				</interceptor-ref>
    

    这样,访问页面,输出变成这个样子:

    intercept invoked
    myInterceptor2 invoked
    validate~~~~~~~~~~~~~~~~
    execute invoked
    result2: success
    success

    通过这种方法,我们禁掉了第三个拦截器对execute方法的拦截。

    另外,会不会觉得当拦截器很多的时候,一个Action配置多个拦截器有些麻烦?我们想到了这个问题,Struts2的设计者也早就想到了,解决方案可以在struts-default.xml中得到一些启示:

    方法就是使用拦截器栈:

    <interceptors>
    		
    			<interceptor name="myInterceptor" class="com.test.interceptor.MyInterceptor">
    					<param name="hello">world</param>
    			</interceptor>
    			
    			<interceptor name="myInterceptor2" class="com.test.interceptor.MyInterceptor2">
    			</interceptor>
    			
    			<interceptor name="myInterceptor3" class="com.test.interceptor.MyInterceptor3">
    			</interceptor>
    			
    			<interceptor-stack name="myStack">
    				<interceptor-ref name="myInterceptor"></interceptor-ref>
    				<interceptor-ref name="myInterceptor2"></interceptor-ref>
    				<interceptor-ref name="defaultStack"></interceptor-ref>
    			</interceptor-stack>
    				
    			
    		</interceptors>
    

    这样在action中我们只要引入myStack,myInterceptor和myInterceptor2还有defaultStack就都起作用了。

    此外,如果在struts.xml中我们这样做:

    <interceptors>
    		
    			<interceptor name="myInterceptor" class="com.test.interceptor.MyInterceptor">
    					<param name="hello">world</param>
    			</interceptor>
    			
    			<interceptor name="myInterceptor2" class="com.test.interceptor.MyInterceptor2">
    			</interceptor>
    			
    			<interceptor name="myInterceptor3" class="com.test.interceptor.MyInterceptor3">
    			</interceptor>
    			
    			<interceptor-stack name="myStack">
    				<interceptor-ref name="myInterceptor"></interceptor-ref>
    				<interceptor-ref name="myInterceptor2"></interceptor-ref>
    				<interceptor-ref name="defaultStack"></interceptor-ref>
    			</interceptor-stack>
    				
    			
    		</interceptors>
    		
    		
    		<default-interceptor-ref name="myStack"></default-interceptor-ref>
    

    也就是给它添加一个默认的拦截器,这样所有的action到会引用到这个默认的拦截器。

    一个应用场景:

    拦截器最重要的使用场合是进行权限验证: 定义一个拦截器去拦截用户的请求,如果用户没有登录的话,我们就让用户登录,不让他往后走了,如果用户登录了,直接让用户进入到相应的主界面。

    struts为我们提供了一个:全局结果(global-results),所有action所共享的。

    struts2默认使用请求转发,我们可以改变它的行为,使用重定向的方式。type="redirect",我们如何知道有这么一个类型呢,回到struts-default.xml中(下面截取与我们相关的一部分):

    <package name="struts-default" abstract="true">
            <result-types>
                <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
                <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>
                <result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/>
                <result-type name="httpheader" class="org.apache.struts2.dispatcher.HttpHeaderResult"/>
                <result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/>
                <result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>
                <result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/>
                <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/>
                <result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/>
                <result-type name="plainText" class="org.apache.struts2.dispatcher.PlainTextResult" />
            </result-types>
    

    可以看到默认的是请求转发dispatcher。下面是我们的程序AuthInterceptor:

    package com.test.interceptor;
    
    import java.util.Map;
    
    import com.opensymphony.xwork2.Action;
    import com.opensymphony.xwork2.ActionInvocation;
    import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
    
    public class AuthInterceptor extends AbstractInterceptor
    {
    
    	@Override
    	public String intercept(ActionInvocation invocation) throws Exception
    	{
    		// 通常我们检查用户是否登录了,是通过session来完成的
    		// 注意到这个session不是HttpSession,它是被struts2封装好的Map,很好的实现了隐藏,此外,便于用单元测试框架进行测试,而不必借助于容器
    		Map map = invocation.getInvocationContext().getSession();
    		
    		if(null == map.get("user")) // 用户没有登录
    		{
    			return Action.LOGIN; // 返回到登录界面
    		}
    		else
    		{
    			return invocation.invoke();
    		}
    	}
    
    }
    

    接下来是structs.xml中相关的配置:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">
    	
    	<struts>
    		
    		<package name="struts2" extends="struts-default">
    		
    		<interceptors>
    		
    			<interceptor name="myInterceptor" class="com.test.interceptor.MyInterceptor">
    					<param name="hello">world</param>
    			</interceptor>
    			
    			<interceptor name="myInterceptor2" class="com.test.interceptor.MyInterceptor2">
    			</interceptor>
    			
    			<interceptor name="myInterceptor3" class="com.test.interceptor.MyInterceptor3">
    			</interceptor>
    			
    			<interceptor name="authInterceptor" class="com.test.interceptor.AuthInterceptor">
    			</interceptor>
    			
    			<interceptor-stack name="myStack">
    				<interceptor-ref name="authInterceptor"></interceptor-ref>
    				<interceptor-ref name="myInterceptor"></interceptor-ref>
    				<interceptor-ref name="myInterceptor2"></interceptor-ref>
    				<interceptor-ref name="defaultStack"></interceptor-ref>
    			</interceptor-stack>
    				
    			
    		</interceptors>
    
    		
    		<global-results>
    			<result name="login" type="redirect">/login.jsp</result>
    		</global-results>
    					
    			<action name="register" class="com.test.action.RegisterAction">
    				<result name="success">/success.jsp</result>
    				<result name="input">/register.jsp</result>
    				<!-- 
    				<interceptor-ref name="myInterceptor"></interceptor-ref>
    				<interceptor-ref name="myInterceptor2"></interceptor-ref>
    				 -->
    				
    				<interceptor-ref name="myStack"></interceptor-ref>
    				
    			</action>
    		</package>
    	
    	
    	</struts>
    

    访问http://localhost:8080/struts2/register.jsp,请求转发的方式登录地址栏显示http://localhost:8080/struts2/register.action,重定向显示http://localhost:8080/struts2/login.jsp;

    扩展——防止表单重复提交: 一般有两种方式

    • 使用重定向。
    • 使用token(令牌)。

    补充:异常处理机制

    我们还以login.jsp为例:

    <body>
      <form action="login.action">
        username: <input type="text" name="username" size="20"/><br>
        password: <input type="password" name="password" size="20"/><br/>
        
        <input type="submit" value="submit"> 
       </form>
      </body>
    

    首先在com.test.exception包下面建立两个类UsernameException:

    package com.test.exception;
    
    public class UsernameException extends Exception
    {
    	private String message;
    
    	public String getMessage()
    	{
    		return message;
    	}
    
    	public void setMessage(String message)
    	{
    		this.message = message;
    	}
    	
    	public UsernameException(String message)
    	{
    		super(message);
    		this.message = message;
    	}
    }
    

    还有一个PasswordException:

    package com.test.exception;
    
    public class PasswordException extends Exception
    {
    	private String message;
    
    	public String getMessage()
    	{
    		return message;
    	}
    
    	public void setMessage(String message)
    	{
    		this.message = message;
    	}
    	
    	public PasswordException(String message)
    	{
    		super(message);
    		this.message = message;
    	}
    }
    

    我们在LoginAction中,假定用户名不为"hello"就抛异常,密码不为"world"也抛异常:

    package com.test.action;
    
    import com.opensymphony.xwork2.ActionSupport;
    import com.test.exception.PasswordException;
    import com.test.exception.UsernameException;
    
    public class LoginAction extends ActionSupport
    {
    	private String username;
    	
    	private String password;
    
    	public String getUsername()
    	{
    		return username;
    	}
    
    	public void setUsername(String username)
    	{
    		this.username = username;
    	}
    
    	public String getPassword()
    	{
    		return password;
    	}
    
    	public void setPassword(String password)
    	{
    		this.password = password;
    	}
    	
    	public String execute() throws Exception
    	{
    		if(!"hello".equals(username))
    		{
    			throw new UsernameException("username invalid");
    		}
    		else if(!"world".equals(password))
    		{
    			throw new PasswordException("password invalid");
    		}
    		else
    		{
    			return SUCCESS;
    		}
    	}
    	
    }
    

    做到这里,访问login页面,输入非"hello"用户名,一大堆异常信息就抛给用户了,这显然是不友好的,因此我们这样处理,使得用户名非法时提示username invalid:

    在struts.xml中struts标签下的嵌套标签:

    <global-results>
    			<result name="login" type="redirect">/login.jsp</result>
    			<result name="usernameInvalid">/usernameInvalid.jsp</result>
    		</global-results>
    		
    		<global-exception-mappings>
    			<exception-mapping result="" exception=""></exception-mapping>
    		</global-exception-mappings>
    

    或者mapping信息或result信息也可以放到局部:

    <action name="login" class="com.test.action.LoginAction">
    				<exception-mapping result="usernameInvalid" exception="com.test.exception.UsernameException"></exception-mapping>
    				<result name="success">/result.jsp</result>
    				<result name="input">/login2.jsp</result>
    			</action>
    

    因此,既可以局部异常映射/结果(mapping/result),也可以全局映射。寻找规则:先找局部,在找全局。

  • 相关阅读:
    Rust入坑指南:亡羊补牢
    antirez:Redis6真的来了
    代码检查又一利器:ArchUnit
    【译】浅谈SOLID原则
    Rust入坑指南:鳞次栉比
    【译】什么才是优秀的代码
    Elasticsearch从入门到放弃:文档CRUD要牢记
    【译】利用Lombok消除重复代码
    Netty 中的心跳检测机制
    Netty 中的异步编程 Future 和 Promise
  • 原文地址:https://www.cnblogs.com/Code-Rush/p/4669384.html
Copyright © 2011-2022 走看看