核心思想:约定大于配置!!
you can just run
springboot的主要优点:
微服务是一种风格,原来是all in one(单体架构),完美的阐述了高内聚,低耦合的意思。
即微服务是一种利用分治法的思想,去把一整套非常复杂的业务逻辑给切分成多个简单的业务问题,并采用模块化方法去实现组合的一种架构方法。
它们是相互独立的,这意味着它们可以采用不同的编程语言和数据存储。微服务中几乎不存在集中管理,它使用轻量级的HTTP、REST或Thrift API来进行内部通信。
微服务风格(microservice style)
它用于描述一种设计应用程序的特别方式,作为一套独立可部署的服务。
简而言之,微服务架构风格,就像是把一个单独的应用程序开发为一套小服务,每个小服务运行在自己的进程中,并使用轻量级机制通信,通常是 HTTP API。
整体应用程序(Monolithic applications)相当成功,但是越来越多的人感觉到有点不妥,特别是在云中部署时。变更发布周期被绑定了——只是变更应用程序的一小部分,却要求整个重新构建和部署。随着时间的推移,很难再保持一个好的模块化结构,使得一个模块的变更很难不影响到其它模块。扩展就需要整个应用程序的扩展,而不能进行部分扩展。
这导致了微服务架构风格(microservice architectural style)的出现:把应用程序构建为一套服务。事实是,服务可以独立部署和扩展,每个服务提供了一个坚实的模块边界,甚至不同的服务可以用不同的编程语言编写。它们可以被不同的团队管理。
组件化(Componentization )与服务(Services),组件(component)是一个可独立替换和升级的软件单元。
当寻找把一个大的应用程序拆分成小的部分时,通常管理都会集中在技术层面,UI团队、服务端业务逻辑团队和数据库团队。当使用这种标准对团队进行划分时,甚至小小的更变都将导致跨团队项目协作,从而消耗时间和预算审批。一个高效的团队会针对这种情况进行改善,两权相害取其轻。业务逻辑无处不在。实践中,这就是 Conway’s Law 的一个例子。
设计一个系统的任何组织(广义上)都会产生这样一种设计,其结构是组织交流结构的复制。
——Melvyn Conway, 1967
Melvyn Conway 的意思是,像下图所展示的,设计一个系统时,将人员划分为 UI 团队,中间件团队,DBA 团队,那么相应地,软件系统也就会自然地被划分为 UI 界面,中间件系统,数据库。
微服务(microservice )的划分方法不同,它倾向围绕业务功能的组织来分割服务。这些服务实现商业领域的软件,包括用户界面,持久化存储,任何的外部协作。因此,团队是跨职能的(cross-functional),包含开发过程所要求的所有技能:用户体验(user-experience)、数据库(database)和项目管理(project management)。
Amazon 理念是“你构建,你运维(you build, you run it)”.
Guardian网站就是这方面的一个优秀的例子,它初期被设计和构建成一个整体架构,但它已经向微服务的发展了。整体构架仍然是它网站的核心,但是他们使用微服务来增加那些使用整体架构API的新特性。这种方法增加这些临时的特性非常方便,比如运动新闻的特稿。这样站点的一个部分可以使用快速的开发语言迅速整合起来,当它过时后可以一次性移除。我们发现一家金融机构用相似的方法增加新的市场营销活动,数周或者几个月后把它撤销。
微服务的优缺点:https://www.zhihu.com/question/65502802
通过springboot进行开发真的很方便,我们只需要新建一个项目,然后新建一个controller别的我们都不需要管,我们事先已经导入了很多依赖,其中就包括springboot内置的tomcat,我们也不需要配置配置文件和tomcat直接run就可以了,就像是我们一开始说的**”you can just run“!!**,springboot相比ssm简化了很多配置,更重要是的springboot是前后端分离的,我们后端写好的项目,打成jar然后前端就直接可以用了,很方便,便于开发。
运行jar的命令“java -jar 包名”。
pom.xml
启动器:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
如果我们要使用什么功能,我们只需要找到对应的启动器就可以了,即“starter”,因为核心依赖都已经配置好了在父工程里面
/**标注这是一个springboot的应用*/
@SpringBootApplication
//通过反射启动run方法 使其springboot启动
SpringApplication.run(HelloworldApplication.class, args);
@SpringBootConfiguration springboot配置
@Configuration spring配置类
@Component 说明这是spring的组件
@EnableAutoConfiguration 自动配置
@AutoConfigurationPackage 自动配置包
@Import({
Registrar.class}) 导入自动配置 包注册
@Import({
AutoConfigurationImportSelector.class}) 自动导入选择
META-INF/spring.factories 自动配置的核心文件
@SpringBootApplication
标记这是一个springboot的应用
@SpringBootConfiguration
核心
@Configuration
spring的配置类
@Component
说明这是一个spring的组件@EnableAutoConfiguration
核心 自动配置
@AutoConfigurationPackage
自动配置包
@Import(AutoConfigurationPackages.Registrar.class)
自动注册包 自动导包的核心
AutoConfigurationPackages.Registrar
@Import(AutoConfigurationImportSelector.class)
自动导入选择 选择了什么东西
getAutoConfigurationEntry
获取自动配置的实体
getCandidateConfigurations
获取候选的配置
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
获取候选位置:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
这个文件里面放着所有的自动配置的类,当我们用了某个类的时候 springboot的注解会识别出来你用的是哪个类,然后进一步调用这个文件里面响应的类。
@ConditionalOnClass
那么多自动配置,为什么有的类没有生效,必须导入start才生效,这是因为springboot很智能,在他的自动配置的文件里的每一个类都有一个注解,通过判断注解里面的内容是否全部都满足,都满足的话才会生效。爆红,所以不满足。springboot所有的自动配置都是在启动的时候扫描并加载的,扫描的文件就是上面说的spring.factories
,所有的配置类都在这里面,但是不一定会生效,先要判断条件是否成立,只要导入了对应的start就有对应的启动器了,有了启动器我们的自动装配就会生效,然后就配置成功了!
自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
spring.factories全路径名:E:\Maven\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar!\META-INF\spring.factories
步骤:
\META-INF\spring.factories
获取指定的值spring-boot-autoconfigure-2.4.3.jar
下yaml是properties的代替者,功能比properties强大,可以存取数组,对象,字符串,相比propertis更快捷,所以springboot推荐使用yaml,空格很重要。
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
application.properties
application.yml
**配置文件的作用 :**修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!
server.port=8081
YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
这种语言以数据作为中心,而不是以标记语言为重点!
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
传统xml配置:
<server>
<port>8081<port>
server>
yaml配置:
server:
prot: 8080
说明:语法要求严格!
1、空格不能省略
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、属性和值的大小写都是十分敏感的。
字面量:普通的值 [ 数字,布尔值,字符串 ]
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
注意:
“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: “kuang \n shen” 输出 :kuang 换行 shen
‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen
#key: value
name: qin
# 数组
arrays: [one,two,three]
arrays:
- one
- two
- three
# 对象
student:
name: wang
age: nv
yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!
首先来一个实例,用狗狗测试一下
@Data
@NoArgsConstructor
@AllArgsConstructor
@Component
public class Dog {
@Value("旺财") //通过注解注入内容
private String name;
@Value("3")
private Integer age;
}
然后我们测试一下
@SpringBootTest
class HelloworldApplicationTests {
@Autowired
private Dog dog;
@Test
void contextLoads() {
System.out.println(dog);
}
}
但是当我们编写一个很复杂类的时候这样做显然很麻烦,所以我们用yaml注入的方式来解决。
首先我们需要一个实体类就叫person吧
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Component
@ConfigurationProperties(prefix = "person")
@PropertySource(value = "classpath:kuang.properties")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birthday;
private Map<String,Object> maps;
private List<Object> list;
private Dog dog;
}
然后我们需要一个application.yaml
文件
person:
name: wanghu
age: 13
happy: true
birthday: 2020/05/20
maps: {
k1: v1,k2: v2}
list:
- code
- music
- gril
dog:
name: kuanghua
age: 3
我们在person实体类中已经实现了匹配了,直接运行就可以了。
我们还可以加载指定的配置文件:@PropertySource(value = "classpath:kuang.properties")
。
@PropertySource :加载指定的配置文件;
@configurationProperties:默认从全局配置文件中获取值;
配置文件还可以编写占位符生成随机数
person:
name: qinjiang${
random.uuid} # 随机uuid
age: ${
random.int} # 随机int
happy: false
birth: 2000/01/01
maps: {
k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: ${
person.hello:other}_旺财
age: 1
我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yml还有我们之前常用的properties , 我们没有讲,我们来唠唠!
【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;
settings–>FileEncodings 中配置;
测试步骤:
1、新建一个实体类User
@Component //注册bean
public class User {
private String name;
private int age;
private String sex;
}
2、编辑配置文件 user.properties
user1.name=kuangshen
user1.age=18
user1.sex=男
3、我们在User类上使用@Value来进行注入!
@Component //注册bean
@PropertySource(value = "classpath:user.properties")
public class User {
//直接使用@value
@Value("${user.name}") //从配置文件中取值
private String name;
@Value("#{9*2}") // #{SPEL} Spring表达式
private int age;
@Value("男") // 字面量
private String sex;
}
4、Springboot测试
@SpringBootTest
class DemoApplicationTests {
@Autowired
User user;
@Test
public void contextLoads() {
System.out.println(user);
}
}
@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图
1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下
3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
4、复杂类型封装,yml中可以封装对象 , 使用value就不支持
结论:
配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@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=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
springboot支持多环境随时切换,为什么要切换环境呢?因为你有可能是开发环境,也有可能是测试环境,所以我们要随时切换。
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;
我们需要通过一个配置来选择需要激活的环境:
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev
server :
port: 8081
spring:
profiles:
active: dev
---
server:
port: 8082
spring:
profiles: dev
---
server:
port: 8083
spring:
profiles: test
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!
外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!
官方外部配置文件说明参考文档
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
配置文件application.yaml
到底能写什么?该怎么写呢?
我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;
//表示这是一个配置类 也可以给容器添加组件
@Configuration(proxyBeanMethods = false)
//启动指定类的ConfigurationProperties功能;
//进入这个ServerProperties查看,将配置文件中对应的值和ServerProperties绑定起来;
//并把ServerProperties加入到ioc容器中
@EnableConfigurationProperties(ServerProperties.class)
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
//判断配置文件中是否存在某个配置:server.servlet.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置 server.servlet.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
}
一句话总结:我们在application.yaml
中配置的内容对应的就是我们的spring.factories
文件中定义好的类的属性和方法,且都是通过注解@ConfigurationProperties(prefix = "spring.data.mongodb")
来实现的,我们在配置文件中配置的内容最后就是由此注解调用。
@ConfigurationProperties(prefix = "spring.redis")
这就是自动装配的原理!
在这里贴一个我认为的比较容易理解的过程:
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
我们怎么知道哪些自动配置类生效?
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类
debug=true
Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)
前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。
jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。
那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?
SpringBoot推荐你可以来使用模板引擎:
模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:
模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。
我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。
怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。给大家三个网址:
Thymeleaf 官网:https://www.thymeleaf.org/
Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf
Spring官方文档:找到我们对应的版本
https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
找到对应的pom依赖:可以适当点进源码看下本来的包!
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-java8timeartifactId>
dependency>
前面呢,我们已经引入了Thymeleaf,那这个要怎么使用呢?
我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。
我们去找一下Thymeleaf的自动配置类:ThymeleafProperties
@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";
private String mode = "HTML";
private Charset encoding;
}
我们可以在其中看到默认的前缀和后缀!
我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。
使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!
要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;
Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!我们去下载Thymeleaf的官方文档!
我们做个最简单的练习 :我们需要查出一些数据,在页面中展示
@RequestMapping("/t1")
public String test1(Model model){
//存入数据
model.addAttribute("msg","Hello,Thymeleaf");
//classpath:/templates/test.html
return "test";
}
2、我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。
我们可以去官方文档的#3中看一下命名空间拿来过来:
xmlns:th="http://www.thymeleaf.org"
3、我们去编写下前端页面
狂神说
测试页面
在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。
只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!
地址 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration
底层源码
由底层源码可以看出我们可以随意的定义视图解析器,因为在最后springboot会帮我们组合所有的视图解析器。
package com.shi.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
/**
* @author Shi Jun Yi
* @version 1.0
* @date 2021/4/6 19:28
* 全面接管springmvc
* 如果你想自定义很多的功能,你可以写这个组件 然后将它交给springboot他就会给我们自动装配
*/
@Configuration //变成配置类
public class MyMvcConfig implements WebMvcConfigurer {
//实现了视图解析器接口的类 我们可以将myviewresolver乘坐我们自己定义的视图解析器
@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;
}
}
}
package com.shi.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**视图跳转*/
//我想跳转到哪里就跳转到哪里 只需要更改第一个参数和第二个参数就可以了,前面的参数相当于域名 后面的参数就是真实的html文件名
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/shi").setViewName("test");
}
}
这个注解的意思是全面接管,全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!
只需在我们的配置类中要加一个@EnableWebMvc。
我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效。
但是为什么我们加了一个@EnableWebMvc自动配置就失效了呢?
原因:首先我们来解剖源码,首先我们在这个注解上点进去会发现有一个继承的父类,
我们可以惊奇的发现自动配置类里面的注解@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
居然是我们刚才找到的@EnableWebMvc
注解里面继承的父类,所以我们可以得出结论,当我们在配置类中定义@EnableWebMvc
注解的时候就相当于在自动配置类中将自动配置给默认失效了。
注意:所有的页面的静态资源都需要用thymeleaf来接管
i18m:国际化的缩写
首页国际化:
@Bean
#{}
然后我们照猫画虎自定义一个处理语言的类 里面重写了两个方法 下图的方法是其中一个
原因:简单点说就是因为我们自己写的方法中调用的类(MyLocaleResolver)和springboot自带的方法中的类都实现相同的接口,并且方法的名称(组件名)和类型都是一样的,所以可以实现功能!必须要有@Bean注解
例如我们的国际化解析器的自定义类我们实现了LocaleResolver
接口,我们可以通过源码看到在WebMvcAutoConfiguration
中有一个localeResolver
方法,这个方法的类型是LocaleResolver
,这个方法就是实现国际化的方法,是springboot自带的,我们点进去这个类型可以发现,这是一个接口,接口的方法只有两个,所以,我们自定义组件的时候只需要实现这个接口以及重写这个接口所有的方法就可以实现国际化解析器的功能了。我们第一次用的视图解析器也是同样的道理。
package com.shi.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
/**
* 视图跳转
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/shi").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
/**
* 自定义的国际化组件就放到了spring中了
* @return
*/
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
参考: https://blog.csdn.net/zhangpower1993/article/details/89016503?utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.baidujs&dist_request_id=&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.baidujs
该项目实现了在springboot中的增删改查(CRUD),拦截器,国际化,登录注销,定制首页,错误页等功能
Spring Data:https://spring.io/projects/spring-data
spring:
datasource:
username: root
password: 123
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
JDBC控制类
package com.shi.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* @author Shi Jun Yi
* @version 1.0
* @date 2021/4/11 20:35
*/
@Controller
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
//查询数据库的所有信息
//没有实体类就用万能的Map来封装数据
@GetMapping("/userlist")
@ResponseBody
public List<Map<String,Object>> userList(){
String sql = "select * from test.data";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
/**
* 增
*/
@RequestMapping("/adduser")
public String addUser(){
String sql = "insert into data values(null,'王东','wang123','男',00000)";
jdbcTemplate.execute(sql);
return "redirect:/userlist";
}
/**
* 删
* @return
*/
@RequestMapping("/deleteuser")
public String deleteUser(){
String sql = "delete from data where userID=11";
jdbcTemplate.execute(sql);
return "redirect:/userlist";
}
/**
* 改
* @return
*/
@RequestMapping("/updateuser")
public String updateUser(){
String sql = "update data set userName='东哥' WHERE userID=10";
jdbcTemplate.execute(sql);
return "redirect:/userlist";
}
}
Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。
Github地址:https://github.com/alibaba/druid/
1、添加上 Druid 数据源依赖。
com.alibaba
druid
1.1.21
2、切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源
3、数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;
4、切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
5、导入Log4j 的依赖
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
6、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;
package com.kuang.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DruidConfig {
/*
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}
}
7、去测试类中测试一下;看是否成功!
@SpringBootTest
class SpringbootDataJdbcApplicationTests {
//DI注入数据源
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
//看一下默认数据源
System.out.println(dataSource.getClass());
//获得连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
DruidDataSource druidDataSource = (DruidDataSource) dataSource;
System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());
//关闭连接
connection.close();
}
}
Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,人家也提供了一个默认的 web 页面。
所以第一步需要设置 Druid 的后台管理页面,比如 登录账号、密码 等;配置后台管理;
package com.shi.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
/**
* @author Shi Jun Yi
* @version 1.0
* @date 2021/4/15 19:29
*/
@Configuration
public class DruidConfig {
/**
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
HashMap<String, String> initParams = new HashMap<>();
//后台管理员登录和密码
initParams.put("loginUsername","admin");
initParams.put("loginPassword","1234");
//后台允许谁访问 为空表示谁都可以访问
initParams.put("allow","");
//deny 后台拒绝谁访问
//initParams.put("deny","ip地址");
//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}
//配置 Druid 监控 之 web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);
//"/*" 表示过滤所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
Maven仓库地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.1
导入依赖
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
在application.properties
或者是application.yaml
中存放数据库的连接信息,还有一些属性值等信息
application.properties
中的配置信息
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 整合mybatis
mybatis.type-aliases-package=com.shi.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
application.yaml
中的配置信息
spring:
datasource:
username: root
password: 123
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
测试数据库是否连接成功
创建实体类,我们来操作test数据库下的user表
package com.shi.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Shi Jun Yi
* @version 1.0
* @date 2021/4/16 15:11
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String id;
private String name;
private String age;
}
创建Mapper包,然后创建Mapper接口
package com.shi.mapper;
import com.shi.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author Shi Jun Yi
* @version 1.0
* @date 2021/4/16 15:13
*/
//这个注解表示了这是一个mybatis的类
@Mapper
@Repository
public interface UserMapper {
List<User> queryUserList();
User queryUserById(String id);
void addUser(User user);
void updateUser(User user);
void deleteUser(String id);
}
之后我们在resources下创建mybatis包然后创建UserMapper.xml
配置文件
<mapper namespace="com.shi.mapper.UserMapper">
<select id="queryUserList" resultType="User">
SELECT * from user
select>
<select id="queryUserById" resultType="User">
select * from user where id=#{id}
select>
<insert id="addUser" parameterType="User">
insert into user values(#{id},#{name},#{age})
insert>
<update id="updateUser" parameterType="User">
update user set name=#{name},age=#{age} where id=#{id}
update>
<delete id="deleteUser" parameterType="String">
delete from user where id = #{id}
delete>
mapper>
最后我们编写controller进行测试
package com.shi.controller;
import com.shi.mapper.UserMapper;
import com.shi.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author Shi Jun Yi
* @version 1.0
* @date 2021/4/16 15:40
*/
@Controller
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/queryuserlist")
@ResponseBody
public List<User> queryUserList(){
List<User> users = userMapper.queryUserList();
return users;
}
@GetMapping("/querybyid")
@ResponseBody
public User queryuserbyid(){
User user = userMapper.queryUserById("2");
return user;
}
@GetMapping("/adduser")
public String addUser(){
userMapper.addUser(new User("3","王静","23"));
return "redirect:/queryuserlist";
}
@GetMapping("/deleteuser")
public String deleteUser(){
userMapper.deleteUser("3");
return "redirect:/queryuserlist";
}
}
过滤器,拦截器也属于安全策略的一种
安全框架:shiro,SpringSecurity
SpringSecurity
package com.shi.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* @author Shi Jun Yi
* @version 1.0
* @date 2021/4/16 17:11
*/
@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");
//没有权限会跳转到登录页面 需要开启登录的页面
http.formLogin();
//开启注销功能 然后跳转到首页
//防止网站工具:get post
http.csrf().disable();//关闭csrf功能
http.logout().logoutSuccessUrl("/");
//开启记住我功能 cookie默认保存
http.rememberMe();
}
//认证 加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//这些数据正常应该从数据库里面读 从内存中读
auth.inMemoryAuthentication()
.withUser("shi").password(new BCryptPasswordEncoder().encode("1234")).roles("vip1","vip2")
.and()//加密
.withUser("root").password(new BCryptPasswordEncoder().encode("333")).roles("vip1","vip2","vip3")
.and()//无加密
.withUser("guest").password("222").roles("vip1");
}
}
shiro是当下常见的安全框架,其不仅可以用在java se中,也可以用在javaee中,主要用于用户验证和授权操作。当然还可以做认证,加密,会话管理,web继承,缓存等。
学习步骤:
在使用Shiro 之前,大家做登录,权限什么的都是五花八门,各种花里胡哨的代码,不同系统的做法很有可能千差万别。
但是使用 Shiro 这个安全框架之后,大家做权限的方式都一致化了,这样的好处就是你的代码我看起来容易,我的代码你也好理解。
Shiro 也比较成熟,基本上能满足大部分的权限需要。
首先我们需要定义一个.ini的文件
#定义用户
[users]
#用户名 zhang3 密码是 12345, 角色是 admin
zhang3 = 12345, admin
#用户名 li4 密码是 abcde, 角色是 产品经理
li4 = abcde,productManager
#定义角色
[roles]
#管理员什么都能做
admin = *
#产品经理只能做产品管理
productManager = addProduct,deleteProduct,editProduct,updateProduct,listProduct
#订单经理只能做产品管理
orderManager = addOrder,deleteOrder,editOrder,updateOrder,listOrder
Subject 在 Shiro 这个安全框架下, Subject 就是当前用户
package com.how2java;
import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class TestShiro {
public static void main(String[] args) {
//用户们
User zhang3 = new User();
zhang3.setName("zhang3");
zhang3.setPassword("12345");
User li4 = new User();
li4.setName("li4");
li4.setPassword("abcde");
User wang5 = new User();
wang5.setName("wang5");
wang5.setPassword("wrongpassword");
List<User> users = new ArrayList<>();
users.add(zhang3);
users.add(li4);
users.add(wang5);
//角色们
String roleAdmin = "admin";
String roleProductManager ="productManager";
List<String> roles = new ArrayList<>();
roles.add(roleAdmin);
roles.add(roleProductManager);
//权限们
String permitAddProduct = "addProduct";
String permitAddOrder = "addOrder";
List<String> permits = new ArrayList<>();
permits.add(permitAddProduct);
permits.add(permitAddOrder);
//登陆每个用户
for (User user : users) {
if(login(user))
System.out.printf("%s \t成功登陆,用的密码是 %s\t %n",user.getName(),user.getPassword());
else
System.out.printf("%s \t成功失败,用的密码是 %s\t %n",user.getName(),user.getPassword());
}
System.out.println("-------how2j 分割线------");
//判断能够登录的用户是否拥有某个角色
for (User user : users) {
for (String role : roles) {
if(login(user)) {
if(hasRole(user, role))
System.out.printf("%s\t 拥有角色: %s\t%n",user.getName(),role);
else
System.out.printf("%s\t 不拥有角色: %s\t%n",user.getName(),role);
}
}
}
System.out.println("-------how2j 分割线------");
//判断能够登录的用户,是否拥有某种权限
for (User user : users) {
for (String permit : permits) {
if(login(user)) {
if(isPermitted(user, permit))
System.out.printf("%s\t 拥有权限: %s\t%n",user.getName(),permit);
else
System.out.printf("%s\t 不拥有权限: %s\t%n",user.getName(),permit);
}
}
}
}
/**
* 判断用户是否拥有某些角色 角色在配置文件中定义好了
* @param user
* @param role
* @return
*/
private static boolean hasRole(User user, String role) {
Subject subject = getSubject(user);
return subject.hasRole(role);
}
/**
* 判断用户是否拥有某些权限 用户的权限是预先定义好的
* @param user
* @param permit
* @return
*/
private static boolean isPermitted(User user, String permit) {
Subject subject = getSubject(user);
return subject.isPermitted(permit);
}
private static Subject getSubject(User user) {
//加载配置文件,并获取工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//获取安全管理者实例
SecurityManager sm = factory.getInstance();
//将安全管理者放入全局对象
SecurityUtils.setSecurityManager(sm);
//全局对象通过安全管理者生成Subject对象
Subject subject = SecurityUtils.getSubject();
return subject;
}
private static boolean login(User user) {
Subject subject= getSubject(user);
//如果已经登录过了,退出
if(subject.isAuthenticated())
subject.logout();
//封装用户的数据
UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPassword());
try {
//将用户的数据token 最终传递到Realm中进行对比
subject.login(token);
} catch (AuthenticationException e) {
//验证错误
return false;
}
return subject.isAuthenticated();
}
}
我们需要把相关权限数据存在数据库
RBAC 是当下权限系统的设计基础,同时有两种解释:
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-allartifactId>
<version>1.2.5version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.7.1version>
dependency>
//获取subject对象
Subject currentUser = SecurityUtils.getSubject();
//获取session对象
Session session = currentUser.getSession();
//验证是否认证
currentUser.isAuthenticated()
//获取权限
currentUser.getPrincipal()
//验证有无此角色
currentUser.hasRole("schwartz")
//验证有无此权限
currentUser.isPermitted("lightsaber:wield")
MD5加密网站: https://www.sojson.com/encrypt_md5.html MD5是不可逆的加密方法。
shiro前端页面标签:
重点类:
package com.shi.springboot_shiro2.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author Shi Jun Yi
* @version 1.0
* @date 2021/4/19 21:29
* 配置类
*/
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("DefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//设置拦截器
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/update","authc");
//授权 跳转到未授权页面
filterMap.put("/add","perms[user:add]");
filterMap.put("/update","perms[user:update]");
//filterMap.put("/add","authc");
//设置登录的请求
bean.setLoginUrl("/tologin");
//设置未授权页面
bean.setUnauthorizedUrl("/noauth");
bean.setFilterChainDefinitionMap(filterMap);
return bean;
}
/**
* DefaultWebSecurityManager
*/
@Bean(name = "DefaultWebSecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联userRealm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建realm对象
*/
@Bean(name = "userRealm")
public UserRealm userRealm(){
return new UserRealm();
}
/**
* 整合shiro和thymeleaf
*/
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
package com.shi.springboot_shiro2.config;
import com.shi.springboot_shiro2.pojo.User;
import com.shi.springboot_shiro2.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author Shi Jun Yi
* @version 1.0
* @date 2021/4/19 21:30
*/
//自定义的realm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 授权
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//info.addStringPermission("user:add");
//将下面的user拿到这边来
Subject subject = SecurityUtils.getSubject();
//拿到User
User currentUser = (User) subject.getPrincipal();
info.addStringPermission(currentUser.getPerms());
return info;
}
/**
* 认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
//认证用户名和密码是否正确
UsernamePasswordToken usertoken = (UsernamePasswordToken) authenticationToken;
//连接真实数据库
User user = userService.queryUserByName(usertoken.getUsername());
if (user == null) {
//没有这个人
return null;
//如果返回null 会爆异常
}
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("loginUser",user);
//密码认证我们不用做,都是shiro来做
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
}
package com.shi.springboot_shiro2.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author Shi Jun Yi
* @version 1.0
* @date 2021/4/19 21:20
* 控制器
*/
@Controller
public class MyController {
@RequestMapping({
"/", "/index"})
public String toIndex(Model model) {
model.addAttribute("msg", "Hello World");
return "index";
}
@RequestMapping("/add")
public String add() {
return "user/add";
}
@RequestMapping("/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";
}
}
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "未经授权无法访问此界面!";
}
/**
* 注销
*/
@RequestMapping("/logout")
public String Logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:index";
}
}
因为我们前后端分离之后会有前端和后端甚至都不在一个服务器上面,然后这样就会产生一个问题,前端和后端的开发人员无法及时的沟通,最终导致问题集中爆发,比如前端有一个需求,只需要改一下页面就可以了,但是后端需要改很多层的内容,时间久了,需求多了就会“打起来”。
**怎么解决?**首先制定schema[计划的纲领],实时更新API,降低集成的风险。前端测试后端接口,其实我们只需要解决实时这个功能就可以了,于是swagger就诞生了。
如何使用?
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
编写一个hello工程
配置swagger–config
@Configuration
@EnableSwagger2
public class SwaggerConfig{
}
Swagger的bean实例Docket(['dɒkɪt] )
package com.shi.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* 配置了swagger的bean实例
*
* @return
*/
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select();
}
private ApiInfo apiInfo(){
//作者信息
Contact contact = new Contact("石石子", "https://www.allcoding.cn", "[email protected]");
return new ApiInfo("石石子的SwaggerAPI文档",
"满招损,谦受益",
"v1.0",
"urn:tos",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList()
);
}
}
package com.shi.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* 配置了swagger的bean实例
*
* @return
*/
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//RequestHandlerSelectors:配置要扫描接口的方式
//basePackage 指定要扫描的包
//any() 扫描全部
//none() 全部不扫描
//withClassAnnotation() : 扫描类上面的注解,参数是一个反射的对象
//withMethodAnnotation() : 扫描方法上面的注解
.apis(RequestHandlerSelectors.basePackage("com.shi.controller"))
//paths() 过滤什么路径
.paths(PathSelectors.ant("/shi/**"))
.build();
}
private ApiInfo apiInfo(){
//作者信息
Contact contact = new Contact("石石子", "https://www.allcoding.cn", "[email protected]");
return new ApiInfo("石石子的SwaggerAPI文档",
"满招损,谦受益",
"v1.0",
"urn:tos",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList()
);
}
}
题目:我只希望我的Swagger在生产环境中使用,在发布的时候不使用
Environment environment
来得到当前的环境信息properties
和发布环境的properties
,通过用server.port=8081
来设置不同的端口号在application.properties
中进行引用spring.profiles.active=pro
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket(Environment environment) {
//设置要显示的swagger环境
Profiles profiles = Profiles.of("dev","test");
//通过environment.acceptsProfiles来判断是否处在自己设定的环境当中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag)
.select()
.apis(RequestHandlerSelectors.basePackage("com.shi.controller"))
.build();
}
}
改了端口号访问的时候必须修改。
.groupName("石石子")
@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");
}
我们可以通过swagger的这个测试功能得到相应的错误报告,他会明确的告诉你哪里错了。
package com.shi.pojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ApiModel("用户实体类")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@ApiModelProperty("用户名")
public String username;
@ApiModelProperty("密码")
public String password;
}
package com.shi.controller;
import com.shi.pojo.User;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@PostMapping("/hello")
@ResponseBody
public String hello(){
return "hello";
}
@PostMapping("/user")
public User user() {
//只要我们的接口中,返回值存在实体类,他就会被扫描到swagger中
return new User();
}
@ApiOperation("Hello控制类")
@GetMapping("/hello2")
public String hello2(@ApiParam("用户名") String username){
return "hello2"+username;
}
@ApiOperation("Postt测试类")
@PostMapping("/postt")
@ResponseBody
public User user2(@ApiParam("用户名2") User user){
return user;
}
}
swagger是一个优秀的工具,出于安全考虑在正式发布的时候一定要关闭swagger,要不然所有的接口都会被看到。
没有实现异步就是我们前端会有一段时间的空白,然后我们只能等待数据查询完毕或者是业务调用完毕才能有结果,但是我们用异步的话就很快,因为我们是多线程来做的即使先调用的业务后调用前端的内容显示,但是我们的前端结果也是秒出的。
但是下面这一种是没有实现异步的,所以当我们执行完查询之后才能在前端上返回内容,当这个空档前台是空白的,用户体验很差。
package com.shi.service;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在处理");
}
}
package com.shi.controller;
import com.shi.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@RequestMapping("/hello")
public String hello(){
asyncService.hello();
return "OK";
}
}
开启异步也很简单,在启动类上面和服务层的方法上面加个注解,如下:
这样一来我们当调用service层方法的时候就是多线程启动,这样就很快,前端返回的结果秒出,用户体验就很好。在很多网站都是用的这个逻辑,例如淘宝,当高并发的时候我们访问就会变慢,然后当我恩访问某个页面就会先返回一个“加载中…”,然后等到后台加载好了直接跳转显示加载的内容,这就是用的异步处理的,用户体验会好一点。
我们想要发送邮件先导入一个依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
<version>2.4.4version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>5.3.5version>
dependency>
<dependency>
<groupId>com.sun.mailgroupId>
<artifactId>jakarta.mailartifactId>
<version>1.6.5version>
dependency>
导入依赖之后我们就要在application.properties中设置参数,如果想用QQ邮箱发送邮件的话,必须拿到POP3/SMTP服务的一串验证码。
然后设置参数
[email protected]
spring.mail.password=
#发送的服务器
spring.mail.host=smtp.qq.com
#开启加密授权验证 QQ特有的
spring.mail.properties.mail.smtl.ssl.enable=true
测试
package com.shi;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
@SpringBootTest
class SpringbootTestApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
SimpleMailMessage mailMessage = new SimpleMailMessage();
//发送的标题
mailMessage.setSubject("hello,极客fx");
//发送的内容
mailMessage.setText("");
//收件人
mailMessage.setTo("");
//发送人用户名
mailMessage.setFrom("");
mailSender.send(mailMessage);
}
}
这样一来,一个简单的邮件发送就好了。
三种方式可以创建复杂的邮件
@Test
//一个复杂的邮件发送
void contextLoads2() throws MessagingException {
MimeMessage mimeMessage = mailSender.createMimeMessage();
//将复杂的内容用helper组装
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
//正文,复杂的邮件发送支持附件和html格式
helper.setSubject("");
helper.setText("你好,我是工藤新一
",true);
//附件
helper.addAttachment("1jpg",new File("C:\\Users\\Lenovo\\Desktop\\一些东西\\5.jpg"));
helper.addAttachment("2jpg",new File("C:\\Users\\Lenovo\\Desktop\\一些东西\\5.jpg"));
helper.setTo("");
helper.setSubject("");
mailSender.send(mimeMessage);
}
TaskScheduler 任务调度者
TaskExecutor 任务执行者
@EnableScheduling //开启定时功能
@Scheduled //什么时候执行
cron表达式
package com.shi.service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalTime;
@Service
public class ScheduledService {
/**
* cron 表达式
* 秒 分 时 日 月 周几(0-7 表示每一天)
* 30 26 17 * * ? 表示 每天的17点26分30秒执行一次
* 30 0/5 8,17 * * ? 表示 每天的8点或者是17点每隔五分钟执行一个
* 0 15 10 ? * 1-6 表示 每个月的周一到周六的十点十五执行一次
* 0/1 * * * * ? 表示 每隔两秒执行一次
* L 表示last 最后一个周,日即可
*/
@Scheduled(cron = "30 26 17 * * ?")
public void hello(){
System.out.println(LocalDate.now().atTime(LocalTime.now())+" 执行了我!!");
}
}
结果:
在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”。
分布式系统是建立在网络上的软件系统。
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
RPC就是指远程过程调用,是一种进程间的通信方式,它是一种技术思想,而不是规范,它允许一台电脑调用另一台电脑的一个函数或过程,本地调用就是在本机上用A函数调用了B函数,而远程过程调用就是在另外一台电脑上的A函数调用了其他一台电脑上的B函数。
RPC两个核心:通信和序列化
分布式架构会遇到的四个核心问题:
解决方案:
springcloud,是一套生态,就是用来解决上面四个问题的,如果想用springcloud,必须先掌握Springboot
OK,至此springboot 完结撒花,接下来就是巩固前面的知识和准备面试啦~加油吧,骚年!!
只要学不死,就往死里学!!