一、springboot是什么
是一种快速使用spring的方式,简化了大量配置文件。
SpringBoot是所有基于spring开发的项目的起点。SpringBoot的目的是为了让用户尽可能快的跑起来Spring应用程序并尽可能减少配置文件。
=========================
二、springboot原理
基于"约定优于配置"(Convention over Configuration)思想,使用默认值简化配置,开发人员仅需规定应用中不符约定的部分。(如果不想用默认值,则需要手动配置)
-------------------------
Spring优点:组件代码是轻量级的。
Spring缺点:配置是重量级的(applicationContext.xml);项目的依赖管理耗时耗力。
-------------------------
SpringBoot解决了spring缺点:
1.起步依赖:springboot将需要依赖的一系列jar包的信息整合成了一个个.pom文件,使用时,只需要引入整合后的pom文件即可。(之前是在pom.xml中引入一堆jar包,现在可以在pom.xml中引入一个整合好的.pom。)
2.自动配置:springboot会自动将一些配置类的bean注入ioc容器;使用时,只需要引入jar包,在需要的地方写@autowired或者@resource从ico容器中拿出来使用即可。
(而原来spring中,除了引入jar包,还需要在applicationContext.xml中配置<bean>标签,或者使用@Bean注解,才能注入ioc容器。例如mybatis,driud连接池等)
=========================
三、springboot源码分析
1.SpringBoot中,使用dependency导入常用jar包时不需要指定版本,因为pom.xml中的spring-boot-starter-parent有一个<version>指定了自己的版本号,而spring-boot-starter-parent中的spring-boot-dependencies中的<properties>标签中,指定了一系列的常用jar包的版本号。(所以自己使用dependency引入常用jar包时不用指定版本,避免了版本冲突)
2.SpringBoot中,对于常用的框架,只需要引入需要的spring-boot-starter即可(例如spring-boot-starter-web),其中已包含需要的一系列jar包,而不需要自己手动一个个导入jar包,简化了配置信息。
3.SpringBoot能够在添加jar包依赖时,使用默认值自动配置一些信息,我们无需配置或只需少量配置就能运行编写的项目。(而在Spring中,我们需要自己对每个导入的jar包设置配置信息,通过<bean>标签或@bean注解)
4.SpringBoot自动配置原理:
(1)SpringBoot引用的启动入口是@SpringBootApplication注解标注的main()方法,@SpringBootApplication能够扫描Spring组件并自动配置SpringBoot
(2)@SpringBootApplication注解是一个组合注解,其中重要的有3个:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan。
(3)@SpringBootConfiguration注解中,包含@Configuration注解,标明该类为配置类;当SpringBoot启动时,扫描到带有该注解的类,就当做配置类进行特殊处理。
(4)●@EnableAutoConfiguration注解,表示开启自动配置功能,其中有@AutoConfigurationPackage与@Import({AutoConfigurationImportSelector.class})注解;
●@AutoConfigurationPackage注解中有@Import({Registrar.class}),当SpringBoot启动时,就会把Registrar类实例化并存入spring容器中;
●Registrar类中有registerBeanDefinitions()方法,将主程序类所在包及其所有子包下的组件扫描到spring容器中;【因此SpringBoot启动类的位置要在最外层的根目录下】
●@Import({AutoConfigurationImportSelector.class})注解,会将AutoConfigurationImportSelector导入spring容器,这个类可以将所有符合条件的@Configuration配置加载到spring容器中;
●AutoConfigurationImportSelector类中有一个loadFactoryNames()方法,这个方法中,会读取"META-INF/spring.factories"配置文件;
●spring.factories文件有多个,在pom.xml中引入的"spring-boot-starter-xxx"中,有些会对应一个jar包,其中有spring.factories文件,这个文件中又配置了一些配置类的信息;
●然后对应的配置类中使用了@Bean注解将默认的配置信息存入了spring容器。(这个配置类也在starter对应jar包中;这样就实现了自动配置)
(5)@ComponentScan注解,配置后默认会扫描该类所在的包下所有的配置类。上方的Registrar类中的registerBeanDefinitions()方法会结合@ComponentScan具体实现配置类扫描。
5.启动方法:SpringApplication.run()
这个方法中主要完成了:
(1)SpringApplication实例的初始化创建(SpringApplication类用于引导和启动一个Spring应用程序),其中通过读取spring.factores文件,实现了SpringBoot自动配置。
(2)调用SpringApplication.run()启动项目(返回的对象属于ApplicationContext,即spring容器)。
其中包含获取并启动SpringApplication监听器、准备运行环境、创建Spring容器、Spring容器前置处理、刷新Spring容器、Spring容器后置处理、执行自定义执行器Runners等步骤。
=========================
四、springboot单元测试
可以不启动整个项目,只初始化ioc容器,只运行测试类进行测试。
步骤:
1.在pom.xml中引入配置(如果有了就不用重复添加):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.在src/test/java/com.xxx.xxx/XXXApplicationTests.java中编写测试方法(创建项目会自动生成;也可以手动创建一个):
@RunWith(SpringRunner.class)
@SpringBootTest
class XXXApplicationTests {
//注入controller,测试url的返回值是否正确
@Autowired
private MyController myController;
@Test
void controllerTest() {
String back = myController.getMsg();
System.out.println(back);
}
}
=========================
五、springboot热部署
当代码有改动时,可以自动部署并生效,而不用手动重启项目。
步骤:
1.在pom.xml中引入配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
2.如果是IDEA开发工具,则需要打开settings继续配置:
左侧栏中选择:Build,Execution,Deployment->Compiler
右侧选中:Build project automatically
3.如果是IDEA开发工具,还需要使用快捷键"Ctrl+Shift+Alt+/"打开Maintenance选项框,选择Registry...,然后在打开的界面中找到"compiler.automake.allow.when.app.running",将对应的value值打钩。
4.如果IDEA快捷键打不开Maintenance,可以查看settings->Keymap,搜索Maintenance。
5.重启IDEA。
=========================
六、springboot整合thymeleaf
SpringBoot不能很好地使用jsp,因此常用thymeleaf模板。
Thymeleaf是一种基于服务器端的Java模板引擎技术,也是一个优秀的面向Java的XML、XHTML、HTML5的页面模板,具有丰富的标签语言、函数和表达式。
在HTML页面上使用Thymeleaf标签,能够动态地替换掉静态内容,使页面动态展示。
使用方式:
1.pom.xml中引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.在application.properties中配置:
spring.thymeleaf.cache = true #启用模板缓存,生产为true;开发时设置为false方便调试
spring.thymeleaf.encoding = UTF-8 #模板编码
spring.thymeleaf.mode = HTML5 #应用于模板的模板模式
spring.thymeleaf.prefix = classpath:/templates/ #页面存放路径,前缀(html路径)
spring.thymeleaf.suffix = .html #页面后缀(代码中可以省略.html了)
3.在resources/static下放静态资源,例如js、css、img等
4.在resources/templates下存放html,访问这些html时需要从controller跳转到才可以
5.在html中使用thymeleaf标签实现动态页面,常用标签有:
th:fragment 标记一个标签并起一个名称,后续替换用。
th:insert 将目标标签中的内容(含标签名)放在这个标签中。(这个自身标签还在)
th:include (3.0版本后已不推荐使用)将目标标签中的内容(不含标签名)放在这个标签中。(这个自身标签还在)
th:replace 将目标标签(含标签名)整个替换掉这个标签。(这个自身标签不在了,被结点标签替换)
th:each 元素遍历
th:if 条件判断,如果符合条件则显示
th:unless 条件判断,如果不符合条件才显示
th:switch 条件判断,进行选择性匹配
th:case 条件判断,进行选择性匹配
th:value 属性值修改,指定标签属性值
th:href 用于设定链接地址
th:src 用于设定链接地址
th:text 用于指定标签显示的文本内容
6.在html中使用thymeleaf标签时,同时会用到标准表达式,格式为:
${...} 变量表达式,主要用于获取上下文中的变量值。
*{...} 选择变量表达式,主要用于从被选定对象中获取属性值;如果没有选定对象,则和变量表达式一样。
#{...} 消息表达式,用于从properties中取值,常用来做页面国际化。
@{...} 链接表达式,一般用于页面跳转或者资源的引入;可以嵌套变量表达式进行变量拼接。
~{...} 片段表达式,用来标记一个片段模板,并根据需要移动或传递给其它模板;常结合th:insert或th:replace使用。
7.国际化页面配置方法
(1)在项目的resources文件夹下创建一个language文件夹,在这个文件夹中编写多个对应不同语言的国际化文件:language.properties、language_zh_CN.properties、language_en_US.properties
language.properties样例(默认用这个):
language.username=用户名
language.password=密码
language.button=登录
language_zh_CN.properties样例(中文):
language.username=用户名
language.password=密码
language.button=登录
language_en_US.properties样例(英文):
language.username=username
language.password=password
language.button=login
(2)在application.properties中增加配置:
#配置国际化文件基础名;格式为:包名.文件前缀名
spring.messages.basename=language.language
(3)在html页面中使用标签例如:
<button type="submit" th:text="#{language.button}">登录</button>
当th:text有值时,标签内容优先使用对应值;没有值时,才使用页面标签内编写的值。
这样就实现了页面国际化,当请求头中的语言信息不同时,就会读取不同的properties文件中对应的value;
例如Accept-Language:en-US,则读取language_en_US.properties;
如果没有匹配到,则使用默认的language.properties
(4)如果想增加手动切换语言的动能,则可以自定义区域解析器,并在html中增加修改按钮。
●在项目中增加一个配置类(使用@Configuration注解),实现LocaleResolver接口:
@Configuration
public class MyLocaleResovel implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
//获取自定义参数l,前端传来的(例如zh_CN,语言_地区)
String l = httpServletRequest.getParameter("l");
//获得请求头中的Accept-Language
String header = httpServletRequest.getHeader("Accept-Language");
Locale locale=null;
//如果不为空,则根据参数new一个Locale对象
if(!StringUtils.isEmpty(l)){
String[] split = l.split("_");
locale=new Locale(split[0],split[1]);
}
//否则根据请求头中默认的new一个Locale对象
else {
// Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
String[] splits = header.split(",");
String[] split = splits[0].split("-");
locale=new Locale(split[0],split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, @Nullable HttpServletResponse httpServletResponse, @Nullable Locale locale) {
}
//将自定义的MyLocaleResovel类重新注册为一个类型为LocaleResolver的Bean组件(把bean放入Spring容器,覆盖默认的LocaleResolver组件)
@Bean
public LocaleResolver localeResolver(){
return new MyLocalResovel();
}
}
●在html中使用a标签与th:href,传递参数l:
<a class="btn btn-sm" th:href="@{/toLoginPage(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/toLoginPage(l='en_US')}">English</a>
点击前端按钮,自定义区域解析器就会收到参数l,new一个Locale对象并存入spring容器,后续解析html中的Thymeleaf标签时就会使用不同的properties语言配置文件,就实现了手动切换语言。
=========================
七、springboot整合mybatis
1.准备好数据库与表
2.创建SpringBoot项目,pom.xml中要引入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.28</version>
<scope>runtime</scope>
</dependency>
3.在application.properties中配置数据库连接信息(或者在yaml,yml中配置):
spring.datasource.url=jdbc:mysql://localhost:3306/MyDataBaseName?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
4.创建数据库表对应的pojo类
5.编写service层代码
6_1.编写dao层代码,可以使用@Mapper注解标注类,使用@Select等注解标注方法并编写sql语句;
6_2.或者仅使用@Mapper注解标注dao层的类,在resources目录下创建xxxMapper.xml文件,在xml文件中编写sql语句;
同时,需要在配置文件application.properties中增加扫描xml的语句:
#配置mapper.xml的路径
mybatis.mapper-locations=classpath:mapper/*.xml
#配置xml文件中指定的实体类别名路径(可以省略xml中配置bean时的前缀,如com.xxx等)
mybatis.type-aliases-package=com.lagou.pojo
7.对于数据库中下划线命名的字段与pojo中驼峰命名的字段无法对应的问题,可以在application.properties中增加配置,开启驼峰命名匹配映射:
mybatis.configuration.map-underscore-to-camel-case=true
=========================
八、springboot整合jpa
1.pom.xml中添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
2.编写ORM实体类,使用@Entity注解标注类并指定对应的数据库表名;使用@Id标注主键,使用@GeneratedValue设置主键自增策略,使用@Column指定对应的表名(如果变量名与表名完全一致则可以省略),例:
@Entity(name = "m_user") //对应表名
public class User {
@Id //主键
@GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自增策略
private Integer id;
private String username;
private String password;
@Column(name = "a_id")
private Integer aId;
//省略get、set方法,实际使用时要加上
//省略toString方法
}
3.编写service层;如果涉及事务,可以在类上加@Transactional注解,表示其中的每个方法都是一个事务。
4.编写Repository接口(类似dao层)
(1)使用这个接口实例化的对象时,可以使用其中内置的方法
(2)如果内置的方法不够,可以新增方法,按照一定的语法规则编写方法名,即可在不写sql的情况下实现功能
(3)也可以新增方法,使用@Query注解+sql的形式实现;如果要使用原生sql,需要增加nativeQuery=true;如果要使用jpa的sql则不用写nativeQuery(默认false);如果涉及到数据的增删改,需要在方法上加@Modifying注解(只是查询则不要加,会报错)
例子如下:
public interface ResumeDao extends JpaRepository<Resume,Long>, JpaSpecificationExecutor<Resume> {
//jpa的sql
@Query("from Resume where id=?1 and name=?2")
public List<Resume> findByJpql(Long id, String name);
//原生sql,nativeQuery属性设置为true
@Query(value = "select * from tb_resume where name like ?1 and address like ?2",nativeQuery = true)
public List<Resume> findBySql(String name, String address);
//省略sql,直接按规则写方法名查询
public List<Resume> findByNameLikeAndAddress(String name, String address);
//原生sql,修改操作
@Modifying
@Query(value = "insert into tb_resume(name,address,phone) values(?1,?2,?3)",nativeQuery = true)
public void insertOne(String name, String address, String phone);
//省略sql的删除操作
@Modifying
public int deleteById(String id);
}
=========================
九、springboot整合redis(只操作redis)
1.下载redis并启动,同时可以下载RedisDesktopManager来管理redis
2.pom.xml中增加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.在application.properties中配置以下信息:
#redis地址
spring.redis.host=127.0.0.1
#redis端口
spring.redis.port=6379
#redis密码,默认是空密码
spring.redis.password=
4.编写pojo类,使用@RedisHash、@Id、@Indexed注解标注,指定操作这个javabean时在redis中的存储空间
例:
//指定这个javabean在redis数据库中的存储空间
//此处表示针对Person这个实体类的数据操作都存储在redis中名为persons的存储空间下
@RedisHash("persons")
public class Person {
@Id //主键,在redis数据库中会默认生成字符串形式的HashKey表示唯一实体对象id(也可以手动指定id)
private String id;
@Indexed //标识对应属性在redis数据库中生成二级索引,索引名称就是属性名,方便进行数据条件查询
private String firstname;
@Indexed
private String lastname;
//省略get、set方法,实际使用时要加上
//省略构造方法,使用时要加无参构造方法
//省略toString()方法
}
5.编写Repository接口。需要注意,继承的是CrudRepository(而不是整合jpa时的CrudRepository)
public interface PersonRepository extends CrudRepository<Person,String> {
List<Person> findByFirstname(String name);
}
6.编写测试类,使用@Autowired将PersonRepository注入,new一个Person对象,即可进行redis的存取操作。
PersonRepository的使用方法同JpaRepository。
=========================
十、springboot整合jpa与redis实现缓存,基于注解方式
1.按照springboot整合jpa的步骤,搭建好环境。(Repository继承JpaRepository即可)
2.下载redis并启动,同时可以下载RedisDesktopManager来管理redis
3.pom.xml中增加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
4.在application.properties中配置以下信息:
# MySQL数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabasename?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
#显示使用jpa进行数据库查询的sql语句,方便调试
spring.jpa.show-sql=true
#开启驼峰命名匹配映射
mybatis.configuration.map-underscore-to-camel-case=true
#解决http乱码用
spring.http.encoding.force-response=true
#redis地址
spring.redis.host=127.0.0.1
#redis端口
spring.redis.port=6379
#redis密码,默认是空密码
spring.redis.password=
5.在启动类XXXApplication.java中的类上加注解@EnableCaching,开启基于注解的缓存支持。
6.在service层中的查询方法上使用@Cacheable注解,程序在执行查询方法时,就会先查redis缓存,如果不存在,则查数据库并将结果存入redis缓存,返回查询结果;如果存在,则直接从redis缓存中获得结果返回,不再查询数据库。
//cacheNames是redis中对应结果的存储空间名
//unless是说,返回为null时,则不存入缓存
//key是指redis的cacheNames指定的存储空间中的key,对应的value是结果;查询时,会根据传入的参数,按照一定的规则转换为key,然后从redis中寻找有没有value
@Cacheable(cacheNames = "article",unless = "#result==null",key = "#pageable.pageNumber+'_'+#pageable.pageSize")
注意,也可以只使用@Cacheable注解,不配置cacheNames、unless、key,此时会按照默认方法把入参按照一定规则自动生成key。
7.在service层中的更新方法上使用@CachePut注解,程序执行该方法时,会先更新数据库,成功后更新缓存。(注意cacheNames与key要与@Cacheable中的对应)
8.在service层中的删除方法上使用@CacheEvict注解,程序执行该方法时,会先删除数据库中的数据,成功后删除缓存数据。(注意cacheNames与key要与@Cacheable中的对应)
9.如果将pojo类存入redis时报错,则要在pojo类上实现序列化接口,implements Serializable。
=========================
十一、springboot整合jpa与redis实现缓存,基于API方式
1.基本流程同上,只是service中不使用@Cacheable、@CachePut、@CacheEvict注解;启动类不使用@EnableCaching注解。
2.service层中,使用@Autowired注解将XXXRepository注入,使用这个进行数据库存取。
3.service层中,使用@Autowired注解将RedisTemplate注入,使用这个进行redis存取,例:
//从redis中根据id获取值
Object o = redisTemplate.opsForValue().get("user_" + id);
//从数据库中获取User,然后存入redis,并设置这个值的缓存有效期为1天
Optional<User> byId = userRepository.findById(id);
if(byId.isPresent()){
User user = byId.get();
redisTemplate.opsForValue().set("user_"+id,user,1,TimeUnit.DAYS);
}
4.然后就可以自己实现数据库与redis缓存之间的代码逻辑了。
=========================
十二、自定义redis缓存序列化机制
当使用Redis可视化管理工具Redis Desktop Manager查看缓存数据时,redis缓存数据的默认格式是HEX,不方便阅读;
此时可以通过自定义redis缓存序列化的方法,让redis缓存保存为方便阅读的形式,例如json格式。
1.如果基于注解实现了redis缓存,则需要新建一个用@Configuration注解标注的类,使用@Bean注解将RedisCacheManager对象自定义并装入spring容器,实现自定义序列化方式。(当存在自定义序列化方式时,则不会使用默认序列化方式)
2.如果基于API实现了redis缓存,则需要新建一个用@Configuration注解标注的类,使用@Bean注解将RedisTemplate对象自定义并装入spring容器,实现自定义序列化方式。(当存在自定义序列化方式时,则不会使用默认序列化方式)
例:
@Configuration
public class RedisConfig {
// 自定义一个RedisTemplate,对API实现redis缓存生效
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 创建JSON格式序列化对象,对缓存数据的key和value进行转换
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);
//设置redisTemplate模板API的序列化方式为json
template.setDefaultSerializer(jackson2JsonRedisSerializer);
return template;
}
// 自定义一个RedisCacheManager,对注解实现redis缓存生效
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
RedisSerializer<String> strSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jacksonSeial =
new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 定制缓存数据序列化方式及时效
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(jacksonSeial))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
}
}