什么是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的同级目录下健包,否则会访问不到
通过官网创建
-
进入官网:https://spring.io/
-
进入项目下载地址
往下滑
输入信息
点击即可下载包
-
在idea中导入项目
-
项目打开,删除不必要的文件
-
运行进行测试,在主类中点击运行即可
-
游览器访问:http://localhost:8080
通过idea创建
-
new
-
设置信息
-
导入依赖
-
项目创建成功
-
如果jdk版本与选择的不一致,在project Structure和pom.xml中修改jdk版本
扩展:
-
更改项目的端口号
在resources目录下的application.properties文件中添加如下语句
#更改项目的端口号 server.port=8081
-
修改spring的bunner(地址:https://www.bootschool.net/ascii)
在resources目录下新建banner.txt将复制过来的图放进入即可,重新启动项目,效果如下图
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})//自动配置导入选择
总结:
- springboot在启动的时候,从类路径下/META-INF/ spring. factories获取指定的值;
- 将这些自动配置的类导入容器,自动配置就会生效,帮我进行自动配置!
- 以前我们需要自动配置的东西,现在springboot帮我们做了!
- 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.2.0.RELEASE.jar这个包下
- 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
- 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration ,JavaConfig!
- 有了自动配置类,免去了我们手动编写配置文件的工作!
配置文件
推荐使用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 中配置;
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
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
-
实体类中使用
@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; }
-
基本校验规则
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前准备
静态资源导入问题
-
在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/
-
-
优先级:resources>static(默认)>public
-
在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";
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
-
在需要使用的html页的标签后加上约束
<html lang="en" xmlns:th="http://www.thymeleaf.org">
-
更多语法可以查看官方文档
修改mvc的默认配置
比如默认的时间格式等等
首页配置和国际化
首页配置
页面的所有静态资源都需要使用thymeleaf接管
使用thymeleaf需要先在头部导入约束
访问url需要使用 @{}
# 关闭模板引擎的缓存
spring.thymeleaf.cache=false
# 我们的配置文件的真实位置
spring.messages.basename=i18n.login
#时间日期格式化
spring.mvc.format.date=yyyy-MM-dd
国际化
示例为将首页国际化
-
配置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=用户名
-
编写application.properties文件,进行配置
# 我们的配置文件的真实位置 spring.messages.basename=i18n.login
-
修改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>
-
在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) { } }
-
在config包下创建MyMvcConfig,将MyLocaleResolver载入spring容器中
@Configuration public class MyMvcConfig implements WebMvcConfigurer { //自定义的国际化组件就生效了 @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); } }
-
检查Controller(检查是否能够访问index)
@Controller public class IndexController { //地址栏访问这两个路径都可以访问首页 @RequestMapping({"/","/index"}) public String index(){ return "index"; } }
-
测试
登录功能的实现
-
编写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"; } }
-
修改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>
编写拦截器
-
在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; } } }
-
在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/**"); } }
-
测试
展示员工列表
实体类
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);
}
}
提取公共页面
-
在需要提取出来的标签中加入如下语句
th:fragment="sidebar"
-
在需要引用的位置加入如下语句
commons/commons为commons路径下的comoms.html文件
sidebar为自定义的名字
后面括号内的为需要传参的值
<div th:insert="~{commons/commons::sidebar(active='main.html')}"></div>
-
另一页面接收判断值
使用${}取值判断,使用三元运算符进行判断
"${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>
添加员工
-
编写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"; }
-
编写前端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>
-
修改时间格式
#时间日期格式化 spring.mvc.format.date=yyyy-MM-dd
修改员工信息
-
编写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"; }
-
编写前端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页面和注销
-
删除员工
-
编写controller
@GetMapping("/delemp/{id}") public String deleteEmp(@PathVariable("id") Integer id){ employeeDao.delete(id); return "redirect:/emps"; }
-
-
404页面
在templates目录下创建error文件夹,将404.html页面放进去即可
-
注销
-
编写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等连接池更加优秀;
-
创建项目,选择依赖
-
创建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
-
进行测试查看是否连接成功
测试类中注入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(); } }
-
使用配置好的模板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"; } }
-
测试是否成功
整合使用自定义的Druid数据源
-
导入依赖
<!-- druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency>
-
配置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
-
测试是否切换成功
@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(); } }
-
设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项
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
-
导入log4j的依赖
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
点击运行项目,查看是否运行成功
-
编写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; } }
-
运行项目进行测试
-
访问http://localhost:8080/druid,输入在config中配置的账号密码后可进入后台,查看使用状态
-
整合mybatis框架
-
新建项目,选择需要的模板
-
导入依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency>
-
编写数据库配置和整合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
-
编写实体类
@Data @AllArgsConstructor @NoArgsConstructor @Component public class User { private int id; private String name; private String pwd; }
-
编写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>
-
编写Service层
-
编写controller层
@RestController public class UserController { @Autowired private UserService userService; @RequestMapping("/queryUserList") public List<User> queryUserList(){ List<User> userList = userService.queryUserList(); return userList; } }
-
测试
SpringSecurity(安全)
Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而不是只在Spring Security 中存在。
实验环境搭建
-
新建一个项目
这里记得要选择thymeleaf
-
导入静态资源
-
静态资源获取:https://gitee.com/ENNRIAAA/spring-security-material
-
-
创建路由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; } }
-
运行项目进行测试
认证和授权
-
导入依赖
<!-- security--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
编写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"); } }
-
测试
注销和权限控制
-
导入依赖
<!-- security-thymeleaf整合包--> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency>
-
注入命名空间
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
-
修改登录和注销按钮
实现用户未登录时只显示登录,用户登录后显示用户名、角色和注销
<!--登录注销--> <div class="right menu"> <!--如果未登录--> <div sec:authorize="!isAuthenticated()"> <a class="item" th:href="@{/toLogin}"> <i class="address card icon"></i> 登录 </a> </div> <!-- 如果登录:用户名,注销--> <div sec:authorize="isAuthenticated()"> <a class="item"> 用户名:<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>
-
修改用户的权限显示
<!-- 菜单根据用户的角色动态实现--> <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>
-
测试
记住我和首页定制
可以使用我们自己的登录页登录
-
编写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"); } }
-
修改登录页面表单
<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>
-
测试
Shiro
异步、定时和邮件任务
异步任务
-
创建一个service包
-
创建一个类AsyncService
异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。
编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;
@Service public class AsyncService { public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("业务进行中...."); } }
-
编写controller包
-
编写AsyncController类
我们去写一个Controller测试一下
@RestController public class AsyncController { @Autowired AsyncService asyncService; @GetMapping("/hello") public String hello(){ asyncService.hello(); return "success"; } }
-
访问http://localhost:8080/hello进行测试,3秒后出现success,这是同步等待的情况。
问题:我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:
-
给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); } }
-
重启测试,网页瞬间响应,后台代码依旧执行!
邮件任务
-
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
-
编写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
如何获取授权码?
-
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); } }
-
效果
定时任务
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,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 表示某月的第二个星期四。 |
测试步骤
-
创建一个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,你被执行了"); } }
-
这里写完定时任务之后,我们需要在主程序上增加**@EnableScheduling** 开启定时任务功能
@SpringBootApplication @EnableAsync//开启异步注解功能 @EnableScheduling//开启定时功能的注解 public class Springboot07TestApplication { public static void main(String[] args) { SpringApplication.run(Springboot07TestApplication.class, args); } }
-
cron表达式在线生成网站:https://cron.qqe2.com/
-
常用的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
注册中心
-
下载zookeeper,官网地址:https://zookeeper.apache.org/
我选择下载的是apache-zookeeper-3.5.9-bin.tar.gz
步骤:
-
打开下载的文件(管理员模式打开),运行/bin/zkServer.cmd,初次运行时可能会报错,因为没有zoo.cfg
也有可能遇到闪退的问题!
闪退的解决方案:右键编辑zkServer.cmd文件,在末尾添加pauser(如果右键不能点击编辑,可以将文件的后缀名改为txt,然后修改完后再改回cmd)
-
修改zoo.cfg配置文件
将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。
注意几个重要位置:
dataDir=./ 临时数据存储的目录(可写相对路径)
clientPort=2181 zookeeper的端口号
修改完成后再次启动zookeeper
-
打开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,不过这个监控即使不装也不影响使用。
安装步骤:
-
下载dubbo-admin
https://github.com/apache/dubbo-admin
左侧选择master后在code中下载zip
-
进入解压后的目录,进入\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
-
项目目录下cmd中打包dubbo-admin(第一次打包过程可能有点慢)
mvn clean package -Dmaven.test.skip=true
-
打包完成后,会生成如图的jar包,可能不是这个名字
-
进入\dubbo-admin-distribution\target\下可以找到这个jar包(应该就在这个位置,如果不在这个位置,可以去其它文件夹的target目录下找一找)
-
执行 \dubbo-admin-distribution\target\ 下的dubbo-admin-0.3.0.jar(前提:zookeeper的服务一定要打开,即运行zkServer.cmd)
打开cmd在该路径下运行:
java -jar dubbo-admin-0.3.0.jar
-
执行完毕,我们去访问一下http://localhost:7001/,这时候我们需要输入登录账户和密码,我们都是默认的root,如果需要修改的话,可以在第二步的properties中修改,然后重新打包
SpringBoot + Dubbo + zookeeper
框架搭建
-
创建一个springboot项目,项目名:provider-server,选择web依赖即可
-
在项目中编写一个服务
创建一个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"; } }
-
再创建一个模块,实现服务消费者,模块名:consumer-server
-
在consumer-server项目下写一个需要获得的服务
public class UserService { //我们需要去拿去注册中心的服务 }
需求:现在我们的用户想使用买票的服务,这要怎么弄呢 ?
服务提供者
编写provider-server项目
-
服务提供者注册到注册中心,我们需要整合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>
-
在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
-
测试
逻辑理解 :应用启动起来,dubbo就会扫描指定的包下带有@component注解的服务,将它发布在指定的注册中心中!
**注意:**在idea中启动项目时一定要先启动zookeeper和dubbo-admin生成的jar包,如果没有启动dubbo-admin,则无法在网页上访问7001端口进入可视化界面
显示出来你的服务则配置成功
服务消费者
编写consumer-server项目
-
导入依赖,和之前一样
<!-- 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>
-
编写application.properties
server.port=8002 # 消费者去哪里拿服务需要暴露自己的名字 dubbo.application.name=consumer-server # 注册中心的地址 dubbo.registry.address=zookeeper://127.0.0.1:2181
-
本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同
-
完善消费者的服务类
@Service//放到容器中 public class UserService { //想拿到provider-server中的票 @Reference//引用, Pom坐标,可以定义路径相同的接口 TicketService ticketService; public void buyTicket(){ String ticket = ticketService.getTicket(); System.out.println("在注册中心拿到=>"+ticket); } }
-
编写测试类
@SpringBootTest class ConsumerServerApplicationTests { @Autowired UserService userService; @Test void contextLoads() { userService.buyTicket(); } }
-
启动测试
-
开启zookeeper
-
开启dubbo-admin【可以不用做】
-
开启服务者(provider-server)
-
消费者消费测试
测试访问成功!
che.dubbo
dubbo-spring-boot-starter
2.7.3
-
-
编写application.properties
server.port=8002 # 消费者去哪里拿服务需要暴露自己的名字 dubbo.application.name=consumer-server # 注册中心的地址 dubbo.registry.address=zookeeper://127.0.0.1:2181
-
本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同
[外链图片转存中…(img-meOEXHD2-1647408744307)]
-
完善消费者的服务类
@Service//放到容器中 public class UserService { //想拿到provider-server中的票 @Reference//引用, Pom坐标,可以定义路径相同的接口 TicketService ticketService; public void buyTicket(){ String ticket = ticketService.getTicket(); System.out.println("在注册中心拿到=>"+ticket); } }
-
编写测试类
@SpringBootTest class ConsumerServerApplicationTests { @Autowired UserService userService; @Test void contextLoads() { userService.buyTicket(); } }
-
启动测试
-
开启zookeeper
-
开启dubbo-admin【可以不用做】
-
开启服务者(provider-server)
-
消费者消费测试
[外链图片转存中…(img-jiW2h1Zp-1647408744308)]
测试访问成功!
-