Spring MVC@Component/@Controller/@Service/@Repository/@RequestParam/@RequestMapping拦截器,过滤器session

本文深入讲解SpringMVC框架,探讨其在SSM框架中的角色,包括SpringMVC的基本使用、控制器设计、视图数据传递、用户列表显示案例,以及如何解决Web软件开发中的常见问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring在Spring基础之上提供了Spring MVC 框架,Spring + Spring MVC + MyBatis 合称 SSM 框架。

1. Web框架是Web软件的半成品,Web框架封装了Web应用程序中大部分技术细节,利用Web框架开发软件快速高效。 使用广泛。
2. Web框架不是必须品,Web软件完全可以不采用Web框架。有些企业会创建自己的框架级解决方案。
3. 使用Web框架必须按照Web的约定使用才能做到事半功倍的效果。

使用Spring MVC 步骤

1. 导入Spring MVC相关包
2. 配置前端控制器
3. 编写配置文件
    1. 配置视图解析器
4. 编写子控制器
5. 编写视图页面

Spring MVC 的请求流程:

### 使用Spring MVC

1. 导入包

<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>4.3.10.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>

		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.4</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.6</version>
		</dependency>

		<dependency>
			<groupId>javax.annotation</groupId>
			<artifactId>javax.annotation-api</artifactId>
			<version>1.3.2</version>
		</dependency>

		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>  

> 这里也导入了项目必须依赖的其他包。

2. 配置前端控制器 web.xml

<servlet>
			<description></description>
			<display-name>DispatcherServlet</display-name>
			<servlet-name>DispatcherServlet</servlet-name>
			<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
			<init-param>
				<param-name>contextConfigLocation</param-name>
				<param-value>classpath:mvc.xml</param-value>
			</init-param>
			<load-on-startup>1</load-on-startup>
		</servlet>
		<servlet-mapping>
			<servlet-name>DispatcherServlet</servlet-name>
			<url-pattern>*.do</url-pattern>
		</servlet-mapping>

> 将*.do请求全部转到 前端控制器

3. 编写Spring MVC 配置文件 mvc.xml

<!-- 扫描控制器组件 -->
		<context:component-scan 
			base-package="controller"/>
		
		<!-- 通知HandlerMapping,处理RequestMapping注解 -->
		<mvc:annotation-driven/>
	
		<!-- 视图处理器 
			当控制器返回视图名称时候,利用视图解析器拼接
			前后缀,找到视图对象
			如: 控制器返回 view 时候,拼接前后缀以后
			   /WEB-INF/jsp/view.jsp 用于定位jsp文件 -->
		<bean id="viewResolver" 
			class="org.springframework.web.servlet.view.InternalResourceViewResolver">
			<!-- prefix: 前缀, suffer:后缀-->
			<property name="prefix" 
				value="/WEB-INF/jsp/"/>
			<property name="suffix" value=".jsp"/>
		</bean>

4. 编写控制器

@Controller
		public class HelloController {
		
			@RequestMapping("/demo.do")
			public String execute() {
				System.out.println("Hello World!"); 
				return "view"; // view.jsp
			}
			
		}

5. 编写视图 /WEB-INF/jsp/view.jsp

<%@ page language="java" 
			contentType="text/html; charset=UTF-8"
		    pageEncoding="UTF-8"%>
		<!DOCTYPE html>
		<html>
		<head>
		<meta charset="">
		<title>View</title>
		</head>
		<body>
			<h1>Hello World!</h1>
		</body>
		</html>

6. 测试。

### 控制器向JSP传送数据

Spring利用ModelAndView封装数据,并且传递JSP,这种封装的好处是将控制器与Request解耦,使控制器可以进行单独的测试。

原理:

案例:

1. 编写控制器:
 


		//将控制器中的计算结果传递到JSP
		@RequestMapping("/test.do")
		public ModelAndView test() {
			//创建一个Map存储显示数据
			//用string 代表显示的视图
			Map<String, Object> model = 
					new HashMap<String, Object>(); 
			String view="test";
			model.put("message", "Hello Spring MVC");
			return new ModelAndView(view, model); 
		}

2. 编写JSP /WEB-INF/jsp/test.jsp

<%@ page language="java" 
			contentType="text/html; charset=UTF-8"
		    pageEncoding="UTF-8"%>
		<!DOCTYPE html>
		<html>
		<head>
		<meta charset="">
		<title>View</title>
		</head>
		<body>
			<h1>从控制器传送数据到视图</h1>
			<p>${message}</p>
		</body>
		</html>

3. 测试

利用ModelMap传递数据到JSP

1. 编写控制器


		//利用ModelMap传递参数到JSP页面
		@RequestMapping("/test2.do")
		public String test2(ModelMap model) {
			model.put("message", "test2");
			return "test"; //test.jsp
		}

2. 测试

### 案例显示用户列表

1. 编写用户实体类

public class User implements Serializable {
			
			private String name;
			private int age;
			private String email;
			
			public User() {
			}
		
			public User(String name, int age, String email) {
				super();
				this.name = name;
				this.age = age;
				this.email = email;
			}
		
			public String getName() {
				return name;
			}
		
			public void setName(String name) {
				this.name = name;
			}
		
			public int getAge() {
				return age;
			}
		
			public void setAge(int age) {
				this.age = age;
			}
		
			public String getEmail() {
				return email;
			}
		
			public void setEmail(String email) {
				this.email = email;
			}
		
			@Override
			public String toString() {
				return "User [name=" + name + ", age=" + age + ", email=" + email + "]";
			}
			
		}

2. 编写控制器

@RequestMapping("/list.do")
		public String list(ModelMap model) {
			List<User> users=new ArrayList<User>();
			users.add(new User("光头强", 40, "110@tedu.cn"));
			users.add(new User("王宝强", 32, "120@tedu.cn"));
			users.add(new User("马蓉", 30, "119@tedu.cn"));
			users.add(new User("老宋", 22, "123@tedu.cn"));
			//向页面传送数据
			model.put("users", users);
			return "list"; 
		}

3. 引入JSTL

        <dependency>
			<groupId>jstl</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency> 	

4. 编写视图 list.jsp

<%@ page language="java" 
			contentType="text/html; charset=UTF-8"
		    pageEncoding="UTF-8"%>
		<%@ taglib prefix="c" 
			uri="http://java.sun.com/jsp/jstl/core" %> 
		<!DOCTYPE html>
		<html>
		<head>
		<meta charset="">
		<title>用户列表</title>
		</head>
		<body>
			<h1>用户列表</h1>
			<ul>
				<c:forEach items="${users}" var="user">
					<li>${user.name}, ${user.age}, ${user.email}</li>
				</c:forEach>
			</ul>
		</body>
		</html>

### SpringMVC

#### 1. 作用

原生的JavaEE中,使用Servlet接收并处理请求的,在实际应用中,某个项目中可能涉及许多种请求,例如:用户注册、用户登录、修改密码、修改资料、退出登录等许多功能,在许多情况下,自行创建的每个Servlet只处理1种请求,即对应1个功能,如果项目中有几百个功能,则需要几百个Servlet来处理!进而,就会导致在项目运行过程中,需要几百个Servlet的实例,同时,编写代码时,每个Servlet都需要在web.xml中进行配置,就会产生大量的配置,在代码管理方面,难度也比较大

使用SpringMVC主要解决了V-C交互问题,即Controller如何接收用户的请求,如何向客户端进行响应,同时,它解决了传统的JavaEE中的问题,每个Controller可以处理若干种不同的请求,且并不存在大量的配置。

#### 2. 练习

**1. 目标**

用户通过`http://localhost:8080/PROJECT/user/reg.do`即可访问用户注册页面,并且在该页面中,表单可以将用户的注册数据提交到`http://localhost:8080/PROJECT/user/handle_reg.do`。

同理,通过`http://localhost:8080/PROJECT/user/login.do`可以访问用户登录页,该页面中可以将用户的登录数据提交到`http://localhost:8080/PROJECT/user/handle_reg.do`。

**2. 创建项目**

创建新的`Maven Project`,勾选`Create a simple project ...`,`Group Id`为`cn.tedu.spring`,`Artifact Id`为`SPRINGMVC-02-UMS`,`Packaging`为`war`,然后点击至创建完成。

首先,项目默认报错,需要通过Eclipse生成web.xml文件。

然后,勾选Tomcat运行环境。

然后,在`pom.xml`中添加必要的依赖,SpringMVC项目至少需要添加`spring-webmvc`依赖。

然后,在`web.xml`中对`DispatcherServlet`进行配置:

<servlet>
		<servlet-name>SpringMVC</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	</servlet>

	<servlet-mapping>
		<servlet-name>SpringMVC</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

最后,在此前的项目中,复制Spring的配置文件到当前项目中来,同时,为了保证项目启动时,就加载该配置文件,应该修改`DispatcherServlet`的配置,使之成为项目启动时即加载的Servlet,且配置它的启动参数,指定Spring配置文件的路径:

<servlet>
		<servlet-name>SpringMVC</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:spring-mvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>SpringMVC</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

**3. 显示注册页**

创建`cn.tedu.spring.UserController`类,在类之前添加`@Controller`,表示这个类是一个**控制器组件**,同时,为了保证这个类将被Spring容器所管理,还需要在Spring的配置文件中添加组件扫描,扫描该类所在的包:

<!-- 组件扫描 -->
	<context:component-scan 
		base-package="cn.tedu.spring" />

如果希望某个类能够被Spring管理,第1种做法是在Spring的配置文件中通过<bean>节点进行配置,第2种做法是在Spring的配置文件中添加组件扫描,并在类之前添加@Component/@Controller/@Service/@Repository注解。如果配置自行编写的类,应该使用第2种做法,如果配置是他人编写的类,则应该使用第1种做法。

由于后续涉及的请求有`/user/reg.do`、`/user/handle_reg.do`等,都是使用`/user`作为请求路径的前一部分的,所以,应该在之前添加`@RequestMapping("/user")`注解。

目前,处理请求的方法应该是:
1. 使用`public`权限;
2. 使用`String`类型的返回值(暂定);
3. 方法名称可以自定义;
4. 方法没有参数;

在类中添加处理请求的方法:

@RequestMapping("/reg.do")
	public String showReg() {
		System.out.println("UserController.showReg()");
		return null;
	}

完成后,可以在浏览器访问`http://localhost:8080/SPRINGMVC-02-UMS/user/reg.do`,在Eclipse的控制台应该输出`UserController.showReg()`,则表示`/user/reg.do`的请求会被以上方法进行处理!

通常使用的视图解析是`InternalResourceViewResolver`,它将根据配置的前缀+处理请求的方法的返回值+配置的后缀,得到位于`webapp`下的某个视图文件的路径

默认情况下,处理请求的方法返回的字符串,表示最终将**转发**到某个JSP页面,且返回值是JSP的路径中去除视图解析器中前缀与后缀的部分。

假设后续创建的JSP文件是`/webapp/WEB-INF/register.jsp`,且视图解析器配置的前缀是`/WEB-INF/`,而后缀是`.jsp`,则处理请求的方法应该返回`"register"`。

所以,处理请求的方法:


	@RequestMapping("/reg.do")
	public String showReg() {
		System.out.println("UserController.showReg()");
		return "register";
	}


并且,在Spring的配置文件中添加视图解析器的配置:


	<!-- 视图解析器 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/" />
		<property name="suffix" value=".jsp" />
	</bean>

基于以上视图解析器的配置,和处理请求的方法的返回值,则需要创建`webapp/WEB-INF/register.jsp`文件用于显示注册页。

创建完成后,再次访问`http://localhost:8080/SPRINGMVC-02-UMS/user/reg.do`路径,则可以看到刚才创建的JSP页面。

最后,完善页面,添加注册表单元素:

<form method="post" action="handle_reg.do">
		<div>请输入用户名</div>
		<div><input name="username" /></div>
		<div>请输入密码</div>
		<div><input name="password" /></div>
		<div>请输入年龄</div>
		<div><input name="age" /></div>
		<div>请输入电话</div>
		<div><input name="phone" /></div>
		<div>请输入邮箱</div>
		<div><input name="email" /></div>
		<div><input type="submit" value="注册" /></div>
	</form>

**4. 处理注册**

由于注册信息将提交到`http://localhost:8080/PROJECT/user/handle_reg.do`,所以,应该在`UserController`中新添加某个方法,对该路径的请求进行处理:
 


	@RequestMapping("/handle_reg.do")
	public String handleReg() {
		return null;
	}

【方式1】 通过HttpServletRequest接收请求参数【不推荐】

在处理请求的方法中添加`HttpServletRequest`参数:


	@RequestMapping("/handle_reg.do")
	public String handleReg(HttpServletRequest request) {
		String username
			= request.getParameter("username");
		String password
			= request.getParameter("password");
		Integer age
			= Integer.valueOf(request.getParameter("age"));
		String phone
			= request.getParameter("phone");
		String email
			= request.getParameter("email");
		System.out.println("username=" + username);
		System.out.println("password=" + password);
		System.out.println("age=" + age);
		System.out.println("phone=" + phone);
		System.out.println("email=" + email);
		return null;
	}

通常并不推荐这种做法,主要原因有:[1] 需要调用方法才可以获取参数,比较麻烦;[2] `getParameter()`方法返回的是`String`类型,如果需要的数据类型是其它类型,则需要自行转换;[3] 不便于执行单元测试。

【方式2】 在处理请求的方法中直接添加所需的数据 【推荐】

@RequestMapping("/handle_reg.do")
	public String handleReg(
			String username, String password,
			Integer age, String phone, String email) {
		System.out.println("[2] username=" + username);
		System.out.println("[2] password=" + password);
		System.out.println("[2] age=" + (age+1));
		System.out.println("[2] phone=" + phone);
		System.out.println("[2] email=" + email);
		return null;
	}

使用时,需要注意的是:**参数的名称必须与请求参数的名称保持一致!**如果名称不一致,对应的参数将是null值。

这种做法的缺点是:**不太适用于请求参数过多的应用场合。**

【方式3】 将多个参数封装到某个类中,并使用这个类型作为处理请求的方法的参数 【推荐】

@RequestMapping("/handle_reg.do")
	public String handleReg(User user) {
		System.out.println(user);
		return null;
	}

使用时,需要注意的是:**自定义类中的属性的名称必须与请求参数的名称保持一致!**如果名称不一致,对应的属性将是null值。

【小结】 如何接收客户端提交的请求参数

如果客户端提交的请求参数较多,则优先使用第3种方式;

如果客户端提交的请求参数的数量可能发生变化(例如随着需求更新而改版),则优先使用第3种方式;

如果客户端提交的请求参数的数量较少,并且固定,则优先使用第2种方式。

注意:以上2种方式可以组合使用。

关于注册的模拟处理:

@RequestMapping("/handle_reg.do")
	public String handleReg(User user, ModelMap modelMap) {
		System.out.println(user);
		// 假设用户名root已经被占用,使用其它数据注册均可以成功
		if ("root".equals(user.getUsername())) {
			// 客户尝试注册已经被占用的用户名
			String message = "您尝试注册的用户名已经被占用!";
			modelMap.addAttribute("msg", message);
			return "error";
		} else {
			// 注册成功:重定向到登录页
			// 当前位置:/user/handle_reg.do
			// 目标位置:/user/login.do
			return "redirect:login.do";
		}
	}

【重定向】 当处理请求的方法的返回值以`redirect:`作为前缀时,表示**重定向**,在使用重定向时,冒号右侧的部分必须是相对路径或绝对路径,不可以是视图名!

**5. 显示登录页**

在`UserController`中添加处理请求的方法:

// /user/login.do
	@RequestMapping("/login.do")
	public String showLogin() {
		System.out.println("UserController.showLogin()");
		return "login";
	}

然后,将`register.jsp`复制粘贴为`login.jsp`,并调整为:

<h1>用户登录</h1>
	<!-- 当前位置:/user/login.do -->
	<!-- 目标位置:/user/handle_login.do -->
	<form method="post" action="handle_login.do">
		<div>请输入用户名</div>
		<div><input name="username" /></div>
		<div>请输入密码</div>
		<div><input name="password" /></div>
		<div><input type="submit" value="登录" /></div>
	</form>

**6. 处理登录**

在`UserController`中添加处理请求的方法:

@RequestMapping("/handle_login.do")
	public String handleLogin(
		String username, String password, ModelMap modelMap) {
		System.out.println("username=" + username);
		System.out.println("password=" + password);
		// 假设admin/123456是正确的用户名与密码,其它的均是错误的
		if ("admin".equals(username)) {
			// 用户名正确,则判断密码
			if ("123456".equals(password)) {
				// 密码正确,登录成功,跳转到主页:/main/index.do
				// 当前位置:/user/handle_login.do
				// 目标位置:/main/index.do
				return "redirect:../main/index.do";
			} else {
				// 密码错误 
				String message = "登录失败!密码错误!";
				modelMap.addAttribute("msg", message);
				return "error";
			}
		} else {
			// 用户名错误 
			String message = "登录失败!用户名不存在!";
			modelMap.addAttribute("msg", message);
			return "error";
		}
	}

#### 3. 关于@RequestMapping注解

`@RequestMapping`注解的主要作用是配置请求路径。该注解可以添加在类之前,也可以添加在某个处理请求的方法之前,当添加在类之前,用于配置路径中的层次(类似于描述本地路径中的文件夹)当添加在方法之前,用于配置类的注解的路径右侧的剩余部分,例如用于配置请求的资源(reg.do或login.do),如果请求的路径之前还有其它路径,也一并配置在这里,例如某请求是`http://localhost:8080/PROJECT/user/list/add.do`,如果在类之前配置的只有`/user`,则在方法之前配置为`/list/add.do`,总的原则就是类之前的注解配置拼接上方法之前的注解配置可以得到完整请求路径即可。

在配置时,最左侧的`/`是可以忽略的,例如某请求是`http://localhost:8080/PROJECT/user/reg.do`,在类和方法之前分别配置

    /user        /reg.do
    user        reg.do
    /user        reg.do
    user        /reg.do

以上4种配置都是正确的!

关于`@RequestMapping`注解,还可以用于**限制请求类型**,当限制了请求类型之后,如果客户端使用错误的方式来提交请求,则会出现**405**错误,提示内容例如:

    Request method 'GET' not supported

实现方式为:

    @RequestMapping(value="/handle_login.do", method=RequestMethod.POST)

关于`value`属性,取值是`String[]`,当只配置1个路径时,直接使用字符串值即可,如果有多个路径,则写成数组格式,例如:

    @RequestMapping({"/reg.do", "/register.do"}) 

另有`path`属性,与`value`是完全等效的,该属性是从4.2版本开始启用的。

关于`method`属性,用于设置请求方式,取值为`RequestMethod`枚举的数组,与`value`一样,如果只有1个值时可以直接编写,如果有多个值,必须写成数组格式。

与`@RequestMapping`注解相似的还有`@GetMapping`、`@PostMapping`……这些就是限制了请求类型的`@RequestMapping`,例如`@PostMapping("/handle_login.do")`等效于`@RequestMapping(value="/handle_login.do", method=RequestMethod.POST)`,这些注解是从**4.3**版本开始启用的。

#### 4. 关于@RequestParam注解

`@RequestParam`是添加在请求参数之前的注解。

通过`@RequestParam`注解**可以解决请求参数名与处理请求的方法的参数名不一致的问题**,例如:

@RequestParam("uname") String username

需要注意的是:**一旦使用以上注解,默认情况下,该参数是必须提交的!**如果客户端没有提交对应的参数,则会导致**400**错误:

    HTTP Status 400 - Required String parameter 'uname' is not present

所以,该注解还可以用于**强制要求客户端必须提交某些参数**。

该注解有属性`required`,是`boolean`类型的,表示**是否必须**的意思,默认值为`true`。

该注解还有属性`defaultValue`,是`String`类型的,表示**默认值**,即客户端的请求中并不包含该参数时,视为提交是某个值!**设置默认值时,需要将required显式的设置为false!**

### 1. 关于HttpSession

哪些数据需要保存到Session:

1. 当前登录的用户的唯一标识,例如用户的id等;

2. 使用频率极高的数据,例如用户的用户名,用户的头像等;

3. 不便于使用其它方式传递或保存的数据。

在SpringMVC的控制器中,处理请求时,如果需要使用Session(无论向Session中存入数据,还是从Session中读取数据),则在处理请求的方法中添加`HttpSession`作为参数,然后在方法体内部使用即可!

### 2. 拦截器:Interceptor

SpringMVC中的拦截器可以应用于许多请求,当用户尝试对指定的请求路径进行访问时,拦截器将被执行,且最终会拦截或放行。

例如创建**验证登录的拦截器**,首先,需要自定义类,并实现`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()");
		}
	
	}

接下来,需要在Spring的配置文件中对拦截器进行配置:

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

完成后,即可开始测试,拦截效果是**页面显示一片空白**,并且,在拦截器的3个方法中,只有`preHandle()`被执行。

在拦截器的`preHandle()`方法运行时,返回`false`表示**拦截**,返回`true`表示**放行**,并且,`postHandle()`和`afterCompletion()`方法都是在`preHandle()`之后的,且仅当**放行**时执行。

关于验证用户登录的拦截代码例如:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("LoginInterceptor.preHandle()");
		// 检查session中是否有username,有则放行,无则拦截并重定向
		HttpSession session = request.getSession();
		if (session.getAttribute("username") == null) {
			response.sendRedirect("../user/login.do");
			return false;
		}
		return true;
	}

关于拦截器的配置,可以配置多项拦截路径,也可以添加“例外”:


	<!-- 拦截器链 -->
	<mvc:interceptors>
		<!-- 第1个拦截器 -->
		<mvc:interceptor>
			<!-- 1. 拦截路径(黑名单) -->
			<mvc:mapping path="/main/*"/>
			<mvc:mapping path="/news/*"/>
			<mvc:mapping path="/user/*"/>
			<!-- 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_login.do"/>
			<!-- 3. 拦截器类 -->
			<bean class="cn.tedu.spring.LoginInterceptor" />
		</mvc:interceptor>
	</mvc:interceptors>

凡是不在黑名单中的访问路径,或在白名单的访问路径,都不会触发拦截器的执行。

在配置拦截器的拦截路径或例外路径时,可以使用通配符:`*`表示某层路径或某个资源,例如`/user/*`可以通配`/user/reg.do`及`/user/login.do`等,`?`可以通配1个字符,`**`可以通配多层级内容,例如`/user/**`可以通配`/user/1/edit`及`/user/1/delete`等。

### 【附】 拦截器(Interceptor)与过滤器(Filter)的区别

拦截器与过滤器有高度相似之处,例如都可以应用于许多请求,并且都可以起到“拦截”的效果,也都可以存在多个组件形成“链”。

关于这2者的区别:

1. 归属不同,过滤器是Java EE中的组件,而拦截器是SpringMVC中的组件,所以,只要开发Java WEB项目,都可以使用过滤器,但是,仅当开发Java WEB项目且使用了SpringMVC框架才可以使用拦截器,并且,由于拦截器是SpringMVC框架下的,只有被DispatcherServlet处理的请求才有可能被拦截器处理;

2. 配置不同,过滤器的配置比较单一,拦截器的配置非常灵活;

3. 执行时间节点不同,过滤器是执行在所有Servlet之前的,而拦截器的第1次执行是执行在DispatcherServlet之后且在Controller之前的。

### 【附】 抽象类与接口的区别

1. 语法区别

2. 类表示的是“类别”,接口表示的是“规范、标准、行为模式”

3. 类与抽象类之间是“类 is a 抽象类”的关系,类与接口之间的关系是“类 has a 接口”的关系

<filter>
		<filter-name></filter-name>
		<filter-class></filter-classme>
	</filter>

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

 

package com.kucun; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; @SpringBootApplication( ) public class DemoApplication extends SpringBootServletInitializer { public static void main(String[] args) { // // ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args); // Arrays.stream(ctx.getBeanNamesForType(SecurityFilterChain.class)) // .forEach(System.out::println); // // // // 测试密码加密示例 // BCryptPasswordEncoder encoder = new BCrypt(); // String rawPassword = "987987"; // String encodedPassword = encoder.encode(rawPassword); // System.out.println("加密后的密码:" + encodedPassword); SpringApplication.run(DemoApplication.class, args); } }<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>KuCun2</groupId> <artifactId>KuCun2</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>KuCun2</name> <description/> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <!-- 请根据需要选择版本 --> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <webVersion>4.0</webVersion> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <protobuf.version>3.21.12</protobuf.version> </properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>8.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>javax.servlet.jsp.jstl</artifactId> <version>1.2.4</version> </dependency> <!-- Spring Boot Starter Data JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- MySQL Connector --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <exclusions> <exclusion> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> </exclusion> </exclusions> </dependency> <!-- Optional: Lombok for reducing boilerplate code --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency> <!-- Jackson Databind --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <!-- Jackson Core --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </dependency> <!-- Jackson Annotations --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> </dependency> <dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>0.4</version> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>${protobuf.version}</version> </dependency> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.30</version> <!-- 统一版本号 --> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project> package com.kucun.Config.user; import java.util.Collection; import com.kucun.data.entity.User; public class CustomUserDetails /*implements UserDetails*/ { // /** // * // */ // private static final long serialVersionUID = 1L; // private final String andy; // 对应andy字段 // private final String name; // private final int role; // private final String password; // private final User users; // private final Collection<? extends GrantedAuthority> authorities; // // public CustomUserDetails(User user, Collection<? extends GrantedAuthority> authorities) { // this.andy = user.getAndy(); // this.name = user.getName(); // this.role = user.getRole(); // this.password = user.getPass(); // user.setPass(null); // this.users=user; // this.authorities = authorities; // } // // // 实现UserDetails接口方法 // @Override public String getUsername() { return andy; } // @Override public String getPassword() { return password; } // @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } // // // 自定义字段访问方法 // public String getName() { return name; } // public User getUser() { return users; } // public int getRole() { return role; } // // // 其他必要方法 // @Override public boolean isAccountNonExpired() { return true; } // @Override public boolean isAccountNonLocked() { return true; } // @Override public boolean isCredentialsNonExpired() { return true; } // @Override public boolean isEnabled() { return true; } }package com.kucun.Config.Role; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.json.Json; import org.springframework.stereotype.Component; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; /** * 权限转化 * @author Administrator * */ @Component public class RoleConverter { // private static final Map<Integer, String> ROLE_MAP = new HashMap<>(); // // @PostConstruct // public void init() { // ROLE_MAP.put(0, "ROLE_ADMIN"); // ROLE_MAP.put(1, "ROLE_USER"); // ROLE_MAP.put(2, "ROLE_MANAGER"); // ROLE_MAP.put(3, "ROLE_AUDITOR"); // } // // public List<GrantedAuthority> convert(int roleCode) { // ObjectMapper mapper = new ObjectMapper(); // try { // System.out.println(mapper.writeValueAsString(Collections.singletonList( // new SimpleGrantedAuthority(ROLE_MAP.getOrDefault(roleCode, "ROLE_GUEST")))).toString());//输出[{"authority":"ROLE_ADMIN"}] // } catch (JsonProcessingException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // return Collections.singletonList( // new SimpleGrantedAuthority(ROLE_MAP.getOrDefault(roleCode, "ROLE_GUEST")) // ); // } // }package com.kucun.Config; // 2. 基础安全配置 //@Configuration //@EnableWebSecurity // 启用Web安全功能 public class SecurityConfig //extends WebSecurityConfigurerAdapter { // @Override // public void configure(WebSecurity web) { // web.ignoring().antMatchers("/check-session"); // } // // 添加自定义Controller // @RestController // public static class SessionCheckController { // @GetMapping("/check-session") // public ResponseEntity<?> checkSession(HttpServletRequest request) { // return request.getSession(false) != null ? // ResponseEntity.ok().build() : // ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); // } // } // /** // * 核心安全过滤器链配置 // * @param http HTTP安全构建器 // * @return 安全过滤器链 // * @throws Exception 配置异常 // * // * █ 配置逻辑说明: // * 1. authorizeHttpRequests: 定义访问控制规则 // * 2. formLogin: 配置表单登录 // * 3. logout: 配置注销行为 // * 4. exceptionHandling: 处理权限异常[^3] // */ // // // 修正后的配置方法 // @Override // protected void configure(HttpSecurity http) throws Exception { // // // // http // .csrf().disable() // .sessionManagement() // .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) // .invalidSessionUrl("/login.html?session=invalid") // .maximumSessions(1) // .maxSessionsPreventsLogin(false) // .and() // .and() // .addFilterBefore(jsonAuthFilter(), UsernamePasswordAuthenticationFilter.class) // 关键配置 // .authorizeRequests() // .antMatchers("/login.html", "/users/login").permitAll() // .antMatchers("/js/**", "/css/**", "/fonts/**", "/images/**","/check-session","/main/bootstrap-3.3.7-dist/**").permitAll() // .antMatchers("/users/guanli/**").hasAuthority("ROLE_ADMIN") // .anyRequest().authenticated() // .and() // .formLogin().disable() //// .loginPage("/login.html") //// .loginProcessingUrl("/users/login") //// //// .successHandler(ajaxAuthenticationSuccessHandler()) // 自定义成功处理器 //// .failureHandler(ajaxAuthenticationFailureHandler()) // 自定义失败处理器 //// .defaultSuccessUrl("/index.html") //// .failureUrl("/login.html?error=true") //// .usernameParameter("andy") // 修改用户名参数名 //// .passwordParameter("pass") // 修改密码参数名 //// .and() // // .logout() // .logoutUrl("/logout") // .logoutSuccessUrl("/login.html") // .and() // .csrf() // .ignoringAntMatchers("/users/login") // .and() // .headers() // .frameOptions().sameOrigin() // .and() // .exceptionHandling() // .accessDeniedHandler(accessDeniedHandler()); // 统一使用Handler // } // // // // // // // 返回JSON格式的成功响应 // @Bean // public AuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() { // return (request, response, authentication) -> { // // // // // 强制创建服务端会话 // request.getSession(true); // // // // // String contextPath = request.getContextPath(); // HttpSession session = request.getSession(true); // Cookie cookie = new Cookie("JSESSIONID", session.getId()); // cookie.setPath(contextPath.isEmpty() ? "/" : contextPath + "/"); // cookie.setMaxAge(1800); // 30分钟 // response.addCookie(cookie); // // // //构建安全响应数据 // Map<String, Object> responseData = new HashMap<>(); // responseData.put("sessionId", request.getSession().getId()); // responseData.put("userInfo",Collections.unmodifiableMap(new HashMap<String, Object>() {/** // * // */ // private static final long serialVersionUID = 1L; // // { // put("Name", ((CustomUserDetails)authentication.getPrincipal()).getName()); // put("role", ((CustomUserDetails)authentication.getPrincipal()).getRole()); // }})); // // // // // 统一返回JSON格式 // response.setContentType(MediaType.APPLICATION_JSON_VALUE); // // new ObjectMapper().writeValue(response.getWriter(), responseData); // // // // // // // // // // response.setContentType(MediaType.APPLICATION_JSON_VALUE); // CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); // // response.setStatus(HttpStatus.OK.value()); // System.out.println(authentication.getPrincipal()+""+authentication.getName()); // // if (request.getHeader("X-Requested-With") == null) { // 非AJAX请求 // response.sendRedirect("/index.html"); // } else { // // //String re=userDetails.getUser().toString() // new ObjectMapper().writeValue(response.getWriter(), userDetails.getUser() // ); // // } // // // // // // // }; // } // // // 返回401状态码和错误信息 // @Bean // public AuthenticationFailureHandler ajaxAuthenticationFailureHandler() { // return (request, response, exception) -> { // if (request.getHeader("X-Requested-With") == null) { // response.sendRedirect("/login.html?error=true"); // } else { // response.setStatus(HttpStatus.UNAUTHORIZED.value()); // response.getWriter().write("{\"error\":\"Authentication failed\"}"); // } // }; // } // // // 处理未认证请求 // @Bean // public AuthenticationEntryPoint ajaxAuthenticationEntryPoint() { // return (request, response, exception) -> { // if (request.getHeader("X-Requested-With") == null) { // response.sendRedirect("/login.html?error=true"); // } else { // response.setStatus(HttpStatus.UNAUTHORIZED.value()); // response.getWriter().write("{\"error\":\"Authentication failed\"}"); // } // }; // } // // // // // // @Bean // public JsonUsernamePasswordAuthenticationFilter jsonAuthFilter() throws Exception { // // JsonUsernamePasswordAuthenticationFilter filter = // new JsonUsernamePasswordAuthenticationFilter(); // filter.setAuthenticationManager(authenticationManagerBean()); // filter.setUsernameParameter("andy"); // 设置自定义参数名 // filter.setPasswordParameter("pass"); // filter.setFilterProcessesUrl("/users/login"); // filter.setAuthenticationSuccessHandler(ajaxAuthenticationSuccessHandler()); // filter.setAuthenticationFailureHandler(ajaxAuthenticationFailureHandler()); // // return filter; // } // // // /** // * 密码编码器(必须配置) // * 使用BCrypt强哈希算法加密 // */ // @Bean // public PasswordEncoder passwordEncoder() { // return new BCryptPasswordEncoder(); // } // // // @Bean // public AccessDeniedHandler accessDeniedHandler() { // System.out.println("0000"); // return (request, response, ex) -> { // if (!response.isCommitted()) { // response.sendRedirect("/error/403"); // } // }; // } // // //} // // // // // // //class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { // private final ObjectMapper objectMapper = new ObjectMapper(); // // @Override // public Authentication attemptAuthentication(HttpServletRequest request, // HttpServletResponse response) // throws AuthenticationException { // System.out.println("收到认证请求,路径:" + request.getRequestURI()); // System.out.println("请求方法:" + request.getMethod()); // System.out.println("Content-Type:" + request.getContentType()); // if (request.getContentType() != null && // request.getContentType().startsWith(MediaType.APPLICATION_JSON_VALUE)) { // // try (InputStream is = request.getInputStream()) { // Map<String, String> authMap = objectMapper.readValue(is, Map.class); // // String username = authMap.getOrDefault(getUsernameParameter(), ""); // String password = authMap.getOrDefault(getPasswordParameter(), ""); // // // 调试日志 // System.out.println("Authentication attempt with: " + username+'_'+ password); // // UsernamePasswordAuthenticationToken authRequest = // new UsernamePasswordAuthenticationToken(username, password); // // setDetails(request, authRequest); // return this.getAuthenticationManager().authenticate(authRequest); // } catch (IOException e) { // throw new AuthenticationServiceException("认证请求解析失败", e); // } // } // Authentication aut= super.attemptAuthentication(request, response); // System.out.println("结果:"+aut.isAuthenticated()); // // return aut; // } } package com.kucun.Config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.kucun.Config.Role.RoleConverter; import com.kucun.Config.user.CustomUserDetails; import com.kucun.data.entity.User; import com.kucun.dataDo.UserRepository; /** * 获取数据 * @author Administrator * */ @Service public class CustomUserDetailsService/* implements UserDetailsService */{ // // @Autowired // private UserRepository userRepository; // // @Autowired // private RoleConverter roleConverter; // // public CustomUserDetailsService() { // // super(); // System.out.println("11111"); // } ///** // * 获取数据库中用户信息 // * @param andy 账号 // * // * @return // * // */ // @Override // public UserDetails loadUserByUsername(String andy) { // System.out.println(andy); // User user = userRepository.findByAndy(andy); // System.out.println(user); // return new CustomUserDetails(user, // roleConverter.convert(user.getRole()) // 关键转换点[^1] // ); // } } 不加安全类的情况下解决重定向问题 }
最新发布
06-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值