一、springboot入门

1、导入依赖

导入springboot版本仲裁中心

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.8.RELEASE</version><relativePath />
</parent>

添加web依赖 springboot的web场景启动器

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

2、主程序入口@SpingBootApplication

@SpringBootApplication:告诉启动类 这是一个SpringBoot应用

@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class,args);}
}

3、编写业务Controller

@RestController里面就包含了@Controller和@ResponseBody 以后就写这个就可以了

@RestController
public class HelloController {@RequestMapping("/hello")public String handle01(){return "Hello, Spring Boot 2!";}
}

4、配置application.properties

这里面可以修改Tomcat的配置和springMVC的配置等(统一的配置文件)

server.port=8888

5、把项目打成jar包,直接在目标服务器执行

 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>
</build>

maven工具栏的clean和package 然后进入到文件夹cmd打开,输入:java -jar xxx.jar 启动

二、 SpringBoot特点

1、依赖管理

  • 父项目做依赖管理

spring-boot-starter-parent的父项目spring-boot-dependencies几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制

开发的导入starter场景启动器

1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的  *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope>
</dependency>

无需关注版本号,自动版本仲裁。当然,我们也可以手动修改我们要的版本

<properties><mysql.version>5.1.43</mysql.version>
</properties>

2、自动配置

1、自动配好Tomcat,SpringMVC...

2、自动配好Web常见功能,如:字符编码问题

3、默认的包结构

主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来,无需以前的包扫描配置(比如Controller的配置只要放到com.boot.controller这些包下面就可以自动扫描)

就想方包外面,想要改变扫描路径

  • 在主启动类里面加@SpringBootApplication(scanBasePackages=“com.pzh”)

  • 或者@ComponentScan 指定扫描路径

4、各种配置都有默认值

  • 默认配置(写在.properties里的)最终都是映射到某个类上,如:MultipartProperties

  • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象

5、按需加载所有自动配置项

  • 引入的starter
  • 引入了哪些场景这个场景的自动配置才会开启
  • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面,如果没有引入特定的starter,里面的相关配置文件会发红 
  • 引入batch后就不发红,代表生效了

三、容器功能

1、组件的添加(底层注解)

@Configuration

可以使用一个配置类代替以前spring的beans.xml,加上@Configuration注解

  • 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的,组件名字默认方法名,也可以加括号自定义
  • 这个配置类本身也是一个组件

属性:proxyBeanMethods:代理bean的方法

  • 全模式true:我们外面调用都是在容器中找那个组件。每次调用都会检查容器会不会有,速度慢。(默认的)别人还要用就true,保证还是原来的组件
  • 轻量级模式false:容器中不会保存代理对象,每次调用都会产生新的对象。优点:不会检查返回的对象在容器中有没有,可以跳过检查,返回速度非常快。如果是单用这个组件,别人也不依赖这个组件,我们就可以false
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {/*** 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象* @return*/@Bean //给容器添加组件,方法名作为组件的id,返回类型就是组件类型。返回的值就是组件在容器中的实例public User user01(){User zhangsan = new User("zhangsan", 18);//User组件依赖了Pet组件zhangsan.setPet(tomcatPet());return zhangsan;}@Bean("tom") //自定义组件的名字为tompublic Pet tomcatPet(){return new Pet("tomcat");}
}

@Import

@Import注解写在一个类的组件类上

@Import({User.class, DBHelper.class})

可以给容器中自动创建出这两个类型的组件,用import导入默认组件的名字为全类名。

我们可以从主运行类中获取导入的组件

String[] type = run.getBeanNamesForType(User.class);
for (String s : type) {System.out.println(s);
}
DBHelper b = run.getBean(DBHelper.class);
System.out.println(b);

@Conditional

条件装配:满足指定的条件时,才能进行组件的注入,有很多派生的子注解,可以放在类和方法上

例:当容器中有tom这个组件的时候,我们再注入user01,也可以放到类上,不满足类里面的都不生效

 @ConditionalOnBean(name = "tom")@Bean //给容器添加组件,方法名作为组件的id,返回类型就是组件类型。返回的值就是组件在容器中的实例public User user01(){User zhangsan = new User("zhangsan", 18);//User组件依赖了Pet组件zhangsan.setPet(tomcatPet());return zhangsan;}//    @Bean("tom") //不想让组件名就是方法名,可以在这个注解里面指定名字public Pet tomcatPet(){return new Pet("tomcat");}

@ImportResource

以前很多都是用的bean.xml方式,我们想要让这些bean都导入进容器,就可以用这个注解放到配置类下,运行我们继续用以前的配置类的形式

@ImportResoure("classpath:beans.xml") //导入Spring的配置文件

@ConfigurationProperties

想让application.properties和javabean绑定,想让配置文件和javaBean绑定用原生代码很复杂,使用@ConfigurationProperties注解就很简单做到

//application.properties文件中
mycar.brand=BYD
mycar.price=100000

prefix属性:是和springboot核心配置文件下面的属性绑定的

为什么要加@Component注解?因为只有在容器中的组件,才会拥有SpringBoot提供的强大功能

@Component
@ConfigurationProperties(prefix = "mycar")    //绑定配置文件的前缀
public class Car {private String brand;private Integer price;...
}

除了@Component注解,还有一种方式,在配置类上面加注解代替之,可以防止想要注册的类是默认不可修改的类,如果是第三方包里面的类不能加@Component就用这种方式

@EnableConfigurationProperties(Car.class)
//1.开启Car配置绑定功能
//2.把Car这个组件自动注册到容器中
public class MyConfig {
...

四、自动配置原理入门

1、@SpringBootConfiguration

@Configuration代表当前是一个配置类,@SpringBootConfiguration是springboot的核心配置类

2、@ComponentScan

指定扫描哪里 spring的注解

3、@EnableAutoConfiguration(重点)

1、@AutoConfigurationPackage 自动导入包

里面利用Registrar可以把mainApplication程序所在的包下面的组件批量注册进来

2、@Import(AutoConfigurationImportSelector.class)

(1)利用getAutoConfigurationEntry(annotationMetatada)给容器中批量导入一些组件

(2)调用getCandidateConfigurations(annotationMetadata,attributes)获取到所有需要导入的配置类

(3)利用工厂加载loadSpringFactories得到所有的组件

(4)从meta-inf/spring.factories位置加载一个文件。默认扫描我们当前系统里面所有meta-inf/spring.factories位置的文件

4、按需开启自动配置项

虽然启动的时候会加载所有的场景的自动配置,默认全部加载 xxxAutoConfiguration

但最终会按照条件装配规则,按需配置 @ConditionalXXX

5、修改默认配置

  • Springboot先加载所有的自动配置
  • 每个自动配置类会按照条件进行生效,默认都会绑定配置文件指定的值
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 只要用户有自己配置的,就以用户的优先
  • 定制化配置:(1)用户直接自己@Bean替换底层的组件 (2)用户去看这个组件是获取配置文件什么值就去修改

xxxAutoConfiguration -> 组件 -> xxxProperties里面拿 -> application.properties里面拿

五、开发小技巧

1、Lombok

包含在spring-boot-dependencies-2.4.1.pom里面。可以简化javaBean的编写,不用写构造函数,gettersetter和toString方法,都用注解代替

还有一个@Slf4j注解用于controller层的日志输出

2、dev-tools

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional>
</dependency>

再按Ctrl+F9可以实现Restart,自动重启

3、Spring Initailizr(项目初始化向导)

  1. 选择我们需要的开发场景
  2. 自动依赖引入

  3. 自动创建项目结构

  4. 自动编好主配置类

六、yaml配置文件

1、语法

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null

    k: v
  • 对象:键值对的集合。map、hash、set、object

    行内写法:  k: {k1:v1,k2:v2,k3:v3}
    #或
    k: k1: v1k2: v2k3: v3
  • 数组:一组按次序排列的值。array、list、queue
    行内写法:  k: [v1,v2,v3]
    #或者
    k:- v1- v2- v3

注意:

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,'与"   双引号表示字符串内容\n会换行而单引号输出\n

2、练习

@Component
@ConfigurationProperties(prefix = "person")
@Data
@ToString
public class Person {private String userName;private Boolean boss;private Date birth;private Integer age;private Pet pet;private String[] interests;private List<String> animal;private Map<String, Object> score;private Set<Double> salarys;private Map<String, List<Pet>> allPets;
}

对应绑定的yml文件内容

person:userName: zhangsanboss: falsebirth: 2019/12/12 20:12:33age: 18pet:name: tomcatweight: 23.4interests: [篮球,游泳]animal:- jerry- marioscore:english:first: 30second: 40third: 50math: [131,140,148]chinese: {first: 128,second: 136}salarys: [3999,4999.98,5999.99]allPets:sick:- {name: tom}- {name: jerry,weight: 47}health: [{name: mario,weight: 47}]

3、yml配置提示

在官方文档里找到Metadata Format,里面有pom配置代码

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional>
</dependency>

再在里面写不用打包到war包的代码,貌似2.4以后默认不用写了,都不会打包

七、Web开发

1 、简单功能分析

静态资源访问

1、静态资源目录

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources

访问 : 当前项目根路径/ + 静态资源名

原理: 静态映射/**。

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

改变默认的静态资源路径

spring:resources:# 把静态资源放在haha文件夹下static-locations: [classpath:/haha/]

2、静态资源访问前缀

默认无前缀不方便后期拦截器的使用,比如要拦截没有登录的/**的请求,前面加/res的可以放行

spring:mvc:# 浏览器地址前面要加上res才能访问静态资源static-path-pattern: /res/**

当前项目+static-path-pattern+静态资源名 =静态资源文件夹下找

3、webjar

自动映射 /webjars/**

https://www.webjars.org/

<dependency><groupId>org.webjars</groupId><artifactId>jquery</artifactId><version>3.5.1</version>
</dependency>

欢迎页支持

1、静态资源路径下index.html。可以配置静态资源路径,但不能配置静态资源访问前缀,否则index.html不能默认访问

2、controller能处理/index

自定义Favicon(网站小图标)

名字叫:favicon.ico就可以。可以配置静态资源路径,但不能配置静态资源访问前缀,否则favicon.ico不能默认访问

浏览器会发送/favicon请求获取到哦图标,整个session期间不再获取

静态资源配置原理

  • springboot启动默认加载xxxAutoConfiguration类(自动配置类)
  • springMVC功能的自动配置类webMvcAutoConfiguration,生效
  • 配置文件的相关属性和xxx进行绑定,webnMvcProperties==spring.mvc、ResourceProperties=spring.resources
  • 一个配置类只有一个有参构造器,那么有参构造器的所有参数的值都会从容器中确定

2、请求参数处理

请求映射

@rest使用及原理

Rest风格支持(使用http请求方式动态来表示对资源的操作)

  • 以前我们用/getUser        /deleteUser这些
  • 现在访问/user   get获取用户,delete删除用户,put修改用户,post保存用户
  • 核心Filter;HiddenHttpMethodFilter   (选择性开启,可以伪造rest风格请求)
    spring:mvc:hiddenmethod:filter:enabled: true   #开启页面表单的Rest功能

Rest原理(表单提交要使用Rest的时候)

  1. 表单会带上_method=put/delete/post
  2. 请求过来被HiddenHttpMethodFilter拦截
  3. 判断请求是否正常,是不是post
  4. 如果是获取_method的值,兼容一下请求:put、delete、patch
  5. 原生request(post)包装模式requesWrapper重写了getMethod方法,返回传入的值
  6. 过滤器放行的时候用wrapper,以后的方法调用getMethod是调用requesWrapper的

注意:支持真实put,delete请求发送的地方,就每必要包装了(Postman工具),不支持真实put,delete请求的地方,可以用过这只包装类的方式伪造用过rest风格请求。

普通参数与基本注解

注解:

@PathVariable路径变量)       @RequestHeader获取请求头        @RequestParam获取请求参数        @CookieValue获取cookie值       @RequestBody获取请求体(post才有请求体,表单提交)

@RestController
public class HelloController {@GetMapping("/car/{id}/owner/{username}")public Map<String,Object> getCar(@PathVariable("id") Integer id,@PathVariable("username") String name,@PathVariable Map<String,String> pv,@RequestHeader("User-Agent") String userAgent,@RequestHeader Map<String,String> header,@RequestParam("age") Integer age,@RequestParam("inters") List<String> inters,@RequestParam Map<String,String> params,@CookieValue("Idea-ce2cbf95") String co,@CookieValue("Idea-ce2cbf95") Cookie cookie){Map<String,Object> map = new HashMap<>();map.put("id",id);map.put("name",name);map.put("pv",pv);
//        map.put("userAgent",userAgent);
//        map.put("header",header); 这两个太多了map.put("age",age);map.put("inters",inters);map.put("params",params);map.put("Idea-ce2cbf95",co);System.out.println(cookie.getName()+":"+cookie.getValue());return map;}@PostMapping("/save")public Map postMethods(@RequestBody String content){ //这个必须是Post才有bodyMap<String,Object> map = new HashMap<>();map.put("content",content);return map;}}

@RequestAttribute获取request域属性(页面转发的时候请求当前转发的数据)

@Controller
public class RequestController {@GetMapping("/goto")public String goToPage(HttpServletRequest request){request.setAttribute("msg","成功了");request.setAttribute("code",200);return "forward:/success";}@ResponseBody@GetMapping("/success")//两种方法接收public Map success(@RequestAttribute("msg") String msg,HttpServletRequest request){Object msg1 = request.getAttribute("msg");Map<String,Object> map = new HashMap<>();map.put("reqMethod_msg",msg1);map.put("annotation_msg",msg);return map;}
}

@MatrixVariable(矩阵变量)

面试题:页面开发的时候cookie禁用了,session里面的内容怎么拿到?

在session里面保存了东西session.set(a,b),每个人都有session的id叫jsessionid保存到cookie里面,cookie每次发请求都会携带。那么现在禁用cookie怎么拿到session的东西呢?

可以使用矩阵变量 url重写:/abc;jsessionid=xxxx把cookie的值使用矩阵变量的方式传递

语法: /cars/sell;low=34;brand=byd,audi,yd        访问的时候用分号隔开

@GetMapping("/cars/{path}")
public Map carsSell(@MatricxVariable("low")Integer low,@MatricxVariable("brand")List<String> brand){Map<String,Object> map = new HashMap<>();map.put("low",low);map.put("brand",brand);return map;
}

Springboot默认禁用矩阵变量的功能 需要手动开启(P31)

new个UrlpathHelper类  urlPathHelper.setRemoveSemicolonContent(false)

3、模板引擎

视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。所有我们要学习Thymeleaf模板引擎

导入

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

还需要在html中引用

<html lang="en" xmlns:th="http://www.thymeleaf.org">

用法

<body>
//会后端传来的值会覆盖掉“哈哈”
<h1 th:text="${msg}">哈哈</h1><a href="www.guigu.com" th:href="${link}">去百度</a>
<!--下面这个@是直接把link当作链接的,不会转意-->
<a href="www.guigu.com" th:href="@{link}">去百度2</a>
</body>

八、构建后台管理系统

1、项目创建

这里可以直接使用脚手架,勾选thymeleaf,web-starter,devtools

2、静态资源处理

把css,js,fonts,images等所有静态文件放到static文件夹

3、编写登录跳转到主页面代码

@Controller
public class IndexController {/*** 去登录页* @return*/@GetMapping(value={"/","/login"})public String loginPage(){return "login";}@PostMapping("/login")//User已经写好了bean类,包含userName和passwordpublic String main(User user, HttpSession session, Model model){if(StringUtils.hasLength(user.getUserName())&&"123456".equals(user.getPassword())){//把登录成功的用户保存起来session.setAttribute("loginUser",user);//登录成功重定向到main.html; 重定向防止表单重复提交return "redirect:/main.html";}else {model.addAttribute("msg","账号或者密码错误");//回到登录页面return "login";}}/*** 去main页面* @param session* @param model* @return*/@GetMapping("/main.html") //这里不会跳到主页面,因为模板引擎跳转页面必须return才可以public String mainPage(HttpSession session, Model model){//判断是否登录, 可以用拦截器,过滤器,下面只是简单处理一下Object loginUser = session.getAttribute("loginUser");if(loginUser != null){//跳转到主页面return "main";}else {model.addAttribute("msg","请登录");return "login";}}
}

4、login.html登录部分代码

<!--表单提交post请求-->
<form class="form-signin" action="http://view.jqueryfuns.com/2014/4/10/7_df25ceea231ba5f44f0fc060c943cdae/index.html" method="post" th:action="@{/login}"><div class="form-signin-heading text-center"><h1 class="sign-title">登录</h1><img src="data:images/login-logo.png" alt=""/></div><div class="login-wrap"><!--登录输入上面提示底层代码使用model.addAttribute写入的msg提示信息--><label style="color: red" th:text="${msg}"></label><input type="text" name="userName" class="form-control" placeholder="用户名" autofocus><input type="password" name="password" class="form-control" placeholder="密码"><button class="btn btn-lg btn-login btn-block" type="submit"><i class="fa fa-check"></i></button>

main.html替换显示登录用户代码(写在行内的用法 两个div里面)

[[${session.loginUser.userName}]]

知识点:

  • 前端表单传过来的两个参数userName, password会自动绑定到User这个POJO上。其实这都是Spring @RequestMapping这个注解的功劳,它会自动扫描形参的POJO,并创建对象,如果前端传进来的参数与POJO成员变量名相同,会通过POJO的setter方法传给该对象。
  • session和model,session 里放的数据可以在其他页面使用,model的数据,只能在接下来的页面使用,其他页面就不能使用,存入session后,取出数据使用get()方法,像是对象,而model使用点,有点类似json字符串?
  • session和request,request对象的生命周期是针对一个客户端(说确切点就是一个浏览器应用程序)的一次请求,当请求完毕之后,request里边的内容也将被释放。,session可以跨越很多页面。而session的生命周期也是针对一个客户端,但是却是在别人设置的会话周期内(一般是20-30分钟),session里边的内容将一直存在,即便关闭了这个客户端浏览器 session也不一定会马上释放掉的。

5、模板抽取

即把各个html的公共部分抽取出来放在一个公共的html页面中,并且各个部分标注自己的属性以便其他页面引用,公共的部分比如导航栏,标题栏,引入的js、css文件等

被引对象标签加上:

  • th:framgent=“标签名称”
  • id=“标签名称”

引用对象标签加上:(与上面一一对应)

  • th:insert=“公共页名称 :: 标签名称”
  • th:insert=“公共页名称 :: #标签名称”

6、数据渲染

通过model加入静态用户数据

@GetMapping("/dynamic_table")public String dynamic_table(Model model){//表格内容的遍历List<User> users = Arrays.asList(new User("zhangsan", "123456"),new User("lisi", "123444"),new User("haha", "aaaaa"),new User("hehe ", "aaddd"));model.addAttribute("users",users);return "table/dynamic_table";}

thymeleaf遍历

<table class="display table table-bordered" id="hidden-table-info">
<thead>
<tr><th>#</th><th>用户名</th><th>密码</th>
</tr>
</thead>
<tbody>
<tr class="gradeX" th:each="user,stats:${users}"> //逗号后加入状态可以实现id计数<td th:text="${stats.count}">Trident</td><td th:text="${user.userName}">Internet</td><td>[[${user.password}]]</td>
</tr>
</tbody>
</table>

7、拦截器

编写一个拦截器类,拦截所有未登录的页面访问登录后才能展示的页面

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {/*** 目标方法执行前* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String requestURI = request.getRequestURI();log.info("拦截的请求路径是{}",requestURI);//登录检查逻辑Object loginUser = request.getSession().getAttribute("loginUser");if (loginUser != null){System.out.println("有用户,放行");return true;}//拦截住,返回登录页面并添加提示信息,信息存在request域中request.setAttribute("msg","请先登录");request.getRequestDispatcher("/").forward(request,response);return false;}/*** 目标方法执行完成以后*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}/*** 页面渲染以后*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

编写一个拦截器的管理类

/*** 1.编写一个拦截器实现HandlerInterceptor接口* 2.拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)* 3.指定拦截规则【如果是拦截所有,静态资源也会被拦截】*/@Configuration
public class AdminWebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") //所有请求都被拦截包括静态资源.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的资源,不会经过拦截器}
}

可以把之前编写的登录页面拦截代码删除了

  • 登录页面会拦截,检查以登录后会放行,设置好的静态资源不会经过拦截器
  • 如果想要在登录页面上面添加一个label显示登录出现的各种问题(账号密码错误/未登录),需要掌握request域和session域作用范围,以及重定向和转发的使用场景

8、文件上传

修改页面表单

<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data"><div class="form-group"><label for="exampleInputEmail1">邮箱</label><input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email"></div><div class="form-group"><label for="exampleInputPassword1">名字</label><input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password"></div><div class="form-group"><label for="exampleInputFile">头像</label><input type="file" name="headerImg" id="exampleInputFile"></div><div class="form-group"><label for="exampleInputFile">生活照</label><input type="file" name="photos" multiple></div><div class="checkbox"><label><input type="checkbox"> Check me out</label></div><button type="submit" class="btn btn-primary">提交</button>
</form>

上传处理代码

@Slf4j
@Controller
public class FormTestController {@GetMapping("/form_layouts")public String form_layouts(){return "form/form_layouts";}/*** 自动封装上传过来的文件* @param email* @param username* @param headerImg* @param photos* @return* @throws IOException*/@PostMapping("/upload")public String upload(@RequestParam("email") String email,@RequestParam("username") String username,@RequestParam("headerImg") MultipartFile headerImg,@RequestParam("photos") MultipartFile[] photos) throws IOException {log.info("上传的信息:email={},username={},headerImg={},photos={}",email,username,headerImg.getSize(),photos.length);//开始上传if(!headerImg.isEmpty()){//实际上可以保存到文件服务器,OSS服务器String originalFilename = headerImg.getOriginalFilename();headerImg.transferTo(new File("D:\\tmp\\"+originalFilename));}if(photos.length>0){for (MultipartFile photo : photos) {if(!photo.isEmpty()){String originalFilename = photo.getOriginalFilename();photo.transferTo(new File("D:\\tmp\\"+originalFilename));}}}return "main";}
}

一些问题:如果不配置的话,默认上传是有大小限制的,要增大一些

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB

9、异常处理

默认规则

  • 默认情况下,Spring Boot提供/error处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据,比如访问的页面不存在 

还要添加jsessionid,里面保存了登录信息

要对其进行自定义,添加View解析为error,error/下的404,5xx页面会被自动解析

让自定义的页面也可以显示错误信息

<section><div class="container "><section class="error-wrapper text-center"><h1><img alt="" src="data:images/500-error.png"></h1><h2>OOOPS!!!</h2><h3 th:text="${error}">Something went wrong.</h3><p class="nrml-txt" th:text="${message}">Why not try refreshing you page? Or
you can <a href="#">contact our support</a> if the problem persists.</p><a class="back-btn" th:href="@{main.html}"> Back To Home</a></section></div>
</section>

10、 Web原生组件注入

1、使用Servlet API

在启动类上加入注解@ServletComponentScan(basePackages = “com.atguigu.admin”) :指定原生Servlet组件都放在那里

在编写的三大组件类上分别加上

  • @WebServlet(urlPatterns = “/my”):效果:直接响应,没有经过Spring的拦截器?
  • @WebFilter(urlPatterns={"/css/*","/images/*"})
  • @WebListener

2、使用RegistrationBean

@Configuration
public class MyRegistConfig {@Beanpublic ServletRegistrationBean myServlet(){MyServlet myServlet = new MyServlet();return new ServletRegistrationBean(myServlet,"/my","/my02");}@Beanpublic FilterRegistrationBean myFilter(){MyFilter myFilter = new MyFilter();
//        return new FilterRegistrationBean(myFilter,myServlet());FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));return filterRegistrationBean;}@Beanpublic ServletListenerRegistrationBean myListener(){MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();return new ServletListenerRegistrationBean(mySwervletContextListener);}
}
  • 注意:configuration注解属性使用默认的 proxyBeanMethods=true 保证原来的组件始终是单实例的,这样保证我们用的是之前的myservlet 不会重新new一个

九、数据源的自动配置

导入JDBC的场景

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

为什么官方帮我们导入数据源、jdbc、事务了缺不帮我们导入驱动呢?

-因为不知道我们要操作什么数据库

默认版本:<mysql.version>8.0.22</mysql.version>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
<!--<version>5.1.49</version>修改为自己的版本-->
</dependency>

(1)直接在下面指定版本可以修改为自己的版本(maven的就近原则)

(2)重新声明版本(maven属性的就近优先原则)(在pom里面改)

<properties><java.version>1.8</java.version><mysql.version>5.1.49</mysql.version>
</properties>

分析自动配置

1、自动配置的类

DateSourceAutoConfiguration:数据源的自动配置

  • 修改数据源相关配置:Spring.datasource
  • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
  • 底层配置好的连接池是HikariDataSource

DataSourceTransactionManagerAutoConfiguration:事务管理器的自动配置

JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud

  • 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
  • @Bean@Primary JdbcTemplate;容器中有这个组件

JndiDataSourceAutoConfiguration: jndi的自动配置 *

XADataSourceAutoConfiguration: 分布式事务相关的 *

2、修改配置项

spring:datasource:url: jdbc:mysql://localhost:3306/day23username: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driver

3、测试

@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {@AutowiredJdbcTemplate jdbcTemplate;@Testvoid contextLoads() {Long aLong = jdbcTemplate.queryForObject("select count(*) from province", Long.class);log.info("记录总数:{}",aLong);}
}

使用Druid数据源

数据源自动装备会用HikariDataSource他是目前性能最好的数据源,但实际开发中我们企业习惯使用阿里的Druid(德鲁伊)数据源,数据库连接池(具有对整套数据源的解决方案)

1、自定义方式

  1. 导入Druid的依赖

    <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.17</version>
    </dependency>
  2. 写配置类 代替bean.xml
    @Configuration
    public class MyDataSourceConfig {//可以绑定application里面的数据,不用set方法@ConfigurationProperties("spring.datasource")// 默认的自动配置是判断容器中没有才会配@ConditionalOnMissingBean(DataSource.class)@Beanpublic DataSource dataSource(){DruidDataSource druidDataSource = new DruidDataSource();
    //        druidDataSource.setUrl();
    //        druidDataSource.setUsername();这些都不用了,上面那个注解就可以跟配置文件绑定
    //        druidDataSource.setPassword();return druidDataSource;}
    }
  3. 通过测试类测试现在的数据源,更换成功
    @Autowired
    DataSource dataSource;
    @Test
    void contextLoads() {log.info("数据源类型:{}",dataSource.getClass());
    }
  4. 根据Druid官方文档配置druid的各种监控功能。官方文档提供的是在web.xml里面配置servlet和filter,我们使用的是springboot,所以可以用到前面的使用Web原生组件注入的方式得到servlet和filter,然后根据文档属性配置这两个组件的类。
    //下面两个放在DataSource方法中,也可以在配置文件中写//加入监控功能druidDataSource.setFilters("stat,wall");druidDataSource.setMaxActive(10);
    /*** 配置 druid的监控页功能* @return*/
    //    @Beanpublic ServletRegistrationBean statViewServlet(){StatViewServlet statViewServlet = new StatViewServlet();ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");registrationBean.addInitParameter("loginUsername","admin");registrationBean.addInitParameter("loginPassword","123456");return registrationBean;}/*** WebStatFilter 用于采集web-jdbc关联监控的数据。*/
    //    @Beanpublic FilterRegistrationBean webStatFilter(){WebStatFilter webStatFilter = new WebStatFilter();FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");return filterRegistrationBean;}

2、使用官方starter方式(更方便)

  1. 引入druid-starter

    <dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version>
    </dependency>
  2. 分析自动配置
    导入的包
    package com.alibaba.druid.spring.boot.autoconfigure;/*** @author lihengming [89921218@qq.com]*/
    @Configuration
    @ConditionalOnClass(DruidDataSource.class)
    @AutoConfigureBefore(DataSourceAutoConfiguration.class)
    @EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
    @Import({DruidSpringAopConfiguration.class,DruidStatViewServletConfiguration.class,DruidWebStatFilterConfiguration.class,DruidFilterConfiguration.class})
    public class DruidDataSourceAutoConfigure
    扩展配置项 spring.datasource.druidDruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patternsDruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启DruidFilterConfiguration.class}) 所有Druid自己filter的配置
  3. 示例配置
    spring:datasource:url: jdbc:mysql://localhost:3306/day23username: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driverdruid:aop-patterns: com.atguigu.admin.*  #监控这个包下的所有东西filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)stat-view-servlet:   # 配置监控页功能enabled: truelogin-username: adminlogin-password: 123456resetEnable: false    #是否有重置按钮web-stat-filter:  # 监控webenabled: trueurlPattern: /*exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'filter:stat:    # 对上面filters里面的stat的详细配置slow-sql-millis: 1000logSlowSql: trueenabled: truewall:enabled: trueconfig:drop-table-allow: false    #不允许删表

十、整合MyBatis操作

1、配置版

1.找到MyBatis的starter

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version>
</dependency>

2.源码分析(后看)

  • 全局配置文件
  • SqlSessionFactory:自动配置好了
  • SqlSession:自动配置SqlSessionTemplate组合了SqlSession
  • Mapper:只要我们写的操作MyBatis的接口标注了@Mapper就会自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class) : MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties

可以修改配置文件中 mybatis 开始的所有;

3.配置mybatis规则,在yaml文件中写

mybatis:config-location: classpath:mybatis/mybatis-config.xml  #全局配置文件位置mapper-locations: classpath:mybatis/mapper/*.xml  #sql映射文件位置

根据源码可知,配置 private Configuration configuration;

mybatis.configuration下面的所有,就是相当于改mybatis全局配置文件中的值

不必在全局配置文件中开启大驼峰,在yml中也可

mybatis:
#  config-location: classpath:mybatis/mybatis-config.xmlmapper-locations: classpath:mybatis/mapper/*.xmlconfiguration:map-underscore-to-camel-case: true可以不写全局配置文件,所有全局配置文件的配置都放在configuration配置项中即可

4.以根据id查询账户为例,编写业务代码

  1. 编写mapper接口。别忘记加上@Mapper注解

    @Mapper
    public interface AccountMapper {public Account getAcct(Long id);
    }
  2. 编写sql映射文件并绑定mapper接口
    @Service
    public class AccountService {@AutowiredAccountMapper accountMapper;public Account getAcctById(Long id){return accountMapper.getAcct(id);}
    }
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.atguigu.admin.mapper.AccountMapper"><select id="getAcct" resultType="com.atguigu.admin.bean.Account">select * from account_tb1 where id = #{id}</select>
    </mapper>
  3. controller
    @Autowired
    AccountService accountService;@ResponseBody
    @GetMapping("/acct")
    public Account getById(@RequestParam("id") Long id){return accountService.getAcctById(id);
    }

2、注解版(更方便)

只要在Mapper类的方法上加数据库注解就行了,其他方法的调用一样

不需要这两个配置文件了纯注解开发

@Mapper
public interface CityMapper {@Select("select * from city where id=#{id}")public City getById(Long id);public void insert(City city);
}

3、混合版本(比较复杂的sql的时候用)

  1. CityMapper

    public void insert(City city);
  2. 添加CityMapper.xml文件
    # id名与mapper类中的方法一致,其他的方法名随意
    <mapper namespace="com.atguigu.admin.mapper.CityMapper">
    //后面两个参数是为了把id也当作返回值封装进去<insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into city(`name`, `state`,`country`) values(#{name},#{state},#{country})</insert>
    </mapper>
  3. Controller
    @ResponseBody
    @PostMapping("/city")
    public City saveCity(City city){cityService.saveCity(city); //save方法自己完善return city;
    }
  4. 使用postman测试
  5. insert也可以用注解方法
    @Insert("insert into city(`name`, `state`,`country`) values(#{name},#{state},#{country})")
    @Options(useGeneratedKeys = true,keyProperty = "id")
    public void insert(City city);

最佳实战

  • 引入mybatis-starter
  • 配置application.yaml中,指定mapper-location位置即可
  • 编写Mapper接口并标注@Mapper注解
  • 简单方法直接注解方式
  • 复杂方法编写mapper.xml进行绑定映射
  • @MapperScan(“com.atguigu.admin.mapper”) 简化,其他的接口就可以不用标注@Mapper注解

4、整合mybatis-plus

建议安装MyBatisX插件

导入坐标

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version>
</dependency>

自动配置

  • MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对****mybatis-plus的定制
  • SqlSessionFactory 自动配置好。底层是容器中默认的数据源
  • **mapperLocations 自动配置好的。有默认值。*classpath*:/mapper/*/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下
  • 容器中也自动配置好了 SqlSessionTemplate
  • @Mapper 标注的接口也会被自动扫描;建议直接在springboot启动类加上 @MapperScan(“com.atguigu.admin.mapper”) 批量扫描就行

以测试查询user数据为例

  1. 添加数据库,创建表
  2. User类文件
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @TableName("user_tb1") //如果表名不是这个,就得写上规定的
    public class User {@TableField(exist = false) //表示忽略注解下面的属性private String userName;@TableField(exist = false)private String password;private Long id;private String name;private Integer age;private String email;
    }
  3. mapper(现在改为继承一个类就可以了,范型就填类文件的类型)
    public interface UserMapper extends BaseMapper<User> { //里面包含各种方法,如果过于复杂还是可以用xml}
  4. 测试
    @Test
    void testUserMapper(){User user = userMapper.selectById(1L);log.info("用户信息:{}",user);
    }

5、crud

1、把数据库查询出来放入表格中

Pojo类在找对应的数据库表的时候是按照名字一样来匹配的

如果表面和类名不一样就在类上加上:@TableName("user_tb1")

1、根据MyBatisPlus,已经可以在mapper省略很多方法,service里一样的也可以

public interface UserService extends IService<User> {
}

2、把数据查出来放在域中,供前端调用

@GetMapping("/dynamic_table")public String dynamic_table(@RequestParam(value = "pn",defaultValue = "1")Integer pn, Model model){//表格内容的遍历List<User> list = userService.list();
//        model.addAttribute("users",list);下面的就包括了users的信息,不做分页的话就写这个就行了Page<User> userPage = new Page<>(pn, 2);Page<User> page = userService.page(userPage, null);long current = page.getCurrent();long pages = page.getPages();long total = page.getTotal();List<User> records = page.getRecords();model.addAttribute("page",page);return "table/dynamic_table";}

3、修改前端代码接收展示需要的数据

  1. 留下一个th循环从数据库中查询数据

    <table class="display table table-bordered table-striped" id="dynamic-table"><thead><tr><th>#</th><th>id</th><th>name</th><th>age</th><th>email</th><th>操作</th></tr></thead><tbody role="alert" aria-live="polite" aria-relevant="all"><tr class="gradeX" th:each="user,stat:${page.records}">    #stat是遍历的状态<td th:text="${stat.count}">Trident</td><td th:text="${user.id}">Trident</td><td class=" " th:text="${user.name}">Internet    Explorer 4.0</td><td class=" " th:text="${user.age}">Win 95+</td><td class="center hidden-phone">[[${user.email}]]</td><td class="center hidden-phone">X</td></tr></tbody><tfoot></tfoot>
    </table>
  2. 分页数据展示,要先整合分页插件(mybatis官方文档中有代码),在table下面添加分页代码
    <div class="span6"><div class="dataTables_paginate paging_bootstrap pagination"><ul><li class="prev disabled"><a href="#">← 前一页</a></li><!--让当前页面进度高亮,并且显示所有页数--><li th:class="${num == page.current?'active':''}" th:each="num:${#numbers.sequence(1,page.pages)}"><!--使得可以通过按分页条更改地址后的页面参数pn达到分页效果--><a th:href="@{dynamic_table(pn=${num})}">[[${num}]]</a></li><li class="next disabled"><a href="#">下一页 → </a></li></ul></div>
    </div>

2、删除用户

1、在操作下面添加删除按钮的样式

<td><a th:href="@{/user/delete/{id}(id=${user.id},pn=${page.current})}" class="btn btn-danger btn-sm" type="button">删除</a>
</td>

括号里面是传的参数给后端

2、controller里面处理删除请求

@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id") Long id,@RequestParam(value = "pn",defaultValue = "1") Integer pn,RedirectAttributes ra){userService.removeById(id);//删除一项之后保证当前页面不变ra.addAttribute("pn",pn);//重定向到当前页面return "redirect:/dynamic_table";
}

关于@PathVariable和@RequestParam注解的区别:

一个id是从前端传过来,一个从地址栏传过来,写法不同

6、redis

1、导入坐标

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、导入包中的自动配置分析

  • RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
  • 自动注入了RedisTemplate<Object, Object> : xxxTemplate;
  • 自动注入了StringRedisTemplate;k:v都是String
  • key:value
  • 底层只要我们使用 **StringRedisTemplate、**RedisTemplate就可以操作redis

3、redis环境搭建

阿里云

  1. 阿里云按量付费redis。经典网络
  2. 申请redis的公网连接地址
  3. 修改白名单 允许0.0.0.0/0 访问

在本地虚拟机中搭建

  1. 虚拟机中下载好redis
  2. 防火墙中开启6376端口允许外接访问
  3. springboot连接redis

4、springboot连接redis

导入坐标(不写jedis默认是Lettuce)

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--        导入jedis-->
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency>

yml中配置redis相关信息

spring:redis:host: 192.168.11.128port: 6379password: 123456client-type: jedis  //也可以用Lettucejedis:pool:max-active: 10

测试连接成功

@Autowired
StringRedisTemplate redisTemplate;@Test
void testRedis(){ValueOperations<String, String> operations = redisTemplate.opsForValue();operations.set("hello","world");  //键值对String hello = operations.get("hello");System.out.println(hello);
}

5、统计相应页面访问次数显示在主页

编写拦截器

@Component //注册到容器中
public class RedisUrlCountInterceptor implements HandlerInterceptor {//redisTemplate是使用redis的工具@AutowiredStringRedisTemplate redisTemplate;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String uri = request.getRequestURI();ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();//让页面的uri为键,访问次数为值存入redis中opsForValue.increment(uri);return true;}
}

把拦截器注册到容器中

@Configuration
public class AdminWebConfig implements WebMvcConfigurer {@AutowiredRedisUrlCountInterceptor redisUrlCountInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//第一个是前面写的registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") //所有请求都被拦截包括静态资源.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的资源,不会经过拦截器//为什么不能new?因为容器中的组件不能new,从容器中拿就行了registry.addInterceptor(redisUrlCountInterceptor).addPathPatterns("/**").excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");}
}

把访问次数展示在首页

@GetMapping("/main.html")
public String mainPage(HttpSession session, Model model){ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();String s = opsForValue.get("/main.html");String s1 = opsForValue.get("/sql");model.addAttribute("mainCount",s);model.addAttribute("sqlCount",s1);return "main";
}
再在thymeleaf里面通过${}接收就行
  • 关于拦截器的选择:

    Filter、Interceptor 几乎拥有相同的功能

    1. Filter是Servlet定义的原生组件。脱离Spring应用也能使用
    2. Interceptor是Spring定义的接口,可以使用Spring 的自动装配等功能

十一、单元测试

导入坐标

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>

使用方法

@SpringBootTest
class Boot05WebAdminApplicationTests {@Testvoid contextLoads() {}
}

SpringBoot整合Junit以后

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚

JUnit5常用注解