一,springboot项目的文件目录
-
xxxAppliaction:springboot的朱入库
-
application.properties:springboot的配置文件 但是大多以后使用yml文件格式
-
启动直接点run
-
建包只能是统计目录下建立,否则不会生效
pom.xml
-
有一个父组件
-
还有依赖
-
所有的springboot的组件都是springboot开头的
-
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <!--依赖的父组件--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.8</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ljh</groupId> <artifactId>SpringBoot_quick2</artifactId> <version>0.0.1-SNAPSHOT</version> <name>SpringBoot_quick2</name> <description>SpringBoot_quick2</description> <properties> <java.version>1.8</java.version> </properties> <!--web依赖--> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--junit的单元测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--@configuartionProperties的执行器配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
二,第一个SpringBoot项目
package com.ljh.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class user {
@RequestMapping("/caixu")
public String kuang(){
return "蔡旭傻逼";
}
}
三,原理初探
自动配置
pom.xml
- spring-boot-dependencies:核心依赖在父工程中
- 我们在写或者引入一些SpringBoot以来的时候,不需要制定一些版本,就是因为这个
启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
- 启动器:说白了就是SpringBoot的应用场景
- 后面-什么就是带入环境
- 例:-web
- 在官网上面可以找到
主程序
@SpringBootApplication //标志这个类是SpringBoot的应用
public class ZSpringBoot01Application {
public static void main(String[] args) {
//通过反射将SpringBoot启动
SpringApplication.run(ZSpringBoot01Application.class, args);
}
}
-
注解
-
@SpringBootConfiguration //SpringBoot的配置 @EnableAutoConfiguration //自动配置
四,yaml的语法和SpringBoot配置
yaml语法
# 官方的配置太多了,理解记忆
# 对空格的要求十分高
# key和value之间有一个空格
# 注入到我们的配置类中
# 普通的key和value
name: qinjiang
# 存一个对象
student:
name: qingjiang
age: 3
student: {name: qinjiang,age: 2}
# 存一个数组
pets:
-cat
-dog
pets:[cat,dog]
#yaml可以村对象,但是properties不可以存对象
SpringBoot配置
@Value("蔡旭")
private String name;
@Value("2")
private Integer age;
# 可以有@ConfigurationProperties(prefix = "Person")注解
# 这个可以直接引用一个对象注入,在使用这个注解的时候,就会有错误提示如果不配置使用注解就会爆红,但是没有什么影响(添加依赖即可)
@Component
//@ConfigurationProperties(prefix = "person")
@PropertySource(value = "classpath:name.properties")
public class Person2 {
@Value("${name}")
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Pet pet;
可以使用properties和yml配置,但是properties会乱码,并且properties不可以一次性全部进入
也可以在yml文件中直接使用JSTEL表达式例如${random.uuid}等等
//松散注入:在yml文件里面是first-name 但是在java'中是firstName.但是也可以匹配成功
Dof{firstName='cai', age=12}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTiJpdO6-1677419918887)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230216232911734.png)]
JSR303检验
@Component
@ConfigurationProperties(prefix = "person")
//这个是JSR303检验,就好比表单检验
@Validated
public class Person {
//如果格式有无的话不可以正常的注入的
@Email()
@Email(message="邮箱格式有无")
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Pet pet;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ucjuj9xJ-1677419918888)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230216234053868.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jVTYI2zK-1677419918888)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230216234103508.png)]
多环境配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3iKJNzgD-1677419918888)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230217142003806.png)]
- SpringBoot的yml配置: —可以分割多个文件.如果使用哪一个就active:dev就行
五,SpringBoot Web开发
静态资源
0,代码分析
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 这个是自定义配置,如果发现自定义配置直接返回(spring.mvc.static-path-pattern)
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//这个是利用webjars导用数据
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
//放在static,public,resources中,但是resources>static(默认)>public,但是public和resources得自己创建
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
1.webjars下面可以访问
2.放在static,public,resources中,但是resources>static(默认)>public
- 一般在static中放一些静态资源
- 在public放一些公共资源
3.也可以在如此配置
- spring.mvc.static-path-pattern=/ 这个可以改变文件路径,但是系统默认的静态文件就会失效
首页如何定制
1.可以将首页放在static,public,resources中,index.html
2.可以将首页放在templates下面,但是需要controller来跳转,这个也需要thymeleaf的支持
thymeleaf
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
-
在使用时,导入pom.xml坐标,并且在html中导入html标签
-
在使用的时候,必须导入相应的pom坐标.,可以在一下链接里面查看文档
-
https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
-
thymeleaf的官方文档是:https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#using-texts
-
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Good Thymes Virtual Grocery</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" type="text/css" media="all" href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" /> </head> <body> <p th:text="#{home.welcome}">Welcome to our grocery store!</p> </body> </html>
-
<html xmlns:th="http://www.thymeleaf.org"> # 这个是thymeleaf的约束
thymeleaf的语法
-
Simple expressions: Variable Expressions: ${...} # 变量 Selection Variable Expressions: *{...} # 选择变量表达式 Message Expressions: #{...} # 消息表达式 Link URL Expressions: @{...} # 链接URL表达式 Fragment Expressions: ~{...} # 片段表达式 Literals Text literals: 'one text', 'Another one!',… Number literals: 0, 34, 3.0, 12.3,… Boolean literals: true, false Null literal: null Literal tokens: one, sometext, main,… Text operations: String concatenation: + Literal substitutions: |The name is ${name}| Arithmetic operations: Binary operators: +, -, *, /, % Minus sign (unary operator): - Boolean operations: Binary operators: and, or Boolean negation (unary operator): !, not Comparisons and equality: Comparators: >, <, >=, <= (gt, lt, ge, le) Equality operators: ==, != (eq, ne) Conditional operators: If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue)
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xKaJjjjC-1677419918889)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230217230239289.png)]
-
第一个thymeleaf的页面
-
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>$Title$</title> </head> <body> <!--所有的html页面都可以用thymeleaf所接管 th:元素名 th:text,th:class--> <div th:text="${nihao}"></div> </body> </html>
-
//在templates目录下的所有页面,都需要Controller来进行跳转 //这个跳转需要模板引擎的支持 thymeleaf @Controller public class IndexController { @RequestMapping("/test") public String index(Model model){ String name = "caixu"; model.addAttribute("nihao",name); return "test"; } }
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBiJFFn3-1677419918889)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230217230426090.png)]
-
-
语法
-
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>$Title$</title> </head> <body> <!--所有的html页面都可以用thymeleaf所接管 th:元素名 th:text,th:class--> <div th:text="${nihao}"></div> <!--th:text不能自动的识别表达式中的html标签,但是utext可以识别--> <div th:utext="${nihao}"></div> <hr> <!--${users里面的值是model里面传过来的值,user是${users}里面的值,${user}的值是来自于遍历的user--> <h3 th:each="user:${users}" th:text="${user}"></h3> </body> </html>
-
@Controller public class IndexController { @RequestMapping("/test") public String index(Model model){ String name = "<h1>caixu</h1>"; model.addAttribute("nihao",name); model.addAttribute("users", Arrays.asList("傻逼","狗日的")); return "test"; } }
-
SpringBoot的MVC配置
-
如果想要加入一些mvc中的特性的话,那就需要我们写一个config类,加@Configuration注解,然后实现WebMvcConfigurer接口.如果要用里面的内容的话可以直接实现里面的方法
-
文件文章:
- If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own
@Configuration
class of typeWebMvcConfigurer
but without@EnableWebMvc
.
- If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own
-
注,不能加@EnableMvc 如果加了就会全面接管
可以实现的方法
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-96pgDyN7-1677419918889)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230218231813718.png)]
-
例如addInterceptors 加一个拦截器
-
//在springboot中,如果我们要扩展springmvc的话,官方建议我们这么做.写一个config然后加@Configuration @Configuration public class MyMvcConfig implements WebMvcConfigurer { //视图跳转 @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/kuang").setViewName("test"); } }
-
在springboot中,有非常多的xxx configuration 帮助我们进行拓展设置,只要看见了这个东西,我们就要注意了.
自定义配置类
//自定义国际化组件 @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); }
六,作业测试
基础准备
-
模板资源地址:https://getbootstrap.com/docs/4.0/examples/
-
在进行首页配置时,所有的页面静态资源都需要使用thymeleaf来接管,例:
th:href="@{/asserts/css/dashboard.css}
国际化
-
1,在resources的目录下面建立一个i18n 里面加入
login.properties
,在这个里面放入login_en_US.properties
和login_zh_CN.properties
分别对应中文和英文 -
2,在application.properties中加入一个
国际化配置
-
#国际化 我们配置文件的真实位置 国际化的message用#{} spring.messages.basename=i18n.login
-
-
3,将html页面更改,
message的消息使用@{}
-
<a class="btn btn-sm" th:href="@{/index.html(I='zh_CN')}">中文</a> <a class="btn btn-sm" th:href="@{/index.html(I='en_US')}">English</a>
-
-
4,加入一个
配置类
-
public class MyLocaleResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { //获取请求中的参数 String language = request.getParameter("I"); System.out.println(language); Locale locale = Locale.getDefault(); //如果没有就使用默认的 //如果请求携带了国际化的参数 if(!StringUtils.isEmpty(language)){ //zh_CN String[] s = language.split("_"); locale = new Locale(s[0], s[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } }
-
-
5,将组件配置到
MvcConfig扩展
里面-
//自定义国际化组件 @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); }
-
拦截器
-
1,在登陆的时候写一个参数 HttpSession session
-
session.setAttribute("loginUser", username);
-
-
2.写一个拦截器的类
-
登录拦截器原理:拦截器原理,在登录的时候,如果登录成功之后就会发出一个session,所以我们只需要检查时候存在登录session即可如果存在就可以访问,如果没有就不可以
-
//登录拦截器 重写方法.返回true放行,false不放 public class LoginHandlerIntercepter implements HandlerInterceptor { //拦截器原理,在登录的时候,如果登录成功之后就会发出一个session,所以我们只需要检查时候存在登录session即可如果存在就可以访问,如果没有就不可以 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //登录成功之后就会存在Session Object loginUser = request.getSession().getAttribute("loginUser"); if(loginUser==null){ request.setAttribute("msg", "没有权限,请先登录"); request.getRequestDispatcher("/index.html").forward(request,response); return false; }else { return true; } } }
-
-
3,在Config里面配置拦截器,重写
public void addInterceptors(InterceptorRegistry registry)
方法-
@Override public void addInterceptors(InterceptorRegistry registry) { //加入一个拦截器,拦截全部页面,除过"/index.html","/","/user/login" registry.addInterceptor(new LoginHandlerIntercepter()) .addPathPatterns("/**") .excludePathPatterns("/index.html","/","/user/login","/asserts/css/*","/asserts/js/*","/asserts/img/*"); }
- addInterceptor:加入拦截器
- addPathPatterns:需要拦截的东西 /** 全部
- excludePathPatterns:这些文件排除在外,不进行拦截
-
抽取与复用
- 使用th:fragment 抽取
th:fragment="sidebar"
- 使用th:replace替换
th:replace="~{commons/commons::topbar}"
- 如果要传参数,可以直接使用()传参,接收判断
员工列表展示
- 前端
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>邮箱</th>
<th>性别</th>
<th>部门</th>
<th>生日</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emp}">
<td th:text="${emp.id}"></td>
<td th:text="${emp.lastName}"></td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender==1?'男':'女'}"></td>
<td th:text="${emp.department.departmentName}"></td>
<td th:text="${emp.birth}"></td>
<td>
<button class="btn btn-sm btn-primary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
</tr>
</tbody>
- 后端
@RequestMapping("/findEmployee")
public String findEmployee(Model model){
Collection<Employee> all = employeeDao.findAll();
model.addAttribute("emp",all);
return "emp/list";
}
新增/添加员工
1,按钮提交
2.跳转到添加界面
3.添加员工成功然后跳转回首页
CRUD完成
404
- 只需要在templates文件夹下面建立一个error文件夹,如果报了错之后,会自动的跳转到404.html’
七,网站开发
前端:
-
模板:别人买好的,我们拿来改
-
框架:组件:自己手动组合! BootStrap,Layui,semantic-ui
- 格栅系统
- 导航栏
- 侧边栏
- 表单
-
1.前端搞定:页面
-
2.设计数据库
-
3,前端让他能够自动运行,独立化工程
-
4.数据接口如何对接:json
-
5.前后端联调测试
八,以下内容
- JDBC
- Mybatis: 重点
- Druid: 重点
- Shiro: 安全
- SpringSecurity: 安全,重点
- 异步任务~,邮件发送,定时任务
- Swagger
- Dubbo + Zookeeper
9,整合数据库
1,JDBC数据库
1.1 创建项目
-
选用关系型数据库里面的JDBC和Mysql Driver
-
所需要的pom坐标
-
<dependency> <!--jdbc--> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--mysql数据库--> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>
-
1.2 配置
-
# 应用服务 WEB 访问端口 server.port=8080 # 数据库驱动: spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 数据库连接地址(时区配置) spring.datasource.url=jdbc:mysql://localhost:3306/ljh02?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 # 数据库用户名&密码: spring.datasource.username=root spring.datasource.password=021012
-
他会直接生成DataSource,直接注入即可
-
如果出现时区报错,就加入一个
serverTimezone=UTC
1.3 运用
-
在Springboot中会有
xxxx Template:Springboot
已经配置好了,直接使用即可 -
package com.ljh.Controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.Map; @RestController public class JDBCController { @Autowired JdbcTemplate jdbcTemplate; //查询数据库的全部信息 @RequestMapping("/userlist") public List<Map<String,Object>> userList(){ String sql = "select * from student"; List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql); return maps; } //添加用户 @RequestMapping("/addUser") public String addUser(){ String sql = "insert into student (id,name,telephoneNumber) values (111,'shabi','123457')"; jdbcTemplate.execute(sql); return "redirect:/userlist"; } //更新用户 @RequestMapping("/update/{id}") public int updateUser(@PathVariable("id")Integer id){ String sql = "update student set id = ?,name=?,telephoneNumber=? where id = "+id; Object[] o = new Object[3]; o[0] = 1; o[1] = "diaosi"; o[2] = "19090"; int update = jdbcTemplate.update(sql, o); return update; } //删除用户 @RequestMapping("/delete/{id}") public int deleteUser(@PathVariable("id")Integer id){ String sql = "delete from student where id = ?"; int update = jdbcTemplate.update(sql, id); return update; } }
2,整合Druid数据源
2.1 配置`
2.2 配置文件的配置
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
-
在Application.properties中配置
-
# 配置数据源 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
-
-
直接使用即可
2.2 内部的配置
-
#数据库连接信息 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 021012 url: jdbc:mysql://localhost:3306/ljh02?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 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=5 druid: stat-view-servlet: # 是否启用StatViewServlet默认值true enabled: true # 访问路径为/druid时,跳转到StatViewServlet url-pattern: /druid/* # 是否能够重置数据 reset-enable: false # 需要账号密码才能访问控制台,默认为root login-username: druid login-password: druid # IP白名单 allow: 127.0.0.1 # IP黑名单(共同存在时,deny优先于allow) deny:
2.3 log4j在druid配置中的整合
-
配置一个config
-
@Configuration public class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druidDataSource(){ return new DruidDataSource(); } //后台监控 //因为Springboot中内置了Servlet容器,所有没有web.xml文件,替代方法:ServletRegistrationBean @Bean public ServletRegistrationBean statViewServlet(){ ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*"); HashMap<String,String> init = new HashMap<>(); //增加配置 init.put("loginUsername", "admin"); //登录key的值是固定的 loginUsername和loginPassword init.put("loginPassword", "123456"); //允许谁能访问 init.put("allow", "");//如果是个空值,那么说谁都可以访问 //后台有人登录 bean.setInitParameters(init); //设置初始化 return bean; } }
-
注: 因为Springboot中内置了Servlet容器,所有没有web.xml文件,替代方法:
ServletRegistrationBean
- Spring Boot 源码的都支持,Spring boot 默认为我们提供了注册 Servlet 三大组件 Servlet、Filter、Listener 的接口。我们只需按需配置和添加少量的代码即可实现添加 Servlet 的功能。
-
注: 在初始化数据的时候我们只能用
登录key的值是固定的 loginUsername和loginPassword
两个值来登录,否则会出现错误 -
注:如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
-
在运行的时候,我们可以使用localhost:8080/druid 来访问druid页面,在页面里面可以发现sql监控(日志信息)
2.4 整合filter过滤器
-
//filter过滤 @Bean public FilterRegistrationBean webStatFilter(){ FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); Map<String,String> init = new HashMap<>(); //可以过滤那些请求 //这些东西不进行统计 init.put("exclusions","*.js,*.css,/druid/*"); //初始化 bean.setInitParameters(init); return bean; }
-
FilterRegistrationBean:通过实现javax.servlet.Filter接口,覆盖其doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)方法,决定拦截或放行
3,整合Mybatis框架(重点)
- 导入依赖
- 配置文件
- @mapper注解
10,SpringSecurity(安全)
在web开发中,安全第一位,过滤器和拦截器
- https://www.freesion.com/article/98931326949/#1__4 SpringSecurity优秀笔记
功能性需求:否
做网站应该在设计网站之初就考虑安全
- 漏洞:隐私泄露
- 架构
shiro,SpringSecurity:两个框架很像,除了类不一样,名字不一样
认证,授权(vip1,vip2,vip3)
- 功能权限
- 访问权限
- 菜单权限
- … 拦截器,过滤器
10,1 准备
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13HI5gm4-1677419918889)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225175944631.png)]
- 在这个页面,我们想让他登录只有,有的人不访问level2或者level3等,就可以采用这些方法
- 在结合SpringSecurity时候,我们会采用Aop的思想,横切
10.2 介绍
Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
- @Enablexxx 开启某个功能
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而不是只在Spring Security 中存在。
参考官网:https://spring.io/projects/spring-security
查看我们自己项目中的版本,找到对应的帮助文档:
https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4
10.3 用户认证和授权
目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能
- https://www.cnblogs.com/magic-sea/p/12824718.html 优秀总结(一)
10.3.1引入 Spring Security 模块
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
10.3.2 写配置类
-
https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/#jc这个页面里面有Java Config的配置[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JEz0PSrc-1677419918890)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225181014014.png)]
-
//1,加注解 @EnableWebSecurity; 2,写继承 WebSecurityConfigurerAdapter @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); } }
10.3.2.1 SpringSecurity中的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wCyXEoE6-1677419918890)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225181202796.png)]
10.3.2.2 http安全
-
@Override //链式编程思想 http安全 protected void configure(HttpSecuity http) throws Exception { //请求授权规则 //首页所有人可以访问,功能页只有对应有权限的人才能访问 //authorizeRequests() 认证请求 //antMatchers("/") 添加页面 //permitAll() 所有人都可以访问 //hasRole("vip1") 谁能够访问 http.authorizeRequests() .antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); //没有权限会跳入到登录页面,需要开启登录页面 //login页面 http.formLogin(); }
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i3ftyASZ-1677419918890)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225182650218.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h9lBXU1u-1677419918890)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225182747057.png)]
10.3.3 认证
-
//认证 springboot 2.1.x可以直接使用 //密码编码,PasswordEncoder //在Spring Security 5.0+ 新增加密 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //withUser 是虚拟数据 传入username password:密码 roles:是权限(角色) //passwordEncoder 是对于密码的加密,如果不加密数据是不能用的 //new BCryptPasswordEncoder().encode("123") 设置密码 //正常是再说数据库里面读取 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("ljh").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2") .and() .withUser("zzt").password(new BCryptPasswordEncoder().encode("234")).roles("vip2","vip3"); }
-
这个给一个用户给了一个角色,这个角色可以访问
http安全里面的hasRole()
访问的人 -
在登录ljh的时候,只能对level1和level2进行访问,在登录zzt的时候,只能对level2和level3里面的人进行访问
-
在访问无权限的时候系统会弹出
-
There was an unexpected error (type=Forbidden, status=403). Forbidden (被禁止的,即,无权访问)
-
-
如果采用数据库认证,则需
-
@Autowired private DataSource dataSource; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // ensure the passwords are encoded properly UserBuilder users = User.withDefaultPasswordEncoder(); auth .jdbcAuthentication() //调用jdbc .dataSource(dataSource) //定义数据源 .withDefaultSchema() .withUser(users.username("user").password("password").roles("USER")) .withUser(users.username("admin").password("password").roles("USER","ADMIN")); }
-
10.3.4 注销
- 1,在
HttpSecuity
中添加http.logout();
方法即可 - 2.注销之后跳转页面
http.logout().logoutSuccessUrl("/");
10.3.5 记住我功能
- 1,与注销类似,在
HttpSecuity
中添加http.rememberMe()
方法即可; - 2,如果自定义就可以看
10.3.6
10.3.6 自定义登录
-
//login页面 loginPage("/toLogin")登录页面 // usernameParameter("user"):前端提交表单的name可以自定义 // passwordParameter("pwd")通前一个一样 // loginProcessingUrl("/login");实际登录页面 http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
- 关于
.loginProcessingUrl("/login")
的理解- 前端里面的
<form th:action="@{/login}" method="post">
,但是登录页面是loginPage("/toLogin")
所以就会存在差异,所以就需要loginProcessingUrl("/login")
来进行更改 - 但是前端是
<form th:action="@{/toLogin}" method="post">
,登录页面也是loginPage("/toLogin")
,就不需要加
- 前端里面的
- 关于
-
自定义页面的记住我功能
-
//记住我功能 实际就是发送了一个cookie 默认保持两周,在自定义页面里面,有一个单选框,我们直接获取单选框的name即可 http.rememberMe().rememberMeParameter("remember");
-
前端里面
-
<input type="checkbox" name="remember"> 记住我
-
我们需要的是
rememberMeParameter("remember");
和name="remember"
相互对应
-
-
11.Shiro
11.1 简介
- Apache Shiro 是一个Java 的安全 (权限) 框架
- Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在]avaSE环境,也可以用在JavaEE环境.
- Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。
- 下载地址: http://shiro.apache.org/
11.2 功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZhvBB0PO-1677419918891)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225210500480.png)]
Authentication
:认证、验证Authorization
:授权Session Management
:会话管理Crytography
:加密机制
11.3 shiro架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xb11Tzru-1677419918891)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225210353680.png)]
-
Subject
-
应用代码直接交互的对象是Subject,也就是说Shiro的对外API 核心就是Subject Subject 代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等 Subject 其实是一个门面,SecurityManager才是实际的执行者 与Subject 的所有交互都会委托给SecurityManager
-
-
SecurityManager
-
安全管理器 所有与安全有关的操作都会与SecurityManager交互,且其管理着所有Subject 它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色
-
-
Realm
-
Shiro从Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm 获取相应的用户进行比较以确定用户身份是否合法 也需要从Realm 得到用户相应的角色/权限进行验证用户是否能进行操作,可以把Realm 看成DataSource
-
-
总结:
-
应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager 我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断
-
11.4 代码分析(入门案例(官网提供))
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;
public class Quickstart {
//集成log4j框架
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// 工厂模式,通过shiro.ini 配置文件中的信息,生成一个工厂实例
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 通过SecurityUtils 获取当前执行的用户Subject:
Subject currentUser = SecurityUtils.getSubject();
//通过当前用户拿到Shiro的Session 可以脱离web存值取值
Session session = currentUser.getSession();
// 使用session存值取值
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// 让我们登录当前用户,以便我们可以检查角色和权限:
// 这里和SpringSecurity 使用了类似的代码,判断用户是否被认证
if (!currentUser.isAuthenticated()) {
//如果被认证,就可以获得一个令牌(token)
//通过用户的账号密码生成一个令牌
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 (AuthenticationException ae) {
//意外情况?错误?
}
}
//currentUser用法
//打印其标识主体(在这种情况下,为用户名) :
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
// 检查角色是否存在
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//粗粒度
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.");
}
//细粒度
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!");
}
//注销
currentUser.logout();
//结束
System.exit(0);
}
}
-
主要流程
-
//获取当前对象 Subject currentUser = SecurityUtils.getSubject(); //根据当前对象获取对应的Session Session session = currentUser.getSession(); //判断用户是否被认证,根据用户名和密码生成令牌 UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); //设置记住我功能 token.setRememberMe(true); //拿到令牌进行登录 currentUser.login(token); //打印其标识主体 currentUser.getPrincipal() //注销 currentUser.logout();
-
11.5 Shiro整合Springboot
11.5.1 准备工作
shiro的架构
//ShiroFilterFactoryBean 第三步
//DefaultWebSecurityManager 第二部
//创建realm对象 需要自定义 由下往上配置
-
Realm对象创建
-
//自定义的UserRealm 让他extends AuthorizingRealm public class UserRealm extends AuthorizingRealm { //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了->授权doGetAuthorizationInfo"); return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了->认证doGetAuthenticationInfo"); return null; } }
-
-
ShiroConfig类的创建
-
@Configuration public class ShiroConfig { //ShiroFilterFactoryBean 第三步 @Bean(name = "filterShiroFilterRegistrationBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); return bean; } //DefaultWebSecurityManager 第二部 @Bean //将上面的userRealm拿下来,让其关联 @Qualifier(方法名) public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联UserRealm securityManager.setRealm(userRealm); return securityManager; } //创建realm对象 需要自定义 由下往上配置 @Bean //被spring托管 第一步 public UserRealm userRealm(){ return new UserRealm(); } }
-
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bL2RwJm8-1677419918891)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230225222235318.png)]
11.5.2 实现登陆拦截
-
//ShiroFilterFactoryBean 第三步 @Bean(name = "filterShiroFilterRegistrationBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); /* * anon:无需认证就可以访问 * authc:必须认证了才能访问 * user:必须拥有 记住我 功能之后才能访问 * perms:拥有对某个资源的权限才能访问 * role: 拥有某个角色才能访问 * */ //登录拦截 Map<String,String> filterMap = new LinkedHashMap<>(); filterMap.put("/user/add", "anon"); filterMap.put("/user/update", "authc"); bean.setFilterChainDefinitionMap(filterMap); //设置登录请求 bean.setLoginUrl("/toLogin"); return bean; }
-
对于某一个连接可以访问,都赋予权限
-
也可以使用通配符
filterMap.put("/user/*", "authc");
user下的全部路径都是需要认证之后才能登录的 -
在登录点击之后会跳到认证里面,即
Realm中的认证
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nuNQluIA-1677419918891)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226082418256.png)]
11.5.3 授权和认证
11.5.3.1 认证
无数据库
当我们定义public class UserRealm extends AuthorizingRealm
之后,会重写doGetAuthenticationInfo
方法,即认证方法
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了->认证doGetAuthenticationInfo");
//用户名,密码 数据库中取一般
String name = "root";
String password = "123";
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
if(!token.getUsername().equals(name)){
return null; //当返回值为null时,他会抛出一个授权的异常 UnknownAccountException
}
//密码认证 shiro做,没有getPassword方法,防止密码泄露 所需要的参数 Object principal, Object credentials, String realmName
return new SimpleAuthenticationInfo("",password,"");
}
- 在11.5.2 中,点击登录之后,他会将数据自动跳转到
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException
方法,即(认证方法) return new SimpleAuthenticationInfo("",password,"");
没有getPassword方法,防止密码泄露
有数据库
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了->认证doGetAuthenticationInfo");
//用户名,密码 数据库中取一般
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userService.queryUserByName(token.getUsername());
if(user==null){ //没有这个人
return null;
}
//密码认证 shiro做,没有getPassword方法,防止密码泄露 所需要的参数 Object principal, Object credentials, String realmName
//shiro里面会进行加密对密码
//可以采用加密方式,md5加密和md5盐值加密
return new SimpleAuthenticationInfo("",user.getPassword(),"");
}
- 在连接数据库时,**纯数字密码需要加单引号 **
11.5.3.2 授权
设置页面的权限
-
在shiro配置类中的
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager)
方法中配置权限 -
//授权 第二个参数表示,需要user用户的add权限才能访问 //所有的权限必须有user:add这个字符串而授权 filterMap.put("/user/add", "perms[user:add]");
- 在登录之后,如果没有权限,我们去访问add,结果是
type=Unauthorized, status=401(无权限操作)
- 在登录之后,如果没有权限,我们去访问add,结果是
设置用户的权限
-
//授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了->授权doGetAuthorizationInfo"); //在我们进入配了权限的页面时,我们就会授权 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //只要经过这个地方,就给加一个user:add权限 //但是这个问题就是,全部人员都会进入这个方法,然后全部人都会被授权 info.addStringPermission("user:add"); return info; }
- 但是这个样子会给全部的人加上user:add的权限
-
改进:在数据库内部添加user:add
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P4zkWjC9-1677419918892)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226100550887.png)]
-
使用数据库来完成查询权限控制
-
//授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了->授权doGetAuthorizationInfo"); //在我们进入配了权限的页面时,我们就会授权 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //只要经过这个地方,就给加一个user:add权限 //但是这个问题就是,全部人员都会进入这个方法,然后全部人都会被授权 // info.addStringPermission("user:add"); //拿到当前登录的对象 Subject subject = SecurityUtils.getSubject(); User principal = (User) subject.getPrincipal(); //拿到当前的user对象 //设置了用户的权限 info.addStringPermission(principal.getPerms()); return info; }
-
//doGetAuthenticationInfo(AuthenticationToken authenticationToken)中的 //user是通过在登录页面查到的人 User user = userService.queryUserByName(token.getUsername()); //把user放到principal中,以供SecurityUtils.getSubject()中的subject调用 return new SimpleAuthenticationInfo(user,user.getPassword(),"");
-
-
doGetAuthenticationInfo中的这行代码表示将user对象存到principal中,如果调用了当前登录对象,subject,就可以通过
subject.getPrincipal()
取出内部的值 -
然后我们就可以将这个值得perms取出,通过
addStringPermission
方法,加给info.返回权限
-
-
例如:用户名ljh 只能进入到add页面进行访问,如果进入到update里面,会进入无权操作页面
用户名lx,只能进入到update页面进行访问,如果进入到add里面,就会进入无权操作页面
-
bean.setUnauthorizedUrl("/noauth"); //没有授权的页面
-
@RequestMapping("/noauth") @ResponseBody public String unauthorized(){ return "未经过授权,无权访问"; }
-
登出操作
-
获取Subject然后调用
subject.logout();
-
@RequestMapping("/logout") @ResponseBody public String logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); return "退出登录"; }
-
真正的登录操作执行
@RequestMapping("/login")
public String login(String username,String password,Model model){
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try{
subject.login(token); //执行登陆的方法
return "index";
}catch (UnknownAccountException e){
//用户名错误
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){
//密码错误
model.addAttribute("msg","密码错误");
return "login";
}
}
- 在
subject.login(token)
的这一行,真正的完成了登入,如果需要加一些登录session的话,就需要在这个地方或者认证的地方放入session
12 Swagger
12.1 介绍
- 后端:后端控制处,服务层,数据访问层
- 前端:前端控制层,视图层
- 伪造后端数据:自己写,没有使用后端
- 前后端交互:=>API
- 前后端相互独立,松耦合
- 前后端甚至可以不是不同服务器
- 前后端分离
- 前端测试后端接口
- 后端提供接口,需要实时更新的消息及改动
Swagger
- 号称世界上最流行的Api框架.
- RestFul Api 文档在线自动生成工具=>
Api文档与API定义同步更新
- 可以在线测试API接口
官网:https://swagger.io/
12.2 在项目中使用Swagger
- 需要springbox
- swagger2
- ui
12.3 Springboot集成Swagger
-
新建一个Springboot项目
-
导入相关依赖
-
<!--3.0.0 只导入--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version> </dependency>
-
-
配置文件配置
-
spring: mvc: pathmatch: matching-strategy: ant_path_matcher
-
-
编写一个hello工程
-
配置Swagger =>Config
-
@Configuration //配置 @EnableSwagger2 //开启swagger @EnableOpenApi //3.0.0 版本 public class SwaggerConfig { }
-
-
// 3.0.0 以上访问这个页面
http://localhost:8080/swagger-ui/index.html -
Swagger 3.0 配置参考: https://cloud.tencent.com/developer/article/1936862
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HBcirwLe-1677419918892)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226121144337.png)]
12.4 配置Swagger
12.4.1 Swagger的bean实例Docket;
-
@Configuration //配置 @EnableSwagger2 //开启swagger @EnableOpenApi //3.0.0 版本 public class SwaggerConfig { @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2)//配置了Swagger的bean实例 .apiInfo(apiInfo()); } //配置Swagger信息 public ApiInfo apiInfo(){ //name url email 作者信息 Contact contact = new Contact("ljh","https://blog.kuangstudy.com","1196915419@qq.com"); return new ApiInfo("LJH Api Documentation", "LJH Api Documentation", "1.0", "https://blog.kuangstudy.com", contact, "" + "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList()); } }
12.4.2 配置扫描接口
Docket.select();
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)//配置了Swagger的bean实例
.apiInfo(apiInfo())
.select()
//RequestHandlerSelectors 配置要扫描的接口的方法
//basePackage 指出要扫描的包
//ang():扫描全部
//none:一个都不扫描
//withClassAnnotation: 扫描类上的注解
//withMethodAnnotation: 扫描方法上的注解
// .apis(RequestHandlerSelectors.withMethodAnnotation(GetMapping.class))
.apis(RequestHandlerSelectors.basePackage("com.ljh.Controller"))
.paths(PathSelectors.ant("/ljh"))//过滤什么路径
//参数:
//ant : 路径
//none:一个也不
//any:全部
//regex:正则
.build();
}
配置是否启动Swagger
.apiInfo(apiInfo())
.enable(true)
-
是否启动,如果为false,则Swagger不能在浏览器中访问]
-
通过环境来判断是否进行Swagger
-
//设置要显示的Swagger的环境 Profiles profiles = Profiles.of("dev"); //获取环境 environment.acceptsProfiles判断是否处于这个环境中 boolean b = environment.acceptsProfiles(profiles); //把b传给enable,自动判断是否进行Swagger
-
-
然后将b传入到enable中即可
- 如果环境是dev(开发环境),就可以直接打开enable
配置Api文档的分组
.groupName("ljh")
-
定义多个Docket就可以实现多个分组
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5cTRIbrP-1677419918892)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226155433552.png)]
-
@Bean public Docket B(){ return new Docket(DocumentationType.SWAGGER_2).groupName("B"); } @Bean public Docket A(){ return new Docket(DocumentationType.SWAGGER_2).groupName("A"); } @Bean public Docket docket(Environment environment){ return new Docket(DocumentationType.SWAGGER_2).groupName("ljh"); }
-
14.4.3 实体类配置
-
只要我们的接口中,只要返回的有实体类,就会被扫描到Swagger
-
//只要我们的接口中,只要返回的有实体类,就会被扫描到Swagger @PostMapping("/user") public User user(){ return new User(); }
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1I3NP7QO-1677419918892)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226160347916.png)]
-
加Api注解的意思为,可以在Swagger页面里面显示中文
@ApiModel("用户实体类") public class User { @ApiModelProperty("用户名") private String username; @ApiModelProperty("密码") private String password;
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBwoqU7C-1677419918892)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226160307950.png)]
-
-
Api注解的样式(
目的就是为了给文档加注解
)-
@ApiModel("用户实体类") //加在类上面
-
@ApiModelProperty("用户名") //加在成员变量的属性上面
-
@ApiOperation("Hello控制类") //接口 @PostMapping("/hello2") public String hello2(@ApiParam("用户名") String username){ //参数Api return "hello"+username; }
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zMIf6vfZ-1677419918893)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226161004284.png[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zc4UmY1N-1677419919936)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226161122971.png)]]
-
12.5 总结
1.我们可以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
2.接口文档实时更新
3.可以在线测试
13 任务
13.1 异步任务
13.1.1 问题
- 在存在异步时,如果不采取措施,那么会导致前端页面转圈,影响用户体验
- 在异步开启后,不是异步的会先行加载,留有异步的操作进行加载
13.1.2 解决措施
-
1.在异步的方法上面加异步注解
-
@Async public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("数据正在发送"); }
-
-
2.在Springboot的启动类上面打开异步的注解
-
@EnableAsync //开启异步 @SpringBootApplication public class ZSpringBoot10TestApplication { public static void main(String[] args) { SpringApplication.run(ZSpringBoot10TestApplication.class, args); } }
-
13.2 定义任务
介绍
TaskScheduler 任务调度程序
TaskExecutor 任务执行者
@EnableScheduling //开启定时功能的注解
@Scheduled //什么时候执行
使用
-
@Service public class ScheduledService { //在一个特定的时间执行这个方法 //cron表达式: //秒 分 时 日 月 周几 // @Scheduled(cron = "0 * * * * 0-7") //在周一到周日的每天的每小时的每分钟的第0秒执行一次 @Scheduled(cron = "30 24 17 * * ?") public void hello(){ System.out.println("hello,你被执行了"); } }
-
@EnableAsync //开启异步 @EnableScheduling //开启定时功能的注解 @SpringBootApplication public class ZSpringBoot10TestApplication { public static void main(String[] args) { SpringApplication.run(ZSpringBoot10TestApplication.class, args); } }
-
在线cron的解析器: https://www.pppet.net/
13.3 邮件发送
13.3.1 配置
-
1,导入依赖
-
<!--邮件依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
-
-
2,导入配置
-
spring.mail.host=smtp.qq.com spring.mail.username=1196915419@qq.com spring.mail.password=ekarnpjvpagegebe # QQ需要开启加密验证 spring.mail.properties.smtp.ssl.enable=true
-
-
3,写测试类,发送邮件
-
@SpringBootTest class ZSpringBoot10TestApplicationTests { @Autowired JavaMailSenderImpl mailSender; @Test void contextLoads() { //一个简单的邮件 SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); simpleMailMessage.setSubject("你好"); simpleMailMessage.setText("秀秀"); simpleMailMessage.setTo("1196915419@qq.com"); simpleMailMessage.setFrom("1196915419@qq.com"); mailSender.send(simpleMailMessage); } @Test void contextLoads2() throws MessagingException { //一个复杂的邮件 MimeMessage mimeMessage = mailSender.createMimeMessage(); //组装 MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true); //多文件需要加true //正文 helper.setSubject("不再年轻了"); helper.setText("<p style=‘color:red’>谢谢你放弃了我</p>",true); //可以发送文本,但是如果要解析html的话就需要加true //附件 helper.addAttachment("1.JPG", new File("E:\\桌面目录(C盘)\\1.JPG")); helper.setTo("3241016337@qq.com"); helper.setFrom("1196915419@qq.com"); mailSender.send(mimeMessage); } }
-
该方法在Springboot的测试类中,直接点击就可以发送
-
14 Springboot集成Redis
-
源码分析
-
@Bean @ConditionalonMissinaBean(name ="redisTempate") // 我可以自己定义一redisTemplate来替换这个默的 public RedisTemplate<object, object> redisTemplate(RedisConnectionFactory redisConnectionFactorythrows UnknownHostException { // 默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化!// 两个泛型都是 object,object 的类型,我们后使用需要强制转换 <string,object> RedisTemplate<object, object> template = new RedisTemplate<>0); template,setConnectionFactory(redisConnectionFactory); return template; } @Bean @conditionalonMissingBean // 由于 string 是redis中最常使用的类型,所以说单独提出来了一个bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException { stringRedisTemplate template = new stringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
-
整合
1.导入依赖
```xml
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
```
2.配置连接
```properties
spring.redis.host=localhost
spring.redis.port=6379
# spring.redis.lettuce.pool 配置redis采用lettuce连接池即可
```
3.测试
-
@SpringBootTest class ZSpringBoot11RedisApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { //redisTemplate 操作不同的数据类型,api和我们的指令是一样的 //opsForValue 操作字符串 //opsForList 操作list集合 //opsForHash //.... // redisTemplate.opsForValue(); //除了基本操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD //获取redis的连接对象 // RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); // connection.flushDb(); // connection.flushAll(); redisTemplate.opsForValue().set("mykey", "ljh"); System.out.println(redisTemplate.opsForValue().get("mykey")); } }
自定义配置类
package com.ljh.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
/**
* 编写自定义的 redisTemplate
* 这是一个比较固定的模板
*/
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 为了开发方便,直接使用<String, Object>
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
// Json 配置序列化
// 使用 jackson 解析任意的对象
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// 使用 objectMapper 进行转义
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key 采用 String 的序列化方式
template.setKeySerializer(stringRedisSerializer);
// Hash 的 key 采用 String 的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value 采用 jackson 的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
// Hash 的 value 采用 jackson 的序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
// 把所有的配置 set 进 template
template.afterPropertiesSet();
return template;
}
}
可以很好的序列化,可以更好的设置编码
RedisUtil 的工具类
package com.ljh.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
-
调用代码
-
@Test public void test2(){ redisUtil.set("diaosi", "zzt"); System.out.println(redisUtil.get("diaosi")); }
-
15 分布式 Dubbo+Zookeeper+Springboot
- 笔记:https://mp.weixin.qq.com/s/sKu9-vH7NEpUd8tbxLRLVQ
15.1 什么是分布式系统
- 在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;
- 分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
- 分布式系统(distributed system)是建立在网络之上的软件系统.
- 首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。
https://cn.dubbo.apache.org/zh-cn/index.html Dubbo官网
15.2 Dubbo
15.2.1 介绍
-
下载:https://www.apache.org/dyn/closer.lua/dubbo/3.1.4/apache-dubbo-3.1.4-src.zip
-
https://github.com/apache/dubbo-admin/tree/master-0.2.0
-
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
dubbo官网 http://dubbo.apache.org/zh-cn/index.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ztGHeMI8-1677419918893)(C:\Users\11969\AppData\Roaming\Typora\typora-user-images\image-20230226195608427.png)]
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
调用关系说明
l 服务容器负责启动,加载,运行服务提供者。
l 服务提供者在启动时,向注册中心注册自己提供的服务。
l 服务消费者在启动时,向注册中心订阅自己所需的服务。
l 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
15.3 Zookeeper
下载:https://zookeeper.apache.org/releases.html#download
- zookeeper:注册中心
- dubbo-admin:是一个额监控管理后台 查看我们注册了那些服务,哪些服务被消费了
- Dubbo : jar包
15.4 整合Springboot
- 不整合了