约定大于配置
SpringBoot入门
1.SpringBoot是什么
SpringBoot并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,同时集成了大量的第三方库,就像maven整合了所有的jar包,SpringBoot整合了所有的框架
1.1.SpringBoot最核心的东西
- 自动装配
- 约定大于配置
2.SpringBoot的主要优点
- Spring开发者更快入门
- 开箱即用,提供默认配置 简化项目配置
- 内嵌式容器简化Web项目,不用配置Tomcat
- 没有冗余代码和XML的配置文件
- 测试变得简单,内置了JUnit和Test等多种测试框架
3.配置SpringBoot的pom.xml文件
<!-- 父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<!-- web场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springboot单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- 剔除依赖 -->
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
设置SpringBoot热部署(修改代码不需要重新部署项目)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
3.1.将项目打包成jar文件
使用maven的package生命周期
如遇到错误配置打包跳过测试用例,打包成功会在target生成一个jar文件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!--跳过项目运行测试用例-->
<skipTests>true</skipTests>
</configuration>
</plugin>
3.2.SpringBoot启动图标修改
新建banner.txt放入resources目录下
图案可在https://www.runoob.com生成,拷入banner.txt就行
3.3.SpringBoot启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
@SpringBootApplication
@SpringBootConfiguration
SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;(底层是@Component
)@EnableAutoConfiguration
自动装配
自动配置:
- springboot在启动之后从classpath中搜寻
META-INF/spring.factories
配置文件 ,所有的自动配置类都在这个文件里面,但是不一定生效,@ConditionalOnXXX
会判断条件是否成立,需要导入对应的start(也就是jar包),注解条件才会成立; - 一旦这个配置类生效,这个配置类就会给容器添加各种组件,这些组件是从对应的properties类中获取的,这个类的每个属性又是和
application.yml
配置文件绑定的 - 然后通过反射实例化被 @Configuration注释的配置类 , 并将其汇总并加载到IOC容器中,也就实现了自动装配。
@ComponentScan
扫描启动类路径和子路径的所有组件,并将其加载进IOC容器
SpringApplication
这个类主要做了以下四件事情:
- 推断应用的类型是普通的项目还是Web项目
- 查找并加载所有可用初始化器 , 设置到initializers属性中
- 找出所有的应用程序监听器,设置到listeners属性中
- 推断并设置main方法的定义类,找到运行的主类
4.yaml语法
SpringBoot优先级:yml>yaml>propertes
yml和yaml语法是一样的,高优先级先加载,低优先级的会覆盖高优先级的
- application.properties
- 语法结构:key=value
- application.yml
- 语法结构:key: 空格 value
配置文件的作业:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了
如:server.port=8081
yml中为:
server:
port:8081
yml要求
语法要求严格
1、空格不能省略
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、属性和值的大小写都是十分敏感的。
通过@ConfigurationProperties(prefix = "person")
去绑定.yml文件中的对象,
而.properties需先使用@PropertySource(value = "classpath:person.properties")
读取配置文件,再使用@Value("name")
绑定对于的字面量
5.JSR303数据校验和多环境切换
5.1.数据校验
依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
使用@Validated
使数据校验注解生效,再使用@Email(message="邮箱格式错误")
或者@Null
等注解进行数据校验,保证数据的正确性
5.2.多环境切换
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties
主配置文件
通过配置spring.profiles.active=dev
切换到开发环境
配置文件加载顺序
1.file:./config/
2.file:./
3.classpath:./config/
4.classpath:./
也就是
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
SpringBoot会加载所有的配置文件,高优先级的覆盖低优先级的,再互补其他配置
config下的优先级会高于根目录、高优先级会覆盖低优先级的配置文件、后期运维外部指定的配置文件优先级最高
运维小技巧
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
可以指定配置文件(优先级最高)
6.日志
注意:在生产上严禁使用System.out输出,性能太低,原因是System.out输出会导致线程等待(同步),而使用Logger输出线程不等待日志的输出(异步)
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
日志级别:trace<debug<info<warn<error
Logger logger = LoggerFactory.getLogger(getClass());
@Test
void contextLoads() {
logger.trace("trace日志。。。");
logger.debug("debug日志。。。");
//SpringBoot默认级别是info,只会打印info及后面的日志,也是root级别
logger.info("info日志。。。");
logger.warn("warn日志。。。");
logger.error("error日志。。。");
}
可以调整日志级别,设置只有高于此级别的才输出
logging.level.com.lvboaa=trace
表示把com.lvboaa包下面的所有类级别都设置为trace,能打印比trace优先级高的日志
日志信息写入文件:
#指定日志文件的名字,不设置path就在项目根目录下,name优先级高,只会写入一个文件
logging.file.name=logging.log
#会将log文件放在项目在电脑上的根目录,创建log文件夹,及spring.log文件
logging.file.path=/log
如果想使用自己的日志配置,只需要将配置文件(如:log-back.xml)放在类路径(classpath:)下即可覆盖默认配置
二、SpringBoot中级
1.数据库
使用Spring Data连接数据库,SQL和NoSQL都能连接
1.1.使用SpringBoot的默认数据库
HikariDataSource
号称JavaWeb速度最快的数据库,相比于传统的C3P0,DBCP
更加优秀
1.2.使用阿里巴巴的Druid数据源
依赖: 两种都可以,下面是和springboot适配了的
<!--druid-->
<!--<dependency>-->
<!--<groupId>com.alibaba</groupId>-->
<!--<artifactId>druid</artifactId>-->
<!--<version>1.1.21</version>-->
<!--</dependency>-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
比其他的数据库多了监控filters:stat
、日志filters:log4j
(日志需要导入log4j依赖,不然会报错)和防止SQL注入filters:wall
(配置文件配置)
在配置文件加入东西可进入http://localhost:8080/druid/index.html监控sql
spring:
datasource:
druid:
initial-size: 5 #连接池初始化大小
min-idle: 10 #最小空闲连接数
max-active: 20 #最大连接数
# 配置监控统计拦截的 Filter,去掉后监控界面 SQL 无法统计,wall 用于防火墙
filters: stat,wall,slf4j
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" #不统计这些请求数据
stat-view-servlet: #访问监控网页的登录用户名和密码
url-pattern: /druid/*
reset-enable: false
# allow: 127.0.0.1
login-username: admin #登录账号
login-password: 12345 #登录密码
1.3.整合mybatis
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
可以直接使用@Mapper
注解,在Dao
层接口的方法上面写SQL语句
1.4.整合thymeleaf
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
2.Swagger
2.1.Swagger简介
主要实现是前后端分离的接口交互
- 号称世界上最流行的API框架
- Restful Api文档自动生成=>Api文档和Api定义同步更新
- 直接运行,可以在线测试Api接口
- 支持多种语言
2.2.SpringBoot整合Swagger
- 1.导入依赖
<!--Swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
一个空的Web项目至少有一个/error
的请求(error page)
- 2.在启动类上加注解
@EnableOpenApi
可使用http://localhost:8080/swagger-ui/index.html访问Swagger的可视化界面
** 配置Swagger-ui的界面
@Configuration
public class SwaggerConfig {
//配置Swagger的Docket的bean实例
@Bean
public Docket getDocket(){
return new Docket(DocumentationType.OAS_30);
//.apiInfo(apiInfo());
}
//配置Swagger信息 apiInfo() Swagger的页面信息
private ApiInfo apiInfo(){
Contact contact = new Contact("Lvboaa", "https://www.baidu.com", "2484420621@qq.com");//作者信息
return new ApiInfo(
"Lvboaa SwaggerApi Document",
"生而为人,我很抱歉。",
"1.0",
"https://www.baidu.com",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
}
3.定时任务
- 创建定时任务
@Service
public class ScheduledService {
//秒 分 时 日 月 周几
//0 * * * * MON-FRI
//注意cron表达式的用法;
@Scheduled(cron = "0 * * * * 0-7")
public void hello(){
System.out.println("hello.....");
}
}
- 在主程序上增加@EnableScheduling 开启定时任务功能
@EnableAsync //开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTaskApplication.class, args);
}
}
cron表达式
http://www.bejson.com/othertools/cron/
常用表达式
(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触发
4.邮件任务
配置步骤
- 需要引入spring-boot-start-mail
- SpringBoot 自动配置MailSenderAutoConfiguration
- 定义MailProperties内容,配置在application.yml中
- 自动装配JavaMailSender
- 测试邮件发送
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
- 配置文件
spring.mail.username=24736743@qq.com
spring.mail.password=你的qq授权码
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true
获取qq邮件授权码
3. 单元测试
@Autowired
JavaMailSenderImpl mailSender;
@Test
public void contextLoads() {
//邮件设置1:一个简单的邮件
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject("通知-明天来狂神这听课");
message.setText("今晚7:30开会");
message.setTo("24736743@qq.com");
message.setFrom("24736743@qq.com");
mailSender.send(message);
}
@Test
public void contextLoads2() throws MessagingException {
//邮件设置2:一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject("通知-明天来狂神这听课");
helper.setText("<b style='color:red'>今天 7:30来开会</b>",true);
//发送附件
helper.addAttachment("1.jpg",new File(""));
helper.addAttachment("2.jpg",new File(""));
helper.setTo("24736743@qq.com");
helper.setFrom("24736743@qq.com");
mailSender.send(mimeMessage);
}
5.SpringSecurity
做网站,安全应该在设计之初就考虑
核心:把权限赋值给角色而不是具体的用户,用户只需要通过获得角色就能获得相应的权限
5.1、认识SpringSecurity
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
- 身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
- 授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。这个概念是通用的,而不是只在Spring Security 中存在。
5.2、配置使用SpringSecurity
配置步骤
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 编写配置类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授权 链式编程
@Override
protected void configure(HttpSecurity http) throws Exception {
//请求授权的规则
//首页所有人都可访问,功能页只能有权限的才能访问
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限默认回到登录页面 相当于controller里面的return "login";
//默认是安全框架自己的登录页面 http.formLogin(); 成功就回到首页 "/"
//设置没有权限返回自己登录的页面 loginPage("/toLogin")
//设置登录页面提交表单请求的路径 loginProcessingUrl("/login"); 认证成功默认到主页"index"
//设置认证成功到达的页面 defaultSuccessUrl("/level1/1")
// 输入框的名字必须:username和password必须和源码匹配
http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login");
//注销功能http.logout();默认回到框架的登录页面
//清空所有cookies和session http.logout().deleteCookies("remove").invalidateHttpSession(true);
//设置注销后去的页面
http.logout().logoutSuccessUrl("/");
//防止攻击,get是明文传送 csrf
//应为登出logout是get请求,可能会有跨站伪造请求,默认csrf是开启的
http.csrf().disable(); //登出失败可能的原因未关闭
//开启记住我功能 cookie 默认保存两周 框架登录页面的Remember
//匹配自己登录页面记住我的name
http.rememberMe().rememberMeParameter("remember");
}
//认证
//密码编码,防止明文显示
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//数据正常应该从数据库读
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("lvbo").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");
}
}
6.Shiro
6.1.简介
也是一个安全框架,可以完成认证、授权、加密、会话管理、web继承和缓存等
Shiro最主要的三个对象
- Subject:当前用户
- SecurityManager:安全管理器,关联Realm
- Realm:连接数据的桥梁,将数据库和Shiro相连
- 自定义Realm实现认证、授权
6.2.具体实现
Shiro文档的QuickStart
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);
// get the currently executing user:
//获取当前用户对象Subject
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
//通过该用户拿到Session
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// let's login the current user so we can check against roles and permissions:
//判断当前用户是否被认证
if (!currentUser.isAuthenticated()) {
//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?
}
}
//say who they are:
//print their identifying principal (in this case, a username):
//获取认证信息码
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
//测试角色
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//test a typed permission (not instance-level)
//测试是否有权限 粗粒度:简单
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
//a (very powerful) Instance Level permission:
//细粒度:更高的
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//all done - log out!
//注销功能
currentUser.logout();
System.exit(0);
}
}
配置类:ShrioConfig.java
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean:3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactory(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(securityManager);
//添加shiro的内置过滤器
/*
anno:无需认证就可访问
authc:必须认证了才可访问
user:必须拥有记住我 才能访问
perms:拥有某个资源的权限才能访问
role:拥有某个权限才能访问
*/
Map filterMap = new LinkedHashMap<>();
//一般前面是接口路径,后面是权限
// filterMap.put("/user/add","authc");
// filterMap.put("/user/update","authc");
//授权,没有授权的会跳到未授权页面
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
//注销 注销成功回到`/` 不建议
//filterMap.put("/logout", "logout");
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登录页面
bean.setLoginUrl("/toLogin");
//设置未授权的页面
bean.setUnauthorizedUrl("/unauthor");
return bean;
}
//DefaultWebSecurityManager:2
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurity(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
//创建realm对象,需要自定义类:1
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
注销代码实现:logout
@RequestMapping("/logout")
public String logout(Model model){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "user/logout";
}
登录授权认证:login
@RequestMapping("/login")
public String login(String username,String password,Model model){
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try{
//进入Shiro框架,执行授权和认证
subject.login(token);
return "index";
}catch (UnknownAccountException e){
model.addAttribute("msg","用户名不存在");
return "login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误");
return "login";
}
}
使用自定义的Realm: UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();//拿到下面认证返回的user对象
if(user.getName().equals("root")){
//添加多个授权
info.addStringPermissions(Arrays.asList(user.getAuthor().split(",")));
}else {
//添加授权 一个授权
info.addStringPermission(user.getAuthor());
}
//获得数据库角色,根据角色授权
/*
User user = (User) principalCollection.getPrimaryPrincipal();
List<Role> roleList = roleservice.getRoleListByUserName(user.getUserName());
List<Permission> permissionList = null;
if (roleList.size() > 0) {
//添加角色
for (Role role : roleList) {
authorizationInfo.addRole(role.getRole());
permissionList = permissionService.getPermissionListByRoleId(role.getRoleId());
for (Permission permission : permissionList) {
//添加权限
authorizationInfo.addStringPermission(permission.getPermission());
}
}
}
*/
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
//用户名、密码,从数据库中取
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User user = userService.querUserByName(userToken.getUsername());
if(user == null){
return null;//无用户名 自动抛出异常UnknownAccountException
}
//密码验证 shiro自己认证 之后进入授权
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
7.Dubbo和Zookeeper集成
微服务解决的四个问题
- 1.API网关,服务路由
- 2.Http,RPC框架,异步调用
- 3.服务注册与发现,高可用
- 4.熔断机制,服务降级
为什么需要解决这些问题?
网络是不可靠的!
7.1.简介
什么是分布式
- 分布式系统是若干独立计算机的集合,但是这些计算机对于用户来说就像单个相关系统
- 需要实现负载均衡(Nginx代理服务器、SpringCloud等)
- 由一组通过网络进行通信、为了完成共同任务而协调工作的计算机结点组成的系统,其目的是利用更多的机器,处理更多的数据
- 是建立在网络上的软件系统
- 明确:只有当单个节点处理能力无法满足计算、储存时才会考虑分布式,因为会引入很多单系统没有的问题
- MVC模式是将应用分成几个不同的模块,但是公共模块不能重用,分布式架构就在MVC的模式上将公共的模块提取出来
- 核心:分布式服务框架(RPC)
RPC
推荐文章:https://www.jianshu.com/p/2accc2840a1b
RPC:远程过程调用(Remote Procedure Call),一种技术思想、而不是规范
- 本地过程调用:本地方法A() 调用本地方法B()
- 远程过程调用:一个机器上的方法A() 调用另一台机器上的方法B()
RPC基本原理:
步骤解析:
核心:
* 通讯:http协议等
* 序列化:方便在网络上数据传输
7.2. Dubbo
简介
基于Java的高性能、轻量级开源RPC框架
核心
- 面向接口的远程调用
- 智能容错和负载均衡
- 服务自动注册和发现
侵入式:要求用户代码“知道”框架的代码,表现为用户代码需要继承框架提供的类。不利于代码的复用。但可以和框架更好的结合、充分发挥框架的作用(struts)
非侵入式:不需要用户代码引入框架代码的信息,从类的编写者角度来看,察觉不到框架的存在。没有过多的依赖,但与用户代码的互动比较困难(spring)
Doubbo是非侵入式
运行原理
-
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
-
服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
-
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
-
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
调用关系说明:
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
代码实现
- provider-server
依赖
<!--dubbo-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!--zookeeper的client-->
<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>
配置:
server.port=8081
#服务应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务器要被注册
dubbo.scan.base-packages=com.lvboaa.service
注册服务
@Service //可以被扫描到,在项目启动时就自动注册到注册中心(dubbo的)
public class TicketServiceImpl implements TicketService {
@Override
public String getTocket() {
return "买了张票。。。";
}
}
- consumer-server
依赖和provider一样
配置
server.port=8082
#消费者去哪拿服务需要暴露自己的名字
dubbo.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
获取服务:需先将TicketService复制到同名包下
@Service//将service注入Spring容器
public class UserService {
//想拿到provider提供的票,去注册中心拿到服务
@Reference //(dubbo的)
TicketService ticketService;
public void buyTicket(){
String tocket = ticketService.getTocket();
System.out.println(tocket);
}
}