Spring MVC 开发(三)

1. SpringMVC中的Interceptor

1.1. 练习目标

在主页显示的“用户名”位置添加超链接,点击后进入“个人中心”。

通过/user/info.do路径,可以显示“个人信息”页面,该页面需要登录后才允许访问。

1.2. 分析问题

一个项目中,可能绝大部分的功能都是需要登录后才允许使用的,但是,在这些功能的处理中,可能都需要执行:

// 判断Session中是否有username
if (session.getAttribute("username") == null) {
    // 没有,则意味着没有登录,则重定向到登录页
    return "redirect:login.do";
}

而大量的复制并粘贴以上代码的做法显然是不可取的!

1.3. 解决方案

拦截器(Interceptor)是SpringMVC中的组件,它是运行在DispatcherServlet之后、每个Controller之前的组件,并且,运行时,可以选择拦截放行,则会导致某些请求可以被处理或不被处理!然后,拦截器还可以运行在每个Controller处理完请求之后。

基于拦截器这样的特点,可以在项目中添加“登录拦截器”,使得要求登录的请求都先经过“登录拦截器”进行判断,在“登录拦截器”中将判断Session是否有效,如果有效,则放行,如果没有有效的Session,则直接拦截,并重定向到登录页。

1.4. 使用方式

1.4.1. 创建拦截器

所有的自定义拦截器,都必须实现HandlerInterceptor接口,所以,在项目中创建cn.tedu.spring.interceptor.LoginInterceptor,实现HandlerInterceptor

public class LoginInterceptor 
    implements HandlerInterceptor {
    
    public boolean preHandle(
            HttpServletRequest request, 
            HttpServletResponse response, 
            Object handler)
            throws Exception {
        System.out.println("LoginInterceptor.preHandle()");
        return false;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("LoginInterceptor.postHandle()");
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("LoginInterceptor.afterCompletion()");
    }

}

1.4.2. 配置拦截器

springmvc.xml中添加拦截器的配置:

<!-- 配置拦截器链 -->
<mvc:interceptors>
    <!-- 配置第1个拦截器 -->
    <mvc:interceptor>
        <!-- 1. 拦截的路径 -->
        <mvc:mapping path="/user/info.do" />
        <mvc:mapping path="/main/index.do" />
        <!-- 2. 指定拦截器类 -->
        <bean class="cn.tedu.spring.interceptor.LoginInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

1.4.3. 执行效果

当尝试访问以上拦截的路径时,可以看到拦截器的preHandle()方法被执行,且界面上显示一片空白,而没有被拦截器路径可以正常访问,拦截器中的任何方法都没有执行。

1.4.4. 完善登录拦截

在控制器中,成功登录时,会在Session中放入用户名,且存入时使用的名称是username,所以,判断是否登录,就是检查Session是否有名为username的值,如果值是存在的,即已经登录,如果值不存在,则没有登录,所以,在拦截器的preHandle()方法中:

public boolean preHandle(
        HttpServletRequest request, 
        HttpServletResponse response, 
        Object handler)
        throws Exception {
    System.out.println("LoginInterceptor.preHandle()");
    // 获取Session
    HttpSession session
        = request.getSession();
    // 判断session中是否有登录信息
    if (session.getAttribute("username") == null) {
        // 没有登录信息,则:重定向到登录页
        response.sendRedirect("../user/login.do");
        // 执行拦截
        return false;
    } else {
        // 有登录信息,则:允许正常访问,直接放行
        return true;
    }
}

1.4.5. 拦截器的更多配置

在配置拦截器时,使用<mvc:mapping path="" />可以配置拦截哪些路径,同一个拦截器的配置中,可以有多个这样的节点。

在配置拦截路径时,可以使用*作为通配符,例如:

<mvc:mapping path="/user/*" /> 

以上配置将表示拦截/user之下的所有路径,例如:/user/login.do/user/reg.do/user/handler_login.do/user/handler_reg.do/user/inf.do等。

使用1个星号(*)只能通配1层路径,即以上配置对于/user/info/change.do这样的路径是不起作用的!如果需要无视路径的层级,应该使用2个星号(*),即配置为:

<mvc:mapping path="/user/**" />

使用以上配置,可适用于例如:/user/login.do/user/info/change.do/user/info/change/password.do等。

由于使用于通配符后,拦截的范围可能过大,还可以使用<mvc:exclude-mapping />节点来配置例外

<mvc:interceptor>
    <!-- 1. 拦截的路径 -->
    <mvc:mapping path="/user/**" />
    <mvc:mapping path="/main/index.do" />

    <!-- 2. 例外的路径,不拦截的路径,即白名单-->
    <mvc:exclude-mapping path="/user/reg.do" />
    <mvc:exclude-mapping path="/user/handle_reg.do" />
    <mvc:exclude-mapping path="/user/login.do" />
    <mvc:exclude-mapping path="/user/handle_reg.do" />

    <!-- 3. 指定拦截器类 -->
    <bean class="cn.tedu.spring.interceptor.LoginInterceptor" />
</mvc:interceptor>

这些例外可以通俗的理解为白名单,即拦截器对于这些路径的请求完全不受理。

注意:以上配置,必须先配置拦截路径,再配置例外,最后配置拦截器类。

2. 字符编码过滤器

使用SpringMVC框架时,默认并不支持中文,为了能够支持中文,需要通过request.setCharacterEncoding("utf-8")这类的语法来指定使用的字符编码!

这项任务不能够在控制器(Controller)中来完成,因为,指定编码必须在Servlet或其之前就执行,如果运行到Servlet时就已经乱码,然后,在控制器中再来调整,就已经晚了!也就意味着,通过SpringMVC的拦截器是无法实现编码的调整的!

解决方案是使用SpringMVC框架中自带的CharacterEncodingFilter类,这个类是一个过滤器类,是运行在所有的Servlet之前的,所以,通过过滤器指定了编码后,适用于所有的Servlet及后续的流程!

这个类并没有明显的指定使用的编码,所以,在配置时,还需要通过初始化参数来确定所使用的编码!

具体的配置是在web.xml中添加:

<!-- 配置字符编码过滤器 -->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

其它

1. 验证用户提交的数据

在开发规范中,认为凡是用户提交的数据,永远是不可靠的,应该对数据进行有效性验证!

通常,关于数据格式(长度、组成字符)的验证,应该使用正则表达式来验证!

package cn.tedu.spring.util;

/**
 * 字符验证工具类
 * @author adminitartor
 */
public class TextValidator {
    
    /**
     * <p>验证用户名的正则表达式</p>
     * <p>验证规则:</p>
     * <ul>
     * <li>必须由4~16字符组成;</li>
     * <li>必须由大写字母、小写字母、数字、下划线组成,不允许使用其它字符;</li>
     * <li>第1个字符必须是大写或小写字母。</li>
     * </ul>
     */
    public static final String REG_EXP_USERNAME 
        = "[a-zA-Z]{1}[a-zA-Z0-9_]{3,15}";
    
    /**
     * 检查用户名的格式是否正确
     * @param username 用户名
     * @return 如果用户名符合验证规则,则返回true,否则,返回false
     * @see #REG_EXP_USERNAME
     */
    public static boolean checkUsername(String username) {
        if (username == null) {
            return false;
        }
        return username.matches(REG_EXP_USERNAME);
    }
    
}

2. 关于内存

一个完整的计算机硬件系统应该包括:运算器、控制器、存储器、输入设备、输出设备。

存储器分为内部存储器(内存/主存)和外部存储器(外存/辅存)。

内存只有:主板上的ROM、CPU内部的Cache(高速缓存)、内存条(RAM)。

RAM:Random Access Memory。

通常在开发领域讨论的内存指的都是内存条。

RAM的特性有:

  • 它是唯一一个CPU可以直接访问的硬件;

  • 一旦断电,内存中的所有数据会全部丢失;

  • 正在执行的程序和数据必须在内存中。

3. 关于static

使用static可以修饰类的成员(属性/成员变量、方法、内部类、内部接口)。

它的特性是:唯一、常驻

唯一:假设Person类中存在被static修饰的static int hands = 2;,无论把Personnew多少次,无论使用哪个对象调用hands属性,对应的都是同一个值,所以,被static修饰的成员不归属于任何一个对象,在调用时,应该使用 类名.成员 的语法来调用,而不推荐使用 对象.成员 来调用。

常驻:被static修饰的成员,会在它所在的类的名称第一次出现在运行的代码中时,就会被加载到内存,并且,会持续的一直在内存中,所以,表现出“常驻内存”的特性!所以,被static修饰的成员不可以直接访问非static成员!

由于static成员是唯一的,所以,慎用static修饰属性(成员变量),因为可能存在多线程下的数据安全问题(多个线程、同时、对同一个数据、进行修改,则可能导致线程安全问题)。

由于static成员是常驻的,所以,无论是用于修饰哪种类型的成员,都应该慎用,因为可能过度的消耗内存!

4. 内存泄露/溢出(Leak)

内存泄露的主要原因是:尝试释放某个资源,却无法释放,而该资源将持续占用内存,却又无法被使用!

当垃圾回收机制尝试回收垃圾时,可能发现这些数据仍处于“使用状态”(可能与另一些资源保持了连接等等),则不会把这些数据视为垃圾,也就不会回收这些数据占用的内存空间!而由于程序中已经无法再使用变量操作这些数据了,对于开发者而言,这些数据已经用不着了,所以,就出现了“用不着的数据没有被当作垃圾来回收,反而一直占用内存”的现象!

所以,少量的内存泄露是没有明显的危害,但是,如果积少成多,会导致可用内存越来越少,当少到极点还继续使用,就会导致数据溢出。

解决内存泄露的做法就是:及时释放掉不需要使用的资源,例如流等等,应该及时调用close()方法进行归还或关闭。

另外,关于垃圾回收,JVM中的垃圾回收机制会定期自动清理垃圾数据,无须开发者手动释放数据所占用的内存空间,即使调用System.gc()方法也不能保证垃圾回收机制马上来回收数据!

5. 乱码

字符集相当于一本字典,记录了每个字符与二进制码的对应关系!

a   110 0001

在ASCII码中,记录了英文输入法下的所有字母、数据、标点符号与二进制码的对应关系。

由于1个字节只有8个二进制位,无法表示中文(汉字的字符种类太多),所以,需要使用2个字节,即16个二制位来表达!

常见的支持中文的编码有:UTF-8、GBK、GB2312……,还有一些其它编码是完全不支持中文的,例如:ISO8859-1、latin1……

由于支持中文的编码格式有多种,但是,每种编码的二进制码与汉字的对应关系不一样,所以,如果存和取的时候,使用了不同的编码,就会导致乱码!

所以,解决乱码的答案:使用统一的、支持中文的编码。

在项目中,需要统一编码的位置:存储位置(例如数据库等)、显示位置(例如HTML/JSP页面、终端/控制台等)、传输过程(接收请求时、响应时、连接数据库时)、程序源代码……

5. 拦截器与过滤器

首先,这2个组件的执行切入点不相同,过滤器是执行在所有的Servlet之前的,而拦截器是执行在DispatcherServlet之后、控制器之前的。

过滤器是Java EE体系中的,使用时,需要在web.xml中进行配置,而拦截器是SpringMVC框架中的,使用时,需要在Spring的配置文件中进行配置。

过滤器的过滤规则相对比较简单,只能通过<url-patter>节点配置它的适用范围,而拦截器的过滤规则相对比较灵活,即可以配置多项黑名单,还可以配置多项白名单。

猜你喜欢

转载自www.cnblogs.com/wood-life/p/10289321.html