Spring Boot学习笔记 [完结]

Spring Boot


初步了解:
SpringBoot就是一个JavaWeb框架

SpringBoot以约定大于配置的核心思想(maven、spring、springmvc、springboot…docker、k8s)

程序 = 数据结构 + 算法(集合框架map、list、数组);程序员

程序 = 面向对象 + 框架;码农


1、微服务

1.1 什么是微服务?

MVC 三层架构
MVVM 微服务架构

业务:service:userService:===> 模块!

springmvc,controller ==> 就负责提供接口!

http:rpc

1.2 单体应用架构

无论是ERP、CRM或者是其他什么系统,你都把数据库访问,web访问,等等各个功能放到一个war包内。

1.3 微服务架构

打破之前 all in one的架构方式,把每个功能独立出来

  • 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用
  • 大型分布式网络服务的调用,这部分有spring cloud来完成,实现分布式;
  • 在分布式中间,进行流式数据计算、批处理,我们有spring cloud data flow。
  • spring为我们像秦楚了整个从开始构建应用到大型分布式应用全流程方案。

Spring:the source for modern java


2. 第一个SpringBoot程序

  • jdk1.8
  • maven 3.6.1
  • springboot:最新版
  • IDEA

官方:提供了一个快速生成的网站!IDEA集成了这个网站

  • 可以在官网直接下载后,导入idea开发
  • 直接使用idea创建一个springboot项目(一般开发直接在IDEA中创建)

Application:程序的主入口
@SpringBootApplication
@SpringConfiguration
@Configuration
@Component


2.1、原理

2.2 原理初探

自动配置:

  1. pom.xml
  • spring-boot-dependencies:核心依赖在父工程中(spring-boot-starter-parent)
  • 我们在写或者引入一些Springboot依赖的时候,不需要指定版本,就因为有这些版本仓库
  1. 启动器
   	<dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter</artifactId>
       </dependency>
  • 启动器:说白了就是Springboot的启动场景;
  • 比如spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖!
  • springboot会将所有的功能场景,都变成一个个的启动器。
  • 我们要使用什么功能,就只需要找到对应的启动器就可以了 starter
  1. 主程序
//SpringBootApplication : 标注这个类是一个springboot的应用
@SpringBootApplication
public class Springboot01HelloworldApplication {

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

    }

}
  • 注解
@SpringBootConfiguration : springboot的配置
	@Configuration : spring配置类
		@Component : 说明这也是一个spring的组件
		
@EnableAutoConfiguration : 自动配置
	@AutoConfigurationPackage : 自动配置包
		@Import(AutoConfigurationPackages.Registrar.class) : 自动配置`包注册`
	@Import(AutoConfigurationImportSelector.class) : 自动配置导入选择
//获取所有的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	

获取候选的位置

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

META-INF/spring.factories : 自动配置的核心文件

Properties properties = PropertiesLoaderUtils.loadProperties(resource);
所有的资源加载到配置类中!

在这里插入图片描述

结论:

Springboot所有的自动配置都在启动的时候被扫描并加载:
spring.factories所有的自动配置类都在这里,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!

  1. springboot在启动的时候,从类路径下 /META-INF/spring.factories获取指定的值
  2. 将这些自动配置的类导入容器,自动配置类就会生效,帮我们进行自动配置
  3. 以前我们需要自动配置的东西,现在springboot帮我们做了!
  4. 整合JavaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.4.5.jar这个包下
  5. 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器
  6. 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration,JavaConfig!
  7. 有了自动配置类,免去了我们手动编写配置文件的工作。

关于SpringBoot,谈谈你的理解:

  • 自动装配
  • run()
四步骤:
	    1. 推断这个应用的类是普通的项目还是Web项目
        2. 查找并加载所有可用初始化器,设置到initializers属性中
        3. 找出所有的应用程序监听器,设置到listeners属性中
        4. 推断并设置main方法的定义类,找到运行的主类

全面接管SpringMVC的配置!


3、SpringBoot配置

3.1 yaml

yaml可以直接给实体类赋值

yaml练习

# k=v
# 对空格的要求十分严格!
# 普通的key-value

# 注入到我们的配置类中!



# 对象
#student:

# 行内写法

student: {name: swaelee,age: 3}

# 数组
pets:
  - cat
  - dog
  - pig

# pets: [cat,dog,pig]

yaml给属性赋值
此处属性与实体类上的@ConfigurationProperties(prefix = “guy1”)注解值相对应

guy1:
  name: SwaeLee${random.uuid}
  age: 12
  happy: true
  birth: 2021/04/19
  maps: {k1: v1,k2: v2,k3: v3}
  hello: lotie666
  lists:
    - code
    - music
    - game

  dog:
    name: ${guy1.hello:hilotie}_柯基666
    age: 3

在这里插入图片描述
松散绑定:
实体类:

private String firstName;

yaml:

dog:
  first-name: 阿黄
  age: 2

结论:

  • 配置yml和properties都可以获取到值,强烈推荐 yml
  • 如果我们在某个业务中,只要获取配置文件中的某个值,可以使用以下@value
  • 如果说,我们专门编写了一个JavaBean来和配置文件进行映射,就直接使用@configurationPeoperties,不要犹豫!

3.2 JSR303校验

在实体类上加@Validated注解
再对属性进行约束:
在这里插入图片描述
源码位置:Maven:validation:jakarta.validation-> validation->constraints


3.3多环境配置及配置文件位置(重点)

优先级问题:

  1. file: ./config/
  2. file:/
  3. classpath:/config/
  4. classpath:/ (默认)
server:
  port: 8081
spring:
  profiles:
    active: dev

---
server:
  port: 8082
spring:
  profiles: dev

---
server:
  port: 8083
spring:
  profiles: test

3.4 自动配置原理再理解

@SpringBootApplication -> @EnableAutoConfiguration -> @Import(AutoConfigurationImportSelector.class)

  1. -> getAutoConfigurationEntry() -> getCandidateConfigurations()
  2. -> getSpringFactoriesLoaderFactoryClass - > loadFactoryNames ->指向配置文件:META-INF/spring.factories
  • 表示这是一个配置类:
    @Configuration

  • 自动装配属性:
    EnableConfigurationProperties(ServerProperties.class)-> @ConfigurationProperties(prefix = “server”)中的属性与application.properties中的属性一一对应

  • Spring的底层注解:根据不同的条件,来判断当前配置或者类是否生效
    @ConditionalOnWebApplication

在我们这配置文件中能配置的东西,都存在一个固有的规律 xxxAutoConfiguration:默认值 xxxProperties 和 配置文件绑定,我们就可以使用自定义的配置了!

总结:

  1. SpringBoot启动 会加载大量的 自动配置类
  2. 我们看我们需要的功能 是否在 SpringBoot默认写好的自动配置类当中
  3. 我们再来看 这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在其中,我们就不需要再手动配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中(也就是yaml)指定指定这些属性的值即可。

核心:
xxxAutoConfiguration:自动配置类;给容器中添加组件
xxxProperties:封装配置文件中相关属性;

我们随意拿到一个,它肯定有一个activemq.properties和activemqConfiguration

spring:
  activemq:
    in-memory:

4、SpringBoot Web开发

jar :webapp
自动装配

  1. 创建应用,选择模块
  2. 配置文件替换原有的操作(注入的过程)
  3. 专注业务代码

思考:
springboot 到底帮我们配置了什么?

  • xxxxAutoConfiguratioin… 向容器中自动配置组件(不全配置,有条件筛选)

我们能不能进行修改?能修改那些东西?

  • xxxxProperties:自动配置类,装配配置文件中自定义的一些内容!

要解决的问题:

  • 导入静态资源,html、css…
  • 首页index
  • jsp,模板引擎 Thymeleaf
  • 装配拓展SpringMVC
  • 增删改查
  • 拦截器
  • 国际化

静态资源


		@Override
		protected void addResourceHandlers(ResourceHandlerRegistry registry) {
			super.addResourceHandlers(registry);
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			ServletContext servletContext = getServletContext();
			addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
			addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
				registration.addResourceLocations(this.resourceProperties.getStaticLocations());
				if (servletContext != null) {
					registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
				}
			});
		}

总结

  1. 在springboot中,我们可以使用以下方式处理静态资源
  • webjars
  • public,static,/**,resources localhost:8080/
  1. 优先级:resources > static(默认) > public

public下放一些公共的访问资源:一些js
static静态资源:图片
resources:上传的文件

4.1 首页如何定制

页面index.html通常放在templates,templates目录下的所有页面,只能通过controller来跳转
这个需要模板引擎的支持!

4.2 Thymeleaf

Thymeleaf官网文档:https://www.thymeleaf.org/

pom依赖

 <!--Thymeleaf ,我们都是基于3.x开发,spring1.x对应Thymelear 2.x-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

导入后,我们将html放在我们templates目录下即可

//在templates目录下的所有页面,只能通过controller来跳转
    //这个需要模板引擎的支持!
@Controller
public class IndexController {

    @RequestMapping("/TEST1")
    public String test(){

        return "index";

    }

}

Thymeleaf语法:

html导入

<html lang="en" xmlns:th="http://www.thymeleaf.org">
  • Simple expressions:
    • Variable Expressions: ${…} 变量
    • Selection Variable Expressions:*{…} 选择
    • Message Expressions: #{…} 消息
    • Link URL Expressions: @{…} url
    • Fragment Expressions: ~{…}(高级)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<p>
    2017首次落败在节目
</p>

<!--所有的html元素都可以被 thymeleaf替换接管: th:元素名-->
<div th:text="${msg1}"></div>
<div th:utext="${msg1}"></div>

<hr>

<p th:each="user:${users}" th:text="${user}"></p>
<!--
<p th:each="user:${users}">
    [[ ${user} ]]
</p>
-->

<p>
    我在外面等A$en选拔结束
</p>

</body>
</html>
@Controller
public class IndexController {

    @RequestMapping("/TEST1")
    public String test(Model model){
        model.addAttribute("msg1","<h5>hello,springboot,01</h5>");
        model.addAttribute("users", Arrays.asList("SwaeLee","Jessie Lee"));
        return "index";

    }

}

  • Conditional operators:
    • If-then: (if) ? (then)
    • If-then-else: (if) ? (then) : (else) 【大部分用它】
    • Default: (value) ?: (defaultvalue)

4.3 SpringMVC配置

  • ContentNegotiatingViewResolver 内容协商视图解析器
    我们在源码 WebMvcAutoConfiguration中 , 搜索ContentNegotiatingViewResolver方法。找到如下方法!

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    // ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}

@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
    if (requestedMediaTypes != null) {
        // 获取候选的视图对象
        List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
        // 选择一个最适合的视图对象,然后把这个对象返回
        View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }
    // .....
}

在doDispatch打断点Debug
发现在这里插入图片描述

/*全面扩展 Springmvc  所有的请求都会经过dispatchservlet
1. 在类上加注解 @Configuration
2. 实现接口 WebMvcConfigurer
如果你想自定义DIY一些功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配

 */

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    //public interface ViewResolver 实现了视图解析器接口的类,我们就可以把它看错视图解析器

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

    //自定义了一个自己的视图解析器MyViewResolver
    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
            return null;
        }
    }

}
  • 转换器和格式化器formatter
    我们在源码WebMvcAutoConfiguration发现Formatters可以在properties中配置

在这里插入图片描述

  • 扩展springmvc

自写start,写一个configuration和properties类,把两个类打到jar包里,放入spring-boot-autoconfigure,org,autoconfigure

//如果我们要扩展springmvc,官方建议我们这样去做
@Configuration
@EnableWebMvc  //这就是,导入了一个类DelegatingWebMvcConfiguration作用:从容器中获取所有的webmvcconfig,如果我们要接管Springmvc,千万不能加这个注解,加了这个注解,就崩盘了
public class MyMvcConfig implements WebMvcConfigurer {

    //视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/lee").setViewName("index");
    }
}

在springboot中,有非常多的 xxx Configuration 帮助我们进行扩展配置,只要看见了这个东西,我们就要注意了!只要在源码中看见了这个类,立马关注它扩展了什么功能

Demo

首页实现

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

   @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

注意html处,需要改的位置

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

		<link th:href="@{/css/sigin.css}" rel="stylesheet">
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
		<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
  • Variable Expressions:${…}
  • Selection Variable Expression:*{…}
  • Message Expressions:#{…}
  • Link URL Expressions:@{…}
  • Fragment Expressions:~{…}
  1. 首页配置:

    1. 注意点,所有页面的静态资源都需要使用thymeleaf接管;
    2. url:@{}
  2. 页面国际化 (首先我们确保IDEA中 File Encodings中配置编码全为UTF-8)

    1. 注意点:我们要配置i18n文件
    2. 我们如果需要在项目中进行按钮自动切换,我们需要自定义一个组件LocalResolver
      IDEA可视化配置
      在这里插入图片描述
      index首页中配置th:text="#{}"
			<!--<label class="sr-only" th:text="#{login.username}">Username</label>-->
			<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
			<!--<label class="sr-only" th:text="#{login.password}">Password</label>-->
			<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
	
			<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
			<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
			<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
		</form>

	</body>
</html>

自定义国际化组件

package com.lee.config;

import org.apache.tomcat.jni.Local;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
 * @author shkstart
 * @date 21.5.7 - 10:46
 */
public class MyLocaleResolver implements LocaleResolver {

    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        //获取请求中的语言参数
        String language = request.getParameter("l");

        Locale locale = Locale.getDefault();//如果没有就使用默认的;

        //如果请求的链接携带了国际化的参数
        if (!StringUtils.isEmpty(language)){
            //zh_CN
            String[] split = language.split("_");
            //国家,地区
            locale = new Locale(split[0], split[1]);
        }

        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

    }
}

在自定义MvcConfig中注册国际化组件

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

   @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }

    //自定义的国际化组件生效
    @Bean
    public LocaleResolver localeResolver(){
       return new MyLocaleResolver();
    }
}
  1. 登录功能+拦截 实现

loginController

@Controller
public class LoginController {

    @RequestMapping("/user/login")
    public String login(
            @RequestParam("username") String username,
            @RequestParam("password") String password,
            Model model){

        /*
        具体的业务
        判断
         */

        if (!StringUtils.isEmpty(username) && "123456".equals(password)){
            return "redirect:/main.html";
        }else {
            //给用户返回登录失败信息
            model.addAttribute("errorLogin","用户名或密码错误");
            return "index";
        }
    }

}

html注意处

<form class="form-signin" th:action="@{/user/login}">
 <!--如果error的值为空,则不显示消息:用户名或密码错误!-->
            <p style="color: darkblue;font-weight: bolder" th:text="${errorLogin}" th:if="${not #strings.isEmpty(errorLogin)}"></p>

注意,在MyMvcConfig掩盖真实数据

@Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
        registry.addViewController("/main.html").setViewName("dashboard");
    }

我们思考,此时不需要登录直接输入main.html网址也可以进入网站,那我们需要用拦截器进行拦截

在LoginController加入

 session.setAttribute("loginUser", username);

LoginHandlerInterceptor

package com.lee.config;

import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @author shkstart
 * @date 21.5.7 - 11:47
 */
public class LoginHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //登录成功后,应该有用户的session;

        Object loginUser = request.getSession().getAttribute("loginUser");// 31

        if (loginUser==null){//没有登录
            request.setAttribute("errorLogin", "没有权限,请先登录");
            request.getRequestDispatcher("/index.html").forward(request, response);
            return false;
        }else {
            return true;
        }


    }
}

在dashboard中取出用户名返回

[[${session.loginUser}]]
  1. 员工列表展示

     1. 提取公共部分
    
    <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="navbar">
    <nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
      <div th:replace="~{commons/commons::navbar}"></div>
        <div th:insert="~{commons/commons::sidebar}"></div>    
    
     2. 如果要传递参数,可直接使用()传参,接收判断即可
    
    	  <a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
    	   <div th:insert="~{commons/commons::sidebar(active='list.html')}"></div> 
    
     3. 列表循环展示
    
    	<table class="table table-striped table-sm">
    						<thead>
    							<tr>
    								<th>员工编号</th>
    								<th>员工姓名</th>
    								<th>员工邮件</th>
    								<th>员工性别</th>
    								<th>员工部门</th>
    								<th>员工生日</th>
                                    <th>操作</th>
    							</tr>
    						</thead>
    						<tbody>
    
                            <tr th:each="emp:${employees}">
                                <td th:text="${emp.getId()}"></td>
                                <td th:text="${emp.getLastName()}"></td>
                                <td th:text="${emp.getEmail()}"></td>
                                <td th:text="${emp.getGender()}==0?'':''"></td>
                                <td th:text="${emp.department.getDepartmentName()}"></td>
                                <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}"></td>
                                <td>
                                    <button class="btn btn-sm btn-primary">编辑</button>
                                    <button class="btn btn-sm btn-danger">删除</button>
                                </td>
                            </tr>
    						</tbody>
    					</table>
    
  2. 添加员工

    1. 按钮提交
    2. 跳转到添加页面
    3. 添加员工成功
    4. 返回首页
      Employee
package com.lee.controller;

import com.lee.dao.DepartmentDao;
import com.lee.dao.EmployeeDao;
import com.lee.pojo.Department;
import com.lee.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;
import java.util.Collection;

/**
 * @author shkstart
 * @date 21.5.8 - 9:48
 */
@Controller
public class EmployeeController {

    //Controller层调Service层
    @Autowired
    EmployeeDao employeeDao;
    @Autowired
    DepartmentDao departmentDao;

    @RequestMapping("/emps")
    public String list(Model model){
        Collection<Employee> employees = employeeDao.getAll();
        model.addAttribute("employees",employees);

        return "employee/list";
    }

    @RequestMapping("/emp")
    public String toAddPage(Model model){
        //查出所有部门的信息
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);
        return "employee/add";
    }

    @PostMapping("/emp")
    public String addEmp(Employee employee){
        //添加的操作 forward
        System.out.println("保存的信息=" + employee);
        employeeDao.add(employee);//调用底层业务方法保存员工信息
        return "redirect:/emps";
    }

    //到员工修改页面
    @GetMapping("/emp/{id}") //href对应GetMapping
    public String toUpdatePage(@PathVariable("id")Integer id,Model model){
        //查出原来的数据
        Employee employee = employeeDao.getEmployeeGetId(id);
        model.addAttribute("emp",employee);
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);

        return "employee/update";
    }


    @PostMapping("/updateEmp")
    public String updateEmp(Employee employee){

        employeeDao.add(employee);

        return "redirect:/emps";
    }

    //删除员工
    @RequestMapping("/delemp/{id}")
    public String deleteEmp(@PathVariable("id")Integer id){
        employeeDao.delete(id);
        return "redirect:/emps";
    }

    //注销
    @RequestMapping("/user/logout")
    public String userLogout(HttpSession session){
        session.removeAttribute("loginUser");
        return "redirect:/index.html";
    }
}

dashboard.html

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
    <div th:replace="~{commons/commons::navbar}"></div>

		<div class="container-fluid">
			<div class="row">

                <!--传递参数给组件-->
                <div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
						<div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
							<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div>
						</div>
						<div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
							<div style="position:absolute;width:200%;height:200%;left:0; top:0"></div>
						</div>
					</div>
					<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
						<h1 class="h2">Dashboard</h1>
						<div class="btn-toolbar mb-2 mb-md-0">
							<div class="btn-group mr-2">
								<button class="btn btn-sm btn-outline-secondary">Share</button>
								<button class="btn btn-sm btn-outline-secondary">Export</button>
							</div>
							<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
                This week
              </button>
						</div>
					</div>

					<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;"></canvas>

					
				</main>
			</div>
		</div>

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" ></script>
		<script type="text/javascript" src="asserts/js/popper.min.js" ></script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js" ></script>

		<!-- Icons -->
		<script type="text/javascript" src="asserts/js/feather.min.js" ></script>
		<script>
			feather.replace()
		</script>

		<!-- Graphs -->
		<script type="text/javascript" src="asserts/js/Chart.min.js" ></script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		</script>

	</body>

</html>

list.html

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">

	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
		<div th:insert="~{commons/commons::navbar}"></div>

		<div class="container-fluid">
			<div class="row">
                <div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<h2><a class="btn btn-sm btn-success" th:href="@{emp}">添加员工</a> </h2>
					<div class="table-responsive">
						<table class="table table-striped table-sm">
							<thead>
								<tr>
									<th>员工编号</th>
									<th>员工姓名</th>
									<th>员工邮件</th>
									<th>员工性别</th>
									<th>员工部门</th>
									<th>员工生日</th>
                                    <th>操作</th>
								</tr>
							</thead>
							<tbody>

                            <tr th:each="emp:${employees}">
                                <td th:text="${emp.getId()}"></td>
                                <td th:text="${emp.getLastName()}"></td>
                                <td th:text="${emp.getEmail()}"></td>
                                <td th:text="${emp.getGender()}==0?'':''"></td>
                                <td th:text="${emp.department.getDepartmentName()}"></td>
                                <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')}"></td>
                                <td>
                                    <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
                                    <a class="btn btn-sm btn-danger" th:href="@{/delemp/}+${emp.id}">删除</a>
                                </td>
                            </tr>
							</tbody>
						</table>
					</div>
				</main>
			</div>
		</div>

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
		<script type="text/javascript" src="asserts/js/popper.min.js"></script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>

		<!-- Icons -->
		<script type="text/javascript" src="asserts/js/feather.min.js"></script>
		<script>
			feather.replace()
		</script>

		<!-- Graphs -->
		<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		</script>

	</body>

</html>

add.html

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">

	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
		<div th:insert="~{commons/commons::navbar}"></div>

		<div class="container-fluid">
			<div class="row">
                <div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<h2><a class="btn btn-sm btn-success" th:href="@{emp}">添加员工</a> </h2>

                    <form th:action="@{/emp}" method="post">
                        <div class="form-group">
                            <label>姓名</label>
                            <input type="text" name="lastName" class="form-control" placeholder="swae">
                        </div>
                        <div class="form-group">
                            <label>邮箱</label>
                            <input type="email" name="email" class="form-control" placeholder="xxx@qq.com">
                        </div>
                        <div class="form-group">
                            <label>性别</label><br>
                            <div class="form-check form-check-inline">
                                <input class="form-check-input" type="radio" name="gender" value="1">
                                <label class="form-check-label"></label>
                            </div>
                            <div class="form-check form-check-inline">
                                <input class="form-check-input" type="radio" name="gender" value="0">
                                <label class="form-check-label"></label>
                            </div>
                            </div>

                            <div class="form-group">
                                <label>部门</label>
                                <select class="form-control" name="department.id">
                                    <!--我们在controller接收的是一个Employee,所以我们需要提交的是其中的一个属性!-->
                                        <option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
                                </select>
                            </div>

                            <div class="form-group">
                                <label>生日</label>
                                <input type="text" class="form-control"name="birth" placeholder="swaelee">
                            </div>
                                <button type=submit class=btn btn-primary>添加</button>
                            </form>

				</main>
			</div>
		</div>

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
		<script type="text/javascript" src="asserts/js/popper.min.js"></script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>

		<!-- Icons -->
		<script type="text/javascript" src="asserts/js/feather.min.js"></script>
		<script>
			feather.replace()
		</script>

		<!-- Graphs -->
		<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		</script>

	</body>

</html>

update.html

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">

	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
		<div th:insert="~{commons/commons::navbar}"></div>

		<div class="container-fluid">
			<div class="row">
                <div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
                    <form th:action="@{/updateEmp}" method="post">
                        <input type="hidden" name="id" th:value="${emp.getId()}">
                        <div class="form-group">
                            <label>姓名</label>
                            <input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="swaelee">
                        </div>
                        <div class="form-group">
                            <label>邮箱</label>
                            <input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="xxx@qq.com">
                        </div>
                        <div class="form-group">
                            <label>性别</label><br>
                            <div class="form-check form-check-inline">
                                <input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" name="gender" value="1">
                                <label class="form-check-label"></label>
                            </div>
                            <div class="form-check form-check-inline">
                                <input th:checked="${emp.getGender()==0}" class="form-check-input" type="radio" name="gender" value="0">
                                <label class="form-check-label"></label>
                            </div>
                            </div>

                            <div class="form-group">
                                <label>部门</label>
                                <select class="form-control" name="department.id">
                                    <!--我们在controller接收的是一个Employee,所以我们需要提交的是其中的一个属性!-->
                                        <option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
                                </select>
                            </div>

                            <div class="form-group">
                                <label>生日</label>
                                <input th:value="${emp.getBirth()}" type="text" class="form-control" name="birth">
                                <!--#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')-->
                            </div>
                                <button type=submit class=btn btn-primary>修改</button>
                            </form>

				</main>
			</div>
		</div>

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
		<script type="text/javascript" src="asserts/js/popper.min.js"></script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>

		<!-- Icons -->
		<script type="text/javascript" src="asserts/js/feather.min.js"></script>
		<script>
			feather.replace()
		</script>

		<!-- Graphs -->
		<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		</script>

	</body>

</html>

5、整合数据库

Data

5.1 整合JDBC

application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatisText?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver

测试 (HikariDataSource 号称 Java WEB 当前速度最快的数据源)

package com.lee;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class Springboot04DataApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        //查看一下默认的数据源
        System.out.println(dataSource.getClass());//class com.zaxxer.hikari.HikariDataSource

        //获得数据库链接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);//HikariProxyConnection@2085952212 wrapping com.mysql.cj.jdbc.ConnectionImpl@7d5508e0

        // xxx Template : SpringBoot已经配置好模板bean,拿来即用    CRUD
        // jdbc
        // redis

        //关闭
        connection.close();
    }

}

controller

package com.lee.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

/**
 * @author shkstart
 * @date 21.5.9 - 10:18
 */
@RestController
public class JDBCController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    // 查询数据库的所有信息
    // 没有实体类,数据库中的东西怎么获取? 万能Map
    @GetMapping("/userList")
    public List<Map<String,Object>> userList(){

        String sql = "select * from user";
        List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
        return list_maps;
    }

    @GetMapping("/addUser")
    public String addUser(){
        String sql = "insert into mybatisText.user(id,name,pwd) value(5,'菲董','999999')";
        jdbcTemplate.update(sql);
        return "insert-ok";
    }

    @GetMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id") int id){
        String sql = "update mybatisText.user set name=?,pwd=? where id="+id;

        //封装
        Object[] objects = new Object[2];
        objects[0] = "菲董2";
        objects[1] = "999998";
        jdbcTemplate.update(sql,objects);
        return "updateUser-okey";
    }

    @GetMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable("id") int id){
        String sql = "delete from mybatistext.user where id = ?";

        jdbcTemplate.update(sql,id)return "deleteUser-okey";

    }

}

5.2 整合Druid数据源

Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了C3P0、DBCP、PROXOOL等DB池的优点,同时加入了日志监控(纯天然)

作用:Druid 可以很好的监控 DB 池链接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。

pom

		<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.24</version>
        </dependency>
          <!--log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatisText?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
	 	initialSize: 5
   	 	minIdle: 5
    	maxActive: 20
   	 	maxWait: 60000
    	timeBetweenEvictionRunsMillis: 60000
    	minEvictableIdleTimeMillis: 300000
    	validationQuery: SELECT 1 FROM DUAL
    	testWhileIdle: true
    	testOnBorrow: false
    	testOnReturn: false
   	 	poolPreparedStatements: true
        filters: stat,wall,log4j
   	 	maxPoolPreparedStatementPerConnectionSize: 20
    	useGlobalDataSourcesStat: true
    	connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

测试

@Test
    void contextLoads() throws SQLException {
        //查看一下默认的数据源
        System.out.println(dataSource.getClass());//class com.alibaba.druid.pool.DruidDataSource

        //获得数据库链接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);//com.mysql.cj.jdbc.ConnectionImpl@18b8d173

        //关闭
        connection.close();
    }

DruidConfig

@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")// 与yml内datasourcel连接
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    // 后台监控功能 : web.xml,ServletRegistrationBean
    // 因为SpringBoot 内置了 servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
    @Bean
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");

        //后台需要有人登录,账号密码
        HashMap<String, String> initParameters = new HashMap<>();
        //增加配置 : 重点
        initParameters.put("loginUsername", "admin");//  登录key 是固定的 loginUsername
        initParameters.put("loginPassword", "123456");// loginPassword

        //允许谁可以访问 : 重点
        initParameters.put("allow", "localhost");

        //禁止谁能访问
//        initParameters.put("swaelee", "192.168.11.123");

        bean.setInitParameters(initParameters);// 设置初始化参数 : 重点
        return bean;
    }
    
	//filter
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();

        bean.setFilter(new WebStatFilter());

        //可以过滤哪些请求?
        HashMap<String , String> initParameters = new HashMap<>();

        initParameters.put("exclusions", "*.js,*.css,/druid/*");
        bean.setInitParameters(initParameters);

        return bean;
    }

}

Druid登陆页面:
在这里插入图片描述

5.3 整合Mybatis(重点)

整合包
mybatis-spring-boot-starter

pom

		 <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
  1. 导入包
  2. 配置文件
  3. mybatis配置
  4. 编写sql
  5. service 层调用 dao层
  6. controller 调用 service层

6、SpringSecurity(安全)(重点)

在web开发中,安全第一位!过滤器,拦截器~
功能性需求:否
做网站:安全应该在什么时候考虑?设计之初!

  • 漏洞,隐私泄露
  • 架构一旦确定

shiro、SpringSecurity:很像~ 除了类不一样,名字不一样;
认证,授权(vip1,vip2,vip3)
authentication access-control framework

  • 功能权限
  • 访问权限
  • 菜单权限
  • … 拦截器,过滤器:大量的原生代码~

6.1 Spring Security

导入pom
thymeleaf

 		<dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>

security

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

6.2 认证与授权

controller层

@Controller
public class RouterController {

    @RequestMapping({"/","/index","/index.html"})
    public String index(){
        return "index";
    }

    @RequestMapping("toLogin")
    public String toLogin(){
        return "views/login";
    }

    @RequestMapping("/level1/{id}")
    public String Level1(@PathVariable("id")int id){
        return "views/level1/" + id;
    }

    @RequestMapping("/level2/{id}")
    public String Level2(@PathVariable("id")int id){
        return "views/level2/" + id;
    }

    @RequestMapping("/level3/{id}")
    public String Level3(@PathVariable("id")int id){
        return "views/level3/" + id;
    }

}

SecurityConfig

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 链式编程
    // 授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有对应有权限的人才能访问

        //请求授权的规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");

        //没有权限默认会到登录页面,需要开启登录的页面
        // /login
        http.formLogin();
    }

    // 认证 Springboot 2.1.X 可以直接使用
    // 密码编码 : PasswordEncoder
    // 在Spring Security 5.0+ 新增了很多的加密方法
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 定制认证规则
        // 这些数据正常应该从数据库中读
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1").and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2").and()
                .withUser("swaelee").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3");


    }

}

6.3 注销及权限控制

Thymeleaf 与 Spring Security整合

 <!--thymeleaf整合包-->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity4</artifactId>
            <version>3.0.4.RELEASE</version>
        </dependency>
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 链式编程
    // 授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有对应有权限的人才能访问

        //请求授权的规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");

        //没有权限默认会到登录页面,需要开启登录的页面
        // /login
        http.formLogin();



        // 注销,跳到首页

        // 网站防止攻击 : get , post
        http.csrf().disable(); // 关闭csrf功能,logout失败可能存在的原因.
        http.logout().logoutSuccessUrl("/");
    }

    // 认证 Springboot 2.1.X 可以直接使用
    // 密码编码 : PasswordEncoder
    // 在Spring Security 5.0+ 新增了很多的加密方法
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 定制认证规则
        // 这些数据正常应该从数据库中读
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1").and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2").and()
                .withUser("swaelee").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3");


    }

}

index

<!--主容器-->
<div class="ui container">

    <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
        <div class="ui secondary menu">
            <a class="item"  th:href="@{/index}">首页</a>

            <!--登录注销-->
            <div class="right menu">

                <!--如果未登录,显示登录按钮-->
                <div sec:authorize="!isAuthenticated()">
                    <a class="item" th:href="@{/toLogin}">
                        <i class="address card icon"></i> 登录
                    </a>
                </div>



                <!--如果已登录:用户名,注销-->
                <div sec:authorize="isAuthenticated()">
                    <!--注销-->
                    <a class="item">
                        用户名: <spqn sec:authentication="name"></spqn>
                        <!--权  限: <span sec:authentication="principal.getPassword()"></span>-->
                    </a>
                </div>
                <div sec:authorize="isAuthenticated()">
                    <!--注销-->
                    <a class="item" th:href="@{/logout}">
                        <i class="sign-out icon"></i> 注销
                    </a>
                </div>

                <!--<a th:href="@{/usr/toUserCenter}">
                    <i class="address card icon"></i> admin
                </a>-->

            </div>
        </div>
    </div>

    <div class="ui segment" style="text-align: center">
        <h3>Spring Security Study by SwaeLee</h3>
    </div>

    <div>
        <br>
        <div class="ui three column stackable grid">
<!--菜单根据用户的角色动态实现-->
            <div class="column" sec:authorize="hasRole('vip1')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 1</h5>
                            <hr>
                            <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                            <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                            <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip2')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 2</h5>
                            <hr>
                            <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                            <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                            <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip3')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 3</h5>
                            <hr>
                            <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                            <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                            <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>
    
</div>

开启记住我功能:

// 开启记住我功能  cookie,默认保存两周
        http.rememberMe().rememberMeParameter("remember");

定制登录页

 // 定制登录页
        http.formLogin().loginPage("/toLogin");     
// http.formLogin().loginPage("/toLogin").usernameParameter().passwordParameter().loginProcessingUrl();

6.4 Shiro

  • Apache Shiro 是一个Java的安全(权限)框架。
  • Shiro可以完成,认证,授权,加密,会话管理,Web继承,缓存等。
  • 下载:http://shiro.apache.org/

在这里插入图片描述
可以在maven项目中测试:

导入pom

 <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.6.0</version>
        </dependency>

        <!--configure logging-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

resources下log4j.properties

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

log4j.logger.org.apache=WARN

log4j.logger.org.springframework=WARN

log4j.logger.org.apache.shiro=INFO

log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache,ehcache.EhCache=WARN

此处需导入ini、action tacker插件(Settings -> Plugins)
shiro.ini

#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
# =============================================================================
# Quickstart INI Realm configuration
#
# For those that might not understand the references in this file, the
# definitions are all based on the classic Mel Brooks' film "Spaceballs". ;)
# =============================================================================

# -----------------------------------------------------------------------------
# Users and their assigned roles
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
# -----------------------------------------------------------------------------
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

Quickstart.java



import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        currentUser.logout();

        System.exit(0);
    }
}
  1. 导入依赖
  2. 配置文件
  3. HelloWorld
    (略)

7、Swagger

学习目标:

  • 了解Swagger的作用和概念
  • 了解前后端分离
  • 在SpringBoot中集成Swagger

前后端分离
Vue + SpringBoot

后端时代:前端只用管理静态页面;html -> 后端。模板引擎 JSP -> 后端是主力

前后端分离式时代:

  • 后端:后端控制层,服务层,数据访问层
  • 前端: 前端控制层,视图层
    • 伪造后端数据,json。已经存在了,不需要后端,前端工程依旧能够跑起来
  • 前后端如何交互? -> API
  • 前后端相对独立,松耦合;
  • 前后端甚至可以部署在不同的服务器上;

产生一个问题:

  • 前后端集成联调,前端人员和后端人员。无法做到,即使协商,尽早解决,最终导致问题集中爆发

解决方案:

  • 首先指定schema[计划的提纲],实时更新最新API,降低集成的风险;
  • 早些年:指定word计划文档;
  • 前后端分离:
    • 前端测试后端接口:postman
    • 后端提供接口,需要实时更新最新的消息及改动!

7.1 Swagger

  • 号称世界上最流行的Api框架;
  • RestFul Api 文档在线自动生成工具 -> Api文档与API定义同步更新
  • 直接运行,可以在线测试API接口;
  • 支持多种语言:(Java,Php…)

官网:https://swagger.io/

在项目中使用Swagger需要 springbox;

  • swagger2
  • ui

7.2 SpringBoot 集成Swagger

  1. 新建一个SpringBoot - web项目
  2. 导入相关依赖
<dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
  1. 编写一个Hello工程
  2. 配置Swagger -> Config
@Configuration
@EnableSwagger2 // 开启Swagger2
public class SwaggerConfig {

    

}
  1. 测试运行
    在这里插入图片描述

7.3 配置Swagger

Swagger的bean实例 Docket;

@Configuration
@EnableSwagger2 // 开启Swagger2
public class SwaggerConfig {



    // 配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo());
    }

    // 配置Swagger信息=apiInfo
    private ApiInfo apiInfo(){

        // 作者信息
        Contact DEFAULT_CONTACT = new Contact("SwaeLee", "https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", "1398803640@qq.com");


        return new ApiInfo(
                "SwaggerAPI日志 By SwaeLee",
                "再小的帆也能远航",
                "v1.0",
                "https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", DEFAULT_CONTACT,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());

    }

}

7.4 Swagger配置扫描接口

Docker.select

// 配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(false)    // enable 是否启动swagger,如果为False,则swagger不能在浏览器中访问
                .select()
                // RequestHandlerSelectors,配置要扫描接口的方法
                // basePackage:指定要扫描的包
                // any(): 扫描全部
                // none():不扫描
                // withClassAnnotation():  扫描类上的注解,参数是一个注解的反射对象
                // withMethodAnnotation(): 扫描方法上的注解
                .apis(RequestHandlerSelectors.basePackage("com.lee.swagger.controller"))
                /* paths():过滤什么路径
                .paths(PathSelectors.ant("/lee/**"))*/
                .build(); // 工厂模式
    }

    // 配置Swagger信息=apiInfo
    private ApiInfo apiInfo(){

        // 作者信息
        Contact DEFAULT_CONTACT = new Contact("SwaeLee", "https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", "1398803640@qq.com");


        return new ApiInfo(
                "SwaggerAPI日志 By SwaeLee",
                "再小的帆也能远航",
                "v1.0",
                "https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", DEFAULT_CONTACT,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());

    }

问题:我只希望我的Swagger在生产环境中使用,在发布的时候不使用?

  • 判断是不是生产环境 flag = false
  • 注入enable(flag)
// 配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(Environment environment){

        // 设置要显示的Swagger环境
        Profiles profiles = Profiles.of("dev","test");

        //获取项目的环境:
        //通过environment.acceptsProfiles判断是否处在自己设定的环境当中
        boolean flag = environment.acceptsProfiles(profiles);


        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(flag)    // enable 是否启动swagger,如果为False,则swagger不能在浏览器中访问
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.lee.swagger.controller"))
//                .paths(PathSelectors.ant("/lee/**"))
                .build(); // 工厂模式
    }

7.5 配置API 文档的分组

  .groupName("SwaeLee")

问题:如何配置多个组?

	@Bean
    public Docket docket1(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("A");
    }
    @Bean
    public Docket docket2(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("B");
    }
    @Bean
    public Docket docket3(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("C");
    }

实体类配置;
只需要两步

  1. 写实体类
@ApiModel("用户类") // 给实体类加入文档注释
public class User {
    @ApiModelProperty("用户名")
    public String username;
    @ApiModelProperty("密码")
    public String password;
}
}
  1. 在Controller配置实体类接口,让返回值有实体类
    //只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中
    @PostMapping("/user")
    public User user(){
        return new User();
    }

在这里插入图片描述

Api注解

@Api("Hello控制类")
@RestController
public class HelloController {

    // /error
    @GetMapping(value = "/hello")
    public String hello(){
        return "hello";
    }

    //只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中
    @PostMapping("/user")
    public User user(){
        return new User();
    }

    // Operation接口,放在方法上的
    @ApiOperation("user控制类")
    @GetMapping("/hello2")
    public String hello(@ApiParam("用户名") String username){
        return "hello"+username;
    }

    @ApiOperation("Post控制类")
    @PostMapping("/postTest")
    public User postTest(@ApiParam("用户名") User user){
        int i = 5/0;
        return user;
    }
}

Swagger测试
不需要在客户端测试,很方便
在这里插入图片描述

总结:

  1. 我们可以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
  2. 接口文档实时更新
  3. 可以在线测试(前后端高效交互)

Swagger是一个优秀的工具

【注意点】在正式发布的时候,关闭Swagger!出于安全考虑,而且节省运行内存。


8、任务

异步任务~

定时任务~ timer

邮件发送~

1. 异步:
service

@Service
public class AsyncService {


    // 告诉Spring这是一个异步的方法
    @Async
    public void hello(){

        try{
            Thread.sleep(3000);
        } catch (InterruptedException e){
            e.printStackTrace();
        }

        System.out.println("数据正在处理...");

    }

}

controller

@RestController
public class AsyncController {

    @Autowired
    AsyncService asyncService;

    @RequestMapping("/hello09")
    public String hello(){
        asyncService.hello(); // 停止三秒,转圈~

        return "ok";
    }

}

在主类上加入@EnableAsync注解 // 开启异步注解功能

2. 邮件发送
导入pom

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

测试类

@Autowired
    JavaMailSenderImpl mailSender;


    @Test
    void contextLoads() {

        // 一个简单的邮件
        SimpleMailMessage mailMessage = new SimpleMailMessage();

        mailMessage.setSubject("123");
        mailMessage.setText("123123");

        mailMessage.setTo("xxx@qq.com");
        mailMessage.setFrom("xxx@qq.com");

        mailSender.send(mailMessage);

    }

    @Test
    void contextLoads2() throws MessagingException {

        // 一个复杂的邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        // 组装
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);

        helper.setSubject("晚上好");
        helper.setText("<p style='color:blue'>l came</p> " +
                "<p style='color:blue'> l saw</p>",true);

        // 附件
        helper.addAttachment("skrt.jpg", new File("C:\\1.jpg"));
//        helper.addAttachment("2.jpg", new File("C:\\1.jpg"));

        helper.setTo("xxx@qq.com");
        helper.setFrom("xxx@qq.com");

        mailSender.send(mimeMessage);

    }

    /**
     *
     * @param html:文本
     * @param subject:主题
     * @param text:正文
     * @throws MessagingException
     */
    public void sendMail(boolean html,String subject,String text) throws MessagingException{

        // 一个复杂的邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        // 组装
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,html);

        helper.setSubject(subject);
        helper.setText(text, true);

        // 附件
        helper.addAttachment("skrt.jpg", new File("C:\\1.jpg"));

        helper.setTo("xxx@qq.com");
        helper.setFrom("xxx@qq.com");

        mailSender.send(mimeMessage);

    }

3. 定时任务

TaskScheduler 任务调度者
TaskExecutor 任务执行者

@ EnableScheduling //开启定时功能的注解
@ Scheduled //什么时候执行

Cron表达式

@Service
public class ScheduledService {

    // 在一个特定的时间执行这个方法 Timer
    //cron 表达式
    //秒 分 时 日 月 周几
    /*
        30 10 5 * * ? 每天5点10分30秒执行一次
        0 10 5,8 * * ? 每天 5点和10点 执行一次
        0 0/5 8 * * ? 每隔5分钟执行一次
        0 15 10 ? * 1-6 每个月的,周一到周六,10.15分执行
     */
    @Scheduled(cron = "30 08 20 * * ?")
    public void hello(){
        System.out.println("hello,你被执行了~");
    }

}

9、Springboot整合Redis

标题 ‘7’ 处


10、分布式 Dubbo + Zookeeper

|阿里云|腾讯云|华为云

分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现时为了用廉价、普通的机器完成单个计算机无法完成的而计算、存储任务。其目的是利用更多的机器,处理更多的数据。
在这里插入图片描述

在这里插入图片描述
单一应用架构:
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:

  1. 性能扩展比较
  2. 难协同开发问题
  3. 不利于升级维护

分布式架构:
提高业务复用及整合的分布式服务框架(RPC)是关键。
在这里插入图片描述

流动计算架构:用于提高机器利用率的资源调度和治理中心(SOA)[Service Oriented Architecture]是关键

在这里插入图片描述

10.1 RPC

通信两种协议 : HTTP RPC

RPC(远程过程调用),是一种进程通信方式,是一种技术的思想,而不是规范。
它允许程序员调用另一个地址空间(通常共享网络的另一台机器上)的过程或函数,而不用程序显式编码这个远程调用的细节。
程序员无论调用本地的还是远程的函数,本质上编写的调用代码基本相同。

也就是说两台服务器A,B,一个应用部署再A服务器上,想要调用B服务器上应用提供的函数、方法,由于不再一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方法完成的需求,比如不同的系统件的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要再多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调用远程函数;

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

RPC两个核心模块:通讯,序列化。

序列化(方便数据传输):数据传输需要转换
在这里插入图片描述

底层式Netty

Dubbo(18年重启):高可用的RPC框架,专注于RPC。Dubbo 3.x Error Exception

HTTP SpringCloud(生态)

专业的事,交给专业的人来做。

10.2 Dubbo

Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

快速启动
官方文档:dubbo官方文档
Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需要 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展进行加载。

在这里插入图片描述

前台 中台(整合) 后台

服务提供者(Provider)

服务消费者(Consumer)

注册中心(Registry)

监控中心(Monitor)

zookeeper:hadoop hive

window下载安装aookeeper

注意在3.5以后,带bin的包才是我们要下载的包

  1. 下载地址:http://mirrors.hust.edu.cn/apache/zookeeper/
    运行
  2. 在这里插入图片描述
    可能遇到的问题:闪退!
    解决方案:编译zkServer.cmd文件末尾添加pause
    在conf中复制一份zoo_sample文件,名为zoo

window下载安装dubbo-admin
官网地址:https://github.com/apache/dubbo-admin/tree/master

  1. 在项目目录下打包dubbo(第一次失败的话再执行一次)
mvn clean package -Dmaven.test.skip=true
  1. 执行dubbo-admin\target 下的dubbo-admin-0.0.1-SNAPSHOT.jar
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

访问localhost:7001
默认密码root-root

zookeeper:注册中心
dubbo-admin:是一个监控管理后台~查看我们注册了哪些服务,哪些服务被消费了。
Dubbo:jar包

测试:

  1. 创建好两个不同的spring boot项目
    ProviderService
public interface TicketService {

    public String getTicket();

}

serviceImpl

//zookeeper:服务注册与发现

@Service //可以被扫描到,项目一启动就注册到Registry中心
@Component // 这里使用Dubbo后,尽量不用Service注解
public class TicketServiceImpl implements TicketService {
    @Override
    public String getTicket() {
        return "Swalee On Da Track";
    }
}
  1. 导入pom
<!--导入依赖 Dubbo + zookeeper-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.3</version>
        </dependency>

        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>
        <!--日志会冲突,需要排除slf4j-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.14</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
  1. 配置
    provider-application.properties
server.port=8001

# 服务应用名字
dubbo.application.name=provider-server
# 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
# 哪些服务要被注册
dubbo.scan.base-packages=com.lee.service

consumer-application.properties

server.port=8002

# 消费者去哪里拿服务暴漏自己的名字
dubbo.application.name=consumer-sever
# Registry中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
  1. 在Consumer中测试
    ConsumerService
@Service // 放到容器中
public class UserService {

    //想拿到provider-sever提供的票,要去注册中心拿到服务
    @Reference //引用,Pom坐标,可以定义路径相同的接口
    TicketService ticketService;

    public void buyTicket(){
        String ticket = ticketService.getTicket();
        System.err.println("在注册中心拿到的=>" + ticket);
    }

}

测试类:

@SpringBootTest
class ConsumerSeverApplicationTests {

    @Autowired
    UserService userService;

    @Test
    void contextLoads() {

        userService.buyTicket();

    }

}

步骤:
前提:zookeeper服务已开启!

  1. 提供者提供服务
    1. 导入依赖
    2. 配置注册中心的地址,以及服务发现名,和要扫描的包
    3. 在想要被注册的服务上面,增加一个注解@Service(Dubbo)
  2. 消费者如何消费
    1. 导入依赖
    2. 配置注册中心的地址,配置/暴漏自己的服务名
    3. 从远程注入服务@Reference

打个总结

回顾以前,架构

三层架构 + MVC

  架构 ---> 解耦

开发框架

Spring
IOC AOP

  IOC:控制反转

(容器)原来我们都是自己一步一步操作,现在交给容器,我们需要什么就去拿就可以了

  AOP:切面(本质,动态代理)

为了解决什么问题?
不影响业务本来的情况下,实现动态增加功能,大量应用在日志,事务…等等方面

  Spring是一个轻量级的Java开发框架

目的:解决企业开发的复杂性问题
Spring是春天,配置文件十分复杂。

SpringBoot

  新一代JavaEE的开发标准

SpringBoot并不是新东西,就是Spring的升级版!
  开箱即用! -> 拿过来就可以用!
  它帮助我们自动配置了非常多的东西,我们拿来即用!

特性:约定大于配置!

随着公司体系越来越大,用户越来越多。

微服务架构 —> 新架构


     模块化,功能化
     用户,支付,签到,娱乐…
     人数过于庞大:一台服务器解决不了;再增加一台服务器。(横向解决问题)
     假设A服务器占用98%资源,B服务器只占用10%–负载均衡


     将原来的整体项目,分成模块化,用户就是一个单独的项目,项目和项目之间需要通信,如何通信?
     用户非常多,而签到十分少。给用户多一点服务器,给签到少一点服务器。


微服务架构问题?
     分布式架构会遇到的四个核心问题?
     1. 这么多服务,客户端该如何去访问?
     2. 这么多服务,服务之间如何进行通信?
     3. 这么多服务,如何统一管理/治理?
     4. 服务挂了,怎么办?

解决方案:
     SpringCloud,是一套生态,就是来解决以上分布式架构的4个问题。
     想使用SpringCloud,必须要掌握SpringBoot,因为SpringCloud是基于SpringBoot;


     1. Spring Cloud NetFlix,出来了一套解决方案!一站式解决方案。我们都可以直接去这里拿
          Api网关,zuul组件
          Feign --> HttpClient --> HTTP的通信方式,同步并阻塞
          服务注册与发现,Eureka
          熔断机制,Hystrix


          2018年年底,NetFlix宣布无限期停止维护。生态不再维护,就会脱节。


     2. Apache Dubbo zookeeper,第二套解决系统
          API:没有,要么找第三方组件,要么自己实现。
          Dubbo是一个高性能的基于Java实现的 RPC通信框架。2.6.x
          服务注册与发现,zookeeper:Hadoop,Hiv
          熔断机制:没有,借助了Hystrix
          不完善,Dubbo 当前 Dubbo 3.0 将提供具备当代特征(如响应性编程)的相关支持,同时汲取阿里内部 HSF 的设计长处来shi


     3. SpringCloud Alibaba 一站式解决方案!


目前,又提出了一种方案:
          服务网格::下一代微服务标准,Server Mesh
          代表解决方案:istio(未来可能需要掌握)


解决的问题,万变不离其宗,一通百通!
     1. API网关,服务路由
     2. HTTP,RPC框架,异步调用
     3. 服务注册与发现,高可用
     4. 熔断机制,服务降级

基于这四个问题,开发一套解决方案,也叫SpringCloud微服务解决方案。


为什么要解决这个问题?本质:网络是不可靠的!

不要停下学习的脚步!


笔记参照-B站up主:狂神说

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值