SpringBoot学习笔记

什么是SpringBoot

SpringBoot也是应该JavaWeb框架,基于Spring的框架而成,它的约定大于配置

  • 什么是Spring?
    • Spring是为了解决企业级应用开发的复杂性而创建的,简化开发
  • Spring是如何简化Java开发的
    • 1.基于POJO的轻量级和最小侵入性编程;
    • 2、通过IOC, 依赖注入(DI) 和面向接口实现松耦合;
    • 3、基于切面(AOP)和惯例进行声明式编程;
    • 4、通过切面和模版减少样式代码;
  • SpringBoot的主要优点
    • 为所有Spring开发者更快的入门
    • 开箱即用,提供各种默认配置来简化项目配置
    • 内嵌式容器简化Web项目
    • 没有冗余代码生成和XML配置的要求

微服务

微服务架构

所谓微服务架构,就是打破之前all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些时 可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。
这样做的好处是:
1.节省了调用资源。
2.每个功能元素的服务都是一个可 替换的、可独立升级的软件代码。

如何构建微服务

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

第一个SpringBoot程序

  • 通过官网搭建环境,在官网下载后,导入idea开发
  • 通过idea快速创建应该springboot项目(一般开发直接在idea中创建)
  • 注:新建包时一定要在HelloApplication的同级目录下健包,否则会访问不到

通过官网创建

  1. 进入官网:https://spring.io/

  2. 进入项目下载地址

    image-20220308114823329

    image-20220308114912320

    往下滑

    image-20220308114937463

    输入信息

    image-20220308115317407

    点击即可下载包

    image-20220308115355016

  3. 在idea中导入项目

  4. 项目打开,删除不必要的文件

    image-20220308115514058

  5. 运行进行测试,在主类中点击运行即可

    image-20220308115551551

  6. 游览器访问:http://localhost:8080

    image-20220308115651633

通过idea创建

  1. new

  2. 设置信息

    image-20220308121041689

  3. 导入依赖

    image-20220308121313960

  4. 项目创建成功

    image-20220308121446928

  5. 如果jdk版本与选择的不一致,在project Structure和pom.xml中修改jdk版本

扩展:

  • 更改项目的端口号

    在resources目录下的application.properties文件中添加如下语句

    #更改项目的端口号
    server.port=8081
    
  • 修改spring的bunner(地址:https://www.bootschool.net/ascii)

    在resources目录下新建banner.txt将复制过来的图放进入即可,重新启动项目,效果如下图

    image-20220308122705416

SpringBoot自动装配和配置文件

SpringBoot自动装配原理

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

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会将所有的功能场景,都变成一个个的启动器

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

主程序

//@SpringBootApplication:标注这个类是一个springboot的应用
@SpringBootApplication
public class Springboot01HelloworldApplication {
    public static void main(String[] args) {
        //将springboot启动
  SpringApplication.run(Springboot01HelloworldApplication.class, args);
    }

}
  • 注解

    @SpringBootConfiguration//spring的配置
    	@Configuration//spring配置类
        @Component//说明这也是一个spring的组件
        
    @EnableAutoConfiguration
        @AutoConfigurationPackage//自动配置包
        @Import({Registrar.class})//自动配置包注册
    	@Import({AutoConfigurationImportSelector.class})//自动配置导入选择
    

总结:

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

配置文件

推荐使用yml文件

SpringBoot使用一个全局的配置文件,配置文件 名称是固定的

  • application.properties
    • 语法结构: key=value
  • application.yml
    • 语法结构: key: 空格value

配置文件的作用:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;

示例:

server:
  port: 8081
# 对空格的要求十分高
# 普通的key-value
# 注入到我们的配置类中
name: lzj

# 对象
student:
  name:lzj
  age: 18

# 行内写法
student2: {name: lzj,age: 20}

# 数组
pets:
  - cat
  - dog
  - pig

pets2: [cat,
yaml注入配置文件

yaml配置注入到实体类

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

2、编写一个实体类 Dog,使用@Value注解给bean注入属性值;

@Component
public class Dog {
    @Value("旺财")
    private String name;
    @Value("3")
    private Integer age;
}

3、在SpringBoot的测试类下注入狗狗输出一下;

@SpringBootTest
class Springboot02ConfigApplicationTests {
    @Autowired
    private Dog dog;

    @Test
    void contextLoads() {
        System.out.println(dog);
    }

}

结果成功输出,@Value注入成功

4、我们在编写一个复杂一点的实体类:Person 类

@Component//注册bean
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;
    }

5、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!

person:
  name: lzj
  age: 18
  happy: true
  birth: 2001/01/18
  maps: {k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    name: 黑豆
    age: 5

6、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!

使用@ConfigurationProperties注解时可能会爆红,可以不用管,也可以按照第七步导入依赖

/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component
@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;
}

7、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!

图片

图片

<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 --><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional>
</dependency>

8、确认以上配置都OK之后,我们去测试类中测试一下:

@SpringBootTest
class Springboot02ConfigApplicationTests {
    @Autowired
    private Person person;

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

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CvKgEknJ-1647408744276)(SpringBoot学习笔记.assets/image-20220308233037771.png)]

结果:所有值全部注入成功!

yaml配置注入到实体类完全OK!

properties注入配置

采用@PropertySource注解和@Value注解

【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;

settings–>FileEncodings 中配置;

image-20220308234856453

resources目录下编写lzj.properties文件

name=lzj

实体类中引用

@Component
//javaConfig 绑定我们配置文件的值,可以采取这些方式
//加载指定配置文件
@PropertySource(value = "classpath:lzj.properties")
public class Person {
    //SPEL表达式取出配置文件的值
    @Value("${name}")
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
}
对比

图片

JSR303校验

@Validated

  1. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    
  2. 实体类中使用

    @Component
    @ConfigurationProperties(prefix = "person")
    @Validated//数据校验
    public class Person {
        @Email(message="邮箱格式错误")//设置被注释的元素必须为邮箱格式,否则返回message
        private String name;
        private Integer age;
        private Boolean happy;
        private Date birth;
        private Map<String,Object> maps;
        private List<Object> lists;
        private Dog dog;
    }
    
  3. 基本校验规则

    Bean Validation 中内置的 constraint

    Constraint详细信息
    @Null被注释的元素必须为 null
    @NotNull被注释的元素必须不为 null
    @AssertTrue被注释的元素必须为 true
    @AssertFalse被注释的元素必须为 false
    @Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @Size(max, min)被注释的元素的大小必须在指定的范围内
    @Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
    @Past被注释的元素必须是一个过去的日期
    @Future被注释的元素必须是一个将来的日期
    @Pattern(value)被注释的元素必须符合指定的正则表达式

    Hibernate Validator 附加的 constraint

    Constraint详细信息
    @Email被注释的元素必须是电子邮箱地址
    @Length被注释的字符串的大小必须在指定的范围内
    @NotEmpty被注释的字符串的必须非空
    @Range被注释的元素必须在合适的范围内

Web应用(未使用数据库)

编写Web前准备

静态资源导入问题
  1. 在springboot中,我们可以使用以下方式处理静态资源

    https://www.webjars.org/

    • webjars localhost:8080/webjars/

      示例(导入依赖):

      <dependency>
          <groupId>org.webjars.npm</groupId>
          <artifactId>jquery</artifactId>
          <version>3.6.0</version>
      </dependency>
      
    • 在resources目录下的resources、static、public包 localhost:8080/

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

  3. 在templates目录下的所有页面,只能通过controller来跳转(需要模板引擎的支持)

模板引擎

与springmvc中的视图解析器相识,可以让controller访问template下的html页面

thymeleaf

官方使用文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#attribute-precedence

public static final String DEFAULT_PREFIX = "classpath:/templates/";

public static final String DEFAULT_SUFFIX = ".html";
  1. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  2. 在需要使用的html页的标签后加上约束

    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
  3. 更多语法可以查看官方文档

修改mvc的默认配置

比如默认的时间格式等等

首页配置和国际化

首页配置

页面的所有静态资源都需要使用thymeleaf接管

使用thymeleaf需要先在头部导入约束

访问url需要使用 @{}

# 关闭模板引擎的缓存
spring.thymeleaf.cache=false
# 我们的配置文件的真实位置
spring.messages.basename=i18n.login
#时间日期格式化
spring.mvc.format.date=yyyy-MM-dd
国际化

示例为将首页国际化

  1. 配置i18n文件(在resources目录下创建i18n目录,然后再创建如下文件)

    login.properties

    login.btn=登录
    login.password=密码
    login.remember=记住我
    login.tip=请登录
    login.username=用户名
    

    login_en_US.properties

    login.btn=Sign in
    login.password=password
    login.remember=Remember me
    login.tip=Please sign in
    login.username=Username
    

    login_zh_CN.properties

    login.btn=登录
    login.password=密码
    login.remember=记住我
    login.tip=请登录
    login.username=用户名
    
  2. 编写application.properties文件,进行配置

    # 我们的配置文件的真实位置
    spring.messages.basename=i18n.login
    
  3. 修改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 Bootstrap</title>
          <!-- 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" 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}">Please sign in</h1>
             <label class="sr-only">Username</label>
             <input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
             <label class="sr-only">Password</label>
             <input type="password" class="form-control" th:placeholder="#{login.password}" required="">
             <div class="checkbox mb-3">
                <label>
              <input type="checkbox" value="remember-me">[[#{login.remember}]]
            </label>
             </div>
             <button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button>
             <p class="mt-5 mb-3 text-muted">© 2017-2018</p>
             <a class="btn btn-sm" th:href="@{/index(l='zh_CN')}">中文</a>
             <a class="btn btn-sm" th:href="@{/index(l='en_US')}">English</a>
          </form>
    
       </body>
    
    </html>
    
  4. 在config包下创建MyLocaleResolver

    public class MyLocaleResolver implements LocaleResolver {
        //解析请求
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            //获取请求中的语言参数
            String language = request.getParameter("l");
            Locale locale = Locale.getDefault();//如果没有就使用默认的
            //如果请求的连接携带了国际化的参数
            if(!StringUtils.isEmpty(language)){
                //zh_CN
                String[] split = language.split("_");
                //国家,地区
                locale=new Locale(split[0],split[1]);
    
            }
            return locale;
        }
    
        @Override
        public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    
        }
    }
    
  5. 在config包下创建MyMvcConfig,将MyLocaleResolver载入spring容器中

    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
        //自定义的国际化组件就生效了
        @Bean
        public LocaleResolver localeResolver(){
            return new MyLocaleResolver();
        }
    }
    
  6. 检查Controller(检查是否能够访问index)

    @Controller
    public class IndexController {
        //地址栏访问这两个路径都可以访问首页
        @RequestMapping({"/","/index"})
        public String index(){
            return "index";
        }
    }
    
  7. 测试

    image-20220310203455174

登录功能的实现

  1. 编写controller

    @Controller
    public class LoginController {
        @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);
                System.out.println("成功");
                return "redirect:/main";
            }else {
                model.addAttribute("msg","用户名或者密码错误");
                System.out.println("错误");
                return "index";
            }
        }
    
        @RequestMapping("/main")
        public String dashboard(){
            return "dashboard";
        }
    }
    
  2. 修改html页面中的表单action,判断msg返回值是否未空

    <form class="form-signin" th:action="@{/user/login}" method="post">
    			<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}">Please sign in</h1>
        
    <!--			如果msg的值为空,则不显示消息-->
    			<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
    
    			<label class="sr-only">Username</label>
    			<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
    			<label class="sr-only">Password</label>
    			<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">[[#{login.remember}]]
            </label>
    			</div>
    			<button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button>
    			<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
    			<a class="btn btn-sm" th:href="@{/index(l='zh_CN')}">中文</a>
    			<a class="btn btn-sm" th:href="@{/index(l='en_US')}">English</a>
    		</form>
    

编写拦截器

  1. 在config目录下创建LoginHandlerInterceptor类

    public class LoginHandlerInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            Object loginUser = request.getSession().getAttribute("loginUser");
            if(loginUser==null){
                request.setAttribute("msg","没有权限,请先登录");
                request.getRequestDispatcher("/").forward(request,response);;
                return false;
            }else {
                return true;
            }
        }
    }
    
  2. 在MyMvcConfig类中配置拦截器到spring容器中

    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
    
        public void addInterceptors(InterceptorRegistry registry){
            registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/","/index","/user/login","/css/**","/img/**","/js/**");
        }
    }
    
  3. 测试

展示员工列表

实体类

Department

@Data
@AllArgsConstructor
@NoArgsConstructor
//部门表
public class Department {
    private Integer id;
    private String departmentName;
}

Employee

@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层模仿数据库

DepartmentDao

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

EmployeeDao

@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,"AA","A123456789@qq.com",0,new Department(101,"教学部")));
        employees.put(1002,new Employee(1002,"BB","B123456789@qq.com",1,new Department(102,"市场部")));
        employees.put(1003,new Employee(1003,"CC","C123456789@qq.com",0,new Department(103,"教研部")));
        employees.put(1004,new Employee(1004,"DD","D123456789@qq.com",1,new Department(104,"运营部")));
        employees.put(1005,new Employee(1005,"EE","E123456789@qq.com",0,new Department(105,"后勤部")));
    }
    //主键自增
    private static Integer initId=1006;
    //增加一个员工
    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);
    }
}
提取公共页面
  1. 在需要提取出来的标签中加入如下语句

    th:fragment="sidebar"
    
  2. 在需要引用的位置加入如下语句

    commons/commons为commons路径下的comoms.html文件

    sidebar为自定义的名字

    后面括号内的为需要传参的值

    <div th:insert="~{commons/commons::sidebar(active='main.html')}"></div>
    
  3. 另一页面接收判断值

    使用${}取值判断,使用三元运算符进行判断

    "${active=='main.html'?'nav-link active':'nav-link'}"
    
列表循环展示
  • 使用三元判断输出男女

  • 使用thymeleaf将时间进行格式化输出

<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.getDepartment().getDepartmentName()}"></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>

添加员工

  1. 编写controller

    @GetMapping("/emp")
    public String toAddpage(Model model){
        //查出所有部门的信息
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);
        return "/emp/add";
    }
    @PostMapping("/emp")
    public String addEmp(Employee employee){
        System.out.println("save=>"+employee);
        employeeDao.save(employee);
        //查出所有部门的信息
        return "redirect:/emps";
    }
    
  2. 编写前端from表单

    <form th:action="@{/emp}" method="post">
                      <div class="form-group">
                         <label>LastName</label>
                         <input name="lastName" type="text" class="form-control" placeholder="lzj">
                      </div>
                      <div class="form-group">
                         <label>Email</label>
                         <input name="email" type="email" class="form-control" placeholder="1481315703@qq.com">
                      </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">
    <!--                        我们在controller接收的是一个Employee,所以我们需要提交的是其中的一个属性-->
                            <option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
                         </select>
                      </div>
                      <div class="form-group">
                         <label>Birth</label>
                         <input name="birth" type="text" class="form-control" placeholder="嘤嘤嘤">
                      </div>
                      <button type="submit" class="btn btn-primary">添加</button>
                   </form>
    
  3. 修改时间格式

    #时间日期格式化
    spring.mvc.format.date=yyyy-MM-dd
    

修改员工信息

  1. 编写controller

    //全部员工页面传递id值过来
    @GetMapping("/emp/{id}")
    public String toUpdateEmp(@PathVariable("id") Integer id,Model model){
        Employee employee = employeeDao.getEmployeeById(id);
        model.addAttribute("emp",employee);
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);
        System.out.println(employee);
        return "emp/update";
    
    }
    
    @PostMapping("/updateEmp")
    public String updateEmp(Employee employee){
        employeeDao.save(employee);
        return "redirect:/emps";
    }
    
  2. 编写前端from表单

                    <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()}" name="lastName" type="text" class="form-control" placeholder="lzj">
                      </div>
                      <div class="form-group">
                         <label>Email</label>
                         <input th:value="${emp.getEmail()}" name="email" type="email" class="form-control" placeholder="1481315703@qq.com">
                      </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">
    <!--                        我们在controller接收的是一个Employee,所以我们需要提交的是其中的一个属性-->
                            <option th:selected="${dept.getId()}==${emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
                         </select>
                      </div>
                      <div class="form-group">
                         <label>Birth</label>
                         <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" name="birth" type="text" class="form-control" placeholder="嘤嘤嘤">
                      </div>
                      <button type="submit" class="btn btn-primary">修改</button>
                   </form>
    

    删除员工、404页面和注销

    1. 删除员工

      • 编写controller

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

      在templates目录下创建error文件夹,将404.html页面放进去即可

    3. 注销

      • 编写controller

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

数据库操作相关

整合JDBC使用

默认数据源为:com.zaxxer.hikari.HikariDataSourc

HikariDataSource号称Java WEB当前速度最快的数据源,相比于传统的C3P0、DBCP、 Tomcat jdbc等连接池更加优秀;

  1. 创建项目,选择依赖

    image-20220312105556365

  2. 创建application.yml,将数据库信息写入

    spring:
      datasource:
        username: root
        password: root
        url: jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
    
  3. 进行测试查看是否连接成功

    测试类中注入DataSource

    @SpringBootTest
    class Springboot04DataApplicationTests {
        @Autowired
        DataSource dataSource;
    
        @Test
        void contextLoads() throws SQLException {
            //查看一下默认的数据源:com.zaxxer.hikari.HikariDataSourc
            System.out.println(dataSource.getClass());
            //获得数据库连接
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            //****Template:SpringBoot已经配置好模板bean,拿来即用 CRUD
    
            connection.close();
        }
    
    }
    
  4. 使用配置好的模板bean来进行crud,查询数据库信息

    SpringBoot配置好的模板:JdbcTemplate,直接使用即可

    @RestController
    public class JDBCController {
        @Autowired
        JdbcTemplate jdbcTemplate;
        //查询数据库的所有信息
        @RequestMapping("/userList")
        public List<Map<String,Object>> userList(){
            String sql="select * from user";
            List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
            return maps;
        }
    
        @RequestMapping("/addUser")
        public String addUser(){
            String sql="insert into mybatis.user(id,name,pwd) values (4,'小明','123456')";
            jdbcTemplate.update(sql);
            return "update-ok";
        }
        @RequestMapping("/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]="zzzzzzzz";
            jdbcTemplate.update(sql,objects);
            return "updateUser-ok";
        }
        @RequestMapping("/deleteUser/{id}")
        public String deleteUser(@PathVariable("id") int id){
            String sql="delete from mybatis.user where id=?";
            jdbcTemplate.update(sql,id);
            return "deleteUser-ok";
        }
    }
    
  5. 测试是否成功

整合使用自定义的Druid数据源

  1. 导入依赖

    <!--        druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
    
  2. 配置application.yml,修改默认数据源

    spring:
      datasource:
        username: root
        password: root
        url: jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    
  3. 测试是否切换成功

    @SpringBootTest
    class Springboot04DataApplicationTests {
        @Autowired
        DataSource dataSource;
    
        @Test
        void contextLoads() throws SQLException {
            //查看一下默认的数据源:com.zaxxer.hikari.HikariDataSourc
            System.out.println(dataSource.getClass());
            //获得数据库连接
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            //****Template:SpringBoot已经配置好模板bean,拿来即用 CRUD
    
            connection.close();
        }
    
    }
    
  4. 设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项

    spring:
      datasource:
        username: root
        password: root
        url: jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        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
    
  5. 导入log4j的依赖

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
  6. 点击运行项目,查看是否运行成功

  7. 编写DruidConfig类

    因为SpringBoot内置了servlet容器,所以没有web.xml,需要配置的东西需要在config类中进行配置

    @Configuration
    public class DruidConfig {
        @ConfigurationProperties(prefix = "spring.datasource")
        @Bean
        public DataSource druidDataSource(){
            return new DruidDataSource();
        }
    
        //后台监控: web.xml     ServletRegistrationBean
        //因为SpringBoot内置了servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
        @Bean
        public ServletRegistrationBean statViewServlet(){
            ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
            //后台需要有人登录,账号密码配置
            HashMap<String, String> initParameters = new HashMap<>();
            //增加配置
            initParameters.put("loginUsername","admin");//key是固定的
            initParameters.put("loginPassword","123456");//key是固定的
            //允许谁可以访问
            // value:localhost只有本机可以访问
            //value为空则所有人都可以访问
            initParameters.put("allow","");
            //禁止谁能够访问
    //        initParameters.put("lzj","192.168.11.111");
            bean.setInitParameters(initParameters);//设置初始化参数
            return bean;
        }
    
        //filter
        public FilterRegistrationBean webStatFilter(){
            FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
            //设置过滤器
            bean.setFilter(new WebStatFilter());
            //可以过滤哪些请求
            HashMap<String, String> initParameters = new HashMap<>();
            //这些东西不进行统计
            initParameters.put("exclusions","*.js,*.css,/druid/*");
            bean.setInitParameters(initParameters);
    
            return bean;
        }
    }
    
  8. 运行项目进行测试

    • 访问http://localhost:8080/druid,输入在config中配置的账号密码后可进入后台,查看使用状态

      image-20220312144204323

整合mybatis框架

  1. 新建项目,选择需要的模板

    image-20220312145024342

  2. 导入依赖

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>
    
  3. 编写数据库配置和整合mybatis配置

    application.properties

    spring.datasource.username=root
    spring.datasource.password=root
    spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
    # 整合mybatis
    mybatis.type-aliases-package=com.lzj.entity
    mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
    
  4. 编写实体类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Component
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    
  5. 编写UserMapper和UserMapper.xml

    UserMapper

    //这个注解表示了这是员工mybatis的mapper类
    @Mapper
    @Repository
    public interface UserMapper {
        List<User> queryUserList();
        User queryUserById(int id);
        int addUser(User user);
        int updateUser(User user);
        int deleteUser(int id);
    }
    

    UserMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.lzj.mapper.UserMapper">
        <select id="queryUserList" resultType="User">
            select * from user
        </select>
        <select id="selectUserById" resultType="User">
            select * from user where id = #{id}
        </select>
        <insert id="addUser" parameterType="User">
            insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
        </insert>
        <update id="updateUser" parameterType="User">
            update user set name=#{name},pwd=#{pwd} where id=#{id}
        </update>
        <delete id="deleteUser">
            delete from user where id=#{id}
        </delete>
    </mapper>
    
  6. 编写Service层

  7. 编写controller层

    @RestController
    public class UserController {
        @Autowired
        private UserService userService;
        @RequestMapping("/queryUserList")
        public List<User> queryUserList(){
            List<User> userList = userService.queryUserList();
            return userList;
        }
    }
    
  8. 测试

SpringSecurity(安全)

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

记住几个类:

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

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

“认证”(Authentication)

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

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

“授权” (Authorization)

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

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

实验环境搭建

  1. 新建一个项目

    这里记得要选择thymeleaf

  2. 导入静态资源

    • 静态资源获取:https://gitee.com/ENNRIAAA/spring-security-material

      image-20220312193101453

  3. 创建路由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;
        }
    
    }
    
  4. 运行项目进行测试

认证和授权

  1. 导入依赖

    <!--        security-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  2. 编写SecurityConfig

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        //授权
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //首页所有人可以访问,功能页只有对应有权限的人才能访问
            http.authorizeHttpRequests()
                    .antMatchers("/").permitAll()
                    .antMatchers("/level1/**").hasRole("vip1")
                    .antMatchers("/level2/**").hasRole("vip2")
                    .antMatchers("/level3/**").hasRole("vip3");
            //没有权限会默认到登录页,需要开启登录的页面
            // /login
            http.formLogin();
    
            //防止网站攻击: get,post
            http.csrf().disable();//关闭csrf功能,登出失败可能存在的原因
    
    
            //注销,开启了注销功能,跳到首页
            http.logout().logoutSuccessUrl("/");
        }
    
        //认证
        //密码编码:PasswordEncoder
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //这些数据正常应该从数据库中读
            auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                    .withUser("lzj").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                    .and()
                    .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                    .and()
                    .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
    
        }
    }
    
  3. 测试

注销和权限控制

  1. 导入依赖

    <!--        security-thymeleaf整合包-->
            <dependency>
                <groupId>org.thymeleaf.extras</groupId>
                <artifactId>thymeleaf-extras-springsecurity5</artifactId>
                <version>3.0.4.RELEASE</version>
            </dependency>
    
  2. 注入命名空间

    xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
    
  3. 修改登录和注销按钮

    实现用户未登录时只显示登录,用户登录后显示用户名、角色和注销

    <!--登录注销-->
                <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">
                            用户名:<span sec:authentication="name"></span>
                            角色:<span sec:authentication="authorities"></span>
                        </a>
                    </div>
                    <div sec:authorize="isAuthenticated()">
                        <a class="item" th:href="@{/logout}">
                            <i class="sign-out icon"></i> 注销
                        </a>
                    </div>
    
                    <!--已登录
                    <a th:href="@{/usr/toUserCenter}">
                        <i class="address card icon"></i> admin
                    </a>
                    -->
                </div>
    
  4. 修改用户的权限显示

    <!--            菜单根据用户的角色动态实现-->
                <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>
    
  5. 测试

记住我和首页定制

可以使用我们自己的登录页登录

  1. 编写SecurityConfig

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        //授权
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //首页所有人可以访问,功能页只有对应有权限的人才能访问
            http.authorizeHttpRequests()
                    .antMatchers("/").permitAll()
                    .antMatchers("/level1/**").hasRole("vip1")
                    .antMatchers("/level2/**").hasRole("vip2")
                    .antMatchers("/level3/**").hasRole("vip3");
            //没有权限会默认到登录页,需要开启登录的页面
            // /login
            //定制根页面,user为表单中username的name,pwd为表单中password的name
            //loginProcessingUrl为表单跳转路径,若无这个,则跳转路径为toLogin
            http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
    
            //防止网站攻击: get,post
            http.csrf().disable();//关闭csrf功能,登出失败可能存在的原因
    
            //注销,开启了注销功能,跳到首页
            http.logout().logoutSuccessUrl("/");
    
            //开启记住我功能   cookie  默认保存时间14//自定义接收前端参数,remember为表单中的名字
            http.rememberMe().rememberMeParameter("remember");
        }
    
        //认证
        //密码编码:PasswordEncoder
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //这些数据正常应该从数据库中读
            auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                    .withUser("lzj").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                    .and()
                    .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                    .and()
                    .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
    
        }
    }
    
  2. 修改登录页面表单

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

Shiro

异步、定时和邮件任务

异步任务

  1. 创建一个service包

  2. 创建一个类AsyncService

    异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。

    编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;

    @Service
    public class AsyncService {
    
       public void hello(){
           try {
               Thread.sleep(3000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           System.out.println("业务进行中....");
      }
    }
    
  3. 编写controller包

  4. 编写AsyncController类

    我们去写一个Controller测试一下

    @RestController
    public class AsyncController {
    
       @Autowired
       AsyncService asyncService;
    
       @GetMapping("/hello")
       public String hello(){
           asyncService.hello();
           return "success";
      }
    
    }
    
  5. 访问http://localhost:8080/hello进行测试,3秒后出现success,这是同步等待的情况。

    问题:我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:

  6. 给hello方法添加**@Async**注解;

    //告诉Spring这是一个异步方法
    @Async
    public void hello(){
       try {
           Thread.sleep(3000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println("业务进行中....");
    }
    

    SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解**@EnableAsync** ,开启异步注解功能;

    @EnableAsync //开启异步注解功能
    @SpringBootApplication
    public class SpringbootTaskApplication {
    
       public static void main(String[] args) {
           SpringApplication.run(SpringbootTaskApplication.class, args);
      }
    
    }
    
  7. 重启测试,网页瞬间响应,后台代码依旧执行!

邮件任务

  1. 引入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    
  2. 编写application.properties

    spring.mail.username=1481315703@qq.com
    # 自己qq邮箱的授权码
    spring.mail.password=你的qq邮箱授权码
    spring.mail.host=smtp.qq.com
    # 开启加密验证,qq需要配置ssl
    spring.mail.properties.mail.smtp.ssl.enable=true
    

    如何获取授权码?

    image-20220314212144316

  3. Spring单元测试

    @SpringBootTest
    class Springboot07TestApplicationTests {
        @Autowired
        JavaMailSenderImpl mailSender;
        @Test
        void contextLoads() {
            //一个简单的邮件
            SimpleMailMessage mailMessage = new SimpleMailMessage();
            mailMessage.setSubject("lzj你好");
            mailMessage.setText("好好学习Java");
            mailMessage.setTo("1481315703@qq.com");
            mailMessage.setFrom("1481315703@qq.com");
    
            mailSender.send(mailMessage);
        }
        
        @Test
        void contextLoads2() throws MessagingException {
            //一个复杂的邮件
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            //组装
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
            helper.setSubject("lzj你好,plus");
            //加了true后可以识别为html,在邮件中会带有相关样式
            helper.setText("<p style='color:red'>好好学习Java呀</p>",true);
            //附件
            helper.addAttachment("mmexport1565261735921.jpg.JPG", new File("C:\\Users\\86158\\Desktop\\证件照\\mmexport1565261735921.jpg.JPG"));
    
            helper.setTo("1481315703@qq.com");
            helper.setFrom("1481315703@qq.com");
    
            mailSender.send(mimeMessage);
        }
    
    }
    
  4. 效果

    image-20220314213242806

定时任务

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

  • TaskExecutor接口 任务执行者
  • TaskScheduler接口 任务调度者

两个注解:

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

cron表达式

是否必需取值范围特殊字符
[0, 59]* , - /
分钟[0, 59]* , - /
小时[0, 23]* , - /
日期[1, 31]* , - / ? L W
月份[1, 12]或[JAN, DEC]* , - /
星期[1, 7]或[MON, SUN]。若您使用[1, 7]表达方式,1代表星期一,7代表星期日。* , - / ? L #

特殊字符

特殊字符含义示例
*所有可能的值。在月域中,*表示每个月;在星期域中,*表示星期的每一天。
,列出枚举值。在分钟域中,5,20表示分别在5分钟和20分钟触发一次。
-范围。在分钟域中,5-20表示从5分钟到20分钟之间每隔一分钟触发一次。
/指定数值的增量。在分钟域中,0/15表示从第0分钟开始,每15分钟。在分钟域中3/20表示从第3分钟开始,每20分钟。
?不指定值,仅日期和星期域支持该字符。当日期或星期域其中之一被指定了值以后,为了避免冲突,需要将另一个域的值设为?
L单词Last的首字母,表示最后一天,仅日期和星期域支持该字符。说明 指定L字符时,避免指定列表或者范围,否则,会导致逻辑问题。在日期域中,L表示某个月的最后一天。在星期域中,L表示一个星期的最后一天,也就是星期日(SUN)。如果在L前有具体的内容,例如,在星期域中的6L表示这个月的最后一个星期六。
W除周末以外的有效工作日,在离指定日期的最近的有效工作日触发事件。W字符寻找最近有效工作日时不会跨过当前月份,连用字符LW时表示为指定月份的最后一个工作日。在日期域中5W,如果5日是星期六,则将在最近的工作日星期五,即4日触发。如果5日是星期天,则将在最近的工作日星期一,即6日触发;如果5日在星期一到星期五中的一天,则就在5日触发。
#确定每个月第几个星期几,仅星期域支持该字符。在星期域中,4#2表示某月的第二个星期四。

测试步骤

  1. 创建一个ScheduledService,存在一个hello方法,他需要定时执行(使用@Scheduled注解)

    @Service
    public class ScheduledService {
        //在一个特定的时间执行这个方法
    
        //cron表达式
        //秒 分   时   日   月   周几
        /*
            30 15 10 * * ?  每天10点15分30秒执行一次
            30 0/5 10,18 * * ? 每天10点和18点,每隔五分钟执行一次
            0 15 10 ? * 1-6 每个月的周一到周六10点15分执行一次
    
         */
        @Scheduled(cron = "0 * * * * 0-7")
        public void hello(){
            System.out.println("hello,你被执行了");
        }
    }
    
  2. 这里写完定时任务之后,我们需要在主程序上增加**@EnableScheduling** 开启定时任务功能

    @SpringBootApplication
    @EnableAsync//开启异步注解功能
    @EnableScheduling//开启定时功能的注解
    public class Springboot07TestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Springboot07TestApplication.class, args);
        }
    
    }
    
  3. cron表达式在线生成网站:https://cron.qqe2.com/

  4. 常用的cron表达式示例

    示例说明
    0 15 10 ? * *每天上午10:15执行任务
    0 15 10 * * ?每天上午10:15执行任务
    0 0 12 * * ?每天中午12:00执行任务
    0 0 10,14,16 * * ?每天上午10:00点、下午14:00以及下午16:00执行任务
    0 0/30 9-17 * * ?每天上午09:00到下午17:00时间段内每隔半小时执行任务
    0 * 14 * * ?每天下午14:00到下午14:59时间段内每隔1分钟执行任务
    0 0-5 14 * * ?每天下午14:00到下午14:05时间段内每隔1分钟执行任务
    0 0/5 14 * * ?每天下午14:00到下午14:55时间段内每隔5分钟执行任务
    0 0/5 14,18 * * ?每天下午14:00到下午14:55、下午18:00到下午18:55时间段内每隔5分钟执行任务
    0 0 12 ? * WED每个星期三中午12:00执行任务
    0 15 10 15 * ?每月15日上午10:15执行任务
    0 15 10 L * ?每月最后一日上午10:15执行任务
    0 15 10 ? * 6L每月最后一个星期六上午10:15执行任务
    0 15 10 ? * 6#3每月第三个星期六上午10:15执行任务
    0 10,44 14 ? 3 WED每年3月的每个星期三下午14:10和14:44执行任务

Dubbo和Zookeeper

Window下安装zookeeper

注册中心

  1. 下载zookeeper,官网地址:https://zookeeper.apache.org/

    我选择下载的是apache-zookeeper-3.5.9-bin.tar.gz

    步骤:

    image-20220315194515823

    image-20220315194632309

    image-20220315194745795

    image-20220315194814807

    image-20220315194858973

    image-20220315194926451

  2. 打开下载的文件(管理员模式打开),运行/bin/zkServer.cmd,初次运行时可能会报错,因为没有zoo.cfg

    也有可能遇到闪退的问题!

    闪退的解决方案:右键编辑zkServer.cmd文件,在末尾添加pauser(如果右键不能点击编辑,可以将文件的后缀名改为txt,然后修改完后再改回cmd)

    image-20220315195317861

  3. 修改zoo.cfg配置文件

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

    注意几个重要位置:

    dataDir=./ 临时数据存储的目录(可写相对路径)

    clientPort=2181 zookeeper的端口号

    修改完成后再次启动zookeeper

  4. 打开zkCli.cmd进行测试(在打开zkCli.cmd前,要先将zkServer.cmd打开)

    ls /:列出zookeeper根下保存的所有节点

    create –e /lzj123:创建一个lzj节点,值为123

    get /lzj:获取/lzj节点的值

Window下安装Dubbo-admin

注意:dubbo-admin可能会使用20880端口,所以在编写生产者的时候要修改生产者的dubbo的端口号

是一个监控管理后台,查看我们注册了哪些服务,哪些服务被消费了

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

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

安装步骤:

  1. 下载dubbo-admin

    https://github.com/apache/dubbo-admin

    左侧选择master后在code中下载zip

    image-20220315201441763

  2. 进入解压后的目录,进入\dubbo-admin-server\src\main\resources

    该目录下有一个application.properties,双击打开该文件

    由于dubbo-admin后台服务端口默认为8080,可能会与tomcat的端口产生冲突,所以我们需要在application.properties中修改默认端口

    修改方法:在文件末尾加入server.port=7001,即将其默认端口修改为7001

    其它需要修改的可以参考如下:(我的话是只修改了端口号就可以了

    server.port=7001
    spring.velocity.cache=false
    spring.velocity.charset=UTF-8
    spring.velocity.layout-url=/templates/default.vm
    spring.messages.fallback-to-system-locale=false
    spring.messages.basename=i18n/message
    spring.root.password=root
    spring.guest.password=guest
    
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    
  3. 项目目录下cmd中打包dubbo-admin(第一次打包过程可能有点慢)

    mvn clean package -Dmaven.test.skip=true
    

    image-20220315201811796

  4. 打包完成后,会生成如图的jar包,可能不是这个名字

    image-20220315204659543

  5. 进入\dubbo-admin-distribution\target\下可以找到这个jar包(应该就在这个位置,如果不在这个位置,可以去其它文件夹的target目录下找一找)

    image-20220315204751378

  6. 执行 \dubbo-admin-distribution\target\ 下的dubbo-admin-0.3.0.jar(前提:zookeeper的服务一定要打开,即运行zkServer.cmd

    打开cmd在该路径下运行:

    java -jar dubbo-admin-0.3.0.jar
    
  7. 执行完毕,我们去访问一下http://localhost:7001/,这时候我们需要输入登录账户和密码,我们都是默认的root,如果需要修改的话,可以在第二步的properties中修改,然后重新打包

    image-20220315205325079

SpringBoot + Dubbo + zookeeper

框架搭建
  1. 创建一个springboot项目,项目名:provider-server,选择web依赖即可

  2. 在项目中编写一个服务

    创建一个service包

    编写接口

    public interface TicketService {
        public String getTicket();
    }
    

    编写实现类

    @Service//可以被扫描到,在项目启动就自动注册到注册中心,一定要注意,这个是dubbo包里的
    @Component//这里不使用@Service是为了方便区分dubbo和spring包下的@Service
    public class TicketServiceImpl implements TicketService{
        @Override
        public String getTicket() {
            return "LZJ学习java";
        }
    }
    
  3. 再创建一个模块,实现服务消费者,模块名:consumer-server

  4. 在consumer-server项目下写一个需要获得的服务

    public class UserService {
       //我们需要去拿去注册中心的服务
    }
    

需求:现在我们的用户想使用买票的服务,这要怎么弄呢 ?

服务提供者

编写provider-server项目

  1. 服务提供者注册到注册中心,我们需要整合Dubbo和zookeeper,所以需要导包

    <!-- dubbo -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.3</version>
    </dependency>
    <!-- zkclient -->
    <dependency>
        <groupId>com.github.sgroschupf</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.1</version>
    </dependency>
    <!--        日志会冲突-->
    <!-- 引入zookeeper -->
    <dependency>
        <!-- curator版本建议先用我这个,全部测试成功后再尝试升版本,否则可能报错
     	curator的版本会跟dubbo-stater依赖版本有关
    	-->
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <!-- 这个zookeeper的版本要跟你本地下载的一致 -->
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.5.9</version>
        <!--排除这个slf4j-log4j12-->
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
  2. 在application.properties文件中配置属性

    dubbo-admin可能会占用dubbo默认的20880端口,所以这里我们将dubbo的端口修改为20881

    server.port=8001
    
    #dubbo.protocol.port=20881
    # 服务应用名字
    dubbo.application.name=provider-server
    # 注册中心地址
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    # 哪些服务要被注册
    dubbo.scan.base-packages=com.lzj.service
    # 修改dubbo默认端口号
    dubbo.protocol.port=20881
    
  3. 测试

    逻辑理解 :应用启动起来,dubbo就会扫描指定的包下带有@component注解的服务,将它发布在指定的注册中心中!

    **注意:**在idea中启动项目时一定要先启动zookeeper和dubbo-admin生成的jar包,如果没有启动dubbo-admin,则无法在网页上访问7001端口进入可视化界面

    显示出来你的服务则配置成功

    image-20220316091446906

服务消费者

编写consumer-server项目

  1. 导入依赖,和之前一样

    <!-- dubbo -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.3</version>
    </dependency>
    <!-- zkclient -->
    <dependency>
        <groupId>com.github.sgroschupf</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.1</version>
    </dependency>
    <!--        日志会冲突-->
    <!-- 引入zookeeper -->
    <dependency>
        <!-- curator版本建议先用我这个,全部测试成功后再尝试升版本,否则可能报错
     	curator的版本会跟dubbo-stater依赖版本有关
    	-->
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <!-- 这个zookeeper的版本要跟你本地下载的一致 -->
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.5.9</version>
        <!--排除这个slf4j-log4j12-->
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
  2. 编写application.properties

    server.port=8002
    
    # 消费者去哪里拿服务需要暴露自己的名字
    dubbo.application.name=consumer-server
    # 注册中心的地址
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    
  3. 本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同

    image-20220316092008951

  4. 完善消费者的服务类

    @Service//放到容器中
    public class UserService {
    
        //想拿到provider-server中的票
        @Reference//引用,     Pom坐标,可以定义路径相同的接口
        TicketService ticketService;
        public void buyTicket(){
            String ticket = ticketService.getTicket();
            System.out.println("在注册中心拿到=>"+ticket);
        }
    }
    
  5. 编写测试类

    @SpringBootTest
    class ConsumerServerApplicationTests {
    
        @Autowired
        UserService userService;
    
        @Test
        void contextLoads() {
            userService.buyTicket();
        }
    
    }
    
  6. 启动测试

    1. 开启zookeeper

    2. 开启dubbo-admin【可以不用做】

    3. 开启服务者(provider-server)

    4. 消费者消费测试

      image-20220316092351015

      测试访问成功!
      che.dubbo
      dubbo-spring-boot-starter
      2.7.3

    com.github.sgroschupf zkclient 0.1 org.apache.curator curator-framework 2.12.0 org.apache.curator curator-recipes 2.12.0 org.apache.zookeeper zookeeper 3.5.9 org.slf4j slf4j-log4j12 ```
  7. 编写application.properties

    server.port=8002
    
    # 消费者去哪里拿服务需要暴露自己的名字
    dubbo.application.name=consumer-server
    # 注册中心的地址
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    
  8. 本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同

    [外链图片转存中…(img-meOEXHD2-1647408744307)]

  9. 完善消费者的服务类

    @Service//放到容器中
    public class UserService {
    
        //想拿到provider-server中的票
        @Reference//引用,     Pom坐标,可以定义路径相同的接口
        TicketService ticketService;
        public void buyTicket(){
            String ticket = ticketService.getTicket();
            System.out.println("在注册中心拿到=>"+ticket);
        }
    }
    
  10. 编写测试类

    @SpringBootTest
    class ConsumerServerApplicationTests {
    
        @Autowired
        UserService userService;
    
        @Test
        void contextLoads() {
            userService.buyTicket();
        }
    
    }
    
  11. 启动测试

    1. 开启zookeeper

    2. 开启dubbo-admin【可以不用做】

    3. 开启服务者(provider-server)

    4. 消费者消费测试

      [外链图片转存中…(img-jiW2h1Zp-1647408744308)]

      测试访问成功!

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱学习的大雄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值