十六、SpringBoot快速整合Mybatis&MybatisPlus

01、目标

整合mybatis和mybatis-plus实现数据库的增删改查

02、学习参考

03、具体实现

03-01、引入mybatis-plus依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.17</version>
</dependency>
<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>

03-02、在application.yml进行配置

application.yml

spring:
  profiles:
    active: prod

application-prod.yml

server:
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml
    min-response-size: 1024
  tomcat:
    accept-count: 1000
    threads:
      max: 800
      min-spare: 100
    max-http-form-post-size: 0
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/kssindexdb?serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: xxx
    password: xxx
    hikari:
      connection-timeout: 60000
      validation-timeout: 3000
      idle-timeout: 60000
      login-timeout: 5
      max-lifetime: 60000
      maximum-pool-size: 30
      minimum-idle: 10
      read-only: false
  application:
    name: edu-front-web
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    mode: HTML
    encoding: UTF-8
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    locale: zh_CN
    generator:
      write-numbers-as-strings: true
      write-bigdecimal-as-plain: true
  servlet:
    content-type: text/html
    multipart:
      max-file-size: 2MB
      max-request-size: 2MB
  mvc:
    servlet:
      load-on-startup: 1
  session:
    store-type: redis
    timeout: 1800
  main:
    allow-bean-definition-overriding: true
mybatis-plus:
  mapper-locations: classpath*:/mapper/*.xml
  type-aliases-package: com.kuangstudy.entity

03-03、增mybatis-plus扫描和分页处理

package com.kuangstudy.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@MapperScan("com.kuangstudy.mapper")
public class MyBatisPlusConfig {
    //逻辑删除插件
//    @Bean
//    public ISqlInjector sqlInjector() {
//        return new LogicSqlInjector();
//    }
    //分页插件
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

03-04、Mybatis-plus对日期时间的处理

package com.kuangstudy.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Slf4j
@Component
public class CommonMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

03-05、定义Entity

package com.kuangstudy.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.Serializable;
import java.util.Date;

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("kss_user")
@ApiModel(value = "User对象", description = "")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "用户唯一id")
    @TableId(value = "userid", type = IdType.ID_WORKER)
    private Long userid;
    @ApiModelProperty(value = "用户的唯一账号")
    private String usernum;
    @ApiModelProperty(value = "用户昵称")
    private String nickname;
    @ApiModelProperty(value = "用户密码")
    private String password;
    @ApiModelProperty(value = "用户邮箱")
    private String email;
    @ApiModelProperty(value = "用户手机号")
    private String mobile;
    @ApiModelProperty(value = "微信登录openid")
    private String openid;
    @ApiModelProperty(value = "0未禁用 1禁用")
    private Integer disabled;
    @ApiModelProperty(value = "VIP到期时间")
    private Date vipEndTime;
    @ApiModelProperty(value = "用户头像")
    private String avatar;
    @ApiModelProperty(value = "用户级别")
    private Integer level;
    @ApiModelProperty(value = "用户签名")
    private String sign;
    @ApiModelProperty(value = "性别:0女 1男")
    private Integer sex;
    @ApiModelProperty(value = "生日")
    private Date birthday;
    @ApiModelProperty(value = "地址")
    private String address;
}

03-06、定义UserMapper接口

package com.kuangstudy.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.kuangstudy.entity.User;

public interface UserMapper extends BaseMapper<User> {
}

03-07、定义UserService和UserServiceImpl

package com.kuangstudy.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.kuangstudy.entity.User;

public interface UserService extends IService<User> {
}
package com.kuangstudy.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kuangstudy.entity.User;
import com.kuangstudy.mapper.UserMapper;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

03-07、定义MybatisUserController

package com.kuangstudy.controller.user;
import com.kuangstudy.entity.User;
import com.kuangstudy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MybatisUserController {
    @Autowired
    private UserService userService;
    @GetMapping("/getuser")
    public User getUser(Integer id) {
        return userService.getById(id);
    }
}

03-08、访问接口

http://localhost:9999/getuser?id=1

{
  "success": true,
  "code": "20000",
  "message": "成功",
  "error": null,
  "data": {
    "userid": "1",
    "usernum": "999981",
    "nickname": "琪琪怪怪",
    "password": "155bd5d2deaedd02eb7ecf1284e07f8c",
    "email": null,
    "mobile": "18681986901",
    "openid": "fff8fea78c7f4765824feb828ed06a5a",
    "disabled": "0",
    "vipEndTime": null,
    "avatar": "/assert/images/avatar/4.jpg",
    "level": "0",
    "sign": "这个人很懒,什么都没有写",
    "sex": "1",
    "birthday": null,
    "address": "中国"
  }
}

十七、@SpringBootApplication启动原理

01、目标

02、剖析@SpringBootApplication源码

分析@SpringBootApplication源码如下:

@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 {
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
    @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;
}

注解的核心部分说明:

  • @Target(ElementType.TYPE) 注解的目标位置:接口、类、枚举
  • @Retention(RetentionPolicy.RUNTIME):注解会在class字节码文件中存在,在运行时可以通过反射获得
  • @Documented 用于生成javadoc文档。
  • @Inherited:在类继续关系中,如果子类要继承父类的注解,那么要该注解必须被@Inherited修改注解

以上是常规的注解,剩下的三个是springboot的核心注解了!
@SpringBootApplication注解是一个复合注解,包括如下三个注解:

以上三个注解在内部只做一件事情把bean注册到springioc容器

03、@SpringBootConfiguration分析

@SpringBootConfiguration: 负责把@Configuration+@Bean的方式的bean注入到ioc容器中。
@ComponentScan:负责将指定包下含有特定注解的类,(比如:@Service@Component@Repository@Controller等),通过扫描放入到ioc容器中。
@EnableAutoConfiguration:通过spring.factories的配置,来实现bean的注册到springioc容器中。

spring boot saml spring boot saml2_spring

十八、剖析@SpringBootConfiguration源码

01、目标

02、剖析@SpringBootConfiguration源码

02-01、分析:@SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;
}

@SpringBootConfiguration其实就是一个@Configuration,说明当前是一个配置类

02-02、分析@Configuration

从Spring3.0以后,@Configuration用于定义配置类,可替换xml配置文件。被注解的类内部包含一个或者多个@bean注解的方法。这个方法讲会被 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类进行扫描。并用户构建bean的定义,初始化spring容器。

  • @Configuration标注在类上,@Bean标注在方式上,等价于spring的xml文件配置的<bean></bean>节点。
  • @Configuration就相当于一个applicationContext.xml文件。

原理分析1:定义一个配置类

package com.kuangstudy.source;
import org.springframework.context.annotation.Configuration;

@Configuration // 当前是一个配置类
public class KsdMyConfiguration {
}

这里就相当于定义了一个applicationContext.xml配置文件<beans></beans>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
</beans>

步骤2:定义一个main函数去加载和启动配置类

package com.kuangstudy.source;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;

public class KsdApplicationTest {
    public static void main(String[] args){
        // 1: 加载一个配置类
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(KsdMyConfiguration.class);
    }
}

03、如何把一个对象加载到spring的ioc容器中

在配置类用@bean来标记初始化。
@bean的作用是:带有@bean的注解方法将返回一个对象,该对象被注册到spring的ioc容器中。

步骤1:创建一个bean

package com.kuangstudy.source;
import lombok.*;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Builder
public class UserBean {
    private String nickname;
    private String password;
}

步骤2:在配置类中注册bean

package com.kuangstudy.source;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // 当前是一个配置类
public class KsdMyConfiguration {
    /**
     * @Description 初始化bean
     * @Param []
     * @return com.kuangstudy.source.UserBean
    **/
    @Bean
    public UserBean getUserBean() {
        UserBean userBean = new UserBean();
        userBean.setNickname("feige");
        userBean.setPassword("1345323");
        return userBean;
    }
}

类似于以前的:

<bean id="userbean" class="com.kuangstudy.source.UserBean">
    <property name="nickname" value="feige"></property>
    <property name="password" value="12154545"></property>
</bean>

步骤3:运行测试类

package com.kuangstudy.source;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;

public class KsdApplicationTest {
    public static void main(String[] args){
        // 1: 加载一个配置类
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(KsdMyConfiguration.class);
        // 2:获取通过配置类加载到ioc,然后通过getBean方法从ioc容器中获取bean对象出来
        UserBean bean = applicationContext.getBean(UserBean.class);
        System.out.println(bean);
    }
}

打印结果如下:

  1. UserBean(nickname=feige, password=1345323)

04、小结

说明:使用传统的xml方式还是基于注解的方式,其结论都是一样:都会通过不同的方式机制把对象加载到ico容器中,让ioc容器去管理对象。

十九、剖析@ComponentScan源码

01、目标

02、剖析springboot的@ComponentScan注解

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  • excludeFilters:过滤不需要扫描的类型
  • @Filter:过滤不需要扫描的注解,FilterType.CUSTOM过滤类型为自定义规则,即指定特定的class进行过滤。
  • classes:过滤指定的class,即剔除了TypeExcludeFilter和AutoConfigurationExcludeFilter

从上面的源码,我们可以得出如下结论:

1、@SpringBootApplication的源码包含@ComponentScan,只要@SpringBootApplication注解所在的包及其子孙包,都将它们下面的class扫描并装入到springioc容器中

2、如果自定义的springbean。不在@SpringBootApplication注解所在的包及其子孙包中,都必须手动加上@ComponentScan注解并指定对应扫描指定bean所在的包。

03、为什么要使用@ComponentScan,它解决了什么问题?

在开发过程中,我们一般定义一个class或者说bean,都是在类上加注解@Service,@Controller或者@Conponent就可以了,但是spring的ioc怎么知道在哪里去加载这些类?就必须告诉spring去哪里找到这些加了这些加了注解的bean的。而@ComponentScan:就是告诉spring容器去哪里找到这些bean的一种机制

  • @ComponentScan的作用:告诉spring容器去扫描@ComponentScan指定包下所有的注解类,然后将扫描的类装入到ioc容器中。

默认情况下:@ComponentScan加载的包是@SpringBootApplication的所在的包及其子孙包,将会将其下面所有符合条件的bean加入到springioc容器中。

04、实践和体验@ComponetScan机制

步骤1:新建一个service
注意:在启动类包以外地方创建,如下:

package com.test;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    public void listorder() {
        System.out.println("查询订单!!!!");
    }
}

步骤2:在启动类中尝试获取OrderService

package com.kuangstudy;
import com.test.OrderService;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableAsync //开启异步执行
@EnableTransactionManagement
@MapperScan("com.kuangstudy.mapper")
public class KuangstudyBootHelloApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(KuangstudyBootHelloApplication.class, args);
        OrderService orderService = run.getBean(OrderService.class);
        orderService.listorder();
    }
}

出现异常

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.test.OrderService' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
    at com.kuangstudy.KuangstudyBootHelloApplication.main(KuangstudyBootHelloApplication.java:18)

异常的信息是:没有找到OrderService这个bean。

步骤3:可以使用@ComponentScan来解决

package com.kuangstudy;
import com.test.OrderService;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableAsync //开启异步执行
@EnableTransactionManagement
@MapperScan("com.kuangstudy.mapper")
@ComponentScan(basePackages = {"com.test"})
public class KuangstudyBootHelloApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(KuangstudyBootHelloApplication.class, args);
        OrderService orderService = run.getBean(OrderService.class);
        orderService.listorder();
    }
}

在启动类上:增加@ComponentScan(basePackages = {“com.test”})即可,解决异常并且加入到ioc容器中去了。
上面会出现异常和问题:@ComponentScan(basePackages = {"com.test"}) 会覆盖默认的@SpringBootApplication的机制,也就是默认扫描主类包及其子孙包下面的类的机制。

步骤4:解决覆盖问题

package com.kuangstudy;
import com.test.OrderService;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableAsync //开启异步执行
@EnableTransactionManagement
@MapperScan("com.kuangstudy.mapper")
@ComponentScan(basePackages = {"com.test","com.kuangstudy"})
public class KuangstudyBootHelloApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(KuangstudyBootHelloApplication.class, args);
        OrderService orderService = run.getBean(OrderService.class);
        orderService.listorder();
    }
}

二十、剖析@EnableAutoConfiguration注解

01、目标

02、剖析@EnableAutoConfiguration注解

@EnableAutoConfiguration源码如下:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

上面中最关键的是:@Import(AutoConfigurationImportSelector.class) 先来分析:@Import

03:@Import有什么作用

@Import作用:就是将其指定的类实例注入到springioc容器中。它是一种搭载机制

@Import作用在类上方,@Bean作用在方法上方

我们用代码的方式来解密@Import机制:

步骤1:创建一个bean

package com.kuangstudy.controller.econfig;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserCourseBean {
    private String nickname="yykk";
    private String password="12356";
}

步骤2:采用@Import注入到springioc中

package com.kuangstudy.controller.econfig;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Service;

@Service
@Import(UserCourseBean.class)
public class UserCourseService {
}

步骤3:定义测试类

package com.kuangstudy.controller.econfig;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestConfiguration {
    public static void main(String[] args) {
        // 1:初始化bean
        ApplicationContext context = new AnnotationConfigApplicationContext(UserCourseService.class);
        // 2: 获取userbean
        UserCourseService userCourseService = context.getBean(UserCourseService.class);
        // 3:测试userCoursebean是否已经装配到容器中
        UserCourseBean userCourseBean = context.getBean(UserCourseBean.class);
        System.out.println(userCourseService);
        System.out.println(userCourseBean);
    }
}

打印结果:

com.kuangstudy.controller.econfig.UserCourseService@346d61be
UserCourseBean(nickname=yykk, password=12356)

说明:usercoursebean也被装配到了ioc容器中.(你可以通过注释 @Import(UserCourseBean.class) 来测试得出结论。

04、面试题分析:Spring的ImportSelector接口有什么用处?

源码分析

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

AutoConfigurationImportSelector

AutoConfigurationImportSelector是一个核心类,专门负责和装配EnableAutoConfiguration的配置类。源码如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}

从源码中可以找到6个核心接口:最核心的是DeferredImportSelector接口。

  • DeferredImportSelector:实现String[] selectImports(AnnotationMetadata var1);方法,其作用是:去收集需要的class注册到springioc容器中。
  • BeanClassLoaderAware:允许一个获取它的classLoader(即当前bean factory加载bean类使用的class loader)的回调类,实现了void setBeanClassLoader(ClassLoader classLoader);
  • ResourceLoaderAware:ResourceLoaderAware回调接口负责将ResourceLoader对象注入到当前的受管Bean实例中,其定义如下。当受管Bean获得ResourceLoader对象后,它便能够通过它获得各种资源。
  • BeanFactoryAware:通过实现void setBeanFactory(BeanFactory var1) throws BeansException; 获取BeanFactory对象。可以获取springioc容器的beanfactory对象操作和获取bean。
  • EnvironmentAware:通过实现void setEnvironment(Environment var1); 获取上下文环境对象。这个对象可以拿到底层系统的参数配置信息
  • Orderedint getOrder(); 决定springioc加载实现类的顺序。越小越先加载

分析:DeferredImportSelector

package org.springframework.context.annotation;
import java.util.function.Predicate;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;
public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
    @Nullable
    default Predicate<String> getExclusionFilter() {
        return null;
    }
}

DeferredImportSelector作用:定义了一个selectImports接口方法,它的作用是去收集需要的class注册到springioc容器中。

ImportSelector的实现类(比如:AutoConfigurationImportSelector)会和@Import结合搭配使用。会把实现类中返回成数组。都注入到ioc容器中。

package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
}

05、面试题分析:动手编写一个@Enable开关类

是springboot中存在大量的开关注解类比如:@EnableTransactionManagement@EnableAsync等。
@Enable*的含义是:开启一项功能,起到了开关的作用。而这些开关的原理是:底层通过@Import+ImportSelector接口来实现的。

步骤1:定义两个需要被ioc容器加载的bean

package com.kuangstudy.controller.selector;

public class PersonBean {
}
package com.kuangstudy.controller.selector;

public class RoleBean {
}

步骤2:定义selector类

package com.kuangstudy.controller.selector;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.util.function.Predicate;

public class PersonRoleImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{
                "com.kuangstudy.controller.selector.RoleBean",
                "com.kuangstudy.controller.selector.PersonBean"
        };
    }
}

步骤3:自定义一个enable的开关类

package com.kuangstudy.controller.selector;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(PersonRoleImportSelector.class)
public @interface EnablePersonRole {
}

作用是:告诉springioc要不要去加载PersonRoleImportSelector中的bean注入。

步骤4:定义配置类,使用enable开关

package com.kuangstudy.controller.selector;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

@EnablePersonRole
public class PersonRoleConfiguration {
}

步骤5:编写测试类

package com.kuangstudy.controller.selector;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestImportSelector {
    public static void main(String[] args) {
        // 1:初始化bean
        ApplicationContext context = new AnnotationConfigApplicationContext(PersonRoleConfiguration.class);
        PersonBean personBean = context.getBean(PersonBean.class);
        RoleBean roleBean = context.getBean(RoleBean.class);
        System.out.println(personBean);
        System.out.println(roleBean);
    }
}

最终结果:

com.kuangstudy.controller.selector.PersonBean@7a419da4
com.kuangstudy.controller.selector.RoleBean@14555e0a

06、面试题分析:自己动手写一个Spring.factories`

spring常用的几个aware bean接口
BeanNameAware
作用:让Bean获取自己在BeanFactory配置中的名字(根据情况是id或者name)。
Spring自动调用。并且会在Spring自身完成Bean配置之后,且在调用任何Bean生命周期回调(初始化或者销毁)方法之前就调用这个方法。换言之,在程序中使用BeanFactory.getBean(String beanName)之前,Bean的名字就已经设定好了。
BeanFactoryAware
要直接在自己的代码中读取spring的bean,我们除了根据常用的set外,也可以通过spring的BeanFactoryAware接口实现,只要实现setBeanFactory方法就可以。
private BeanFactory  beanFactory;
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
这样我们就可以直接拿东西用了,如
Object  object = beanFactory.getBean(beanName);
我们既然可以通过set来拿到我们要的对象,为什么还要用这个beanFactory呢,道理很简单,因为有些情况是需要动态的去获取对象的,比如说我有10个银行的处理对象,他们都继承了我的BankService对象,但是具体处理的时候要哪家银行的对象呢?这个依赖于用户的选择。你可以注入10个BankService实例,然后用if --else来搞,不过那样太坨了。每增加一家银行你都需要改代码。
通过beanFactory的话,那就一行代码搞定,只要给beanName就OK了,动点脑筋吧beanName配置的有规律点,然后根据用户的银行选择,凑出个beanName,大功告成了!
简单一句话的理解是:beanFactory让你可以不依赖注入方式,随意的读取IOC容器里面的对象,不过beanFactory本身还是要注入的
ApplicationContextAware
当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean。换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。
public class LocalUtil implements ApplicationContextAware{
  private static ApplicationContext applicationcontext;
  @Override
  public void setApplicationContext(ApplicationContext applicationcontext)throws BeansException {
this.applicationcontext = applicationcontext;
  }
  public static ApplicationContext getApplicationContext()throws BeansException {
return applicationcontext;
  }
}
ResourceLoaderAware
ResourceLoaderAware回调接口负责将ResourceLoader对象注入到当前的受管Bean实例中,其定义如下。当受管Bean获得ResourceLoader对象后,它便能够通过它获得各种资源。
public interface ResourceLoaderAware {
    void setResourceLoader(ResourceLoader resourceLoader); 
} 
ServletContextAware
在spring中,凡是实现ServletContextAware接口的类,都可以取得ServletContext。
BeanClassLoaderAware
 允许一个获取它的classLoader(即当前bean factory加载bean类使用的class loader)的回调类,实现了void setBeanClassLoader(ClassLoader classLoader);
InitializingBean
接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候会执行该方法。

二十一、剖析解密spring.factories原理

01、目标

1、搞明白Spring的ImportSelector的原理和Spring的spring.facories文件是用来干嘛的?
2、@EnableAutoConfiguration是如何通过spring.facories来实现bean的注册?
3、动手编码练习,自定义一个spring.factories文件。

02、解密Spring的spring.facories的原理

步骤1:在@EnableAutoConfiguration对应的开关类中的AutoConfigurationImportSelector.java找到selectImports方法

public Iterable<Entry> selectImports() {
            if (this.autoConfigurationEntries.isEmpty()) {
                return Collections.emptyList();
            } else {
                Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
                // 这里就是去拿配置的class,把所有的 spring.factories的配置类全部加载到springioc容器中
                Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
                processedConfigurations.removeAll(allExclusions);
                return (Iterable)this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> {
                    return new Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName);
                }).collect(Collectors.toList());
            }
        }

重点核心代码分析:

// 这里就是去拿配置的class,把所以的 spring.factories的配置类全部加载到springioc容器中
Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
  • this.autoConfigurationEntries 它是一个arrayList集合。private final List<AutoConfigurationImportSelector.AutoConfigurationEntry> autoConfigurationEntries = new ArrayList();

步骤2:debug启动springboot项目

spring boot saml spring boot saml2_spring boot saml_02

 很清楚的看到集合装配了很多配置类。问题来了?
autoConfigurationEntries是怎么赋值的?
找到这个类中的process方法,如下:

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
        () -> String.format("Only %s implementations are supported, got %s",
    AutoConfigurationImportSelector.class.getSimpleName(),
    deferredImportSelector.getClass().getName()));
    // 这里就是把springioc容器中加载好的配置类筛选出来。然后装配给集合
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(annotationMetadata);
    // 在这里赋值的    
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
    this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}

AutoConfigurationEntry又是如何而来的?

/**
     * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
     * of the importing {@link Configuration @Configuration} class.
     * @param annotationMetadata the annotation metadata of the configuration class
     * @return the auto-configurations that should be imported
     */
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
         AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 1: 收集要注册的springioc中的class
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // 2:去除重复的classp配置
        configurations = removeDuplicates(configurations);
        // 3:把加载出来的class进行过滤处理。因为set的特性就是不重复元素。相同元素会过滤。
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions); 
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

分析getCandidateConfigurations()方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { 
        // 这里就是去加载spring.factories文件的方法,这个文件中定义了springboot中所有的配置类。
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                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;
    }

分析:SpringFactoriesLoader.loadFactoryNames

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        // 这里是真正加载`spring.factories`的地方
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

分析:loadSpringFactories方法:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();
            try {
               // 找到了核心的位置:META-INF/spring.factories,并把类路径下的spring.factories进行匹配获取加载到内存中
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");
                // 开始循环遍历文件中的信息
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    // 把文件加载出来,转换成properties,实质就是一个:Map
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    // 开始遍历和循环每一项
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        // 获取对应配置文件中的每一项的key。
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        // 获取对应配置文件中的每一项的值。
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;
                        // 然后存放于Map的中。    
                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }
                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

spring.factories,这个文件在哪里找到

spring boot saml spring boot saml2_spring_03

spring.factories 是springboot的解耦扩展机制,这种机制是模仿java的SPI机制来进行实现的。

spring.factories :内容是什么样子的?

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

为什么返回的是:Map<String,List<String>>
因为spring.factories的文件对应每种key有很多种,每种key对应的value也很多。当然是map<string,list>类型来装载,看上面的源代码就可以得知。

二十二、自定义实现Spring.factories文件&自定义starter机制

01、目标

自定义starter机制

02、分析

步骤1:在/src/main/resources目录下新建一个,META/Spring.factoires文件

spring boot saml spring boot saml2_数据库_04

步骤2:新建一个配置类和一个bean

注意包名:是不同的包哦。当然你可以放在不同的工程下即可。

package com.kuang.message;
import java.sql.Connection;

public class MessageTemplate {
    public Connection getConnection() {
        System.out.println("我是一个链接方法....");
        return null;
    }
}
package com.kuang.message;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MessageTemplateConfiguration {
    @Bean
    public MessageTemplate messageTemplate() {
        return new MessageTemplate();
    }
}

步骤3:然后在配置文件spring.factories中去装配MessageConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.kuang.message.MessageTemplateConfiguration

步骤4:体验MessageTemplate是否加载到ioc容器中

package com.kuangstudy;
import com.kuang.message.MessageTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.sql.Connection;
@SpringBootApplication
@EnableAsync //开启异步执行
@EnableTransactionManagement
@MapperScan("com.kuangstudy.mapper")
public class KuangstudyBootHelloApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(KuangstudyBootHelloApplication.class, args);
        MessageTemplate bean = applicationContext.getBean(MessageTemplate.class);
        Connection connection = bean.getConnection();
    }
}

打印结果:我是一个链接方法.... 说明以及被加载到ioc容器中去了。

通过断点查看如下:

spring boot saml spring boot saml2_spring boot_05

spring boot saml spring boot saml2_spring_06

二十三、 SpringBoot中的@Primary 和 @Qualifier

01、目标

掌握SpringBoot中的@Primary 和 @Qualifier

02、问题

当一个接口有2个不同实现时,使用@Autowired注解时会报org.springframework.beans.factory.NoUniqueBeanDefinitionException异常信息。
解决
(1)使用Qualifier注解,选择一个对象的名称,通常比较常用
(2)Primary可以理解为默认优先选择,不可以同时设置多个,内部实质是设置BeanDefinition的primary属性

注解

备注

@Primary

优先方案,被注解的实现,优先被注入

@Qualifier

先声明后使用,相当于多个实现起多个不同的名字,注入时候告诉我你要注入哪个

03、@Qualifier

接口

public interface EmployeeService {
    public EmployeeDto getEmployeeById(Long id);
}

接口对应的两个实现类:EmployeeServiceImpl和EmployeeServiceImpl1:接口对应的两个实现类:EmployeeServiceImpl和EmployeeServiceImpl1:

@Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService {
    public EmployeeDto getEmployeeById(Long id) {
                return new EmployeeDto();
     }
}
@Service("employeeService1")
public class EmployeeServiceImpl1 implements EmployeeService {
    public EmployeeDto getEmployeeById(Long id) {
         return new EmployeeDto();
    }
}

这个时候就要用到@Qualifier注解了,qualifier的意思是合格者,通过这个标示,表明了哪个实现类才是我们所需要的,我们修改调用代码,添加@Qualifier注解,需要注意的是@Qualifier的参数名称必须为我们之前定义@Service注解的名称之一!

@Controller
@RequestMapping("/emplayee.do")
public class EmployeeInfoControl {
    @Autowired
    @Qualifier("employeeService")
    EmployeeService employeeService;
    @RequestMapping(params = "method=showEmplayeeInfo")
    public void showEmplayeeInfo(HttpServletRequest request, HttpServletResponse response, EmployeeDto dto) {
            //#略
    }
}

04、@Primary

@Primary:和@Qualifier 一样,@Primary也一样,使用场景经常是:在spring 中使用注解,常使用@Autowired, 默认是根据类型Type来自动注入的。但有些特殊情况,对同一个接口,可能会有几种不同的实现类,而默认只会采取其中一种的情况下 @Primary 的作用就出来了。

接口

public interface Singer {
    String sing(String lyrics);
}

有下面的两个实现类:

@Component // 加注解,让spring识别
public class MetalSinger implements Singer{
    @Override
    public String sing(String lyrics) {
        return "I am singing with DIO voice: "+lyrics;
    }
}
@Primary
@Component
public class OperaSinger implements Singer{
    @Override
    public String sing(String lyrics) {
        return "I am singing in Bocelli voice: "+lyrics;
    }
}
@Controller
@RequestMapping("/emplayee.do")
public class EmployeeInfoControl {
    @Autowired
    Singer singer; // 最终会把OperaSinger注入给它
    @RequestMapping(params = "method=showEmplayeeInfo")
    public void showEmplayeeInfo(HttpServletRequest request, HttpServletResponse response, EmployeeDto dto) {
            //#略
    }
}

二十四、SpringBoot中初始化加载的四种方式

01、目标

SpringBoot中初始化加载的四种方式

02、分析

在平时的业务模块开发过程中,平常我们经常会有一些需求是项目启动时候加载一下预置数据,难免会需要做一些全局的任务、缓存、线程等等的初始化工作,那么如何解决这个问题呢?方法有多种,但具体又要怎么选择呢?

spring boot saml spring boot saml2_spring boot saml_07

按照前面的分析,Spring-boot容器启动流程总体可划分为2部分:

  • 执行注解:扫描指定范围下的bean、载入自动配置类对应的bean加载到IOC容器。
  • main方法中具体SpringAppliocation.run(),全流程贯穿SpringApplicationEvent(经典的spring事件驱动模型),有6个子类:
    ApplicationFailedEvent.class
    ApplicationPreparedEvent.class
    ApplicationReadyEvent.class
    ApplicationStartedEvent.class
    ApplicationStartingEvent.class
    SpringApplicationEvent.class

03、加载方式一、实现InitializingBean 接口

public class InitSequenceBean implements InitializingBean {   
    public InitSequenceBean() {   
       System.out.println("InitSequenceBean: constructor");   
    }   
    @PostConstruct  
    public void postConstruct() {   
       System.out.println("InitSequenceBean: postConstruct");   
    }   
    public void initMethod() {   
       System.out.println("InitSequenceBean: init-method");   
    }   
    @Override  
    public void afterPropertiesSet() throws Exception {   
       System.out.println("InitSequenceBean: afterPropertiesSet");   
    }   
}

InitializingBean 是 Spring 提供的一个接口,只包含一个方法 afterPropertiesSet()。凡是实现了该接口的类,当其对应的 Bean 交由 Spring 管理后,当其必要的属性全部设置完成后,Spring 会调用该 Bean 的 afterPropertiesSet()。在Bean在实例化的过程中执执行顺序为:Constructor > @PostConstruct

04、加载方式二、@PostConstruct注解

@Component
public Class AAA {    
    @Autowired    
    private BBB b;   
    public AAA() {        
        System.out.println("此时b还未被注入: b = " + b);    
    }    
    @PostConstruct    
    private void init() {        
        System.out.println("此时b已经被注入: b = " + b);    
    }
}

在具体Bean的实例化过程中执行,@PostConstruct注解的方法,会在构造方法之后执行,顺序为Constructor > @Autowired > @PostConstruct,所以这个注解就避免了一些需要在构造方法里使用依赖组件的尴尬(与之对应的还有@PreDestroy,在对象消亡之前执行,原理差不多)使用特点如下:
-只有一个非静态方法能使用此注解
-被注解的方法不得有任何参数
-被注解的方法返回值必须为void
-被注解方法不得抛出已检查异常
-此方法只会被执行一次

05、加载方式三、实现ApplicationRunner接口

@Component
@Order(0)
public class ApplicationRunnerTest implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.printf("*****************************applicationContext ***********");
        System.out.printf("***");
    }
}

06、加载方式四、实现CommandLineRunner接口或者实现CommandLineRunner接口

由上可知,我们只要实现这两个中的任何一个接口便可以完成我们的资源初始化任务,可以看到它们的加载是在容器完全启动之前。它两的区别是:前者的run方法参数是String…args,直接传入字符串,后者的参数是ApplicationArguments,对参数进行了封装。功能上是一样的。同时也可以使用 @Order注解来实现资源加载的先后顺序,值越小,优先级越高。实例如下:

@Component
@Order(1)
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("...init resources by implements CommandLineRunner");
    }
}
@Component
@Order(2)
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        System.out.println("...init resources by implements ApplicationRunner");
    }
}

06、加载方式四、ApplicationListener

ApplicationListener 就是spring的监听器,能够用来监听事件,典型的观察者模式。如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。这种事件机制都必须需要程序显示的触发。其中spring有一些内置的事件,当完成某种操作时会发出某些事件动作。比如监听ContextRefreshedEvent事件,当所有的bean都初始化完成并被成功装载后会触发该事件,实现ApplicationListener接口可以收到监听动作,然后可以写自己的逻辑。同样事件可以自定义、监听也可以自定义,完全根据自己的业务逻辑来处理。所以也能做到资源的初始化加载!

定义事件处理类

package com.kuangstudy.service.event;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ApplicationContextEvent;

public class EmailEvent extends ApplicationContextEvent {
    private String userid;
    private String email;
    public EmailEvent(ApplicationContext context,String userid, String email) {
        super(context);
        this.userid = userid;
        this.email = email;
    }
    public void sendEmail(String email) {
        System.out.println(userid + ":发送邮件!!!!" + email);
    }
}

定义事件监听

package com.kuangstudy.service.event;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class EmailListerner implements ApplicationListener<EmailEvent> {//ContextRefreshedEvent为启动事件
    @Override
    public void onApplicationEvent(EmailEvent event) {
        System.out.println("初始化数据");
        event.sendEmail("xuechengfeifei");
    }
}

进行事件发布

@Autowired
private ApplicationContext applicationContext;
// 事件发布
applicationContext.publishEvent(new EmailEvent(applicationContext,"1","xuechengfeifei@163.com"));

二十五、Spring实战系列(一)-监听器模式开发

“对于Spring框架,现实公司使用的非常广泛,但是由于业务的复杂程度不同,了解到很多小伙伴们利用Spring开发仅仅是利用了Spring的IOC,即使是AOP也很少用,但是目前的Spring是一个大家族,形成了一个很大的生态,覆盖了我们平时开发的方方面面,抛开特殊的苛刻要求之外,Spring的生态其实已经很全面了,所以在此开个系列来研究下Spring提供给我们的一些平时不太却又很实用的内容。”

说明:
我们平时在开发的时候都在讲高内聚低耦合,而且目前开发业务的时候业务一般在一个工程内部,但是为了考虑后期的可维护性,所以内部又以包名划分出了多个业务模块,但是有时不可避免的出现多个模块之间业务调用的情况。尤其是在采用RPC框架的时候,我们可能抽象出了一些服务接口,但是在同一个工程内部,RPC服务接口互相调用总觉得怪怪的,有没有相对优雅的方式来处理呢?答案是有的,就是利用Spring的事件监听模式。

定义一个场景:用户签到并获取积分,用户签到,记录签到信息,然后给用户添加对应的签到积分。
根据描述我们需要划分一下模块:积分模块,签到模块。下面开始设计代码
1、积分模块

1.1、积分业务接口

public interface PointBizService {
    /**
     * 处理用户积分
     *
     * @param source
     * @return
     */
    public long handleUserPoint(int source);
}

1.2、积分业务实现类

@Service
public class PointBizServiceImpl implements PointBizService {
    @Override
    public long handleUserPoint(int source) {
        return 0;
    }
}

 1.3、积分事件

public class PointEvent<PointHandleVO> extends ApplicationEvent {
    /**
     * Create a new ApplicationEvent.
     *
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public PointEvent(PointHandleVO source) {
        super(source);
    }

2、签到模块

2.1、签到业务接口

public interface SignBizService {
    /**
     * @param uid
     * @return
     */
    public boolean userSign(long uid);
}

2.2、签到业务实现类

@Service
public class SignBizServiceImpl implements SignBizService {
    @Autowired
    private ApplicationContextUtils applicationContextUtils;
    @Override
    public boolean userSign(long uid) {
        //....处理原有的业务
        applicationContextUtils.getApplicationContext().publishEvent(new PointEvent(new PointHandleVO(2)));
        return false;
    }
}

3、工具类

@Component
public class ApplicationContextUtils implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

以上是整体业务的核心代码,简单梳理下监听器的流程:1、用户处理完原有的签到业务后,通过工具类获取到ApplicationContext类,然后调用publishEvent(ApplicationEvent event)方法发布事件,然后PointEventListener事件监听器类监听到自己的PointEvent事件,进行业务处理,这样的实现避免了业务服务的直接调用,看着简洁些,但是这个也有个缺点:这种写法只能在同一个java进程中使用。如果是在分布式环境下利用好集群的特性,可以引入消息队列等第三方中间件来帮助实现业务的解耦。

二十六、Spring实战系列(二)-有关属性配置文件的加载和使用

我们平时使用Spring进行开发的时候经常会需要加载配置文件,而且Spring加载配置文件的方式又提供了很多种,我这次先把一些普通的加载方式说明下,然后再就一些比较特殊的情况的加载进行说明。

1、常用加载方式:
1.1、xml中进行配置文件引入

<context:property-placeholder location="classpath:config/*.properties"  file-encoding="utf-8"/>
    这个还有个原始的配置是配置 一个PropertyPlaceholderConfigurer
<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
     <property name="location">
       <value>conf/jdbc.properties</value>
     </property>
      <property name="fileEncoding">
       <value>UTF-8</value>
    </property>
</bean>

1.2、通过注解进行导入

@PropertySource(value = “properties/config.properties”)
以上是导入一个配置文件,如果需要导入多个,value是支持数组的,还有一个注解是@PropertySources,他是支持@PropertySource的数组格式。根据需要进行选用。

1.3、通过定义PropertyPlaceholderConfigurer的Bean来进行加载

@Bean
public PropertyPlaceholderConfigurer initPropertyPlaceholderConfigurer() {
    PropertyPlaceholderConfigurer placeholderConfigurer = new PropertyPlaceholderConfigurer();
    placeholderConfigurer.setLocation(new ClassPathResource("properties/*.properties"));
    placeholderConfigurer.setFileEncoding("UTF-8");
    return placeholderConfigurer;
}

以上是加载配置文件的几种方式,springboot是内部已经配好了扫描跟路径下的application.properties文件。
接下来进入我们今天的重点,如何使用已经导入配置文件中的属性。

2、使用配置文件中的属性

2.1、xml中使用—占位符

<mongo:mongo-client id="dataMongoConnection" replica-set="${mongo.data.hostport}" credentials="${mongo.data.username}:${mongo.data.password}@admin">
    <!--<mongo:mongo-client id="dataMongoConnection" replica-set="${mongo.data.hostport}">-->
        <mongo:client-options connections-per-host="${mongo.data.connectionsPerHost}" threads-allowed-to-block-for-connection-multiplier="${mongo.data.threadsAllowedToBlockForConnectionMultiplier}"
                              connect-timeout="${mongo.data.connectTimeout}"
                              max-wait-time="${mongo.data.maxWaitTime}"
                              socket-keep-alive="${mongo.data.socketKeepAlive}"
                              socket-timeout="${mongo.data.socketTimeout}"/>
</mongo:mongo-client>

 2.2、注解的使用

@Value("${jdbc.username}")
private String username;

以上是经常使用的方式,接下来将一个不常用的方式,有时候我们在引入Spring的第三方工具包的时候,有时在采用注解配置第三方核心配置类的时候发现采用注解引入属性值的时候发现不起作用,观察这些配置类的时候会发现他们经常会实现Spring的一些顶级接口类,例如:BeanDefinitionRegistryPostProcessor。因为PropertyPlaceholderConfigurer的初始化级别是最低的,当在注册这些顶级Bean的时候,我们的属性值是还没有被赋值的,所以导致属性值都是初始化值,如何解决这种情况呢。

在Spring调用加载配置文件的时候,有一个通用类可以获取到当前环境的属性文件上下文Environment,我们可以直接通过注解直接引入这个类。

@Autowired
private Environment environment;
在获取到这个类之后我们就可以自由的操作属性值了,操作如下:
environment.resolvePlaceholders("${dataSource.url}");
        environment.resolvePlaceholders("${dataSource.username}");
        environment.resolvePlaceholders("${dataSource.password}");

这时候就解决@Value不可用的问题,其实代码environment.resolvePlaceholders(“${dataSource.url}”);这个被调用后返回的是解析后的字符串值,这点也要注意下。

二十七、Spring实战系列(三)-BeanPostProcessor的妙用

01、概述

对于Spring开发时,我们有时会遇到同一个接口有多个实现类,为了避免错误,我们通常在具体调用的地方通过ApplicationContext根据业务的需要来选择不同的接口实现类,虽然可以在抽象出一个工厂方法,但是还是感觉不够优雅,如果通过@Autowired直接引入接口,则需要在某个实现类上标注@Primary,否则会报错。那么书归正传如何优雅的解决上述的问题呢,此处就介绍一种利用Spring的BeanPostProcessor来处理。话不多说先上接口

示例:

1、声明接口

public interface HelloService {
    public void sayHello();
}

2、对应的接口实现类1:

@Service
public class HelloServiceImpl1 implements HelloService{
    @Override
    public void sayHello() {
        System.out.println("你好我是HelloServiceImpl1");
    }
}

3、对应接口实现类2:

@Service
public class HelloServiceImpl2 implements HelloService{
    @Override
    public void sayHello() {
        System.out.println("你好我是HelloServiceImpl2");
    }
}

4、自定义注解:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface RountingInjected {
    String value() default "helloServiceImpl1";
}

5、自定义BeanPostProcessor实现类:

@Component
public class HelloServiceInjectProcessor implements BeanPostProcessor {
    @Autowired
    private ApplicationContext applicationContext;
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetCls = bean.getClass();
        Field[] targetFld = targetCls.getDeclaredFields();
        for (Field field : targetFld) {
            //找到制定目标的注解类
            if (field.isAnnotationPresent(RountingInjected.class)) {
                if (!field.getType().isInterface()) {
                    throw new BeanCreationException("RoutingInjected field must be declared as an interface:" + field.getName()
                            + " @Class " + targetCls.getName());
                }
                try {
                    this.handleRoutingInjected(field, bean, field.getType());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return bean;
    }
    /**
     * @param field
     * @param bean
     * @param type
     * @throws IllegalAccessException
     */
    private void handleRoutingInjected(Field field, Object bean, Class type) throws IllegalAccessException {
        Map<String, Object> candidates = this.applicationContext.getBeansOfType(type);
        field.setAccessible(true);
        if (candidates.size() == 1) {
            field.set(bean, candidates.values().iterator().next());
        } else if (candidates.size() == 2) {
            String injectVal = field.getAnnotation(RountingInjected.class).value();
            Object proxy = RoutingBeanProxyFactory.createProxy(injectVal, type, candidates);
            field.set(bean, proxy);
        } else {
            throw new IllegalArgumentException("Find more than 2 beans for type: " + type);
        }
    }

6、对应的代理实现类:

public class RoutingBeanProxyFactory {
    private final static String DEFAULT_BEAN_NAME = "helloServiceImpl1";
    public static Object createProxy(String name, Class type, Map<String, Object> candidates) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setInterfaces(type);
        proxyFactory.addAdvice(new VersionRoutingMethodInterceptor(name, candidates));
        return proxyFactory.getProxy();
    }
    static class VersionRoutingMethodInterceptor implements MethodInterceptor {
        private Object targetObject;
        public VersionRoutingMethodInterceptor(String name, Map<String, Object> beans) {
            this.targetObject = beans.get(name);
            if (this.targetObject == null) {
                this.targetObject = beans.get(DEFAULT_BEAN_NAME);
            }
        }
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            return invocation.getMethod().invoke(this.targetObject, invocation.getArguments());
        }
    }
}

7、结果测试类

@Component
public class HelloServiceTest {
    @RountingInjected(value = "helloServiceImpl2")
    private HelloService helloService;
    public void testSayHello() {
        helloService.sayHello();
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("colin.spring.basic.advanced.bbp");
        HelloServiceTest helloServiceTest = applicationContext.getBean(HelloServiceTest.class);
        helloServiceTest.testSayHello();
    }

上述是整个解决方案的示例流程,其核心思想就是根据自定义注解拦截要注入的接口实现类,运用java反射和代理的知识点来进行有效的实现类注入。
再次补充下BeanPostProcessor的一些知识点,
BeanPostProcessor接口作用:

如果我们想在Spring容器中完成bean实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理。我们需要定义一个或多个BeanPostProcessor接口实现类,然后注册到Spring IoC容器中。

Spring中Bean的实例化过程图示:

spring boot saml spring boot saml2_数据库_08

注意:
1、接口中的两个方法都要将传入的bean返回,而不能返回null,如果返回的是null那么我们通过getBean方法将得不到目标。
2、BeanFactory和ApplicationContext对待bean后置处理器稍有不同。ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有bean,并把它们注册为后置处理器,然后在容器创建bean的适当时候调用它,因此部署一个后置处理器同部署其他的bean并没有什么区别。而使用BeanFactory实现的时候,bean 后置处理器必须通过代码显式地去注册,在IoC容器继承体系中的ConfigurableBeanFactory接口中定义了注册方法

* Add a new BeanPostProcessor that will get applied to beans created  
 * by this factory. To be invoked during factory configuration.  
 * <p>Note: Post-processors submitted here will be applied in the order of  
 * registration; any ordering semantics expressed through implementing the  
 * {@link org.springframework.core.Ordered} interface will be ignored. Note  
 * that autodetected post-processors (e.g. as beans in an ApplicationContext)  
 * will always be applied after programmatically registered ones.  
 * @param beanPostProcessor the post-processor to register   
void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);

另外,不要将BeanPostProcessor标记为延迟初始化。因为如果这样做,Spring容器将不会注册它们,自定义逻辑也就无法得到应用。假如你在<beans />元素的定义中使用了’default-lazy-init’属性,请确信你的各个BeanPostProcessor标记为’lazy-init=”false”‘。

InstantiationAwareBeanPostProcessor
InstantiationAwareBeanPostProcessor是BeanPostProcessor的子接口,可以在Bean生命周期的另外两个时期提供扩展的回调接口, 即实例化Bean之前(调用postProcessBeforeInstantiation方法)和实例化Bean之后(调用postProcessAfterInstantiation方法), 该接口定义如下:

package org.springframework.beans.factory.config;    
import java.beans.PropertyDescriptor;    
import org.springframework.beans.BeansException;    
import org.springframework.beans.PropertyValues;    
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {    
    Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;    
    boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;    
    PropertyValues postProcessPropertyValues(    
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)    
            throws BeansException;    
}

其使用方法与上面介绍的BeanPostProcessor接口类似,只时回调时机不同。

如果是使用ApplicationContext来生成并管理Bean的话则稍有不同,使用ApplicationContext来生成及管理Bean实例的话,在执行BeanFactoryAware的setBeanFactory()阶段后,若Bean类上有实现org.springframework.context.ApplicationContextAware接口,则执行其setApplicationContext()方法,接着才执行BeanPostProcessors的ProcessBeforeInitialization()及之后的流程。

二十八、Spring实战系列(四)-动态注入接口Bean

01、概述

上一篇我们分析了BeanPostProcessor的基本使用,接下来我们分析下如何使用该类实现动态的接口注入,示例说明:在BeetlSQL框架中,在使用自动扫描注入时,我们通常只需要配置上要扫描的包路径,然后在该路径下声明对应的Dao接口类,这些接口类都默认继承BaseMapper接口类,然后我们在使用这些Dao类的时候,直接根据类型注入(@Autowired)即可使用,这个其实和Mybatis的那一套相似,也和Spring自身的Spring-data框架也类似。这个经常用于框架的开发,那么我就该部分的实现做相应的解释,这三个框架具体实现可能有差距,感兴趣的小伙伴自行去查看源码,我会以一个很简单的例子来讲解大概的实现逻辑。

问题描述:

继承Spring框架,实现声明某个自定义接口(UserMapper),改接口继承通用接口BaseMapper,(通用接口BaseMapper有默认的实现类),实现通过类型注入UserMapper类,然后通过Spring框架的上下文类(ApplicationContext实现类)的getBean()方法拿到UserMapper类来调用内部提供的方法。

1、声明BaseMapper接口类

public interface BaseMapper {
    /**
     * @param value
     */
    public void add(String value);
    /**
     * @param key
     */
    public void remove(String key);
}

2、默认BaseMapper实现类:CustomBaseMapper

public class CustomBaseMapper implements BaseMapper {
    private final Logger logger = Logger.getLogger(this.getClass().getName());
    private List<String> dataList = new CopyOnWriteArrayList<>();
    /**
     * @param value
     */
    @Override
    public void add(String value) {
        logger.info("添加数据:" + value);
        dataList.add(value);
    }
    /**
     * @param key
     */
    @Override
    public void remove(String key) {
        if (dataList.isEmpty())
            throw new IllegalArgumentException("Can't remove because the list is Empty!");
    }
}

接下来是继承Spring的核心代码

3、首先我们要先定义一个扫描某路径下的类,该类继承ClassPathBeanDefinitionScanner,自定义扫描类:DefaultClassPathScanner

public class DefaultClassPathScanner extends ClassPathBeanDefinitionScanner {
    private final String DEFAULT_MAPPER_SUFFIX = "Mapper";
    public DefaultClassPathScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }
    private String mapperManagerFactoryBean;
    /**
     * 扫描包下的类-完成自定义的Bean定义类
     *
     * @param basePackages
     * @return
     */
    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        // 如果指定的基础包路径中不存在任何类对象,则提示
        if (beanDefinitions.isEmpty()) {
            logger.warn("系统没有在 '" + Arrays.toString(basePackages) + "' 包中找到任何Mapper,请检查配置");
        } else {
            processBeanDefinitions(beanDefinitions);
        }
        return beanDefinitions;
    }
    /**
     * 注册过滤器-保证正确的类被扫描注入
     */
    protected void registerFilters() {
        addIncludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                    throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                //TODO 这里设置包含条件-此处是个扩展点,可以根据自定义的类后缀过滤出需要的类
                return className.endsWith(DEFAULT_MAPPER_SUFFIX);
            }
        });
        addExcludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                    throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                return className.endsWith("package-info");
            }
        });
    }
    /**
     * 重写父类的判断是否能够实例化的组件-该方法是在确认是否真的是isCandidateComponent
     * 原方法解释:
     * 确定给定的bean定义是否有资格成为候选人。
     * 默认实现检查类是否不是接口,也不依赖于封闭类。
     * 以在子类中重写。
     *
     * @param beanDefinition
     * @return
     */
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        // 原方法这里是判断是否为顶级类和是否是依赖类(即接口会被排除掉-由于我们需要将接口加进来,所以需要覆盖该方法)
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }
    /**
     * 扩展方法-对扫描到的含有BeetlSqlFactoryBean的Bean描述信息进行遍历
     *
     * @param beanDefinitions
     */
    void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();
            String mapperClassName = definition.getBeanClassName();
            // 必须在这里加入泛型限定,要不然在spring下会有循环引用的问题
            definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClassName);
            //依赖注入
            definition.getPropertyValues().add("mapperInterface", mapperClassName);
            // 根据工厂的名称创建出默认的BaseMapper实现
            definition.getPropertyValues().add("mapperManagerFactoryBean", new RuntimeBeanReference(this.mapperManagerFactoryBean));
            definition.setBeanClass(BaseMapperFactoryBean.class);
            // 设置Mapper按照接口组装
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            logger.info("已开启自动按照类型注入 '" + holder.getBeanName() + "'.");
        }
    }
    public void setMapperManagerFactoryBean(String mapperManagerFactoryBean) {
        this.mapperManagerFactoryBean = mapperManagerFactoryBean;
    }
}

4、核心的接口实现类:BaseMapperFactoryBean

public class BaseMapperFactoryBean<T> implements FactoryBean<T>, InitializingBean, ApplicationListener<ApplicationEvent>, ApplicationContextAware {
    /**
     * 要注入的接口类定义
     */
    private Class<T> mapperInterface;
    /**
     * Spring上下文
     */
    private ApplicationContext applicationContext;
    //也因该走工厂方法注入得来
    private BaseMapper mapperManagerFactoryBean;
    public BaseMapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    @Override
    public T getObject() throws Exception {
        //采用动态代理生成接口实现类,核心实现
        return (T) Proxy.newProxyInstance(applicationContext.getClassLoader(), new Class[]{mapperInterface}, new MapperJavaProxy(mapperManagerFactoryBean, mapperInterface));
    }
    @Override
    public Class<?> getObjectType() {
        return this.mapperInterface;
    }
    @Override
    public boolean isSingleton() {
        return true;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        //TODO 判断属性的注入是否正确-如mapperInterface判空
        if (null == mapperInterface)
            throw new IllegalArgumentException("Mapper Interface Can't Be Null!!");
    }
    /**
     * Handle an application event.
     *
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        //TODO 可依据事件进行扩展
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    public void setMapperManagerFactoryBean(BaseMapper mapperManagerFactoryBean) {
        this.mapperManagerFactoryBean = mapperManagerFactoryBean;
    }
}

5、定义默认的BaseMapper的FactoryBean-MapperManagerFactoryBean

public class MapperManagerFactoryBean implements FactoryBean<BaseMapper>, InitializingBean, ApplicationListener<ApplicationEvent> {
    @Override
    public BaseMapper getObject() throws Exception {
        return new CustomBaseMapper();
    }
    @Override
    public Class<?> getObjectType() {
        return BaseMapper.class;
    }
    @Override
    public boolean isSingleton() {
        return true;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        //TODO 判断属性的注入是否正确
    }
    /**
     * Handle an application event.
     *
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println(event.toString());
    }
}

6、核心的java动态代理类:MapperJavaProxy

public class MapperJavaProxy implements InvocationHandler {
    private BaseMapper baseMapper;
    private Class<?> interfaceClass;
    public MapperJavaProxy(BaseMapper baseMapper, Class<?> interfaceClass) {
        this.baseMapper = baseMapper;
        this.interfaceClass = interfaceClass;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException("mapperInterface is not interface.");
        }
        if (baseMapper == null) {
            baseMapper = new CustomBaseMapper();
        }
        return method.invoke(baseMapper, args);
    }
}

7、调用时的核心配置类:DefaultClassRegistryBeanFactory

public class DefaultClassRegistryBeanFactory implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor, BeanNameAware {
    private String scanPackage;
    private String beanName;
    private String mapperManagerFactoryBean;
    private ApplicationContext applicationContext;
    public String getScanPackage() {
        return scanPackage;
    }
    public void setScanPackage(String scanPackage) {
        this.scanPackage = scanPackage;
    }
    public String getMapperManagerFactoryBean() {
        return mapperManagerFactoryBean;
    }
    public void setMapperManagerFactoryBean(String mapperManagerFactoryBean) {
        this.mapperManagerFactoryBean = mapperManagerFactoryBean;
    }
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        if (StringUtils.isEmpty(this.scanPackage)) {
            throw new IllegalArgumentException("scanPackage can't be null");
        }
        String basePackage2 = this.applicationContext.getEnvironment().resolvePlaceholders(this.scanPackage);
        String[] packages = StringUtils.tokenizeToStringArray(basePackage2, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        DefaultClassPathScanner defaultClassPathScanner = new DefaultClassPathScanner(beanDefinitionRegistry);
        defaultClassPathScanner.setMapperManagerFactoryBean(mapperManagerFactoryBean);
        defaultClassPathScanner.registerFilters();
        defaultClassPathScanner.doScan(packages);
    }
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}

8、调用测试

8.1、假设你在包目录:colin.spring.basic.advanced.inject.dao下声明自定义的类UserMapper

public interface UserMapper extends BaseMapper {
}

8.2、声明配置类:ClassRegistryBeanScannerConfig

@Configuration
public class ClassRegistryBeanScannerConfig {
    @Bean(name = "mapperManagerFactoryBean")
    public MapperManagerFactoryBean configMapperManagerFactoryBean() {
        MapperManagerFactoryBean mapperManagerFactoryBean = new MapperManagerFactoryBean();
        return mapperManagerFactoryBean;
    }
    @Bean
    public DefaultClassRegistryBeanFactory configDefaultClassRegistryBeanFactory() {
        DefaultClassRegistryBeanFactory defaultClassRegistryBeanFactory = new DefaultClassRegistryBeanFactory();
        defaultClassRegistryBeanFactory.setScanPackage("colin.spring.basic.advanced.inject.dao");
        defaultClassRegistryBeanFactory.setMapperManagerFactoryBean("mapperManagerFactoryBean");
        return defaultClassRegistryBeanFactory;
    }
}

8.3、测试调用

public static void main(String[] args) {
        AnnotationConfigApplicationContext acApplicationCOntext = new AnnotationConfigApplicationContext("colin.spring.basic.advanced.inject");
        UserMapper userMapper = acApplicationCOntext.getBean(UserMapper.class);
        userMapper.add("lalaldsf");
        acApplicationCOntext.stop();
    }

总结:
此处对于BeanPostProcessor接口的调用应该属于高级应用了,该思路常用来解决扩展或集成Spring框架,其核心的思路可以分为以下几步:
1、自定义实现类路径扫描类,决定哪些类应该被注入进Spring容器。
2、采用Java动态代理来动态实现对于声明接口类的注入。
3、实现BeanDefinitionRegistryPostProcessor,在Spring初始化初期将需要扫描导入Spring容器的类进行注入。

二十九、Spring实战系列(五) - Spring中的InitializingBean接口的使用

01、分析

InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。
测试,如下:

import org.springframework.beans.factory.InitializingBean;
public class TestInitializingBean implements InitializingBean{
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("ceshi InitializingBean");        
    }
    public void testInit(){
        System.out.println("ceshi init-method");        
    }
}

定义一个配置类如下:

package com.kuangstudy.initializingbean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TestInitializingBeanConfiguration {
    @Bean(initMethod = "testInit")
    public TestInitializingBean getTestInitializingBean() {
        return new TestInitializingBean();
    }
}

运行程序,得出结果:

ceshi InitializingBean
ceshi init-method

从结果可以看出,在Spring初始化bean的时候,如果该bean实现了InitializingBean接口,并且同时在配置文件中指定了init-method,系统则是先调用afterPropertieSet()方法,然后再调用init-method中指定的方法。

那么这种方式在spring中是怎么实现的呢,通过查看Spring加载bean的源码类AbstractAutowiredCapableBeanFactory可以看出其中的奥妙,AbstractAutowiredCapableBeanFactory类中的invokeInitMethods说的非常清楚,如下:

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable {
    //判断该bean是否实现了实现了InitializingBean接口,如果实现了InitializingBean接口,则只掉调用bean的afterPropertiesSet方法
    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (logger.isDebugEnabled()) {
            logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
        }
        if (System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                    public Object run() throws Exception {
                        //直接调用afterPropertiesSet
                        ((InitializingBean) bean).afterPropertiesSet();
                        return null;
                    }
                },getAccessControlContext());
            } catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        }                
        else {
            //直接调用afterPropertiesSet
            ((InitializingBean) bean).afterPropertiesSet();
        }
    }
    if (mbd != null) {
        String initMethodName = mbd.getInitMethodName();
        //判断是否指定了init-method方法,如果指定了init-method方法,则再调用制定的init-method
        if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                !mbd.isExternallyManagedInitMethod(initMethodName)) {
            //进一步查看该方法的源码,可以发现init-method方法中指定的方法是通过反射实现
            invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}

02、使用场景

  • 在SpringMVC中AbstractHandlerMethodMapping就实现了InitializingBean接口,当一个RequestMappingHandlerMapping的实例创建完成后会接着调用afterPropertiesSet方法,扫描所有的controller完成所有的handler method的注册。
  • 在mybatis中的SqlSessionFactoryBean中,用来初始化sqlSessionFactory,以及断言判断有没有数据源等。

03、总结:

1、Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。
2、实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。
3、如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。

三十、Spring中Bean 的完整生命周期

Spring Bean的生命周期(非常详细) - Chandler Qian - 博客园

在传统的Java应用中,bean的生命周期很简单,使用Java关键字 new 进行Bean 的实例化,然后该Bean 就能够使用了。一旦bean不再被使用,则由Java自动进行垃圾回收。

相比之下,Spring管理Bean的生命周期就复杂多了,正确理解Bean 的生命周期非常重要,因为Spring对Bean的管理可扩展性非常强,下面展示了一个Bean的构造过程

spring boot saml spring boot saml2_spring_09

如上图所示,Bean 的生命周期还是比较复杂的,下面来对上图每一个步骤做文字描述:

  • Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
  • Bean实例化后对将Bean的引入和值注入到Bean的属性中
  • 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
  • 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
  • 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来
  • 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
  • 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
  • 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
  • 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
  • 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

 摘自org.springframework.beans.factory.BeanFactory, 全部相关接口如下,上述已有的就不用着重标注,把额外的相关接口着重标注下

spring boot saml spring boot saml2_java_10

————————————初始化————————————

  • BeanNameAware.setBeanName() 在创建此bean的bean工厂中设置bean的名称,在普通属性设置之后调用,在InitializinngBean.afterPropertiesSet()方法之前调用
  • BeanClassLoaderAware.setBeanClassLoader(): 在普通属性设置之后,InitializingBean.afterPropertiesSet()之前调用
  • BeanFactoryAware.setBeanFactory() : 回调提供了自己的bean实例工厂,在普通属性设置之后,在InitializingBean.afterPropertiesSet()或者自定义初始化方法之前调用
  • EnvironmentAware.setEnvironment(): 设置environment在组件使用时调用
  • EmbeddedValueResolverAware.setEmbeddedValueResolver(): 设置StringValueResolver 用来解决嵌入式的值域问题
  • ResourceLoaderAware.setResourceLoader(): 在普通bean对象之后调用,在afterPropertiesSet 或者自定义的init-method 之前调用,在 ApplicationContextAware 之前调用。
  • ApplicationEventPublisherAware.setApplicationEventPublisher(): 在普通bean属性之后调用,在初始化调用afterPropertiesSet 或者自定义初始化方法之前调用。在 ApplicationContextAware 之前调用。
  • MessageSourceAware.setMessageSource(): 在普通bean属性之后调用,在初始化调用afterPropertiesSet 或者自定义初始化方法之前调用,在 ApplicationContextAware 之前调用。
  • ApplicationContextAware.setApplicationContext(): 在普通Bean对象生成之后调用,在InitializingBean.afterPropertiesSet之前调用或者用户自定义初始化方法之前。在ResourceLoaderAware.setResourceLoader,ApplicationEventPublisherAware.setApplicationEventPublisher,MessageSourceAware之后调用。
  • ServletContextAware.setServletContext(): 运行时设置ServletContext,在普通bean初始化后调用,在InitializingBean.afterPropertiesSet之前调用,在 ApplicationContextAware 之后调用注:是在WebApplicationContext 运行时
  • BeanPostProcessor.postProcessBeforeInitialization() : 将此BeanPostProcessor 应用于给定的新bean实例 在任何bean初始化回调方法(像是InitializingBean.afterPropertiesSet或者自定义的初始化方法)之前调用。这个bean将要准备填充属性的值。返回的bean示例可能被普通对象包装,默认实现返回是一个bean。
  • BeanPostProcessor.postProcessAfterInitialization() : 将此BeanPostProcessor 应用于给定的新bean实例 在任何bean初始化回调方法(像是InitializingBean.afterPropertiesSet或者自定义的初始化方法)之后调用。这个bean将要准备填充属性的值。返回的bean示例可能被普通对象包装
  • InitializingBean.afterPropertiesSet(): 被BeanFactory在设置所有bean属性之后调用(并且满足BeanFactory 和 ApplicationContextAware)。

————————————销毁————————————
在BeanFactory 关闭的时候,Bean的生命周期会调用如下方法:

  • DestructionAwareBeanPostProcessor.postProcessBeforeDestruction(): 在销毁之前将此BeanPostProcessor 应用于给定的bean实例。能够调用自定义回调,像是DisposableBean 的销毁和自定义销毁方法,这个回调仅仅适用于工厂中的单例bean(包括内部bean)
  • 实现了自定义的destory()方法