Springboot框架
- 微服务阶段
javase:OOP
mysql:持久化
html+css+js+jquery+框架
javaweb:独立开发MVC三层架构的网站
SSM:框架;简化我们的开发流程,配置也开始较为复杂
war:tomcat运行
Spring再简化:SpringBoot -jar:内嵌tomcat;微服务·架构!
服务越来越多:springcloud
- SpringBoot学习路线
SpringBoot
- 是什么
- 如何配置编写yaml
- 自动装配的原理
- 集成web开发:业务的核心
- 分布式开发:Dubbo+zookeeper
- swagger:接口文档
- 任务调度
- SpringSecurity:Shiro
SpringCould
- 微服务
- springcould入门
- Resful风格
- Eureka
- Ribbon
- Feign
- HyStrix
- Zuul路由网关
- SpringCloud config:git
- 原理初探
自动配置:
pom.xml
- spring-boot-dependencies:核心依赖在父工程中
- 我们写或引入一些Springboot依赖的时候,不需要指定版本,就因为由这些版本仓库
启动器
```xml
org.springframework.boot spring-boot-starter 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- **启动器:**Springboot的启动场景
- 比如spring-boot-starter-web,他会帮我们导入web环境的所有依赖!
- springboot会将
- 注解
- ```java
@SpringBootApplication:标注这个类是一个springboot的应用:启动类下的所有资源被导入
- SpringBootConfiguration:Springboot配置
- @Configuration:Spring的配置类
- @Component:说明这也是一个Spring的组件
- @EnableAutoConfiguration:自动配置
- @AutoConfigurationPackage:自动配置包
- @Import(AutoConfigurationPackages.Registrat.class):自动配置"包注册"
- @Import(AutoConfigurationImportSelector.class):自动配置导入选择
//获取所有配置
List<String> configurations=getCandidateConfigurations(annotationMetadata,attribuates);总结:springboot所有的自动配置都是在启动的时候扫面并加载:spring.factories所有的自动配置类都在这里,但不一定生效,要判断条件是否成立,只有导入对应的star,就有对应的启动器,有了启动器,自动配置就会生效,然后就配置成功。
步骤
- springboot启动,从类路径下/META-INF/spring.factories获取指定的值;
- 将这些自动配置的类导入容器,自动装配生效
- 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfig-2.2.0.RELEASE.JAR中
- 他会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器
- 了解主启动类怎么运行
开始执行run()方法
- 推断应用类型是普通项目还是Web项目
- 查找并加载所有可用初始化器,设置带initializers属性中
- 找出所有的应用程序监听器,设置到listeners属性中
- 推断并设置main方法的定义类,找到运行的主类
- yaml语法
语法结构:key: 空格 value
1 | #对象 |
- 给属性赋值的几种方式
1 | server: |
1 |
|
- JSR303校验
1 | 被注释元素必须为空 |
- 多环境切换及配置文件位置
配置文件可以存放的位置:
- file:./config/
- file:./
- classpath:/config/
- classpath:/
1 | #多个properties文件,配置文件的指定 |
1 | #yaml文件中环境的切换 |
- SpringBoot Web开发
1 | 要解决的问题: |
- 在springboot中,我们可以使用以下方式处理静态资源
- webjars localhost:8080/webjars/
- public,static,/**,resources localhost:8080/
- 优先级:resource>static(默认)>public
- Thymeleaf模板引擎
templetes目录下的所有页面,只能通过controller来跳转
1
2
3
4
5
6
7
8
9<!--导入模板引擎依赖-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>Thymeleaf的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!--1.导入头文件xmlns:th="http://www.thymeleaf.org-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" type="text/css" media="all"
href="../../css/gtvg.css" th:href="@{/css/gtvg.css}"/>
</head>
<body>
<h1>Test</h1>
<p th:text="#{home.welcome}">Welcome to our grocery store!</p>
</body>
</html>
Thymeleaf语法:
- 变量:${……}
- 选择表达式:*{……}
- 消息:#{……}
- 链接:@{……}
- Framgent Expressions:~{……}
- 包含:th:insert th:replace
- 遍历:th:each
- 条件判断:th:if th:unless th:switch th:case
- 定义变量:th:object th:with1
2
3
4
5
6
7
public class MymvcController implements WebMvcConfigurer {
//静态资源过滤器
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
}
- MVC配置原理
自定义视图解析器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MvcConfig implements WebMvcConfigurer {
//自定义一个自己的视图解析器MyViewResolver
public static class MyViewResolver implements ViewResolver{
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
//ViewResolver 实现了视图解析器接口的类,我们就可以把他看作视图解析器
public ViewResolver mvcConfig(){
return new MyViewResolver();
}
}扩展springmvc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27@Configuration
public class MymvcController implements WebMvcConfigurer {
//自定义视图解析器
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
@Bean//注入容器,自定义国际化组件生效了
public LocaleResolver MyLocalResolver(){
return new MyLocalResolver();
}
//自定义拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {//拦截哪些
registry.addInterceptor(new LoginHandleInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/css/*","/js/**","/img/**");//排除哪些
}
@Override//自定义资源过滤器
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
}
- 国际化
首页配置:所有页面静态资源都需要thymeleaf接管
页面国际化:
- 配置i18n文件
- 如果需要在项目中进行按钮自动切换,需要自定义组件LocalResolver
- 需要将自己写的组件配置到spring容器中@Bean
- #{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//自定义国际化
public class MyLocalResolver implements LocaleResolver {
//解析请求
public Locale resolveLocale(HttpServletRequest request) {
//获取参数中的请求连接
String language=request.getParameter("l");
Locale local=Locale.getDefault();//如果没有使用默认
if(!StringUtils.isEmpty(language)){
String[] split=language.split(language);
local=new Locale(split[0],split[1]);//国家。地区
}
return local;
}
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14//自定义视图解析器
public class MymvcController implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
//注入容器
public LocaleResolver MyLocalResolver(){
return new MyLocalResolver();
}
}1
2
3
4
5
6
7
8#关闭模板引擎的缓存
spring:
thymeleaf:
cache: false
#国际化配置文件的文件位置
message.basename: i18n.login
mvc:
data-format: yyyy-MM-dd
- 拦截器
1 | //自定义拦截器 |
- 学习目标
- JDBC
- Mybatis
- Druid
- Shiro
- Spring Security
- 异步任务,邮件发送,定时任务
- Swagger
- Dubbo+Zookeeper
- JDBC
配置文件
1
2
3
4
5
6Spring:
datasource:
username: root
password: xxxxxx
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
- Druid
1 | #整合Druid https://www.cnblogs.com/hellokuangshen/p/12497041.html |
- Mybatis
```xml
org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.1 1
2
3
4
5
6
7
8
9
10
11
12
13
2. ```java
//编写mapper接口
@Mapper
@Repository
public interface UserMapper {
@Select("select * from EmployeeMap")//例:注解方式
List<EmployeeMap> queryUserList();
user queryUserById(String name);
int addUser(user user);
int updateUser(user user);
int deleteUser(String name);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<!--编写sql语句抽取到xml文件中-->
<mapper namespace="com.kuang.mapper.EmployeeMapper">
<!--<cache/>开启缓存-->
<resultMap id="EmployeeMap" type="Employee">
<id property="id" column="eid"/>
<result property="lastName" column="last_name"/>
<result property="email" column="email"/>
<result property="gender" column="gender"/>
<result property="birth" column="birth"/>
<association property="eDepartment" javaType="Department">
<id property="id" column="did"/>
<result property="departmentName" column="dname"/>
</association>
</resultMap>
<select id="getEmployees" resultMap="EmployeeMap">
select e.id as eid,last_name,email,gender,birth,d.id as did,d.department_name as dname
from department d,employee e
where d.id = e.department
</select>
<insert id="save" parameterType="Employee">
insert into employee (last_name,email,gender,department,birth)
values (#{lastName},#{email},#{gender},#{department},#{birth});
</insert>
<select id="get" resultType="Employee">
select * from employee where id = #{id}
</select>
<delete id="delete" parameterType="int">
delete from employee where id = #{id}
</delete>
</mapper>```yaml
#连接数据库
spring:
datasource:username: root password: xxxxx #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#整合mybatis
mybatis:#实体类存在的位置
type-aliases-package: com.jilf.demo.pojo
#扫描sql抽取xml的位置
mapper-locations: classpath:mybatis/mapper/*.xml1
2
3
4
5
6
7
8
##### - MybatisPlus
- ```yaml
#开启日志,方便调试
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
```xml
com.baomidou mybatis-plus-boot-starter 3.0.5 org.apache.velocity velocity-engine-core 2.0 mysql mysql-connector-java 8.0.29 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
- ```java
//代码生成器
public class CodeGenerator {
@Test
public void run() {
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir("C:\\Users\\Jilfoyle\\Desktop\\项目源码\\day02\\guli_parent\\service\\service_edu" + "/src/main/java");
gc.setAuthor("testjava");
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
//UserServie
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.ID_WORKER_STR); //主键策略
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("eduservice"); //模块名
//包 com.atguigu.eduservice
pc.setParent("com.atguigu");
//包 com.atguigu.eduservice.controller
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("edu_course","edu_course_description","edu_chapter","edu_video");//表
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}```java
//UserMapper继承BaseMapper
@Repository
public interface UserMapper extends BaseMapper{ }
1
2
3
4
5
6
7
8
9
10
11
- ```java
@Autowired
private UserMapper userMapper;
@Test
public void contextLoads() {
//查询操作
List<User> users=userMapper.selectList(null);
System.out.println(users);
}命名特点:表中字段create_time,实体类中createTime
自动填充:不用set,用mp方法实现
在实体类的属性上添加注解@TableField(fill=FieldFill.INSERT)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class User {
//value="字段",可字段和属性的映射,即使不符合命名特点
//type,设置主键类型和主键生成策略
//主键自动填充,需要设置主键自增
private long id;
private String name;
private Integer age;
private String email;
//添加时自动填充
private Date createTime;
//修改时自动填充
private Date updateTime;
}```java
//实现MetaObjectHandler中的方法
@Component
public class MymetaObjectHandler implements MetaObjectHandler {//使用mp实现添加操作,该方法执行 @Override public void insertFill(MetaObject metaObject) { //字段 要修改的值 this.setFieldValByName("createTime",new Date(),metaObject); this.setFieldValByName("updateTime",new Date(),metaObject); } //使用mp修改操作,该方法执行 @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updateTime",new Date(),metaObject); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- **乐观锁**:主要解决 丢失更新
- 若不考虑事务隔离性,产生读问题:脏读,不可重复读,幻读(虚读)
- 实现方式:
- 取出记录时,获得当前version
- 更新时,带上这个version
- 执行更新时,set version=newVersion where version=oldVersion
- 如果version不对,就更新失败
- 实现:
1. 数据库中添加version字段
```sql
AlTER TABLE 'user' add column 'version' INT实体类添加@Version注解
1
2
3
private Integer version;元对象处理接口添加version的inser默认值
1
2
3
4
5//在MymetaObjectHandler中
public void insertFill(MetaObject metaObject){
this.setFieldValByName("version",1,metaObject);
}```java
//在MyBatisPlus的接口中注册Bean,创建配置类;插件固定
@EnableTransactionManagement
@Configuration
@MapperScan(“扫描mapper包”)
public class MybatisPlusConfig{/** *乐观锁插件 */ @Bean public OptimisticLockerInterceptor optimisticLockInterceptor(){ return new OptimisticLockerInterceptor(); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- **悲观锁**:串行
- **mp的简单查询**
```java
@Test//根据id查询
public void testSelectById(){
User user= userMapper.selectById(1);
System.out.println(user);
}
@Test//通过多个id,批量查询
public void testSelectBatchIds(){
List<User> users=userMapper.selectBatchIds(Arrays.asList(1L,2L,3L));
users.forEach(System.out::println);
}
@Test//通过map封装条件查询,批量查询
public void testSelectByMap(){
HashMap<String,Object> map= new HashMap<>();
map.put("name","zzzzz");
map.put("age",20);
List<User> users=userMapper.selectByMap(map);
for (User user:users) {
System.out.println(user);
}
MyBatisPlus实现分页:
分页插件
1
2
3
4
public PaginationInterceptor pagintionInterceptor(){
return new PaginationInterceptor();
}```java
@Test//分页操作public void testPage(){ //创建Page对象:(当前页,每页记录数) Page<User> page=new Page<>(1,3); //调用mp分页查询方法 userMapper.selectPage(page,null); System.out.println(page.getCurrent());//获取当前页 System.out.println(page.getRecords());//获取记录数 System.out.println(page.getSize());//获取当前记录数 System.out.println(page.getTotal());//获取总记录数 System.out.println(page.hasNext());//下一页 System.out.println(page.hasPrevious());//上一页 }
1
2
3
4
5
6
7
8
9
10
11
12
- **mp实现逻辑删除**
- 表中添加逻辑删除字段,对应实体类添加属性,属性添加注解**@TableLogic**
- 配置逻辑删除插件
```java
@Bean//逻辑删除插件
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}1
2
3
4
5mybatis:
global-config: #逻辑删除默认值为0,1.不设置也可以
db-config:
logic-delete-value: 1
logic-not-delete-value: 01
2
3
4
5//逻辑删除测试文件
public void testSqlInjector(){
int result=userMapper.deleteById(4L);
System.out.println(result);
}
配置性能分析插件
1
2
3
4
5
6
7
8//SQL执行性能分析插件,开发环境使用,线上不推荐,maxTime指的是sql最大执行时长
//设置dev test环境开启
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor=new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//超过此处设定的ms则sql不执行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}条件查询
1
2
3
4
5
6
7
8
9
public void t1(){//条件查询 ge>= gt> ne!= eq= it< le<= between
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.ge("age",30);
List<User> users=userMapper.selectList(wrapper);
for(User user:users){
System.out.println(user);
}
}插件配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MpConfig {
//乐观锁插件
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
//分页插件
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
//逻辑删除插件
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
//SQL执行性能分析插件,开发环境使用,线上不推荐,maxTime指的是sql最大执行时长
//设置dev test环境开启
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor=new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//超过此处设定的ms则sql不执行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
}自动填充处理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//自动填充
public class MymetaObjectHandler implements MetaObjectHandler {
//使用mp实现添加操作,该方法执行
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
this.setFieldValByName("version",1,metaObject);//版本号初始为1
}
//使用mp修改操作,该方法执行
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
- SpringSecurity(安全)
简介:针对Spring boot的安全框架,对于安全控制,仅需引入spring-boot-starter-security模块。进行少量配置,即可实现强大的安全管理!
几个重要的类:
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuillder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式,@Enablelxxxx开启某个功能
Spring Security的两个主要目标是“认证”和”授权”(访问控制)
- “认证”:(Authentication)
- “授权”:(Authorization)
常见的权限控制模块:
功能权限
访问权限
菜单权限
1 | <!--导包--> |
- 整合Swagger
- ```xml
<!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <scope>provided </scope> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <scope>provided </scope> <version>2.7.0</version>
io.swagger swagger-annotations 1.5.13 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- ```java
@Configuration//配置类
@EnableSwagger2 //swagger注解
@EnableWebMvc
public class SwaggerConfig {
@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("网站-课程中心API文档")
.description("本文档描述了课程中心微服务接口定义")
.version("1.0")
.contact(new Contact("java", "http://atguigu.com", "1123@qq.com"))
.build();
}
}
/**
*若在其他模块中需要引用
*1.引入模块所在依赖
*2.在启动类中中开启组件扫描ComponentScan(basePachages={"包路径"})
*/
1 |
|
- 带条件的分页查询
1 |
|
- 全局统一异常处理类
1 |
|
- 自定义异常处理类
创建自定义异常类
1
2
3
4
5
6
7
8
9
public class GuliException extends RuntimeException{
private Integer code;
private String msg;
}业务中需要的位置抛出GuliException
1
2
3
4
5try{
int a=10/0;
}catch(Exception e){
throw new GuliException(20001,"自定义异常")
}添加异常处理方法
1
2
3
4
5
6
public R error(GuliException e){
e.printStackTrace();
return R.error().message(e.getMsg()).code(e.getCode);
}
- 统一日志处理
配置日志级别:OFF,FATAL,ERROR,WARN,INFO,DEBUG,ALL
默认情况下,spring boot从控制台打印出来日志级别只有INFO及以上级别,可以配置日志级别
1
2
3#设置日志级别
logging.level.root=WARN
#这种方式只能将日志打印在控制台配置Logback日志
1
2#删除: logging.level.root=WARN
#mybatis-plus.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl在resource中创建logback-spring.xml
https://blog.csdn.net/datastructure18/article/details/120919329如果程序运行出现异常,把异常信息输出到文件中
- 在异常处理类上添加@Slf4j注解
- 在异常方法中输出异常语句:log.error(e.getMessage());
- ES6前端技术
1 | // var 声明的变量没有局部作用域 |
- OSS云存储
https://www.aliyun.com/product/oss?spm=5176.19720258.J_3207526240.36.e9392c4aCHGaCR
导入依赖
1
2
3
4
5
6
7
8
9
10
11
12<!--阿里云oss依赖-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.1.0</version>
</dependency>
<!--日期工具栏依赖-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version></version>
</dependency>配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#服务端口
server.port=8002
#服务名
spring.application.name=service-oss
#环境设置
spring.profiles.active=dev
#阿里云OSS
#不用的服务器,地址不同
aliyun.oss.file.endpoint=xxxxxx
aliyun.oss.file.keyid=xxxxxxxx
aliyun.oss.file.keysecret=xxxxxxx
#bucket可以在控制台创建,也可以用java代码创建
aliyun.oss.file.bucketname=edu-jilf创建工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ConstantPropertiesUtils implements InitializingBean {//初始化接口,重写afterPropertiesSet()方法
//读取配置文件
// Endpoint
private String endpoint;
// 阿里云账号AccessKey
private String accessKeyId;
private String accessKeySecret;
// 填写Bucket名称
private String bucketName;
//定义静态常量
public static String END_POINT;
public static String ACCESS_KEYID;
public static String ACCESS_KEYSECRET;
public static String BUCKET_NAME;
public void afterPropertiesSet() throws Exception {
END_POINT=endpoint;
ACCESS_KEYID=accessKeyId;
ACCESS_KEYSECRET=accessKeySecret;
BUCKET_NAME=bucketName;
}
}
- EasyExcel
应用场景:数据导入,导出,异构系统之间的数据传输
```xml
com.alibaba easyexcel 2.1.1 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>${poi.version}</version> </dependency> <!--xlsx--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>${poi.version}</version> </dependency>
1
2
3
4
5
6
7
8
9
10
11
2. ```java
<!--封装实体-->
@Data
public class DemoData {
@ExcelProperty("学生编号")
private Integer sno;
@ExcelProperty("学生姓名")
private String name;
}
```java
public class test { public static void main(String[] args) { //写法一: //写入文件夹地址和excel文件名称 String filename="D://write.xlsx"; EasyExcel.write(filename,DemoData.class).sheet("学生列表").doWrite(getData()); //写法二: //需要手动关闭流 String fileName = "F:\\112.xlsx"; // 这里 需要指定写用哪个class去写 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); WriteSheet writeSheet = EasyExcel.writerSheet("写入方法二").build(); excelWriter.write(data(), writeSheet); /// 千万别忘记finish 会帮忙关闭流 excelWriter.finish(); } private static ListgetData(){ List list=new ArrayList<>(); for(int i=0; i<10; i++){ DemoData demoData=new DemoData(); demoData.setSno(i); demoData.setName("张三"+i); list.add(demoData); } return list; } } 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4. ```java
//测试读操作
@RestController
@CrossOrigin
@RequestMapping("/eduservice/subject")
public class EduSubjectController {
@Autowired
private EduSubjectService subjectService;
//添加课程分类
//获取上传过来的文件,把文件内容读出来
@PostMapping("addSubject")
public R addSubject(MultipartFile file){
//上传过来的文件
subjectService.saveSubject(file,subjectService);
return R.ok();
}
}```java
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {@Override//添加课程分类 public void saveSubject(MultipartFile file,EduSubjectService subjectService) { try{ //文件输入流 InputStream in=file.getInputStream(); //调用方法进行读取 EasyExcel.read(in, SubjectData.class, new SubjectExcelListener(subjectService)).sheet().doRead(); }catch (Exception e){ e.printStackTrace(); } }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
- ```java
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
//因为SubjectListener不能交给spring进行管理,需要自己new,不能注入其他对象
//不能实现数据库操作
public EduSubjectService subjectService;
public SubjectExcelListener() { }
public SubjectExcelListener(EduSubjectService subjectService) {
this.subjectService = subjectService;
}
@Override
public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
if (subjectData == null){
throw new GuliException(20001,"文件数据为空");
}
//一行一行读,每次读取两个值,第一个值为一级分类,第二个值为二级分类
//判断一级分类是否重复
EduSubject existOneSubject=this.existOneSubject(subjectService,subjectData.getOneSubjectName());
if(existOneSubject == null){//没有一级分类,进行添加
existOneSubject=new EduSubject();
existOneSubject.setParentId("0");
existOneSubject.setTitle(subjectData.getOneSubjectName());//一级分类名称
subjectService.save(existOneSubject);
}
String pid=existOneSubject.getId();
//添加二级分类
EduSubject existTwoSubject=this.existOneSubject(subjectService,subjectData.getTwoSubjectName(),pid);
if(existTwoSubject == null){
existTwoSubject = new EduSubject();
existTwoSubject.setParentId(pid);
existTwoSubject.setTitle(subjectData.getTwoSubjectName());//二级分类名称
subjectService.save(existTwoSubject);
}
}
//判断一级分类不重复添加
private EduSubject existOneSubject(EduSubjectService subjectService,String name ){
QueryWrapper<EduSubject> wrapper=new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id",'0');
EduSubject onesubject=subjectService.getOne(wrapper);
return onesubject;
}
//判断二级分类不重复添加
private EduSubject existOneSubject(EduSubjectService subjectService,String name,String pid ){
QueryWrapper<EduSubject> wrapper=new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id",pid);
EduSubject twosubject=subjectService.getOne(wrapper);
return twosubject;
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}```java
@Data
public class SubjectData {@ExcelProperty(index = 0) private String oneSubjectName; @ExcelProperty(index = 1) private String twoSubjectName;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
##### - 富文本编辑器
参考:[首页 - Vue Element Admin (gitee.io)](https://panjiachen.gitee.io/vue-element-admin/#/dashboard)
1. 复制脚本库:将脚本库复制到static目录下(在vue-element-admin-master的static路径下)
2. 配置html变量
- 在webpack.dev.conf.js中添加配置,
- 使在html页面中可以使用这里定义的Base_URL变量
```javascript
new HtmlWedpackPlugin({
……,
templateParameters: {
BASE_URL:config.dev.assetsPublicPath+config.dev.assetsSubDirectory
}
})
引入脚本:在guli-admin/index.html中引入js脚本
1
2<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
<script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>
组件使用:
复制组件:src/components/Tinymce
引入组件:课程信息中引入Tinymce
1
2
3
4
5
6
7<script>
import Tinymce from '@/components/Tinymce'
export default {
components:{Tinymce},
}
</script>组件模板
1
2
3<el-form-item label="课程简介">
<tinymce :height="300" />
</el-form-item>组件样式
1
2
3
4
5<style scoped>
.tinymce-container {
line-height: 29px;
}
<style>
- Git
安装:https://registry.npmmirror.com/binary.html?path=git-for-windows/v2.36.1.windows.1/
配置
1
2
3
4
5#配置用户·和邮箱
git config --global user.name "Jilfoyle"
git config --global user.email "918703864@qq.com"
#查询配置信息
git config --global --listgit基本理论:
工作区域:工作目录(working),暂存区(Stage/Index),资源库(Repository),远程仓库(Remote)
项目搭建
1
2git init #本地初始化
git clone url #远程clonegit文件操作
- 文件
- nginx
1 | #常用命令 |
- 打包操作
1 | <!--1.项目打包时会将java目录中的*.xml文件也进行打包--> |
- 添加jar包到本地maven仓库
1 | #例:添加aliyun-java-vod-upload-1.4.14.jar |
- 微服务
1 | 1.微服务是架构风格 |
- SpringCloud
1 | SpringCloud是一系列框架的集合,它利用Spring Boot的简化了分布式基础设施的开发,如服务发现。服务注册,配置中心,消息总线,负载均衡,熔断器,数据监控等,都可以用SpringBoot的开发风格做到一键启动和部署 |
- SpringCloud基础服务组件
- 服务发现—Nacos
- 服务调用—Netflix Feign
- 熔断器—Netflix Hystrix
- 服务网关—Spring Cloud GateWay
- 分布式配置—Spring Cloud Config(Nacos)
- 消息总线—Spring Cloud Bus(Nacos)
- SpringCloud小版本
- SNAPSHOT:快照版本,随时可能修改
- M:MileStone,M1表示里程碑版本,一般同时标注PRE,表示预览版
- SR:Service Release,SR1表示第一个正式版本,一般同时标注GA,表示稳定版本
- Nacos下载和安装
地址:https://github.com/alibaba/nacos/releases
版本:nacos-server-1.1.4.tar.gz或nacos-server-1.1.4.zip解压任意目录
- 服务注册
引入依赖
1
2
3
4
5
6
7
8
9
10
11<!--引入依赖-->
<!--服务注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>在要注册的服务配置文件application中配置Nacos地址
1
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
在启动类添加**@EnableDiscoveryClient**注解
- 服务调用
在调用端的服务启动类上添加**@EnableFeignClients**注解
创建接口,在接口上添加注解**@Component,@FeignClient(“服务名字”)**
1
2
3
4
5
6
7
8//@PathVariable注解一定要指定参数名称,否则出错
public interface Vodclient{
//定义调用的方法路径
//完整路径
public R removeAlyVideo(; String id)
}在需要调用的controller中注入Vodclient实现远程调用
- SpringCloud调用接口流程
1 | 接口化请求调用--call-->(1)Feign --Hystrix熔断处理机制--> (2)Hystrix --请求通过,则通过Ribbon负载均衡器,挑选合适的服务提供端-->(3)Ribbon--请求包转换成Http Client客户端框架做真实的http通信请求-->(4)HttpClient/OkHttp |
- 整合熔断器
添加熔断器依赖
1
2
3
4
5
6
7
8
9
10<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--hystrix依赖,主要是用 @HystrixCommand -->
<dependency>
<groupId>org.springframework.cloud</groupId>
</dependency>在调用端配置文件中开启熔断器
1
2#开启熔断机制
feign.hystrix.enabled=true在创建interface之后,还需要创建interface的实现类,在实现类中实现方法,出错了输出内容
1
2
3
4
5
6
7
8
9
10
11
12
13
public class VodFileDegradeFeignClient implements VodClient {
//远程调用出错会执行
//删除一个视频
public R removeAlyVideo(String id) {
return R.error().message("删除视频出错");
}
//批量删除视频
public R deleteBatch(List<String> videoList) {
return R.error().message("删除多个视频出错");
}
}在接口上添加注解
1
- Redis
特点:
- 基于Key-value进行存储的
- 支持多种数据结构:string(字符串),list(列表),hash(哈希),set(集合),zset(有序集合)
- 支持持久化,通过内存存储,也可以存到硬盘里
- 支持过期时间,支持事务,消息订阅
- 读取速度110000次/s,写的速度是81000次/s
- 所有操作都是原子性的,同时支持对几个操作全并后的原子性执行
场景:
- 经常查询,不经常修改,不是特别重要的数据放到redis中缓存
Springboot整合redis
:
引入依赖
1
2
3
4
5
6
7
8
9
10
11
12<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring2.x集成redis所需的common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>创建配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65package com.atguigu.servicebase;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
public class RedisConfig extends CachingConfigurerSupport {
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}开启缓存注解:
(1)
@Cacheable
:根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存返回数据,若缓存不存在,则执行方法,并把结果存入缓存中,一般用在
查询方法
上属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与value差不多,二选一即可 key 可选属性,可以使用SpEL标签自定义缓存的key (2)缓存@CachePut
使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在
新增方法
上。查看源码,属性值如下:
属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与value差不多,二选一即可 key 可选属性,可以使用SpEL标签自定义缓存的key (3)缓存@CacheEvict
使用该注解标志的方法,会清空指定的缓存。一般用在
更新或者删除
方法上查看源码,属性值如下:
属性/方法名 解释 value 缓存名,必填,它指定了你的缓存存放在哪块命名空间 cacheNames 与value差不多,二选一即可 key 可选属性,可以使用SpEL标签自定义缓存的key allEntries 是否清空所有缓存,默认为false.如果指定为true,则方法调用后将立即清空所有缓存 beforeInvocation 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存
- 杂记
- mysql
* 内连接:
* 左外连接:
* 右外连接:
1 | //获取路由中id值 |
1 | # coding=utf8 |
1 | import com.aliyun.oss.ClientConfiguration; |
1 | public class OssServiceApplication extends SpringBootServletInitializer { |
- Springboot面试
6.1、SpringBoot是什么(了解)
是Spring的子项目,主要简化 Spring开发难度,去掉了繁重配置,提供各种启动器,可以让程序员很快上手,节省开发时间。
6.2、SpringBoot的优点(必会)
SpringBoot对上述 Spring的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。
版本锁定:解决是maven 依赖版本容易冲突的问题,集合了常用的并且测试过的所有版本
使用了Starter(启动器)管理依赖并能对版本进行集中控制,如下的父工程带有版本号, 就是对版本进行了集中控制;
起步依赖:解决了完成某一个功能要整合的jar 包过多的问题,集合了常用的jar 包;
自动配置:解决了整合框架或者技术的配置文件过多,集合了所有的约定的默认配置;
内置Tomcat:通过内置的tomcat,无需再用其他外置的Tomcat 就直接可以运行javaEE程序。
总之:人们把 Spring Boot 称为搭建程序的脚手架。其最主要作用就是帮我们快速的构建 庞大的spring项目,并且尽可能的减少一切 xml配置,做到开箱即用,迅速上手,让我们 关注与业务而非配置。
6.3、运行SpringBoot项目的方式(必会)
可以打包;
可以使用 Maven插件直接运行;
直接运行 main方法。
6.4、SpringBoot的启动器starter(必会)
(1)什么是starter?
starter启动器,可以通过启动器集成其他的技术,比如说: web, mybatis, redis等等。可以提供对应技术的开发和运行环境,比如: pom中引入spring-boot-starter-web, 就可以进行web开发。
**
**
(2)starter执行原理?
SpringBoot在启动时候会去扫描 jar 包中的一个名为spring.factories;
根据文件中的配置,去加载自动配置类. 配置文件格式是 key=value, value 中配置了很 多需要Spring加载的类;
Spring 会去加载这些自动配置类, Spring 读取后,就会创建这些类的对象,放到 Spring 容器中.后期就会从 Spring容器中获取这些类对象。
**
**
(3)SpringBoot 中常用的启动器
spring-boot-starter-web, 提供 web技术支持
spring-boot-starter-test
spring-boot-starter-jdbc
spring-boot-starter-jpa
spring-boot-starter-redis…等等
6.5、SpringBoot运行原理剖析(必会)
(一) SpringApplication类作用及run()方法作用
SpringApplication 这个类整合了其他框架的启动类, 只要运行这一个类,所有的整合 就都完成了;
调用run函数,将当前启动类的字节码传入(主要目的是传入@SpringBootApplication这个注解), 以及 main 函数的args参数;
通过获取当前启动类的核心信息, 创建 IOC容器。
(二) 当前启动类@SpringBootApplication详细剖析
run 函数传入的当前启动类字节码, 最重要的是传入了@SpringBootApplication, 点开该注解源码, 会发现有多个注解组成,接下来会详细解释每个注解的含义. 点开这个注解源码, 发现有4类注解。
(1) 第一类: JDK原生注解4个
@Target(ElementType.TYPE) //当前注解的使用范围 @Retention(RetentionPolicy.RUNTIME) //生命周期
@Documented //声明在生成 doc文档时是否带着注解
@Inherited //声明是否子类会显示父类的注解
(2)第二类: @SpringBootConfiguration
点开该注解源码, 会发现本质是@Configuration,定义该类是个配置类功能等同于xml 配置文件。
提到@Configuration就要提到他的搭档@Bean, 使用这两个注解就可以创建一个简 单的 Spring配置类, 可以用来替代相应的xml配置文件.可以理解为创建了 IOC容器了。
(3)第三类: @ComponentScan, 包扫描功能
这个注解对应 Spring 的XML 配置中的@ComponentScan,其实就是自动扫描并加 载符合条件的组件(比如@Component 和@Repository 等)或者 bean 定义, 最终将这些 bean 定义加载到IoC容器中。
也可以通过basePackages 等属性来细粒度的定制@ComponentScan 自动扫描的范围, 如果不指定, 则默认扫描@ComponentScan所在类的 package 及子包进行扫描。
**注:所以SpringBoot的启动类最好是放在root package 下,因为默认不指定 basePackages, 这样能扫描root package 及子包下的所有类。
**
(4)第四类: @EnableAutoConfiguration
点开源码会发现,本质是@import, 自动导入功能
@EnableAutoConfiguration 也是借助@Import 的帮助,将所有符合自动配置条件的 bean定义加载到 IoC容器. @EnableAutoConfiguration会根据类路径中的 jar 依赖为项目进行自动配置, 如:添加了 spring-boot-starter-web 依赖, 会自动添加 Tomcat 和 SpringMVC 的依赖, SpringBoot 会对 Tomcat和 SpringMVC进行自动配置;
那么SpringBoot是如何完成自动配置的呢?
A. SpringBoot 自动配置的注解是 @EnableAutoConfiguration;
B. 我们用的时候是在启动类上加@SpringBootApplication,这个注解是复合注解,内部包含 @EnableAutoConfiguration;
C. @EnableAutoConfiguration内部有一个@Import, 这个注解才是完成自动配置的关键;
D. @Import 导入一个类(AutoConfigurationImportSelector),这个类内部提供了一个方法(selectImports). 这个方法会扫描导入的所有 jar 包下的spring.factories文件. 解析文件中自动配置类key=value, 将列表中的类创建,并放到Spring容器中。
6.5.1、总结
总之一个@SpringBootApplication注解就搞定了所有事, 它封装了核心的 @SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan 这三 个类,大大节省了程序员配置时间,这就是SpringBoot 的核心设计思想。
6.6、SpringBoot热部署(了解)
导入 spring-boot-devtools这个 jar 包: 就可以完成热部署了。
6.7、SpringBoot中的配置文件(必会)
(1)有哪些配置文件?
application.yml 或application.properties
bootstrap.yml 或bootstrap.properties
(2)上面两种配置文件有什么区别?
\1. bootstrap 由父 ApplicationContext 加载, 比 application 配置文件优先被加
载;
\2. bootstarp里的属性不能被覆盖;
\3. application: springboot 项目中的自动化配置;
\4. bootstrap:
使用 spring cloud config 配置中心时, 需要加载连接配置中心的配置属性的, 就 可以使用 bootstrap来完成;
加载不能被覆盖的属性;
加载一些加密/解密的数据。
(3)读取配置文件的方式?
读取默认配置文件
需要注入 Environment 类, 使用 environment.getProperty(peorperties 中的 key), 这 样就能获得key 对应的value值;
@value(${key.value}) 直接读取。
读取自定义配置文件
自定义配置文件后缀必须是.propeties;
编写和自定义配置文件对应的java类, 类上放 3个注解:
@ConfigurationProperties(“前缀”)
@PropertySource(“指定配置文件”)
@Component 包扫描
读取的时候就跟读取默认配置文件一样。
6.8、SpringBoot支持哪些日志框架(了解)
Java Utils logging
Log4j2
Lockback
如果你使用了启动器,那么 springboo 默认将 Lockback作为日志框架。
6.9、SpringBoot常用注解(必会)
\1. @SpringBootApplication: 它 封 装 了 核 心 的 @SpringBootConfiguration +@EnableAutoConfiguration +@ComponentScan 这三个类,大大节省了程序员配置时间,这就是 SpringBoot的核心设计思想;
2.@EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IOC容器;
\3. @MapperScan:spring-boot支持mybatis组件的一个注解,通过此注解指定mybatis接口类的路径,即可完成对mybatis 接口的扫描;
4.@RestController 是@Controller 和 @ResponseBody 的 结 合 , 一 个 类 被 加 上 @RestController注解,数据接口中就不再需要添加@ResponseBody,更加简洁;
5.@RequestMapping,我们都需要明确请求的路径;
6.@GetMappping,@PostMapping, @PutMapping, @DeleteMapping 结合 @RequestMapping使用, 是 Rest 风格的, 指定更明确的子路径;
7.@PathVariable:路径变量注解,用{}来定义url 部分的变量名;
8.@Service 这个注解用来标记业务层的组件,我们会将业务逻辑处理的类都会加上这个注解交给spring 容器。事务的切面也会配置在这一层。当让 这个注解不是一定要用。有个泛指组件的注解,当我们不能确定具体作用的时候 可以用泛指组件的注解托付给 spring容器;
9.@Component 和spring的注解功能一样, 注入到IOC 容器中;
10.@ControllerAdvice 和 @ExceptionHandler 配合完成统一异常拦截处理. 备注: 面试的时候记住6.7 个即可~
7、SpringCloud
7.1、SOA和微服务的区别?(必会)**
谈到 SOA和微服务的区别, 那咱们先谈谈架构的演变。
1. 集中式架构
项目功能简单, 一个项目只需一个应用, 将所有功能部署在一起, 这样的架构好处是减 少了部署节点和成本。
缺点: 代码耦合,开发维护困难, 无法水平扩展, 单点容错率低,并发能力差。
2. 垂直拆分架构
当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分:
优点:系统拆分实现了流量分担,解决了并发问题;可以针对不同模块进行优化, 方便水平扩展,负载均衡,容错率提高。
缺点:系统间相互独立,会有很多重复开发工作,影响开发效率。
3. 分布式服务
当垂直应用越来越多, 随着项目业务功能越来越复杂, 并非垂直应用这一条线进行数据调用, 应用和应用之间也会互相调用, 也就是完成某一个功能,需要多个应用互相调用, 这就 是将功能拆完来完成的分布式架构。
优点: 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率。
缺点: 系统间耦合度变高,调用关系错综复杂,难以维护。
4. 服务治理架构SOA
SOA :面向服务的架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键, 而最初的服务治理基石是 Dubbo 服务治理。
以前分布式服务的问题?
服务越来越多,需要管理每个服务的地址, 调用关系错综复杂,难以理清依赖关系;
服务过多,服务状态难以管理,无法根据服务情况动态管理。
SOA服务治理架构的优点:
服务注册中心,实现服务自动注册和发现,无需人为记录服务地址;
服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系;
动态监控服务状态监控报告,人为控制服务状态。
SOA服务治理架构的缺点:
服务间依然会有依赖关系,一旦某个环节出错会影响较大(容错机制);
服务关系复杂,运维、测试部署困难,不符合开发-运维一站式维护的思想。
5. 微服务
前面说的 SOA,英文翻译过来是面向服务。微服务,似乎也是服务,都是对系统进行拆分。因此两者非常容易混淆,但其实缺有一些差别:
微服务的特点:
单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责;
微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口 API。并不关心 服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供 Rest的接口即可;
自治:自治是说服务间互相独立,互不干扰。
团队独立:每个服务都是一个独立的开发团队,人数不能过多。
技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉。
前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、 移动段开发不同接口。
数据库分离:每个服务都使用自己的数据源。
部署独立:服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持 续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合, 易维护. 基于 docker容器是开发。
目前微服务微服务架构主流的是 SpringBoot+Dubbo 和 SpringBoot+SpringCloud的架构模式。
综上, 无论是 SOA 还是微服务, 都需要进行服务调度, 目前主流的服务调度室 RPC 和 HTTP两种协议, 而 Dubbo 基于 RPC 的远程调度机构, SpringCloud 是基于 Rest 风格(基于http协议实现的)的 Spring 全家桶微服务服务治理框架。说到这里也可以继续说下 Dubbo和SpringCloud的区别。
7.2、SpringCloud是什么?(了解)
SpringCloud是一系列框架的集合,集成SpringBoot,提供很多优秀服务:服务发现 和注册,统一配置中心, 负载均衡,网关, 熔断器等的一个微服务治理框架。
7.3、SpringCloud的优势?(了解)
因为 SpringCloud源于 Spring,所以它的质量,稳定性,持续性都是可以保证的;
SpringCloud天热支持 SpringBoot 框架,就可以提高开发效率,能够实现需求;
SpringCloud更新很快,后期支持很给力;
SpringCloud可以用来开发微服务。
7.4、SpringCloud有哪些核心组件?(必会)
1.Eureka: 注册中心, 服务注册和发现;
2.Ribbon: 负载均衡, 实现服务调用的负载均衡;
3.Hystrix: 熔断器;
4.Feign: 远程调用;
5.Gateway: 网关;
6.Spring Cloud Config: 配置中心;
(1)Eureka
提供服务注册和发现, 是注册中心。有两个组件: Eureka 服务端和 Eureka 客户端。
7.Eureka 服务端: 作为服务的注册中心, 用来提供服务注册, 支持集群部署;
8.Eureka 客户端: 是一个 java 客户端, 将服务注册到服务端, 同事将服务端的信息缓存 到本地, 客户端和服务端定时交互。
\1. 原理
Eureka-Server:就是服务注册中心(可以是一个集群),对外暴露自己的地址;
提供者:启动后向 Eureka 注册自己信息(地址,服务名称等),并且定期进行服务续约;
消费者:服务调用方,会定期去Eureka 拉取服务列表,然后使用负载均衡算法选出一 个服务进行调用;
心跳(续约):提供者定期通过 http方式向Eureka 刷新自己的状态。
2. 服务下线、失效剔除和自我保护
服务的注册和发现都是可控制的,可以关闭也可以开启。默认都是开启;
注册后需要心跳,心跳周期默认 30秒一次,超过 90秒没发心跳认为宕机;
服务拉取默认 30秒拉取一次;
Eureka 每个60 秒会剔除标记为宕机的服务;
Eureka 会有自我保护,当心跳失败比例超过阈值(默认 85%),那么开启自我保护,不再剔除服务;
Eureka 高可用就是多台Eureka互相注册在对方上。
(2)Ribbon
Ribbon 是 Netflix 发布的云中服务开源项目. 给客户端提供负载均衡, 也就是说 Ribbon是作用在消费者方的;
简单来说, 它是一个客户端负载均衡器, 它会自动通过某种算法去分配你要连接的机器;
SpringCloud认为 Ribbon这种功能很好, 就对它进行了封装, 从而完成负载均衡;
Ribbon默认负责均衡策略是轮询策略。
(3)Hystrix熔断器
有时候可能是网络问题, 一些其它问题, 导致代码无法正常运行, 这是服务就挂了, 崩溃了。熔断器就是为了解决无法正常访问服务的时, 提供的一种解决方案;
解决因为一个服务崩溃而引起的一系列问题, 使问题只局限于这个服务中,不会影响其他服务;
Hystrix 提供了两种功能, 一种是服务降级, 一种是服务熔断。
1. 服务降级原理
Hystrix 为每个服务分配了小的线程池, 当用户发请求过来, 会通过线程池创建线 程来执行任务, 当创建的线程池已满或者请求超时(这里和多线程线程池不一样,不 存在任务队列), 则启动服务降级功能;
降级指的请求故障时, 不会阻塞, 会返回一个友好提示(可以自定义, 例如网站维 护中请稍后重试), 也就是说不会影响其他服务的运行。
2. 服务熔断原理
状态机有 3个状态:
Closed:关闭状态(断路器关闭),所有请求都正常访问;
Open:打开状态(断路器打开),所有请求都会被降级。Hystix 会对请求情况计数, 当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全关闭。默认失败比例的阈值是50%,请求次数最少不低于 20次;
HalfOpen:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放 1次请求通过,若这个请求是健康的, 则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。
(4)Feign: 远程调用组件
后台系统中, 微服务和微服务之间的调用可以通过 Feign组件来完成;
Feign组件集成了Ribbon 负载均衡策略(默认开启的, 使用轮询机制), Hystrix 熔断器 (默认关闭的, 需要通过配置文件进行设置开启);
被调用的微服务需要提供一个接口, 加上@@FeignClient(“url”)注解;
调用方需要在启动类上加上@EnableFeignClients, 开启 Feign组件功能。
(5)Gateway: 路由/网关
1.对于项目后台的微服务系统, 每一个微服务都不会直接暴露给用户来调用的, 但是如果用户知道了某一个服务的ip:端口号:url:访问参数, 就能直接访问你. 如果再是恶意访问, 恶意攻击, 就会击垮后台微服务系统.因此, 需要一个看大门的大boss, 来保护我们的后台系统;
2.Gateway 支持过滤器功能,对请求或响应进行拦截,完成一些通用操作。
Gateway 提供两种过滤器方式:“pre”和“post”
pre 过滤器,在转发之前执行,可以做参数校验、权限校验、流量监控、日志输出、 协议转换等。post 过滤器,在后端微服务响应之后并且给前端响应之前执行,可以做响应内容、 响应头的修改,日志的输出,流量监控等。
3.Gateway 还提供了两种类型过滤器
(一) GatewayFilter:局部过滤器,针对单个路由
\1. GatewayFilter 局部过滤器,是针对单个路由的过滤器。
\2. 在Spring Cloud Gateway 组件中提供了大量内置的局部过滤器,对请求和响应 做过滤操作。
\3. 遵循约定大于配置的思想,只需要在配置文件配置局部过滤器名称,并为其指定对 应的值,就可以让其生效。
(二) GlobalFilter :全局过滤器,针对所有路由
\1. GlobalFilter 全局过滤器,不需要在配置文件中配置,系统初始化时加载,并作用 在每个路由上。
\2. Spring Cloud Gateway 核心的功能也是通过内置的全局过滤器来完成。
\3. 自定义全局过滤器步骤:
1 定义类实现 GlobalFilter 和 Ordered接口;
2 复写方法 3 完成逻辑处理。
(6)Spring Cloud Config
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在 Spring Cloud 中,有分布式配置中心组件 spring CloudConfig ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程 Git仓库中.
7.5、SpringBoot和SpringCloud的关系(必会)
1.SpringBoot是为了解决 Spring配置文件冗余问题, 简化开发的框架;
2.SpringCloud是为了解决微服务之间的协调和配置问题, 还有服务之间的通信, 熔断, 负载均衡远程调度任务框架;
3.SpringCloud需要依赖 SpringBoot 搭建微服务, SpringBoot使用了默认大于配 置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,SpringCloud 很大的一部分是基于SpringBoot 来实现;
4.SpringBoot不需要依赖 SpringCloud就可以独立开发. SpringBoot 也可以集成 Dubbo 进行开发。
7.6、SpringCloud和Dubbo的区别(高薪常问)
1.SpringCloud和Dubbo 都是主流的微服务架构。
SpringCloud是Apache下的 Spring体系下的微服务解决方案;
Dubbo 是阿里系统中分布式微服务治理框架。
2.技术方面对比
SpringCloud 功能远远超过 Dubbo, Dubbo 只实现了服务治理(注册和发现). 但是SpringCloud提供了很多功能, 有21个子项目;
Dubbo 可以使用 Zookeeper 作为注册中心, 实现服务的注册和发现, SpringCloud 不仅可以使用 Eureka 作为注册中心, 也可以使用 Zookeeper 作为 注册中心;
Dubbo 没有实现网关功能, 只能通过第三方技术去整合. 但是 SpringCloud 有 zuul路由网关, 对请求进行负载均衡和分发. 提供熔断器, 而且和git 能完美集成。
3.性能方面对比
由于Dubbo 底层是使用Netty 这样的 NIO 框架,是基于 TCP协议传输的,配合以Hession序列化完成 RPC;
而 SpringCloud 是基于 Http 协议+Rest 接口调用远程过程的,相对来说,Http 请求会有更大的报文,占的带宽也会更多;
使用 Dubbo 时, 需要给每个实体类实现序列化接口, 将实体类转化为二进制进行 RPC 通信调用.而使用SpringCloud时, 实体类就不需要进行序列化。
刚才有提到注册中心不一样,那么Eureka和Zookeeper有什么区别? 我们继续往下说~**
**
**
**
7.7 Eureka和Zookeeper的区别(高薪常问)
在谈这个问题前我们先看下 CAP 原则: C(Consistency)-数据一致性; A(Availability)服务可用性; P(Partition tolerance)-服务对网络分区故障的容错性, 这三个特性在任何分 布式系统中不能同时满足, 最多同时满足两个, 而且 P(分区容错性)是一定要满足的。
1.Eureka 满足AP(服务可用性和容错性), Zookeeper 满足 CP(数据一致性和容错性);
2.Zookeeper 满足 CP , 数据一致性, 服务的容错性. 数据在各个服务间同步完成后才返回用户结果, 而且如果服务出现网络波动导致监听不到服务心跳, 会立即从服务列表中 剔除, 服务不可用;
3.Eureka 满足 AP , 可用性, 容错性. 当因网络故障时, Eureka 的自我保护机制不会立即剔除服务, 虽然用户获取到的服务不一定是可用的, 但至少能够获取到服务列表. 用户 访问服务列表时还可以利用重试机制, 找到正确的服务. 更服务分布式服务的高可用需求;
4.Eureka 集群各节点平等, Zookeeper 集群有主从之分。
4.1. 如果 Zk 集群中有服务宕机,会重新进行选举机制,选择出主节点, 因此可能会导致整个集群因为选主而阻塞, 服务不可用;
4.2. Eureka 集群中有服务宕机,因为是平等的各个服务器,所以其他服务器不受影响.
5.Eureka 的服务发现者会主动拉取服务, ZK服务发现者是监听机制。
5.1. Eureka 中获取服务列表后会缓存起来, 每隔 30秒重新拉取服务列表;
5.2. Zk 则是监听节点信息变化, 当服务节点信息变化时, 客户端立即就得到通知。