原文地址: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.xml
中
。
接下来我们使用 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-web
和spring-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
:静态资源映射,主要用于配置静态资源文件存放路径,如:JS
、CSS
、Image
等
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
不会直接依赖于HttpServletRequest
和HttpServletResponse
等HttpServlet
对象,它们可以通过Controller
的方法参数灵活的获取到。@Controller
用于标记在一个类上,使用它标记的类就是一个Spring MVC Controller
对象。
分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping
注解。
@Controller
只是定义了一个控制器类,而使用@RequestMapping
注解的方法才是真正处理请求的处理器。 -
@RequestMapping
RequestMapping
是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。RequestMapping
注解有六个属性:-
value
,method
-
value
:指定请求的实际地址,指定的地址可以是 URI Template 模式 -
method
:指定请求的method类型,GET
、POST
、PUT
、DELETE
等
-
-
consumes
,produces
-
consumes
:指定处理请求的提交内容类型(Content-Type
),例如application/json
,text/html
-
produces
: 指定返回的内容类型,仅当 request 请求头中的(Accept
)类型中包含该指定类型才返回
-
-
params
,headers
-
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
,分别验证 LoginInterceptor
和 PermissionInterceptor
拦截器是否生效。
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群
,及时关注我们的课程更新