0%

SpringMVC如何处理请求

通过servletApi

但是使用占位符不能使用此方法进行获取

1
2
String username = request.getParameter("username");
String password = request.getParameter("password");

通过控制器方法的形参获得参数

1
2
3
4
5
6
@RequestMapping("/testParam")
//在这里将我们的发送的请求名与方法的名字相同,即可获得响应的参数
public String testParam(String username, String password){
System.out.println("username:"+username+",password:"+password);
return "success";
}

获取多个参数

1
2
3
4
5
6
<form th:action="@{/target}" method="get">
<input type="checkbox" name="hobbies" value="a">a
<input type="checkbox" name="hobbies" value="b">b
<input type="checkbox" name="hobbies" value="c">c
<input type="submit" value="以post方式提交表单">
</form>
1
2
3
4
5
6
@RequestMapping(value = "/target")
public String toTarget(String []hobbies){
System.out.println(Arrays.toString(hobbies));
System.out.println(hobbies[0]);
return "target";
}

@RequestParam

在上面的使用方法中我们前端的name与controller中的参数名称必须相同,但是如果不相同的话,参数会返回空值,获取不到。而在这里我们可以使用@RequestParam的方法进行表示

1
2
3
4
5
6
7
8
@RequestMapping(value = "/target")
public String toTarget(
@RequestParam("hobbies_1") String []hobbies
){
System.out.println(Arrays.toString(hobbies));
System.out.println(hobbies[0]);
return "target";
}
1
2
3
4
5
6
<form th:action="@{/target}" method="get">
<input type="checkbox" name="hobbies_1" value="a">a
<input type="checkbox" name="hobbies_1" value="b">b
<input type="checkbox" name="hobbies_1" value="c">c
<input type="submit" value="以post方式提交表单">
</form>
1
2
3
4
5
6
7
8
9
10
11
public @interface RequestParam {
@AliasFor("name")
String value() default "";

@AliasFor("value")
String name() default "";
//在这里将spring进行装配,而且为true,如果装配不成功,则会进行报错,而不是返回一个默认值,也就是说我们前端必须传输一个hobbies_1的参数,当然我们也可以将其更改为false@RequestParam(value = "hobbies_1",required = false)
boolean required() default true;
//我们也可以设置默认值,在使用的时候,如果前端传输空参数,我们可以使用这个方法不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值为""时,则使用默认值为形参赋值
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

@RequestHeader

1
2
3
4
5
6
7
8
9
public String toTarget(
@RequestParam(value = "hobbies",required = false) String []hobbies,
@RequestHeader(value="Host")String host
){
System.out.println(Arrays.toString(hobbies));
System.out.println(hobbies[0]);
System.out.println(host);
return "target";
}

@CookieValue

cookie时浏览器端的服务器会话技术,而session是服务器端的会话技术,session依赖于cookie

@CookieValue是将cookie数据和控制器方法的形参创建映射关系

当我们第一次访问浏览器的时候,我们会在请求报文中查找是否有cookie记录,如果没有则交给浏览器创建一个cookie,响应给服务器,服务器进行保存,下一次进行访问的时候会将session携带过去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//我第一次打开网页请求获得session,浏览器分配一个session
@RequestMapping( value = {"/","/hi"})
public String index(HttpServletRequest request){
HttpSession session= request.getSession();
return "index";
}
//在次页面
@RequestMapping(value = "/target")
public String toTarget(
@RequestParam(value = "hobbies",required = false) String []hobbies,
@RequestHeader(value="Host")String host,
@CookieValue("JSESSIONID:")String JSESSIONID
){
System.out.println(Arrays.toString(hobbies));
System.out.println(hobbies[0]);
System.out.println(host);
System.out.println(JSESSIONID);
return "target";
}

image-20220424145025027

image-20220424145049596

通过POJO获取参数

注意在类中一定要定义set方法才可以赋值,至于为什么,现在不清楚,插眼!!!!后面看到源码解决

1
2
3
4
5
6
7
8
<form th:action="@{/target}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
性别:<input type="radio" name="sex" value="男"><input type="radio" name="sex" value="女"><br>
年龄:<input type="text" name="age"><br>
邮箱:<input type="text" name="email"><br>
<input type="submit">
</form>
1
2
3
4
5
@RequestMapping(value = "/target")
public String toTarget(User user){
System.out.println(user.toString());
return "target";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.zss.mvc.pojo;

public class User {
Integer id;
String username;
String password;
String sex;
Integer age;
String email;


public void setId(Integer id) {
this.id = id;
}

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

public void setSex(String sex) {
this.sex = sex;
}

public void setAge(Integer age) {
this.age = age;
}

public void setEmail(String email) {
this.email = email;
}

public User(){

}
public User(Integer id,String username, String password, String sex, Integer age, String email) {
this.id=id;
this.username = username;
this.password = password;
this.sex = sex;
this.age = age;
this.email = email;
}

@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
", email='" + email + '\'' +
'}';
}
}

解决乱码

注意我们改变请求的参数的乱码设置一定要在获得请求之前,如果在后面则不会发生作用

这里注意我们上面使用的是post方法,但是如果我们改到get方法,在其中配置相关编码,将get方法发送的url转换成utf-8编码,但是post方法还是存在乱码问题

image-20220424154256157

所以我们应该在dispatcherservlet之前进行设置,所以我们在过滤器中进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--设置过滤器,设置编码-->
<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>
//设置响应的编码是否也启用encoding
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
//对所有的页面进行响应
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

mvc常用组件

  • DispatcherServlet:前端控制器,不需要工程师开发,由框架提供

作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求

  • HandlerMapping:处理器映射器,不需要工程师开发,由框架提供

作用:根据请求的url、method等信息查找Handler,即控制器方法

  • Handler:处理器,需要工程师开发

作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理

  • HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供

作用:通过HandlerAdapter对处理器(控制器方法)进行执行

  • ViewResolver:视图解析器,不需要工程师开发,由框架提供

作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView

  • View:视图

作用:将模型数据通过页面展示给用户

DispatcherServlet初始化过程

插眼,没有听懂

域对象共享数据

session和model的区别_qq_42331499的博客-CSDN博客_model和session有什么不同

session的创建于销毁

session 创建和销毁时机_hotwater1015的博客-CSDN博客

当我们关闭浏览器,再打开它,连接服务器时,服务器端会分配一个新的session,也就是说会启动一个新的会话。那么原来的session是不是被销毁了呢?
通过实现一个SessionListener可以发现,当浏览器关闭时,原session并没有被销毁(destory方法没有执行),而是等到timeout到期,才销毁这个session。关闭浏览器只是在客户端的内存中清除了与原会话相关的cookie,再次打开浏览器进行连接时,浏览器无法发送cookie信息,所以服务器会认为是一个新的会话。因此,如果有某些与session关联的资源想在关闭浏览器时就进行清理(如临时文件等),那么应该发送特定的请求到服务器端,而不是等到session的自动清理。

session的作用域只与浏览器是否关闭有问题,当tomcat服务器关闭时,此时session会保存到磁盘中,当服务器再次开启的时候,session会活化到浏览器中,但是只要不关闭浏览器,session就不会改变。但是注意如果cookie已经销毁了,就找不到对应的session了,哪怕时间并没有超出。

使用ModelAndView向request域对象共享数据

ModelAndView拥有两个功能,一个是共享数据,另外一个设置视图名称

1
2
3
<body>
<a th:href="@{/target}">跳转</a>
</body>
1
2
3
4
5
6
7
8
9
@RequestMapping(value = "/target")
public ModelAndView toTarget(){
ModelAndView mav=new ModelAndView();
//存储数据
mav.addObject("test","test ModelAndView");
//设置视图
mav.setViewName("target");
return mav;
}
1
2
3
4
<table>
<tr><td th:text="${test}">
</td></tr>
</table>

使用Model传递域对象

1
2
3
4
5
@RequestMapping("/target")
public String target(Model model){
model.addAttribute("model","model");
return "target";
}

使用map共享域对象

1
2
3
4
5
@RequestMapping("/target")
public String target(Map<String ,Object> map){
map.put("map","hello map");
return "target";
}

使用ModelMap

1
2
3
4
5
@RequestMapping("/target")
public String target(ModelMap modelMap){
modelMap.addAttribute("modelmap","hello modelmap");
return "target";
}

总结

上述四种获取域对象的方法中,除了ModelAndView之外,其实其他的三个方法都是被同一个对象进行了实例化

1
2
3
4
5
6
@RequestMapping("/target")
public String target(ModelMap modelMap){
modelMap.addAttribute("modelmap","hello modelmap");
System.out.println(modelMap.getClass().getName());
return "target";
}

image-20220425102258113

image-20220425102421872

而modelMap继承了 LinkedHashMap,LinkedHashMap实现了接口map

image-20220425102605199

1
2
3
4
public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}

源码

我们在SpringMvc中启动debug模式,并在dispathcher中打断点,其实不管我们使用什么方法进行域对象的保存或者打开什么网页,我们都会经过ModelANDViewimage-20220425105123718

Session共享数据

使用原生的api,HttpSession session

application共享数据

对应整个工程,在服务器启动时就创建了,使用方法域Session相同

SpringMVC视图

服务器内部转发与客户端重定向 | 偷掉月亮 (moonshuo.cn)转发是服务器的一次请求,而重定向则是两次请求,同时重定向可以重定向到任何页面,但是转发只能转发给系统的组件进行处理,而WEB-INF的文件资源是不能被直接访问的,也就是不能通过重定向进行访问,但是注意虽然转发与重定向的看起来效果相同,但是地址栏的信息不会一样

转发视图

SpringMVC中默认的转发视图是InternalResourceView,转发是浏览器的一次请求,最终的地址为最终要展现的地址

1
2
3
4
5
6
7
8
9
10
//这个的逻辑处理满足不了条件,需要交给另外 一个子组件进行处理,使用forward:/请求的功能对应的操作,此过程会产生两次视图请求,但是浏览器仅仅响应了一次
@RequestMapping("/InternalResource")
public String InternalResource(){
return "forward:/testHello";
}

@RequestMapping("/testHello")
public String testHello(){
return "转发目标页面";
}
1
2
3
4
5
<body>
<a th:href="@{/InternalResource}">转发</a>
<a th:href="@{/Redirect}">重定向</a>

</body>

image-20220425122155614

重定向视图

SpringMVC中默认的重定向视图是RedirectView,重定向是两次请求,发送之后,最终的地址为???//testHello,在这个过程中浏览器发送了两次请求,???/Redirect地址进行了重定向,告诉浏览器去访问???/testHello

1
2
3
4
5
6
7
8
9
@RequestMapping("/Redirect")
public String Redirect(){
return "redirect:/testHello";
}

@RequestMapping("/testHello")
public String testHello(){
return "重定向的目标页面";
}

image-20220425122352589

视图控制器

当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示,即下面的两个程序效果相同

1
2
3
4
@RequestMapping( value = {"/hi"})
public String index(){
return "index";
}
1
2
3
4
5
<mvc:view-controller path="/hi" view-name="index"></mvc:view-controller>
<!--如果浏览器发送的信息,dispatcherservlet处理不了,则交给defaultservlet处理,开启对静态资源的访问和默认驱动-->
<mvc:default-servlet-handler/>
<!--开启注解驱动,要这两个标签同时使用-->
<mvc:annotation-driven/>

基于配置的异常处理器

HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver(默认异常处理)和SimpleMappingExceptionResolver(自定义异常处理)

在xml文件进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--配置异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
properties的键表示处理器方法执行过程中出现的异常
properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--
exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享
-->
<property name="exceptionAttribute" value="ex"/>
</bean>

controller进行配置

1
2
3
4
5
6
@RequestMapping("/test")
public String test(){
//故意出现错误
System.out.println(1/0);
return "target";
}

image-20220501100452968

基于注解的异常处理器

//@ControllerAdvice将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController {

1
2
3
4
5
6
7
//@ExceptionHandler用于设置所标识方法处理的异常
@ExceptionHandler(ArithmeticException.class)
//ex表示当前请求处理中出现的异常对象
public String handleArithmeticException(Exception ex, Model model){
model.addAttribute("ex", ex);
return "error";
}

拦截器配置

过滤器 和 拦截器的 6个区别,别再傻傻分不清了_程序员小富的博客-CSDN博客_过滤器和拦截器

Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。

SpringMVC中的拦截器用于拦截控制器方法的执行

SpringMVC中的拦截器需要实现HandlerInterceptor

SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//这是 在modelandview之前进行的
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.zss.mvc.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

public class FirstInterceptor implements HandlerInterceptor {
//crtl+o执行方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
//返回true进行拦截
return true;
}

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

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

xml文件配置

全部拦截1

1
2
3
4
<!--配置拦截器-->
<mvc:interceptors>
<bean class="com.zss.mvc.interceptor.FirstInterceptor"/>
</mvc:interceptors>

全部拦截2

在拦截器进行标记为普通组件

1
2
3
//标记为普通组件
@Component
public class FirstInterceptor implements HandlerInterceptor {

同时也需要xml文件进行扫描

1
2
<!--扫描组件,扫描整个大包-->
<context:component-scan base-package="com.zss.mvc"/>
1
2
3
<mvc:interceptors>
<ref bean="firstInterceptor"/>
</mvc:interceptors>

精准拦截

1
2
3
4
5
6
7
8
9
10
11
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--拦截所有-->
<mvc:mapping path="/*"/>
<!--排除-->
<mvc:exclude-mapping path="/target"/>
<ref bean="firstInterceptor"/>
</mvc:interceptor>

</mvc:interceptors>

当然也可以在路径中进行精确定位进行拦截

三个抽象方法

SpringMVC中的拦截器有三个抽象方法:

preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法

postHandle:控制器方法执行之后执行postHandle()

afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()

多个拦截器的顺序

1
2
3
4
5
<!--配置拦截器-->
<mvc:interceptors>
<ref bean="firstInterceptor"/>
<ref bean="secondInterceptor"/>
</mvc:interceptors>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class FirstInterceptor implements HandlerInterceptor {
//crtl+o执行方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("1--preHandle");
//返回true进行拦截
return true;
}

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

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

image-20220428160643972

首先和我们的配置的顺序相关,其次会,是一个逐层紧密闭合的过程

pre

1
2
3
4
5
6
7
8
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}

post

1
2
3
4
for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}

return false

如果我将第二个拦截器返回false,那么

image-20220428162725939

其实在第二个返回false的时候,第二个的post与after都不会执行了,

创建初始化类,代替web.xml

public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 指定spring的配置类
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}

/**
* 指定SpringMVC的配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}

/**
* 指定DispatcherServlet的映射规则,即url-pattern
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

/**
* 添加过滤器
* @return
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceRequestEncoding(true);
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{encodingFilter, hiddenHttpMethodFilter};
}

配置代替springmvc的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//标记为配置文件
@Configuration
//扫描组件
@ComponentScan("com.atguigu.mvc.controller")
//开启MVC注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

//使用默认的servlet处理静态资源
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
//使默认的处理可用
configurer.enable();
}

//配置文件上传解析器
@Bean
public CommonsMultipartResolver multipartResolver(){
return new CommonsMultipartResolver();
}

//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//创建拦截器规则
FirstInterceptor firstInterceptor = new FirstInterceptor();
//添加拦截路径,拦截所有,与/*不同
registry.addInterceptor(firstInterceptor).addPathPatterns("/**");
}

//配置视图控制

@Override
public void addViewControllers(ViewControllerRegistry registry) {
//找到/时,对应index羊肉面
registry.addViewController("/").setViewName("index");
}

//配置异常映射
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
//设置异常对应的对象,以及将要对应的页面
prop.setProperty("java.lang.ArithmeticException", "error");
//设置异常映射
exceptionResolver.setExceptionMappings(prop);
//设置共享异常信息的键
exceptionResolver.setExceptionAttribute("ex");
resolvers.add(exceptionResolver);
}

//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}

//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}

//生成视图解析器并未解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}


}

1
2
3
4
5
6
<!--web依赖:tomcat,dispatcherServlet,xml等,使用springmvc构建web,tomcat作为默认容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

自动装配原理

依赖

pom.xml的核心依赖在parent中,其中我们的许多的核心依赖都在其中

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

我们在引入的springboot依赖的适合,不需要写入版本

而在springboot中我们需要使用哪一种功能只需要引入哪一种start即可

主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.zss.springbootdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author zss
*/
//标注这是一个springboot的启动应用
@SpringBootApplication
public class SpringBootdemoApplication {

public static void main(String[] args) {
//将springboot启动
SpringApplication.run(SpringBootdemoApplication.class, args);
}

}

我们进入到其中,查看他的注解形式

image-20220517161608830

1
@SpringBootConfiguration

当我们进入到@SpringBootConfiguration中时候可以发现其中的一个配置类@Configuration,这是表明我们spring的一个配置类

image-20220517161822495

1
@EnableAutoConfiguration

而对于@EnableAutoConfiguration,是自动导入配置的,当我们呢进入到其中,@AutoConfigurationPackage自动配置包的注解,而当我们再一次深入探究,@Import({AutoConfigurationImportSelector.class})导入选择器,

image-20220517162000179

image-20220517170838853

所以所springboot的所有自动配置都是在启动的时候进行了配置和加载,但是不一定生效,而是判断是否导入了相关的启动器。

SpringBoot配置文件

springboot的全局配置文件,配置名称是固定的,

application.properties–语法:key=value

application.yml–语法: key value

注意此时yml文件可以存储对象,虽然properties也可以存储对象,当时方法比较麻烦,当然也可以存储数组,对空格的要求非常高

image-20220518134637133

image-20220518134344668

对实体类进行赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Component
@ConfigurationProperties(prefix = "student")
public class Student {
private String name;
public Integer age;

public Student() {
}

public Student(String name, Integer age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1
2
3
student:
name: zss
age: 20

image-20220518140058590

使用properties进行赋值

1
2
3
4
5
6
@Component
//在此处指定他的位置即可
@PropertySource(value = "classpath:zss.properties")
public class Student {
private String name;
public Integer age;

进行数据校验

我们在这里进行数据校验的时候,可以直接在后台判断结果是否合法

导入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

选择进行校验的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.zss.springbootdemo.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Email;


/**
* @author zss
*/
@Component
@ConfigurationProperties(prefix = "student")
@Validated
public class Student {
@Email(message = "邮箱格式错误")
private String name;
public Integer age;

public Student() {
}

public Student(String name, Integer age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

image-20220518142147235

配置的顺序

SpringBoot配置文件——加载顺序 - CyberPelican - 博客园 (cnblogs.com)

img

如果在不同的目录中存在多个配置文件,它的读取顺序是:
1、config/application.properties(项目根目录中config目录下)
2、config/application.yml
3、application.properties(项目根目录下)
4、application.yml
5、resources/config/application.properties(项目resources目录中config目录下)
6、resources/config/application.yml
7、resources/application.properties(项目的resources目录下)
8、resources/application.yml

在实际的开发应用中,我们拥有许多的环节,比如测试环境,生产环境等,我们可以在这里进行指定

springboot的多环境配置

1
2
3
4
#表示激活此环境
spring:
profiles:
active: test

配置数据库连接池

1
2
3
4
5
6
7
#驱动包+url+账号+密码
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/customer
username: root
password: 200101

导入驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--阿里巴巴数据库连接池依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<!--mysql驱动依赖-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>

输出数据源查看,可以看到默认的数据源

1
2
3
4
5
6
7
8
9
10
@SpringBootTest
class RbacProjectApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() {
System.out.println(dataSource);
}

}

image-20220523095630811

接下来需要我们指定数据源

1
type: com.alibaba.druid.pool.DruidDataSource

image-20220523101358364

当然此时我们也可以使用druid提供的配置信息,我们重新写一个配置类,进行后台监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.zss.rbacproject.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;

/**
* @author zss
*/
@Configuration
public class DruidConfig {

@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
//servlet后台监控
@Bean
public ServletRegistrationBean servletRegistrationBean(){
ServletRegistrationBean<StatViewServlet> bean=new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*");
//后台需要有人登录,账号密码设置
HashMap<String ,String> initParameters=new HashMap<>();
initParameters.put("loginUsername","zss");
initParameters.put("loginUsername","200101");

//允许谁可以访问
initParameters.put("allow","");
bean.setInitParameters(initParameters);
return bean;

}
}

登录账号密码,我们可以查看许多的运行的语句,在这里我们可以监控我们多条语句的执行

image-20220523110002381

当然我们也可以在这里设置过滤器

1
2
3
4
5
6
7
8
9
10
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean=new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());

//可以过滤拿一些请求
Map<String,String > stringStringMap=new HashMap<>();
stringStringMap.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(stringStringMap);
return bean;
}

导入整合包

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>

建立类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package com.zss.rbacproject.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Set;

/**
* @author zss
*/
@Component
public class Employee {
@TableId(value = "id",type= IdType.AUTO)
private Long id;
private String name;
private String password;
private String email;
private Integer age;
private boolean admin=false;
private Long deptId;
@TableField(exist = false)
private String deptName;
@TableField(exist = false)
private List<Role> roles;
@TableField(exist = false)
Set<String> expressions;

public Set<String> getExpressions() {
return expressions;
}

public void setExpressions(Set<String> expressions) {
this.expressions = expressions;
}

public List<Role> getRoles() {
return roles;
}

public void setRoles(List<Role> roles) {
this.roles = roles;
}

public String getDeptName() {
return deptName;
}

public void setDeptName(String deptName) {
this.deptName = deptName;
}

public Employee() {
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public boolean isAdmin() {
return admin;
}

public void setAdmin(boolean admin) {
this.admin = admin;
}

public Long getDeptId() {
return deptId;
}

public void setDeptId(Long deptId) {
this.deptId = deptId;
}
}

建立接口类

1
2
3
4
5
6
7
8
9
10
11
@Repository
public interface EmployeeMapper extends BaseMapper<Employee> {
IPage<Employee> selectPage(EmployeeQueryObject qo);

int insertBatchRelation(@Param("id") Long id, List<Long> roleIds);

void deleteRoleRelation(Long id);

Set<String> selectExpressions(Long id);
}

建立对应的映射文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zss.rbacproject.mapper.EmployeeMapper">

<insert id="insertBatchRelation">
insert into employee_role(employee_id, role_id) VALUES
<foreach collection="roleIds" item="role_id" separator=",">
(#{id},#{role_id})
</foreach>
</insert>
<delete id="deleteRoleRelation">
delete from employee_role where employee_id=#{id}
</delete>


<select id="selectPage" resultType="com.zss.rbacproject.pojo.Employee">
select employee.id, employee.name, password, email, age, admin, dept_id, d.name as deptName, sn from employee join department d on employee.dept_id = d.id
<where>
<if test="keyword!= null and keyword != '' ">
and (employee.name like CONCAT('%',#{keyword},'%') or email like CONCAT('%',#{keyword},'%'))
</if>
<if test="deptId != -1" >
and dept_id=#{deptId}
</if>
</where>
</select>
<select id="selectExpressions" resultType="java.lang.String">
select pr.expression from permission as pr join role_permission rp on pr.id = rp.permission_id join employee_role er on rp.role_id = er.role_id where employee_id=#{id}
</select>

</mapper>

告知springbootmapper文件的位置

1
2
3
4
mybatis-plus:
#这里是配置包的别名
type-aliases-package: com.zss.rbacproject.pojo
mapper-locations: classpath:/mapper/*.xml

简介

SpringSecurity是针对spring项目的安全框架,也是springboot底层安全默认的技术选型,它可以实现强大的web安全控制,我们仅需要引入spring-boot-starter-security模块,进行少量但是配置,即可实现强大的安全管理。

常用的类

​ WebSecurityConfigurer:自定义Security策略

​ AuthenticationManagerBuilder:自定义认证策略

​ @EnableWebSecurity:开启WebSecurity模式

步骤

导入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

配置

1
2
3
4
5
6
7
8
9
//这是一个固定的架子模式
@EnableWebSecurity
public class WebConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
}

详细配置,授权模式

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void configure(HttpSecurity http) throws Exception {
//现在我们定义仅仅首页可以直接访问
http.authorizeHttpRequests().antMatchers("/").permitAll()
//现在我们定义部门页面只有vip角色可以进行访问
.antMatchers("department/**").hasRole("zss");
//认证失败在,则跳转到发送/login请求,跳转到login页面
http.formLogin();

//注销功能
http.logout();
}

image-20220523151019636

image-20220523151107677

虽然拦截器也可以做到这种功能,但是拦截器功能比较麻烦,而且代码繁琐,我们来看一拦截器实现的代码

1
2
3
4
5
6
@Override
public void addInterceptors(InterceptorRegistry registry) {
//这里是登录页面访问的拦截器
registry.addInterceptor( loginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/index","/login","/js/**","/css/**");
//这里是权限控制的拦截器,具体的拦截内容就不再进行展示 registry.addInterceptor(authorizationInterceptor()).addPathPatterns("/**").excludePathPatterns("/index","/login","/js/**","/css/**");
}

认证模式

1
2
3
4
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("zss").password("200101").roles("zss");
}

静态资源

我们默认的静态资源都是存放在static下面

image-20220519093003995

那么他是如何自动找到这个文件下面的???进入到源码中进行查看可以发现,他们都去寻找了webjars 的一个文件

image-20220519093209781

image-20220519093825829

同时这一段代码也告诉我们如何访问到静态资源,启服务器,这样我们可以访问成功

image-20220519094605189

当然这也只是一种的方式,那么另外一种方式,我们这里的所有的资源都会被默认的收录。

image-20220519095408227

而在这个顺序中,resource的优先级最高,其次是static最后是public

首页定制

首先会自动寻找资源下面的index文件,而对于templates下面的页面,只能通过controller进行跳转。

image-20220519101141908

MVC扩展配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.zss.springbootdemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

/**
* @author zss
*/
//表明这是一个配置类
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}


public static class MyViewResolver implements ViewResolver{

@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
}

我们以视图解析器为例

1
2
3
4
public interface ViewResolver {
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}

我们去寻找对应的实现方法,这里是遍历视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
List<View> candidateViews = new ArrayList();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
Iterator var5 = this.viewResolvers.iterator();

while(var5.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var5.next();
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}

Iterator var8 = requestedMediaTypes.iterator();

while(var8.hasNext()) {
MediaType requestedMediaType = (MediaType)var8.next();
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
Iterator var11 = extensions.iterator();

while(var11.hasNext()) {
String extension = (String)var11.next();
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}

if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}

return candidateViews;
}

image-20220519111127256

我们可以看到这个图片中viewrasolvers,除了springboot自带的,还有引入的thymeleaf和自定义的myvvcconfig,而会自动从中选择一个最好的视图进行解析

SpringMVC doDispatch方法的基本思路梳理_HelloWorld_EE的博客-CSDN博客

扩展mvc进行视图跳转

1
2
3
4
5
6
7
8
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//尝试视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("show").setViewName("haha");
}
}

但是这个注解之中不能加上@EnableWebMvc,一旦加上这个注解,会导致所有的配置都不生效,我们可以看出他导入了一个类

image-20220519142130750

我们进入到这个类,会发现这个继承了这个另外一个属性WebMvcConfigurerComposite

image-20220519143340932

进入到mvc自动装配的类WebMvcAutoConfiguration.class,我们查看到@ConditionalOnMissingBean({WebMvcConfigurationSupport.class}),当缺失这个类的时候,注解才会生效,但是@EnableWebMvc又间接的使用了这个类,会导致所有的配置全部失效。

image-20220519143115055