springboot的学习记录

微服务的介绍

源码链接

更多整合

微服务:每一个功能元素最终都是一个可独立替换和独立升级的软件单元;

开始的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

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)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值