微服务:每一个功能元素最终都是一个可独立替换和独立升级的软件单元;
开始的helloworld:
1创建一个maven工程:
2添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3启动程序
//@SpringBootApplication 来标注一个主程序,说明这是一个SpringBoot应用
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
//spring应用启动起来
SpringApplication.run(HelloApplication.class,args);
}
}
4测试访问 http://localhost:8080/aa
@Controller
public class HelloController {
@ResponseBody
@RequestMapping("/aa")
public String aa(){
return "hello world !";
}
}
如果是部署
在pom/xml文件引用插件 <!-- 这个插件,可以将应用打包成一个可执行的jar包;-->
导入这个maven插件,利用idea(package)打包,生成的jar包,可以使用java -jar xxx.jar
启动
Spring Boot 使用嵌入式的Tomcat无需再配置Tomcat(jar 包的classes文件)
starters:Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来,要用什么功能就导入什么场景的启动器。
其实starters的作用就是简化以来配置,其它的啥事情也没做。
66666666666666
将springboot跑起来,开始了解启动原理,原理了解了,才会更好的学习
但是了解之前首先要对spring的一些注解有所了解
@ComponentScan 包扫描注解,对应配置文件xml中<context:component-scan>标签
@Configuration表示这是这个类是一个主配置类,对应的就是xml文件
@Bean 表示想容器中注册一个组件和@Component的作用是一样的
@import容器中注册一个组件(标注再类上面)id是全类名
public class C {
}
@import通过 ImportSelectort通过选择器注册组件
public class TestImportSelector implements ImportSelector {
//返回值,就是到导入到容器中的组件全类名
//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.ecpss.config.A"};
}
}
@import通过实现ImportBeanDefinitionRegistrar的接口注册组件
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(Chaoshen.class);
registry.registerBeanDefinition("chaoshen", beanDefinition);
}
}
@Condition(按照条件进行判断,满足条件就注册组件)精华注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
Condition是一个接口
public interface Condition {
/**
* ConditionContext:判断条件能使用的上下文(环境)
* AnnotatedTypeMetadata:注释信息
*/
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
要有判断条件 ,必须实现Condition接口
可以看到我们可以继续注册bean ,移除bean,判断bean......
public class Rcondition implements Condition{
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata)
{
BeanDefinitionRegistry registry = conditionContext.getRegistry();
boolean b = registry.containsBeanDefinition("getXas");//判断容器中是否有getXas的bean
if(b){
return true;
}
return false;
}
@Conditional(Rcondition.class)//判断容器中是否有getXas的bean
@Bean("b")//如果上面的条件成立那就注册一个名字为b的组件
public Po1 p1(){
return new Po1("bili");
}
Conditional(可以加载类上面,也可放在方法上)
还可以通过factorybean注册组件
@ConfigurationProperties注解
@Component
@Data
@ConfigurationProperties(prefix = "person")//默认从全局配置文件中获取值;
/**
* 1使用ConfigurationProperties标注的对象必须是springboot中的一个组件@Component
* 2使用ConfigurationProperties将配置文件中的对象属性和对象属性进行绑定
* 3前缀使用person
*/
public class Person {
@Value("${person.last-name}")
private String lastName;
private Integer age;
@Value("true")
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
application.yml中的person属性
person:
lastName: zhangsan 1字符串
age: 12 2整数
boss: true 3boolean
birth: 2106/1/1 4日期
maps: {k1: v1,k3: 12} 5map集合的行内写法 2 还可以 maps:
k1: v1
k2: v2
lists: 6list集合 2 还可以行内写法[1,2,3,4]
-1
-2
-3
dog: 7对象
name: xiao
age: 12
@value("12")//设置值为12
@value("${lastName}")//读取配置文件中的lastName值
等于如下设置值的方式<bean id="b1">
<property name="name" value="12"/>
</bean>
test:如下
@Autowired
Person person;
@Test
public void contextLoads() {
System.out.println(person);
}
@ConfigurationProperties支持JSR303数据校验 @value不支持
注意:使用ConfigurationProperties注解所有的变量属性都是写在全局变量中的,默认从全局配置文件中读取值
@PropertySource
读取指定文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值
(1)classpath下的配置文件
使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值
(2)本地环境下
@ImportResource:
导入Spring的配置xml,让配置文件里面的内容生效;
日志
日志门面: SLF4J;
日志实现:Logback;
以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;
给系统里面导入slf4j的jar和 logback的实现jar
如何让系统中所有的日志都统一到slf4j;
1、将系统中其他日志框架先排除出去;
2、用中间包来替换原有的日志框架;
3、我们导入slf4j其他的实现
日志使用;
1、默认配置
SpringBoot默认帮我们配置好了日志;
//记录器
Logger logger = LoggerFactory.getLogger(getClass());
/日志的级别;
//由低到高 trace<debug<info<warn<error
//可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效
logger.trace("这是trace日志...");
logger.debug("这是debug日志...");
//SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;root级别
logger.info("这是info日志...");
logger.warn("这是warn日志...");
logger.error("这是error日志...");
使用指定配置日志文件
给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了
就可以了。
web开发
1)、创建SpringBoot应用,选中我们需要的模块;
2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
3)、自己编写业务代码;
SpringBoot对静态资源的映射规则;WebMvcAutoConfiguration
有了前面的基础,我们直接看WebMvcAutoConfiguration的源码:
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)//判断是否web环境
//判断环境中有Servlet DispatcherServlet WebMvcConfigurer这三个类
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//当前容器中没有WebMvcConfigurationSupport这个bean
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
//加载的优先级为Ordered.HIGHEST_PRECEDENCE + 10 –> Integer.MIN_VALUE + 10
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
//在DispatcherServletAutoConfiguration, ValidationAutoConfiguration 加载后进行装配。
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
WebMvcAutoConfiguration中有的内部类
@Configuration
//导入EnableWebMvcConfiguration这个组件
@Import(EnableWebMvcConfiguration.class)
//导入WebMvcProperties ResourceProperties 两个组件,并且与配置文件的属性绑定
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter
implements WebMvcConfigurer, ResourceLoaderAware {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(
this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod))
.setCacheControl(cacheControl));
}
}
// private String staticPathPattern = "/**";
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/" };
"/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射 (映射到根路径下面的各种静态资源文件文件夹)
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":当前项目的根路径
欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;
localhost:8080/ 找index.html页面
@EnableWebMvcConfiguration
模板引擎Freemarker、Thymeleaf
1引入thymeleaf;
//依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Thymeleaf使用
只要我们把HTML页面放在classpath:/templates/,thymeleaf就能自动渲染
使用:
1、导入thymeleaf的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
2、使用thymeleaf语法;
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>成功!</h1>
<!--th:text 将div里面的文本内容设置为 -->
<div th:text="${hello}">这是显示欢迎信息</div>
</body>
</html>
@responseBody是页面没有跳转直接返回json字符串
错误处理机制
springboot返回默认的错误页面--》
ErrorMvcAutoConfiguration
定制错误页面
(1)有模板引擎的情况下:【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
(2)没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
(3)以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
异常处理器
@ControllerAdvice
public class MyExcetionHandler {
//浏览器客户端返回的都是json ---没有自适应
@ResponseBody
@ExceptionHandler(Exception.class)
public Map<String,Object> handleException(Exception e){只要 出现异常springmvc 就会调
用这个方法处理异常
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
//自适应
@ExceptionHandler(Exception.class)
public String handleExceptionAdapter(Exception e, HttpServletRequest request){只要
出现异常springmvc 就会调用这个方法处理异常
Map<String,Object> map = new HashMap<>();
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.notexist");
map.put("message",e.getMessage());
//转发到/error
return "forward:/error";
}
}
SpringBoot默认使用Tomcat作为嵌入式的Servlet容器;
注册Servlet三大组件【Servlet、Filter、Listener】
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
public class Mylistener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("启动");//web服务器的启动监听器
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
//默认拦截: / 所有请求;包静态资源,但是不拦截jsp请求; /*会拦截jsp
//可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
-----------------------------------------
切入成嵌入式容器
切换成jetty排除tomcat-stater直接引入jetty-starter
缺点:默认不支持jsp
要使用jsp
外面安装Tomcat---应用war包的方式打包;
docker
SpringBoot与数据访问
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.url=jdbc:mysql://127.0.0.1/abcd?serverTimezone=GMT
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
启动过程中会出现这段话:所有用下面的驱动
DataSourceConfiguration数据源的配置,对于数据源做了各种条件判断默认使用HikariDataSource的数据源
DataSourceInitializer自动建表
整合Druid数据源
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
指定使用的连接池
需要我们配置一些参数属性配置没有对应
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")//参数绑定
@Bean
public DruidDataSource dataSource(){
return new DruidDataSource();
}
整合mybatis
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
(注解方式)
1.配置数据源
2建表
3javabean
4mapper接口
5测试@RestController==@Controlle+@responseBady
没有指定@mapper ,使用MapperScan批量扫描所有的Mapper接口;
(2)配置文件的方式
1mybatis的主配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="com.example.javabean.Users" alias="users"></typeAlias>
</typeAliases>
<!--因为已经配置了资源映射了-所以不用在映射资源文件了-->
<!--<mappers>-->
<!--<mapper resource="mybatis/mappers/UserMapper.xml"/>-->
<!--</mappers>-->
</configuration>
2映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUser" resultType="users">
select * from user where id = #{id}
</select>
</mapper>
3.springboot配置文件配置mybatis指定 全局配置文件的位置和sql映射文件的路径
mybatis.config-location=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mappers/*.xml
-----------------------------扩展- 整合通用mapper--------------------------------------------------
1. 配置文件
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.url=jdbc:mysql://127.0.0.1/abcd?serverTimezone=GMT
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 打印日志到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2.实体
@Data
@NoArgsConstructor
@Table(name = "userd1")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String addx;
}
3 接口
@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User> {
}
4 测试
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void test1(){
User user=new User();
user.setName("dec");
user.setAddx("re");
User one = userMapper.selectOne(user);
System.out.println(one.getId());
}
}
图片项目结构
-----------------------------------------------------------------------------------------
整合SpringData JPA
1编写一个实体类(bean)和数据表进行映射,并且配置好映射关系;
//使用JPA注解配置映射关系
@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "tbl_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user;
public class User {
@Id //这是一个主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键
private Integer id;
@Column(name = "last_name",length = 50) //这是和数据表对应的一个列
private String lastName;
@Column //省略默认列名就是属性名
private String email;
2配置数据源和jpa的一些配置
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.url=jdbc:mysql://127.0.0.1/jpa?serverTimezone=GMT%2b8
#serverTimezone=GMT%2b8 解决了mysql数据库 相差8个小时的差距
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
3编写一个Dao接口来操作实体类对应的数据表(Repository)
public interface UserRepository extends JpaRepository<User,Integer> {
}
4.测试
@RestController
public class UserController {//运行localhost:8080/name=aa&address=bb
@GetMapping("/user")
public User insertUser(User user) {
User save = userRepository.save(user);
return save;
}
}
-----------------------第一步还可以使用xml进行数据库表的生成---------
1 配置文件
spring.jpa.mapping-resources=/META-INF/orm.xml
2 orm.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD
3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- <property name="connection.datasource">java:comp/env/jdbc/ds_riskcontrol</property> -->
<property
<mapping resource="META-INF/orm/usertype.hbm.xml"/>
<mapping resource="META-INF/orm/TaskLock.hbm.xml"/>
</session-factory>
</hibernate-configuration>
3 TaskLock.hbm.xml文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field">
<class name="com.ecpss.directsettle.domain.order.Order" table="ds_order" batch-size="20">
<comment>订单</comment>
<id name="id" access="property">
<column name="id" not-null="true">
<comment>主键</comment>
</column>
<generator class="sequence">
<param name="sequence_name">SEQ_DS_ORDER</param>
</generator>
</id>
<version name="version" type="long">
<column name="version" default="0" not-null="true">
<comment>版本号</comment>
</column>
</version>
<property name="createTime" type="timestamp" update="false">
<column name="create_time" sql-type="date" not-null="true" default="systimestamp">
<comment>创建时间</comment>
</column>
</property>
<property name="updateTime" type="timestamp">
<column name="update_time" sql-type="date" not-null="true">
<comment>更新时间</comment>
</column>
</property>
<property name="systemNo">
<column name="system_no" unique="true" not-null="true">
<comment>流水号</comment>
</column>
</property>
<property name="orderStatus">
<column name="order_status" not-null="true">
<comment>订单状态</comment>
</column>
<type name="org.hibernate.type.EnumType">
<param name="enumClass">com.ecpss.directsettle.domain.order.status.OrderStatus</param>
<param name="type">12</param>
</type>
</property>
<property name="orderNo">
<column name="order_no" unique="true" not-null="true">
<comment>订单号</comment>
</column>
</property>
<property name="amount" type="com.ecpss.directsettle.comnport.jpa.hibernate.MoneyUT">
<column name="order_amount" not-null="true">
<comment>订单金额</comment>
</column>
</property>
<property name="refundStatus">
<column name="refund_status" not-null="true" default="0" length="1">
<comment>是否退款</comment>
</column>
</property>
</class>
</hibernate-mapping>
@Column(columnDefinition="int default 0")
private boolean kuozhan;
@Column(columnDefinition="varchar(255) default 'aa'")
private String test;
//时间配置注解版
@Column(columnDefinition="timestamp",insertable=false,updatable = false)
@Temporal(TemporalType.TIMESTAMP)
private Date createTime;
等于
<property name="refundStatus">
<column name="refund_status" not-null="true" default="0" length="1">
<comment>是否退款</comment>
</column>
</property>
自定义starter
高级部分
缓存 直接缓存配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
步骤:1开启基于注解的缓存
2标注缓存注解即可 :
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存。(方法运行之后)
开启日志
CacheManager中管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每个缓存组件都有自己的唯一名字;
属性:
- CacheName/value:指定存储缓存组件的名字
- key:缓存数据使用的key,可以使用它来指定。默认是使用方法参数的值,1-方法的返回值
- 编写Spel表达式:#id 参数id的值, #a0/#p0 #root.args[0]
- keyGenerator:key的生成器,自己可以指定key的生成器的组件id
- key/keyGendertor二选一使用
- cacheManager指定Cache管理器,或者cacheReslover指定获取解析器
- condition:指定符合条件的情况下,才缓存;
- unless:否定缓存,unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断
- sync:是否使用异步模式,unless不支持
原理:CacheAutoConfiguration 导入
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
debug=true;看哪个缓存配置生效
SimpleCacheConfiguration生效 作用给容器注册一个CacheManager:ConcurrentMapCacheManager
它也是保存缓存数据的容器
key 默认是参数作为key.
一般情况下是使用缓存中间件
redis
1、导入依赖(并且之前已经安装redis)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、修改配置文件
spring.redis.host=localhost
3、添加测试类
@Autowired
StringRedisTemplate stringRedisTemplate;//操作字符串【常用】
@Autowired
RedisTemplate redisTemplate;//操作k-v都是对象
@Test
public void test01(){
// stringRedisTemplate.opsForValue().append("msg", "hello");//字符串
String msg = stringRedisTemplate.opsForValue().get("msg");
//集合
stringRedisTemplate.opsForList().leftPush("l1","1");
stringRedisTemplate.opsForList().leftPush("l1","aa");
stringRedisTemplate.opsForList().leftPush("l1","2");
System.out.println(msg);
}
springboot 默认使用jdk底层的序列化器,所以我们需要自己使用json序列化器(需要自定义)
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> jsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(jsonRedisSerializer);
return template;
}
}
测试:
@Autowired
RedisTemplate<Object,Employee> empRedisTemplate;
@Test
public void test02(){
Employee emp = employeeMapper.getEmpById(2);
empRedisTemplate.opsForValue().set("emp-01", emp);
}
RedisCacheManager操作redis的缓存
整合mq
Message 消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组 成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出 该消息可能需要持久性存储)等。
Publisher 消息的生产者,也是一个向交换器发布消息的客户端应用程序。
Exchange 交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。 Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有 所区别
Queue 消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息 可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
1.安装rabbitmq,选中
2配置文件配置 3测试
spring:
rabbitmq:
host: 10.138.223.126
port: 5672
username: guest
password: guest
rabbitTemplate 给rabbitmq发送和接收消息组件
springboot与任务
(1)异步任务
@EnableAsync 和@Async注解配合使用(3步)
1.service
@Service
public class AsynService {
@Async
public void hello(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务处理中");
}
}
2.controller
@RestController
public class AsysController {
@Autowired
public AsynService asynService;
@RequestMapping("/asyn")
public String success(){
asynService.hello();
return "success";
}
}
3.在启动类上标注@EnableAsync
//结束了
(2)定时任务
@Scheduled()和@EnableScheduling 两个注解的配合使用(2步)
1.service
@Service
public class SchedualService {
@Scheduled(cron = "0 * * * * MON-FRI")
public void hello(){
System.out.println("hello");
}
}
2.在启动类上标注@EnableScheduling
--------------------------------------cron 表达式------
cron一共有7位,但是最后一位是年,可以留空,所以我们可以写6位:
cron 表达式(6位)
second , minute, hour, day of month, month and day of week.
demo:
0 * * * * MON-FRI ---不管那一天那一月,那一小时,星期一到星期五的整秒进行运行
(
-代表区间,第一位替换
1-4 (表示1到4秒都要运行
)
(
,枚举,第一位替换
1,2,3,4(表示1到4秒都要运行
)
(/ 步长 代表多长时间执行一次
0/4 (表示从零秒开始,每隔四秒执行一次
)
cron表达式:
| 字段 | 允许值 |允许的特殊字符 |
| :--- | :---------------------- |:---------------- |
| 秒 | 0-59 |, - * / |
| 分 | 0-59 | , - * / |
| 小时 | 0-23 | , - * / |
| 日期/天 | 1-31 | , - * ? / L W C |
| 月份 | 1-12 | , - * / |
| 星期 |1-7,1表示星期天,2表示星期一| , - * ? / L C # |
------------------------------------------------------
| ? | 日/星期冲突匹配 |
| L | 最后 |
| W | 工作日 |
| C | 和calendar联系后计算过的值 |
| # | 星期,4#2,第2个星期四
最难的就是问号。
0 0 3 * * ? 每天3点执行 ,因为是每一天,所以第六位星期的位置,我们是不需要关注的,就是不确定的值。
日期和星期是两个相互排斥的元素,通过问号来表明不指定值。比如,1月10日,比如是星期1,如果在星期的
位置是另指定星期二,就前后冲突矛盾了。
0 10 3 ? * 1 每周星期天,3点10分 执行,注:1表示星期天
(3)邮件任务
以qq用户发送到163邮箱为例,qq用户发送消息到qq邮箱服务器,qq邮箱服务器将消息发送到163服务器
163用户读取163服务器的消息
1.加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
2.邮件的自动配置类MailSenderAutoConfiguration的MailProperties查看配置属性
在配置文件中配置:
spring.mail.host=smtp.qq.com
spring.mail.password=12345
spring.mail.username=3075763007@qq.com
spring.mail.properties.mail.stmp.ssl.enable=true
//注意:在spring.mail.password处的值是需要在邮箱设置里面生成的授权码,这个不是真实的密码。
3.
@Autowired
JavaMailSender javaMailSender;
@Test
public void contextLoads() {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();//简单邮件
simpleMailMessage.setSubject("天气问题");//设置主题
simpleMailMessage.setText("天气真好");//设置发送内容
simpleMailMessage.setTo("3075763007@qq.com");//给谁发
javaMailSender.send(simpleMailMessage);
}
---------------------------------
@Autowired
JavaMailSender javaMailSender;
@Test
public void contextLoads1()throws Exception{
//发送复杂邮件
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
mimeMessageHelper.setSubject("吃饭");
mimeMessageHelper.setText("内容");
mimeMessageHelper.setTo("发送到哪里");
mimeMessageHelper.addAttachment("1.jpg",new File("文件地址"));
javaMailSender.send(mimeMessage);
}
整合quartz
1 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2 job --继承QuartzJobBean
public class TestQuartz extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("cccc");
}
}
3 配置类 --trigger 和jobDetail
@Configuration
public class TestCongfig {
@Bean
public JobDetail jobDetail(){
return JobBuilder.newJob(TestQuartz.class)
.withIdentity("testjob").storeDurably().requestRecovery(true).build();
}
@Bean
public Trigger getTrigger(){
return TriggerBuilder.newTrigger().withIdentity("trigger").forJob(jobDetail())
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build();
}
}
//补充 日历方式
CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
安全问题
安全框架(shiro,spring Security)
这里整合spring Security
1.依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.创建Spring Security的配置类WebSecurityConfig
@EnableWebSecurity//通过@EnableWebSecurity注解开启Spring Security的功能
public class Mysecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//定制请求的授权规则
http.authorizeRequests().antMatchers("/").permitAll().
antMatchers("/level1/**").hasRole("USER").
antMatchers("/level2/**").hasRole("vip2").
antMatchers("/level3/**").hasRole("vip3");
//开启登陆功能
//1.发送登陆/login请求,如果没有登陆页面,就来到登陆页面
//2.重定向到/login?error表示认证失败
http.formLogin();
//开启注销功能,访问/login注销,清空session
http.logout().logoutSuccessUrl("/");//注销成功后来到首页
}
//定义认证规则
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).
withUser("admin").
password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
}
}
<div sec:authorize="!isAuthenticated()">
<h2 align="center">游客您好,如果想查看xxx <a th:href="@{/login}">请登录</a></h2>
</div>
<div sec:authorize="isAuthenticated()">
<h1>欢迎<span sec:authentication="name"></span>登陆,当前的角色为<span
sec:authentication="principal.authorities"></span></h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
</form>
</div>
//名称空间
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
--------------------------------------------------------------------------
这里搞死我了!!!
我这里默认的版本是2.1.4,但是sec变迁无效
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
这里不管是5还是改成4都无效
--------------------------------------------
所以这一一定是版本不兼容的问题,名称空间不用改还是4
方法一:降低boot版本到2.0.7加上如下依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.2.RELEASE</version>
</dependency>
方法二:修改boot版本到2.1.2加上如下依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
-------------------------------------
修改boot版本pom文件这里直接修改想要的版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>2.1.1.RELEASE</version>
</parent>
springboot与检索 elasticsearch
springboot 默认是使用springdata操作es
两种方法(1)
Jest方式 默认不生效,需要导入jest客户端依赖
(1)依赖
<dependency>
<groupId>io.searchbox</groupId>
<artifactId>jest</artifactId>
<version>5.3.3</version>
</dependency>
(2)
spring.elasticsearch.jest.uris=http://192.168.88.129:9200 默认使用本地(启动es服务啊!)
(3)启动 出现Setting server pool to a list of 1 servers: [http://localhost:9200] 可以了
(4) 商品类
@Data
public class Goods {
// 表示这是一个主键
@JestId
private Integer id;
private String name;
private String desc;
private double price;
}
(5) 测试
@Autowired
JestClient jestClient;
@Test
public void testJestEsAdd() throws Exception {
Goods goods = new Goods().setId(1).setName("好看的").setDesc("这个神木").setPrice(11.3);
Index index = new Index.Builder(goods).index("goods").type("good").build();
jestClient.execute(index);
}
@Test
public void testJestEsQuery() throws Exception {
String json="{\n" +
" \"query\":{\n" +
" \"match\": {\"name\":\"red\"}\n" +
" }\n" +
"}";
Search build = new Search.Builder(json).addIndex("movie_index").addType("movie").build();
SearchResult result = jestClient.execute(build);
System.out.println(result.getJsonObject());
}
(2)spring data es
(1)依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
(2)配置文件配置
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=192.168.88.129:9300
启动——————出现--》Adding transport node : 127.0.0.1:9300 说明启动成功
rocketmq
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.2.0</version>
</dependency>
步骤
1.创建DefaultMQProducer 创建一个消息生产者,并设置一个消息生产者组
2.指定 NameServer 地址
3.开启DefaultMQProducer
4.创建Message
5.发送消息
6.关闭DefaultMQProducer
springboot整合dubbo
docker pull zookeeper
docker run --name zookeeper -p 2181:2181 --restart always -d zookeeper
创建一个empty工程。然后file --module
场景步骤:
1需要将服务提供者注册到注册中心
2然后消费者到注册中心订阅服务
做法步骤:
服务提供者
1引入依赖dubbo 和zk
2配置dubbo扫描包和注册中心
3使用@service 发布服务
pom
<!--启动器-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>
<!--zk客户端工具-->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
(1)服务提供者配置
#dubbo应用的名字
dubbo.application.name=provider-ticket
#注册中心的地址
dubbo.registry.address=zookeeper://192.168.88.128:2181
# 将service下面的包发布出去
dubbo.scan.base-packages=com.ecpss.ticket.service
(2)服务结构
(3)接口
(4)接口的实现
@Component
@Service // 这个是dubbo @Service 作用将服务发布出去
public class TicketServiceImpl implements TicketService{
@Override
public String getTicket() {
return "《狂神》";
}
}
服务消费者3步骤:
——————————————————————————————————————————————————————————————————
1(配置 只需要配置 dubbo应用名称 和 注册中心)
1引入和服务端相同的依赖
2配置dubbo注册中心地址
#应用名
dubbo.application.name=consumer-user
#注册中心
dubbo.registry.address=zookeeper://192.168.88.128:2181
——————————————————————————————————————————————————————————————————
2(一模一样的接口)
package com.ecpss.ticket.service;
public interface TicketService {
String getTicket();
}
——————————————————————————————————————————————————————————————————————
3(调用服务)
@Service//spring 的组件servcie
public class UserService {
@Reference //引用服务(按照全类名)
TicketService ticketService;
public void hello(){
String ticket = ticketService.getTicket();
System.out.println("您已经成功买票:"+ticket);
}
}
——————————————————————————————————————————————
//最后测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class Springboot14ConsumerUserApplicationTests {
@Autowired
UserService userService;
@Test
public void contextLoads() {
userService.hello();
}
}
扩展:
1我们一般在幂等操作加上重试次数(@Reference(retries = 1 )比如查询,删除,修改(消费者端)
2版本控制消费者和服务提供者需要一样版本
@Reference(retries = 1,timeout = 3000,version = "1.0")
3我们一般在消费者端设置超时时间默认1000ms如果因为网络问题没有响应就报错,为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。
@Reference(retries = 1,timeout = 3000,version = "1.0") //消费者优先于服务提供者
springboot整合cloud (通过轻量级http进行通信的)
简约三部:注册中心,提供者,消费者--需要三个模块。
这里每个模块的依赖完全一样
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
1 注册中心模块2
1依赖
2配置
server:
port: 8761
eureka:
instance:
hostname: eureka-server #实例的主机名
client:
register-with-eureka: false #不把自己注册到euraka上
fetch-registry: false #不从euraka上来获取服务的注册信息(因为不同于消费者需要获取注册中心的信息)
service-url: #指定注册中心的地址,其他服务在这里注册
defaultZone: http://localhost:8761/eureka/
3.在主配置类上添加注解@EnableEurekaServer 启动注册中心,完成。
4.访问http://localhost:8761/
2 服务提供者模块
1服务
@Service
public class TicketSerivce {
public String getTicket() {
return "《大话西游》";
}
}
@RestController
public class TicketController {
@Autowired
TicketSerivce ticketSerivce;
@GetMapping("/ticket")
public String getTicket() {
return ticketSerivce.getTicket();
}
}
---------------------------------------------------
2配置
server:
port: 8001
spring:
application:
name: provider-ticket
eureka:
instance:
prefer-ip-address: true #注册是服务使用IP地址
client:
service-url:
defaultZone: http://localhost:8761/eureka/
--------------------------------------------------
3启动访问http://localhost:8001/ticket
3 消费者模块
1消费
@RestController
public class UserController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/buy")
public String buyTicket(String name){
//使用http 加上 服务提供者暴露的实例名称 加上 配置请求/ticket
String s = restTemplate.getForObject("http://PROVIDER-TICKET/ticket",
String.class);//传入什么类型返回什么类型
return name+"购买了"+" "+s;
}
}
2配置
spring:
application:
name: consumer-user
server:
port: 8200
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://localhost:8761/eureka/
3在主启动类上使用@EnableDiscoveryClient注解发现服务并写一个配置类注入RestTemplate
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced//启用负载均衡 客户端
public RestTemplate getRestemplete(){
return new RestTemplate();
}
}
4启动访问http://localhost:8200/buy?name=cc
Eureka页面服务
热部署插件加ctrl+f9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
-------------------------------------------------------------------------------------------------------
springmvc
1视图解析器
2扩展springmvc
编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc**
注意:
WebMvcConfigurer可以自己定制springmvc的功能,扩展的时候只要重写它的方法就行了
1)、WebMvcAutoConfiguration是SpringMVC的自动配置类
2)、在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class)
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//从容器中获取所有的WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
//一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用;
@Override
// public void addViewControllers(ViewControllerRegistry registry) {
// for (WebMvcConfigurer delegate : this.delegates) {
// delegate.addViewControllers(registry);
// }
}
}
}
3)、容器中所有的WebMvcConfigurer都会一起起作用;
4)、我们的配置类也会被调用;
效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
@Configuration
public class Myc implements WebMvcConfigurer{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/xc").setViewName("success");
}
}
//功能发送/xc请求,映射到success.html页面
原理:WebMvcAutoConfiguration是SpringMVC的自动配置类内部类WebMvcAutoConfigurationAdapter
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter
implements WebMvcConfigurer, ResourceLoaderAware {}
WebMvcAutoConfigurationAdapter在做其他自动配置时会导入;@Import(**EnableWebMvcConfiguration**.class)
EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {//自动装配所以的
configurer
//一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用;
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
拦截器机制登录检查
1 写一个拦截器
2 配置 ,加入到容器中
1--------------------------
public class LoginHandlerInterceptor implements HandlerInterceptor {
//目标方法执行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if(user == null){
//未登陆,返回登陆页面
request.setAttribute("msg","没有权限请先登陆");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
//已登陆,放行请求
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2------------------
@Configuration
// WebMvcConfigurerAdapter过时,使用WebMvcConfigurer接口
public class MyMvcConfig implements WebMvcConfigurer {
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//super.addInterceptors(registry);
//静态资源; *.css , *.js
//SpringBoot已经做好了静态资源映射
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login");
}
/** 任意路径下的任意请求,excludePathPatterns一些请求,/登录请求。
源码解析:springboot的主入口类
@SpringBootApplication : Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
-
@SpringBootConfiguration : Spring Boot的配置类,标注在某个类上,表示这是一个Spring Boot的配置类
-
@Configuration : 配置类上来标注这个注解,配置类也是容器中的一个组件@Component
-
@EnableAutoConfiguration:开启自动配置功能
分析源码可以知道:SpringBoot的主入口类为run方法然后进入
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//保存主配置类
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//判断当前是否一个web应用
this.webEnvironment = deduceWebEnvironment();
//从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//从多个配置类中找到有main方法的主配置类
this.mainApplicationClass = deduceMainApplicationClass();
}
它实际上会构造一个SpringApplication的实例,然后运行它的run方法:
其中:setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 这里的入参type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 使用Set保存names来避免重复元素
Set<String> names = new LinkedHashSet<>(//loadFactoryNames注意这里是真正的加载的地方
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根据names来进行实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 对实例进行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader
classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,
Collections.emptyList());
}
//加载实现的地方
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
找到自动配置starter的 META-INF/spring.factories下面的配置文件的ApplicationContextInitializer
spring-boot和spring-boot-autoconfigure
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
通过构造方法,和参数实例,然后返回。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
也是一样:得到ApplicationListener(注意是listener)10个
run方法
//获取SpringApplicationRunListeners;从类路径下META-INF/spring.factories
SpringApplicationRunListeners listeners = getRunListeners(args);
//回调所有的获取SpringApplicationRunListener.starting()方法
listeners.starting();
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//准备上下文环境;将environment保存到ioc中;而且applyInitializers();
//applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
//回调所有的SpringApplicationRunListener的contextPrepared();
//prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
//整个SpringBoot应用启动完成以后返回启动的ioc容器;
return context;
run方法里面有
创建Spring上下文 context = createApplicationContext();
Spring上下文前置处理 prepareContext(context, environment, listeners, applicationArguments,printedBanner);
Spring上下文刷新refreshContext(context);
Spring上下文后置处理afterRefresh(context, applicationArguments);
--------------------------------------------自动装配------------------------------------------
@SpringBootApplication,通过使用它,不仅仅能标记这是一个 Spring Boot 应用,而且能够开启自动配置的功能。这是为什么呢?
SpringBootConfiguration//表明这是一个springboot的配置类
以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能;这样自动配置才能生效;
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage:自动配置包(作用为对于springboot的主配置的包下面的bean 加载注册)
- @Import(AutoConfigurationPackages.Registrar.class):
- Spring的底层注解@Import,给容器中导入一个组件;导入的组件由AutoConfigurationPackages.Registrar.class;
- 将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;(所以Controller的组件可以扫描进入容器中)
(结构)package=为com.example.demo验证了上面标红的地方
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry
registry) {
AutoConfigurationPackages.register(registry, new String[]{(new
AutoConfigurationPackages.PackageImport(metadata)).getPackageName()});
}
进入register方法()为将组建注册到ioc容器中
@Import(EnableAutoConfigurationImportSelector.class),其实就是给容器中导入组件
EnableAutoConfigurationImportSelector:导入哪些组件的选择器;
在这个类处理EnableAutoConfiguration自动配置
将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中; 会给容器中导入非常多的自动配置
类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件;
1.可以查看selectImports()方法的内容;
2.List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);`获取候选的配置
-
getCandidateConfigurations()得到候选的配置
有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;
3)、每一个自动配置类进行自动配置功能;
4)、以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;
-
调用了
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,classLoader)
; -
Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;
-
以前我们需要自己配置的东西,自动配置类都帮我们;
-
J2EE的整体整合解决方案和自动配置都在
spring-boot-autoconfigure-1.5.9.RELEASE.jar
; -
-
2、自动配置原理:
1)、SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration
2)、@EnableAutoConfiguration 作用:
-
利用EnableAutoConfigurationImportSelector给容器中导入一些组件?
-
可以查看selectImports()方法的内容;
-
将类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中
-
SpringFactoriesLoader.loadFactoryNames() 扫描所有jar包类路径下 META-INF/spring.factories 把扫描到的这些文件的内容包装成properties对象 从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中 和上面的原理是一样的
-
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
获取候选的配置
@Configuration //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(HttpProperties.class) //启动指定类的ConfigurationProperties
功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc
容器中
@ConditionalOnWebApplication //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果
满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有这个类
CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing =
true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立
的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {
所有在配置文件中能配置的属性都是在xxxxProperties类中封装者‘;配置文件能配置什么就可以参照某个功能对应的这个属性类
@ConfigurationProperties(prefix = "spring.http.encoding") //从配置文件中获取指定的值和bean的属性进行绑定 public class HttpEncodingProperties { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
spring.http.encoding.charset=utf-8
@ConfigurationProperties(prefix = "spring.http.encoding") //从配置文件中获取指定的值和bean的属性进行绑定
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
根据当前不同的条件判断,决定这个配置类是否生效?
一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
精髓:
1)、SpringBoot启动会加载大量的自动配置类
2)、我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;
3)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
4)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值;
xxxxAutoConfigurartion:自动配置类;
给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
自动配置类必须在一定的条件下才能生效;
我们怎么知道哪些自动配置类生效;
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean;
@ConditionalOnMissingBean 容器中不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing =
true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立
的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@EnableConfigurationProperties 注解
@ConfigurationProperties的使用demo
//依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
如果不使用@Component而是用EnableConfigurationProperties 的demo:
输出的值仍然是一样的
效果我们看到了也是将指定的类,实现和 @Component
被注解的类是一样的,创建成 Bean 对象,然后加入到容器中,最后
使用ConfigurationProperties进行对应类的参数绑定。
原理:
//EnableConfigurationPropertiesImportSelector.java
//@EnableConfigurationProperties 注解指定的类,逐个注册成对应的 BeanDefinition 对象。
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
getTypes(metadata).forEach((type) -> register(registry,
(ConfigurableListableBeanFactory) registry, type));
}
//得到被注解EnableConfigurationProperties标注的所有的类型
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
EnableConfigurationProperties.class.getName(), false);
return collectClasses((attributes != null) ? attributes.get("value")
: Collections.emptyList());
}
到了这里已经对springboot的自动配置有了一些了解,同理,我们可以看看Aop的自动配置。
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
AnnotatedElement.class })//表示系统是否存在EnableAspectJAutoProxy类和Aspect,Advice类时生效
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
//配置文件中有没有指定spring.aop.auto=true,如果没有指定也是生效的
public class AopAutoConfiguration {//默认使用CglibAutoProxyConfiguration,如果在配置文件中配置了spring.aop.proxy-target-class=false就会使用JdkDynamicAutoProxyConfiguration
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}}
总结自动配置原理
1.首先springboot启动的时候已经通过@springbootApplication向容器中导入了xxxAutoConfiguration组件
2 然后通过@EnableConfigurationProperties(HttpProperties.class)像容器中注册了HttpProperties的
组件 EnableConfigurationProperties实现了注册功能
3 @ConfigurationProperties(prefix = "spring.http")通过ConfigurationProperties与配置文件
spring.http前缀的属性进行绑定
属性可以配什么看具体类HttpProperties的属性
4 所有在配置文件中能配置的属性都是在xxxxProperties类中封装者
@Bean//像容器中注册characterEncodingFilter组件
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
//从配置文件拿到属性值
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
所以properties是HttpProperties 类型的,有上面总结可知,characterEncodingFilter已经与配置文件
进行绑定了
然后就会characterEncodingFilter()组件通过一些condition的条件判断就会生效
springboot 事务
@EnableTransactionManagement
@Transactional
@Transactional(rollbackFor = Exception.class)