SpringBoot
什么是Spring:Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
如何简化Java的开发的
- 基于POJO的轻量级和最小侵入性编程
- 通过IOC,依赖注入和面向接口实现松耦合
- 基于切面和惯例进行声明式编程
- 通过切面和模板减少样式代码
SppringBoot主要优点:
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码和生成XML配置的要求
- 让所有的Spring开发者更快的入门
什么是微服务:微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合,可以通过http的方式进行互通,要说微服务架构,可以先看看我们以前的单体应用架构。
单体应用架构:我们将一个应用中的所有应用服务都封装在一个应用中,数据访问,web访问等放在一个war包中。
- 这样做的好处是,易于开发和测试,也十分方便部署,当要拓展时,只要将war复制多份,然后放到多个服务器上,在做个负载均衡就可以了。
- 单体架构应用的缺点是,哪怕要修改一个非常小的地方,都要停掉整个服务,重新打包,再部署这个应用的war包,特别是对于一个大型应用,我们不可能把所有内容放在一个应用里面,如何维护,如何分工合作都是问题。
微服务架构就是打破 all in one 的架构方式,把每个功能元素独立出来,把独立出来的功能元素动态组合,节省调用资源,每个功能元素的服务器都是一个可替换的,可独立升级的代码。
第一个SpringBoot
- 可以从官网新建项目后下载,解压后使用 IDEA 打开
- IDEA中新建 spring initiali 项目,选中 spring web 直接创建
导入 jar 包即可
自动装配
pom.xml
- Spring Boot Dependencies 核心依赖在父工程中
- 我们在写或者引入一些 SpringBoot 依赖的时候,不需要指定版本,就是因为父工程中已经写了。
启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
比如:spring-boot-starter-web,它就会自动帮我们导入web项目所有的依赖。
springboot会将所有的的功能场景,都变成一个个的启动器。
需要什么功能,找到对应的启动器就可以了(官网找) 。
主程序
//标注这是一个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({Registrar.class}) //导入注册器
@Import({AutoConfigurationImportSelector.class}) //导入选择器
//获取所有的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
获取候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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:自动配置的核心文件
SpringBoot所有装配是在启动时扫描并加载,所有的自动配置类都在spring.factories,但只有导入了对应的start,有了启动器,对应的配置才会生效,然后自动装配。
-
SpringBoot在启动的时候,从类路径下META-INF/spring.factories获取指定的值。
-
将这些自动配置的类导入容器,自动配置类就会自动生效,帮我们自动配置。
-
以前我们需要自动配置的东西,现在SpringBoot帮我们做了。
-
整合JavaEE,解决方案和自动配置的东西都在springframework.boot.autoconfigure:2.4.0包下。
-
它会把所有需要导入的组件以类名的方式返回,这些组件就会被添加到容器。
-
容器中也会存在非常多的 ***AutoConfigure 的文件,就是这个类给容器导入了这个场景所需要的所有组件。
-
有了自动装配,免去了我们手动编写配置注入功能组件的工作。
SpringApplication这个类主要是:
- 推断应用的类型是普通的项目还是web项目。
- 查找并加载所有可用初始化器,设置的initializers属性中。
- 找出所有的应用程序监听器,设置到listeners中。
- 推断并设置main方法的定义类,找到运行的主类。
自动装配的原理:
-
SpringBoot启动会加载大量的自动配置类。
-
我们看我们需要的配置类有没有在SpringBoot写好的自动配置类中。
-
再看这个配置类中到底装配了哪些组件(如果我们需要的组件在里面,就不用手动装配了)。
-
给容器中自动配置类添加组件时,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性即可。
***AutoConfiguration:自动配置类,给容器中添加组件
***Properties:封装配置文件的相关属性
yaml中配置debug:true,启动可以查看哪些配置类生效了。
在springboot中,有非常多的***.Configuretion会帮助我们进行拓展配置,只要看见了这个,需要注意。
@EnableWebMvc,这配置是导入了一个类,DelegatingWebMvcConfiguration,从容器中获取所有的WebMvcConfigurer。
yaml讲解
SpringBoot使用一个全局的配置文件,配置文件名称是固定的
- application.properties
- 语法结构:key=value
- application.yaml
- 语法结构:key:空格 value
配置文件可以帮我们修改SpringBoot自动配置的默认值。
YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。
标记语言
以前的配置文件,大多数使用xml来配置:比如一个简单的端口配置,对比一下yaml和xml
yaml配置:
server:
port: 8088
xml配置
<server>
<port>8088</port>
</server>
使用注解@ConfigurationProperties将配置文件中的值映射到这个组件中,将类中的属性与配置文件一一对应。
yaml也可以这样使用
person:
name: 周周${random.uuid}
age: ${random.int}
happy: true
birth: 2020/10/10
maps: {k1: v1,k2: v2}
list:
- code
- music
- girl
dog:
name: ${person.zr:hello}_旺财
age: 5
也可以使用@PropertySource加载指定的properties文件【不建议使用,建议使用yaml】
@ConfigurationProperties只用绑定一次,@Value需要每个字段添加。
yaml可以松散绑定,即yaml中写first-name,实体类写firstName,-后的字母默认是大写的,这就是松散绑定。
JSR303数据验证,就是我们可以在字段增加一层过滤器验证,可以保证数据的合法性。
复杂类型对象,yaml可以封装对象,@Value就不可以。
结论:
- 配置yaml和properties都可以获取到值,强烈推荐yaml。
- 如果我们在某个业务中,只获取配置文件中的某个值,可以使用一下@Value。
- 如果在javaBean和配置文件进行映射,就直接使用@ConfigurationProperties。
JSR303
验证是否为邮箱格式
@Component
@ConfigurationProperties(prefix = "person")
@Validated//数据校验
public class Person {
@Email
private String name;
}
其它注解
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 检查数字是否介于min和max之间.
@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
@Pattern 正则表达式
Web开发
静态资源
在springboot中,我们可以使用以下路径处理静态资源
- webjars http://localhost:8080/webjars 【WebMvcAutoConfiguration.addResourceHandlers】 webjars 需导入
- public,static,/**,resources http://localhost:8080 【WebMvcAutoConfiguration-->ResourceProperties-->Resources】
优先级:resorces>static(默认)>public
首页
WebMvcAutoConfiguration下部分源码
private Optional<Resource> getWelcomePage() {
String[] locations = WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations());
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}
即在资源配置目录下放入index.html即可自动识别。
Thymeleaf
pom中引入Thymeleaf,就可以将.html放在templates下访问。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
部分源码
@ConfigurationProperties(
prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
......
}
可以看出来前缀是classpath:/templates/,后缀.html,即html文件放在classpath:/templates/下
测试
package com.zr.controller;
//在templates下的所有页面,只能通过controller来跳转
//需要模板引擎的支持
@Controller
public class IndexController {
@RequestMapping("/test")
public String index(Model model){
model.addAttribute("msg","hello test");
return "test";
}
}
test.html
<body>
<div th:text="${msg}"></div>
</body>
拓展SpringMvc
package com.zr.config;
//扩展springmvc
@Configuration
//@EnableWebMvc //这配置是导入了一个类,DelegatingWebMvcConfiguration,从容器中获取所有的WebMvcConfigurer。
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/zr").setViewName("test");
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义视图解析器
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
员工管理系统
首页配置,使用了thymeleaf接管,所有的herf引用改为th:herf="@{}"(th标签支持xmlns:th="http://www.thymeleaf.org")
页面国际化
- 需要配置i18n文件
- 如果需要在项目中进行按钮自由切换,我们需要自定义一个组件LocaleResolver
- 然后将自己写的组件配置到spring容器 @Bean
-
{}
整合JDBC使用
对于数据访问层,无论使SQL(关系型数据库)还是NOSQL(非关系型数据库),SpringBoot底层都是采用 Spring Data 的方式进行统一处理。
application.yml
spring:
datasource:
username: root
password: 123456
#时区配置报错 增加时区配置serverTimezone=UTC
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&chacterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis数据库 user表
测试
@SpringBootTest
class Springboot04DataApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看默认的数据源
System.out.println(dataSource.getClass());
//获得数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//***templete: SpringBoot已经配置好的bean 拿来即用
connection.close();
}
}
controller
@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>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
@GetMapping("/addUser")
public String addUser(){
String sql = "insert into user(id,name,pwd) values(7,'韩信','6666')";
jdbcTemplate.update(sql);
return "add OK!";
}
@GetMapping("/updateUser/{id}")
public String updateUser(@PathVariable("id") int id){
String sql = "update user set name=?,pwd=? where id="+id;
//封装
Object[] objects = new Object[2];
objects[0] = "小周啊";
objects[1] = "8888";
jdbcTemplate.update(sql,objects);
return "update OK!";
}
@GetMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable("id") int id){
String sql = "delete from user where id=?";
jdbcTemplate.update(sql,id);
return "delele OK!";
}
}
整合Druid数据源
Druid是阿里巴巴开源平台上的一个数据库连接池实现,结合了C3P0,DBCP,PEOXOOL等DB池的优点,同时加入了日志监控。
SpringBoot默认的是Hikari数据源。
application.yml
spring:
datasource:
username: root
password: 123456
#时区配置报错 增加时区配置serverTimezone=UTC
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&chacterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#SpringBoot默认不注入这些属性的,需要自己绑定
#durid配置
initialSize: 5
minIdel: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控拦截,stat 监控统计, wall 防御sql注入,log4j 日志
#如果允许时报错,导入log4j依赖
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
监控
DruidConfig
@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<StatusManagerServlet> bean = new ServletRegistrationBean<>(new StatusManagerServlet(), "/druid/*");
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要有人登录,账号密码配置
HashMap<String, String> initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername","admin"); //登录的key是固定的
initParameters.put("loginPassword","123456");
//允许谁可以访问
initParameters.put("allow","");
//禁止谁能访问 initParameters.put("zr","39.405.48.101");
bean.setInitParameters(initParameters); //设置初始化参数
return bean;
}
@Bean
//filter
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
//可以过滤哪些请求
HashMap<String, String> initParameters = new HashMap<>();
//这些不进行统计
initParameters.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParameters);
return bean;
}
}
启动后访问localhost:8080/druid ,账号密码为配置中设置的值,登陆后进入监控界面。
整合MyBatis
依赖mybatis-spring-boot-starter
application.properties
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&chacterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#整合mybatis
mybatis.type-aliases-package=com.zr.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
User
package com.zr.pojo;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private String pwd;
}
UserMapper
package com.zr.mapper;
//这个注解表示了这是一个mybatis的mapper类
@Mapper
@Repository
public interface UserMapper {
List<User> queryUserList();
User queryById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
resource/mapper/UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zr.mapper.UserMapper">
<select id="queryUserList" resultType="User">
select * from user;
</select>
<select id="queryById" resultType="User">
select * from user where id=#{id};
</select>
<insert id="addUser" parameterType="User">
insert into user(id,name,pwd) values (#{id},#{name},#{pwd});
</insert>
<update id="updateUser" parameterType="User">
update user set name = #{name},pwd=#{pwd} where id=#{id};
</update>
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id};
</delete>
</mapper>
UserController
package com.zr.controller;
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/queryUserList")
public List<User> queryUserList(){
List<User> userList = userMapper.queryUserList();
return userList;
}
@GetMapping("/queryById/{id}")
public User queryById(@PathVariable("id") int id){
User user = userMapper.queryById(id);
System.out.println(user);
return user;
}
@GetMapping("/addUser")
public String addUser(){
userMapper.addUser(new User(7,"韩信","33333"));
return "addUser OK";
}
@GetMapping("/updateUser")
public String updateUser(){
userMapper.updateUser(new User(7,"韩信","54321"));
return "updateUser OK";
}
@GetMapping("/deleteUser")
public String deleteUser(){
userMapper.deleteUser(7);
return "deleteUser OK";
}
}
以上仅为测试mybatis,没有编写业务层。
SpringSecurity
SpringSecurity是针对spring项目的安全框架,也是 SpringBoot 底层安全模块默认的技术选型,它可以实现强大的web安全控制,对于安全控制,我们只需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理。
记住以下几个类:
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
SpringSecurity的两个主要目标是“认证” 和 “授权”(访问控制)
“认证” Authentication
“授权” Authorization
这两个概念是统用的,而不只是SpringSecurity中存在。
项目结构
application.properties
spring.thymeleaf.cache=false
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>登录</title>
<!--semantic-ui-->
<link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
</head>
<body>
<!--主容器-->
<div class="ui container">
<div class="ui segment">
<div style="text-align: center">
<h1 class="header">登录</h1>
</div>
<div class="ui placeholder segment">
<div class="ui column very relaxed stackable grid">
<div class="column">
<div class="ui form">
<form th:action="@{/login}" method="post">
<div class="field">
<label>Username</label>
<div class="ui left icon input">
<input type="text" placeholder="Username" name="username">
<i class="user icon"></i>
</div>
</div>
<div class="field">
<label>Password</label>
<div class="ui left icon input">
<input type="password" name="password">
<i class="lock icon"></i>
</div>
</div>
<div class="field">
<input type="checkbox" name="remember"> 记住我
</div>
<input type="submit" class="ui blue submit button"/>
</form>
</div>
</div>
</div>
</div>
<div style="text-align: center">
<div class="ui label">
</i>注册
</div>
<br><br>
<small>blog.kuangstudy.com</small>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study by 秦疆</h3>
</div>
</div>
</div>
<script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/qinjiang/js/semantic.min.js}"></script>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>首页</title>
<!--semantic-ui-->
<link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
<link th:href="@{/qinjiang/css/qinstyle.css}" rel="stylesheet">
</head>
<body>
<!--主容器-->
<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">
用户名:<span sec:authentication="name"></span>
<!--角色:<span sec:authentication="principal.getAuthorities()"></span>-->
<!--角色:<span sec:authentication="authorities"></span>-->
</a>
</div>
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
<!--已登录
<a th:href="@{/usr/toUserCenter}">
<i class="address card icon"></i> admin
</a>
-->
</div>
</div>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study by 秦疆</h3>
</div>
<div>
<br>
<div class="ui three column stackable grid">
<!--菜单根据用户的角色动态显示sec:authorize="hasRole('vip1')-->
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/qinjiang/js/semantic.min.js}"></script>
</body>
</html>
RouterController
package com.zr.controller;
@Controller
public class RouterController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "views/login";
}
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id") int id){
return "views/level1/"+id;
}
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id")int id){
return "views/level2/"+id;
}
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id")int id){
return "views/level3/"+id;
}
}
SecurityConfig
package com.zr.config;
@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");
//没有权限默认会到登录页 开启登录页面
//usernameParameter("username").passwordParameter("password")
// 括号内为前端传入的用户名为username,底层源码默认为username,当前端为name,pwd时就需要更改括号内问name,pwd
//loginProcessingUrl("/login");//点登录跳转的路径
http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login");
http.csrf().disable();//防止跨站脚本请求攻击关闭 登出失败可能的原因
//开启了注销功能
http.logout().logoutSuccessUrl("/index");
//开启记住我功能,cookie 默认保存两周,自定义接收前端的参数
//开启记住我功能 默认保存两周
http.rememberMe().rememberMeParameter("remember");
}
//认证
//密码编码:PasswordEncoder
//在Spring Security 5.0+中,新增了很多的加密方式
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//正常应该从数据库中读 这里是从内存中读取
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("zr").password(new BCryptPasswordEncoder().encode("12345")).roles("vip2","vip3")
.and()
.withUser("zz").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1","vip2")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1","vip2","vip3");
}
}
Shiro
Apache Shiro 是一个Java的安全(权限)框架。
Shiro 可以非常容易地开发出足够好的应用,其不仅可以用在JavaSE环境,还可以用在JavaEE环境。
Shiro可以完成认证,授权,加密,会话管理,Web集成,缓存等。
重要对象:
Subject:应用代码直接交互的对象是subject,也就是说shiro的对外核心API就是subject,subject代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是subject,如网络爬虫,机器人等,与subject的所有交互都会委托给SecurityManager,subject是一个门面,SubjectManager才是具体的执行者。
SecurityManager:安全管理器,即所有与安全有关的操作都会与SecurityManager交互,并且它管理着所有的subject,可以看出它是shiro的核心,它负责与shiro的其它组件交互,相当于SprinfMvc的DispatcherServlet的角色。
Realm:shiro从realm获得安全数据(如用户,角色,权限),就是说SecurityManager要验证用户身份,那么它需要从realm获得相应的用户进行比较,来确定用户的身份是否合法,也需要从realm得到用户相应的角色,权限来验证用户的操作是否能够进行,可以把realm当成DataSource。
整合shiro练习
目录结构
数据库为整合JDBC中的数据库增加一个perms字段
依赖pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<!-- shrio,thymeleaf整合-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
application.yml
spring:
datasource:
username: root
password: 123456
#时区配置报错 增加时区配置serverTimezone=UTC
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&chacterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#SpringBoot默认不注入这些属性的,需要自己绑定
#durid配置
initialSize: 5
minIdel: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控拦截,stat 监控统计, wall 防御sql注入,log4j 日志
#如果允许时报错,导入log4j依赖
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
application.properties
mybatis.type-aliases-package=com.zr.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
login.html
<body>
<h1>登录</h1>
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
<p>用户名:<input name="username" type="text"></p>
<p> 密码:<input name="password" type="password"></p>
<p><input type="submit"></p>
</form>
</body>
add.html
<body>
<h1>add</h1>
</body>
update.html
<body>
<h1>update</h1>
</body>
User实体类
package com.zr.pojo;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
private String perms;
}
UserMapper
package com.zr.mapper;
@Repository
@Mapper
public interface UserMapper {
public User queryUserByName(String name);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zr.mapper.UserMapper">
<select id="queryUserByName" parameterType="String" resultType="User">
select * from user where name=#{name};
</select>
</mapper>
UserService
package com.zr.service;
import com.zr.pojo.User;
public interface UserService {
public User queryUserByName(String name);
}
UserServiceImpl
package com.zr.service;
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
MyController
package com.zr.controller;
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","hello shiro");
return "index";
}
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/update")
public String update(){
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@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); //执行登录的方法,如果没有一次就ok
return "index";
}catch (UnknownAccountException a){ //用户名不存在
model.addAttribute("msg","用户名错误!");
return "login";
}catch (IncorrectCredentialsException a){ //密码不存在
model.addAttribute("msg","密码错误!");
return "login";
}
}
@RequestMapping("/noauth")
@ResponseBody
public String unAuthorized(){
return "未授权不能访问此页面!";
}
}
UserRealm
package com.zr.config;
//自定义的UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了==>>授权:AuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//info.addStringPermission("user:add");
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User)subject.getPrincipal(); //拿到user对象
//设置当前用户的权限
info.addStringPermission(currentUser.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了==>>认证:AuthenticationInfo");
//用户名密码 数据库中取
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){
return null;
}
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);
//密码认证 shiro默认做
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
}
ShiroConfig
package com.zr.config;
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/*
anon:无需认证就可以访问
authc:必须认证
user:必须拥有记住我功能才能用
perms:拥有某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
//授权 没有授权会跳转到未授权界面(401)
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//返回登录页
bean.setLoginUrl("/toLogin");
//未授权页面
bean.setUnauthorizedUrl("/noauth");
return bean;
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建realm对象,自定义
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//整合shiroDialect 用来整合shiro和thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
Swagger
学习目标:
- 了解Swagger的概念和作用
- 巩固前后端分离
- 在SpringBoot中巩固Swagger
Swagger简介
- 号称最流行的Api框架
- RestFul Api 文档在线自动生成工具=>Api文档与Api定义同步更新
- 直接运行,可以在线测试Api接口
- 支持多种语言
在项目中使用swagger需要导入jar包
- swagger2
- ui
SpringBoot集成Swagger
-
新建一个SpringBoot-web项目
-
导入相关依赖
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
-
编写helloword工程
-
配置swagger===>config
package com.zr.config; @Configuration @EnableSwagger2 //开启swagger2 public class SwaggerConfig { }
-
访问http://localhost:8080/swagger-ui.html
配置Swagger
Swagger的Bean实例 Docket
package com.zr.config;
import java.util.ArrayList;
@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
//配置swagger的Docket的 Bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
//配置swagger文档的信息
private ApiInfo apiInfo(){
//作者信息
Contact contact = new Contact("周周", "http://www.cnblogs.com/zhou-zr", "813794474@qq.com");
return new ApiInfo("周周的SwaggerApi文档",
"coding",
"1.0",
"http://www.cnblogs.com/zhou-zr",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList()
);
}
}
http://localhost:8080/swagger-ui.html
Swagger配置扫描接口
Docket select()
在配置Swagger中修改
//配置swagger的Docket的 Bean实例
//enable 是否启动swagger 如果为false 不能再浏览器中访问swagger
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(false)
.select()
//配置要扫描接口的方式
//basePackage 指定扫描的包
//any() 扫描全部
//none() 都不扫描
//withClassAnnotation() 扫描类上的注解 参数是一个注解的反射对象
//withMethodAnnotation() 扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.zr.controller"))
//过滤什么路径
.paths(PathSelectors.ant("/zr/**"))
.build();
}
根据是否是生产环境来开启swagger
//配置swagger的Docket的 Bean实例
//enable 是否启动swagger 如果为false 不能再浏览器中访问swagger
@Bean
public Docket docket(Environment environment){
//设置要显示的swagger环境
Profiles profiles = Profiles.of("dev");
//通过environment.acceptsProfiles判断是否处在自己设定的环境当中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag)
.select()
.apis(RequestHandlerSelectors.basePackage("com.zr.controller"))
//过滤什么路径
//.paths(PathSelectors.ant("/zr/**"))
.build();
}
application.properties
spring.profiles.active=dev
application-dev.properties
server.port=8081
application-pro.properties
server.port=8082
配置Api文档分组
.groupName("zzr")
如何配置多个分组:配置多个Docket实例即可。
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}
@Bean
public Docket docket4(){
return new Docket(DocumentationType.SWAGGER_2).groupName("C");
}
接口测试
实体类配置
package com.zr.pojo;
// @Api(注释)
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户名")
public String username;
@ApiModelProperty("密码")
public String password;
}
HelloController
package com.zr.controller;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
//只有我们的接口中,返回值中存在实体类,就会被扫描到swagger中
@PostMapping("/user")
public User user(){
return new User();
}
@ApiOperation("Hello控制类")
@GetMapping("/hello2")
public String hello(@ApiParam("用户名") String username){
return "hello"+username;
}
@ApiOperation("post测试")
@PostMapping("/postt")
public User postt(@ApiParam("用户名")User user){
return user;
}
}
总结:
- 我们以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
- 接口文档实时更新
- 可以在线测试
注意点:在正式上线的时候,关闭Swagger。
任务
异步任务
AsyncService
package com.zr.service;
@Service
public class AsyncService {
//告诉spring这是一个异步的方法
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在处理");
}
}
AsyncController
package com.zr.controller;
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@RequestMapping("/hello")
public String hello(){
asyncService.hello(); //停止3秒
return "ok";
}
}
主程序开启异步注解的功能
package com.zr;
//开启异步注解的功能
@EnableAsync
@SpringBootApplication
public class Springboot09TestApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot09TestApplication.class, args);
}
}
定时任务
TaskScheduler 任务调度者
TaskExecutor 任务执行者
@EnableScheduling //开启定时功能的注解
@Scheduled //什么时候执行
Cron 表达式
主程序开启定时任务的支持
package com.zr;
@EnableScheduling //开启定时功能的注解
@SpringBootApplication
public class Springboot09TestApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot09TestApplication.class, args);
}
}
定时任务设置
package com.zr.service;
@Service
public class ScheduledService {
//在一个特定的时间执行
/*
0 30 10 * * ? 每天的10.30 执行一次
0 0/5 10,16 * * ? 每天的10点到16点 每隔五分钟执行一次
0 20 10 ? * 1-6 每个月的周一到周六 10.20执行一次
*/
//cron表达式 秒 分 时 日 月 星期几(0-7 ?每一天)
@Scheduled(cron = "0 * * * * ?")
public void hello(){
System.out.println("hello,你被执行了");
}
}
邮件任务
package com.zr;
@SpringBootTest
class Springboot09TestApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
//一个简单的邮件
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("小周,你好!");
mailMessage.setText("好好学习!");
mailMessage.setTo("813794474@qq.com");
mailMessage.setFrom("813794474@qq.com");
mailSender.send(mailMessage);
}
@Test
void contextLoads2() throws MessagingException {
//一个复杂的邮件
// MimeMessage 也可以直接new创建
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组装
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
helper.setSubject("周周,你好呀!");
helper.setText("<h1><p style='color:red'>好好学习!</p></h1>",true);
//附件
helper.addAttachment("winC.PNG",new File("C:\Users\zr\Pictures\截图\winC.PNG"));
helper.addAttachment("2.jpg",new File("C:\Users\zr\Pictures\截图\winC.PNG"));
helper.setTo("813794474@qq.com");
helper.setFrom("813794474@qq.com");
mailSender.send(mimeMessage);
}
}
分布式
分布式系统理论
什么是分布式系统:
在《分布式系统原理与范型》 一书中有如下定义:分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。
分布式系统是有一组通过网络进行通信,为了完成共同的任务而协调工作的计算机节点组成的系统,分布式系统的出现是为了用廉价的,普通的机器完成单个计算机无法完成的计算,存储任务。其目的是利用更多的机器,处理更多的数据。
只有当单个节点无法满足日益增长的计算,存储任务需求时,且硬件的提升高昂到得不偿失时,且程序也不能进一步优化的时候,我们才需要考虑分布式系统。
单一应用架构:当网站流量很小时,只需一个应用,将所有的功能部署在一起,以减少部署节点和成本,此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
适用于小型网站,小型管理系统,将所有的应用部署到一个应用中,简单易用。
缺点:性能扩展比较难,协同开发问题,不利于升级维护。
垂直应用架构:当数据量逐渐增大,单一应用增加机器带来的加速度越来越小,将一个应用拆分成互不相干的几个应用,以提升效率,此时,用于加速前端页面开发的Web框架(MVC)是关键。
通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职,性能更易扩展。
缺点:公用模块无法重复利用,开发性的浪费。
分布式服务架构:当垂直应用越来越多,应用之间交互不可避免。将核心应用抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构:当服务越来越多,容量的评估,小服务资源的浪费问题等问题逐渐显现,此时需要增加一个调度中心基于访问的压力实时管理集群的容量,提高集群利用率。此时。用于提高资源利用率的资源调度和治理中心(SOA)是关键。
RPC
什么是RPC:
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间的通信方式,它是一种技术的思想,而不是规范,它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不是程序员显示编码这个远程调用的细节,即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
RPC两个核心:通信和序列化。
序列化:数据传输需要转换。
分布式 Dubbo + Zookeeper + SpringBoot
Apache Dubbo是一款高性能,轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
服务提供者(Provider):暴漏服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的 。
服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
容器(Container):服务运行容器。
调用关系说明
-
服务容器负责启动,加载,运行服务提供者。
-
服务提供者在启动时,向注册中心注册自己提供的服务。
-
服务消费者在启动时,向注册中心订阅自己所需的服务。
-
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
-
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
-
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
window下安装zookeeper
官网下载zookeeper,解压至指定目录,管理员模式下进入到zookeeper的bin目录下,启动zkServer.cmd。
【注意报错】在zkServer.cmd源文件中加pause,可查看报错信息。
解决:进入conf目录,将zoo_sample.cfg复制一份将名字改为zoo.cfg。
启动zkServer.cmd , 启动zkCli.cmd
window下安装Dubbo
dubbo本身并不是一个服务软件,它其实就是一个jar包,把Java程序连接到zookeeper,并利用zookeeper消费,提供服务。
为了让用户更好的监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不安装也不影响使用。
下载dubbo-admin
cmd进入下载的目录下,执行mvn clean package -Dmaven.test.skip=true
打包成功
执行dubbo-admin arget下的dubbo-admin-0.0.1-SNAPSHOT.jar
命令:java -jar dubbo-admin-0.0.1-SNAPSHOT.jar 【注意 zookeeper的服务一定要打开】
执行完毕后访问 http://localhost:7001 默认账号密码是root,root。
登录成功界面
zookeeper:注册中心
dubbo-admin是一个监控管理后台,可以查看我们注册了哪些服务,哪些服务被消费了。
Dubbo:jar包
步骤:前提开启zookeeper
- 提供者提供服务
- 导入依赖
- 配置注册中心的地址,以及服务发现名,和要扫描的包
- 在想要被注册的服务上增加一个注解@Service (Dubbo的)
- 消费者如何消费
- 导入依赖
- 配置注册中心的地址,配置自己的服务名
- 从远程注入服务@Reference
项目结构:
provider-server
TickerService
package com.zr.service;
public interface TickerService {
public String getTicker();
}
TickerServiceImpl
package com.zr.service;
//服务注册与发现
@Service //可以被扫描到 项目一启动就自动注册到注册中心
@Component //使用dubbo后尽量不要使用service
public class TickerServiceImpl implements TickerService{
@Override
public String getTicker() {
return "zzr";
}
}
application.properties
server.port=8001
#服务应用的名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册
dubbo.scan.base-packages=com.zr.service
pom.xml(consumer-server的pom相同)
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<!-- zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!--日志冲突-->
<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>
<!-- 排除slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
consumer-server
TickerService
package com.zr.service;
public interface TickerService {
public String getTicker();
}
UserService
package com.zr.service;
@Service //放到容器中
public class UserService {
//想拿到票provider-server提供的票 去注册中心拿
@Reference //引用 pom坐标 定义路径相同的接口名
TickerService tickerService;
public void byTicket(){
String ticker = tickerService.getTicker();
System.out.println("在注册中心拿到了一张票"+ticker);
}
}
application.properties
server.port=8002
#消费者从那里去拿需要暴漏自己的名字
dubbo.application.name=consumer-server
#注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
测试
package com.zr;
@SpringBootTest
class ConsumerServerApplicationTests {
@Autowired
UserService userService;
@Test
void contextLoads() {
userService.byTicket();
}
}
结果
dubbo-admin中查看服务提供者