javase:OOP
MySQL:持久化
html+css+js+jquery+框架:视图,框架不熟练,css不好
javaweb:独立开发MVC三层架构的网站:原始
ssm:框架:简化了我们的开发流程,配置也开始较为复杂;
在此之前项目打包都是war包,程序在Tomcat中运行
spring再简化:springBoot-jar包,内嵌Tomcat;微服务架构!
服务越来越多:springCloud
微服务是一种架构风格,他要求我们在开发一个应用的时候,这个应用必须建成一系列小服务组合,可以通过http方式进行通信。
所谓微服务加购,就是打破之前all in one的架构方式,把每个功能元素独立出来,把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些可以整合多个功能元素,所以微服务架构是对功能元素进行赋值,而没有对整个应用进行复制,这样做的好处是:
程序核心:高内聚(在划分模块时,要把功能关系紧密的放到一个模块中)
低耦合(模块之间的联系越少越好,接口越简单越好)
论文地址:https://martinfowler.com/articles/microservices.html
中文版论文地址:https://www.cnblogs.com/liuning8023/p/4493156.html
环境准备:
开发工具:
Spring官方提供了非常方便的工具让我们快速构建应用,IDEA也集成了这个网站
Spring Initializr:https://start.spring.io
使用Spring Initializr 的 Web页面创建项目
使用 IDEA 直接创建项目
通过上面步骤完成了基础项目的创建。就会自动生成以下文件。
1、程序的主启动类(程序的主入口)
2、一个 application.properties 配置文件(SpringBoot的核心配置文件)
3、一个 测试类
4、一个 pom.xml
pom.xml文件分析:
打开pom.xml,看看Spring Boot项目的依赖:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.4version>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.7.4version>
plugin>
plugins>
build>
编写一个http接口
@RestController
public class HelloController {
//接口:http://localhost:8080/hello
@RequestMapping("/hello")
public String hello(){
//调用业务,接收前端的参数!
return "hello,world";
}
}
更改端口号:在application资源文件中配置
server.port = xxxx
图像彩蛋
1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.7.4version>
dependency>
starter
//程序的主入口
//@SpringBootApplication 标注这个类是springboot的应用 启动类下的所有资源被导入
@SpringBootApplication
public class Springboot01HelloworldApplication {
//将springboot应用启动
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
注解:@SpringBootApplication
1. @SpringBootConfiguration:springboot的配置
2.@EnableAutoConfiguration:自动导入配置
获取所有的配置
在AutoConfigurationImportSelector这个类里面
//获取所有的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
获取候选的配置
//获取候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
//Assert 断言
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
META-INF/spring.factories:自动配置的核心文件
SpringBoot所有的自动配置,都在启动类中被扫描并加载:spring.factories
所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的starter,就有对应的启动器了,有了启动器,我们的自动装配就会生效,然后就配置成功了
spring.factories
获取指定的值最初以为就是运行了一个main方法,没想到却开启了一个服务;
@SpringBootApplication
public class Springboot01HelloworldApplication {
//该方法返回一个configurableApplicationContext对象
// 参数一:应用入口的类 参数类:命令行参数
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
SpringApplication.run分析
分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;
这个类主要做了以下四件事情:
查看构造器:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
// ......
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances();
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
关于SpringBoot,谈谈你的理解:
全面接管SpringMVC的配置!
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
key=value
key:空格 value
**配置文件的作用 **:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
比如可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!
YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
这种语言以数据作为中心,而不是以标记语言为重点!
标记语言
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
yaml配置:
server:
port: 8080
xml配置:
<server>
<port>8080port>
server>
# k=v键值对
# 普通的key-value
name: xiaoming
#相当于name=xiaoming
# 存对象
student:
name: xiaoming
age: 12
#相当于
#student.name = xiaoming
#student.age = 12
# 行内写法
student1: {name: xiaoming,age: 13}
#数组
pets:
- cat
- dog
- pyg
pets1: [cat,dog]
说明:语法要求严格!
1、空格不能省略
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、属性和值的大小写都是十分敏感的。
字面量:普通的值 [ 数字,布尔值,字符串 ]
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
k: v
注意:
" " 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: “xiao \n ming” 输出 :xiao 换行 ming
’ ’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘xiao \n ming’ 输出 :xiao \n ming
对象、Map(键值对)
#对象、Map格式
k:
v1:
v2:
maps: {k1: v1,k2: v2}
在下一行来写对象的属性和值得关系,注意缩进;比如:
student:
name: xiaoming
age: 3
行内写法
student: {name: xiaoming,age: 3}
数组( List、set )
用 - 值表示数组中的一个元素,比如:
pets:
- cat
- dog
- pig
行内写法
pets: [cat,dog,pig]
修改SpringBoot的默认端口号
配置文件中添加,端口号的参数,就可以切换端口;
server:
port: 8082
可以注入到我们的配置类当中
yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
1、在springboot项目中的resources目录下新建一个文件 application.yaml
2、编写一个实体类 Dog;
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
//添加到spring组件中
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog {
private String name;
private Integer age;
}
3、编写一个person类
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = "person":将配置文件中的person下面的所有属性——对应
只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能
*/
@Component
//绑定yaml的值 prefix = "person"
@ConfigurationProperties(prefix = "person")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
4、 在yaml中写入对象
person:
name: 小明
age: 3
happy: false
birth: 2022/10/12
maps: {k1: v1,k2: v2}
lists:
- code
- music
- girl
dog:
name: 旺财
age: 3
5、 在测试程序中测试
@SpringBootTest
class Springboot02ConfigApplicationTests {
//自动装配
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
**@PropertySource **:加载指定的配置文件;
@configurationProperties:默认从全局配置文件中获取值;
1、在resources目录下新建一个person.properties文件
name=xiaoming
2、然后在代码中指定加载person.properties文件
// javaconfig绑定我么配置文件的值,可以采取这些方式!
// 加载指定的配置文件
@PropertySource(value = "classpath:person.properties")
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
//SPEL表达式取出配置文件的值
@Value("${name}")
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
配置文件还可以编写占位符生成随机
Person:
name: xiaoming${random.uuid} # 随机uuid
age: ${random.int} # 随机int
happy: false
birth: 2022/10/12
maps: {k1: v1,k2: v2}
lists:
- code
- music
- girl
dog:
name: ${person.hello:other}_旺财 #如果hello没有那么就默认值hello+旺财
age: 3
@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;
功能对比图:
1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下
3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
4、复杂类型封装,yml中可以封装对象 , 使用value就不支持
结论:
配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!
spring-boot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
@Component
//绑定yaml的值 prefix = "person"
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
//(message = "邮箱格式不对")可写可不写
@Email(message = "邮箱格式不对")
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
使用数据校验,可以保证数据的正确性;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。
数值检查
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 被指定的元素必须在合适的范围内
@Range(min=10000,max=50000,message=”range.bean.wage”)
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)]()]()
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;
在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;
需要通过一个配置来选择需要激活的环境:
需要通过一个配置来选择需要激活的环境:
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev
和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !
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文件作为Springboot的默认配置文件
优先级1:项目路径下的config文件夹配置文件优先级
优先级2:项目路径下配置文件优先级
优先级3:资源路径下的config文件夹配置文件优先级
优先级4:资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部
加载主配置文件;互补配置;
在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;
#配置项目的访问路径
server.servlet.context-path=/yingxu
官网链接:官方外部配置文件说明参考文档
配置文件到底能写什么?怎么写?
SpringBoot官方文档中有大量的配置,无法全部记住
官方文档
以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration
//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class})
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
}
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
一但这个配置类生效;这个配置类就会给容器中添加各种组件;
这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
// .....
}
去配置文件里面试试前缀,看提示!
这就是自动装配的原理!
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
我们怎么知道哪些自动配置类生效?
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类
debug=true
Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)
【演示:查看输出的日志】
springboot到底帮我们配置了什么?我们能不能修改?能修改哪些东西?能不能扩展?
解决问题:
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
什么是webjars:WebJars是将web前端资源(js,css等)打成jar包文件,然后借助Maven工具,以jar包形式对web前端资源进行统一依赖管理,保证这些Web资源版本唯一性。WebJars的jar包部署在Maven中央仓库上。
webjars官网
要使用jQuery,只要要引入jQuery对应版本的pom依赖即可!
<dependency>
<groupId>org.webjars.npmgroupId>
<artifactId>jqueryartifactId>
<version>3.6.1version>
dependency>
http://localhost:8080/webjars/jquery/3.6.1/dist/jquery.js
在这些目录下创建1.js
http://localhost:8080/1.js
优先级:resources>static(默认)>puiblic
很少使用webjars
也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置;
spring.resources.static-locations=classpath:/coding/,classpath:/yingxu/
一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!
前端交给我们的页面,是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.7.4/reference/htmlsingle/#using.build-systems.starters
找到对应的pom依赖:可以适当点进源码看下本来的包!
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
Maven会自动下载jar包,我们可以去看下下载的东西;
前面呢,已经引入了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什么都不需要配置,只需要将他放在指定的文件夹下即可!
测试
1、编写一个TestController
@Controller
public class TestController {
@GetMapping ("/test")
public String test(Model model){
//classpath:/templates/test.html
return "test";
}
}
2、编写一个测试页面 test.html 放在 templates 目录下
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>测试页面h1>
body>
html>
3、启动项目请求测试
要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;
Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!去下载Thymeleaf的官方文档!
做个最简单的练习 :我们需要查出一些数据,在页面中展示
1、修改测试请求,增加数据传输;
@Controller
public class TestController {
@RequestMapping ("/test")
public String test(Model model){
//存入数据
model.addAttribute("msg","hello,springboot");
return "test";
}
}
2、要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。
可以去官方文档的#3中看一下命名空间拿来过来:
xmlns:th="http://www.thymeleaf.org"
3、编写下前端页面
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div th:text="${msg}">div>
body>
html>
4、启动测试!
OK,入门搞定,来认真研习一下Thymeleaf的使用语法!
1、可以使用任意的 th:attr 来替换Html中原生属性的值!
2、能写哪些表达式呢?
#4
Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
1)、获取对象的属性、调用方法
2)、使用内置的基本对象:#18
#ctx : the context object.
#vars: the context variables.
#locale : the context locale.
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.
3)、内置的一些工具对象:
#execInfo : information about the template being processed.
#uris : methods for escaping parts of URLs/URIs
#conversions : methods for executing the configured conversion service (if any).
#dates : methods for java.util.Date objects: formatting, component extraction, etc.
#calendars : analogous to #dates , but for java.util.Calendar objects.
#numbers : methods for formatting numeric objects.
#strings : methods for String objects: contains, startsWith, prepending/appending, etc.
#objects : methods for objects in general.
#bools : methods for boolean evaluation.
#arrays : methods for arrays.
#lists : methods for lists.
#sets : methods for sets.
#maps : methods for maps.
#aggregates : methods for creating aggregates on arrays or collections.
==================================================================================
Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
Message Expressions: #{...}:获取国际化内容
Link URL Expressions: @{...}:定义URL;
Fragment Expressions: ~{...}:片段引用表达式
Literals(字面量)
Text literals: 'one text' , 'Another one!' ,…
Number literals: 0 , 34 , 3.0 , 12.3 ,…
Boolean literals: true , false
Null literal: null
Literal tokens: one , sometext , main ,…
Text operations:(文本操作)
String concatenation: +
Literal substitutions: |The name is ${name}|
Arithmetic operations:(数学运算)
Binary operators: + , - , * , / , %
Minus sign (unary operator): -
Boolean operations:(布尔运算)
Binary operators: and , or
Boolean negation (unary operator): ! , not
Comparisons and equality:(比较运算)
Comparators: > , < , >= , <= ( gt , lt , ge , le )
Equality operators: == , != ( eq , ne )
Conditional operators:条件运算(三元运算符)
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special tokens:
No-Operation: _
练习测试:
1、 编写一个Controller,放一些数据
@Controller
public class IndexController {
@GetMapping ("/test")
public String test(Model model){
//存入数据
model.addAttribute("msg","hello,springboot
");
model.addAttribute("users", Arrays.asList("xiaoming","zhangsan"));
return "test";
}
}
2、测试页面取出数据
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div th:text="${msg}">div>
<div th:utext="${msg}">div>
<br/>
<h3 th:each="user:${users}" th:text="${user}">h3>
body>
html>
3、启动项目测试!
http://localhost:8080/test
在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。
只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!
地址 :官网
在Properties文件中,可以进行自动配置它!
如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:
# 自定义的配置日期格式化!
spring.mvc.format.date=dd/MM/yyyy
spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss
其余的就不一一举例了!
这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。
SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!
扩展使用SpringMVC 官方文档如下:
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;自己写一个;新建一个包叫config,写一个类MyMvcConfig;
//应为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送/yingxu , 就会跳转到test页面;
registry.addViewController("/yingxu").setViewName("test");
}
}
确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!
可以去分析一下原理:
1、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)
3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration
这个父类中有这样一段代码:
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 从容器中获取所有的webmvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
}
4、可以在这个类中去寻找一个刚才设置的viewController当做参考,发现它调用了一个
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
5、点进去看一下
public void addViewControllers(ViewControllerRegistry registry) {
Iterator var2 = this.delegates.iterator();
while(var2.hasNext()) {
// 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
delegate.addViewControllers(registry);
}
}
所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;
@EnableWebMvc 就是导入了一个 DelegatingWebMvcConfiguration 类,从容其中获取所有的webmvcconfig
在SpringBoot中由非常多的xxxConfiguration,会帮助我们进行扩展,只要看见了这个东西我们就要注意了
官方文档:
If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.
全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!
只需在我们的配置类中要加一个@EnableWebMvc。
我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;
不加注解之前,访问首页:
给配置类加上注解:@EnableWebMvc
发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;
当然,在开发中,不推荐使用全面接管SpringMVC
思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:
1、这里发现它是导入了一个类,我们可以继续进去看
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
2、它继承了一个父类 WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// ......
}
3、回顾一下Webmvc自动配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;
而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!
有的时候,网站会去涉及中英文甚至多语言的切换,这时候就需要学习国际化了!
SpingBoot-Web静态资源 密码:web
先在IDEA中统一设置properties的编码问题!
编写国际化配置文件,抽取页面需要显示的国际化页面消息。我们可以去登录页面查看一下,哪些内容我们需要编写国际化的配置!
1、我们在resources资源文件下新建一个i18n目录,存放国际化配置文件
2、建立一个login.properties文件,还有一个login_zh_CN.properties;发现IDEA自动识别了我们要做国际化操作;文件夹变了
3、我们可以在这上面去新建一个文件;
弹出如下页面:我们再添加一个英文的;
这样就快捷多了!
4、接下来,我们就来编写配置,我们可以看到idea下面有另外一个视图;
没有Resource Bundle在idea的Plugins中下载一个 Resource Bundle Editor就有了
这个视图我们点击 + 号就可以直接添加属性了;我们新建一个login.tip,可以看到边上有三个文件框可以输入
我们添加一下首页的内容!
然后依次添加其他页面内容即可!
然后去查看我们的配置文件;
login.properties :默认
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
英文:
login.btn=Sign in
login.password=Password
login.remember=Remember me
login.tip=Please sign in
login.username=Username
中文:
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
OK,配置文件步骤搞定!
我们去看一下SpringBoot对国际化的自动配置!这里又涉及到一个类:MessageSourceAutoConfiguration
里面有一个方法,这里发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource;
// 获取 properties 传递过来的值进行判断
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
// 设置国际化文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(
StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
我们真实 的情况是放在了i18n目录下,所以我们要去配置这个messages的路径;
spring.messages.basename=i18n.login
去页面获取国际化的值,查看Thymeleaf的文档,找到message取值操作为:#{…}。我们去页面测试下:
IDEA还有提示,非常智能的!
但是我们想要更好!可以根据按钮自动切换中文英文!
在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器!
我们去我们webmvc自动配置文件,寻找一下!看到SpringBoot默认配置:
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
// 容器中没有就自己配,有的话就用用户配置的
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
// 接收头国际化分解
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
AcceptHeaderLocaleResolver 这个类中有一个方法
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = this.getDefaultLocale();
// 默认的就是根据请求头带来的区域信息获取Locale进行国际化
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
} else {
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = this.getSupportedLocales();
if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
} else {
return defaultLocale != null ? defaultLocale : requestLocale;
}
} else {
return requestLocale;
}
}
}
那假如我们现在想点击链接让我们的国际化资源生效,就需要让我们自己的Locale生效!
我们去自己写一个自己的LocaleResolver,可以在链接上携带区域信息!
修改一下前端页面的跳转连接:
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">Englisha>
我们去写一个处理的组件类!
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
//可以在链接上携带区域信息
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getParameter("l");
Locale locale = Locale.getDefault(); // 如果没有获取到就使用系统默认的
//如果请求链接不为空
if (!StringUtils.isEmpty(language)){
//分割请求参数
String[] split = language.split("_");
//国家,地区
locale = new Locale(split[0],split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的MvcConofig下添加bean;
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
我们重启项目,来访问一下,发现点击按钮可以实现成功切换!
# 1.配置首页 :
1.1 注意点,所有页面的静态资源都需要使用thymeleaf接管;
1.2 @{}
# 2.页面的国际化
#确保在setting中 fileEncoding 都是utf-8
注意点: 2.1 我们需要配置i18n文件
2.2 我们如果需要在项目中进行按钮自动切换,就需要自定义一个组件
MyLocaleResolver
2.3 记得将自己写的组件配置到spring容器中 @bean
2.4 #{}
# 3.登录+拦截器
# 4.CRUD 员工列表展示
4.1.提取公共页面 th:fragment 或者 th:replace
4.1.1 th:fragment="sidebar"
4.1.2
4.1.3 如果要使用参数,可以使用()传参
4.2 列表循环展示 list页面中
# 5.添加员工
1.按钮提交
2.跳转到添加页面
3.添加员工成功
4.返回首页
日期:默认`dd/MM/yyyy`. 修改 spring.mvc.date-format=
# 6.CRUD搞定
# 7. 404
前端搞定:页面长什么样子:数据
设计数据库
前端让他能够自动运行,独立化工程
数据接口如何对接,json,对象all in one
前后端联调测试
有一套自己熟悉的后台模板:工作必要! 后台框架:xadmin
前端界面:至少自己能够通过前端框架,组合出来一个网站页面
让这个网站能够独立运行!
对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。
Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。
Sping Data 官网:Sping Data 官网
数据库相关的启动器 :可以参考官方文档:
官方文档
1、新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块
2、项目建好之后,发现自动帮我们导入了如下的启动器:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
3、编写yaml配置文件连接数据库;
spring:
datasource:
username: root
password: 123456
# 假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
4、配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下
@SpringBootTest
class Springboot04DataApplicationTests {
//DI注入数据源
@Autowired
@Resource
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源 com.zaxxer.hikari.HikariDataSource
System.out.println(dataSource.getClass());
//获得数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//关闭
connection.close();
}
}
结果:可以看到默认配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置
全局搜索一下,找到数据源的所有自动配置都在 :DataSourceAutoConfiguration文件:
@Import(
{Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class}
)
protected static class PooledDataSourceConfiguration {
protected PooledDataSourceConfiguration() {
}
}
这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.2.5 默认使用HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;
HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;
可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。
关于数据源我们并不做介绍,有了数据库连接,显然就可以 CRUD 操作数据库了。但是我们需要先了解一个对象 JdbcTemplate
1、有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库;
2、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。
3、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。
4、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
5、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类
JdbcTemplate主要提供以下几类方法:
编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试;
@RestController
public class JDBCController {
@Autowired
@Resource
JdbcTemplate jdbcTemplate;
//查询数据库的所有信息
//没有实体类,数据库中的东西,怎么获取? Map
@GetMapping("/userList")
public List<Map<String,Object>> userList(){
String sql="select * from user";
List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
return list_maps;
}
//新增一个用户
@GetMapping("/addUser")
public String addUser(){
//插入语句,注意时间问题
String sql = "insert into mybatis.user(id,name,pwd) values (6,'李四','123')";
jdbcTemplate.update(sql);
//查询
return "addUser-Ok";
}
//修改用户信息
@GetMapping("/updateUser/{id}")
public String updateUser(@PathVariable("id") int id){
//插入语句
String sql = "update mybatis.user set name=?,pwd=? where id="+id;
//数据
Object[] objects = new Object[2];
objects[0] = "李四2";
objects[1] = "111";
jdbcTemplate.update(sql,objects);
//查询
return "updateUser-Ok";
}
//删除用户
@GetMapping("/deleteUser/{id}")
public String delUser(@PathVariable("id") int id){
//插入语句
String sql = "delete from mybatis.user where id=?";
jdbcTemplate.update(sql,id);
//查询
return "deleteUser-Ok";
}
}
测试请求
http://localhost:8080/userList
http://localhost:8080/addUser
http://localhost:8080/updateUser/6
http://localhost:8080/deleteUser/6
结果正常;
到此,CURD的基本操作,使用 JDBC 就搞定了。
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/
com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:
配置 | 缺省值 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错 |
|
url | 连接数据库的url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto |
|
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:详细 | |
driverClassName | 根据url自动识别 | 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
validationQueryTimeout | 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 | |
testOnBorrow | false | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 1分钟(1.0.14) | 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 |
numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
minEvictableIdleTimeMillis | 30分钟(1.0.14) | 连接保持空闲而不被驱逐的最长时间 |
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall | |
proxyFilters | 类型是List |
1、添加上 Druid 数据源依赖。
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.13version>
dependency>
2、去配置Druid,配置自定义的数据源
spring:
datasource:
username: root
password: 123456
# 假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
# 自定义数据源
type: com.alibaba.druid.pool.DruidDataSource
3、接下来就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码
spring:
datasource:
username: root
password: 123456
# 假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
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
# 底层开启功能,stat(sql监控),wall(防火墙)
filters: stat,wall,log4j2
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
4、因为Druid中包含log4j所以需要导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
5、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性
@Configuration
public class DruidConfig {
@Bean
//绑定到application.yaml文件中生效
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDateSource(){
return new DruidDataSource();
}
}
6、Druid具有看监控功能,可以方便用户在web端看见后台的操作,配置后台监控和过滤器
#配置监控统计拦截的filters,stat:监控统计、log4j2:日志记录、wall:防御sql注入
druid:
# 配置后台监控
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: 123456
resetEnable: false
#allow: 192.168.188.165 #ip白名单,以逗号分割传递多个值;相对应的deny为ip黑名单,不过貌似没啥用
# filter后台过滤
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
完整:
spring:
datasource:
username: root
password: 123456
# 假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
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
# 底层开启功能,stat(sql监控),wall(防火墙)
filters: stat,wall,log4j2
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#配置监控统计拦截的filters,stat:监控统计、log4j2:日志记录、wall:防御sql注入
druid:
# 配置后台监控
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: 123456
resetEnable: false
#allow: 192.168.188.165 #ip白名单,以逗号分割传递多个值;相对应的deny为ip黑名单,不过貌似没啥用
# filter后台过滤
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
然后在浏览器输入你的项目地址加端口号+/druid就可以查看了例如:http://localhost:8080/druid/
官方文档:官方文档
Maven仓库地址:mybatis-spring-boot-starter
1、导入 MyBatis 所需要的依赖
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
2、配置数据库连接信息
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3、导入lombok
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
4、Maven资源过滤设置
<build>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>
build>
5、在pojo文件夹下编辑User实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
6、在mapper文件夹下编辑UserMapper
//这个实体类既需要具有增删改查的功能又需要注册到spring中托管所有需要Repository和mapper
@Mapper
//这个注解代表了这是一个mybatis的mapper接口
@Repository //Dao层
public interface UserMapper {
//查询所有
List<User> queryUserList();
//通过id查询
User queryUserById(int id);
//增加
int addUser(User user);
//修改
int updateUser(User user);
//删除
int deleteUser(int id);
}
7、在resources资源目录下创建mybatis文件夹
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yingxu.mapper.UserMapper">
<select id="queryUserList" resultType="user">
select * from mybatis.user
select>
<select id="queryUserById" resultType="user">
select * from mybatis.user where id=#{id}
select>
<insert id="addUser" parameterType="user">
insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd})
insert>
<update id="updateUser" parameterType="user">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
update>
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id}
delete>
mapper>
8、在application.properties配置文件中配置
# 整合Mybatis
# 让spring识别到mapper文件
mybatis.type-aliases-package=com.yingxu.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
完整:
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 整合Mybatis
# 让spring识别到mapper文件
mybatis.type-aliases-package=com.yingxu.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
9、Service层(没有业务可以不需要,直接跳到步骤11
)
public interface UserService {
//查询所有
List<User> queryUserList();
//通过id查询
User queryUserById(int id);
//增加
int addUser(User user);
//修改
int updateUser(User user);
//删除
int deleteUser(int id);
}
10、Service层实现类
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
//查询所有
@Override
public List<User> queryUserList() {
return userMapper.queryUserList();
}
//通过id查询
@Override
public User queryUserById(int id) {
return userMapper.queryUserById(id);
}
//增加
@Override
public int addUser(User user) {
return userMapper.addUser(user);
}
//修改
@Override
public int updateUser(User user) {
return userMapper.updateUser(user);
}
//删除
@Override
public int deleteUser(int id) {
return userMapper.deleteUser(id);
}
}
11、编写controller
@RestController
public class UserController {
@Autowired
private UserService userService;
//查询所有
@RequestMapping("/userList")
public List<User> queryUserList(){
List<User> userList = userService.queryUserList();
for (User user : userList) {
System.out.println(user);
}
return userList;
}
//通过id查询
@RequestMapping("/queryUser/{id}")
public User queryUserById(@PathVariable("id")int id){
User user = userService.queryUserById(id);
return user;
}
//增加
@RequestMapping("/addUser")
public String addUser(){
int add = userService.addUser(new User(7, "七七", "123"));
if (add>0){
return "addUser-ok";
}
return "addUser-no";
}
//修改
@RequestMapping("/updateUser")
public String updateUser(){
int update = userService.updateUser(new User(7, "七七2", "123456"));
if (update>0){
return "updateUser-ok";
}
return "updateUser-no";
}
//删除
@RequestMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable("id")int id){
int delete = userService.deleteUser(id);
if (delete>0){
return "deleteUser-ok";
}
return "deleteUser-no";
}
}
12、测试
http://localhost:8080/userList
http://localhost:8080/queryUser/1
http://localhost:8080/addUser
http://localhost:8080/updateUser
http://localhost:8080/deleteUser/7
官方文档
shiro、SpringSecurity:很像,除了类不一样
SpringSecurity是Springboot底层安全模块默认的技术选型,它可以实现强大的Web安全机制,只需要少数的spring-boot----spring-security
依赖,进行少量的配置,就可以实现
SpringBoot中的SpringSecurity依赖:
功能权限、访问权限、菜单权限…,我们使用过滤器,拦截器需要写大量的原生代码,这样很不方便
所以在网址设计之初,就应该考虑到权限验证的安全问题,其中Shiro、SpringSecurity使用很多
1、创建项目
创建完后pom.xml中有以下两个依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
2、将静态项目导入
spring-security静态项目 密码:web
3、application.properties中设置
spring.thymeleaf.cache=false
4、配置Controller层
@Controller
public class startController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/toLogin")
public String login(){
return "views/login";
}
//可以实现共用
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id")int id){
return "views/level1/"+id;
}
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id")int id){
return "views/level2/"+id;
}
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id")int id){
return "views/level3/"+id;
}
}
5、config文件夹下创建SecurityConfig
如果要使用JDBC就想看下面文档
[JDBC数据库编写文档](JDBC Authentication :: Spring Security)
@EnableWebSecurity:开启WebSecurity模式
两个单词:en是认证,or是权限
//认证和授权
@EnableWebSecurity
public class SecurityConfig{
//认证
@Bean
public UserDetailsService users(){
//密码加密
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
//设置用户
User.UserBuilder users = User.builder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("yingxu").password(encoder.encode("123456")).roles("vip1").build());
manager.createUser(users.username("admin").password(encoder.encode("123456")).roles("vip1","vip2").build());
manager.createUser(users.username("root").password(encoder.encode("123456")).roles("vip1","vip2","vip3").build());
return manager;
}
//授权
@Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能也特定人访问
//请求授权的规则
http
.authorizeHttpRequests(authorize -> authorize
// 请求放开
.antMatchers("/**").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3")
// 其他地址的访问均需验证权限
.anyRequest().authenticated()
)
//没有权限默认为登陆页
.formLogin(withDefaults());
return http.build();
}
}
旧版本与新版本对比
1、开启自动配置的注销的功能
.logout().logoutSuccessUrl("/");
//授权
@Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能也特定人访问
//请求授权的规则
http
.authorizeHttpRequests(authorize -> authorize
// 请求放开
.antMatchers("/**").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3")
// 其他地址的访问均需验证权限
.anyRequest().authenticated()
)
//没有权限默认为登陆页
.formLogin(withDefaults())
//注销,开启注销功能,跳转到首页
.logout().logoutSuccessUrl("/");
return http.build();
}
2、查看pom依赖
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity5artifactId>
dependency>
3、html页面引用
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
4、修改登录注销
<div sec:authorize="!isAuthenticated()"> 未登录显示
<div sec:authorize="isAuthenticated()"> 以登陆显示
<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="principal.username">span>
角色:<span sec:authentication="principal.authorities">span>
a>
div>
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="sign-out icon">i> 注销
a>
div>
div>
5、根据不同的权限显示不同的模块
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
有vip1权限时显示..........
div>
div>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
有vip2权限时显示..........
div>
div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
有vip3权限时显示..........
div>
div>
6、关闭csrf功能(登录不了的添加,一般不添加)
//防止网站工具
.csrf().disable()//关闭csrf功能,登录失败肯存在的原因~
//授权
@Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能也特定人访问
//请求授权的规则
http
.authorizeHttpRequests(authorize -> authorize
// 请求放开
.antMatchers("/**").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3")
// 其他地址的访问均需验证权限
.anyRequest().authenticated()
)
//没有权限默认为登陆页
.formLogin(withDefaults())
//防止网站工具
.csrf().disable()//关闭csrf功能,登录失败肯存在的原因~
//注销,开启注销功能,跳转到首页
.logout().logoutSuccessUrl("/");
return http.build();
}
1、开启记住我功能
//开启记住我功能 cookie
http.rememberMe();
//授权
@Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能也特定人访问
//请求授权的规则
http
.authorizeHttpRequests(authorize -> authorize
// 请求放开
.antMatchers("/**").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3")
// 其他地址的访问均需验证权限
.anyRequest().authenticated()
)
//没有权限默认为登陆页
.formLogin(withDefaults())
//注销,开启注销功能,跳转到首页
.logout().logoutSuccessUrl("/");
//开启记住我功能 cookie
http.rememberMe();
return http.build();
}
登录文档
1、修改login.html页面
<form th:action="@{/toLogin}" method="post">
<div class="field">
<input type="checkbox" name="remember"/>记住我
div>
2、定制登录页
//定制登录页
.formLogin(form -> form
.loginPage("/toLogin")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
)
loginProcessingUrl()
)3、自定义记住我
//开启记住我功能 cookie
//rememberMeParameter()自定义接收前端的参数
http.rememberMe().rememberMeParameter("remember");
4、需要关闭csrf功能(不然注销报错)
.csrf().disable()//关闭csrf功能
完整:
//授权
@Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能也特定人访问
//请求授权的规则
http
.authorizeHttpRequests(authorize -> authorize
// 请求放开
.antMatchers("/**").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3")
// 其他地址的访问均需验证权限
.anyRequest().authenticated()
)
//没有权限默认为登陆页
//.formLogin(withDefaults())
//定制登录页
.formLogin(form -> form
.loginPage("/toLogin")
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
)
//防止网站工具
.csrf().disable()//关闭csrf功能,登录失败肯存在的原因~
//注销,开启注销功能,跳转到首页
.logout().logoutSuccessUrl("/index");
//开启记住我功能 cookie
//rememberMeParameter()自定义接收前端的参数
http.rememberMe().rememberMeParameter("remember");
return http.build();
}
shiro官网:shiro官网
核心三大对象:用户Subject, 管理用户SecurityManager, 连接数据Realms
Subject
:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
SecurityManager
:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm
: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
整合shiro:
1、 创建项目
2、导入依赖
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.8.0version>
dependency>
3、编写静态页面
index.html页面:
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>首页h1>
<p th:text="${msg}">p>
<hr/>
<a th:href="@{/user/add}">adda> | <a th:href="@{/user/update}">updatea>
body>
html>
add.html页面:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>addh1>
body>
html>
update.html页面:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>updateh1>
body>
html>
login.html页面:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>登陆h1>
<hr>
<p th:text="${msg}" style="color: red">p>
<form th:action="@{/login}">
<p>用户名:<input type="text" name ="username">p>
<p>密码:<input type="text" name ="password">p>
<p><input type="submit">p>
body>
html>
4、编写控制层
@Controller
public class MyController {
@RequestMapping({"/","/index"})
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";
}
}
}
5、编写配置类
//自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了==>授权");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了==>认证");
//用户名和 密码
String name="root";
String password="123456";
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
if (!userToken.getUsername().equals(name)){
return null;//抛出异常
}
//密码认证,shiro做~
return new SimpleAuthenticationInfo("",password,"");
}
}
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean:第3步
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//关联DefaultWebSecurityManager
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user: 必须拥有记住我才能访问
perms:拥有对某个资源的权限
role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
//登录认证
/*
filterMap.put("/user/add","anon");
filterMap.put("/user/update","authc");
*/
//设置权限 *:通配符
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//登陆拦截,设置登录请求
bean.setLoginUrl("/toLogin");
return bean;
}
//DefaultWebSecurityManager:第2步
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建realm 对象,需要自定义类:第1步
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
6、运行
1、导入依赖
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.13version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.9.0version>
dependency>
2、创建application.yaml配置文件
spring:
datasource:
username: root
password: 123456
# 假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
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
# 底层开启功能,stat(sql监控),wall(防火墙)
filters: stat,wall,log4j2
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
3、创建pojo层
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
4、在mapper文件夹下编辑UserMapper
mapper–>UserMapper:
@Mapper
@Repository //Dao层
public interface UserMapper {
User queryUserByName(String name);
}
5、在resources资源目录下创建mapper文件夹
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yingxu.mapper.UserMapper">
<select id="queryUserByName" resultType="user">
select * from mybatis.user where name=#{name}
select>
mapper>
6、在application.properties配置文件中配置
# 让spring识别到mapper文件
mybatis.type-aliases-package=com.yingxu.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
7、Service层
public interface UserService {
User queryUserByName(String name);
}
8、Service层实现类
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
9、修改认证
//自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了==>授权");
return null;
}
//认证
@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;//抛出异常
}
//密码认证,shiro做~
return new SimpleAuthenticationInfo("",user.getPwd(),"");
}
}
10、在数据库增加perms字段
user:add
,user:update
两个值11、ShiroConfig类里授权
//授权,没有授权的话会跳转到未授权页面
//必须带有user:add才有权限
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
12、MyController类里添加
//未授权页面
@RequestMapping("/unauthorized")
@ResponseBody
public String unauthorized(){
return "未经授权无法访问此页面";
}
13、ShiroConfig类里添加
//未授权页面
bean.setUnauthorizedUrl("/unauthorized");
11-13步骤中ShiroConfig类里的ShiroFilterFactoryBean:第3步
完整版:
//ShiroFilterFactoryBean:第3步
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//关联DefaultWebSecurityManager
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user: 必须拥有记住我才能访问
perms:拥有对某个资源的权限
role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
//授权,没有授权的话会跳转到未授权页面
//必须带有user:add才有权限
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
//登录认证
//设置权限 *:通配符
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//登陆拦截,设置登录请求
bean.setLoginUrl("/toLogin");
//未授权页面
bean.setUnauthorizedUrl("/unauthorized");
return bean;
}
14、在User类中添加
private String perms;
字段@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
private String perms;
}
15、在UserRealm类中授权
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了==>授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//info.addStringPermission("user:add");
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User)subject.getPrincipal();//拿到User对象
//设置当前用户的权限
info.addStringPermission(currentUser.getPerms());
return info;
}
16、修改UserRealm类中认证
//密码认证,shiro做~
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
15-16步骤中UserRealm类完整版:
//自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了==>授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//info.addStringPermission("user:add");
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User)subject.getPrincipal();//拿到User对象
//设置当前用户的权限
info.addStringPermission(currentUser.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了==>认证");
//用户名和 密码
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
//数据库取值
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){
return null;//抛出异常
}
//密码认证,shiro做~
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
}
17、运行测试
http://localhost:8080/login?username=张三&password=123 有add的权限
http://localhost:8080/login?username=root&password=123456 有update的权限
http://localhost:8080/login?username=李四&password=123 都没有权限
1、导入依赖
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.1.0version>
dependency>
2、在ShiroConfig类最后面添加
@Bean
//整合ShiroDialect 用来整合shiro和thymeleaf
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
3、修改index.html页面
方法一:
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>首页h1>
<p th:text="${msg}">p>
<hr/>
<div shiro:guest="true">
<a th:href="@{/toLogin}">登录a>div>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">adda>div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">updatea>
div>
body>
html>
方法二:
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);
完整:
//认证
@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;//抛出异常
}
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);
//密码认证,shiro做~
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>首页h1>
<p th:text="${msg}">p>
<hr/>
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录a>div>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">adda>div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">updatea>
div>
body>
html>
前后端分离
产生的问题
解决方案
Swagger
SpringBoot集成Swagger => springfox,两个jar包
使用Swagger
要求:jdk 1.8 + 否则swagger2无法运行
步骤:
1、新建一个SpringBoot-web项目
2、添加Maven依赖
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-boot-starterartifactId>
<version>3.0.0version>
dependency>
3、编写HelloController,测试确保运行成功!
application.properties
添加spring.mvc.pathmatch.matching-strategy=ant_path_matcher
4、要使用Swagger,我们需要编写一个配置类-SwaggerConfig来配置 Swagger
@Configuration //配置类
@EnableSwagger2 // 开启Swagger2的自动配置
public class SwaggerConfig {
}
@Configuration //配置类
@EnableOpenApi // 开启Swagger3的自动配置
public class SwaggerConfig {
}
5、访问测试
1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger。
//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.OAS_30);
}
2、可以通过apiInfo()属性配置文档信息
//配置swagger信息=apiInfo
private ApiInfo apiInfo(){
ApiInfo apiInfo = new ApiInfoBuilder()
.title("潆勖的SwaggerAPI文档") // 标题
.description("坚持")// 描述
.version("v1.0")// 版本
.build();
return apiInfo;
}
3、Docket 实例关联上 apiInfo()
//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo());//重新配置了默认的文档信息
}
4、重启项目,访问测试 http://localhost:8080/swagger-ui/index.html 看下效果;
1、构建Docket时通过select()方法配置怎么扫描接口。
//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
//RequestHandlerSelectors 要扫描接口的方式
//指定要扫描的包 basePackage()
//any() 扫描全部
//withClassAnnotation() 扫描类上的注解,需要注解的反射对象
//withMethodAnnotation() 扫描方法上注解
//只会扫描有RestController注解的类.生成一个接口
.apis(RequestHandlerSelectors.basePackage("com.yingxu.controller"))
.build();//工厂模式
}
2、重启项目测试,由于我们配置根据包的路径扫描接口,所以我们只能看到一个类
3、除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式:
any() // 扫描所有,项目中的所有接口都会被扫描到
none() // 不扫描接口
// 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
withMethodAnnotation(final Class<? extends Annotation> annotation)
// 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
withClassAnnotation(final Class<? extends Annotation> annotation)
basePackage(final String basePackage) // 根据包路径扫描接口
4、除此之外,我们还可以配置接口扫描过滤:
//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.yingxu.controller"))
// 配置如何通过path过滤,即这里只扫描请求以/yingxu开头的接口
.paths(PathSelectors.ant("/yingxu/**"))//过滤路径
.build();//工厂模式
}
5、这里的可选值还有
any() // 任何请求都扫描
none() // 任何请求都不扫描
regex(final String pathRegex) // 通过正则表达式控制
ant(final String antPattern) // 通过ant()控制
1、通过enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了
//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.enable(false) //配置是否启用Swagger,如果是false,在浏览器将无法访问
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.yingxu.controller"))
// 配置如何通过path过滤,即这里只扫描请求以/yingxu开头的接口
//.paths(PathSelectors.ant("/yingxu/**"))//过滤路径
.build();//工厂模式
}
2、如何动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示?
//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(Environment environment){
// 设置要显示swagger的环境
Profiles of = Profiles.of("dev", "test");
// 判断当前是否处于该环境
// 通过 enable() 接收此参数判断是否要显示
boolean flag = environment.acceptsProfiles(of);
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.enable(flag) //配置是否启用Swagger,如果是false,在浏览器将无法访问
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.yingxu.controller"))
// 配置如何通过path过滤,即这里只扫描请求以/yingxu开头的接口
//.paths(PathSelectors.ant("/yingxu/**"))//过滤路径
.build();//工厂模式
}
3、添加开发环境和发布环境
spring.profiles.active=dev
server.port=8081
server.port=8082
4、可以在项目中增加一个dev的配置文件查看效果!
1、如果没有配置分组,默认是default。通过groupName()方法即可配置分组:
@Bean
public Docket docket(Environment environment){
// 设置要显示swagger的环境
Profiles of = Profiles.of("dev", "test");
// 判断当前是否处于该环境
// 通过 enable() 接收此参数判断是否要显示
boolean flag = environment.acceptsProfiles(of);
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.groupName("潆勖")//分组
.enable(flag) //配置是否启用Swagger,如果是false,在浏览器将无法访问
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.yingxu.controller"))
// 配置如何通过path过滤,即这里只扫描请求以/yingxu开头的接口
//.paths(PathSelectors.ant("/yingxu/**"))//过滤路径
.build();//工厂模式
}
2、重启项目查看分组
3、如何配置多个分组?配置多个分组只需要配置多个docket即可:
@Bean
public Docket docket1(){
return new Docket(DocumentationType.OAS_30).groupName("group1");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.OAS_30).groupName("group2");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.OAS_30).groupName("group3");
}
4、重启项目查看即可
1、新建一个实体类
@ApiModel("用户实体")
public class User {
@ApiModelProperty("用户名")
public String username;
@ApiModelProperty("密码")
public String password;
}
2、只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中:
//只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中
@RequestMapping("/getUser")
public User getUser(){
return new User();
}
3、重启查看测试
注:并不是因为@ApiModel这个注解让实体显示在这里了,而是只要出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。
@ApiModel为类添加注释
@ApiModelProperty为类属性添加注释
Swagger的所有注解定义在io.swagger.annotations包下
下面列一些经常用到的,未列举出来的可以另行查阅说明:
Swagger注解 | 简单说明 |
---|---|
@Api(tags = “xxx模块说明”) | 作用在模块类上 |
@ApiOperation(“xxx接口说明”) | 作用在接口方法上 |
@ApiModel(“xxxPOJO说明”) | 作用在模型类上:如VO、BO |
@ApiModelProperty(value = “xxx属性说明”,hidden = true) | 作用在类方法和属性上,hidden设置为true可以隐藏该属性 |
@ApiParam(“xxx参数说明”) | 作用在参数、方法和字段上,类似@ApiModelProperty |
在类上加
@Api(tags = "潆勖的类接口")
@RestController
public class MyController {
@ApiOperation("潆勖的接口")
@PostMapping("/yingxu")
public String yingxu(@ApiParam("这个名字会被返回")String username){
return username;
}
}
也可以给请求的接口配置一些注释
@ApiOperation("潆勖的接口")
@PostMapping("/yingxu")
public String yingxu(@ApiParam("这个名字会被返回")String username){
return username;
}
这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!
相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。
Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。
1、默认的 访问 http://localhost:8080/swagger-ui/index.html
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>3.0.0version>
dependency>
2、bootstrap-ui 访问 http://localhost:8081/doc.html
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>swagger-bootstrap-uiartifactId>
<version>1.9.6version>
dependency>
3、Layui-ui 访问 http://localhost:8081/docs.html
<dependency>
<groupId>com.github.caspar-chengroupId>
<artifactId>swagger-ui-layerartifactId>
<version>1.1.3version>
dependency>
4、mg-ui 访问 http://localhost:8081/document.html
<dependency>
<groupId>com.zyplayergroupId>
<artifactId>swagger-mg-uiartifactId>
<version>2.0.2version>
dependency>
1、创建一个service包
2、创建一个类AsyncService
异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。
编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;
@Service
public class AsyncService {
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("业务进行中....");
}
}
3、编写controller包
4、编写AsyncController类
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@GetMapping("/hello")
public String hello(){
asyncService.hello();
return "success";
}
}
5、访问http://localhost:8080/hello进行测试,3秒后出现success,这是同步等待的情况。
问题:我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:
6、给hello方法添加@Async注解;
//告诉Spring这是一个异步方法
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("业务进行中....");
}
SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;
@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTaskApplication.class, args);
}
}
7、重启测试,网页瞬间响应,后台代码依旧执行!
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。
两个注解:
在main方法上加入
Cron表达式
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒 | 0-59 | , - * / |
分 | 0-59 | , - * / |
小时 | 0-23 | , - * / |
日期 | 1-31 | , - * ? / L W C |
月份 | 1-12 或者 JAN-DEC | , - * / |
星期 | 1-7 或者 SUN-SAT | , - * ? / L C # |
年(可为空) | 留空, 1970-2099 | , - * / |
字段 | 详情 |
---|---|
秒 | 允许值范围: 0~59 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常 “*” 代表每隔1秒钟触发 “,” 代表在指定的秒数触发,比如"0,15,45"代表0秒、15秒和45秒时触发任务 “-” 代表在指定的范围内触发,比如"25-45"代表从25秒开始触发到45秒结束触发,每隔1秒触发1次 “/” 代表触发步进(step),“/“前面的值代表初始值(”“等同"0”),后面的值代表偏移量,比如"0/20"或者”/20"代表从0秒钟开始,每隔20秒钟触发1次,即0秒触发1次,20秒触发1次,40秒触发1次;"5/20"代表5秒触发1次,25秒触发1次,45秒触发1次;"10-45/20"代表在[10,45]内步进20秒命中的时间点触发,即10秒触发1次,30秒触发1次 |
分钟 | 允许值范围: 0~59 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常 “*” 代表每隔1分钟触发 “,” 代表在指定的分钟触发,比如"10,20,40"代表10分钟、20分钟和40分钟时触发任务 “-” 代表在指定的范围内触发,比如"5-30"代表从5分钟开始触发到30分钟结束触 发,每隔1分钟触发 “/” 代表触发步进(step),“/“前面的值代表初始值(”“等同"0”),后面的值代表偏移量,比如"0/25"或者”/25"代表从0分钟开始,每隔25分钟触发1次,即0分钟触发1次,第25分钟触发1次,第50分钟触发1次;"5/25"代表5分钟触发1次,30分钟触发1次,55分钟触发1次;"10-45/20"代表在[10,45]内步进20分钟命中的时间点触发,即10分钟触发1次,30分钟触发1次 |
小时 | 允许值范围: 0~23 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常 “*” 代表每隔1小时触发 “,” 代表在指定的时间点触发,比如"10,20,23"代表10点钟、20点钟和23点触发任务 “-” 代表在指定的时间段内触发,比如"20-23"代表从20点开始触发到23点结束触发,每隔1小时触发 “/” 代表触发步进(step),“/“前面的值代表初始值(”“等同"0”),后面的值代表偏移量,比如"0/1"或者”/1"代表从0点开始触发,每隔1小时触发1次;"1/2"代表从1点开始触发,以后每隔2小时触发一次 |
日期 | 允许值范围: 1~12 (JAN-DEC),不允许为空值,若值不合法,调度器将抛出SchedulerException异常 “*” 代表每个月都触发 “,” 代表在指定的月份触发,比如"1,6,12"代表1月份、6月份和12月份触发任务 “-” 代表在指定的月份范围内触发,比如"1-6"代表从1月份开始触发到6月份结束触发,每隔1个月触发 “/” 代表触发步进(step),“/“前面的值代表初始值(”“等同"1”),后面的值代表偏移量,比如"1/2"或者”/2"代表从1月份开始触发,每隔2个月触发1次;"6/6"代表从6月份开始触发,以后每隔6个月触发一次;"1-6/12"表达式意味着每年1月份触发 |
星期 | 允许值范围: 1~7 (SUN-SAT),1代表星期天(一星期的第一天),以此类推,7代表星期六(一星期的最后一天),不允许为空值,若值不合法,调度器将抛出SchedulerException异常 “*” 代表每星期都触发; “?” 与{日期}互斥,即意味着若明确指定{日期}触发,则表示{星期}无意义,以免引起冲突和混乱 “,” 代表在指定的星期约定触发,比如"1,3,5"代表星期天、星期二和星期四触发 “-” 代表在指定的星期范围内触发,比如"2-4"代表从星期一开始触发到星期三结束触发,每隔1天触发 “/” 代表触发步进(step),“/“前面的值代表初始值(”“等同"1”),后面的值代表偏移量,比如"1/3"或者”/3"代表从星期天开始触发,每隔3天触发1次;“1-5/2"表达式意味着在[1,5]范围内,每隔2天触发,即星期天、星期二、星期四触发 “L” 如果{星期}占位符如果是"L”,即意味着星期的的最后一天触发,即星期六触发,L= 7或者 L = SAT,因此,“5L"意味着一个月的最后一个星期四触发 “#” 用来指定具体的周数,”#“前面代表星期,”#"后面代表本月第几周,比如"2#2"表示本月第二周的星期一,"5#3"表示本月第三周的星期四,因此,“5L"这种形式只不过是”#"的特殊形式而已 |
年份 | 允许值范围: 1970~2099 ,允许为空,若值不合法,调度器将抛出SchedulerException异常 “*“代表每年都触发 ”,“代表在指定的年份才触发,比如"2011,2012,2013"代表2011年、2012年和2013年触发任务 ”-“代表在指定的年份范围内触发,比如"2011-2020"代表从2011年开始触发到2020年结束触发,每隔1年触发 ”/“代表触发步进(step),”/“前面的值代表初始值(”“等同"1970”),后面的值代表偏移量,比如"2011/2"或者”/2"代表从2011年开始触发,每隔2年触发1次 |
注意:除了{日期}和{星期}可以使用"?"来实现互斥,表达无意义的信息之外,其他占位符都要具有具体的时间含义,且依赖关系为:年->月->日期(星期)->小时->分钟->秒数
字段 | 详情 |
---|---|
* | “*”字符被用来指定所有的值。如:""在分钟的字段域里表示“每分钟”。 |
? | “?”字符只在日期域和星期域中使用。它被用来指定“非明确的值”。当你需要通过在这两个域中的一个来指定一些东西的时候,它是有用的。看下面的例子你就会明白。 月份中的日期和星期中的日期这两个元素时互斥的一起应该通过设置一个问号来表明不想设置那个字段。 |
- | “-”字符被用来指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点”。 |
, | “,”字符被用来指定另外的值。如:“MON,WED,FRI”在星期域里表示”星期一、星期三、星期五”。 |
/ | “/”字符用于指定增量。如:“0/15”在秒域意思是每分钟的0,15,30和45秒。“5/15”在分钟域表示每小时的5,20,35和50。符号“”在“/”前面(如:/10)等价于0在“/”前面(如:0/10)。记住一条本质:表达式的每个数值域都是一个有最大值和最小值的集合,如:秒域和分钟域的集合是0-59,日期域是1-31,月份域是1-12。字符“/”可以帮助你在每个字符域中取相应的数值。如:“7/6”在月份域的时候只有当7月的时候才会触发,并不是表示每个6月。 |
L | 字符“L”是‘last’的省略写法可以表示day-of-month和day-of-week域,但在两个字段中的意思不同,例如day-of-month域中表示一个月的最后一天。如果在day-of-week域表示‘7’或者‘SAT’,如果在day-of-week域中前面加上数字,它表示一个月的最后几天,例如‘6L’就表示一个月的最后一个星期五。 |
W | 字符“W”只允许日期域出现。这个字符用于指定日期的最近工作日。例如:如果你在日期域中写 “15W”,表示:这个月15号最近的工作日。所以,如果15号是周六,则任务会在14号触发。如果15好是周日,则任务会在周一也就是16号触发。如果是在日期域填写“1W”即使1号是周六,那么任务也只会在下周一,也就是3号触发,“W”字符指定的最近工作日是不能够跨月份的。字符“W”只能配合一个单独的数值使用,不能够是一个数字段,如:1-15W是错误的。 |
“L”和“W”可以在日期域中联合使用,LW表示这个月最后一周的工作日。 | |
# | 字符“#”只允许在星期域中出现。这个字符用于指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周)。“2#1”表示本月第一周的星期一。“4#5”表示第五周的星期三。 |
C | 字符“C”允许在日期域和星期域出现。这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。如:日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)。 |
1、创建一个ScheduledService
每两秒执行一次:
@Service
public class ScheduledService {
//秒 分 时 日 月 周几
//注意cron表达式的用法;
@Scheduled(cron = "0/2 * * * * ?")
public void hello(){
System.out.println("hello.....");
}
}
2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能
@EnableAsync//开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class Springboot09AsyncApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot09AsyncApplication.class, args);
}
}
3、我们来详细了解下cron表达式;
cron表达式
4、常用的表达式
(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触发
邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持
测试:
1、引入pom依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
看它引入的依赖,可以看到 jakarta.mail
<dependency>
<groupId>com.sun.mailgroupId>
<artifactId>jakarta.mailartifactId>
<version>1.6.7version>
<scope>compilescope>
dependency>
2、配置文件:
application.properties
spring.mail.username=你的qq号@qq.com
spring.mail.password=你的qq授权码
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true
获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务
3、Spring单元测试
@SpringBootTest
class Springboot09AsyncApplicationTests {
@Autowired
@Resource
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
//邮件设置1:一个简单的邮件
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("hello,潆勖");//邮件标题
mailMessage.setText("加油学习!");//内容
mailMessage.setTo("[email protected]");//收件人
mailMessage.setFrom("[email protected]");//发件人
mailSender.send(mailMessage);
}
@Test
void contextLoads2() throws MessagingException {
//邮件设置2:一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组装 true:启用复杂的邮件
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject("潆勖,你好");//邮件标题
helper.setText("加油学习呀!",true);//内容
//发送附件
helper.addAttachment("1.jpg",new File("G:\\JavaSE\\img\\1.jpg"));
helper.setTo("[email protected]");//收件人
helper.setFrom("[email protected]");//发件人
mailSender.send(mimeMessage);
}
}
查看邮箱,邮件接收成功!
在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
分布式系统(distributed system)是建立在网络之上的软件系统。
首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。
在Dubbo的官网文档有这样一张图
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:
1、性能扩展比较难
2、协同开发问题
3、不利于升级维护
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点:公用模块无法重复利用,开发性的浪费
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架(RPC)**是关键。
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;
推荐阅读文章:https://www.jianshu.com/p/2accc2840a1b
RPC基本原理
步骤解析:
RPC两个核心模块:通讯,序列化。
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
dubbo官网 :dubbo官网
1.了解Dubbo的特性
2.查看官方文档
dubbo基本概念
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
调用关系说明
服务容器负责启动,加载,运行服务提供者。
服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者在启动时,向注册中心订阅自己所需的服务。
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
点进dubbo官方文档,推荐使用Zookeeper 注册中心:Zookeeper 注册中心
什么是zookeeper呢?可以查看官方文档
1、下载zookeeper :地址, 最新版!解压zookeeper
2、运行/bin/zkServer.cmd ,初次运行会报错,没有zoo.cfg配置文件;
可能遇到问题:闪退 !
解决方案:编辑zkServer.cmd
文件末尾添加pause
。这样运行出错就不会退出,会提示错误信息,方便找到原因。
3、修改zoo.cfg配置文件
将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。
注意几个重要位置:
dataDir=./ 临时数据存储的目录(可写相对路径)
clientPort=2181 zookeeper的端口号
修改完成后再次启动zookeeper
4、使用zkCli.cmd测试
ls /
:列出zookeeper根下保存的所有节点[zk: 127.0.0.1:2181(CONNECTED) 4] ls /
[zookeeper]
create –e /yingxu 123
:创建一个yingxu节点,值为123get /yingxu
:获取/yingxu节点的值再来查看一下节点
dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。
但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。
我们这里来安装一下:
1、下载dubbo-admin
地址 :dubbo-admin
2、解压进入目录
修改 dubbo-admin\dubbo-admin-server\src\main\resources\application.properties 指定zookeeper地址
# 账号密码
admin.root.user.name=root
admin.root.user.password=root
# 注册中心的地址
admin.registry.address=zookeeper://127.0.0.1:2181
admin.config-center=zookeeper://127.0.0.1:2181
admin.metadata-report.address=zookeeper://127.0.0.1:2181
3、在项目目录下打包dubbo-admin
mvn clean package -D maven.test.skip=true
第一次打包的过程有点慢,需要耐心等待!直到成功!
4、执行 dubbo-admin\dubbo-admin-distribution\target 下的dubbo-admin-0.5.0.jar
dubbo-admin-0.5.0.jar下载 密码:java
java -jar dubbo-admin-0.5.0.jar
【注意:zookeeper的服务一定要打开!】
【注意:如果运行了 zookeeper,8080 端口会被占用,启动失败,需要在 zoo.cfg
加上 admin.serverPort=8888
】
执行完毕,访问一下 http://localhost:8080/, 这时候需要输入登录账户和密码,都是默认的root-root;
登录成功后,查看界面
安装完成!
1. 启动zookeeper !
2. IDEA创建一个空项目;
3.创建一个Sping模块,实现服务提供者:provider-server , 选择web依赖即可
4.项目创建完毕,写一个服务,比如卖票的服务;
提供者
:
编写接口
public interface TicketService {
public String getTicket();
}
编写实现类
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "《Java》";
}
}
application.properties配置
server.port=8001
5.创建一个模块,实现服务消费者:consumer-server , 选择web依赖即可
6.项目创建完毕,写一个服务,比如用户的服务;
消费者
:
编写service
public class UserService {
//需要去拿去注册中心的服务
}
application.properties配置
server.port=8002
需求:现在用户想使用买票的服务,要怎么弄呢 ?
1、将服务提供者注册到注册中心,需要整合Dubbo和zookeeper,所以需要导包
从dubbo官网进入github,看下方的帮助文档,找到dubbo-springboot,找到依赖包
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>3.1.1version>
dependency>
zookeeper的包去maven仓库下载,zkclient;
<dependency>
<groupId>com.github.sgroschupfgroupId>
<artifactId>zkclientartifactId>
<version>0.1version>
dependency>
【新版的坑】zookeeper及其依赖包,解决日志冲突,还需要剔除日志依赖;
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>5.3.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>5.3.0version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.8.0version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
2、在springboot配置文件中配置dubbo相关属性!
server.port=8001
#当前应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#扫描指定包下服务
dubbo.scan.base-packages=com.yingxu.provider.service
3、在service的实现类中配置服务注解,发布服务!注意导包问题
//zookeeper:服务注册与发现
@DubboService //将服务发布出去,可以被扫描到,在项目一启动就自动注册到注册中心
@Component //放在容器中
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "《Java》";
}
}
逻辑理解 :应用启动起来,dubbo就会扫描指定的包下带有@component注解的服务,将它发布在指定的注册中心中!
1、导入依赖,和之前的依赖一样;
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>3.1.1version>
dependency>
<dependency>
<groupId>com.github.sgroschupfgroupId>
<artifactId>zkclientartifactId>
<version>0.1version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>5.3.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>5.3.0version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.8.0version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
2、配置参数
server.port=8002
#当前应用名字
dubbo.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
3、本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;
4、完善消费者的服务类
@DubboService
public class UserService {
//需要去拿去注册中心的服务
@DubboReference //引用,Pom坐标,可以定义路径相同的接口名
TicketService ticketService;
public void buyTicket(){
String ticket = ticketService.getTicket();
System.out.println("在注册中心买到"+ticket);
}
}
5、测试类编写;
@SpringBootTest
class ConsumerServerApplicationTests {
@Autowired
UserService userService;
@Test
void contextLoads() {
userService.buyTicket();
}
}
1. 开启zookeeper
2. 打开dubbo-admin实现监控【可以不用做】
3. 开启服务者
4. 消费者消费测试
ok , 这就是SpingBoot + dubbo + zookeeper实现分布式开发的应用,其实就是一个服务拆分的思想;
步骤:
前提:zookeeper服务已开启
提供者提供服务:
消费者如何消费