前言

    Springboot装配和注入及使用场景举例,纯干货,带你真正玩转Springboot.......

装配:创建bean,并加入IOC容器。

注入:创建bean之间的依赖关系。

1.注入的几种方式:

  • 通过构造函数注入
  • 通过属性注入
  • 通过Setter方法注入
  • 通过@Configuration + @bean,成员方法注入
/**
 * @author dlhe
 * @date 2023-02-14 15:55
 * @description 抽象bean接口
 */
public interface CompanyBean {

    public Person getWirePerson();
}

1.1构造器注入: 

/**
 * @author dlhe
 * @date 2023-02-14 15:31
 * @description 通过构造函数注入
 */
@Component("companyWireByConstruct")
public class CompanyWireByConstruct implements CompanyBean{

    private final Person person;

    // 此处可以省略 @Autowired,因为创建bean时会执行构造器
    CompanyWireByConstruct(Person person){
        this.person =person;
    }

    @Override
    public Person getWirePerson() {
        return this.person;
    }
}

1.2 属性注入

/**
 * @author dlhe
 * @date 2023-02-14 15:31
 * @description 通过属性注入
 */
@Component("companyWireByProperty")
public class CompanyWireByProperty implements CompanyBean{

    @Autowired
    private Person person;

    @Override
    public Person getWirePerson() {
        return this.person;
    }
}

1.3 Setter方法注入

/**
 * @author dlhe
 * @date 2023-02-14 15:31
 * @description 通过Setter注入
 */
@Component("companyWireBySetter")
public class CompanyWireBySetter implements CompanyBean{

    private Person person;

    @Autowired
    public void setPerson(Person person) {
        this.person = person;
    }

    @Override
    public Person getWirePerson() {
        return this.person;
    }
}

1.4 @Bean方式:

/**
 * @author dlhe
 * @date 2023-02-14 15:24
 * @description springboot中 @Configuration + @bean,成员方法注入
 */
@Configuration
public class CompanyConfig {

    @Bean("companyConfigBean")
    public CompanyBean company(Person person){
        CompanyWireBySetter company =new CompanyWireBySetter();
        company.setPerson(person);
        return company;

    }
}

测试类:

/**
 * @author dlhe
 * @date 2023-02-14 16:19
 * @description
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class BeanTest {

    @Autowired
    @Qualifier("companyWireByConstruct")
    CompanyBean companyWireByConstruct;

    @Autowired
    @Qualifier("companyWireBySetter")
    CompanyBean companyWireBySetter;

    @Autowired
    @Qualifier("companyWireByProperty")
    CompanyBean companyWireByProperty;

    @Autowired
    @Qualifier("companyConfigBean")
    CompanyBean companyConfigBean;

    @Test
    public void testCompany(){
        System.out.println(companyWireByConstruct.getWirePerson().toString());
    }

    @Test
    public void testCompany1(){
        System.out.println(companyWireBySetter.getWirePerson().toString());
    }

    @Test
    public void testCompany2(){
        System.out.println(companyWireByProperty.getWirePerson().toString());
    }

    @Test
    public void testCompany3(){
        System.out.println(companyConfigBean.getWirePerson().toString());
    }
}

2.装配的几种方式:

  • xml方式定义
  • 扫描包+模式注解--》@componetScan + @componet,@service,@comtroller,@resource,@resposity....
  • @Configuration + @Bean ,即方式一的注解方式。
  • @EnableConfigurationProperties + @ConfigurationProperties 一般用来装配配置文件。
  • @import导入配置类或普通类,多个用逗号隔开
  • 实现FactoryBean接口动态构建一个bean(OpenFeign就是基于此实现)
  • 实现ImportBeanDefinitionRegistrar接口,批量注册bean(扫描包下的带注解的),Springboot的启动类注解有用到。
  • 在resources里面下,新建META-INF/spring.factories 文件里,申明被自动装配的configration,作为jar给别人依赖时会被装配进容器。
  • @import + ImportSelector接口,启动类加上自定义@EnableXXX 注解,作为jar给别人依赖时会被装配进容器。

2.1 @import举例:

//如下Man会装配进来并注册
@Import(Man.class)

@Configuration("manImport")
public class ManImport implements ManBean {
    Man man;

    ManImport(Man man){
        this.man = man;
    }

    @Override
    public Man getWireMan() {
        return this.man;
    }
}

2.2 FactoryBean动态构建bean举例:

场景举例:输入日志接口:分为控制台(console)和日志管理器(Logger)两类,现若只调用日志接口打印方法,如何按日志的级别(方法参数)自动选择输入器输出?

或者进一步,若日志级别0,则用日志管理器类,并按排序的优先级

解决方案:

// 日志父接口
public interface ISpi<T> {
    //满足条件

    boolean verify(T condition);

    /**
     * 排序,数字越小,优先级越高
     * @return
     */
    default int order() {
        return 10;
    }
}

日志接口和日志实现类:

public interface IPrint extends ISpi<Integer> {

    default void execute(Integer level, Object... msg) {
        print(msg.length > 0 ? (String) msg[0] : null);
    }

    void print(String msg);
}

@Component
public class ConsolePrint implements IPrint {
    @Override
    public void print(String msg) {
        System.out.println("console print: " + msg);
    }

    @Override
    public boolean verify(Integer condition) {
        return condition <= 0;
    }
}

@Slf4j
@Component
public class LogPrint implements IPrint {
    @Override
    public void print(String msg) {
        log.info("log print: {}", msg);
    }

    @Override
    public boolean verify(Integer condition) {
        return condition > 0;
    }
}

重点:代理对象(FactoryBean实现类)

public class SpiFactoryBean<T> implements FactoryBean<T> {
    private Class<? extends ISpi> spiClz;

    private List<ISpi> list;

    public SpiFactoryBean(ApplicationContext applicationContext, Class<? extends ISpi> clz) {
        this.spiClz = clz;

        Map<String, ? extends ISpi> map = applicationContext.getBeansOfType(spiClz);
        list = new ArrayList<>(map.values());
        list.sort(Comparator.comparingInt(ISpi::order));
    }

    @Override
    @SuppressWarnings("unchecked")
    public T getObject() throws Exception {
        // jdk动态代理类生成
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                for (ISpi spi : list) {
                    if (spi.verify(args[0])) {
                        // 第一个参数作为条件选择
                        return method.invoke(spi, args);
                    }
                }

                throw new NoSpiChooseException("no spi server can execute! spiList: " + list);
            }
        };

        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{spiClz},
                invocationHandler);
    }

    @Override
    public Class<?> getObjectType() {
        return spiClz;
    }
}

代理对象的装配和注入:

@Configuration
public class PrintAutoConfig {

    /*
    * 此处注入的是applicationContext上下文,来创建代理对象
    */
    @Bean
    public SpiFactoryBean printSpiPoxy(ApplicationContext applicationContext) {
        return new SpiFactoryBean(applicationContext, IPrint.class);
    }
    /*
    * 注入的是代理对象,创建的是代理实例
    */

    @Bean
    @Primary
    public IPrint printProxy(SpiFactoryBean spiFactoryBean) throws Exception {
        return (IPrint) spiFactoryBean.getObject();
    }
}

2.3 ImportBeanDefinitionRegistrar批量注册bean

场景:把某个包下带某个注解的类都加到容器

1.定义扫描注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Savey {
}

2.定义启动类注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(ImportCustomBeanDefinitionRegistrar.class)
public @interface EnableCustomBean {

    /**
     * 定义要扫描的包
     */
    String[] basePackages() default {};
}

3.生效启动类注解:

@SpringBootApplication
@EnableCustomBean(basePackages = {"com.boot.boot.savey"})
public class BootApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootApplication.class, args);
    }

}

4.创建目录及目标bean:

创建com.boot.boot.savey目录,在目录下创建一些要自定义Bean,并给类加上 Savey注解!!

@Savey
@Getter
@Setter
@ToString
public class Money {}

@Savey
@Getter
@Setter
@ToString
public class Work {}

//这个类就不加Savey注解了,不想它加入到容器,不想玩游戏,只想工作!所以不加了!
@Getter
@Setter
@ToString
public class PlayGame {}

5.实现ImportBeanDefinitionRegistrar接口:

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //获取EnableCustoBean注释的属性
    final Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableCustomBean.class.getName());

    //获取包扫描
    ClassPathScanningCandidateComponentProvider pathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);

    //添加过滤 带有Savey这个注解的类
    pathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Savey.class));

    LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();

    for (String basePackages : (String[]) attributes.get("basePackages")) {
        candidateComponents.addAll(pathScanningCandidateComponentProvider.findCandidateComponents(basePackages));
    }
    
    //注册Bean
    for (BeanDefinition candidateComponent : candidateComponents) {
        registry.registerBeanDefinition(candidateComponent.getBeanClassName(), candidateComponent);
    }
}

6.test

@SpringBootTest
public class ImportCustomScanPackagesDefinitionRegisterTest {

    @Resource
    ApplicationContext applicationContext;

    @Test
    public void test_scan_packages() {
        //返回容器中所有bean
        Arrays.stream(applicationContext.getBeanDefinitionNames()).forEach(System.out::println);
    }
}

-------
输出
-------
com.boot.boot.savey.Money
com.boot.boot.savey.Work

spring.factories: 

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.dlhe.config.PersonConfig

2.4 @import + ImportSelector接口

1.定义configration:

@Configuration
@EnableConfigurationProperties(Person.class)
public class PersonConfig implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println(".......created PersonConfig");
    }
}

2.实现ImportSelector,指定装配的configration

ublic class MyWireSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{PersonConfig.class.getName()};
    }

}

3.定义启动注解:

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
//导入目标Seletor
@Import(MyWireSelector.class)
public @interface EnableOutWireBean {

}

4.注解生效:(外部项目启动类中)

@SpringBootApplication
@EnableOutWireBean
public class Demo1Applicaiotn {
    public static void main(String[] args) {

        SpringApplication.run(Demo1Applicaiotn.class);
    }
}

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐