Springboot基础学习

Springboot

1、介绍

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。

1.1、依赖方面

<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        
        <artifactId>spring-boot-starterartifactId>
    dependency>

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
        
    dependency>

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
    dependency>
dependencies>

1.2、修改Springboot启动图标

在resources下创建banner.txt文件,将要替换的内容复制进去即可

       _
       \`*-.
        )  _`-.
       .  : `. .
       : _   '  \
       ; *` _.   `*-._
       `-.-'          `-.
         ;       `       `.
         :.       .        \
         . \  .   :   .-'   .
         '  `+.;  ;  '      :
         :  '  |    ;       ;-.
         ; '   : :`-:     _.`* ;
      .*' /  .*' ; .*`- +'  `*'
      `*-*   `*-*  `*-*'

1.3、Springboot自动装配原理分析

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FmkYqmZy-1659458588010)(F:\Paint\SpringBoot自动装配原理.png)]

Springboot的application.properties中配置

debug=true:(输出我们已经配置的组件)
Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)

2、JSR303校验

2.1、yaml语法

  1. 空格不能省略,键值对中间必须要有一个空格
  2. 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
  3. 属性和值的大小写都是十分敏感的。

2.2、yaml配置文件格式

server:
	prot: 8080

2.3、yaml配置bean属性

person:
  name: luffy${random.uuid}
  age: ${random.int}
  happy: false
  birth: 2020/12/2
#  hello: haha
  maps: {k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    firstName: ${person.hello:hehe}小黑
    age: 3

2.4、yaml绑定配置文件

@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
   	private Dog dog;
}

2.5、测试yaml

 @Autowired
    private Person person;
    @Test
    void contextLoads() {
        System.out.println(person);
    }

2.6、松散绑定

yml中的first-name = bean中的firstName

2.7、数据校验

@Validated
public class person{
    // JSR303常用校验	
    @NotNull(message="名字不能为空")
    private String userName;
    @Max(value=120,message="年龄最大不能查过120")
    private int age;
    @Email(message="邮箱格式错误")
    private String email;   
}


// 空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.
    
// Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
    
// 长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.

// 日期检查
@Past       验证 DateCalendar 对象是否在当前时间之前  
@Future     验证 DateCalendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则

2.8、application.yml可以安装的位置

优先级:

  • file:config/appplication.yml
  • file:application.yml
  • resources:/config/application.yml
  • resources:application.yml

2.9、yml多配置

server:
  port: 8080
# 选择使用哪些配置
spring:
  profiles:
    active: dev # 激活dev配置
---
server:
  port: 8081
spring:
  profiles: test
---
server:
  port: 8082
spring:
  profiles: dev

3、web开发

3.1、静态资源

源码

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
        return;
    }
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    // 第一种方式 webjars
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    // 第二种方式
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}

webjars方法(不推荐)

  1. 需要导入需要使用的静态资源例如Jquery的maven依赖
  2. 导入的静态资源在该依赖的META-INF/resources/webjars下
  3. 访问的路径跟随http://localhost:8080/webjars/就可以使用

推荐方法

/**:在http://localhost:8080/访问的静态资源会获取以下路径的静态资源

优先级排序:

  1. classpath:/META-INF/resources/
  2. classpath:/resources/
  3. classpath:/static/(默认创建)
  4. classpath:/public/

在application.properties自己配置静态资源路径,那么系统规定的路径则不生效

spring.mvc.static-path-pattern=/hello/,classpath:/luffy/

3.2、首页

在对应的静态资源路径下创建index.html即可

源码

private Resource getIndexHtml(String location) {
    return this.getIndexHtml(this.resourceLoader.getResource(location));
}

private Resource getIndexHtml(Resource location) {
    try {
        Resource resource = location.createRelative("index.html");
        if (resource.exists() && resource.getURL() != null) {
            return resource;
        }
    } catch (Exception var3) {
    }

    return null;
}
public interface ResourceLoader {
    String CLASSPATH_URL_PREFIX = "classpath:";

    Resource getResource(String location);

    @Nullable
    ClassLoader getClassLoader();
}

3.3、网页图标定制

新版本代码被更改

以下是旧版本的方法

  1. 找一张图片后缀名改为ico
  2. 放在public静态文件夹下即可
  3. 设置application.properties关闭默认图标的属性
    1. spring.mvc.favicon.enabled=false

具体看源码,这里不细说

3.4、thymeleaf模板引擎

thymeleaf模板引擎用的界面都放在templates下

源码

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

	private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
	// 前缀
	public static final String DEFAULT_PREFIX = "classpath:/templates/";
	// 后缀
	public static final String DEFAULT_SUFFIX = ".html";
	...
}

使用步骤

  1. 引入依赖

    
    <dependency>
        <groupId>org.thymeleafgroupId>
        <artifactId>thymeleaf-spring5artifactId>
    dependency>
    <dependency>
        <groupId>org.thymeleaf.extrasgroupId>
        <artifactId>thymeleaf-extras-java8timeartifactId>
    dependency>
    
  2. Controller层

    @RequestMapping("/test")
    public String test(Model model){
        model.addAttribute("msg","hello,springboot!");
        return "test";
    }
    
  3. html界面

    
    <div th:text="${msg}">div>
    

3.5、thymeleaf语法

转义语法和遍历操作

  1. Controller层

    @Controller
    public class TestController {
    
    
        @RequestMapping("/test")
        public String test(Model model){
            model.addAttribute("msg1","

    hello,springboot!

    "
    ); model.addAttribute("msg2","

    hello,springboot!

    "
    ); model.addAttribute("users", Arrays.asList("路飞","海贼王")); return "test"; } }
  2. html界面

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
    head>
    <body>
    
    
    
    <div th:text="${msg1}">div>
    
    
    <div th:utext="${msg2}">div>
    
    
    <h1 th:each="users:${users}" th:text="${users}">h1>
    
    
    
    
    
    body>
    html>
    

常用语法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sB9Ro99p-1659458588012)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220622101953141.png)]

3.6、SpringMVC扩展

Sprint-boot一定要学会看源码,不会看源码一定学不好

在Config文件夹下创建一个个Config类,实现implements WebMvcConfigurer,重写一些方法,达到MVC的扩展

不能再使用@EnableWebMvc,因为这个注解里最终也实现了相同的类,继续使用会使他们全部失效

/**
 *  扩展SpringMVC:实现WebMvcConfigurer接口,重写某些方法让我们实现一些新的配置
 *      注意:不能再使用@EnableWebMvc,这个方法会让扩展方法失效
 */
@Configuration
public class MyMVCConfig implements WebMvcConfigurer {
    /**
     * 视图跳转 访问/ssl,跳转到config.html
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/ssl").setViewName("config");
    }
}

4、项目实战

4.1、准备工作

pojo

@Data
//有参构造
@AllArgsConstructor
//无参构造
@NoArgsConstructor
public class Department {
    private Integer id;
    private String  departmentName;
}
@Data
@NoArgsConstructor
public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender;//0女1男
    private Department department;
    private Date birth;


    public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.department = department;
        this.birth = new Date();
    }
}

dao

@Repository
public class EmployeeDao {

    private static Map<Integer, Employee> employees = null;

    @Autowired
    private DepartmentDao departmentDao;

    static {
        employees = new HashMap<Integer,Employee>();//创建一个员工表
        employees.put(1001,new Employee(1001,"马嘉祺","[email protected]",1,new Department(101,"教学部")));
        employees.put(1002,new Employee(1002,"丁程鑫","[email protected]",1,new Department(101,"教学部")));
        employees.put(1003,new Employee(1003,"宋亚轩","[email protected]",1,new Department(102,"市场部")));
        employees.put(1004,new Employee(1004,"刘耀文","[email protected]",1,new Department(102,"市场部")));
        employees.put(1005,new Employee(1005,"张真源","[email protected]",1,new Department(103,"教研部")));
        employees.put(1006,new Employee(1006,"严浩翔","[email protected]",1,new Department(104,"运营部")));
        employees.put(1007,new Employee(1007,"贺峻霖","[email protected]",1,new Department(104,"运营部")));

    }
    //主键自增
    private static Integer initId = 1008;

    //增加一个员工
    public void save(Employee employee){
        if (employee.getId()==null){
            employee.setId(initId++);
        }
        employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
        employees.put(employee.getId(),employee);
    }

    //查询所有员工
    public Collection<Employee> getAll(){
        return employees.values();
    }

    //根据ID查询员工
    public Employee getEmployeeById(Integer id){
        return employees.get(id);
    }

    //根据员工id删除员工
    public void delete(Integer id){
        employees.remove(id);
    }
}
@Repository
public class DepartmentDao {

    private static Map<Integer, Department> departments = null;
    static {
        departments = new HashMap<Integer, Department>();//创建一个部门表

        departments.put(101,new Department(101,"教学部"));
        departments.put(102,new Department(102,"市场部"));
        departments.put(103,new Department(103,"教研部"));
        departments.put(104,new Department(104,"运营部"));
        departments.put(105,new Department(105,"财务部"));

    }
    //获得所有部门信息
    public Collection<Department> getDepartments(){
        return  departments.values();
    }
    
    //通过id得到部门
    public Department getDepartmentById(Integer id){
        return departments.get(id);
    }
}

4.2、首页实现

MyMvcConfig

//如果你想diy一些定制化的功能,只要写这个组件,然后将它交给Springboot,Springboot就会帮我们自动装配!
//扩展Springmvc   dispatchservlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

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

}

关闭thymeLeaf缓存

#关闭thymeLeaf缓存
spring.thymeleaf.cache=false

前端界面

404.html

DOCTYPE html>

<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 Bootstraptitle>
		
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		
		<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>
		<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
			<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company namea>
			<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
			<ul class="navbar-nav px-3">
				<li class="nav-item text-nowrap">
					<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign outa>
				li>
			ul>
		nav>

		<div class="container-fluid">
			<div class="row">
				<nav class="col-md-2 d-none d-md-block bg-light sidebar">
					<div class="sidebar-sticky">
						<ul class="nav flex-column">
							<li class="nav-item">
								<a class="nav-link active" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-home">
										<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">path>
										<polyline points="9 22 9 12 15 12 15 22">polyline>
									svg>
									Dashboard <span class="sr-only">(current)span>
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-file">
										<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z">path>
										<polyline points="13 2 13 9 20 9">polyline>
									svg>
									Orders
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-shopping-cart">
										<circle cx="9" cy="21" r="1">circle>
										<circle cx="20" cy="21" r="1">circle>
										<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6">path>
									svg>
									Products
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-users">
										<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">path>
										<circle cx="9" cy="7" r="4">circle>
										<path d="M23 21v-2a4 4 0 0 0-3-3.87">path>
										<path d="M16 3.13a4 4 0 0 1 0 7.75">path>
									svg>
									Customers
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-bar-chart-2">
										<line x1="18" y1="20" x2="18" y2="10">line>
										<line x1="12" y1="20" x2="12" y2="4">line>
										<line x1="6" y1="20" x2="6" y2="14">line>
									svg>
									Reports
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-layers">
										<polygon points="12 2 2 7 12 12 22 7 12 2">polygon>
										<polyline points="2 17 12 22 22 17">polyline>
										<polyline points="2 12 12 17 22 12">polyline>
									svg>
									Integrations
								a>
							li>
						ul>

						<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
              <span>Saved reportsspan>
              <a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                <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-plus-circle"><circle cx="12" cy="12" r="10">circle><line x1="12" y1="8" x2="12" y2="16">line><line x1="8" y1="12" x2="16" y2="12">line>svg>
              a>
            h6>
						<ul class="nav flex-column mb-2">
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Current month
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Last quarter
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Social engagement
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Year-end sale
								a>
							li>
						ul>
					div>
				nav>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<h1>404h1>
				main>
			div>
		div>

		
		
		<script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}" >script>
		<script type="text/javascript" th:src="@{/js/popper.min.js}" >script>
		<script type="text/javascript" th:src="@{/js/bootstrap.min.js}" >script>

		
		<script type="text/javascript" th:src="@{/js/feather.min.js}" >script>
		<script>
			feather.replace()
		script>

		
		<script type="text/javascript" th:src="@{/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>

dashboard.html

DOCTYPE html>

<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 Bootstraptitle>
		
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		
		<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>
		<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
			<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company namea>
			<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
			<ul class="navbar-nav px-3">
				<li class="nav-item text-nowrap">
					<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign outa>
				li>
			ul>
		nav>

		<div class="container-fluid">
			<div class="row">
				<nav class="col-md-2 d-none d-md-block bg-light sidebar">
					<div class="sidebar-sticky">
						<ul class="nav flex-column">
							<li class="nav-item">
								<a class="nav-link active" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-home">
										<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">path>
										<polyline points="9 22 9 12 15 12 15 22">polyline>
									svg>
									Dashboard <span class="sr-only">(current)span>
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-file">
										<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z">path>
										<polyline points="13 2 13 9 20 9">polyline>
									svg>
									Orders
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-shopping-cart">
										<circle cx="9" cy="21" r="1">circle>
										<circle cx="20" cy="21" r="1">circle>
										<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6">path>
									svg>
									Products
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-users">
										<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">path>
										<circle cx="9" cy="7" r="4">circle>
										<path d="M23 21v-2a4 4 0 0 0-3-3.87">path>
										<path d="M16 3.13a4 4 0 0 1 0 7.75">path>
									svg>
									Customers
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-bar-chart-2">
										<line x1="18" y1="20" x2="18" y2="10">line>
										<line x1="12" y1="20" x2="12" y2="4">line>
										<line x1="6" y1="20" x2="6" y2="14">line>
									svg>
									Reports
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-layers">
										<polygon points="12 2 2 7 12 12 22 7 12 2">polygon>
										<polyline points="2 17 12 22 22 17">polyline>
										<polyline points="2 12 12 17 22 12">polyline>
									svg>
									Integrations
								a>
							li>
						ul>

						<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
              <span>Saved reportsspan>
              <a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                <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-plus-circle"><circle cx="12" cy="12" r="10">circle><line x1="12" y1="8" x2="12" y2="16">line><line x1="8" y1="12" x2="16" y2="12">line>svg>
              a>
            h6>
						<ul class="nav flex-column mb-2">
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Current month
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Last quarter
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Social engagement
								a>
							li>
							<li class="nav-item">
								<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
									<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Year-end sale
								a>
							li>
						ul>
					div>
				nav>

				<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">Dashboardh1>
						<div class="btn-toolbar mb-2 mb-md-0">
							<div class="btn-group mr-2">
								<button class="btn btn-sm btn-outline-secondary">Sharebutton>
								<button class="btn btn-sm btn-outline-secondary">Exportbutton>
							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>

		
		
		<script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}" >script>
		<script type="text/javascript" th:src="@{/js/popper.min.js}" >script>
		<script type="text/javascript" th:src="@{/js/bootstrap.min.js}" >script>

		
		<script type="text/javascript" th:src="@{/js/feather.min.js}" >script>
		<script>
			feather.replace()
		script>

		
		<script type="text/javascript" th:src="@{/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>

index.html

DOCTYPE html>
<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>Signin Template for Bootstraptitle>
		
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
		
		<link th:href="@{/css/signin.css}" rel="stylesheet">
	head>

	<body class="text-center">
		<form class="form-signin" action="dashboard.html">
			<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
			<h1 class="h3 mb-3 font-weight-normal">Please sign inh1>
			<label class="sr-only">Usernamelabel>
			<input type="text" class="form-control" placeholder="Username" required="" autofocus="">
			<label class="sr-only">Passwordlabel>
			<input type="password" class="form-control" placeholder="Password" required="">
			<div class="checkbox mb-3">
				<label>
          <input type="checkbox" value="remember-me"> Remember me
        label>
			div>
			<button class="btn btn-lg btn-primary btn-block" type="submit">Sign inbutton>
			<p class="mt-5 mb-3 text-muted">© 2017-2018p>
			<a class="btn btn-sm">中文a>
			<a class="btn btn-sm">Englisha>
		form>

	body>

html>

list.html

DOCTYPE html>

<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 Bootstraptitle>
        
        <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

        
        <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>
        <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
            <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company namea>
            <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
            <ul class="navbar-nav px-3">
                <li class="nav-item text-nowrap">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign outa>
                li>
            ul>
        nav>

        <div class="container-fluid">
            <div class="row">
                <nav class="col-md-2 d-none d-md-block bg-light sidebar">
                    <div class="sidebar-sticky">
                        <ul class="nav flex-column">
                            <li class="nav-item">
                                <a class="nav-link active" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                    <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-home">
                                        <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">path>
                                        <polyline points="9 22 9 12 15 12 15 22">polyline>
                                    svg>
                                    Dashboard <span class="sr-only">(current)span>
                                a>
                            li>
                            <li class="nav-item">
                                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                    <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-file">
                                        <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z">path>
                                        <polyline points="13 2 13 9 20 9">polyline>
                                    svg>
                                    Orders
                                a>
                            li>
                            <li class="nav-item">
                                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                    <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-shopping-cart">
                                        <circle cx="9" cy="21" r="1">circle>
                                        <circle cx="20" cy="21" r="1">circle>
                                        <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6">path>
                                    svg>
                                    Products
                                a>
                            li>
                            <li class="nav-item">
                                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                    <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-users">
                                        <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">path>
                                        <circle cx="9" cy="7" r="4">circle>
                                        <path d="M23 21v-2a4 4 0 0 0-3-3.87">path>
                                        <path d="M16 3.13a4 4 0 0 1 0 7.75">path>
                                    svg>
                                    Customers
                                a>
                            li>
                            <li class="nav-item">
                                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                    <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-bar-chart-2">
                                        <line x1="18" y1="20" x2="18" y2="10">line>
                                        <line x1="12" y1="20" x2="12" y2="4">line>
                                        <line x1="6" y1="20" x2="6" y2="14">line>
                                    svg>
                                    Reports
                                a>
                            li>
                            <li class="nav-item">
                                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                    <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-layers">
                                        <polygon points="12 2 2 7 12 12 22 7 12 2">polygon>
                                        <polyline points="2 17 12 22 22 17">polyline>
                                        <polyline points="2 12 12 17 22 12">polyline>
                                    svg>
                                    Integrations
                                a>
                            li>
                        ul>

                        <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
                            <span>Saved reportsspan>
                            <a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                <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-plus-circle"><circle cx="12" cy="12" r="10">circle><line x1="12" y1="8" x2="12" y2="16">line><line x1="8" y1="12" x2="16" y2="12">line>svg>
                            a>
                        h6>
                        <ul class="nav flex-column mb-2">
                            <li class="nav-item">
                                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                    <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-file-text">
                                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
                                        <polyline points="14 2 14 8 20 8">polyline>
                                        <line x1="16" y1="13" x2="8" y2="13">line>
                                        <line x1="16" y1="17" x2="8" y2="17">line>
                                        <polyline points="10 9 9 9 8 9">polyline>
                                    svg>
                                    Current month
                                a>
                            li>
                            <li class="nav-item">
                                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                    <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-file-text">
                                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
                                        <polyline points="14 2 14 8 20 8">polyline>
                                        <line x1="16" y1="13" x2="8" y2="13">line>
                                        <line x1="16" y1="17" x2="8" y2="17">line>
                                        <polyline points="10 9 9 9 8 9">polyline>
                                    svg>
                                    Last quarter
                                a>
                            li>
                            <li class="nav-item">
                                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                    <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-file-text">
                                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
                                        <polyline points="14 2 14 8 20 8">polyline>
                                        <line x1="16" y1="13" x2="8" y2="13">line>
                                        <line x1="16" y1="17" x2="8" y2="17">line>
                                        <polyline points="10 9 9 9 8 9">polyline>
                                    svg>
                                    Social engagement
                                a>
                            li>
                            <li class="nav-item">
                                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                                    <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-file-text">
                                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
                                        <polyline points="14 2 14 8 20 8">polyline>
                                        <line x1="16" y1="13" x2="8" y2="13">line>
                                        <line x1="16" y1="17" x2="8" y2="17">line>
                                        <polyline points="10 9 9 9 8 9">polyline>
                                    svg>
                                    Year-end sale
                                a>
                            li>
                        ul>
                    div>
                nav>

                <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
                    <h2>Section titleh2>
                    <div class="table-responsive">
                        <table class="table table-striped table-sm">
                            <thead>
                                <tr>
                                    <th>#th>
                                    <th>Headerth>
                                    <th>Headerth>
                                    <th>Headerth>
                                    <th>Headerth>
                                tr>
                            thead>
                            <tbody>
                                <tr>
                                    <td>1,001td>
                                    <td>Loremtd>
                                    <td>ipsumtd>
                                    <td>dolortd>
                                    <td>sittd>
                                tr>
                                <tr>
                                    <td>1,002td>
                                    <td>amettd>
                                    <td>consecteturtd>
                                    <td>adipiscingtd>
                                    <td>elittd>
                                tr>
                                <tr>
                                    <td>1,003td>
                                    <td>Integertd>
                                    <td>nectd>
                                    <td>odiotd>
                                    <td>Praesenttd>
                                tr>
                                <tr>
                                    <td>1,003td>
                                    <td>liberotd>
                                    <td>Sedtd>
                                    <td>cursustd>
                                    <td>antetd>
                                tr>
                                <tr>
                                    <td>1,004td>
                                    <td>dapibustd>
                                    <td>diamtd>
                                    <td>Sedtd>
                                    <td>nisitd>
                                tr>
                                <tr>
                                    <td>1,005td>
                                    <td>Nullatd>
                                    <td>quistd>
                                    <td>semtd>
                                    <td>attd>
                                tr>
                                <tr>
                                    <td>1,006td>
                                    <td>nibhtd>
                                    <td>elementumtd>
                                    <td>imperdiettd>
                                    <td>Duistd>
                                tr>
                                <tr>
                                    <td>1,007td>
                                    <td>sagittistd>
                                    <td>ipsumtd>
                                    <td>Praesenttd>
                                    <td>mauristd>
                                tr>
                                <tr>
                                    <td>1,008td>
                                    <td>Fuscetd>
                                    <td>nectd>
                                    <td>tellustd>
                                    <td>sedtd>
                                tr>
                                <tr>
                                    <td>1,009td>
                                    <td>auguetd>
                                    <td>sempertd>
                                    <td>portatd>
                                    <td>Mauristd>
                                tr>
                                <tr>
                                    <td>1,010td>
                                    <td>massatd>
                                    <td>Vestibulumtd>
                                    <td>laciniatd>
                                    <td>arcutd>
                                tr>
                                <tr>
                                    <td>1,011td>
                                    <td>egettd>
                                    <td>nullatd>
                                    <td>Classtd>
                                    <td>aptenttd>
                                tr>
                                <tr>
                                    <td>1,012td>
                                    <td>tacititd>
                                    <td>sociosqutd>
                                    <td>adtd>
                                    <td>litoratd>
                                tr>
                                <tr>
                                    <td>1,013td>
                                    <td>torquenttd>
                                    <td>pertd>
                                    <td>conubiatd>
                                    <td>nostratd>
                                tr>
                                <tr>
                                    <td>1,014td>
                                    <td>pertd>
                                    <td>inceptostd>
                                    <td>himenaeostd>
                                    <td>Curabiturtd>
                                tr>
                                <tr>
                                    <td>1,015td>
                                    <td>sodalestd>
                                    <td>ligulatd>
                                    <td>intd>
                                    <td>liberotd>
                                tr>
                            tbody>
                        table>
                    div>
                main>
            div>
        div>

        
        
        <script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}">script>
        <script type="text/javascript" th:src="@{/js/popper.min.js}">script>
        <script type="text/javascript" th:src="@{/js/bootstrap.min.js}">script>

        
        <script type="text/javascript" th:src="@{/js/feather.min.js}">script>
        <script>
            feather.replace()
        script>

        
        <script type="text/javascript" th:src="@{/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>

4.3、页面国际化

实现页面中文和英文语言切换!!!

  1. 在Resource下创建对应的切换配置文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e512MW5a-1659458588014)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220702135818964.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vq7dzPgf-1659458588014)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220702140228426.png)]

  1. 在application.properties中写好配置文件的路径
#配置文件的位置
spring.messages.basename=i18n.login
  1. 在前端页面中使用语法取值
<form class="form-signin" action="dashboard.html">
    <img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
    <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">h1>
    <label class="sr-only">Usernamelabel>
    <input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
    <label class="sr-only">Passwordlabel>
    <input type="password" class="form-control" th:placeholder="#{login.password}" required="">
    <div class="checkbox mb-3">
        <label>
            <input type="checkbox" value="remember-me" th:text="#{login.remember}">
        label>
    div>
    <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">button>
    <p class="mt-5 mb-3 text-muted">© 2017-2018p>
    <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')}">Englisha>
form>
  1. 编写MyLocalResolver类

    public class MyLocalResolver implements LocaleResolver {
    
        //解析请求
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            String  language = request.getParameter("l");
            Locale locale = Locale.getDefault();//如果没有就使用默认的
            //如果请求链接携带了国际化的参数
            if (!StringUtils.isEmpty(language)){
                String[] split =  language.split("_");
                locale =  new Locale(split[0],split[1]);
            }
            return locale;
        }
    
        @Override
        public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    
        }
    }
    
  2. 在mymvcconfig中注册

    @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 MyLocalResolver();
        }
    
    }
    

注意:需要先在idea设置的file encoding中将编码语言全部设置为utf-8,否则可能会乱码。

4.4、登录功能实现

  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("msg","用户名或者密码错误");
                return "index";
            }
        }
    }
    
  2. 在MyMvcConfig.java中配置虚拟请求

    registry.addViewController("main.html").setViewName("dashboard");
    
  3. 在前端页面用P标签接收错误信息

    <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p>
    

4.5、登录拦截器

  1. LoginHandlerInterceptor

    public class LoginHandlerInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            //登陆成功之后应该有用户的session
            Object loginUser = request.getSession().getAttribute("loginUser");
    
            if(loginUser == null){
                 request.setAttribute("msg","没有权限,请先登录");
                 request.getRequestDispatcher("/index.html").forward(request,response);
                 return false;
             }else{
                 return true;
             }
    
        }
    }
    
  2. MyMvcConfig配置拦截器的bean

    //登录拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/index.html","/","/user/login","/css/**","/img/**","/img/**");
    }
    
  3. 在登录请求中配置session

    @RequestMapping("/user/login")
    public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model, HttpSession session){
        if(!StringUtils.isEmpty(username) && "123456".equals(password)){
            session.setAttribute("loginUser",username);
            //登陆成功之后重定向页面
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","用户名或者密码错误");
            return "index";
        }
    }
    
  4. 在首页前端登录成功取到session展示

    [[${session.loginUser}]]
    

4.6、展示员工页面

  1. 在templates创建子文件夹(emp放子页面,commons是碎片化管理)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pJMJqU3I-1659458588015)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220703103230189.png)]

  2. commons的代码(主要区分了顶部和侧边的代码,方便共用)

    • th:fragment=“××”(定义碎片)
    • th:insert=“~{commons/commons::××}”(使用碎片)
    
    <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
        <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]a>
        <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
        <ul class="navbar-nav px-3">
            <li class="nav-item text-nowrap">
                <a class="nav-link" th:href="@{/user/signOut}">注销a>
            li>
        ul>
    nav>
    
    
    
    
    <nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
        <div class="sidebar-sticky">
            <ul class="nav flex-column">
                <li class="nav-item">
                    <a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/main.html}">
                        <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-home">
                            <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">path>
                            <polyline points="9 22 9 12 15 12 15 22">polyline>
                        svg>
                        首页 <span class="sr-only">(current)span>
                    a>
                li>
                <li class="nav-item">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                        <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-file">
                            <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z">path>
                            <polyline points="13 2 13 9 20 9">polyline>
                        svg>
                        Orders
                    a>
                li>
                <li class="nav-item">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                        <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-shopping-cart">
                            <circle cx="9" cy="21" r="1">circle>
                            <circle cx="20" cy="21" r="1">circle>
                            <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6">path>
                        svg>
                        Products
                    a>
                li>
                <li class="nav-item">
                    <a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
                        <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-users">
                            <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">path>
                            <circle cx="9" cy="7" r="4">circle>
                            <path d="M23 21v-2a4 4 0 0 0-3-3.87">path>
                            <path d="M16 3.13a4 4 0 0 1 0 7.75">path>
                        svg>
                        员工管理
                    a>
                li>
                <li class="nav-item">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                        <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-bar-chart-2">
                            <line x1="18" y1="20" x2="18" y2="10">line>
                            <line x1="12" y1="20" x2="12" y2="4">line>
                            <line x1="6" y1="20" x2="6" y2="14">line>
                        svg>
                        Reports
                    a>
                li>
                <li class="nav-item">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                        <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-layers">
                            <polygon points="12 2 2 7 12 12 22 7 12 2">polygon>
                            <polyline points="2 17 12 22 22 17">polyline>
                            <polyline points="2 12 12 17 22 12">polyline>
                        svg>
                        Integrations
                    a>
                li>
            ul>
    
    
    
            <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
                <span>Saved reportsspan>
                <a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <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-plus-circle"><circle cx="12" cy="12" r="10">circle><line x1="12" y1="8" x2="12" y2="16">line><line x1="8" y1="12" x2="16" y2="12">line>svg>
                a>
            h6>
    
    
            <ul class="nav flex-column mb-2">
                <li class="nav-item">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                        <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-file-text">
                            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
                            <polyline points="14 2 14 8 20 8">polyline>
                            <line x1="16" y1="13" x2="8" y2="13">line>
                            <line x1="16" y1="17" x2="8" y2="17">line>
                            <polyline points="10 9 9 9 8 9">polyline>
                        svg>
                        Current month
                    a>
                li>
                <li class="nav-item">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                        <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-file-text">
                            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
                            <polyline points="14 2 14 8 20 8">polyline>
                            <line x1="16" y1="13" x2="8" y2="13">line>
                            <line x1="16" y1="17" x2="8" y2="17">line>
                            <polyline points="10 9 9 9 8 9">polyline>
                        svg>
                        Last quarter
                    a>
                li>
                <li class="nav-item">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                        <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-file-text">
                            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
                            <polyline points="14 2 14 8 20 8">polyline>
                            <line x1="16" y1="13" x2="8" y2="13">line>
                            <line x1="16" y1="17" x2="8" y2="17">line>
                            <polyline points="10 9 9 9 8 9">polyline>
                        svg>
                        Social engagement
                    a>
                li>
                <li class="nav-item">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                        <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-file-text">
                            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
                            <polyline points="14 2 14 8 20 8">polyline>
                            <line x1="16" y1="13" x2="8" y2="13">line>
                            <line x1="16" y1="17" x2="8" y2="17">line>
                            <polyline points="10 9 9 9 8 9">polyline>
                        svg>
                        Year-end sale
                    a>
                li>
            ul>
        div>
    nav>
    
  3. 在dashboard和list下加入使用碎片的方法,使代码复用性提高

    • sidebar和topbar是碎片的名称
    • 后面跟的括号是传参名active,当在dashboard下点击的时候传参main.html使侧边栏高亮
    • 高亮对应代码:th:class=“${active==‘main.html’?‘nav-link active’:‘nav-link’}”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nSYPFiQd-1659458588016)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220703103626117.png)]

  1. EmployeeController(点击员工管理模块时查询员工信息并返回到list.html展示)

    @RequestMapping("/emps")
    public String list(Model model){
        Collection<Employee> employees = employeeDao.getAll();
        model.addAttribute("emps",employees);
        return "emp/list";
    }
    
  2. list页面遍历员工信息

    <table class="table table-striped table-sm">
    
        <thead>
            <tr>
                <th>idth>
                <th>lastNameth>
                <th>emailth>
                <th>genderth>
                <th>departmentth>
                <th>birthth>
                <th>操作th>
            tr>
        thead>
    
        <tbody>
            <tr th:each="emp:${emps}">
                <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:ss')}">     td>
                <td>
                    <button class="btn btn-sm btn-primary">editbutton>
                    <button class="btn btn-sm btn-danger">delbutton>
                td>
            tr>
        tbody>
    
    table>
    

4.7、增加员工

  1. 在用户展示页面添加一个添加员工的按钮

    
    <h2>
       
       <a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工a>
    h2>
    
  2. 编写增加页面add.html

    LastName
    Email
    Gender
    Department
    Birth
    添加
  3. EmployeeController添加两个请求

    //点击添加按钮请求到添加页面,因为添加需要部门信息,所以先查询部门信息作为参数传递
    @RequestMapping("/emp")
    public String toAddpage(Model model){
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);
        return "emp/add";
    }
    
    
    //点击添加成功按钮,将添加的数据保存,然后重定向到查询用户页面的请求
    @PostMapping("/emp")
    public String toAddEmp(Employee employee){
        employeeDao.save(employee);
        return "redirect:/emps";
    }
    
  4. 修改日期格式

    # 修改日期格式,默认是yyyy/MM/dd
    spring.mvc.date-format=yyyy-MM-dd
    

4.7、修改员工

  1. 编辑按钮传参

    <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.getId()}">编辑a>
    
  2. EmployeeController的两个请求

    //接收传递的id,然后查询部门信息,跳转到修改界面
    @GetMapping("/emp/{id}")
    public String toUpdateEmpPage(@PathVariable("id") Integer id, Model model) {
        Employee employee = employeeDao.getEmployeeById(id);
        model.addAttribute("emp", employee);
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments", departments);
        return "emp/update";
    }
    
    //将修改的信息进行修改后重定向到查询用户页面的请求
    @PostMapping("/updateEmp")
    public String updateEmp(Employee employee) {
        employeeDao.save(employee);
        return "redirect:/emps";
    }
    
  3. 编写修改页面

    <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>LastNamelabel>
                <input name="lastName" th:value="${emp.getLastName()}" type="text" class="form-control"
                       id="exampleInputEmail1" placeholder="lastName">
            div>
    
            <div class="form-group">
                <label>Emaillabel>
                <input name="email" th:value="${emp.getEmail()}" type="email"
                       class="form-control" id="exampleInputPassword1" placeholder="email">
            div>
    
            <div class="form-group">
                <label>Genderlabel>
                <div class="form-check form-check-inline">
                    <input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio"
                           value="1" name="gender"/>
                    <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"
                           value="0" name="gender"/>
                    <label class="form-check-label">label>
                div>
            div>
    
            <div class="form-group">
                <label>Departmentlabel>
                
                <select name="department.id" class="form-control">
                    
                    <option th:each="dept:${departments}"
                            th:selected="${dept.getId()==emp.getDepartment().getId()}"
                            th:text="${dept.getDepartmentName()}"
                            th:value="${dept.getId()}">option>
                select>
            div>
    
            <div class="form-group">
                <label>Birthlabel>
                
                <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" name="birth" type="text"
                       class="form-control" placeholder="yyyy-MM-dd">
            div>
    
            <button type="submit" class="btn btn-default">修改button>
    
        form>
    main>
    
    

4.8、删除员工

  1. 删除按钮传参

    <a class="btn btn-sm btn-danger" th:href="@{/deleteEmp/}+${emp.getId()}">删除a>
    
  2. EmployeeController的一个请求

    @GetMapping("/deleteEmp/{id}")
    public String deleteEmp(@PathVariable("id") Integer id) {
        employeeDao.delete(id);
        return "redirect:/emps";
    }
    

4.9、错误页面

  • springBoot封装了,在template中创建error包,下面放404.html,会自动出错时跳转到该页面

4.10、注销

  1. 注销按钮

    <a class="nav-link" th:href="@{/user/signOut}">注销a>
    
  2. LoginController的一个请求

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

5、springboot整合

5.1、整合JDBC

  1. 加入依赖

    <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-jdbcartifactId>
            dependency>
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <scope>runtimescope>
    
  2. 使用yaml连接数据库配置

    spring:
      datasource:
        username: root
        password: root
        #?serverTimezone=UTC解决时区的报错
        url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        driver-class-name: com.mysql.cj.jdbc.Driver
    
  3. 测试整合JDBC环境

    @SpringBootTest
    class Springboot04DataApplicationTests {
    
        //DI注入数据源
        @Autowired(required = false)
        DataSource dataSource;
    
        @Test
        public void contextLoads() throws SQLException {
            //看一下默认数据源
            System.out.println(dataSource.getClass());
            //获得连接
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            //关闭连接
            connection.close();
        }
    
    }
    
  4. 测试CRUD

    @RestController
    public class JDBCController {
    
        @Autowired(required = false)
        JdbcTemplate jdbcTemplate;
    
        // 查询数据库的所有信息
        // 没有实体类,获取数据库的东西,怎么获取? Map
        @GetMapping("/userList")
        public List<Map<String,Object>> userList() {
            String sql = "select * from mybatis.user";
            List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
            return list_maps;
        }
    
        @GetMapping("/addUser")
        public String addUser() {
            String sql = "insert into mybatis.user(id, name, pwd) values(8,'小明','123456')";
            jdbcTemplate.update(sql);
            return "update-ok";
        }
    
        @GetMapping("/updateUser/{id}")
        public String updateUser(@PathVariable("id") int id) {
            String sql = "update mybatis.user set name  = ?,pwd = ? where id = " + id;
            //封装
            Object[] objects = new Object[2];
    
            objects[0] = "小明2";
            objects[1] = "aaaaaaa";
    
            jdbcTemplate.update(sql,objects);
            return "update-ok";
        }
    
        @GetMapping("/deleteUser/{id}")
        public String deleteUser(@PathVariable("id") int id) {
            String sql = "delete from mybatis.user where id = ?";
            jdbcTemplate.update(sql,id);
            return "update-ok";
        }
    }
    

5.2、整合Druid

  • Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
  1. 添加 Druid 和log4j数据源依赖

    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druid-spring-boot-starterartifactId>
        <version>1.1.24version>
    dependency>
    <dependency>
        <groupId>log4jgroupId>
        <artifactId>log4jartifactId>
        <version>1.2.17version>
    dependency>
    
  2. yaml配置文件添加Druid的需求

    type: com.alibaba.druid.pool.DruidDataSource
    
    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    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:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
  3. 编写Druid配置类

    @Configuration
    public class DruidConfig {
        
        
        //将自定义的Druid配置进IOC容器
        @ConfigurationProperties(prefix = "spring.datasource")
        @Bean
        public DataSource getDataSource() {
            return new DruidDataSource();
        }
    
    
        //配置 Druid 监控管理后台的Servlet;
        //内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
        @Bean
        public ServletRegistrationBean statViewServlet() {
            ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
    
            // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
            // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
            Map<String, String> initParams = new HashMap<>();
            initParams.put("loginUsername", "root"); //后台管理界面的登录账号
            initParams.put("loginPassword", "admin"); //后台管理界面的登录密码
    
            //后台允许谁可以访问
            //initParams.put("allow", "localhost"):表示只有本机可以访问
            //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
            initParams.put("allow", "");
            //deny:Druid 后台拒绝谁访问
            //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问
    
            //设置初始化参数
            bean.setInitParameters(initParams);
            return bean;
        }
    
    
        //配置 Druid 监控 之  web 监控的 filter
        //WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
        @Bean
        public FilterRegistrationBean webStatFilter() {
            FilterRegistrationBean bean = new FilterRegistrationBean();
            bean.setFilter(new WebStatFilter());
    
            //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
            Map<String, String> initParams = new HashMap<>();
            initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
            bean.setInitParameters(initParams);
    
            //"/*" 表示过滤所有请求
            bean.setUrlPatterns(Arrays.asList("/*"));
            return bean;
        }
    
    
    }
    

配置完druid类之后访问http://localhost:8080/druid/可以进入druid的监控,账号密码为配置类里指定的,进入之后可以查看每条sql的信息

5.3、整合mybatis

  1. 导入依赖

    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>2.1.2version>
    dependency>
    
  2. 配置application.yaml

    # 整合数据源
    spring:
      datasource:
        username: root
        password: root
        url: jdbc:mysql://localhost:3306/mybatis?userUnicode=true&chacacterEncoding=utf-8&serverTimezone=UTC
        # Springboot使用 com.mysql.cj.jdbc.Driver 针对Mysql8以上,5可能会有bug
        driver-class-name: com.mysql.cj.jdbc.Driver
    
    
    # 整合mybatis
    mybatis:
      type-aliases-package: com.example.springboot05mybatis.pojo
      # 解决绑定异常:mapper.xml最好和接口的包名路径一致
      mapper-locations: classpath:mybatis/mapper/*.xml
    
  3. 实体类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    
  4. mapper层接口

    @Mapper// 这个注解表示了这是一个mapper的注解类
    @Repository
    public interface UserMapper {
    
        List<User> getAllUser();
    
        User getUserById(@Param("id") int id);
    
        void addUser(User user);
    
        void deleteUser(@Param("id")int id);
    
        User updateUser(User user);
    }
    
  5. mapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.springboot05mybatis.mapper.UserMapper">
    
        <select id="getAllUser" resultType="User">
            SELECT * FROM mybatis.user;
        select>
    
        <select id="getUserById" parameterType="int" resultType="User">
            select * from mybatis.user where id=#{id};
        select>
    
        <insert id="addUser" parameterType="User">
            insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd})
        insert>
    
        <update id="updateUser" parameterType="User">
            update mybatis.user set name=#{name},pwd=#{pwd} where id = #{id}
        update>
    
        <delete id="deleteUser" parameterType="int">
            delete from mybatis.user where id = #{id}
        delete>
    mapper>
    

6、SpringSecurity

6.1、SpringSecurity介绍

SpringSecurity 是针对spring项目的安全框架,也是springboot底层默认的安全模块技术选型,它可以实现强大的Web安全机制,只需要少数的spring-boot--spring-security依赖,进行少量的配置,就可以实现强大的安全管理

  • WebSecurityConfigurerAdapter:自定义Security策略

  • AuthenticationManagerBuilder:自定义认证策略

  • @EnableWebSecurity:开启WebSecurity模式【@Enablexxxx:开启某个功能】

SpringSecurity的两个主要目标

  • 认证方式:Authentication
  • 权限:Authorization

6.1、Springboot整合SpringSecurity环境搭建

  1. 导入依赖

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-securityartifactId>
    dependency>
    <dependency>
        <groupId>org.thymeleaf.extrasgroupId>
        <artifactId>thymeleaf-extras-java8timeartifactId>
    dependency>
    
    <dependency>
        <groupId>org.thymeleaf.extrasgroupId>
        <artifactId>thymeleaf-extras-springsecurity5artifactId>
        <version>3.0.4.RELEASEversion>
    dependency>
    
  2. RouterController

    @Controller
    public class RouterController {
    
        @RequestMapping({"/", "/index"})
        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;
        }
    }
    
  3. 导入静态资源

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0VXsgx4T-1659458588017)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220706154208100.png)]

  4. MySecurityConfig安全配置类编写

    @EnableWebSecurity
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
    
        // url授权:  HttpSecurity
        @Override
        protected void configure(HttpSecurity security) throws Exception {
            // 首页所有人可以访问,但是功能也只有对有权限的人可以访问
            security
                .authorizeRequests() // 认证请求
                .antMatchers("/").permitAll()//所有人都可以进入这个请求
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3")
                ;
    
    
            // 自带登录页面,http://localhost:8080/login
            // 定制登录页,loginPage("/toLogin")
            // 指定表单提交url:loginProcessingUrl("/login")
            security.formLogin().loginPage("/toLogin")
                .usernameParameter("username").passwordParameter("password")
                .loginProcessingUrl("/login");
    
            // 防止网站工具     get post
            security.csrf().disable();//关闭csrf功能,登录失败存在的原因
    
            // 开启记住我功能:本质就是记住一个cookies,默认保存2周
            security.rememberMe().rememberMeParameter("remember");
    
            // 开启注销功能,源码http://localhost:8080/logout,并且注销成功后跳转到/的Controller
            security.logout().logoutSuccessUrl("/");
        }
    
    
    
    
        // 用户认证:AuthenticationManagerBuilder
        // SpringSecurity5 以后默认需要新郑密码密码加密方式
        @Override
        public void configure(AuthenticationManagerBuilder builder) throws Exception {
            // 内存中测试数据
            builder.inMemoryAuthentication()   // SpringSecurity5 以后默认需要新郑密码密码加密方式
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("luffy").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1")
                .and()
                .withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2", "vip3")
                ;
        }
    }
    
    
  5. index.html修改thymeleaf和security搭建的代码

    
    <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" th:href="@{/logout}">
                
                用户名:<span sec:authentication="name">span>
                
            a>
            <a class="item" th:href="@{/logout}">
                <i class="sign-out card icon">i> 注销
            a>
        div>
    div>
    
  6. login.html

    <form th:action="@{/login}" method="post">
        <div class="field">
            <label>Usernamelabel>
            <div class="ui left icon input">
                <input type="text" placeholder="Username" name="username">
                <i class="user icon">i>
            div>
        div>
        <div class="field">
            <label>Passwordlabel>
            <div class="ui left icon input">
                <input type="password" name="password">
                <i class="lock icon">i>
            div>
        div>
        <div class="field">
            <div class="field">
                <input type="checkbox" name="remember"> 记住我
            div>
        div>
        <input type="submit" class="ui blue submit button"/>
    form>
    

6、Shiro

Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。

  • 核心三大对象:用户Subject, 管理用户SecurityManager, 连接数据Realms
    • Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
    • SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
    • Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。

6.1、Shiro快速开始

  1. 导入依赖

    
    <dependency>
        <groupId>com.github.theborakompanionigroupId>
        <artifactId>thymeleaf-extras-shiroartifactId>
        <version>2.0.0version>
    dependency>
    
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
    dependency>
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druidartifactId>
        <version>1.1.22version>
    dependency>
    <dependency>
        <groupId>log4jgroupId>
        <artifactId>log4jartifactId>
        <version>1.2.17version>
    dependency>
    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>2.1.2version>
    dependency>
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
    dependency>
    
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-springartifactId>
        <version>1.5.3version>
    dependency>
    
    <dependency>
        <groupId>org.thymeleafgroupId>
        <artifactId>thymeleaf-spring5artifactId>
    dependency>
    <dependency>
        <groupId>org.thymeleaf.extrasgroupId>
        <artifactId>thymeleaf-extras-java8timeartifactId>
    dependency>
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-coreartifactId>
        <version>1.5.3version>
    dependency>
    
  2. 编写shiro.ini

    [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
    
  3. 编写Quickstart类

    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):
            DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
            IniRealm iniRealm=new IniRealm("classpath:shiro.ini");
            defaultSecurityManager.setRealm(iniRealm);
    
    
            // 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(defaultSecurityManager);
    
            // 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);
        }
    }
    
  4. 运行Quickstart类打印shiro基础信息

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ybDkGtR-1659458588017)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220707155611813.png)]

6.2、springboot整合shiro环境搭建

  1. 导入依赖

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
    dependency>
    
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-springartifactId>
        <version>1.4.1version>
    dependency>
    
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
    dependency>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
    dependency>
    
    <dependency>
        <groupId>log4jgroupId>
        <artifactId>log4jartifactId>
        <version>1.2.17version>
    dependency>
    
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druidartifactId>
        <version>1.1.23version>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-thymeleafartifactId>
    dependency>
    
    
    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>2.1.3version>
    dependency>
    <dependency>
        <groupId>com.github.theborakompanionigroupId>
        <artifactId>thymeleaf-extras-shiroartifactId>
        <version>2.0.0version>
        <scope>compilescope>
    dependency>
    
  2. MyController

    @Controller
    public class MyController {
    
        @RequestMapping({"/","/index"})
        public String toIndex(Model model){
            model.addAttribute("msg","hello,shiro");
            return "index";
        }
    
        @RequestMapping("/user/add")
        public String add(){
            return "user/add";
        }
    
        @RequestMapping("/user/update")
        public String update(){
            return "user/update";
        }
    
        @RequestMapping("/toLogin")
        public String toLogin(){
            return "login";
        }
    
        @RequestMapping("/login")
        public String login(String username,String password,Model model) {
            //获取当前用户
            Subject subject = SecurityUtils.getSubject();
    
            //封装用户的登录数据
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
    
            // 验证登录
            try {
                // 执行登录操作,跨类调用
                subject.login(usernamePasswordToken);
                model.addAttribute("msg", "成功登录");
                return "index";
            } catch (UnknownAccountException uae) {
                model.addAttribute("msg", "用户名错误");
                return "login";
            } catch (IncorrectCredentialsException ice) {
                model.addAttribute("msg", "用户密码错误");
                return "login";
            }
    
        }
    
    
        @RequestMapping("/noauth")
        @ResponseBody
        public String unauthorized(){
            return "未经授权无法访问";
        }
    
    }
    
  3. ShiroConfig配置类

    @Configuration
    public class ShiroConfig {
    
        //第三步:创建 ShiroFilterFactoryBean
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            //设置安全管理器
            bean.setSecurityManager(defaultWebSecurityManager);
    
            //添加shiro的内置过滤器
            /*
               anon:无需认证就可以访问
               authc:必须认证了才能访问
               user:必须拥有"记住我"功能才能用
               perms:拥有对某个资源的权限才能访问
               role:拥有某个角色权限才能访问
             */
            //拦截
            Map<String,String> filterMap = new LinkedHashMap<String,String>();
            //授权,正常的情况下,没有权限会跳转到未授权页面
            filterMap.put("/user/add","perms[user:add]");
            filterMap.put("/user/update","perms[user:update]");
            filterMap.put("/user/*","authc");
            bean.setFilterChainDefinitionMap(filterMap);
    
    
            //设置登录的请求
            bean.setLoginUrl("/toLogin");
    
            //设置未授权页面
            bean.setUnauthorizedUrl("/noauth");
    
            return bean;
        }
    
        //第二步:创建 DefaultWebSecurityManager
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            //关联UserRealm
            securityManager.setRealm(userRealm);
            return securityManager;
        }
    
        //第一步:创建 realm 对象 自定义
        @Bean
        public UserRealm userRealm(){
            return new UserRealm();
        }
        
        //整合ShiroDialect   用来整合Shiro和Thymeleaf
        @Bean
        public ShiroDialect getShiroDialect(){
            return new ShiroDialect();
        }
    
    }
    
  4. shiro配置类需要的UserRealm类

    //自定义  继承AuthorizingRealm
    public class UserRealm extends AuthorizingRealm {
    
        @Autowired
        private UserService userService;
    
        //授权
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    
            //这样是写死的,只要用户认证成功以后进来,这里给予权限就代表所有用户都拥有此权限
    //        info.addStringPermission("user:add");
    
            //拿到当前登录的对象
            Subject subject = SecurityUtils.getSubject();
            User currentUser = (User) subject.getPrincipal();//拿到user对象
    
            //设置当前用户的权限
            info.addStringPermission(currentUser.getPerms());
    
            return info;
        }
    
    
    
        //认证
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            // 模拟数据库中查出用户名、密码
            UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
    
            User user = userService.queryUserByName(userToken.getUsername());
    
            if(user == null){//没有这个人
                return null;
            }
            
            Subject currentSubject= SecurityUtils.getSubject();
            Session session = currentSubject.getSession();
            session.setAttribute("loginUser",user);
    
            // 密码验证,shiro完成,不需要用户判断.直接返回
            return new SimpleAuthenticationInfo(user, user.getPwd(), "");
    
        }
    }
    
  5. application.yaml

    # 整合数据源
    spring:
      datasource:
        username: root
        password: root
        url: jdbc:mysql://localhost:3306/mybatis?userUnicode=true&chacacterEncoding=utf-8&serverTimezone=UTC
        # Springboot使用 com.mysql.cj.jdbc.Driver 针对Mysql8以上,5可能会有bug
        driver-class-name: com.mysql.cj.jdbc.Driver
    
    
    # 整合mybatis
    mybatis:
      type-aliases-package: com.luffy.shirospringboot.pojo
      # 解决绑定异常:mapper.xml最好和接口的包名路径一致
      mapper-locations: classpath:mapper/*.xml
    
    #SpringBoot默认是不注入这些的,需要自己绑定
    #druid数据源专有配置
    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:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Properity
    #则导入log4j 依赖就行
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionoProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
  6. index.html

    DOCTYPE html>
    <html lang="en" xmlns:shiro="http://www.w3.org/1999/xhtml">
        <head>
            <meta charset="UTF-8">
            <title>Titletitle>
        head>
        <body>
    
            <h1>homeh1>
            <div th:if="${session.loginUser==null}">
                <a th:href="@{/toLogin}">登录a>
            div>
            <p th:text="${msg}">p>
    
    
            <div shiro:hasPermission="user:add">
                <a th:href="@{/user/add}">adda>
            div>
    
            <div shiro:hasPermission="user:update">
                <a th:href="@{/user/update}">updatea>
            div>
    
    
        body>
    html>
    
  7. login.html

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Titletitle>
        head>
        <body>
    
    
            <h1>loginh1>
    
            <p th:text="${msg}" style="color: red">p>
    
            <form th:action="@{/login}">
                <p>username:<input type="text" name="username">p>
                <p>password:<input type="text" name="password">p>
                <p>LOGIN<input type="submit">p>
            form>
    
    
        body>
    html>
    

7、Swagger

7.1、 介绍

swagger号称世界上最流行的API框架,API文档与API定义同步更新,直接运行,支持多种语言。

在项目中使用swagger需要Springfox(2.x版本):

  • swagger2
  • swagger-ui

7.2、集成

  1. 导入依赖

    <dependency>
        <groupId>io.springfoxgroupId>
        <artifactId>springfox-swagger2artifactId>
        <version>3.0.0version>
    dependency>
    <dependency>
        <groupId>io.springfoxgroupId>
        <artifactId>springfox-swagger-uiartifactId>
        <version>3.0.0version>
    dependency>
    
    <dependency>
        <groupId>io.springfoxgroupId>
        <artifactId>springfox-boot-starterartifactId>
        <version>3.0.0version>
    dependency>
    
  2. controller类

    @RestController
    public class HelloController {
    
         @RequestMapping(value = "/hello")
         public String hello(){
             return "hello";
         }
    
    }
    
  3. 编写SwaggerConfig配置类

    @Configuration
    @EnableSwagger2 //开启Swagger2
    public class SwaggerConfig {
    
    }
    
  4. 访问:http://localhost:端口号/swagger-ui/index.html,(注意swagger2.x版本中访问的地址的为http://localhost:端口号/swagger-ui.html),即可访问Swagger首页

  5. 如果Swagger3.0版本跟Springboot不匹配,以下是匹配版本号(不想降低Springboot版本号也有别的解决方法自行百度)

    Spring Boot版本 Swagger 版本
    2.5.6 2.9.2

7.3、配置Swagger信息

  1. config配置类添加

    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
    }
    
    
    //配置文档信息
    private ApiInfo apiInfo() {
        Contact contact = new Contact("路飞", "http://www.baidu.com/", "[email protected]");
        return new ApiInfo(
            "Swagger学习", // 标题
            "学习演示如何配置Swagger", // 描述
            "v1.0", // 版本
            "http://terms.service.url/组织链接", // 组织链接
            contact, // 联系人信息
            "Apach 2.0 许可", // 许可
            "许可链接", // 许可连接
            new ArrayList<>()// 扩展
        );
    }
    

7.4、配置扫描接口及开关

  1. 构建Docket时通过select()方法配置怎么扫描接口

    @Bean
    public Docket docket(Environment environment) {
        
        return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .enable(false) //配置是否启用Swagger,如果是false,在浏览器将无法访问
            .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
            .apis(RequestHandlerSelectors.basePackage("com.luffy.swaggerdemo.controller"))
            //                .paths(PathSelectors.ant("/luffy/swaggerdemo/**"))
            .build();
    
        //                RequestHandlerSelectors的所有扫描方式:
        //                  any() // 扫描所有,项目中的所有接口都会被扫描到
        //                  none() // 不扫描接口
        //                  // 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
        //                  withMethodAnnotation(final Class annotation)
        //                  // 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
        //                  withClassAnnotation(final Class annotation)
        //                  basePackage(final String basePackage) // 根据包路径扫描接口
        //                  paths(PathSelectors.ant("/luffy/swaggerdemo/**"))  //过滤什么路径:过滤/luffy/swaggerdemo下的所有路径
    }
    

7.5、Swagger在生产环境中使用,在发布的时候不使用(问题)

  1. 配置几个不同的properties环境,在总的properties配置使用的环境

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9RHfSspM-1659458588018)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220712123806470.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Jioetta-1659458588018)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220712123853933.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VTkWrKKb-1659458588019)(C:\Users\22341\AppData\Roaming\Typora\typora-user-images\image-20220712123907210.png)]

  1. config配置类,根据显示的环境是否能够找到返回布尔值提供给enable方法决定是否开启swagger

    @Bean
    public Docket docket(Environment environment) {
    
        // 设置要显示swagger的环境
        Profiles of = Profiles.of("dev", "test");
        // 判断当前是否处于该环境
        // 通过 enable() 接收此参数判断是否要显示
        boolean flag = environment.acceptsProfiles(of);
    
        return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .enable(flag) //配置是否启用Swagger,如果是false,在浏览器将无法访问
            .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
            .apis(RequestHandlerSelectors.basePackage("com.luffy.swaggerdemo.controller"))
            //                .paths(PathSelectors.ant("/luffy/swaggerdemo/**"))
            .build();
    
        //                RequestHandlerSelectors的所有扫描方式:
        //                  any() // 扫描所有,项目中的所有接口都会被扫描到
        //                  none() // 不扫描接口
        //                  // 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
        //                  withMethodAnnotation(final Class annotation)
        //                  // 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
        //                  withClassAnnotation(final Class annotation)
        //                  basePackage(final String basePackage) // 根据包路径扫描接口
        //                  paths(PathSelectors.ant("/luffy/swaggerdemo/**"))  //过滤什么路径:过滤/luffy/swaggerdemo下的所有路径
    }
    

8、异步、定时、邮件任务

8.1、异步任务

  1. 创建一个service

    @Service
    public class AsyncService {
        @Async
        public void hello(){
            try {
                Thread.sleep(3000);
            }catch (Exception e){
    
            }
            System.out.println("数据正在处理!");
        }
    }
    
  2. 编写controller

    @RestController
    public class AsyncController {
        @Autowired
        private AsyncService asyncService;
    
        @RequestMapping("/hello")
        public String hello(){
            asyncService.hello();
            return "OK!";
        }
    }
    
  3. SpringbootTaskApplication主启动类

    @EnableAsync
    @SpringBootApplication
    public class Springboot09TestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Springboot09TestApplication.class, args);
        }
    
    }
    
  4. @EnableAsync注解:启动异步

  5. @Async注解:标记一个方法为异步

  6. 一般一个请求执行以后要等待返回结果成功后才会返回对应界面,异步任务即是先返回成功界面再继续执行请求任务

8.2、邮件任务

  1. 导入依赖

    <dependency>
       <groupId>org.springframework.bootgroupId>
       <artifactId>spring-boot-starter-mailartifactId>
    dependency>
    
  2. application.properties文件配置

    [email protected]
    spring.mail.password=dvfdnthbsjjreaei
    spring.mail.host=smtp.qq.com
    # qq需要配置ssl
    spring.mail.properties.mail.smtp.ssl.enable=true
    
  3. 测试类

    @SpringBootTest
    class Springboot09TestApplicationTests {
    
        public static void main(String[] args) {
    
        }
    
        @Autowired
        JavaMailSenderImpl mailSender;
    
        @Test
        public void contextLoads() {
            //邮件设置1:一个简单的邮件
            SimpleMailMessage message = new SimpleMailMessage();
            message.setSubject("通知-明天来狂神这听课");
            message.setText("今晚7:30开会");
    
            message.setTo("[email protected]");
            message.setFrom("[email protected]");
            mailSender.send(message);
        }
    
        @Test
        public void contextLoads2() throws MessagingException {
            //邮件设置2:一个复杂的邮件
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
    
            helper.setSubject("通知-明天来狂神这听课");
            helper.setText("今天 7:30来开会",true);
    
            //发送附件
            helper.addAttachment("1.jpg",new File("E:\\图片\\1.jpg"));
    
            helper.setTo("[email protected]");
            helper.setFrom("[email protected]");
    
            mailSender.send(mimeMessage);
        }
    
    }
    

8.3、定时任务

项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。

  • TaskExecutor接口
  • TaskScheduler接口

两个注解:

  • @EnableScheduling
  • @Scheduled

使用方式:

  1. Springboot09TestApplication

    @EnableAsync//开启异步注解功能
    @EnableScheduling //开启基于注解的定时任务
    @SpringBootApplication
    public class Springboot09TestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Springboot09TestApplication.class, args);
        }
    
    }
    
  2. Service类

    @Service
    public class ScheduledService {
    
        //秒   分   时   日   月   周几
        //0 * * * * MON-FRI
        //注意cron表达式的用法;
        @Scheduled(cron = "0/2 * * * * ?")
        public void hello(){
            System.out.println("hello.....");
        }
    
        /*
        (1)0/2 * * * * ?   表示每2秒 执行任务
        (1)0 0/2 * * * ?   表示每2分钟 执行任务
        (1)0 0 2 1 * ?   表示在每月的1日的凌晨2点调整任务
        (2)0 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作业
        (3)0 15 10 ? 6L 2002-2006   表示2002-2006年的每个月的最后一个星期五上午10:15执行作
        (4)0 0 10,14,16 * * ?   每天上午10点,下午2点,4点
        (5)0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时
        (6)0 0 12 ? * WED   表示每个星期三中午12点
        (7)0 0 12 * * ?   每天中午12点触发
        (8)0 15 10 ? * *   每天上午10:15触发
        (9)0 15 10 * * ?     每天上午10:15触发
        (10)0 15 10 * * ?   每天上午10:15触发
        (11)0 15 10 * * ? 2005   2005年的每天上午10:15触发
        (12)0 * 14 * * ?     在每天下午2点到下午2:59期间的每1分钟触发
        (13)0 0/5 14 * * ?   在每天下午2点到下午2:55期间的每5分钟触发
        (14)0 0/5 14,18 * * ?     在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
        (15)0 0-5 14 * * ?   在每天下午2点到下午2:05期间的每1分钟触发
        (16)0 10,44 14 ? 3 WED   每年三月的星期三的下午2:10和2:44触发
        (17)0 15 10 ? * MON-FRI   周一至周五的上午10:15触发
        (18)0 15 10 15 * ?   每月15日上午10:15触发
        (19)0 15 10 L * ?   每月最后一日的上午10:15触发
        (20)0 15 10 ? * 6L   每月的最后一个星期五上午10:15触发
        (21)0 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最后一个星期五上午10:15触发
        (22)0 15 10 ? * 6#3   每月的第三个星期五上午10:15触发
        */
    
    }
    

9、Dubbo和Zookeeper集成

9.1、什么是分布式系统?

  • 分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统

  • 分布式系统(distributed system)是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。

9.2、安装Zookeeper

  1. 网址:https://zookeeper.apache.org/(下载带bin的)

  2. 运行/bin/zkServer.cmd ,初次运行会报错(闪退),在conf下没有zoo.cfg配置文件

  3. 将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg

  4. 运行/bin/zkServer.cmd(服务端)

  5. 运行zkCli.cmd(客户端)

  6. 在客户端输入:ls /(列出zookeeper根下保存的所有节点)

  7. 在客户端输入:create –e /luffy 123(创建一个luffy节点,值为123)

  8. 在客户端输入:get /luffy(获取/luffy节点的值)

9.3、安装Dubbo-admin

dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。

但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。

使用方法

  1. 下载dubbo-admin

    • https://github.com/stackXu/dubbo-admin
  2. 解压进入目录

    • 修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址
  3. 在Dubbo-admin项目目录下打包dubbo-admin

    • mvn clean package -Dmaven.test.skip=true
  4. 执行 dubbo-admin\target 下的dubbo-admin-0.0.1-SNAPSHOT.jar

    • 这里给的下载网址直接有jar包,如果zookeeper和dubbo的配置都一致,即可直接运行jar包

    • 在jar包目录运行cmd:java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

    • 注意:zookeeper服务要开启

  5. 全部执行完访问 http://localhost:7001/ ,输入登录账户和密码,默认的是root-root,即可显示dubbo-admin界面

9.4、服务注册发现实战

1. 启动zookeeper !

2. IDEA创建一个空项目;

3.创建一个模块,实现服务提供者:provider-server , 选择web依赖即可

卖票接口

package com.luffy.service;

public interface TicketService {
   public String getTicket();
}

卖票实现类

package com.luffy.service;

public class TicketServiceImpl implements TicketService {
   @Override
   public String getTicket() {
       return "《狂神说Java》";
  }
}

提供者依赖


<dependency>
   <groupId>org.apache.dubbogroupId>
   <artifactId>dubbo-spring-boot-starterartifactId>
   <version>2.7.3version>
dependency>

<dependency>
   <groupId>com.github.sgroschupfgroupId>
   <artifactId>zkclientartifactId>
   <version>0.1version>
dependency>

<dependency>
   <groupId>org.apache.curatorgroupId>
   <artifactId>curator-frameworkartifactId>
   <version>2.12.0version>
dependency>
<dependency>
   <groupId>org.apache.curatorgroupId>
   <artifactId>curator-recipesartifactId>
   <version>2.12.0version>
dependency>
<dependency>
   <groupId>org.apache.zookeepergroupId>
   <artifactId>zookeeperartifactId>
   <version>3.4.14version>
   
   <exclusions>
       <exclusion>
           <groupId>org.slf4jgroupId>
           <artifactId>slf4j-log4j12artifactId>
       exclusion>
   exclusions>
dependency>

提供者application.properties

#当前应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#扫描指定包下服务
dubbo.scan.base-packages=com.luffy.service

4.创建一个模块,实现服务消费者:consumer-server , 选择web依赖即可

5.项目创建完毕,我们写一个服务,比如用户的服务

消费者依赖



<dependency>
   <groupId>org.apache.dubbogroupId>
   <artifactId>dubbo-spring-boot-starterartifactId>
   <version>2.7.3version>
dependency>


<dependency>
   <groupId>com.github.sgroschupfgroupId>
   <artifactId>zkclientartifactId>
   <version>0.1version>
dependency>

<dependency>
   <groupId>org.apache.curatorgroupId>
   <artifactId>curator-frameworkartifactId>
   <version>2.12.0version>
dependency>
<dependency>
   <groupId>org.apache.curatorgroupId>
   <artifactId>curator-recipesartifactId>
   <version>2.12.0version>
dependency>
<dependency>
   <groupId>org.apache.zookeepergroupId>
   <artifactId>zookeeperartifactId>
   <version>3.4.14version>
   
   <exclusions>
       <exclusion>
           <groupId>org.slf4jgroupId>
           <artifactId>slf4j-log4j12artifactId>
       exclusion>
   exclusions>
dependency>

消费者application.properties

#当前应用名字
dubbo.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

消费者的服务类

package com.luffy.service;

import com.guo.provider.service.TicketService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;

@Service //注入到容器中
public class UserService {

   @Reference //远程引用指定的服务,他会按照全类名进行匹配,看谁给注册中心注册了这个全类名
   TicketService ticketService;

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

}

6.编写测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsumerServerApplicationTests {

    @Autowired
    UserService userService;

    @Test
    public void contextLoads() {

        userService.buyTicket();

    }

}

你可能感兴趣的:(spring,boot,学习,spring)