Java单体应用 - 常用框架 - 07.Spring MVC - 项目重构(iot-admin3)

原文地址:http://www.work100.net/training/monolithic-frameworks-spring-mvc-iot-admin3.html
更多教程:光束云 - 免费课程

项目重构(iot-admin3)

序号 文内章节 视频
1 概述 -
2 配置和结构重构 -
3 Controller控制器重构 -
4 使用拦截器 -
5 实例源码 -

请参照如上章节导航进行阅读

1.概述

我们继续以上一章节 Spring Web 中的案例项目 iot-admin2 为基础,复制一份重命名为 iot-admin3,修改 pom.xmliot-admin3

接下来我们使用 Spring MVC 重构 iot-admin3 项目。

2.配置和结构重构

2.1.修改POM

spring-web 的依赖改为对 spring-webmvc 的依赖,同时删除对 spring-context 的依赖:


    org.springframework
    spring-webmvc
    5.2.3.RELEASE

spring-webmvc 中已经依赖了 spring-webspring-context

完整的 pom.xml 代码如下:



    4.0.0

    net.work100.training.stage2
    iot-admin3
    1.0.0-SNAPSHOT
    war

    
        5.2.3.RELEASE
        4.0.1
        1.2
        4.12
        1.7.25
    

    
        
            org.springframework
            spring-webmvc
            ${spring.version}
        

        
            javax.servlet
            javax.servlet-api
            ${javax.servlet-api.version}
        
        
            javax.servlet
            jstl
            ${javax.jstl.version}
        

        
            junit
            junit
            ${junit.version}
            test
        
        
            org.slf4j
            slf4j-log4j12
            ${slf4j.version}
        
    

2.2.配置 web.xml

CharacterEncodingFilter

配置字符集过滤器,用于解决中文编码问题


    encodingFilter
    org.springframework.web.filter.CharacterEncodingFilter
    
        encoding
        UTF-8
    
    
        forceEncoding
        true
    


    encodingFilter
    /*

DispatcherServlet

配置 Spring 的 Servlet 分发器处理所有 HTTP 的请求响应


    springServlet
    org.springframework.web.servlet.DispatcherServlet
    
        contextConfigLocation
        classpath*:/spring-mvc*.xml
    
    1


    springServlet
    /

load-on-startup 标记容器是否在启动的时候就加载这个servlet,当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载;当为正数时,其值越小,启动该servlet的优先级越高。

完整 web.xml 代码如下:




    
        contextConfigLocation
        classpath:spring-context*.xml
    
    
        org.springframework.web.context.ContextLoaderListener
    

    
        encodingFilter
        org.springframework.web.filter.CharacterEncodingFilter
        
            encoding
            UTF-8
        
        
            forceEncoding
            true
        
    
    
        encodingFilter
        /*
    

    
        springServlet
        org.springframework.web.servlet.DispatcherServlet
        
            contextConfigLocation
            classpath*:/spring-mvc*.xml
        
        1
    
    
        springServlet
        /
    

2.3.配置 spring-mvc.xml

在目录 src/main/resources 下新建文件 spring-mvc.xml,代码如下:




    Spring MVC Configuration

    
    

    
    
        
    

    
    

    
    
        
        
    

    
    

相关配置说明:

  • context:property-placeholder:动态加载属性配置文件以变量的方式引用需要的值
  • context:component-scan:当前配置文件为 MVC 相关,故只需要扫描包含 @Controller 的注解即可,由于 spring-context.xml 配置文件中也配置了包扫描,所以还需要排除 @Controller 的注解扫描。
  • InternalResourceViewResolver:视图文件解析器的一种,用于配置视图资源的路径和需要解释的视图资源文件类型,这里有两个需要配置的属性 prefix(前缀)以及 suffix(后缀)。
    • prefix:配置视图资源路径,如:/WEB-INF/views/
    • suffix:配置视图资源类型,如:.jsp
  • mvc:resources:静态资源映射,主要用于配置静态资源文件存放路径,如:JSCSSImage

2.4.配置及结构完善

配置 iot-admin.properties

spring-mvc.xml 中,我们配置了 用于动态加载属性配置文件,实际开发中我们会将系统所需的一些配置信息封装到 .properties 配置文件中便于统一的管理。

在目录 src/main/resources 下创建一个名为 iot-admin.properties 的配置文件,内容如下:

#============================#
#==== Framework settings ====#
#============================#

# views path
web.view.prefix=/WEB-INF/views/
web.view.suffix=.jsp

重构项目结构

WEB-INF 下新建目录 views,然后将 .jsp 文件移动到 views 下。

webapp 下新建目录 static,然后将 assets 文件夹及其所属文件移动到 static 下。

目录结构如下图:

2.5.修改 spring-context.xml

由于 spring-mvc.xml 中已经配置了 @Controller 注解的扫描,而 spring-context.xml 中配置的是扫描全部注解,故在这里需要将 @Controller 注解的扫描配置排除。

修改 spring-context.xml 配置,代码如下:




    

    
    
        
    

3.Controller控制器重构

3.1.修改注解方式

UserDaoImpl 类

修改 UserDaoImpl 类的注解方式,代码如下:

package net.work100.training.stage2.iot.admin.dao.impl;

import net.work100.training.stage2.iot.admin.dao.UserDao;
import net.work100.training.stage2.iot.admin.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;

/**
 * 

Title: UserDaoImpl

*

Description:

*

Url: http://www.work100.net/training/monolithic-frameworks-spring-mvc.html

* * @author liuxiaojun * @date 2020-02-13 13:23 * ------------------- History ------------------- * * 2020-02-13 liuxiaojun 初始创建 * ----------------------------------------------- */ @Repository public class UserDaoImpl implements UserDao { private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class); public User getUser(String loginId, String loginPwd) { logger.debug("调用方法 getUser(loginId:{}, loginPwd:{})", loginId, loginPwd); // 根据 loginId 查询出用户信息 User user = getUserByLoginId(loginId); if (user != null) { // 验证 loginPwd 是否正确(区分大小写) if (user.getLoginPwd().equals(loginPwd)) { return user; } } return null; } /** * 获取模拟的用户数据 * * @param loginId 登录ID * @return */ private User getUserByLoginId(String loginId) { // 模拟 DB 存在的用户数据 User dbUser = new User(); dbUser.setUserName("Xiaojun"); dbUser.setLoginId("admin"); dbUser.setLoginPwd("admin"); // 判断是否存在 loginId 的用户(忽略大小写) if (dbUser.getLoginId().equalsIgnoreCase(loginId)) { logger.info("匹配上用户:{}", dbUser); return dbUser; } logger.warn("未匹配任何用户,将返回 null"); return null; } }

UserServiceImpl 类

修改 UserServiceImpl 类的注解方式,代码如下:

package net.work100.training.stage2.iot.admin.service.impl;

import net.work100.training.stage2.iot.admin.dao.UserDao;
import net.work100.training.stage2.iot.admin.entity.User;
import net.work100.training.stage2.iot.admin.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 

Title: UserServiceImpl

*

Description:

*

Url: http://www.work100.net/training/monolithic-frameworks-spring-mvc.html

* * @author liuxiaojun * @date 2020-02-13 13:26 * ------------------- History ------------------- * * 2020-02-13 liuxiaojun 初始创建 * ----------------------------------------------- */ @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; public User login(String loginId, String loginPwd) { return userDao.getUser(loginId, loginPwd); } }

注解说明

  • @Repository :不需要指定名称,因为只有一个实现类
  • @Service : 不需要指定名称,因为只有一个实现类
  • @Autowired :自动注入,Spring 自动寻找实现类来实例化对象

3.2.重构 Controller 代码

修改 LoginController

修改 LoginController 类,代码如下:

package net.work100.training.stage2.iot.admin.web.controller;

import net.work100.training.stage2.iot.admin.entity.User;
import net.work100.training.stage2.iot.admin.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 

Title: LoginController

*

Description:

*

Url: http://www.work100.net/training/monolithic-frameworks-spring-mvc.html

* * @author liuxiaojun * @date 2020-02-13 13:28 * ------------------- History ------------------- * * 2020-02-13 liuxiaojun 初始创建 * ----------------------------------------------- */ @Controller public class LoginController { @Autowired private UserService userService; /** * 登录页面 * * @return */ @RequestMapping(value = {"", "login"}, method = RequestMethod.GET) public String login() { return "login"; } /** * 登录逻辑 * * @param loginId 登录ID * @param loginPwd 登录密码 * @return */ @RequestMapping(value = "login", method = RequestMethod.POST) public String login(@RequestParam(required = true) String loginId, @RequestParam(required = true) String loginPwd) { User user = userService.login(loginId, loginPwd); // 登录成功 if (user != null) { return "redirect:main"; } // 登录失败 else { return login(); } } }

新建 MainController

新建 MainController 类,代码如下:

package net.work100.training.stage2.iot.admin.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * 

Title: MainController

*

Description:

* * @author liuxiaojun * @date 2020-02-20 15:19 * ------------------- History ------------------- * * 2020-02-20 liuxiaojun 初始创建 * ----------------------------------------------- */ @Controller public class MainController { /** * 主页 * * @return */ @RequestMapping(value = "main", method = RequestMethod.GET) public String main() { return "main"; } }

运行

重新启动 Tomcat 验证效果。

注解说明

  • @Controller

    Spring MVC 中,控制器 Controller 负责处理由 DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个 Model ,然后再把该 Model 返回给对应的 View 进行展示。

    Spring MVC 中提供了一个非常简便的定义 Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用 @Controller 标记一个类是 Controller ,然后使用 @RequestMapping@RequestParam 等一些注解用以定义 URL 请求Controller 方法之间的映射,这样的 Controller 就能被外界访问到。

    此外 Controller 不会直接依赖于 HttpServletRequestHttpServletResponseHttpServlet 对象,它们可以通过 Controller 的方法参数灵活的获取到。

    @Controller 用于标记在一个类上,使用它标记的类就是一个 Spring MVC Controller 对象。
    分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了 @RequestMapping 注解。
    @Controller 只是定义了一个控制器类,而使用 @RequestMapping 注解的方法才是真正处理请求的处理器。

  • @RequestMapping

    RequestMapping 是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径

    RequestMapping 注解有六个属性:

    • valuemethod

      • value:指定请求的实际地址,指定的地址可以是 URI Template 模式
      • method:指定请求的method类型, GETPOSTPUTDELETE
    • consumesproduces

      • consumes:指定处理请求的提交内容类型(Content-Type),例如 application/json, text/html
      • produces: 指定返回的内容类型,仅当 request 请求头中的(Accept)类型中包含该指定类型才返回
    • paramsheaders

      • params:指定 request 中必须包含某些参数值是,才让该方法处理
      • headers:指定 request 中必须包含某些指定的 header 值,才能让该方法处理请求

4.使用拦截器

4.1.简介

Spring Web MVC 的处理器拦截器,类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。

4.2.应用场景

  • 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等
  • 权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面
  • 性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间
  • 通用行为:读取 Cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现

4.3.如何使用拦截器

  • Spring MVC 拦截器需要实现 HandlerInterceptor 接口,该接口定义了 3 个方法,分别为:

    • preHandle()
    • postHandle()
    • afterCompletion()

    通过重写这 3 个方法来对用户的请求进行拦截处理的。

  • preHandle(HttpServletRequest request, HttpServletResponse response, Object handle)

    该方法在请求处理之前进行调用。Spring MVC 中的 Interceptor 是链式调用的,在一个应用中或者说是在一个请求中可以同时存在多个 Interceptor 。
    每个 Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是 Interceptor 中的 preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求做一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。
    该方法的返回值是布尔值 Boolean 类型的,当它返回为 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为 true 时,就会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候,就会是调用当前请求的 Controller 中的方法。

  • postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView)

    通过 preHandle 方法的解释咱们知道这个方法包括后面要说到的 afterCompletion 方法都只能在当前所属的 Interceptor 的 preHandle 方法的返回值为 true 的时候,才能被调用。
    postHandle 方法在当前请求进行处理之后,也就是在 Controller 中的方法调用之后执行,但是它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以咱们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。
    postHandle 方法被调用的方向跟 preHandle 是相反的,也就是说,先声明的 Interceptor 的 postHandle 方法反而会后执行。

  • afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)

    也是需要当前对应的 Interceptor 的 preHandle 方法的返回值为 true 时才会执行。
    因此,该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行,这个方法的主要作用是用于进行资源清理的工作。

4.4.创建登录拦截器

我们知道对系统的相关操作是需要登录后才可以使用的,当未登录时是无法直接访问需要登录权限的操作的,为了做到这个效果,我们使用登录拦截器来判断用户是否登录,如果用户已登录则放行让用户继续操作,否则就将其跳转到登录页。

net.work100.training.stage2.iot.admin.web 包下新建一个包 interceptor,然后在 interceptor 包下定义一个名为 LoginInterceptor 的拦截器,代码如下:

package net.work100.training.stage2.iot.admin.web.interceptor;

import net.work100.training.stage2.iot.admin.entity.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 

Title: LoginInterceptor

*

Description:

*

Url: http://www.work100.net/training/monolithic-frameworks-spring-mvc.html

* * @author liuxiaojun * @date 2020-02-20 16:22 * ------------------- History ------------------- * * 2020-02-20 liuxiaojun 初始创建 * ----------------------------------------------- */ public class LoginInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("LoginInterceptor"); User user = (User) request.getSession().getAttribute("user"); // 未登录 if (user == null) { response.sendRedirect("/login"); } // 为 true 时放行,进入 postHandle return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }

LoginInterceptor 拦截器的主要功能是检测用户是否登录,如果未登录则跳转至登录页(/login)

4.5.在 spring-mvc.xml 中配置拦截器

拦截器定义后还需要在 spring-mvc.xml 中配置拦截器,代码如下:


    
        
        
        
        
    

相关配置说明:

  • mvc:interceptor:定义一个拦截器
    • mvc:mapping:映射路径,需要拦截的请求路径
    • mvc:exclude-mapping:需要排除的请求路径,比如登录页本身是不需要拦截的,这里还包括了静态资源路径也是不需要拦截的
    • bean class:配置指定的拦截器对象

4.6.完善使用场景

我们实际的使用场景中还应该有个判断逻辑:当用户已经登录了,访问 /login 时,需要将请求跳转至 /main

创建拦截器 PermissionInterceptor

这时我们需要再创建一个拦截器 PermissionInterceptor,代码如下:

package net.work100.training.stage2.iot.admin.web.interceptor;

import net.work100.training.stage2.iot.admin.entity.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 

Title: PermissionInterceptor

*

Description:

*

Url: http://www.work100.net/training/monolithic-frameworks-spring-mvc.html

* * @author liuxiaojun * @date 2020-02-20 21:16 * ------------------- History ------------------- * * 2020-02-20 liuxiaojun 初始创建 * ----------------------------------------------- */ public class PermissionInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("PermissionInterceptor"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { if ("login".equals(modelAndView.getViewName())) { User user = (User) request.getSession().getAttribute("user"); if (user != null) { response.sendRedirect("/main"); } } } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }

修改 spring-mvc.xml 配置

增加 PermissionInterceptor 拦截器的配置,代码如下:



    
        
        
        
        
    
    
        
        
    

拦截器的执行顺序为:从下往上,即先定义的后执行

spring-mvc.xml 完整配置:




    Spring MVC Configuration

    
    

    
    
        
    

    
    

    
    
        
        
    

    
    

    
    
        
            
            
            
            
        
        
            
            
        
    

4.7.运行验证

重启 Tomcat ,分别验证 LoginInterceptorPermissionInterceptor 拦截器是否生效。

5.实例源码

实例源码已经托管到如下地址:

  • https://github.com/work100-net/training-stage2/tree/master/iot-admin3
  • https://gitee.com/work100-net/training-stage2/tree/master/iot-admin3

上一篇:简介

下一篇:Maven 模块化开发


如果对课程内容感兴趣,可以扫码关注我们的 公众号QQ群,及时关注我们的课程更新

公众号

QQ交流群

你可能感兴趣的:(Java单体应用 - 常用框架 - 07.Spring MVC - 项目重构(iot-admin3))