SpringBoot
微服务阶段
- javase: OOP
- mysql:持久化
- html+css+js+jquery+框架
- javaweb:MVC 三层架构 的网站
- ssm:简化了开发流程 配置也相对复杂
- Spring:
- SpringBoot:内嵌Tomact 微服务架构
- springCloud
SpringBoot
SpringBoot 是基于 Spring 的开发 SpringBoot 本身不提供Spring 框架的核心特性以及扩展性功能 只是用于快速开发一代基于Spring 框架的应用程序 他不是用来替代Spring的解决方法, 而是和Spring紧密结合用于提升Spring开发体验的
Spring Boot 约定大于配置 默认帮我们进行了很多设置 多数SpringBoot 应用只需要很少的Spring配置
同时集成了大量的常用第三方库配置(Redis,MongoDB,Jpa,RabbitMQ,Quartz …) Spring Boot 中这些第三方库几乎可以零配置
SpringBoot 其实不是什么新的框架 它默认配置了很多框架的使用方法 就像maven整合了所有的jar包 SpringBoot 整合了所有的框架
优点:
- 为所有Spring开发者更快的入门
- 开箱即可 提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和XML配置的要求
微服务
微服务是一种架构风格 它要求我们在开发一个应用的时候 这个应用必须构建成一系列小服务的组合 可以通过http 的方式进行互通
单体应用架构(all in one)
将一个应用中的所有应用服务都封装在一个应用中
- 易于开发和测试 也十分方便部署 当需要扩展时 只需要将 war 复制多份 然后放在多个服务器上 再做个负载均衡就可以了
- 缺点 : 哪怕要修改一个非常小的地方 都需要停掉整个服务 重新打包 部署 war包
微服务架构
all in one 的架构方式 我们把所有的功能单元放在一个应用里面 然后我们把整个应用部署到服务器上 如果负 载能力不行 我们将整个应用进行水平复制 进行扩展 然后在负载均衡
微服务架构 就是打破之前 all in one 的架构方式 把每个功能元素独立出来 把独立出来的功能元素的动态组合 需要的功能元素才拿来组合 需要多一些时可以整合多个功能元素 所以微服务架构是对功能元素进行复制 而没有对整个应用进行复制
好处:
- 节省了调用资源
- 每个功能元素的服务都是一个可替代的 可独立升级的软件代码
怎么样构建微服务:
- 构建一个个功能独立的微服务应用单元 可以使用 SpringBoot 可以帮我们快速构建一个应用
- 大型分布式网络服务的调用 这部分有SpringCloud 来完成 实现分布式
- 在分布式中间 进行流式数据计算 批处理 使用 spring cloud data flow
- spring 为我们提供了从开始构建应用到大型分布式应用全流程方案
Hello Word:
- jdk 1.8
- maven 3.6.1
- springboot
- IDEA
方式一:
在 官网 直接下载后 导入 idea 开发
Spring 官网:
项目结构:
HelloworldApplication :程序的主入口 不能修改 所有的包都要创建在 它的同级目录下 不然无法访问
helloController
@RestController
public class helloController {@RequestMapping("/hello")public String Hello(){//调用业务接收前端的参数return "hello world!!!";}
}
运行:
-
项目元数据:创建时输入的 Project Metadata 部分 也就是Maven 项目的基本元素 包括:groupid artifactld version name description 等等
-
parent:继承
spring-boot-starter-parent
的依赖管理 控制版本与打包等内容 -
dependencies:项目具体依赖 这里包括
spring-boot-strater-web
用于实现 HTTP 接口 (该接口包含 Spring MVC )- 使用Spring MVC 构建Web (包括Restful)应用程序的入门者 使用Tomact 作为默认嵌入式容器
spring-boot-starter-test
:用于编写单元测试的依赖包 -
build:构建配置部分 默认使用
spring-boot-maven-plugin
配合spring-boot-starter-parent
就可以把 Spring Boot 应用打包成 JAR 运行。
方式二:
使用idea 创建一个springboot 项目
helloController
//SpringBoot 完成一件事: 自动装配@RestController
@RequestMapping("/hello")
public class helloController {@ResponseBody@RequestMapping("/hello")public String Hello(){//调用业务接收前端的参数return "hello world!!!";}
}
application.properties
# 更改项目访问的端口号
server.port=8081
banner.txt
[{+?]~++++{.!{{{{[}}{{{{{_. .{++++{ {{}{{++++++{++++++?}]++?{{_++{ -{++++}{+rr+r\+{+++++{$$ +++++++[{<<+++++{}++++++{+++++++{<{}+{$k {+++++}}{^}{++[{++++++++++++++{ _++++++++{,{++++++++++++++++++]}?+++++++++++{{{++++++++++++++++++++++++++++++}{{+++++++++++++++++++++++_++++{{+++++++++++++++++++}}}}}}}++I{+++++++++++++++++++[}}}}}}}_+{[]++vY+++++++++++++++}}}}}}}}++{{++++X$X|+++++++++++++}}}}}[+++{{_++++X%$$qXXv\|XXX)++++++++++-^. :{++++++XX$$$$$qXv++++++++++++{l-++++++++++++++++++++++++++{I{++++++++++++++++++++++++{C0{}++++++++++++++++++-{0CCOOOOO0{{[+++++++++_{{0OOO0CCOOOOOOOOOOOOOOUCOOOOOOOOOOOOn<<<""<<CLOOOOOOOOOOOOOOOOOOOOOOOOOOOOC ;<<<<<<<<<. COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOC <<<<!<<. COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOCz<;!> COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOC{QOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOL 'COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOY"< <COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOC<<<<<<`'COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOC[COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOC]CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<< ^<^<< ^<^<< ^<^$$$$$$$$> O$$$$$$$z
pom.xml
<?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.5.5</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.zhang</groupId><artifactId>SpringBoot-study-01</artifactId><version>0.0.1-SNAPSHOT</version><name>SpringBoot-study-01</name><description>SpringBoot-study-01</description><properties><java.version>1.8</java.version></properties><!--版本号不用写 它会继承父依赖--><dependencies><!--启动器--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><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></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build><repositories><repository><id>aliyunmaven</id><url>https://maven.aliyun.com/repository/public</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></repository></repositories><pluginRepositories><pluginRepository><id>nexus-aliyun</id><name>Nexus-aliyun</name><url>https://maven.aliyun.com/repository/public</url><snapshots><enabled>false</enabled></snapshots><releases><enabled>true</enabled></releases></pluginRepository></pluginRepositories>
</project>
运行:
原理
自动配置
pom.xml
- spring-boot-dependencies: 核心依赖在父工程中
- 我们在或 引入一些SpringBoot 依赖的时候不需要指定版本 因为有版本仓库管理
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.5</version><relativePath/> <!-- lookup parent from repository -->
</parent>
启动器
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId>
</dependency>
- 就是springboot的启动场景
- 比如
spring-boot-starter-web
就会帮我们导入 web 所需要的所有环境 - spring boot 会将所有的功能场景变成一个个 启动器
starter
- 我们要什么功能 只需要找到对应的启动器即可
主程序
// @SpringBootApplication :标注这个类是一个 springboot 的应用 程序
@SpringBootApplication
public class SpringBootStudy01Application {// 将spring boot 应用启动public static void main(String[] args) {SpringApplication.run(SpringBootStudy01Application.class, args);}
}
注解:
@SpringBootConfiguration : springboot 的配置@Configuration :spring 配置类@Component: 说明也是一个spring的组件@EnableAutoConfiguration : 自动配置@AutoConfigurationPackage : 自动配置包@Import(AutoConfigurationPackages.Registrar.class) :自动配置 包注册@Import(AutoConfigurationImportSelector.class) : 自动配置导入选择
SpringApplication.run:
该方法·主要分为两部分 一部分是 springApplication 的实例 二是run方法 的执行
SpringApplication:
- 推断应用的类型是普通的项目 还是web 项目
- 查找并加载所有的可用初始化器 设置到initializers 属性中
- 找出所有的应用程序监听器 设置到 listeners 属性中
- 推断并设置 main 方法的定义类 找到运行的主类
SpringBoot 配置文件
配置文件:使用一个 全局的配置文件
- application.properties
- 语法: key = value
- application.yml
- 语法: key: 空格 value
配置文件的作用:
修改springboot 的默认配置
yaml
标记语言:以前的配置文件 大多数是用xml 来配置
xml 配置
<server><port>8081</port>
</server>
yaml 配置:
server:prot: 8081
application.yaml
替代 application.properties
# 对空格要求十分严格
# 可以注入到配置类中
# 普通的 k-v
name: xiaotao# 对象
student:name: xiaotaoage: 18# 行内写法
student1: {name: xiaotao,age: 18}# 数组
pets:- cat- dog- pigpers: [cat,dog,pig]
实体类:
Dog
@Component
@Data
public class Dog {@Value("旺财")private String name;@Value("3")private Integer age;
}
Person
@Component
@Data
@ConfigurationProperties(prefix = "person")
public class Person {private String name;private Integer age;private Boolean happy;private Date birth;private Map<String,Object> map;private List<Object> list;private Dog dog;
}
application.yaml
# 通过 yaml 给实体类赋值
person:name: xiaotaoage: 18happy: truebirth: 2021/10/31map: {k1: v1,k2: v2}list:- code- music- girldog:name: 旺财age: 2
测试
@SpringBootTest
class SpringBootStudy01ApplicationTests {@Autowiredprivate Person person;@Testvoid contextLoads() {System.out.println(person);}}
pom.xml
添加依赖即可解决
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional>
</dependency>
运行:
# 通过 yaml 给实体类赋值
person:name: xiaotao${random.uuid} # 产生一个随机的 uuidage: ${random.int}happy: truebirth: 2021/10/31map: {k1: v1,k2: v2}list:- code- music- girldog:name: ${person.hello:hello}_旺财 # 如果 person.hello 存在则取 对应值 否则 取 helloage: 2
yaml 及 properties 对比
松散绑定
JSR303 数据校验
在字段增加一层过滤器验证 可以保证数据的合法性
Bean Validation 中内置的 constraint
Hibernate Validator 附加的 constraint
多环境配置
application.properties
application.yaml
使用yaml 一个配置文件即可以
通过 ---
即可区分不同配置环境
server:port: 8081
spring:profiles:active: dev---
server:port: 8082
spring:profiles: dev---
server:port: 8083
spring:profiles: test
自动装配原理
-
SpringBoot 启动会加载大量的自动配置类
-
我们看我们需要的功能有没有在SpringBoot 默认写好的自动配置类中
-
看这个自动配置类中到底配置了哪些组件· 只要我们要用到的组件存在其中 我们就不需要手动配置了
-
给容器中自动配置类添加组件的时候 会从·properties类中获取属性 我们只需要在配置文件中指定这些属性的值即可
xxxAutoConfigurartion: 自动配置类 给容器添加组件
xxxProperties : 封装配置文件中相关属性
debug = true # 开启日志 查看哪些自动配置类生效 哪些没有生效
Springboot Web 开发
自动配置
SpringBoot 到底帮我们配置了什么? 我们能不能修改? 能修改什么? 能不能扩展?
- xxxAutoConfiguraion 向容器中自动配置组件
- xxxProperties : 自动配置类 装配配置文件中自定义的一些内容
要解决的问题:
- 导入静态资源
- 首页
- jsp 模板引擎 Thymeleaf
- 装配扩展SpringMVC
- 增删改查
- 拦截器
- 国际化
静态资源
https://www.webjars.org/
方式一: 以 maven 的方式引入静态资源 (不推荐)
<dependency><groupId>org.webjars.npm</groupId><artifactId>jquery</artifactId><version>3.6.0</version>
</dependency>
方式二: 放在 classpath:static classpath:public classpath:resources 文件夹下面
Thymeleaf
在 templates 目录下的所有页面 只能通过controller 来跳转
需要模板引擎的支持 thymeleaf
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
引入命名空间
<html lang="en" xmlns:th="www.thymeleaf.arg">
首页
所有的静态资源都需要使用 thylemeaf 接管
url : @{}
页面国际化
- 我们需要配置 I18n 文件
- 我们需要在项目中进行按钮自动切换 需要自定义一个组件 LocaleResolver
- 将组件 配置到 Spring 容器中托管
@Bean
- #{}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8vNNGdtH-1690941414317)(http://qiniu.xiaotao.cloud/QQ截图20211105001121.png)]
CRUD
数据库
无论是 sql(关系型数据库) 还是 nosql(非关系型数据库)springboot 都是采用 SpringData 的方式进行统一管理
JDBC
JDBCController
@RestController
public class JDBCController {@AutowiredJdbcTemplate jdbcTemplate;@GetMapping("/userList")public List<Map<String,Object>> userList(){String sql = "select * from stuinfo";List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);return maps;}@GetMapping("/addUser")public String addUser(){String sql = "insert into stuinfo values('T123100','小灰灰','女',19,'重庆')";int i = jdbcTemplate.update(sql);return "addUser";}@GetMapping("/update")public String update(){String sql = "update stuinfo set stu_name = '小涛',stu_sex = '女',stu_age = 18,stu_Address = '重庆' where stu_id = 'T123005';";int i = jdbcTemplate.update(sql);return "update";}@GetMapping("/del/{id}")public String del(@PathVariable("id") String id){String sql = "delete from stuinfo where stu_id = ?";int i = jdbcTemplate.update(sql,id);return "del";}
}
Druid
application.yaml
spring:datasource:username: rootpassword: 123456url: jdbc:mysql://localhost:3306/mybatis?useUniocde=true&characterEncoding=utf-8&serverTimezone=UTCdriver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSource # 指定使用 Druid 数据源#Spring Boot 默认是不注入这些属性值的,需要自己绑定#druid 数据源专有配置initialSize: 5minIdle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: true#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4jfilters: stat,wall,log4jmaxPoolPreparedStatementPerConnectionSize: 20useGlobalDataSourceStat: trueconnectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
DriverConfig
@Configuration
public class DriverConfig {/*** 使自定义配置 druid 生效* @return*/@ConfigurationProperties(prefix = "spring.datasource")@Beanpublic DataSource druidDataSource(){return new DruidDataSource();}// 后台监控 运行访问 http://localhost:8080/druid@Beanpublic ServletRegistrationBean statViewServlet(){ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");// 后台需要有人登录 账号 密码 配置HashMap<String,String> initParameters = new HashMap<>();// 增加配置// 登录的 key 是固定的 loginUsername loginPasswordinitParameters.put("loginUsername","admin");initParameters.put("loginPassword","123456");// 允许谁能访问 值为空 代表 任何人都可以initParameters.put("allow","");// 禁止谁访问//initParameters.put("xiaotao","192.168.42.233");bean.setInitParameters(initParameters); // 设置初始化参数return bean;}
}
后台监控
访问: http://localhost:8080/druid
执行一次查询
监控到
过滤
//filter
public FilterRegistrationBean webStatFilter(){FilterRegistrationBean bean = new FilterRegistrationBean();HashMap<String,String> initParameters = new HashMap<>();initParameters.put("exclusions","*.js,*.css,/druid/*"); // 这些不统计bean.setInitParameters(initParameters);bean.setFilter(new WebStatFilter());return bean;
}
Mybatis
- 导入包
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version>
</dependency>
- 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhang.mapper.StuInfoMapper"><select id="queryStuList" resultType="StuInfo">select * from stuinfo</select>
</mapper>
- mybatis配置
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver# 整合mybatis
mybatis.type-aliases-package=com.zhang.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
- mapper service controller
SpringSecurity (安全) 权限
- 在 web 开发中 安全第一位
- 安全应该在设计之初考虑
- 漏洞 隐私泄露
shiro SpringSecurity
两个都是用于安全的框架 很像 除了类不一样
认证 授权
- 功能权限
- 访问权限
- 菜单权限
- 原来 拦截器 过滤器 …
- 现在框架
springSecurity 是针对 spring 项目的安全框架 样式 SpringBoot底层安全模块默认的选形 他可以实现强大的web 安全控制 外面只需要 引入
进行少量的配置 即可实现强大的web安全管理
- WebSecurityConfigurerAdapter:自定义 Security 模式
- :自定义认证策略
- @EnableWebSecurity:开启WebSecurity 模式 @Enablexxx : 开启莫个功能
SpringSecurity 的两个主要目标是“认证” 和 “授权”(访问控制)
认证(Authentication)
授权(Authorization)
SpringSecurity
导入包:
<!--security-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
SecurityConfig
/*** 权限 管理 通过 AOP 横切*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 授权* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {// 首页所有人可以访问 功能页只有对应有权限的人才能访问// 链式编程// antMatchers("/"):代表哪些页面// permitAll():所有人都可以访问// hasRole("vip1") : 指定人可以访问http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/level1/**").hasRole("vip1").antMatchers("/level2/**").hasRole("vip2").antMatchers("/level3/**").hasRole("vip3");// 没有权限默认会到登录页面 需要开启登录的页面 会走默认的页面 http://localhost:8080/login// loginPage("/toLogin") : 定制自己的登录页面// 框架默认使用的是 username password 要使用 不同的可以加上下面的属性http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");// 开启注销功能// 他会有防止网站攻击: 只能使用 post 请求// 不加需要 post 请求 才能注销http.csrf().disable(); // 关闭 csrf 功能 可以使用 get 请求 (解决不能注销问题)http.logout().logoutSuccessUrl("/");//开启记住我 cookie 默认保存两周 自定义接收前端参数http.rememberMe().rememberMeParameter("remember");}/*** 认证* @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 从内存中获取// 需要进行密码加密auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("xiaotao").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2").and().withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3").and().withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");// 也可以从数据库中获取// auth.jdbcAuthentication().withUser("").password("").roles("");}
}
RouterController
@Controller
public class RouterController {@RequestMapping({"/","/index"})public String index(){return "index";}@RequestMapping("/toLogin")public String login(){return "views/login";}@RequestMapping("/level1/{id}")public String level1(@PathVariable("id") Integer id){return "views/level1/"+id;}@RequestMapping("/level2/{id}")public String level2(@PathVariable("id") Integer id){return "views/level2/"+id;}@RequestMapping("/level3/{id}")public String level3(@PathVariable("id") Integer id){return "views/level3/"+id;}
}
实现有什么权限看到那个页面:
<!--security thymeleaf 整合-->
<dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity4</artifactId><version>3.0.4.RELEASE</version>
</dependency>
<!--登录注销-->
<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>--></a><!--注销--><a class="item" th:href="@{/logout}"><i class="sign-out icon"></i> 注销</a></div>
</div>
<!--sec:authorize="hasRole('vip1')" : 假如有 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>
shiro
- Apache shiro 是一个java 的安全权限框架
- shiro 可以非常容易的开发出足够好的应用 不仅可以在 javaSE 环境 也可以在 javaEE 环境
- shiro 可以完成 认证 授权 加密 会话管理 web集成 缓存
subject: 用户
SecurityManager:管理所有用户
Realm:连接数据
快速开始
- 导入依赖
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.8.0</version>
</dependency><!-- configure logging -->
<dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId><version>1.7.21</version>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.21</version>
</dependency>
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>
- log4j.properties
log4j.rootLogger=INFO, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n# General Apache libraries
log4j.logger.org.apache=WARN# Spring
log4j.logger.org.springframework=WARN# Default Shiro logging
log4j.logger.org.apache.shiro=INFO# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
- shiro.ini
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
- Quickstart.java
public class Quickstart {private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);public static void main(String[] args) {Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);// 获取当前的用户对象 subjectSubject currentUser = SecurityUtils.getSubject();// 通过当前用户得到 sessionSession session = currentUser.getSession();session.setAttribute("someKey", "aValue");String value = (String) session.getAttribute("someKey");if (value.equals("aValue")) {log.info("subject=>session====> [" + value + "]");}// 测试当前用户是否被认证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 more exceptions here (maybe custom ones specific to your application?catch (AuthenticationException ae) { // 认证失败//unexpected condition? error?}}//获取当前用户的信息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.");}//test a typed permission (not instance-level)if (currentUser.isPermitted("lightsaber:wield")) {log.info("You may use a lightsaber ring. Use it wisely.");} else {log.info("Sorry, lightsaber rings are for schwartz masters only.");}//a (very powerful) Instance Level permission:if (currentUser.isPermitted("winnebago:drive:eagle5")) {log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +"Here are the keys - have fun!");} else {log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");}//注销currentUser.logout();// 结束系统System.exit(0);}
}
SpringBoot 集成 shiro
- 导入依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--shiro 整合 Spring 的包 --><!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.7.1</version></dependency><!--shiro 整合 thymeleaf 的包 --><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version></dependency><!--log4j--><dependency><groupId>com.att.inno</groupId><artifactId>log4j</artifactId><version>1.2.13</version></dependency><!-- mybatis-spring-boot-starter --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><!--thymeleaf--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><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></dependencies>
IndexController
@Controller
public class IndexController {@RequestMapping({"/","/index","index.html"})public String index(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); // 执行了登录方法return "index";} catch (UnknownAccountException uae) { // 用户名不存在model.addAttribute("msg","用户名错误");return "login";} catch (IncorrectCredentialsException ice) { // 密码错误model.addAttribute("msg","密码错误");return "login";} catch (LockedAccountException lae) { // 账户被锁定model.addAttribute("msg","账户被锁定");return "login";}}/*** 未授权页面* @return*/@RequestMapping("/unauth")@ResponseBodypublic String unauthorized(){return "没有授权不能访问此页面";}
}
ShiroConfig
@Configuration
public class ShiroConfig {// 第三步 ShiroFilterFactoryBean@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();// 设置安全管理器factoryBean.setSecurityManager(defaultWebSecurityManager);// 添加shiro 的内置过滤器/*** anon: 无需认证就可以访问* authc:必须认证了才能访问* user:必须拥有记住我 才能实现* perms:拥有对某个资源的权限才能访问* role:拥有某个角色权限才能访问*/Map<String, String> filterMap = new LinkedHashMap<>();// 授权filterMap.put("/user/add","perms[user:add]");filterMap.put("/user/update","perms[user:update]");// 拦截/*filterMap.put("/user/add","authc");filterMap.put("/user/update","authc");*/// 可以使用通配符filterMap.put("/user/*","authc");factoryBean.setFilterChainDefinitionMap(filterMap);// 设置登录页面factoryBean.setLoginUrl("/toLogin");// 设置未授权页面factoryBean.setUnauthorizedUrl("/unauth");return factoryBean;}// 第二步 DefaultWebSecurityManager@Bean(name = "securityManager") //@Qualifier("userRealm") userRealm: 为下面的方法名 或 name 都可以public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 关联 realmsecurityManager.setRealm(userRealm);return securityManager;}// 第一步 创建 realm 需要自定义类@Bean(name = "userRealm") // 或 @Bean(name = "userRealm")public UserRealm userRealm(){return new UserRealm();}// 整合shiro thymeleaf@Beanpublic ShiroDialect getShiroDialect(){return new ShiroDialect();}}
UserRealm
/*** 自定义 realm*/
public class UserRealm extends AuthorizingRealm {@AutowiredUserService userService;/*** 授权* @param principalCollection* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("执行了 ==》 授权 doGetAuthorizationInfo");SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//获取到当前登录的对象Subject subject = SecurityUtils.getSubject();User currentUSer = (User) subject.getPrincipal();//设置当前用户的权限info.addStringPermission(currentUSer.getPerms());return info;}/*** 认证* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("执行了 ==》 认证 doGetAuthenticationInfo");UsernamePasswordToken userToken = (UsernamePasswordToken) token;// 用户名 数据库中获取User user = userService.queryUserByName(userToken.getUsername());if(user==null){return null; //抛出异常}// 把当前登录用户存入sessionSubject currentSubject = SecurityUtils.getSubject();currentSubject.getSession().setAttribute("loginUser",user);// 密码认证 shiro 会自己实现return new SimpleAuthenticationInfo(user,user.getPwd(),""); //这里必须存入 user 上面才能获取}
}
UserMapper
@Mapper
@Repository
public interface UserMapper {User queryUserByName(String name);
}
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.arg"xmlns:shiro="http://www.thymeleaf.arg/thymeleaf-extras-shiro">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h1>首页</h1><p th:if="${session.loginUser==null}"><a th:href="@{/toLogin}">登录</a></p><h3 th:text="${msg}"></h3>
<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>
application.yaml
spring:datasource:username: rootpassword: 123456url: jdbc:mysql://localhost:3306/mybatis?useUniocde=true&characterEncoding=utf-8&serverTimezone=UTCdriver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSource # 指定使用 Druid 数据源#Spring Boot 默认是不注入这些属性值的,需要自己绑定#druid 数据源专有配置initialSize: 5minIdle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: true#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4jfilters: stat,wall,log4jmaxPoolPreparedStatementPerConnectionSize: 20useGlobalDataSourceStat: trueconnectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
Swagger
学习目标:
- 了解Swgger 的作用及概念
- 了解前后端分离
- 在SpringBoot中集成Swagger
简介
前后端分离:
Vue + SpringBoot
后端时代:前端只用管理静态页面 html ===》后端 模板引擎 jsp
前后端分离时代:
后端:控制层 服务层 数据访问层
前端:控制层 试图访问层
伪后端数据 json
前后端如何交互 ====》 API
前后端相对独立 松耦合
前后端甚至可以部署在不同的服务器上
问题:
前后端继承联调 前后端人员无法做到“及时协商 今早解决” 最后导致问题爆发
解决方案:
首先制定 schema 【计划提纲】 实时更新最新API 降低集成风险
- 原来:指定 word 计划文档
- 前后端分离:
- 前端测试后端接口: postman
- 后端提供接口 需要实时更新最新的消息及改动
Swagger
- 世界上 最流行的API 框架
- RestFul Api 文档在线自动生成工具 =》 Api 文档与 api 定义同时更新
- 直接运行 可以在线测试 Api 接口
- 支持多种语言 :java php …
SpringBoot 集成 Swagger
在项目中使用Swagger需要springfox
导入依赖
- swagger
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version>
</dependency>
- ui
<!-- 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>
Config 配置:
@Configuration
@EnableSwagger2 // 开启 swagger2
public class SwaggerConfig {}
测试运行: swagger-ui.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xNLsWfZ2-1690941414320)(http://qiniu.xiaotao.cloud/QQ截图
.png)]
配置 swagger 基本信息
@Configuration
@EnableSwagger2 // 开启 swagger2
public class SwaggerConfig {// 配置了 Swagger的Docket 的bean 实例@Beanpublic Docket docket(){return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());}// 配置 swagger 信息 apiInfopublic ApiInfo apiInfo(){// 作者信息Contact contact = new Contact("小涛","https://www.xiaotao.cloud/","2177393158@qq.com");return new ApiInfo("xiaotao 的 SwaggerApi ","即使再小的帆也能远航!","1.0","https://www.xiaotao.cloud/",contact,"Apache 2.0","http://www.apache.org/licenses/LICENSE-2.0",new ArrayList());}
}
Swagger 配置扫描接口
// 配置了 Swagger的Docket 的bean 实例@Beanpublic Docket docket(){return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()// RequestHandlerSelectors: 配置要扫描接口的方式// .basePackage("com.zhang.controller"): 指定要扫描的包// .any(): 扫描全部// .none(): 不扫描// .withClassAnnotation(RestController.class): 扫描类上的注解 参数是一个注解的反射对象// .withMethodAnnotation(GetMapping.class): 扫描方法上的注解.apis(RequestHandlerSelectors.basePackage("com.zhang.controller"))// paths: 过滤什么路径.paths(PathSelectors.ant("/zhang/**")).build();}
配置是否启动 Swagger
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(true) // 默认为 true 改为false 则不能使用.select().apis(RequestHandlerSelectors.basePackage("com.zhang.controller")).build();
实现在开发的时候使用swagger 上线时不使用swagger
@Beanpublic Docket docket(Environment environment){// 实现在开发的时候使用swagger 扫描 上线时不使用swagger//设置要显示的环境Profiles profiles = Profiles.of("dev", "test");// 通过 environment.acceptsProfiles 判断是否处于自己设定的环境中boolean flag = environment.acceptsProfiles(profiles);return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(flag) // flag : true false.select().apis(RequestHandlerSelectors.basePackage("com.zhang.controller")).build();}
配置Api分组:
.groupName("xiaotao")
配置 多个分组:
@Bean
public Docket docket1(){return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}@Bean
public Docket docket2(){return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}@Bean
public Docket docket3(){return new Docket(DocumentationType.SWAGGER_2).groupName("C");
}
接口注释
实体类
@ApiModel("用户实体类")
public class User {@ApiModelProperty("用户名")public String username;@ApiModelProperty("密码")public String password;
}
controller
@RestController
public class HelloController {@GetMapping("/hello")public String hello(){return "hello word";}// 只要接口中 返回值存在 实体类 他就会被 扫描到 swagger 中@PostMapping("/user")public User user(){return new User();}//Operation 接口@ApiOperation("Hello 控制类")@GetMapping("/hello2")public String hello2(@ApiParam("用户名") String username){return "hello"+username;}@ApiOperation("post 测试控制类")@PostMapping("/postt")public User postt(@ApiParam("用户") User user){return user;}
}
总结:
- 我们可以使用 swagger 给一些比较难理解的属性或接口 增加注释信息
- 接口文档实时更新
- 可以在线测试
- 注: 在正式发布时 关闭swagger 这样更加安全 节省内存
任务
异步任务
@Service
public class AsyncService {// 告诉 spring 这是一个 异步请求@Asyncpublic void hello(){try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("数据正在处理.....");}
}
@RestController
public class AsyncController {@Autowiredprivate AsyncService asyncService;@RequestMapping("/hello")public String hello(){asyncService.hello(); // 不使用异步注解 停止 3秒return "ok";}
}
主启动类
@EnableAsync // 开启异步请求注解
@SpringBootApplication
public class SpringBootTask08Application {public static void main(String[] args) {SpringApplication.run(SpringBootTask08Application.class, args);}}
定时任务
TaskScheduler 任务调度者
TaskExecutor 任务执行者@EnableScheduling 开启定时功能的注解
@Scheduled 什么时候执行
主启动类
@EnableAsync // 开启异步请求的注解
@EnableScheduling // 开启定时功能的注解
@SpringBootApplication
public class SpringBootTask08Application {public static void main(String[] args) {SpringApplication.run(SpringBootTask08Application.class, args);}
}
@Service
public class ScheduledService {/*** 在特定的时间执行* cron : 表达式* 秒 分 时 日 月 周几*/@Scheduled(cron = "0 * * * * 0-7")public void hello(){System.out.println("hello 我出来啦!");}
}
常用表达式例子(1)0/2 * * * * ? 表示每2秒 执行任务(1)0 0/2 * * * ? 表示每2分钟 执行任务(1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点 (5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时 (6)0 0 12 ? * WED 表示每个星期三中午12点 (7)0 0 12 * * ? 每天中午12点触发 (8)0 15 10 ? * * 每天上午10:15触发 (9)0 15 10 * * ? 每天上午10:15触发 (10)0 15 10 * * ? 每天上午10:15触发 (11)0 15 10 * * ? 2005 2005年的每天上午10:15触发 (12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发 (13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发 (14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 (15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发 (16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发 (17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发 (18)0 15 10 15 * ? 每月15日上午10:15触发 (19)0 15 10 L * ? 每月最后一日的上午10:15触发 (20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发 (21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发 (22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
邮件任务
- 导入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId>
</dependency>
- 配置文件
spring.mail.username=2177393158@qq.com
spring.mail.password=ktdihwxprzqveaad
spring.mail.host=smtp.qq.com# 开启加密验证 (qq才需要)
spring.mail.properties.mail.smtp.ssl.enable=true
- 测试
@Autowired
JavaMailSenderImpl mailSender;/*** 简单的邮件*/
@Test
void contextLoads() {SimpleMailMessage mailMessage = new SimpleMailMessage();mailMessage.setSubject("hello xiaotao");mailMessage.setText("嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿");mailMessage.setTo("2177393158@qq.com");mailMessage.setFrom("2177393158@qq.com");mailSender.send(mailMessage);
}/*** 复杂的邮件*/
@Test
void contextLoads2() throws MessagingException {MimeMessage mimeMessage = mailSender.createMimeMessage();// 组装MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);//正文helper.setSubject("hello xiaotao");helper.setText("<p style='color:red'>嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿</P>",true);// 附件helper.addAttachment("1.jpg",new File("D:\\01.jpg"));helper.addAttachment("2.jpg",new File("D:\\01.jpg"));helper.setTo("2177393158@qq.com");helper.setFrom("2177393158@qq.com");mailSender.send(mimeMessage);
}
分布式 Dubbo + Zookeeper + SpringBoot
什么是分布式系统:
在 《分布式系统原理与泛型》一书中有如下定义:“分布式系统是若干独立计算机的集合 这些计算机对于用户来说就像单个相关系统”
分布式系统(distributed system)是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件
利用更多的机器 处理更多的数据
只有当单个机器无法处理的时候我们才会考虑使用分布式
详细概念:
https://mp.weixin.qq.com/s/sKu9-vH7NEpUd8tbxLRLVQ
Zookeeper 安装
Zookeeper : 注册中心
下载:http://archive.apache.org/dist/zookeeper/
管理员身份
Dubbo 安装
Dubbo : 一个jar包
下载dubbo-admin
地址 :https://github.com/apache/dubbo-admin/tree/master-0.2.0
先要启动 (管理员)
zkServer.cmd
:打开 zookeeper 服务
去cmd 运行(管理员身份)
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
访问:
dubbo-admin : 是一个监控管理后台 查看我们注册了那些服务· 那些服务被消费了
SpringBoot 整合 Dubbo + Zookeeper
新建项目
服务提供者
导入依赖:
<!-- Dubbo Spring Boot Starter -->
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>2.7.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency><groupId>com.github.sgroschupf</groupId><artifactId>zkclient</artifactId><version>0.1</version>
</dependency><!-- 引入zookeeper -->
<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>
application.properties
server.port=8081#当前应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#扫描指定包下服务
dubbo.scan.base-packages=com.zhang.service
TicketServiceImpl
package com.zhang.service;import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;@Service // dubbo :下的 在项目一启动就自动注到注册中心
@Component //使用了dubbo 后尽量不要用 service 注解 会和上一个冲突
public class TicketServiceImpl implements TicketService{@Overridepublic String getTicket() {return "小涛 你好呀!!";}
}
先要启动 (管理员)
zkServer.cmd
:打开 zookeeper 服务
去cmd 运行(管理员身份)
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
启动项目:
provider-server
服务消费者
导入依赖:(同上)
<!-- Dubbo Spring Boot Starter -->
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>2.7.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency><groupId>com.github.sgroschupf</groupId><artifactId>zkclient</artifactId><version>0.1</version>
</dependency><!-- 引入zookeeper -->
<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>
application.properties
server.port=8082#当前应用名字
dubbo.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
UserService
package com.zhang.service;import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;@Service // 放到spring 容器中
public class UserService {// 获取到 provider-server 提供的票 需要去注册中心那到服务/*** 两种方式:* 1. pom 坐标* 2. 定义路径相同的接口名*/// 这里使用的是 路径相同的接口名@Reference // 引用TicketService ticketService;public void buyTicket(){String ticket = ticketService.getTicket();System.out.println("在注册中心拿到票===> "+ticket);}
}
TicketService
public interface TicketService {public String getTicket();
}
测试:
@SpringBootTest
class ConsumerServerApplicationTests {@AutowiredUserService userService;@Testvoid contextLoads() {userService.buyTicket();}}
启动项目