Spring全家桶--SpringBoot(二)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: Spring全家桶--SpringBoot(二)

2.4 开发小技巧



2.4.1 lombok


lombok能简化JavaBean的开发


在pom文件里添加依赖


<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>


并在idea中下载插件


image.png


以前的getset方法我们不用写了,通过注解@Data,它就可以在编译的时候自动生成getset方法了


image.png


@NoArgsConstructor//为JavaBean添加无参构造
@AllArgsConstructor//为JavaBean添加有参构造
@Data
@ToString
public class Pet {
    private String name;
}


Slf4j


简化日志开发


添加@Slf4j注解既有此功能


image.png


2.4.2 dev-tools


项目或者页面修改以后:Ctrl+F9;


修改后的页面效果就会立马显示出来


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


2.4.3 Spring Initailizr


这个功能能帮我们快速构建SpringBoot项目


如下:


image.png


image.png


选中想要开发的项目需要的组件


image.png


目录结构直接给我们创建好了,而且SpringBoot的启动程序也写好了


我们只需要关注逻辑代码即可~


image.png


三、SpringBoot2核心功能



3.1 配置文件


3.1.1 properties


就是正常的配置文件,和前面学的一样


3.1.2 yaml


3.1.简介


YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。


非常适合用来做以数据为中心的配置文件


这个我太熟悉了啊,当时在专科的时候参加云计算竞赛,要写容器编排。我搁那天天练这个啊,当时也不知道格式啥的,就是死记硬背。


我想说的是,这个很重要,后面学ansible的时候也会用到这个,给爷狠学!!!


基本语法


key: value;kv之间有空格


大小写敏感


使用缩进表示层级关系


缩进不允许使用tab,只允许空格


缩进的空格数不重要,只要相同层级的元素左对齐即可


'#'表示注释


字符串无需加引号,如果要加,’'与""表示字符串内容 会被 转义/不转义


数据类型


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

k: v

1

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

行内写法:  k: {k1:v1,k2:v2,k3:v3}#或k:   k1: v1  k2: v2  k3: v3

1

数组:一组按次序排列的值。array、list、queue

行内写法:  k: [v1,v2,v3]#或者k: - v1 - v2 - v3

1

示例


person:  userName: zs  boss: true  birth: 2019/12/9  age: 18#  interests: [唱,跳,rap]  interests:    - 唱    - 跳    - rap#  集合也可以跟数组一样用-的形式来表示#  也可以写成[]的形式  animal: [Cat,Dog]#  map的两种写法#  score:#    english: 99#    math: 80#  score: {english:90,math:78}#   这里面的冒号后面不用跟空格,因为这是json的表示方法# 也可以写成键值对的形式{k:V,K:V}  score: {english:90,math:78}  salarys:    - 9999.98    - 9999.97  pet:    name: dog    weight: 99.97  allPets:    sick:#     以下这两种都表示对象,分别是k:v的形式和{}的形式      - {name: dog,weight: 34}      - name: cat        weight: 23    health:      - {name: A,weight: 10}      - {name: B,weight: 20}


访问测试


image.png


配置提示


自定义的类和配置文件绑定一般没有提示。


导入一个依赖即可~


<dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-configuration-processor</artifactId>            <optional>true</optional>        </dependency> <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>                <configuration>                    <excludes>                        <exclude>                            <groupId>org.springframework.boot</groupId>                            <artifactId>spring-boot-configuration-processor</artifactId>                        </exclude>                    </excludes>                </configuration>            </plugin>        </plugins>    </build>

之后就能愉快的自动提示了


image.png


3.2 Web开发


3.2.1 静态资源访问


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


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


原理: 静态映射/**


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


image.png


启动成功后,直接访问 当前项目根路径/ + 静态资源名


image.png


改变默认的静态资源路径


改变为AAA文件为静态资源文件夹


image.png


image.png


webjar


WebJars是一个很神奇的东西,可以让大家以jar包的形式来使用前端的各种框架、组件。


完成依赖添加

<dependency>        <groupId>org.webjars</groupId>        <artifactId>jq
ery</artifactId>        <version>3.5.1</version>    </dependency>


进行测试


可以尝试CTRL+f9快速编译


记得重启之后在访问,便可访问web静态资源了


image.png


3.2.2 欢迎页支持


静态资源路径下 index.html


可以配置静态资源路径


但是不可以配置静态 资源的访问前缀。否则导致 index.html不能被默认访问


就是把index页面放到静态资源目录下,他会自动访问这个页面


如果访问不了,记得clean一下(Maven插件clean功能)


image.png


3.2.3 自定义Favicon


favicon.ico 放在静态资源目录下即可更换网站图标


设置静态资源访问路径会导致 Favicon 功能失效


image.png


3.2.4 请求映射


@xxxMapping
Rest风格支持使用HTTP请求方式动词来表示对资源的操作
以前访问资源路径:
/getUser 获取用户
/deleteUser 删除用户
/editUser 修改用户
/saveUser 存用户
现在访问资源路径:
/user


GET-获取用户

DELETE-删除用户

PUT-修改用户

POST-保存用户

核心Filter; HiddenHttpMethodFilter


SpringBoot中手动开启


#开启rest风格在springboot里要手动开启
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true


表单


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
测试REST风格
<form action="/user" method="get">
    <input value="REST-GET 提交" type="submit">
</form>
<form action="/user" method="post">
    <input value="REST-POST 提交" type="submit">
</form>
表单method=post, 隐藏域 _method=put 
<form action="/user" method="post">
    <input name="_method" type="hidden" value="put">
    <input value="REST-put 提交" type="submit">
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE">
    <input value="REST-delete 提交" type="submit">
</form>
</body>
</html>


3.2.5 普通参数与基本注解


基本注解:


@PathVariablev 获取路径变量


@RequestHeader 获取请求头


@RequestParams 获得请求参数


@CookieValue 获取Cookie的值


@RequestBody 获取请求体


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title></head><body>测试REST风格<form action="/user" method="get">    <input value="REST-GET 提交" type="submit"></form><form action="/user" method="post">    <input value="REST-POST 提交" type="submit"></form><form action="/user" method="post">    <input name="_method" type="hidden" value="put">    <input value="REST-put 提交" type="submit"></form><form action="/user" method="post">    <input name="_method" type="hidden" value="DELETE">    <input value="REST-delete 提交" type="submit"></form><hr>测试基本注解<ur>    <a href="car/3/owner/zs?age=18&interest=girl&interest=boy">car/{id}}/owner/{username}</a>    <li>@PathVariable(路径变量)</li>    <li>@RequestHeader(获取请求头)</li>    <li>@RequestParam(获取请求参数)</li>    <li>@CookieValue(获取cookie)</li>    <li>@RequestAttribute(获取request域属性)</li>    <li>@RequestBody(获取请求体)</li>    <li>@MatrixVariable(矩阵变量)</li></ur><form action="/save" method="post">    测试@RequestBody注解<br>    用户名:<input name="UserName"><br>    邮箱:<input name="email"><br>    <input type="submit" value="提交"></form></body></html>
1
@GetMapping用于将HTTP get请求映射到特定处理程序的方法注解
具体来说,@GetMapping是一个组合注解,是@RequestMapping(method = RequestMethod.GET)的缩写。
@PostMapping用于将HTTP post请求映射到特定处理程序的方法注解
具体来说,@PostMapping是一个组合注解,是@RequestMapping(method = RequestMethod.POST)的缩写


package com.caq.boot.controller;import org.springframework.web.bind.annotation.*;import sun.management.Agent;import java.util.HashMap;import java.util.List;import java.util.Map;@RestControllerpublic class ParameterController {    @GetMapping("/car/{id}/owner/{username}")    public Map<String,Object> getCar(@PathVariable("id") Integer id,                                     @PathVariable("username") String username,                                     @PathVariable Map<String,String> pv,                                     @RequestHeader("user-Agent") String userAgent,                                     @RequestHeader Map<String,String> header,                                     @RequestParam("age") Integer age,                                     @RequestParam("interest") List<String> interest,                                     @RequestHeader Map<String,String> param                                     ){        /*        @CookieValue("_ga")        传入一个Map<String,String>可以接受所有的参数        @PathVariable带key了就拿特点的,不带了就拿所有的        @RequestHeader同上         */        HashMap<String, Object> map = new HashMap<>();        map.put("id",id);        map.put("username",username);//        map.put("pv",pv);//        map.put("userAgent",userAgent);//        map.put("header",header);//        map.put("age",age);//        map.put("interest",interest);        map.put("param",param);        map.put("_ga",_ga);        return map;    }    @PostMapping("/save")    public Map postMethod(@RequestBody String content){        HashMap<String, Object> map = new HashMap<>();        map.put("content",content);        return map;    }}


3.2.6 视图解析流程


目标方法处理的过程中,所有数据都会被放在ModelAndViewContainer 里面。包括数据和视图地址

方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer

任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)

processDispatchResult 处理派发结果(页面改如何响应)

视图解析:


返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);


返回值以 redirect: 开始:new RedirectView() --》 render就是重定向


返回值是普通字符串: new ThymeleafView()—>


3.2.7 Thymeleaf


为什么用Thymeleaf?


JSP不好用,Thymeleaf好用它适用于SpringBoot模块。它使用html作为模板页,通过html一些特殊标签语法代表其含义,不破坏html的结构


image.png


Thymeleaf是一款现代化、服务端Java模板引擎


Thymeleaf是Springboot官方支持的模板引擎,有着动静分离等独有特点


Thymeleaf的主要目标是为您的开发工作流程带来优雅自然的模板-HTML可以在浏览器中正确显示,也可以作为静态原型工作,从而可以在开发团队中加强协作。

Thymeleaf拥有适用于Spring Framework的模块,与您喜欢的工具的大量集成以及插入您自己的功能的能力,对于现代HTML5 JVM Web开发而言,Thymeleaf是理想的选择——尽管它还有很多工作要做。

基本语法

表达式名字 语法 用途

变量取值 ${…} 获取请求域、session域、对象等值

选择变量 *{…} 获取上下文对象值

消息 #{…} 获取国际化等值

链接 @{…} 生成链接

片段表达式 ~{…} jsp:include作用,引入公共页面片段

标签 作用 示例

th:id 替换id <input th:id="${user.id}"/>

th:text 文本替换 <p text:="${user.name}">bigsai</p>

th:utext 支持html的文本替换 <p utext:="${htmlcontent}">content</p>

th:object 替换对象 <div th:object="${user}"></div>

th:value 替换值 <input th:value="${user.name}" >

th:each 迭代 <tr th:each="student:${user}" >

th:href 替换超链接 <a th:href="@{index.html}">超链接</a>

th:src 替换资源 <script type="text/javascript" th:src="@{index.js}"></script>

2. thymeleaf使用


我们只需要给request域中放一些数据,然后跳转到页面我们就能通过thymeleaf来取得这些值


引入依赖包


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


控制层代码


package com.caq.boot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ViewTestController {
    @GetMapping("/thymeleaf")
    public String forwardTest(Model model){
        //model中的数据会被放在请求域中 request.setAttribute("a",aa)
        model.addAttribute("message","你好thymeleaf");
        model.addAttribute("link","http://www.baidu.com");
        return "success";
    }
}


html页面


<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title >Title</title>
</head>
<body>
<h1 th:text="${message}">哈哈</h1>
<h2>
    <a href="www.jd.com" th:href="${link}">去百度</a>
    <a href="www.jd.com" th:href="@{link}">去百度</a>
</h2>
</body>
</html>


补充:


假如在这个页面上有一个请求,url 为 “showAgain”


那么访问全 url 就是:http://localhost:8080/first/showAgain 相对路径,就是show.html 同级目录


假如在这个页面上有一个请求,url 为 “/showAgain”


那么访问全 url 就是 http://localhost:8080/showAgain 绝对路径,就是从根目录 拼接 url


@GetMapping("/responsive_table")
public String responsive_table(){
    return "table/responsive_table";
}

image.png


3.2.8 SpringBoot实战


学了这么多我们来用SpringBoot构建一个后台管理系统


我们只关注业务逻辑的实现,前端代码展示不关注


1. SpringBoot项目创建


选中web开发常用的组件


thymeleaf、web-starter、devtools、lombok


2. 静态资源处理


自动配置好,我们只需要把所有静态资源放到static文件夹下即可


3. 路径构建


th:action="@{/login}"


4. 模板抽取


因为left side和header section部分所有的页面都是一样的,所以我们抽取出来一个公共部分,需要的这段代码的地方直接引用公共代码,这样让我们的代码更加简介。


怎么抽取呢?


我们用thymeleaf,fragment类似于JSP的tag,在html中文件中,可以将多个地方出现的元素块用fragment包起来使用。


th:insert:保留自己的主标签,保留th:fragment的主标签。

th:replace:不要自己的主标签,保留th:fragment的主标签。

th:include:保留自己的主标签,不要th:fragment的主标签。(官方3.0后不推荐)


<div th:insert="footer :: copy"></div>  
<div th:replace="footer :: copy"></div>  
<div th:include="footer :: copy"></div>


结果为:


<div>  
    <footer>  
       the content of footer   
    </footer>    
</div>    
<footer>  
    the content of footer 
</footer>    
<div>  
    the content of footer 
</div>


5. 页面跳转


如果账户密码匹配了就把账号密码封装到User对象中然后把对象保存到session域,以便后面的页面根据是否登录做判断。


密码匹配保存对象重定向至新页面


不配通过thymeleaf传入渲染后的数据到view层提示账号或密码错误之后返回到登录页


@PostMapping("/login")
public 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";
    }
}


3.2.9 拦截器


SpringBoot中的拦截器和我们javaweb阶段学的filter很像


拦截器就是用来拦截请求,根据拦截规则进行放行


拦截器的使用步骤


编写一个拦截器实现HandlerInterceptor接口

拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)

指定拦截规则(如果是拦截所有,静态资源也会被拦截)

1.编写一个拦截器实现HandlerInterceptor接口


package com.caq.admin.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
 * 登录检查
 * 配置拦截器要拦截哪些请求
 * 把配置放在容器中
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 目标方法执行前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        log.info("拦截的请求是"+requestURI);
        //        登录检查逻辑
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");
        if (loginUser != null){
            return true;
        }
        //拦截住,未登录,跳转到登录页
//        session.setAttribute("msg","Please login");
        request.setAttribute("msg","Please Login!!!");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }
    /**
     * 目标方法执行后
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}",modelAndView);
    }
    /**
     * 页面渲染之后
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion{}执行异常()",ex);
    }
}


2.拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)


指定拦截规则(如果是拦截所有,静态资源也会被拦截)


ackage com.caq.admin.config;
import com.caq.admin.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * 编写一个拦截器实现HandlerInterceptor接口
 * 拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
 * 指定拦截规则(如果是拦截所有,静态资源也会被拦截)
 */
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")//所有请求都会被拦截,包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");
    }
}


3.测试


我们访问main页面进行测试,原来写的拦截规则就可以去掉了,因为拦截器会帮我们自动拦截。


为了测试拦截器的常用方法的执行顺序我们在访问main页面的时候进行日志打印,查看我们访问main页面时所执行的方法


@GetMapping("/main.html")
    public String mainPage(HttpSession session,Model model){
        log.info("当前方法是:{}","mainPage");
//        //是否登录,拦截器,过滤器
//        Object loginUser = session.getAttribute("loginUser");
//        if (loginUser != null){
            return "main";
//        }else {
//            model.addAttribute("msg","请重新登陆");
//            return "login";
//        }
    }


测试一:直接访问main页面


我们直接访问main页面进行测试,发现拦截器帮我们拦截了下来并提示我们需要先登录才能访问


image.png


测试二:登录之后,查看拦截器方法的执行顺序


preHandle在目标方法执行前执行


目标方法执行


postHandle在目标方法执行后执行


afterCompletion在页面渲染后执行


image.png


3.2.10 文件上传功能


MultipartFile headerImg 允许上传单个文件


MultipartFile[] photos 允许上传多个文件


表单代码


<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>


控制层代码


package com.caq.admin.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
/**
 * 文件上传测试
 */
@Slf4j
@Controller
public class FormTestController {
    @GetMapping("/form_layouts")
    public String form_layouts(){
        return "form/form_layouts";
    }
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg")MultipartFile headerImg,
                         @RequestPart("photos")MultipartFile[] photos){
        log.info("上传的信息:email={},username={}.headerImg={},photos={}",email,username,headerImg.getSize(),photos.length);
        return "main";
    }
}


测试结果为


image.png



3.2.11 异常处理


官网给出的解释如下:


默认情况下,Spring Boot提供/error处理所有错误的映射


对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据


image.png



我们自定义错误页的时候,可以通过thymeleaf把错误信息打印到错误页面


<section class="error-wrapper text-center">
    <h1><img alt="" src="images/404-error.png"></h1>
    <h2 th:text="${status}">page not found</h2>
    <h3 th:text="${message}">We Couldn’t Find This Page</h3>
    <a class="back-btn" th:href="@{/main.html}"> Back To Home</a>
</section>

image.png


image.png


error/下的4xx,5xx页面会被自动解析


error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页


3.2.12 Web原生组件注入


简单API的使用,不用过多说明。会用即可


使用Servlet API


@ServletComponentScan(“com.caq.admin”) 指定原生的servlet组件都放在哪里


@WebServlet(urlPatterns = “/my”) 直接响应,没有经过spring的拦截器


@WebListener


@WebFilter(urlPatterns = {"/css/","/images/"})


只有结合这几个注解开发,我们的原生组件才能注入过来


日常开发推荐以上这种方式


使用RegistrationBean


@Configuration
public class MyRegistConfig {
    @Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();
        return new ServletRegistrationBean(myServlet,"/my","/my02");
    }
    @Bean
    public FilterRegistrationBean myFilter(){
        MyFilter myFilter = new MyFilter();
//        return new FilterRegistrationBean(myFilter,myServlet());
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }
    @Bean
    public ServletListenerRegistrationBean myListener(){
        MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}


3.2.13 嵌入式Servlet容器


默认支持的webServer


Tomcat, Jetty, or Undertow

ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器


<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>


原理


SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat


web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext


ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory``(Servlet 的web服务器工厂---> Servlet 的web服务器)


SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory


底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration


ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)


ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory


TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();


内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)


3.2.14 SpringBoot原理套路


引入场景starter – xxxxAutoConfiguration --导入xxx组件 - 绑定xxxProperties – 绑定配置文件项


3.3 数据访问


3.3.1 数据源的自动配置


我们发现只要导入jdbc场景,SpringBoot就会自动给我们导入HiKariDS


HiKariDS它是性能最好的数据源


导入JDBC场景


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


image.png


导入jdbc之后并没有导入数据库的驱动,是因为官方不知道我们要导入什么数据库


之后数据库的版本要和驱动版本对应


image.png


有两种修改方式


重新声明版本(maven的属性的就近优先原则)


直接依赖引入具体版本(maven的就近依赖原则)


image.png


配置文件


spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mp
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver


测试


@Slf4j
@SpringBootTest
class AdminSystemApplicationTests {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Test
    void contextLoads() {
        Long aLong = jdbcTemplate.queryForObject("select count(*) from user", Long.class);
        log.info("总数有"+aLong);
    }
}
2022-02-19 11:50:03.332  INFO 7200 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-02-19 11:50:03.804  INFO 7200 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2022-02-19 11:50:04.120  INFO 7200 --- [           main] c.caq.admin.AdminSystemApplicationTests  : Started AdminSystemApplicationTests in 4.476 seconds (JVM running for 6.428)
2022-02-19 11:50:04.521  INFO 7200 --- [           main] c.caq.admin.AdminSystemApplicationTests  : 总数有5


3.3.2 使用Druid数据源


修改默认的数据源步骤:


导入第三方数据源

设置配置类

测试


<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.17</version>
</dependency>


@Configuration
public class MyDataSourceConfig {
    //把组件的东西和配置文件的东西进行绑定
    @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource dataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }
}


@Slf4j
@SpringBootTest
class AdminSystemApplicationTests {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    DataSource dataSource;
    @Test
    void contextLoads() {
        Long aLong = jdbcTemplate.queryForObject("select count(*) from user", Long.class);
        log.info("总数有"+aLong);
        log.info("数据源类型:{}",dataSource.getClass());
    }
}
...........
2022-02-19 12:12:09.644  INFO 16124 --- [           main] c.caq.admin.AdminSystemApplicationTests  : 总数有5
2022-02-19 12:12:09.644  INFO 16124 --- [           main] c.caq.admin.AdminSystemApplicationTests  : 数据源类型:class com.alibaba.druid.pool.DruidDataSource


开启druid监控功能


步骤:


配置监控页,开启监控功能

sql查询进行测试


@Configuration
public class MyDataSourceConfig {
    //把组件的东西和配置文件的东西进行绑定
    @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        //加入监控功能
        druidDataSource.setFilters("stat");
        return druidDataSource;
    }
    /**
     * 配置druid的监控页
     * @return
     */
    @Bean
    public ServletRegistrationBean statViewServlet(){
        StatViewServlet statViewServlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet,"/druid/*");
        return registrationBean;
    }
}


@Autowired
JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/sql")
public String queryFormDb()   {
    Long aLong = jdbcTemplate.queryForObject("select count(*) from user", Long.class);
    return aLong.toString();
}


image.png


使用官方starter方式


使用官方的stater方式之后,我们就不用写配置类了。所有的功能设置我们都可以由配置文件来完成


所有功能开启参照如下步骤即可:


引入依赖

设置配置文件

测试

引入druid-starter


<dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.17</version>
    </dependency>


设置配置文件


写配置文件这种方式简化了我们去写配置类往spring容器中注入组件这种方式,因为它们有自动配置功能…


spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mp
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      aop-patterns: com.caq.admin.* # 监控SpringBean
      filters: stat,wall  # 底层开启功能,stat(sql监控),wall(防火墙)
      stat-view-servlet:  # 配置监控页功能
        enabled: true
        login-username: admin
        login-password: admin
        reset-enable: false
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
      filter:
        stat: # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          log-slow-sql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false

测试


功能都被完整的开启了


image.png


3.3.3 整合Mybatis操作


引入依赖:


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


image.png


原生操作


创建实体类

写mybatis主配置文件

写接口

写mapper映射文件

获得sqlsession

测试

SpringBoot整合Mybatis


释放了mybatis-config.xml文件,通过yml文件中的指定来替换。数据库映射mapper.xml文件通过在接口上写@Mapper注解和方法上写注解的方式实现


yml文件


mybatis:
#  通过yml配置文件的方式解放了mybatis-config.xml文件
  configuration:
    map-underscore-to-camel-case: true #开启驼峰命名


Mapper接口


package com.caq.admin.mapper;
import com.caq.admin.bean.Account;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
//通过写@Mapper注解的方式释放了写mapper.xml映射文件的方式
@Mapper
public interface AccountMapper {
    @Select("select * from account where id = #{id}")
    Account test(Long id);
}


package com.caq.admin.service;
import com.caq.admin.bean.Account;
import com.caq.admin.mapper.AccountMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountService {
    @Autowired
    AccountMapper accountMapper;
    public Account getAcc(Long id){
        return accountMapper.test(id);
    }
}


//

service层


package com.caq.admin.service;
import com.caq.admin.bean.Account;
import com.caq.admin.mapper.AccountMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountService {
    @Autowired
    AccountMapper accountMapper;
    public Account getAcc(Long id){
        return accountMapper.test(id);
    }
}


controller层


@Controller
@Slf4j
public class IndexController {
    @Autowired
    AccountService accountService;
    @ResponseBody
    @GetMapping("/account")
    public Account getAccount(@RequestParam("id") Long id){
        return accountService.getAcc(id);
    }
}


测试


image.png


3.3.4 整合Mybatis-Plus完成CRUD


引入mp依赖


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


编写mysql数据源信息


spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatis-plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl


编写实体类


package com.pyy.mp.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}


编写Mapper接口继承BaseMapper


@Repository和@Controller、@Service、@Component的作用差不多,都是把对象交给spring管理。


@Repository用在持久层的接口上,这个注解是将接口的一个实现类交给spring管理。


package com.pyy.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pyy.mp.pojo.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper extends BaseMapper<User> {
}


在我们学习MP的时候,直接写个mapper就好了。但是在我们的web开发阶段,我们将Mapper分成service接口和实现类。面向接口编程使程序更灵活,逻辑更清楚


面向接口编程是先把客户的业务逻辑线提取出来,作为接口,业务具体实现通过该接口的实现类来完成。当客户需求变化时,只需编写该业务逻辑的新的实现类,通过更改配置文件中该接口的实现类就可以完成需求,不需要改写现有代码,减少对系统的影响。(这个概念的理解很重要,先记着后面实践的时候会越来越清晰!)


package com.caq.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.caq.admin.bean.User;
public interface UserService extends IService<User> {
}
package com.caq.admin.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.caq.admin.bean.User;
import com.caq.admin.mapper.UserMapper;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> {
}


在SpringBoot启动类中添加MapperScan注解扫描Mapper类


因为MP是第三方技术,要和Spring整合的话需要交给Spring来管理。所以一定要加@MapperScan注解在SB启动类上


package com.pyy.mp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.pyy.mp")
@SpringBootApplication
public class MpApplication {
    public static void main(String[] args) {
        SpringApplication.run(MpApplication.class, args);
    }
}


测试


package com.caq.admin.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.caq.admin.bean.User;
import com.caq.admin.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@Controller
public class testController {
    //    @Autowired
//    UserService userService;
//    @Autowired
//    UserMapper userMapper;
    @Autowired
    UserServiceImpl userServiceImpl;
    @GetMapping("/")
    public String test1(@RequestParam(value = "pn",defaultValue = "1") Integer pn, Model model) {
//        List<User> list = userService.list(null);
//        model.addAttribute("users",list);
//        List<User> list = userMapper.selectList(null);
//        model.addAttribute("users",list);
        List<User> list = userServiceImpl.list(null);
//        model.addAttribute("users",list);
//        分页数据
        Page<User> userPage = new Page<>(pn,2);
//        分页查询的结果
        IPage<User> page = userServiceImpl.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 "a";
    }


<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <table>
        <thead>
        <tr>
            <th>#</th>
            <th>id</th>
            <th>name</th>
            <th>age</th>
            <th>email</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="user,stat:${page.records}">
            <td th:text="${stat.count}">序号</td>
            <td th:text="${user.id}">id</td>
            <td th:text="${user.name}">name</td>
            <td th:text="${user.age}">age</td>
            <td th:text="${user.email}">email</td> 
            <td>
                <a th:href="@{/user/delete/{id}(id=${user.id})}" type="button">删除</button>
            </td>
        </tr>
        </tbody>
    </table>
    当前第[[${page.current}]]页  总计 [[${page.pages}]]页  共[[${page.total}]]条记录
    <ul>
        <li><a href="#">← 前一页</a></li>
        <li th:each="num:${#numbers.sequence(1,page.pages)}">
            <a th:href="@{/(pn=${num})}">[[${num}]]</a>
        </li>
        <li><a href="#">下一页 → </a></li>
    </ul>
<!-- </body>th:class="${num == page.current?'active':''}" -->
</html>


为了提高访问速度,我写了个简陋的前端页面,但是实现了RD功能


完成CRUD功能需要很多thymeleaf知识点,不是我们学习重点,所以现写到RD功能


image.png


3.4 单元测试


3.4.1 Junit5的变化


Junit5是SpringBoot2.2.0版本后开始引入的默认单元测试默认库


image.png


以前:


@SpringBootTest + @RunWith(SpringTest.class)


SpringBoot整合Junit以后。


编写测试方法:@Test标注(注意需要使用junit5版本的注解)

Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚


3.4.2 Junit5常用注解


@Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试


@ParameterizedTest :表示方法是参数化测试,下方会有详细介绍


@RepeatedTest :表示方法可重复执行,下方会有详细介绍


@DisplayName :为测试类或者测试方法设置展示名称


@BeforeEach :表示在每个单元测试之前执行


@AfterEach :表示在每个单元测试之后执行


@BeforeAll :表示在所有单元测试之前执行


@AfterAll :表示在所有单元测试之后执行


@Tag :表示单元测试类别,类似于JUnit4中的@Categories


@Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore


@Timeout :表示测试方法运行如果超过了指定时间将会返回错误


@ExtendWith :为测试类或测试方法提供扩展类引用


import org.junit.jupiter.api.Test; //注意这里使用的是jupiter的Test注解!!


public class TestDemo {
  @Test
  @DisplayName("第一次测试")
  public void firstTest() {
      System.out.println("hello world");
  }


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4天前
|
缓存 前端开发 Java
【Spring】——SpringBoot项目创建
SpringBoot项目创建,SpringBootApplication启动类,target文件,web服务器,tomcat,访问服务器
|
1月前
|
监控 Java 数据库连接
详解Spring Batch:在Spring Boot中实现高效批处理
详解Spring Batch:在Spring Boot中实现高效批处理
166 12
|
1月前
|
安全 Java 测试技术
详解Spring Profiles:在Spring Boot中实现环境配置管理
详解Spring Profiles:在Spring Boot中实现环境配置管理
82 10
|
28天前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
89 5
|
2月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
109 2
|
2月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
249 1
|
2月前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
37 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
2月前
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
41 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
|
2月前
|
Java 测试技术 Spring
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
这篇文章介绍了Spring Boot中配置文件的语法、如何读取配置文件以及如何通过静态工具类读取配置文件。
192 0
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
|
2月前
|
SQL Java 数据库
Springboot+spring-boot-starter-data-jdbc实现数据库的操作
本文介绍了如何使用Spring Boot的spring-boot-starter-data-jdbc依赖来操作数据库,包括添加依赖、配置数据库信息和编写基于JdbcTemplate的数据访问代码。
242 2