Bean注入容器的多种方式

一、将Bean注册到Spring容器的方式:

  • 静态注册
  • 条件注册
  • 动态注册
Spring容器组件添加五大法宝:

1. 包扫描+组件标注注解
2. 包扫描+@Bean
3. @Import / @ImportResource
4. 包扫描+FacotoryBean
5. 包扫描+ BeanFactoryPostProcessor

只有@Import是不需要包扫描(ComponentScan / @SpringBootApplication(scanBasePackages = {"com.example.demo1"})),可以直接引入其他模块或者第三方jar包的类,该类可以添加了component系列注解的类,或者是个普通的实体类也可以加载到容器中。

@ComponentScan 的作用就是根据定义的扫描路径,把符合扫描规则的类装配到spring容器中。
如果需要注入容器的组件(类)不在@ComponentScan的扫描范围内(如:在其他的模块中, 或者引入的第三方jar包),则需要显示的把路径添加进来,如:
@SpringBootApplication(scanBasePackages = {"com.example.demo1", "com.guanyu.common"}))

以SpringBoot工程举例:
Bean注入容器的多种方式_第1张图片
DemoApplication启动类在根目录,则该目录及其子目录内的所有通过@Conponent系列标注的类都可以被扫描到,注入到Spring容器中。
这时候,如果是和com.example.demo并列的包,或者更上一层的包内通过@Conponent系列标注的类无法被DemoApplication扫描到的,也就无法注入Spring容器中,这时候,需要在启动类中手动添加扫描路径:@SpringBootApplication(scanBasePackages = {“com.example.demo1”, “com.guanyu.common”})),即可将其他模块或者路径下的类扫描进容器中。

如果是第三方jar包中的类,也是无法直接扫描进容器的
解决方法1:如果第三方jar中的类已经添加了@Compoent系列注解,可以将路径添加扫描路径,使其可以被扫描到,也就可以进入Spring容器。
解决方法2:如果第三方jar中的类没有添加@Compoent系列注解,可以使用@Import进行注入,当然如果第三方jar中添加了@Component注解,也是可以使用@Import进行注入。

二、详细介绍

1、静态注册
方式1: xml配置方式

<bean id="xxxx"  class="xxxx.xxxx"/>

方式2: 注解

(1)使用@Component系列
    这几个注解都是同样的功能,被注解的类将会被Spring 容器创建单例对象。
    @Component : 侧重于通用的Bean类
    @Service:标识该类用于业务逻辑
    @Controler:标识该类为Spring MVC的控制器类
    @Repository: 标识该类是一个实体类,只有属性和Setter,Getter
      
(2)@Configuration系列
    @Configuration + @Bean
    @Configuration + @Import
    @Configuration + @Import + ImportSelector
    
    当我们设计一个 class,注册给 ioc 容器来管理对象的生命周期时,会使用 @Component, @Repository , @Controller , @Service 标识到。但是这样模式下,只能限于自己编写的类。如果依赖到一个第三方库的 class,也想交由 Ioc 机制来管理和使用,由于我们没有源码无法加入 @Component 等注解声明,就需要 @Bean 或 @Import 来把三方 class 注册 (导入) 给 Ioc 容器。
    
    @Bean 和 @Import的区别是:@Bean用在方法上,@Import用在类上

   【1】@Import
       参数value接收一个Class数组,将你传入的类以全类名作为id加入IOC容器中
   【2】ImportSelector
       ImportSelector强调的是复用性,使用它需要创建一个类实现ImportSelector接口,实现方法的返回值是字符串数组,也就是需要注入容器中的组件的全类名。id同样也是全类名。

@Import示例

public class PiggUser {

    private String name;

    private Integer age;

    public PiggUser() {
        System.out.println("初始化了PiggUser()");
    }
    //省略get,set
}

public class PiggPet {

    private String name;

    private Integer age;

    public PiggPet() {
        System.out.println("初始化了PiggPet()");
    }
    //省略get,set
}

@Configuration
@Import({PiggUser.class, PiggPet.class})
public class PiggMainConfig {
	
	public PiggMainConfig() {
        System.out.println("初始化了PiggMainConfig()");
    }
}

@Import + ImportSelector 示例

public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"info.pigg.dict.entity.PiggPet","info.pigg.dict.entity.PiggUser"};
    }
}

@Configuration
@Import(MyImportSelector.class)
public class PiggMainConfig {

    public PiggMainConfig() {
        System.out.println("初始化了PiggMainConfig()");
    }
}

@Import + ImportSelector 示例


import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName(Person.class.getName());
        MutablePropertyValues values = beanDefinition.getPropertyValues();
        values.addPropertyValue("age", 10);
        values.addPropertyValue("name", "ZhangSan");
        //这里注册bean
        registry.registerBeanDefinition("person666", beanDefinition );

    }
}

@Configuration
@Import(MyImportSelector.class)
public class PiggMainConfig {

    public PiggMainConfig() {
        System.out.println("初始化了PiggMainConfig()");
    }
}

方式3:@ImportResource + < bean id=“”>
这种方法实际上是通过读取xml文件来配置bean,首先我们定义一个bean.xml,并在其中定义bean:
Bean注入容器的多种方式_第2张图片
然后我们创建一个配置类,类上标注@Configuration告诉spring这是个注解类,再用@ImportResource注解类,并告诉spring我们需要读取的xml文件位置:
Bean注入容器的多种方式_第3张图片
Bean注入容器的多种方式_第4张图片
执行结果:
在这里插入图片描述

2、条件注册bean
使用Condition,实现步骤:
(1)定义一个接口

package com.example.demo.service;

public interface UserService{
    void register();
}
(2)定义一个实现类
public class UserServiceImpl implements UserService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    @Override
    public void register(){
        UserDTO userDTO = new UserDTO(this);
        userDTO.setAge(10);
        userDTO.setUserId(1001);
        System.out.println("register user");
        eventPublisher.publishEvent(userDTO);
    }
}
(3)定义一个MyCondition 实现 Condition
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * 

项目名称:

*

文件名称:

*

功能描述:

*

创建时间: 2022/8/19

* @author Top * @version v1.0 */
public class MyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //实现具体的逻辑判断,然后返回true 或者false,这里也就是条件注册bean的关键点 return true; } }
(4)定义配置类
import com.example.demo.service.impl.User2ServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

/**
 * 

项目名称:

*

文件名称:

*

功能描述:

*

创建时间: 2022/8/19

* @author Top * @version v1.0 */
@Configuration public class MyConfig { @Bean @Conditional(MyCondition.class) public User2ServiceImpl user2Service(){ return new User2ServiceImpl(); } }

@Conditional的扩展注解
@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。

@ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。

@ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。

@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。

@ConditionalOnMissingClass:某个class类路径上不存在的时,才会实例化一个Bean。

@ConditionalOnNotWebApplication:不是web应用时,才会实例化一个Bean。

@ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。

@ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。

@ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。

@ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。

@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。

@ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。

@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。

@ConditionalOnExpression:基于SpEL表达式的条件判断。

@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。

@ConditionalOnResource:当类路径下有指定的资源时触发实例化。

@ConditionalOnJndi:在JNDI存在的条件下触发实例化。

@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。

3、动态注册bean

方法1: 使用FactoryBean
- FactoryBean也叫做Bean工厂,是用于生产Bean对象的类

FactoryBean通常是用来创建比较复杂的Bean,但是在ioc容器启动的时候不会创建实例,只有在使用的时候才会创建对象

(1)定义

import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

@Component
public class MyFactoryBean implements FactoryBean {


    @Override
    public Person getObject() throws Exception {
        Person person = new Person();
        person.setAge(20);
        person.setName("guoguo");
        return person;
    }

    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }

}

(2)调用

    @Autowired
    private Person person;

    @GetMapping("/myPage")
    @ResponseBody
    public Map<Object, Object> expenseStatement(Object str) throws IOException {
        System.out.println(person);
        return null;
    }

方法2: 使用BeanDefinitionRegistryPostProcessor

(1)定义接口

public interface UserService{
    void register();
}
(2)定义实现类
public class UserServiceImpl implements UserService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    @Override
    public void register(){
        UserDTO userDTO = new UserDTO(this);
        userDTO.setAge(18);
        userDTO.setName("精灵王jinglingwang.cn");
        userDTO.setUserId(1001);
        System.out.println("register user");
        eventPublisher.publishEvent(userDTO);
    }
}
(3)定义注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CoreAnnotation {
    String[] value() default {};
}
(4)动态注册bean
package com.example.demo.common;


import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

@Component
public class DefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, EnvironmentAware {
    private Environment environment;
    private ResourceLoader resourceLoader;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        //用扫描器根据指定注解进行扫描获取BeanDefinition
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry, false, environment, resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(CoreAnnotation.class));
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents("com");
        try {
            registerCandidateComponents(beanDefinitionRegistry, candidateComponents);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;

    }

    /**
     * 注册 BeanDefinition
     */
    private void registerCandidateComponents(BeanDefinitionRegistry registry, Set<BeanDefinition> candidateComponents) throws ClassNotFoundException {
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                Map<String, Object> customImportAnnotationAttributesMap = annotationMetadata.getAnnotationAttributes(CoreAnnotation.class.getName());
                AnnotationAttributes customImportAnnotationAttributes = Optional.ofNullable(AnnotationAttributes.fromMap(customImportAnnotationAttributesMap)).orElseGet(AnnotationAttributes::new);
                String[] values = customImportAnnotationAttributes.getStringArray("value");
                String className = annotationMetadata.getClassName();
                Class<?> clazzName = Class.forName(className);
//                AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(CustomImportFactoryBean.class)
//                        .addPropertyValue("type", clazzName)
//                        .addPropertyValue("beanName", beanName)
//                        .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
//                        .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
//                        .getBeanDefinition();
//                registry.registerBeanDefinition(beanName, beanDefinition);
                Arrays.asList(values).forEach(m ->{
                    RootBeanDefinition mbean = null;
                    try {
                        mbean = new RootBeanDefinition(clazzName);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    registry.registerBeanDefinition(m, mbean);
                });
            }
        }
    }
}

方法3: 使用ImportBeanDefinitionRegistrar
(1)定义接口

package com.example.demo.service;

public interface UserService{
    void register();
}

(2)定义实现类

public class UserServiceImpl implements UserService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    @Override
    public void register(){
        UserDTO userDTO = new UserDTO(this);
        userDTO.setAge(10);
        userDTO.setUserId(1001);
        System.out.println("register user");
        eventPublisher.publishEvent(userDTO);
    }
}

(3)定义import

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface EnableCustomImport {
    String[] packages() default {};
}

(4)动态注册bean

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    private Environment environment;
    private ResourceLoader resourceLoader;

    @SneakyThrows
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean enableCustomImport = importingClassMetadata.hasAnnotation(EnableCustomImport.class.getName());
        //@Import不是在这个EnableCustomImport注解上的不执行
        if (!enableCustomImport) {
            return;
        }
        Map<String, Object> annotationAttributesMap = importingClassMetadata.getAnnotationAttributes(EnableCustomImport.class.getName());
        AnnotationAttributes annotationAttributes = Optional.ofNullable(AnnotationAttributes.fromMap(annotationAttributesMap)).orElseGet(AnnotationAttributes::new);
        // 获取需要扫描的包
        String[] packages = retrievePackagesName(importingClassMetadata, annotationAttributes);
        // useDefaultFilters = false,即第二个参数 表示不扫描 @Component、@ManagedBean、@Named 注解标注的类
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false, environment, resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(CoreAnnotation.class));
        // 扫描包
        for (String needScanPackage : packages) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(needScanPackage);
            try {
                registerCandidateComponents(registry, candidateComponents);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 获取需要扫描的包
     */
    private String[] retrievePackagesName(AnnotationMetadata annotationMetadata, AnnotationAttributes annotationAttributes) {
        String[] packages = annotationAttributes.getStringArray("packages");
        if (packages.length > 0) {
            return packages;
        }
        //如果不存在,则默认第一个包开始
        String className = annotationMetadata.getClassName();
        return new String[]{className.split("\\.")[0]};
    }
    
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

三、使用场景
(1)最常见的注册方式

  • 静态注册

(2)最灵活的注册方式

  • 条件注册

(3)更加自定义注册方式

  • 动态注册
    (如果要写一个类似于 mybatis @mapper 注解这种,Feign 远程调用实现,一般都是用一个接口定义好就可用了,底层就是需要我们动态代理注册bean进入容器中的)

你可能感兴趣的:(Java,spring,java,spring,boot)