SpringBoot
心态:如何学习新东西,如何持续学习,如何关注这个行业。
面试中的软实力!
聊天+举止+谈吐+见解。
学习底层虽然效果来得慢,但它是谈资!
很多人说不学spring直接学springboot也是可以的,但也有人说学习要渐进。我选择了后者,在花了一个学期的时间学习前置知识,比如数据库sql,spring思想等,最后用SSM的知识做了一个小项目。在学习springboot的时候,我越发觉得前面那些知识的重要,通过对比学习更体会到springboot对spring的简化。
一、基本概念
-
简化的原理:约定大于配置
-
SpringBoot不是新的框架,它默认配置了很多框架的使用方式,就像Maven整合了所有的jar包,SpringBoot整合了所有的框架
-
优点:
- 更快入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式窗口简化web项目
- 没有冗余代码生成和xml配置的要求
-
微服务:
从all in one(一个war包完整复制到各个服务器,分流),到把各个模块放到拆分到各个服务器,各个模块互不影响,高内聚,低偶合。和iso七层模型有异曲同工之妙。
-
微服务文章:
http://martinfowler.com/articles/microservices.html
https://www.cnblogs.com/liuning8023/p/4493156.html
二、第一个SpringBoot程序
1、创建工程方式
- 在官网中下载zip包,然后用idea打开。https://start.spring.io/(记得勾选web依赖)
- 在idea快速创建。实际上,idea是集成了官网,是idea帮你在官网创建了。
2、小玩意
- 改变端口号:server.port=8081
- 改变初始图标:在resources根目录下,创建banner.txt文件,在https://www.bootschool.net/ascii-art网站得到自己喜欢的ascii图样,复制到txt文件内,就可以直接改变。
三、原理集合
1、自动装配原理
(更准确地说是@SpringBootApplication注解做了什么,其中包含了自动装配的原理)
- 根据注解层次来看就行了。
- 第一级,分三个部分,自动扫描,说明组件,及最重要的是它是一个自动配置。
- 在自动配置分支下,@import导入了一个最重要的自动配置选择器。
- 选择器中,有一个得到候选配置的方法,会在spring-boot-autoconfigure-2.4.2.jar的META-IF中的spring.factories文件中找到所有的自动配置类
- 自动配置类不一定生效,有@ConditionalOnxxx注解限制生效。这里也是starter的原理由来
- 每个xxxAutoConfiguration类必定会搭配一个xxxProperties类,而xxxProperties类中的默认配置可以通过yaml主配置文件更改。
2、run主程序启动原理
开启了一个服务,不是简单运行一个方法!
- 推断应用的类型是普通的项目还是web项目
- 查找并加载所有可用的初始化器,设置到initializers属性中
- 找出所有的应用程序监听器,设置到listeners属性中
- 推断并设置main方法的定义类,找到运行的这一类
3、配置文件原理
这也是springboot自动装配原理的一部分。
三者层次关系如下:
- 一般在构造方法中或者@EnableConfigurationProperties注解中可以找到对应的xxxProperties类
- 在springboot装配xxxAutoConfiguration类的时候,会先找到xxxProperties类中的默认配置
- 如果在配置文件中有改变默认配置,则加载配置文件中的配置
- 在xxxProperties类中,肯定有一个这样的类似注解:@ConfigurationProperties(prefix = “server”),作用是识别配置文件中的前缀"server", 把这个类绑定到配置文件中相关前缀。(具体看yaml赋值应用) 如server.port = 8081, 则是设置了xxxProperties类中的port属性。(server是自定义的前缀),里面调用了setPort()方法。
4、MVC扩展原理
- 组件配置法:其实,如果用户没有配置bean(组件),那么会使用默认的bean配置;如果用户配置了,就用用户配置的;如果允许存在多个bean,那么会组合起来用。
- 重写接口法:重写WebMvcConfigurer接口方法,可以扩展
- 不能加@EnableWebMvc。原因如下
- @EnableWebMvc上一级注解中,有@Import({DelegatingWebMvcConfiguration.class}),即导入了DelegatingWebMvcConfiguration类
- 而WebMvcAutoConfiguration中有@ConditionalOnMissingBean(WebMvcConfigurationSupport.class),即WebMvcConfigurationSupport类不存在bean中,自动配置类才生效。而DelegatingWebMvcConfiguration是WebMvcConfigurationSupport的子类
- 结论:加了@EnableWebMvc会使mvc自动配置全面失效
- 公司自研starter,思路之一就是利用@ConditionalOnxxx注解
- 在springboot中,有非常多的xxxConfiguration(如MyMvcConfiguration),会帮助我们进行扩展配置,只要看到这个东西,就要注意他改变了默认的什么东西
//配置类,并实现了mvc的配置接口
@Configuration
public class MyMvcConfiguration implements WebMvcConfigurer {
//把特定的功能类添加到bean即可
@Bean
public ViewResolver viewResolver() {
return new MyViewResolver();
}
//要扩展,只需写一个类,实现了某个特定的接口
public static class MyViewResolver implements ViewResolver {
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
//alt+insert
//方法二:实现接口
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("index");
}
}
四、yaml基础
1、基本语法
基本语法:对空格的要求极其严格
key:空格value
对象:
#写法一
student:
name: hao
age: 3
#写法二
student: {name: hao,age: 3}
#propetiest,只能保存键值对
student.name=hao
student.age=3
数组
pets:
- cat
- dog
- pig
pets: [cat,dog,pig]
2、赋值应用
- 应用一:把配置写在yaml中,然后注入到配置类
- 应用二:把yaml中的值写入实体类中
- 总的来说,都是把类和配置文件绑定在一起
<!--导入这个依赖后,写配置会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
yaml文件:
dog:
name: nick
age: 3
person:
name: tony
age: 30
birthday: 2020/03/01
list:
- love
- lucky
- apple
#这里注意一下map的绑定方式
map: {k1: v1,k2: v2,k3: v3}
dog:
name: nickkk
age: 3
Person:
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private int age;
private Date birthday;
private List<String> list;
private Map<String, Object> map;
private Dog dog;
}
Dog:
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "dog")
public class Dog {
private String name;
private int age;
}
通过configurationProperties注解和prefix值来指定注入,然后用Autowried来绑定。
而原先spring是采用@value("")来实现的。
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Dog {
@Value("${dog.name}")//这里用特定值的绑定
private String name;
private int age;
}
3、其它注解
- @Value(“spring的EL表达式”),从配置文件中拿值
- @PropertySource(value = “classpath:文件名”),加载指定的配置文件。配合@value来使用
五、校验
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
在实体类上@Validated,在要校验的值中加上自己要校验的注解。具体的在导入的依赖包中的javax-validation-constraints中 自己查看
六、配置文件优先级及多环境
四个位置可以放配置文件:(优先级从大到小)
- 项目根目录/config/application.yaml
- 项目根目录下/application.yaml
- resources(类路径)/config/applicaiton.yaml
- resources(类路径)/application.yaml
得出结论:springboot默认给我们的是最低级的配置文件!这样可以在不同的生产环境中覆盖!
在不同环境中:
可以有三个properties配置文件:
- application.properties
- application-dev.properties
- application-test.properties
多环境配置:
在默认配置文件中:
spring.profiles.active=dev
还可以用yaml的多模块性:
server:
port: 8081
spring:
profiles:
active: dev #在这里选择哪个环境,不写就默认
---
server:
port: 8082
spring:
profiles: dev #取个名字
---
server:
port: 8083
profiles: test
七、实战
1、静态资源位置
在WebMvcAutoConfiguration类中获得以下信息:
- webJars
- resources/public, static, resources, /**
- 优先级:resources>static(默认)>public
- 自定义:spring.mvc.static-path-pattern=classpath:/路径
补充:src目录(编译后生成的clases目录)和resources目录(会被直接放在classes目录下)都是classpath路径
2、首页问题
- 首页名为index.html要放在public,resources,static
- templates目录要通过controller才能访问到
3、thymeleaf语法
<div th:text="${message}"></div>
<div th:utext="${message}"></div>
<div th:each="user:${users}">
[[${user}]]
</div>
<div th:each="user:${users}" th:text="${user}">
</div>
八、真正的实战
1、国际化
- 在resources目录下创建i18n,下面创建login.properties和login_zh_CN.properties和login_en_US.properties
- 在主配置文件中配置spring.mvc.message.base=i18n.login
- 自己实现一个LocateResover,并注入到bean中
- 用thymeleaf的#语法,直接读(因为在主配置已经配置过了)
2、自己配置controller
在@Configuration类即java配置类中,实现WebMvcConfigurer, 然后重写addViewControllers,
里面registry.addViewController("/hello").setViewName(“index”)
3、自定义404
在templates文件夹下,创建一个error文件夹,然后创建404.html, 报404错误后,直接跳到这里。如果是500错误,则创建一个500.html文件
九、数据源
1、jdbc
启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
dataSource接口
@SpringBootTest
class Boot1ApplicationTests {
//默认数据源为com.zaxxer.hikari.pool.HikariProxyConnection
//得到这个接口,那么所有的操作就和普通的jdcb一样了
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
Connection connection = dataSource.getConnection();
System.out.println("数据源为:" + connection.getClass());
Statement statement = connection.createStatement();
String sql = "select * from music_shop.song";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
System.out.println(resultSet.getString(2));
}
}
}
Spring模板
在之前的Javaweb学习中,学习了手动封装JdbcTemplate,其好处是通过(sql语句+参数)模板化了编程。而真正的JdbcTemplate类,是Spring框架为我们写好的。它是 Spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。除了JdbcTemplate,spring 框架还为我们提供了很多的操作模板类。
@SpringBootTest
class Boot1ApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() throws SQLException {
String sql = "select * from music_shop.song";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
for (Map<String, Object> map : maps) {
System.out.println(map);
}
}
}
2、Druid初探
利用DataSource接口
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.2</version>
</dependency>
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/music_shop?useSSL=true&useUnicode=true&charactEncoding=true
driver-class-name: com.mysql.jdbc.Driver
#把数据源类型更改一下就行,其他所有操作不变
type: com.alibaba.druid.pool.DruidDataSource
@SpringBootTest
class Boot1ApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
Connection connection = dataSource.getConnection();
System.out.println(connection.getClass());
//class com.alibaba.druid.pool.DruidPooledConnection
}
}
自定义配置
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/music_shop?useSSL=true&useUnicode=true&charactEncoding=true
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
# druid 配置
dbType: mysql # 指定数据库类型 mysql
initialSize: 5 # 启动初始化连接数量
minIdle: 5 # 最小空闲连接
maxActive: 20 # 最大连接数量(包含使用中的和空闲的)
maxWait: 60000 # 最大连接等待时间 ,超出时间报错
timeBetweenEvictionRunsMillis: 60000 # 设置执行一次连接回收器的时间
minEvictableIdleTimeMillis: 300000 # 设置时间: 该时间内没有任何操作的空闲连接会被回收
validationQuery: select 'x' # 验证连接有效性的sql
testWhileIdle: true # 空闲时校验
testOnBorrow: false # 使用中是否校验有效性
testOnReturn: false # 归还连接池时是否校验
poolPreparedStatements: false # mysql 不推荐打开预处理连接池
filters: stat,wall,logback #设置过滤器 stat用于接收状态,wall防止sql注入,logback说明使用logback进行日志输出
userGlobalataSourceStat: true # 统计所有数据源状态
connectionProperties: druid.stat.mergSql=true;druid.stat.slowSqlMillis=500 # sql合并统计 设置慢sql时间为500,超过500 会有记录提示
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
// 注册后台监控界面
@Bean
public ServletRegistrationBean servletRegistrationBean(){
// 绑定后台监控界面的路径 为localhost/druid
ServletRegistrationBean bean=new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
Map<String,String>map=new HashMap<>();
// 设置后台界面的用户名
map.put("loginUsername","guoxw");
//设置后台界面密码
map.put("loginPassword","123456");
// 设置那些ip允许访问," " 为所有
map.put("allow","");
// 不允许该ip访问
map.put("deny","33.32.43.123");
bean.setInitParameters(map);
return bean;
}
// 监听获取应用的数据,filter用于收集数据,servlet用于数据展示
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean bean=new FilterRegistrationBean();
// 设置过滤器
bean.setFilter(new WebStatFilter());
// 对所有请求进行过滤捕捉监听
bean.addUrlPatterns("/*");
Map<String,String>map=new HashMap<>();
// 排除 . png .js 的监听 用于排除一些静态资源访问
map.put("exclusions","*.png,*.js");
bean.setInitParameters(map);
return bean;
}
3、Mybatis
重要配置
#mybatis配置
mybatis:
type-aliases-package: com.hao.pojo #给类起别名
mapper-locations: classpath:mybatis/mapper/*.xml #说明mapper.xml文件位置
mapper接口
@Mapper//说明是一个mybatis mapper类
@Repository//交给spring管理
public interface SongMapper {
List<Song> getAllSongs();
}
xml文件
resources目录下的mybatis下的mapper目录下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hao.mapper.SongMapper">
<select id="getAllSongs" resultType="Song">
select * from song;
</select>
</mapper>
十、安全
1、SrpingSecurity
Spring Security是针对Spring项目的安全框架,也是SpringBoot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,仅需要引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理。
- WebSecurityConfigurerAdapter: 自定义Security策略
- AuthenticationManagerBuilder: 自定义认证策略
Authentication认证 Authorization授权
用户认证与授权
//一定不能忘了加这个注解,否则配置无效
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//添加请求认证
http.authorizeRequests()
.antMatchers("/", "/index", "/index.html").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//定制登录功能
http.formLogin()//没认证,则跳到登录页
.loginPage("/toLogin")//自定义首页
.usernameParameter("user")//首页中接收用户名的参数名即name属性
.passwordParameter("password")//首页中接收密码的参数名即name属性
.loginProcessingUrl("login");//登录时,表单要发送的请求地址
//注销,注销成功后跳到首页
http.logout().logoutSuccessUrl("/");
//记住我。如果自定义了首页,则自定义的首页中可以加入一个checkbox, 并把name属性改为和下面指定的一样就可以了。
//如果没有自定义首页,则不用加.remeberMeParameter方法
http.rememberMe().rememberMeParameter("rememberMe");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//从内存中取账号密码
//密码在新版本中要使用编码,否则报错
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("zhong").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1", "vip2", "vip3")
.and()
.withUser("wen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1")
.and()
.withUser("hao").password(new BCryptPasswordEncoder().encode("123456")).roles("vip3");
}
}
2、Shiro
十一、Swagger
优点:
- 可以给接口或者实体类中标注注释
- 接口文档可以实时更新
- 可以在线测试
- 可以分组,后端人员职责明确。前端人员定位错误更快。
1、初始
maven:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
加入配置类:
@Configuration
@EnableSwagger2
public class SwaggerConfig {
}
2、配置主界面信息
@Configuration
@EnableSwagger2
public class SwaggerConfig {
//配置了Swagger的Docket的bean实例
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
Contact contact = new Contact("wenhao", "", "");
return new ApiInfo(
"API文档",
"你介绍",
"v1.0",
"http://xxx",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE",
new ArrayList<>()
);
}
}
3、获得生产环境
Profiles profiles = Profiles.of("dev", "test");
boolen flag = environment.accptsProfiles(profiles);
4、扫描接口
//配置了Swagger的Docket的bean实例
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
//select下面的是用来扫描接口用的
.select()
//RequestHandlerSelectors有多种方法可以选择扫描方式
.apis(RequestHandlerSelectors.basePackage("com.hao.controller"))
//过滤接口路径
.paths(PathSelectors.ant("/hello"))
.build();
}
5、标注注解
实体类标注:
注意:控制器接口方法中,必须要用到实体类才能被扫描到。
原因:swagger本身就是为接口服务的,接口中没有实体类,那还扫描干嘛?
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("歌曲实体类")
public class Song {
@ApiModelProperty("歌曲实体类的名字")
private String name;
}
标注接口:
@ApiOperation("欢迎控制器接口方法")
@RequestMapping("/")
//如果参数是标注在实体类上,swagger会把实体类解析成实体类的字段
//比如,song有一个name字段,那么,会显示需要name参数
public String hello(@ApiParam("歌曲对象")Song song, Model model) {
model.addAttribute("message", "hellowo");
return "index";
}
6、分组
创建多个docket实例对象即可
@Bean
public Docket docketA() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("A");
}
@Bean
public Docket docketB() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("B");
}
十二、任务
1、异步任务
@Async //标注在要开启异步的方法上
@EnableAsync //标注在主入口上,开启异步注解功能
2、邮件任务
yaml:
spring:
mail:
username: xxx@qq.com
password: xxxx
host: smtp.qq.com
properties: #QQ邮箱特有的配置属性
mail:
smtp:
ssl:
enable: true
简单邮件:
@Test
void contextLoads() throws SQLException {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject("这是主题:xxx");
simpleMailMessage.setText("这是内容:xxx");
simpleMailMessage.setTo("xx@qq.com");
simpleMailMessage.setFrom("xxx@qq.com");
javaMailSender.send(simpleMailMessage);
}
复杂邮件:
@Test
void contextLoads() throws SQLException, MessagingException {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
//true代表能发送复杂文件
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
mimeMessageHelper.setSubject("这是主题");
mimeMessageHelper.setText("<h style='color:purple'> 能解析html </h>", true);
mimeMessageHelper.addAttachment("这是附件的名称.jpg", new File("C:\\Users\\aj\\Desktop\\123.JPG"));
mimeMessageHelper.setTo("xxx@qq.com");
mimeMessageHelper.setFrom("xxx@qq.com");
javaMailSender.send(mimeMessage);
}
3、定时任务
两个接口:
TaskScheduler 任务调度者
TaskExecutor 任务执行者
两个注解:
@EnableScheduling //标注在程度主入口,开启定时功能
@Scheduled(cron = "crom表达式")//表示什么时候执行
十三、分布式
四个核心问题:
- 这么多服务,客户端该如何去访问
- 这么多服务,服务之间如何进行通信
- 这么多服务,如何治理
- 服务挂了,怎么办
**问题本质:**网络不可靠
解决的方法:
- API网关,服务路由
- HTTP,RPC框架,异步调用
- 服务注册与发现,高可用
- 熔断机制,服务降级