Spring Boot
文章目录
初步了解:
SpringBoot就是一个JavaWeb框架
SpringBoot以约定大于配置的核心思想(maven、spring、springmvc、springboot…docker、k8s)
程序 = 数据结构 + 算法(集合框架map、list、数组);程序员
程序 = 面向对象 + 框架;码农
1、微服务
1.1 什么是微服务?
MVC 三层架构
MVVM 微服务架构
业务:service:userService:===> 模块!
springmvc,controller ==> 就负责提供接口!
http:rpc
1.2 单体应用架构
无论是ERP、CRM或者是其他什么系统,你都把数据库访问,web访问,等等各个功能放到一个war包内。
1.3 微服务架构
打破之前 all in one的架构方式,把每个功能独立出来
- 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用
- 大型分布式网络服务的调用,这部分有spring cloud来完成,实现分布式;
- 在分布式中间,进行流式数据计算、批处理,我们有spring cloud data flow。
- spring为我们像秦楚了整个从开始构建应用到大型分布式应用全流程方案。
Spring:the source for modern java
2. 第一个SpringBoot程序
- jdk1.8
- maven 3.6.1
- springboot:最新版
- IDEA
官方:提供了一个快速生成的网站!IDEA集成了这个网站
- 可以在官网直接下载后,导入idea开发
- 直接使用idea创建一个springboot项目(一般开发直接在IDEA中创建)
Application:程序的主入口
@SpringBootApplication
@SpringConfiguration
@Configuration
@Component
2.1、原理
2.2 原理初探
自动配置:
- pom.xml
- spring-boot-dependencies:核心依赖在父工程中(spring-boot-starter-parent)
- 我们在写或者引入一些Springboot依赖的时候,不需要指定版本,就因为有这些版本仓库
- 启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
- 启动器:说白了就是Springboot的启动场景;
- 比如spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖!
- springboot会将所有的功能场景,都变成一个个的启动器。
- 我们要使用什么功能,就只需要找到对应的启动器就可以了
starter
- 主程序
//SpringBootApplication : 标注这个类是一个springboot的应用
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
//将springboot应用启动
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
- 注解
@SpringBootConfiguration : springboot的配置
@Configuration : spring配置类
@Component : 说明这也是一个spring的组件
@EnableAutoConfiguration : 自动配置
@AutoConfigurationPackage : 自动配置包
@Import(AutoConfigurationPackages.Registrar.class) : 自动配置`包注册`
@Import(AutoConfigurationImportSelector.class) : 自动配置导入选择
//获取所有的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
获取候选的位置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
META-INF/spring.factories : 自动配置的核心文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
所有的资源加载到配置类中!
结论:
Springboot所有的自动配置都在启动的时候被扫描并加载:
spring.factories所有的自动配置类都在这里,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!
- springboot在启动的时候,从类路径下 /META-INF/spring.factories获取指定的值
- 将这些自动配置的类导入容器,自动配置类就会生效,帮我们进行自动配置
- 以前我们需要自动配置的东西,现在springboot帮我们做了!
- 整合JavaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.4.5.jar这个包下
- 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器
- 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration,JavaConfig!
- 有了自动配置类,免去了我们手动编写配置文件的工作。
关于SpringBoot,谈谈你的理解:
- 自动装配
- run()
四步骤:
1. 推断这个应用的类是普通的项目还是Web项目
2. 查找并加载所有可用初始化器,设置到initializers属性中
3. 找出所有的应用程序监听器,设置到listeners属性中
4. 推断并设置main方法的定义类,找到运行的主类
全面接管SpringMVC的配置!
3、SpringBoot配置
3.1 yaml
yaml可以直接给实体类赋值
yaml练习
# k=v
# 对空格的要求十分严格!
# 普通的key-value
# 注入到我们的配置类中!
# 对象
#student:
# 行内写法
student: {name: swaelee,age: 3}
# 数组
pets:
- cat
- dog
- pig
# pets: [cat,dog,pig]
yaml给属性赋值
此处属性与实体类上的@ConfigurationProperties(prefix = “guy1”)注解值相对应
guy1:
name: SwaeLee${random.uuid}
age: 12
happy: true
birth: 2021/04/19
maps: {k1: v1,k2: v2,k3: v3}
hello: lotie666
lists:
- code
- music
- game
dog:
name: ${guy1.hello:hilotie}_柯基666
age: 3
松散绑定:
实体类:
private String firstName;
yaml:
dog:
first-name: 阿黄
age: 2
结论:
- 配置yml和properties都可以获取到值,强烈推荐 yml
- 如果我们在某个业务中,只要获取配置文件中的某个值,可以使用以下@value
- 如果说,我们专门编写了一个JavaBean来和配置文件进行映射,就直接使用@configurationPeoperties,不要犹豫!
3.2 JSR303校验
在实体类上加@Validated注解
再对属性进行约束:
源码位置:Maven:validation:jakarta.validation-> validation->constraints
3.3多环境配置及配置文件位置(重点)
优先级问题:
- file: ./config/
- file:/
- classpath:/config/
- classpath:/ (默认)
server:
port: 8081
spring:
profiles:
active: dev
---
server:
port: 8082
spring:
profiles: dev
---
server:
port: 8083
spring:
profiles: test
3.4 自动配置原理再理解
@SpringBootApplication -> @EnableAutoConfiguration -> @Import(AutoConfigurationImportSelector.class)
- -> getAutoConfigurationEntry() -> getCandidateConfigurations()
- -> getSpringFactoriesLoaderFactoryClass - > loadFactoryNames ->指向配置文件:META-INF/spring.factories
-
表示这是一个配置类:
@Configuration -
自动装配属性:
EnableConfigurationProperties(ServerProperties.class)-> @ConfigurationProperties(prefix = “server”)中的属性与application.properties中的属性一一对应 -
Spring的底层注解:根据不同的条件,来判断当前配置或者类是否生效
@ConditionalOnWebApplication
在我们这配置文件中能配置的东西,都存在一个固有的规律 xxxAutoConfiguration:默认值 xxxProperties 和 配置文件绑定,我们就可以使用自定义的配置了!
总结:
- SpringBoot启动 会加载大量的 自动配置类
- 我们看我们需要的功能 是否在 SpringBoot默认写好的自动配置类当中
- 我们再来看 这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在其中,我们就不需要再手动配置了)
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中(也就是yaml)指定指定这些属性的值即可。
核心:
xxxAutoConfiguration:自动配置类;给容器中添加组件
xxxProperties:封装配置文件中相关属性;
我们随意拿到一个,它肯定有一个activemq.properties和activemqConfiguration
spring:
activemq:
in-memory:
4、SpringBoot Web开发
jar :webapp
自动装配
- 创建应用,选择模块
- 配置文件替换原有的操作(注入的过程)
- 专注业务代码
思考:
springboot 到底帮我们配置了什么?
- xxxxAutoConfiguratioin… 向容器中自动配置组件(不全配置,有条件筛选)
我们能不能进行修改?能修改那些东西?
- xxxxProperties:自动配置类,装配配置文件中自定义的一些内容!
要解决的问题:
- 导入静态资源,html、css…
- 首页index
- jsp,模板引擎 Thymeleaf
- 装配拓展SpringMVC
- 增删改查
- 拦截器
- 国际化
静态资源
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
ServletContext servletContext = getServletContext();
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (servletContext != null) {
registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
}
});
}
总结
- 在springboot中,我们可以使用以下方式处理静态资源
- webjars
- public,static,/**,resources localhost:8080/
- 优先级:resources > static(默认) > public
public下放一些公共的访问资源:一些js
static静态资源:图片
resources:上传的文件
4.1 首页如何定制
页面index.html通常放在templates,templates目录下的所有页面,只能通过controller来跳转
这个需要模板引擎的支持!
4.2 Thymeleaf
Thymeleaf官网文档:https://www.thymeleaf.org/
pom依赖
<!--Thymeleaf ,我们都是基于3.x开发,spring1.x对应Thymelear 2.x-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
导入后,我们将html放在我们templates目录下即可
//在templates目录下的所有页面,只能通过controller来跳转
//这个需要模板引擎的支持!
@Controller
public class IndexController {
@RequestMapping("/TEST1")
public String test(){
return "index";
}
}
Thymeleaf语法:
html导入
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- Simple expressions:
- Variable Expressions: ${…} 变量
- Selection Variable Expressions:*{…} 选择
- Message Expressions: #{…} 消息
- Link URL Expressions: @{…} url
- Fragment Expressions: ~{…}(高级)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>
2017首次落败在节目
</p>
<!--所有的html元素都可以被 thymeleaf替换接管: th:元素名-->
<div th:text="${msg1}"></div>
<div th:utext="${msg1}"></div>
<hr>
<p th:each="user:${users}" th:text="${user}"></p>
<!--
<p th:each="user:${users}">
[[ ${user} ]]
</p>
-->
<p>
我在外面等A$en选拔结束
</p>
</body>
</html>
@Controller
public class IndexController {
@RequestMapping("/TEST1")
public String test(Model model){
model.addAttribute("msg1","<h5>hello,springboot,01</h5>");
model.addAttribute("users", Arrays.asList("SwaeLee","Jessie Lee"));
return "index";
}
}
- Conditional operators:
- If-then: (if) ? (then)
- If-then-else: (if) ? (then) : (else) 【大部分用它】
- Default: (value) ?: (defaultvalue)
4.3 SpringMVC配置
- ContentNegotiatingViewResolver 内容协商视图解析器
我们在源码 WebMvcAutoConfiguration中 , 搜索ContentNegotiatingViewResolver方法。找到如下方法!
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图对象
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择一个最适合的视图对象,然后把这个对象返回
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
// .....
}
在doDispatch打断点Debug
发现
/*全面扩展 Springmvc 所有的请求都会经过dispatchservlet
1. 在类上加注解 @Configuration
2. 实现接口 WebMvcConfigurer
如果你想自定义DIY一些功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配
*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//public interface ViewResolver 实现了视图解析器接口的类,我们就可以把它看错视图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义了一个自己的视图解析器MyViewResolver
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
- 转换器和格式化器formatter
我们在源码WebMvcAutoConfiguration发现Formatters可以在properties中配置
- 扩展springmvc
自写start,写一个configuration和properties类,把两个类打到jar包里,放入spring-boot-autoconfigure,org,autoconfigure
//如果我们要扩展springmvc,官方建议我们这样去做
@Configuration
@EnableWebMvc //这就是,导入了一个类DelegatingWebMvcConfiguration作用:从容器中获取所有的webmvcconfig,如果我们要接管Springmvc,千万不能加这个注解,加了这个注解,就崩盘了
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/lee").setViewName("index");
}
}
在springboot中,有非常多的 xxx Configuration 帮助我们进行扩展配置,只要看见了这个东西,我们就要注意了!只要在源码中看见了这个类,立马关注它扩展了什么功能
Demo
首页实现
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
注意html处,需要改的位置
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<link th:href="@{/css/sigin.css}" rel="stylesheet">
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
- Variable Expressions:${…}
- Selection Variable Expression:*{…}
- Message Expressions:#{…}
- Link URL Expressions:@{…}
- Fragment Expressions:~{…}
-
首页配置:
- 注意点,所有页面的静态资源都需要使用thymeleaf接管;
- url:@{}
-
页面国际化 (首先我们确保IDEA中 File Encodings中配置编码全为UTF-8)
- 注意点:我们要配置i18n文件
- 我们如果需要在项目中进行按钮自动切换,我们需要自定义一个组件LocalResolver
IDEA可视化配置
index首页中配置th:text="#{}"
<!--<label class="sr-only" th:text="#{login.username}">Username</label>-->
<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<!--<label class="sr-only" th:text="#{login.password}">Password</label>-->
<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
</form>
</body>
</html>
自定义国际化组件
package com.lee.config;
import org.apache.tomcat.jni.Local;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
/**
* @author shkstart
* @date 21.5.7 - 10:46
*/
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数
String language = request.getParameter("l");
Locale locale = Locale.getDefault();//如果没有就使用默认的;
//如果请求的链接携带了国际化的参数
if (!StringUtils.isEmpty(language)){
//zh_CN
String[] split = language.split("_");
//国家,地区
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
在自定义MvcConfig中注册国际化组件
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
//自定义的国际化组件生效
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
- 登录功能+拦截 实现
loginController
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(
@RequestParam("username") String username,
@RequestParam("password") String password,
Model model){
/*
具体的业务
判断
*/
if (!StringUtils.isEmpty(username) && "123456".equals(password)){
return "redirect:/main.html";
}else {
//给用户返回登录失败信息
model.addAttribute("errorLogin","用户名或密码错误");
return "index";
}
}
}
html注意处
<form class="form-signin" th:action="@{/user/login}">
<!--如果error的值为空,则不显示消息:用户名或密码错误!-->
<p style="color: darkblue;font-weight: bolder" th:text="${errorLogin}" th:if="${not #strings.isEmpty(errorLogin)}"></p>
注意,在MyMvcConfig掩盖真实数据
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
我们思考,此时不需要登录直接输入main.html网址也可以进入网站,那我们需要用拦截器进行拦截
在LoginController加入
session.setAttribute("loginUser", username);
LoginHandlerInterceptor
package com.lee.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author shkstart
* @date 21.5.7 - 11:47
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功后,应该有用户的session;
Object loginUser = request.getSession().getAttribute("loginUser");// 31
if (loginUser==null){//没有登录
request.setAttribute("errorLogin", "没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
}else {
return true;
}
}
}
在dashboard中取出用户名返回
[[${session.loginUser}]]
-
员工列表展示
1. 提取公共部分
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="navbar"> <nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar"> <div th:replace="~{commons/commons::navbar}"></div> <div th:insert="~{commons/commons::sidebar}"></div>
2. 如果要传递参数,可直接使用()传参,接收判断即可
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}"> <div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>
3. 列表循环展示
<table class="table table-striped table-sm"> <thead> <tr> <th>员工编号</th> <th>员工姓名</th> <th>员工邮件</th> <th>员工性别</th> <th>员工部门</th> <th>员工生日</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="emp:${employees}"> <td th:text="${emp.getId()}"></td> <td th:text="${emp.getLastName()}"></td> <td th:text="${emp.getEmail()}"></td> <td th:text="${emp.getGender()}==0?'女':'男'"></td> <td th:text="${emp.department.getDepartmentName()}"></td> <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}"></td> <td> <button class="btn btn-sm btn-primary">编辑</button> <button class="btn btn-sm btn-danger">删除</button> </td> </tr> </tbody> </table>
-
添加员工
- 按钮提交
- 跳转到添加页面
- 添加员工成功
- 返回首页
Employee
package com.lee.controller;
import com.lee.dao.DepartmentDao;
import com.lee.dao.EmployeeDao;
import com.lee.pojo.Department;
import com.lee.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
import java.util.Collection;
/**
* @author shkstart
* @date 21.5.8 - 9:48
*/
@Controller
public class EmployeeController {
//Controller层调Service层
@Autowired
EmployeeDao employeeDao;
@Autowired
DepartmentDao departmentDao;
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("employees",employees);
return "employee/list";
}
@RequestMapping("/emp")
public String toAddPage(Model model){
//查出所有部门的信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "employee/add";
}
@PostMapping("/emp")
public String addEmp(Employee employee){
//添加的操作 forward
System.out.println("保存的信息=" + employee);
employeeDao.add(employee);//调用底层业务方法保存员工信息
return "redirect:/emps";
}
//到员工修改页面
@GetMapping("/emp/{id}") //href对应GetMapping
public String toUpdatePage(@PathVariable("id")Integer id,Model model){
//查出原来的数据
Employee employee = employeeDao.getEmployeeGetId(id);
model.addAttribute("emp",employee);
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "employee/update";
}
@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
employeeDao.add(employee);
return "redirect:/emps";
}
//删除员工
@RequestMapping("/delemp/{id}")
public String deleteEmp(@PathVariable("id")Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}
//注销
@RequestMapping("/user/logout")
public String userLogout(HttpSession session){
session.removeAttribute("loginUser");
return "redirect:/index.html";
}
}
dashboard.html
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<div th:replace="~{commons/commons::navbar}"></div>
<div class="container-fluid">
<div class="row">
<!--传递参数给组件-->
<div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
<div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div>
</div>
<div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:200%;height:200%;left:0; top:0"></div>
</div>
</div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group mr-2">
<button class="btn btn-sm btn-outline-secondary">Share</button>
<button class="btn btn-sm btn-outline-secondary">Export</button>
</div>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
This week
</button>
</div>
</div>
<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;"></canvas>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" ></script>
<script type="text/javascript" src="asserts/js/popper.min.js" ></script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js" ></script>
<!-- Icons -->
<script type="text/javascript" src="asserts/js/feather.min.js" ></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" src="asserts/js/Chart.min.js" ></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
list.html
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<div th:insert="~{commons/commons::navbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><a class="btn btn-sm btn-success" th:href="@{emp}">添加员工</a> </h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>员工编号</th>
<th>员工姓名</th>
<th>员工邮件</th>
<th>员工性别</th>
<th>员工部门</th>
<th>员工生日</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${employees}">
<td th:text="${emp.getId()}"></td>
<td th:text="${emp.getLastName()}"></td>
<td th:text="${emp.getEmail()}"></td>
<td th:text="${emp.getGender()}==0?'女':'男'"></td>
<td th:text="${emp.department.getDepartmentName()}"></td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')}"></td>
<td>
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
<a class="btn btn-sm btn-danger" th:href="@{/delemp/}+${emp.id}">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" src="asserts/js/popper.min.js"></script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>
<!-- Icons -->
<script type="text/javascript" src="asserts/js/feather.min.js"></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
add.html
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<div th:insert="~{commons/commons::navbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><a class="btn btn-sm btn-success" th:href="@{emp}">添加员工</a> </h2>
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>姓名</label>
<input type="text" name="lastName" class="form-control" placeholder="swae">
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" name="email" class="form-control" placeholder="xxx@qq.com">
</div>
<div class="form-group">
<label>性别</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>部门</label>
<select class="form-control" name="department.id">
<!--我们在controller接收的是一个Employee,所以我们需要提交的是其中的一个属性!-->
<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>生日</label>
<input type="text" class="form-control"name="birth" placeholder="swaelee">
</div>
<button type=submit class=btn btn-primary>添加</button>
</form>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" src="asserts/js/popper.min.js"></script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>
<!-- Icons -->
<script type="text/javascript" src="asserts/js/feather.min.js"></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
update.html
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<div th:insert="~{commons/commons::navbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/updateEmp}" method="post">
<input type="hidden" name="id" th:value="${emp.getId()}">
<div class="form-group">
<label>姓名</label>
<input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="swaelee">
</div>
<div class="form-group">
<label>邮箱</label>
<input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="xxx@qq.com">
</div>
<div class="form-group">
<label>性别</label><br>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender()==0}" class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>部门</label>
<select class="form-control" name="department.id">
<!--我们在controller接收的是一个Employee,所以我们需要提交的是其中的一个属性!-->
<option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>生日</label>
<input th:value="${emp.getBirth()}" type="text" class="form-control" name="birth">
<!--#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')-->
</div>
<button type=submit class=btn btn-primary>修改</button>
</form>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" src="asserts/js/popper.min.js"></script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>
<!-- Icons -->
<script type="text/javascript" src="asserts/js/feather.min.js"></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
5、整合数据库
Data
5.1 整合JDBC
application.yml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatisText?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
测试 (HikariDataSource 号称 Java WEB 当前速度最快的数据源)
package com.lee;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootTest
class Springboot04DataApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源
System.out.println(dataSource.getClass());//class com.zaxxer.hikari.HikariDataSource
//获得数据库链接
Connection connection = dataSource.getConnection();
System.out.println(connection);//HikariProxyConnection@2085952212 wrapping com.mysql.cj.jdbc.ConnectionImpl@7d5508e0
// xxx Template : SpringBoot已经配置好模板bean,拿来即用 CRUD
// jdbc
// redis
//关闭
connection.close();
}
}
controller
package com.lee.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* @author shkstart
* @date 21.5.9 - 10:18
*/
@RestController
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
// 查询数据库的所有信息
// 没有实体类,数据库中的东西怎么获取? 万能Map
@GetMapping("/userList")
public List<Map<String,Object>> userList(){
String sql = "select * from user";
List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
return list_maps;
}
@GetMapping("/addUser")
public String addUser(){
String sql = "insert into mybatisText.user(id,name,pwd) value(5,'菲董','999999')";
jdbcTemplate.update(sql);
return "insert-ok";
}
@GetMapping("/updateUser/{id}")
public String updateUser(@PathVariable("id") int id){
String sql = "update mybatisText.user set name=?,pwd=? where id="+id;
//封装
Object[] objects = new Object[2];
objects[0] = "菲董2";
objects[1] = "999998";
jdbcTemplate.update(sql,objects);
return "updateUser-okey";
}
@GetMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable("id") int id){
String sql = "delete from mybatistext.user where id = ?";
jdbcTemplate.update(sql,id);
return "deleteUser-okey";
}
}
5.2 整合Druid数据源
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了C3P0、DBCP、PROXOOL等DB池的优点,同时加入了日志监控(纯天然)。
作用:Druid 可以很好的监控 DB 池链接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
pom
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.24</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
yml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatisText?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourcesStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
测试
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源
System.out.println(dataSource.getClass());//class com.alibaba.druid.pool.DruidDataSource
//获得数据库链接
Connection connection = dataSource.getConnection();
System.out.println(connection);//com.mysql.cj.jdbc.ConnectionImpl@18b8d173
//关闭
connection.close();
}
DruidConfig
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")// 与yml内datasourcel连接
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
// 后台监控功能 : web.xml,ServletRegistrationBean
// 因为SpringBoot 内置了 servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要有人登录,账号密码
HashMap<String, String> initParameters = new HashMap<>();
//增加配置 : 重点
initParameters.put("loginUsername", "admin");// 登录key 是固定的 loginUsername
initParameters.put("loginPassword", "123456");// loginPassword
//允许谁可以访问 : 重点
initParameters.put("allow", "localhost");
//禁止谁能访问
// initParameters.put("swaelee", "192.168.11.123");
bean.setInitParameters(initParameters);// 设置初始化参数 : 重点
return bean;
}
//filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
//可以过滤哪些请求?
HashMap<String , String> initParameters = new HashMap<>();
initParameters.put("exclusions", "*.js,*.css,/druid/*");
bean.setInitParameters(initParameters);
return bean;
}
}
Druid登陆页面:
5.3 整合Mybatis(重点)
整合包
mybatis-spring-boot-starter
pom
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
- 导入包
- 配置文件
- mybatis配置
- 编写sql
- service 层调用 dao层
- controller 调用 service层
6、SpringSecurity(安全)(重点)
在web开发中,安全第一位!过滤器,拦截器~
功能性需求:否
做网站:安全应该在什么时候考虑?设计之初!
- 漏洞,隐私泄露
- 架构一旦确定
shiro、SpringSecurity:很像~ 除了类不一样,名字不一样;
认证,授权(vip1,vip2,vip3)
authentication access-control framework
- 功能权限
- 访问权限
- 菜单权限
- … 拦截器,过滤器:大量的原生代码~
6.1 Spring Security
导入pom
thymeleaf
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
6.2 认证与授权
controller层
@Controller
public class RouterController {
@RequestMapping({"/","/index","/index.html"})
public String index(){
return "index";
}
@RequestMapping("toLogin")
public String toLogin(){
return "views/login";
}
@RequestMapping("/level1/{id}")
public String Level1(@PathVariable("id")int id){
return "views/level1/" + id;
}
@RequestMapping("/level2/{id}")
public String Level2(@PathVariable("id")int id){
return "views/level2/" + id;
}
@RequestMapping("/level3/{id}")
public String Level3(@PathVariable("id")int id){
return "views/level3/" + id;
}
}
SecurityConfig
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 链式编程
// 授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页只有对应有权限的人才能访问
//请求授权的规则
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限默认会到登录页面,需要开启登录的页面
// /login
http.formLogin();
}
// 认证 Springboot 2.1.X 可以直接使用
// 密码编码 : PasswordEncoder
// 在Spring Security 5.0+ 新增了很多的加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 定制认证规则
// 这些数据正常应该从数据库中读
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1").and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2").and()
.withUser("swaelee").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3");
}
}
6.3 注销及权限控制
Thymeleaf 与 Spring Security整合
<!--thymeleaf整合包-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 链式编程
// 授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页只有对应有权限的人才能访问
//请求授权的规则
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限默认会到登录页面,需要开启登录的页面
// /login
http.formLogin();
// 注销,跳到首页
// 网站防止攻击 : get , post
http.csrf().disable(); // 关闭csrf功能,logout失败可能存在的原因.
http.logout().logoutSuccessUrl("/");
}
// 认证 Springboot 2.1.X 可以直接使用
// 密码编码 : PasswordEncoder
// 在Spring Security 5.0+ 新增了很多的加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 定制认证规则
// 这些数据正常应该从数据库中读
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1").and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2").and()
.withUser("swaelee").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3");
}
}
index
<!--主容器-->
<div class="ui container">
<div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">首页</a>
<!--登录注销-->
<div class="right menu">
<!--如果未登录,显示登录按钮-->
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
</div>
<!--如果已登录:用户名,注销-->
<div sec:authorize="isAuthenticated()">
<!--注销-->
<a class="item">
用户名: <spqn sec:authentication="name"></spqn>
<!--权 限: <span sec:authentication="principal.getPassword()"></span>-->
</a>
</div>
<div sec:authorize="isAuthenticated()">
<!--注销-->
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
<!--<a th:href="@{/usr/toUserCenter}">
<i class="address card icon"></i> admin
</a>-->
</div>
</div>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study by SwaeLee</h3>
</div>
<div>
<br>
<div class="ui three column stackable grid">
<!--菜单根据用户的角色动态实现-->
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
开启记住我功能:
// 开启记住我功能 cookie,默认保存两周
http.rememberMe().rememberMeParameter("remember");
定制登录页
// 定制登录页
http.formLogin().loginPage("/toLogin");
// http.formLogin().loginPage("/toLogin").usernameParameter().passwordParameter().loginProcessingUrl();
6.4 Shiro
- Apache Shiro 是一个Java的安全(权限)框架。
- Shiro可以完成,认证,授权,加密,会话管理,Web继承,缓存等。
- 下载:http://shiro.apache.org/
可以在maven项目中测试:
导入pom
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.6.0</version>
</dependency>
<!--configure logging-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
resources下log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
log4j.logger.org.apache=WARN
log4j.logger.org.springframework=WARN
log4j.logger.org.apache.shiro=INFO
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache,ehcache.EhCache=WARN
此处需导入ini、action tacker插件(Settings -> Plugins)
shiro.ini
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# =============================================================================
# Quickstart INI Realm configuration
#
# For those that might not understand the references in this file, the
# definitions are all based on the classic Mel Brooks' film "Spaceballs". ;)
# =============================================================================
# -----------------------------------------------------------------------------
# Users and their assigned roles
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
# -----------------------------------------------------------------------------
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
Quickstart.java
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple Quickstart application showing how to use Shiro's API.
*
* @since 0.9 RC2
*/
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// The easiest way to create a Shiro SecurityManager with configured
// realms, users, roles and permissions is to use the simple INI config.
// We'll do that by using a factory that can ingest a .ini file and
// return a SecurityManager instance:
// Use the shiro.ini file at the root of the classpath
// (file: and url: prefixes load from files and urls respectively):
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
// for this simple example quickstart, make the SecurityManager
// accessible as a JVM singleton. Most applications wouldn't do this
// and instead rely on their container configuration or web.xml for
// webapps. That is outside the scope of this simple quickstart, so
// we'll just do the bare minimum so you can continue to get a feel
// for things.
SecurityUtils.setSecurityManager(securityManager);
// Now that a simple Shiro environment is set up, let's see what you can do:
// get the currently executing user:
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// let's login the current user so we can check against roles and permissions:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//all done - log out!
currentUser.logout();
System.exit(0);
}
}
- 导入依赖
- 配置文件
- HelloWorld
(略)
7、Swagger
学习目标:
- 了解Swagger的作用和概念
- 了解前后端分离
- 在SpringBoot中集成Swagger
前后端分离
Vue + SpringBoot
后端时代:前端只用管理静态页面;html -> 后端。模板引擎 JSP -> 后端是主力
前后端分离式时代:
- 后端:后端控制层,服务层,数据访问层
- 前端: 前端控制层,视图层
- 伪造后端数据,json。已经存在了,不需要后端,前端工程依旧能够跑起来
- 前后端如何交互? -> API
- 前后端相对独立,松耦合;
- 前后端甚至可以部署在不同的服务器上;
产生一个问题:
- 前后端集成联调,前端人员和后端人员。无法做到,即使协商,尽早解决,最终导致问题集中爆发
解决方案:
- 首先指定schema[计划的提纲],实时更新最新API,降低集成的风险;
- 早些年:指定word计划文档;
- 前后端分离:
- 前端测试后端接口:postman
- 后端提供接口,需要实时更新最新的消息及改动!
7.1 Swagger
- 号称世界上最流行的Api框架;
- RestFul Api 文档在线自动生成工具 -> Api文档与API定义同步更新
- 直接运行,可以在线测试API接口;
- 支持多种语言:(Java,Php…)
在项目中使用Swagger需要 springbox;
- swagger2
- ui
7.2 SpringBoot 集成Swagger
- 新建一个SpringBoot - web项目
- 导入相关依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
- 编写一个Hello工程
- 配置Swagger -> Config
@Configuration
@EnableSwagger2 // 开启Swagger2
public class SwaggerConfig {
}
- 测试运行
7.3 配置Swagger
Swagger的bean实例 Docket;
@Configuration
@EnableSwagger2 // 开启Swagger2
public class SwaggerConfig {
// 配置了Swagger的Docket的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
// 配置Swagger信息=apiInfo
private ApiInfo apiInfo(){
// 作者信息
Contact DEFAULT_CONTACT = new Contact("SwaeLee", "https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", "1398803640@qq.com");
return new ApiInfo(
"SwaggerAPI日志 By SwaeLee",
"再小的帆也能远航",
"v1.0",
"https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", DEFAULT_CONTACT,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
}
}
7.4 Swagger配置扫描接口
Docker.select
// 配置了Swagger的Docket的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(false) // enable 是否启动swagger,如果为False,则swagger不能在浏览器中访问
.select()
// RequestHandlerSelectors,配置要扫描接口的方法
// basePackage:指定要扫描的包
// any(): 扫描全部
// none():不扫描
// withClassAnnotation(): 扫描类上的注解,参数是一个注解的反射对象
// withMethodAnnotation(): 扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.lee.swagger.controller"))
/* paths():过滤什么路径
.paths(PathSelectors.ant("/lee/**"))*/
.build(); // 工厂模式
}
// 配置Swagger信息=apiInfo
private ApiInfo apiInfo(){
// 作者信息
Contact DEFAULT_CONTACT = new Contact("SwaeLee", "https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", "1398803640@qq.com");
return new ApiInfo(
"SwaggerAPI日志 By SwaeLee",
"再小的帆也能远航",
"v1.0",
"https://blog.csdn.net/SwaeLeeUknow?spm=1000.2115.3001.5343", DEFAULT_CONTACT,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
}
问题:我只希望我的Swagger在生产环境中使用,在发布的时候不使用?
- 判断是不是生产环境 flag = false
- 注入enable(flag)
// 配置了Swagger的Docket的bean实例
@Bean
public Docket docket(Environment environment){
// 设置要显示的Swagger环境
Profiles profiles = Profiles.of("dev","test");
//获取项目的环境:
//通过environment.acceptsProfiles判断是否处在自己设定的环境当中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag) // enable 是否启动swagger,如果为False,则swagger不能在浏览器中访问
.select()
.apis(RequestHandlerSelectors.basePackage("com.lee.swagger.controller"))
// .paths(PathSelectors.ant("/lee/**"))
.build(); // 工厂模式
}
7.5 配置API 文档的分组
.groupName("SwaeLee")
问题:如何配置多个组?
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("C");
}
实体类配置;
只需要两步
- 写实体类
@ApiModel("用户类") // 给实体类加入文档注释
public class User {
@ApiModelProperty("用户名")
public String username;
@ApiModelProperty("密码")
public String password;
}
}
- 在Controller配置实体类接口,让返回值有实体类
//只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中
@PostMapping("/user")
public User user(){
return new User();
}
Api注解
@Api("Hello控制类")
@RestController
public class HelloController {
// /error
@GetMapping(value = "/hello")
public String hello(){
return "hello";
}
//只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中
@PostMapping("/user")
public User user(){
return new User();
}
// Operation接口,放在方法上的
@ApiOperation("user控制类")
@GetMapping("/hello2")
public String hello(@ApiParam("用户名") String username){
return "hello"+username;
}
@ApiOperation("Post控制类")
@PostMapping("/postTest")
public User postTest(@ApiParam("用户名") User user){
int i = 5/0;
return user;
}
}
Swagger测试
不需要在客户端测试,很方便
总结:
- 我们可以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
- 接口文档实时更新
- 可以在线测试(前后端高效交互)
Swagger是一个优秀的工具
【注意点】在正式发布的时候,关闭Swagger!出于安全考虑,而且节省运行内存。
8、任务
异步任务~
定时任务~ timer
邮件发送~
1. 异步:
service
@Service
public class AsyncService {
// 告诉Spring这是一个异步的方法
@Async
public void hello(){
try{
Thread.sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("数据正在处理...");
}
}
controller
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@RequestMapping("/hello09")
public String hello(){
asyncService.hello(); // 停止三秒,转圈~
return "ok";
}
}
在主类上加入@EnableAsync注解 // 开启异步注解功能
2. 邮件发送
导入pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
测试类
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
// 一个简单的邮件
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("123");
mailMessage.setText("123123");
mailMessage.setTo("xxx@qq.com");
mailMessage.setFrom("xxx@qq.com");
mailSender.send(mailMessage);
}
@Test
void contextLoads2() throws MessagingException {
// 一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
// 组装
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
helper.setSubject("晚上好");
helper.setText("<p style='color:blue'>l came</p> " +
"<p style='color:blue'> l saw</p>",true);
// 附件
helper.addAttachment("skrt.jpg", new File("C:\\1.jpg"));
// helper.addAttachment("2.jpg", new File("C:\\1.jpg"));
helper.setTo("xxx@qq.com");
helper.setFrom("xxx@qq.com");
mailSender.send(mimeMessage);
}
/**
*
* @param html:文本
* @param subject:主题
* @param text:正文
* @throws MessagingException
*/
public void sendMail(boolean html,String subject,String text) throws MessagingException{
// 一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
// 组装
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,html);
helper.setSubject(subject);
helper.setText(text, true);
// 附件
helper.addAttachment("skrt.jpg", new File("C:\\1.jpg"));
helper.setTo("xxx@qq.com");
helper.setFrom("xxx@qq.com");
mailSender.send(mimeMessage);
}
3. 定时任务
TaskScheduler 任务调度者
TaskExecutor 任务执行者@ EnableScheduling //开启定时功能的注解
@ Scheduled //什么时候执行Cron表达式
@Service
public class ScheduledService {
// 在一个特定的时间执行这个方法 Timer
//cron 表达式
//秒 分 时 日 月 周几
/*
30 10 5 * * ? 每天5点10分30秒执行一次
0 10 5,8 * * ? 每天 5点和10点 执行一次
0 0/5 8 * * ? 每隔5分钟执行一次
0 15 10 ? * 1-6 每个月的,周一到周六,10.15分执行
*/
@Scheduled(cron = "30 08 20 * * ?")
public void hello(){
System.out.println("hello,你被执行了~");
}
}
9、Springboot整合Redis
10、分布式 Dubbo + Zookeeper
|阿里云|腾讯云|华为云
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现时为了用廉价、普通的机器完成单个计算机无法完成的而计算、存储任务。其目的是利用更多的机器,处理更多的数据。
单一应用架构:
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:
- 性能扩展比较
- 难协同开发问题
- 不利于升级维护
分布式架构:
提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构:用于提高机器利用率的资源调度和治理中心(SOA)[Service Oriented Architecture]是关键。
10.1 RPC
通信两种协议 : HTTP RPC
RPC(远程过程调用),是一种进程通信方式,是一种技术的思想,而不是规范。
它允许程序员调用另一个地址空间(通常共享网络的另一台机器上)的过程或函数,而不用程序显式编码这个远程调用的细节。
程序员无论调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器A,B,一个应用部署再A服务器上,想要调用B服务器上应用提供的函数、方法,由于不再一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方法完成的需求,比如不同的系统件的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要再多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调用远程函数;
RPC两个核心模块:通讯,序列化。
序列化(方便数据传输):数据传输需要转换
底层式Netty
Dubbo(18年重启):高可用的RPC框架,专注于RPC。Dubbo 3.x Error Exception
HTTP SpringCloud(生态)
专业的事,交给专业的人来做。
10.2 Dubbo
Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
快速启动
官方文档:dubbo官方文档
Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需要 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展进行加载。
前台 中台(整合) 后台
服务提供者(Provider)
服务消费者(Consumer)
注册中心(Registry)
监控中心(Monitor)
zookeeper:hadoop hive
window下载安装aookeeper
注意在3.5以后,带bin的包才是我们要下载的包
- 下载地址:http://mirrors.hust.edu.cn/apache/zookeeper/
运行
可能遇到的问题:闪退!
解决方案:编译zkServer.cmd文件末尾添加pause
在conf中复制一份zoo_sample文件,名为zoo
window下载安装dubbo-admin
官网地址:https://github.com/apache/dubbo-admin/tree/master
- 在项目目录下打包dubbo(第一次失败的话再执行一次)
mvn clean package -Dmaven.test.skip=true
- 执行dubbo-admin\target 下的dubbo-admin-0.0.1-SNAPSHOT.jar
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
访问localhost:7001
默认密码root-root
zookeeper:注册中心
dubbo-admin:是一个监控管理后台~查看我们注册了哪些服务,哪些服务被消费了。
Dubbo:jar包
测试:
- 创建好两个不同的spring boot项目
ProviderService
public interface TicketService {
public String getTicket();
}
serviceImpl
//zookeeper:服务注册与发现
@Service //可以被扫描到,项目一启动就注册到Registry中心
@Component // 这里使用Dubbo后,尽量不用Service注解
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "Swalee On Da Track";
}
}
- 导入pom
<!--导入依赖 Dubbo + zookeeper-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!--日志会冲突,需要排除slf4j-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
- 配置
provider-application.properties
server.port=8001
# 服务应用名字
dubbo.application.name=provider-server
# 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
# 哪些服务要被注册
dubbo.scan.base-packages=com.lee.service
consumer-application.properties
server.port=8002
# 消费者去哪里拿服务暴漏自己的名字
dubbo.application.name=consumer-sever
# Registry中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
- 在Consumer中测试
ConsumerService
@Service // 放到容器中
public class UserService {
//想拿到provider-sever提供的票,要去注册中心拿到服务
@Reference //引用,Pom坐标,可以定义路径相同的接口
TicketService ticketService;
public void buyTicket(){
String ticket = ticketService.getTicket();
System.err.println("在注册中心拿到的=>" + ticket);
}
}
测试类:
@SpringBootTest
class ConsumerSeverApplicationTests {
@Autowired
UserService userService;
@Test
void contextLoads() {
userService.buyTicket();
}
}
步骤:
前提:zookeeper服务已开启!
- 提供者提供服务
- 导入依赖
- 配置注册中心的地址,以及服务发现名,和要扫描的包
- 在想要被注册的服务上面,增加一个注解@Service(Dubbo)
- 消费者如何消费
- 导入依赖
- 配置注册中心的地址,配置/暴漏自己的服务名
- 从远程注入服务@Reference
打个总结
回顾以前,架构
三层架构 + MVC
架构 ---> 解耦
开发框架
Spring
IOC AOPIOC:控制反转
(容器)原来我们都是自己一步一步操作,现在交给容器,我们需要什么就去拿就可以了
AOP:切面(本质,动态代理)
为了解决什么问题?
不影响业务本来的情况下,实现动态增加功能,大量应用在日志,事务…等等方面Spring是一个轻量级的Java开发框架
目的:解决企业开发的复杂性问题
Spring是春天,配置文件十分复杂。
SpringBoot
新一代JavaEE的开发标准
SpringBoot并不是新东西,就是Spring的升级版!
开箱即用! -> 拿过来就可以用!
它帮助我们自动配置了非常多的东西,我们拿来即用!特性:约定大于配置!
随着公司体系越来越大,用户越来越多。
微服务架构 —> 新架构
模块化,功能化
用户,支付,签到,娱乐…
人数过于庞大:一台服务器解决不了;再增加一台服务器。(横向解决问题)
假设A服务器占用98%资源,B服务器只占用10%–负载均衡
将原来的整体项目,分成模块化,用户就是一个单独的项目,项目和项目之间需要通信,如何通信?
用户非常多,而签到十分少。给用户多一点服务器,给签到少一点服务器。
微服务架构问题?
分布式架构会遇到的四个核心问题?
1. 这么多服务,客户端该如何去访问?
2. 这么多服务,服务之间如何进行通信?
3. 这么多服务,如何统一管理/治理?
4. 服务挂了,怎么办?
解决方案:
SpringCloud,是一套生态,就是来解决以上分布式架构的4个问题。
想使用SpringCloud,必须要掌握SpringBoot,因为SpringCloud是基于SpringBoot;
1. Spring Cloud NetFlix,出来了一套解决方案!一站式解决方案。我们都可以直接去这里拿
Api网关,zuul组件
Feign --> HttpClient --> HTTP的通信方式,同步并阻塞
服务注册与发现,Eureka
熔断机制,Hystrix
2018年年底,NetFlix宣布无限期停止维护。生态不再维护,就会脱节。
2. Apache Dubbo zookeeper,第二套解决系统
API:没有,要么找第三方组件,要么自己实现。
Dubbo是一个高性能的基于Java实现的 RPC通信框架。2.6.x
服务注册与发现,zookeeper:Hadoop,Hiv
熔断机制:没有,借助了Hystrix
不完善,Dubbo 当前 Dubbo 3.0 将提供具备当代特征(如响应性编程)的相关支持,同时汲取阿里内部 HSF 的设计长处来shi
3. SpringCloud Alibaba 一站式解决方案!
目前,又提出了一种方案:
服务网格::下一代微服务标准,Server Mesh
代表解决方案:istio(未来可能需要掌握)
解决的问题,万变不离其宗,一通百通!
1. API网关,服务路由
2. HTTP,RPC框架,异步调用
3. 服务注册与发现,高可用
4. 熔断机制,服务降级基于这四个问题,开发一套解决方案,也叫SpringCloud微服务解决方案。
为什么要解决这个问题?本质:网络是不可靠的!
不要停下学习的脚步!
笔记参照-B站up主:狂神说