首页 > 其他分享 >SpringBoot

SpringBoot

时间:2022-12-30 22:55:54浏览次数:41  
标签:SpringBoot spring springframework org import lei public

1简介

1.1、什么是spirngboot?

springboot在spring的基础之上,搭建起来的框架,能够帮助我们整合市面上最流行的框架,帮助我们快速搭建起来项目。

springboot不是新的技术,而是新的框架,是基于spring来搭建起来的。

特性:约定大于配置!

1.2、为什么使用springboot

  • 开发效率快,内置有配置好的版本依赖。
  • 基于spring。
  • 轻松上手

2 第一个SpringBoot程序

我的开发环境:

  • java 8
  • Maven-3.6.1
  • SpringBoot 2.6.11

创建springboot有两种方式:

  1. 在https://start.spring.io/上创建后,下载完成,通过IDEA打开即可。
  2. 在IDEA中直接创建。

3 原理初探

自动装配 :

pom.xml

  • spring-boot-dependencies :核心依赖在父工程中!
  • 引入一些springboot依赖的时候,不需要指定版本,因为有这些版本

启动器

  • <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    
  • 启动器:springboot的启动场景

  • 比如 spring-boot-starter-web ,它就会自动帮我们自动导入web环境所有的依赖

  • springboot会将所有功能场景,变成一个个的启动器

  • 我们需要使用什么功能,就需要找到对应的启动器就可以了

主程序

package com.lei;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication :标注这个类是一个springboot的应用
@SpringBootApplication
public class Springboot01HellowordApplication {

    public static void main(String[] args) {
        //将spring boot应用启动
        SpringApplication.run(Springboot01HellowordApplication.class, args);
    }

}

注解

@SpringBootConfiguration :spring boot的配置
  @Configuration  :spring配置类
   @Component:这是一个spring的组件

@EnableAutoConfiguration :自动配置
  @AutoConfigurationPackage :自动配置包
   @Import({Registrar.class}) :自动配置包注册·
  @Import({AutoConfigurationImportSelector.class}) :导入选择器
  

获取候选的配置

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

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

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

​ 1.spring boot 在启动的时候,从类路径下META-INF/spring.factories获取指定EnableAutoConfiguration的值;

​ 2.将这些自动配置的类导入容器,自动配置就会生效,进行自动配置

​ 3.以前需要配置的东西,现在spring boot帮我们做了!

​ 4.整合javaEE 整体解决方案和自动配置都在springboot-autoconfigure的jar包中;

​ 5.它会把需要导入的组件,以类名的方式返回,这些组件就会被添加导容器

​ 6.它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;并自动配置,@Configuration

​ 7.有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

4 Spring Boot 配置

springboot支持两种格式的配置文件,.properties.yml

如果在同一位置有同时具有.properties和.yml格式的配置文件,.properties优先。

如果存在多个相同配置,会有优先级。

4.1、配置文件区别

.properties.yml区别在于语法结构不同。

  • .properties结构 :key=value
server.port=8081
  • .yml结构 :key: value
server:  port: 8081

4.2、实体类获取配置信息

加载单个配置

// @Value 加载单个配置@Value("${student.name}")

加载多个配置

案例:创建学生对象,用于默认就把配置信息加载进去。

1、在springboot项目中的resources目录下新建一个文件 application.yml

student: 
 name: Zhang San 
 birthdate: 1990/09/01  
 interests: [eat, sleep]

2、添加实体类

//注册bean到容器中@Component
// 开头为student配置
@ConfigurationProperties(prefix = "student")
@Data
	public class Student {  
	private String name;  
	private Date birthdate;   
	private List<String> interests;
}

idea提示没有找到springboot配置注解处理器。
img

需要添加springboot配置注解处理器,方便在Test测试。

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

3、测试类中测试。

@SpringBootTestclass DemoApplicationTests {  
  @Autowired  
  Student student; 
  @Test   
  void contextLoads() {    
  	System.out.println(student);  
    }
 }

结果:
img

4.3、加载指定的配置文件

日常开发中,配置文件可能存在多个。

1、我们去在resources目录下新建一个student.properties文件,yaml不生效。

student.name=Wang moustudent.birthdate=1995/09/01student.interests=[sleep,dream]

2、修改配置类

//注册bean到容器中
@Component
// 开头为student配置
@ConfigurationProperties(prefix = "student")
// 资源路径
@PropertySource(value = "classpath:student.properties")
@Data
public class Student {   
	private String name;    
	private Date birthdate; 
	private List<String> interests;
}

3、测试查看效果
img

4.4、yaml语法总结

  • 语法要求严格
  • 空格不能省略
  • 以缩进来控制层级关系
  • 对大小写敏感
# 普通格式:k: v# 对象格式:k:  v1:  v2:
# 数组模式:k:  - v  - v  - v
# 行内写法k: {k1:v1,k2:v2}k: {v,v,v}

4.5、配置文件优化级

springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:

1.类路径 
	1.类路径  
	2.类路径/config包
2.当前目录  
	1.当前目录  
	2.当前目录中的/config子目录
	3.子目录的/config直接子目录

img

4.6、多环境切换

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;

例如:

application-test.yml:代表测试环境配置

application-dev.yml:代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件,如果没有就会找application.yml

我们需要通过一个配置来选择需要激活的环境:

spring:  profiles:    active: dev #使用开发环境。

4.7、自动配置原理

SpringBoot官方文档中有大量的配置。我们无法全部记住。

我们来简单分析一下。

5.1、分析自动配置原理

这块内容,建议大家去看视频。

我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例。

解释自动配置原理;

@Configuration //表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;//启动指定类的ConfigurationProperties功能;  //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;  //并把HttpProperties加入到ioc容器中@EnableConfigurationProperties({HttpProperties.class}) //Spring底层@Conditional注解  //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;  //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效@ConditionalOnWebApplication(type = Type.SERVLET)//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;@ConditionalOnClass({CharacterEncodingFilter.class})//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;  //如果不存在,判断也是成立的  //即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;@ConditionalOnProperty(    prefix = "spring.http.encoding",    value = {"enabled"},    matchIfMissing = true)public class HttpEncodingAutoConfiguration {    //他已经和SpringBoot的配置文件映射了    private final Encoding properties;    //只有一个有参构造器的情况下,参数的值就会从容器中拿    public HttpEncodingAutoConfiguration(HttpProperties properties) {        this.properties = properties.getEncoding();    }    //给容器中添加一个组件,这个组件的某些值需要从properties中获取    @Bean    @ConditionalOnMissingBean //判断容器没有这个组件?    public CharacterEncodingFilter characterEncodingFilter() {        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();        filter.setEncoding(this.properties.getCharset().name());        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));        return filter;    }    //。。。。。。。}

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!

如果没有把对应的依赖引用进来,这个配置类也会不生效。

通过不同的条件来进行判断是否要启动配置。

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 这样就可以形成我们的配置文件可以动态的修改springboot的内容。
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类

对比小结

@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图

图片

1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加

2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下

3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性

4、复杂类型封装,yml中可以封装对象 , 使用value就不支持

结论:

配置yml和配置properties都可以获取到值 , 强烈推荐 yml;

如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;

如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

自动装配 精髓

1、SpringBoot启动会加载大量的自动配置类

2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

xxxxAutoConfigurartion:自动配置类;给容器中添加组件

xxxxProperties:封装配置文件中相关属性;

5 Spring Boot Web 开发

静态资源

要解决的问题:

  • 导入静态资源
  • 首页
  • jsp,模板引擎Thymeleaf
  • 装配SpringMvc
  • 增删改查
  • 拦截器
  • 国际化

1 在springboot,我们可以使用以下的方式处理静态资源

2 优先级:resource> static(默认)>public

首页如何定制

  • 静态资源路径下 index.html

  • 可以配置静态资源路径。

  • 不能与静态资源前缀共用。

    网页小图标可以自定义 把图片名字改成favicon.ico放到静态资源路径下

image-20221125204443004

image-20221125204515977

模板引擎

thymeleaf

​ 结论:只有需要使用thymeleaf,只需要导入对应的依赖就可以了!我们将html放到我们的templates目录下即可

6 员工管理系统

项目结构

image-20221128195545807

pojo 实体类

Department 部门表

package com.lei.pojo;

/**
 * @author lei
 * @verson:1.8
 */
//部门表
public class Department {
    private Integer id;
    private String department;

    public Department() {
    }

    public Department(Integer id, String department) {
        this.id = id;
        this.department = department;
    }

    public Integer getId() {
        return id;
    }

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

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }
}

Employee 员工表

package com.lei.pojo;

import java.util.Date;

/**
 * @author lei
 * @verson:1.8
 */
public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private  Integer gender;
    private Department department;
    private Date birth;

    public Employee() {
    }

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

    public Integer getId() {
        return id;
    }

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

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

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

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }
}

dao包(模拟数据库)

DepartmentDao

package com.lei.dao;

import com.lei.pojo.Department;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Repository;

/**
 * @author lei
 * @verson:1.8
 */
//部门dao
    @Repository
public class DepartmentDao {
    //模拟数据库
    private static Map<Integer, Department> departments =null;
    static {
        departments=new HashMap<>();//创建一个部门表
        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> getDepartment(){
        return departments.values();
    }
    //通过id得到部门
    public Department getDepartment(Integer id){
        return departments.get(id);
    }
}

EmployeeDao

package com.lei.dao;

import com.lei.pojo.Department;
import com.lei.pojo.Employee;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

/**
 * @author lei
 * @verson:1.8
 */
//员工dao
    @Repository
public class EmployeeDao {
    private static Map<Integer, Employee> employees =null;
    @Autowired
    private DepartmentDao departmentDao;
    static {
        employees=new HashMap<>();//创建一个部门表
        employees.put(1001,new Employee(1001,"AA","[email protected]",1,new Department(101,"教学部")));
        employees.put(1002,new Employee(1002,"bb","[email protected]",0,new Department(101,"市场部")));
        employees.put(1003,new Employee(1003,"cc","[email protected]",1,new Department(101,"教研部")));
        employees.put(1004,new Employee(1004,"dd","[email protected]",0,new Department(101,"运营部")));
        employees.put(1005,new Employee(1005,"ee","[email protected]",1,new Department(101,"后勤部")));




    }
    //主键自增
    private static Integer intId = 1006;
    //增加一个员工
    public void save(Employee employee){
        if (employee.getId()==null){
            employee.setId(intId++);
        }
        employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
        employees.put(employee.getId(),employee);
    }
    //查询所有员工信息
    public Collection<Employee> getAll(){
        return employees.values();
    }
    //通过id查询员工
    public Employee getEmployeeByqid(Integer id){
        return employees.get(id);
    }
    //通过id删除员工
    public void delete(Integer id){
         employees.remove(id);
    }
}

1.首页配置:

1.注意点,所有页面的静态资源都需要使用thmeleaf来接管;

2.url: @{}

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
	<meta 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 Bootstrap</title>
	<link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/sign-in/">
	<!-- Bootstrap core CSS -->
	<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

	<!-- Custom styles for this template -->
	<link th:href="@{/css/signin.css}" rel="stylesheet">
</head>

<body class="text-center">
<form class="form-signin" th:action="@{/user/login}">
	<img class="mb-4" src="https://getbootstrap.com/docs/4.0/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
	<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>

	<!--/*@thymesVar id="msg" type="ch"*/-->
<!--	如果msg的值为空,则不显示消息-->
	<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
	<input type="text" name="username" class="form-control" th:placeholder="#{login.user}" required autofocus>
	<input type="password" name="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}">Sign in</button>
			<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
			<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
			<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
		</form>

	</body>

</html>

2.页面国际化:

1.我们需要配置i18n文件

login.password=密码
login.remember=记住我
login.tip=请登录
login.email=电子邮件
login.btn=登录

2.我们如果需要在项目中进行自动切换,我们需要自定义一个组件LocaleResolver

package com.lei.config;

import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;

/**
 * @author lei
 * @verson:1.8
 */
public class MyLocaleResolver implements LocaleResolver {

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

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

        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

3.记得将自己写的组件配置到Spring容器 @Bean

package com.lei.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author lei
 * @verson:1.8
 */
@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 localResolver(){
        return new MyLocaleResolver();
    }
}

3 登录实现

LoginController类编写

package com.lei.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author lei
 * @verson:1.8
 */
@Controller
//跳转首页 登录验证
public class LoginController {
    @RequestMapping("/user/login")

    public String login(@RequestParam("username")String name, @RequestParam("password")String password, Model model){

        if (StringUtils.hasText(name)&&"123456".equals(password)){
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","用户名或密码错误");
            return "index";
        }

    }
}

视图解析器

package com.lei.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author lei
 * @verson:1.8
 */
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
        registry.addViewController("/main.html").setViewName("dashboard");
    }
    //自定义国际化组件
    @Bean
    public LocaleResolver localResolver(){
        return new MyLocaleResolver();
    }

//后面添加的
    //添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/index.html","/","/user/login","/css/**","/js/**","img/**","/emp/**");
    }
}

登录表单

form class="form-signin" th:action="@{/user/login}">
   <img class="mb-4" src="https://getbootstrap.com/docs/4.0/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
   <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>

   <!--/*@thymesVar id="msg" type="ch"*/-->
<!--   如果msg的值为空,则不显示消息-->
   <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
   <input type="text" name="username" class="form-control" th:placeholder="#{login.user}" required autofocus>
   <input type="password" name="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}">Sign in</button>
         <p class="mt-5 mb-3 text-muted">© 2017-2018</p>
         <a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
         <a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
      </form>

4 登录拦截器

编写LoginHandlerInterceptor 拦截类

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


    }
}

在MyMvcConfig中注册

 //添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/index.html","/","/user/login","/css/**","/js/**","img/**");
    }

5 员工展示

1提取公共页面

th:fragment="topbar"
<div th:insert="~{commons/commons::topbar}"></div>

如果要获取后端的值

<div th:insert="~{commons/commons::sidebar(active='main.html')}"></div>

2获取用户数据

​ 查询所有的员工

@Controller
public class EmployeeController {
    @Autowired
    EmployeeDao employeeDao;
    //查询所有的员工
    @RequestMapping("/emps")
    public String list(Model model){
        Collection<Employee> employees = employeeDao.getAll();
        model.addAttribute("emps",employees);
        return "emp/list";
    }
}

前端获取 遍历

<table class="table table-striped table-sm">
   <thead>
      <tr>
         <th>id</th>
         <th>lastName</th>
         <th>email</th>
         <th>gender</th>
         <th>department</th>
         <th>birth</th>
         <th>操作</th>
      </tr>
   </thead>
   <tbody>
      <!--/*@thymesVar id="emps" type="com"*/-->
      <tr th:each="emp:${emps}">

         <!--/*@thymesVar id="getId" type=""*/-->
         <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.getDepartment()}"></td>
         <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
         <td>
            <button class="btn btn-sm btn-primary">编辑</button>
            <button class="btn btn-sm btn-danger">删除</button>
         </td>
      </tr>
   </tbody>
</table>

6添加员工

1 按钮提交

<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a> </h2>

2跳转到添加页面

//跳转到添加页面
@GetMapping("/emp")
public String toAddpage(Model model){
    //查出所有部门信息
    Collection<Department> department = departmentDao.getDepartment();
    model.addAttribute("department",department);
    return "emp/add";
}

3添加页

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

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

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

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

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

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

            <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
               <form th:action="@{/emp}" method="post">
                  <div class="form-group">
                     <label>LastName</label>
                     <input type="text"name="lastName" class="form-control" placeholder="海绵宝宝">
                  </div>
                  <div class="form-group">
                     <label>Email</label>
                     <input type="email" name="email" class="form-control" placeholder="[email protected]">
                  </div>
                  <div class="form-group">
                     <label>Gender</label><br>
                     <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="gender" value="1">
                        <label class="form-check-label">男</label>
                     </div>
                     <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="gender" value="0">
                        <label class="form-check-label">女</label>
                     </div>
                  </div>
                  <div class="form-group">
                     <label>department</label>
                     <select class="form-control" name="department.id">
                        <option th:each="dept:${department}" th:text="${dept.getDepartment()}" th:value="${dept.getId()}"></option>

                     </select>
                  </div>
                  <div class="form-group">
                     <label>Birth</label>
                     <input type="text" name="birth" class="form-control" placeholder="">
                     <button class="btn btn-primary">添加</button>
                  </div>
               </form>
            </main>
         </div>
      </div>

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

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

      <!-- Graphs -->
      <script type="text/javascript" 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 添加成功 返回首页

//添加请求
@PostMapping("/emp")
public String Addpemp(Employee employee){
    System.out.println(employee);
    employeeDao.save(employee);


    return "redirect:/emps";
}

7 修改员工

提交按钮

<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.getId()}">编辑</a>

跳转到添加页面

//到员工修改页面
public String toUpdateEmp(@PathVariable("id") Integer id,Model model){
    //查询原来的书籍
    Employee employeeByqid = employeeDao.getEmployeeByqid(id);
    model.addAttribute("emp",employeeByqid);
    //查出所有部门信息
    Collection<Department> department = departmentDao.getDepartment();
    model.addAttribute("department",department);
    return "emp/update";
}

修改页

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

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

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

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

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

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

            <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
               <form th:action="@{/updateEmp}" method="post">
                  <input type="hidden" name="id" th:value="${emp.getId()}">
                  <div class="form-group">
                     <label>LastName</label>
                     <input  th:value="${emp.getLastName()}" type="text"name="lastName" class="form-control" placeholder="海绵宝宝">
                  </div>
                  <div class="form-group">
                     <label>Email</label>
                     <input  th:value="${emp.getEmail()}"type="email" name="email" class="form-control" placeholder="[email protected]">
                  </div>
                  <div class="form-group">
                     <label>Gender</label><br>
                     <div class="form-check form-check-inline">
                        <input  th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" name="gender" value="1">
                        <label class="form-check-label">男</label>
                     </div>
                     <div class="form-check form-check-inline">
                        <input th:checked="${emp.getGender()==0}" class="form-check-input" type="radio" name="gender" value="0">
                        <label class="form-check-label">女</label>
                     </div>
                  </div>
                  <div class="form-group">
                     <label>department</label>
                     <select class="form-control" name="department.id">
                        <option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${department}" th:text="${dept.getDepartment()}" th:value="${dept.getId()}"></option>

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

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

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

      <!-- Graphs -->
      <script type="text/javascript" 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>

修改成功

@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
    employeeDao.save(employee);
    return "redirect:/emps";
}

8 删除

删除链接

<a class="btn btn-sm btn-danger" th:href="@{/delemp/}+${emp.getId()}">删除</a>

删除collerter

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

9 注销

<a class="nav-link" th:href="@{/user/logout}">注销</a>
//删除员工
@GetMapping("/delemp/{id}")
public String deleteEmp(@PathVariable("id") Integer id){
    employeeDao.delete(id);
    return "redirect:/emps";
}

7 druid 数据源

yam配置

spring:
  datasource:
    username: root
    password: "0414"
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

      #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

druid 配置类

package com.lei.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import java.util.HashMap;
import javax.sql.DataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author lei
 * @verson:1.8
 */
@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druiddDataSource(){
        return new DruidDataSource();
    }
    @Bean
    //后台监控
    public ServletRegistrationBean a(){
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");

        //后台需要有人登录 登录密码

        HashMap<String, String> initParameters = new HashMap<>();
        //增加配置
        initParameters.put("loginUsername","admin");  //登录key 是固定的 loginUsername loginPassword
        initParameters.put("loginPassword","123456");

        //允许谁访问
        initParameters.put("allow","");

        bean.setInitParameters(initParameters);//设置初始化参数
        return bean;
    }

}

8 Mybatis

配置文件

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

#整合mybatis
mybatis.type-aliases-package=com.lei.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

创建实体类

public class User {
    private int id ;
    private String name;
    private String pwd;

    public User(int i, String aa, String s) {
    }
}

mapper接口

package com.lei.mapper;

import com.lei.pojo.User;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

/**
 * @author lei
 * @verson:1.8
 */
//这个注解表示了这是一个mybatis 的mapper类:dao
@Mapper
@Repository
public interface UserMapper {

    List<User> queryUserList();

    User queryUserById(int id);

    int addUser(User user);
    int updateUser(User user);

    int deleteUser(int id);
}

在resources中创建一个mybatis包 在mybatis包中建一个mapper包

在mapper包里UserMapper

  • resources

    • mybatis

      • mapper

        • UserMapper.xml

          <?xml version="1.0" encoding="utf-8" ?>
          <!DOCTYPE mapper
                  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
                  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
          <mapper namespace="com.lei.mapper.UserMapper">
              <select id="queryUserList" resultType="User">
                  select *from mybatis.user u
              </select>
              <select id="queryUserById" resultType="User">
                  select *from mybatis.user u 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 mybatis.user u where id=#{id}
              </delete>
          </mapper>
          

9 SpringSecurity(安全)

在web开发中、安全第一位!过滤器 拦截器-

shiro,SpringSecurity:很像~除了类不一样,名字不一样

认证 ,授权(vip1,vip2,vip3)

功能权限

访问功能

菜单权限

拦截器,过滤器:大量的原生代码-

aop横切类

认识SpringSecurity

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

这个概念是通用的,而不是只在Spring Security 中存在。

认证和授权

目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能

1、引入 Spring Security 模块

编写基础配置类

package com.lei.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @author lei
 * @verson:1.8
 */
//aop
@EnableWebSecurity
public class SecurityConfig  extends WebSecurityConfigurerAdapter {
    //链式编程
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有对应有权限的才能访问
        //请求授权的规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasAnyRole("vip1")
                .antMatchers("/level2/**").hasAnyRole("vip2")
                .antMatchers("/level3/**").hasAnyRole("vip3");

        //没有权限去登录页
        http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
        //注销
        http.logout().logoutSuccessUrl("/");
        //开启记住我功能
        http.rememberMe();
    }
     //认证
    //密码加密
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("lei").password( new BCryptPasswordEncoder().encode("123")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");
    }
}

、我们在前端,增加一个注销的按钮,index.html 导航栏中

<a class="item" th:href="@{/logout}">
   <i class="address card icon"></i> 注销
</a>

3、我们可以去测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面!

4、但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?

// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");

5、测试,注销完毕后,发现跳转到首页OK

6、我们现在又来一个需求:用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如kuangshen这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?

我们需要结合thymeleaf中的一些功能

sec:authorize="isAuthenticated()":是否认证登录!来显示不同的页面

Maven依赖:

<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>

7、修改我们的 前端页面

  1. 导入命名空间

  2. xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
    
  3. 修改导航栏,增加认证判断

  4. <!--登录注销-->
    <div class="right menu">
    
       <!--如果未登录-->
       <div sec:authorize="!isAuthenticated()">
           <a class="item" th:href="@{/login}">
               <i class="address card icon"></i> 登录
           </a>
       </div>
    
       <!--如果已登录-->
       <div sec:authorize="isAuthenticated()">
           <a class="item">
               <i class="address card icon"></i>
              用户名:<span sec:authentication="principal.username"></span>
              角色:<span sec:authentication="principal.authorities"></span>
           </a>
       </div>
    
       <div sec:authorize="isAuthenticated()">
           <a class="item" th:href="@{/logout}">
               <i class="address card icon"></i> 注销
           </a>
       </div>
    </div>
    

8、重启测试,我们可以登录试试看,登录成功后确实,显示了我们想要的页面;

9、如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试:在 配置中增加 http.csrf().disable();

http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.logout().logoutSuccessUrl("/");

10、我们继续将下面的角色功能块认证完成!

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

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

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

11、测试一下!

12、权限控制和注销搞定!

记住我

现在的情况,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单

1、开启记住我功能

//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
//。。。。。。。。。。。
   //记住我
   http.rememberMe();
}

2、我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!

思考:如何实现的呢?其实非常简单

我们可以查看浏览器的cookie

图片

3、我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie

图片4、结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie,具体的原理我们在JavaWeb阶段都讲过了,这里就不在多说了!

定制登录页

现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?

1、在刚才的登录页配置后面指定 loginpage

http.formLogin().loginPage("/toLogin");

2、然后前端也需要指向我们自己定义的 login请求

<a class="item" th:href="@{/toLogin}">
   <i class="address card icon"></i> 登录
</a>

3、我们登录,需要将这些信息发送到哪里,我们也需要配置,login.html 配置提交请求及方式,方式必须为post:

在 loginPage()源码中的注释上有写明:

图片

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

4、这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码!我们配置接收登录的用户名和密码的参数!

http.formLogin()
  .usernameParameter("user")
  .passwordParameter("pwd")
  .loginPage("/toLogin")
  .loginProcessingUrl("/login"); // 登陆表单提交请求

5、在登录页增加记住我的多选框

<input type="checkbox" name="remember"> 记住我

6、后端验证处理!

//定制记住我的参数!
http.rememberMe().rememberMeParameter("remember");

10shiro

subject 用户
SecurityManager 管理所有用户
Realm 连接数据
   <!--  导入shiro包      -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.10.0</version>
</dependency>

配置shiro

ShiroConfig

package com.lei.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author lei
 * @verson:1.8
 */
@Configuration
public class ShiroConfig {

    //ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager getdefaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(getdefaultWebSecurityManager);
        return  shiroFilterFactoryBean;
    }
    //DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getdefaultWebSecurityManager(UserRealm userRealm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //关联userRealm
        defaultWebSecurityManager.setRealm(userRealm);
        return  defaultWebSecurityManager;
    }

    // 创建 ream 对象,需要自定义类

    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
}

UserRealm

package com.lei.config;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * @author lei
 * @verson:1.8
 */
// 自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

登录拦截 和授权

在ShiroConfig中编写

package com.lei.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import java.util.HashMap;
import java.util.LinkedHashMap;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author lei
 * @verson:1.8
 */
@Configuration

public class ShiroConfig {

    //ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager getdefaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(getdefaultWebSecurityManager);

        //添加shiro内置过滤器
        /*
            anon : 无需认证就可以访问
            authc:必须认证才能访问
            user:必须拥有 记住我的功能才能用
            perms: 拥有对某个资源权限才能用
            role: 拥有某个角色权限才能用
         */
        //拦截
        HashMap<String, String> filterMap = new LinkedHashMap<>();
        //授权
        filterMap.put("/user/add","perms[user:add]");

        filterMap.put("/user/*","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        //设置登录请求
        shiroFilterFactoryBean.setLoginUrl("/toLogin");

        //未授权页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
        return  shiroFilterFactoryBean;
    }
    //DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getdefaultWebSecurityManager(UserRealm userRealm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //关联userRealm
        defaultWebSecurityManager.setRealm(userRealm);
        return  defaultWebSecurityManager;
    }

    // 创建 ream 对象,需要自定义类

    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

    //ShiroDialect:用来整合shiro thymeleaf
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

}

实现用户认证和认证

在MyController 中 编辑请求

 @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //获取当前用户
        Subject subject = SecurityUtils.getSubject();

        //封装用户登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //执行登录
        try {
            subject.login(token);

            return "index";
        } catch (UnknownAccountException e) {//用户不存在
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e) {//密码不存在
            model.addAttribute("msg", "密码错误");
            return "login";
        }


    }
    @RequestMapping("/noauth")
    @ResponseBody
    public String  unexpected(){
        return "未授权不能访问";
    }

在 UserRealm认证和授权

package com.lei.config;

import javax.jws.soap.SOAPBinding.Use;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

/**
 * @author lei
 * @verson:1.8
 */
// 自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //用户名, 密码 数据库中取
        String name ="root";
        String password ="123";
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        if (!userToken.getUsername().equals(name)){
            return null;//抛出异常
        }
        //密码认证,shiro做

        return new SimpleAuthenticationInfo("",password,"");
    }
}

整合数据库

1导入包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.7-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lei</groupId>
    <artifactId>springboot-07-shiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-07-shiro</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--      数据库  -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.11</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>
        <!--
               subject 用户
               SecurityManager 管理所有用户
               Realm 连接数据-->


            <!--  导入shiro包      -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.10.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pluginRepository>
    </pluginRepositories>

</project>

配置durid数据源

application.yml

spring:
  datasource:
    username: root
    password: "0414"
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

      #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

配置mybatis

mybatis.type-aliases-package=com.lei.pojo
mybatis.mapper-locations=classpath:mapper/*.xml

shior认证和授权

package com.lei.config;

import com.lei.pojo.User;
import com.lei.service.UserService;
import javax.jws.soap.SOAPBinding.Use;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @author lei
 * @verson:1.8
 */
// 自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermission("user:add");
        return info;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //用户名, 密码 数据库中取

        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        //连接数据库
        User user = userService.queryByname(userToken.getUsername());

        if (user==null){//没有这些人
            return null;//抛出异常
        }

        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        session.setAttribute("loginUser",user);
        //密码认证,shiro做

        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }
}

整合thymeleaf

导包

<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.1.0</version>
</dependency>

在ShiroConfig中写一个方法

//ShiroDialect:用来整合shiro thymeleaf
@Bean
public ShiroDialect shiroDialect(){
    return new ShiroDialect();
}

前端页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p th:text="${msg}"></p>
<hr>
<!--从session中判断值-->
<div th:if="${session.loginUser==null}">
    <a th:href="@{/toLogin}">登录</a>
</div>
<div shiro:hasPermission="user:add">
    <a th:href="@{/user/add}">add</a>
</div>

<div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">update</a>
</div>



</body>
</html>

11 Swagger

  • RestFul Api 文档在线自动生成工具=》Api文档与Api定义同步更新
  • 直接运行,可以在线测试Api接口
  • 支持多种语言

在项目中使用Swagger需要 springbox;

  • swagger2
  • swaggerui

Spring boot 集成Swagger

1.创建项目

2.导入相关依赖

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

3.编写一个hello工程

4.配置Swagger

/**
 * @author lei
 * @verson:1.8
 */
@Configuration
@EnableSwagger2 //开启swagger
public class SwaggerConfig {
}

swagger版本不兼容

在服务的application.properties配置中添加如下配置:

# 处理SpringBoot2.6.x与Swagger2 3.0.0版本冲突
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

5.测试链接 http://localhost:8080/swagger-ui.html

配置Swagger

Swagger的bean的实例 Dochet

package com.lei.Config;

import java.util.ArrayList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @author lei
 * @verson:1.8
 */
@Configuration
@EnableSwagger2 //开启swagger
public class SwaggerConfig {
    //配置Swagger的Docket的bean实例
    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo());
    }

    //配置swagger信息
    public ApiInfo apiInfo(){
        Contact contact = new Contact("磊", "", "[email protected]");
        return new ApiInfo(
                "磊的SwaggerApi文档",
                "不要放弃",
                "1.0", 
                "urn:tos",
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                 new ArrayList());

    }
}

Swagger配置扫描接口和 配置是否启动swagger

Docket.select()

Docket.enable()

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

    //配置swagger信息
    public ApiInfo apiInfo(){

配置api文档分组

.groupName("磊")

配置多个组

public Docket docket1(){
    return  new Docket(DocumentationType.SWAGGER_2).groupName("SS");
}

实体类配置

package com.lei.pojo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

/**
 * @author lei
 * @verson:1.8
 */
@ApiModel("用户实体类")
public class User {
    @ApiModelProperty("用户名")
    public String username;
    public String password;
}
package com.lei.controller;

import com.lei.pojo.User;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lei
 * @verson:1.8
 */

@RestController
public class HelloController {
    @GetMapping ("/hello")
    public String hello(){
        return "hello";
    }
    //只要接口中有返回实体类
    @ApiOperation("hello控制")
    @PostMapping("/user")
    public User USER(){
        return new User();
    }

}

可以在线测试

可以通过swagger给一些较难理解的属性或接口,增加注释信息

12 任务

异步任务

编写service

package com.lei.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @author lei
 * @verson:1.8
 */
@Service
public class AsyncService {
    //告诉spring这是一个异步方法
    @Async
    public void hello(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("数据正在处理");
    }
}

编写controller

package com.lei.controller;

import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.lei.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lei
 * @verson:1.8
 */
@RestController
public class AsyncController {
    @Autowired
    AsyncService asyncService;

    @RequestMapping("/hello")
    public String hello(){
        asyncService.hello();
        return "ok";
    }
}

在main方法开启异步注解

//开启异步注解
@EnableAsync
@SpringBootApplication
public class Springboot09TestApplication {

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

}

邮件

添加包

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

配置

[email protected]
spring.mail.password=nwehbjceuakvdefb
spring.mail.host=smtp.qq.com
#开启加密认证‘
spring.mail.properties.mail.smtp.ssl.enable=true

测试

package com.lei;

import java.io.File;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;

@SpringBootTest
class Springboot09TestApplicationTests {
@Autowired
    JavaMailSenderImpl mailSender;
    @Test
    void contextLoads() {
        //一个简单的邮件
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setSubject("磊你好啊");
        mailMessage.setText("xxxxxxxx");
        mailMessage.setTo("[email protected]");
        mailMessage.setFrom("[email protected]");
        mailSender.send(mailMessage);
    }
    @Test
    void contextLoads1() throws MessagingException {
        //一个复杂的邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组装
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
        helper.setSubject("磊你好啊");
        helper.setText("<p style='color:red'>谢谢自己吧<p>",true);
        helper.setTo("[email protected]");
        helper.setFrom("[email protected]");
        //附件
        helper.addAttachment("微信图片_20221107150707.jpg",new File("E:\\截图\\微信图片_20221107150707.jpg"));
        mailSender.send(mimeMessage);
    }

}

定时任务

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

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

Cron表达式

在主方法main开启功能注解

//开启定时功能的注解
@EnableScheduling

编写

package com.lei.service;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class ScheduledService {

    //在一个特定的时间执行这个方法
    //cron 表达式
    //秒 分 时 日 月 周几
    @Scheduled(cron = "0 6 0 * * ?")
    public void hello(){
        System.out.println("hello,你被执行了");
    }
}

13 分布式 Dubbo+Zookeeper+Springboot

zookeeper :注册中心

dubbo-admin:是一个监管管理后台-查看我们注册了那些服务,那些服务被销毁

dubbo:jar包

标签:SpringBoot,spring,springframework,org,import,lei,public
From: https://www.cnblogs.com/lei0/p/17015986.html

相关文章