Spring IOC 知识点汇总

阿福聊编程

@Configuration配置spring并启动spring容器

@Configuration用于定义配置类,标注在类上,相当于把该类作为spring的xml配置文件中的<beans>,作用是用于配置spring容器(应用上下文)

实例说明:

配置类

@Configuration
public class MyConfig {
    public MyConfig() {
        System.out.println("TestConfig容器初始化!!!!");
    }
}

相当于xml

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

测试类

public class TestConfig {
    @Test
    public void test(){
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MyConfig.class);
    }
}

运行结果

TestConfig容器初始化!!!!

@Configuration&@Bean给容器中注册组件

@Bean标注在方法上,相当于xml配置配置中的</bean>

代码实例说明:

注册类

public class ConfigurationBean {
    public void start() {
        System.out.println("容器初始化开始!!!!!");
    }
}

配置类

@Configuration
public class MyConfiguration {
    //可以指定bean 的名称,也可以指定注入容器之前加载的方法
    @Bean()
     public ConfigurationBean configurationBean(){
         return new ConfigurationBean();
     }

}

相当于xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="configurationBean" class="zfcoding.bean.ConfigurationBean" ></bean> 
</beans>

测试方法

@Test
    public void ConfigurationTest(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
        ConfigurationBean configurationBean=(ConfigurationBean) applicationContext.getBean("configurationBean");
        System.out.println(configurationBean);
    }

运行结果:

容器初始化开始!!!!!
zfcoding.bean.ConfigurationBean@77be656f

说明几点:

(1)、@Bean注解标注在方法上,如果未通过@Bean指定bean的名称,则默认与标注的方法名相同;

(2)、@Bean注解标注在方法上,如果想通过@Bean指定bean的名称,例如 @Bean("configurationBean1"),那么容器中实例化bean的名称为configurationBean1;

@Bean 指定初始化和销毁的方法及@Scope ,@Lazy

容器bean管理的生命周期:

我们可以自定义初始化和销毁的方法,容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁的方法。

@Bean 中指定初始化和销毁的方法。

@Bean(initMethod = "init",destroyMethod = "destory")

xml

<bean id="student" class="zfcoding.bean.Student"  init-method="init" destroy-method="destory"></bean>

@Scope

默认情况下: singleton 单实例(默认值),ioc容器启动会调用方法创建对象放到ioc容器当中。以后每次获取直接获取对象。

prototype :多实例,ioc容器启动并不会调用方法创建对象放到容器当中。,而是每次获取的时候才会调用方法创建对象,调用一次创建一次。

@Lazy 懒加载,

针对单实例bean,默认在容器启动的时候创建对象

懒加载:容器启动不创键对象,第一次使用Bean创建对象,并初始化。

实例说明:

public class Student {
    
    public Student() {
        System.out.println("Student容器加载开始!!!!");
    }
    //对象创建完成,并赋值好,调用初始化方法......
    public void init() {
        System.out.println("Student.init()初始化开始.....");
    }
    //销毁针对(单实例),容器关闭的时候调用。
    //多实例(@Scope("prototype")):容器不会管理这个bean,容器也不回调用销毁的方法。
    public void destory() {
        System.out.println("Student.destory().........");
    }
}
@Configuration
public class MyConfigStudent {
    //@Lazy
    //@Scope("prototype")
    @Bean(value = "student",initMethod = "init",destroyMethod = "destory")
    public Student student(){
        return new Student();
    }
}

测试类一

public class MyConfigStudentTest {
    @Test
    public void test(){
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MyConfigStudent.class);
    }
}

运行结果

Student容器加载开始!!!!
Student.init()初始化开始.....

那么说明,ioc容器启动会调用方法创建对象放到ioc容器当中。以后每次获取直接获取对象。

如果我们使用@Lazy注解(单实例),容器启动不创键对象,测试结果没有输出信息。

测试类二,我们第一次使用Bean创建对象,并初始化

public class MyConfigStudentTest {
    @Test
    public void test(){
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MyConfigStudent.class);
        Student student = (Student)applicationContext.getBean("student");
        System.out.println(student);
        applicationContext.close();
    }
}

运行结果

Student容器加载开始!!!!
Student.init()初始化开始.....
zfcoding.bean.Student@7364985f
Student.destory().........

说明懒加载(@Lazy):当容器启动不创键对象,而是第一次使用Bean创建对象,并初始化。

InitializingBean 和DisposableBean

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

测试类

public class TestInitializingBean implements InitializingBean {
    public void afterPropertiesSet() throws Exception {
        System.out.println("TestInitializingBean.afterPropertiesSet()......");
    }

    public void init() {
        System.out.println("TestInitializingBean.init().......");
    }
}

配置类

@Configuration
public class MyInitializingBeanConfig {
    @Bean(value = "testInitializingBean",initMethod = "init")
    public TestInitializingBean testInitializingBean(){
        return new TestInitializingBean();
    }
}

测试类

public class TestnitializingBeanConfig {
    @Test
    public void test(){
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MyInitializingBeanConfig.class);
    }
}

运行结果:

TestInitializingBean.afterPropertiesSet()......
TestInitializingBean.init().......

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

那么这种方式在spring中是怎么实现的呢,通过查看Spring加载bean的源码类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>() {
                        @Override
                        public Object run() throws Exception {
                            ((InitializingBean) bean).afterPropertiesSet();
                            return null;
                        }
                    }, getAccessControlContext());
                }
                catch (PrivilegedActionException pae) {
                    throw pae.getException();
                }
            }
            else {
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }

        if (mbd != null) {
            String initMethodName = mbd.getInitMethodName();
            if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                    !mbd.isExternallyManagedInitMethod(initMethodName)) {
                invokeCustomInitMethod(beanName, bean, mbd);
            }
        }
    }

总结:

Spring为bean提供了两种初始化bean的方式,一种是实现InitializingBean接口,重写afterPropertiesSet方法,另一种四在配置文件中通过init-method指定,但是如果同时指定的话是先调用InitializingBean实现的方法。

@PostConstruct和@PreDestroy

可以使用JS250,@PostConstruct 标注在方法上面,bean创建完成并且属性赋值完成,来执行初始化方法

@PreDestroy ,在容器销毁bean之前通知我们进行清理工作.

@ComponentScan&@Configuration给容器注册组件

@Configuration+@ComponentScan 注入组件, 只是把标注了注解为@Controller ,@Service, @Repository, @Component 加入到Spring容器中.

xml 方式的配置

<context:component-scan base-package="zfcoding"></context:component-scan>

代码实例说明:

需要注入的类

@Component
public class PersonComponent {
}
@Repository
public class PersonDao {
}
@Service
public class PersonService {
}
@Controller
public class PersonController {
}

配置类


@Configuration
//@ComponentScan(basePackages = "zfcoding",
////        includeFilters =@ComponentScan.Filter(type=FilterType.ANNOTATION,classes = Controller.class),useDefaultFilters=false )
@ComponentScan(basePackages = "zfcoding",
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}))
public class MyCompoentScan {

}

测试的方法

 @Test
    public void ComponentSanTest(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyCompoentScan.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String definitionName:definitionNames){
            System.out.println(definitionName);
        }
    }

运行结果

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myCompoentScan
personComponent
personDao

说明:

excludeFilters 排除不需要加载的组件

includeFilters 只包含需要加载的组件,使用的时候需要把 useDefaultFilters=false,才能生效

@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}),指定的过滤规则,排除注解标注是@Controller和@Service 类。

@Conditional-设置组件作用域

按照一定的条件进行判断,满足条件给容器中注册bean

自定义Conditional条件需要编写一个类,这个类需要实现Condition接口,我们直接使用就@Conditional({WindowsEnvironment.class})

@Conditional既可以定义在方法上,也可以定义在类上,代码实现自定义条件需要实现Condition接口

条件类

public class WindowsEnvironment implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        BeanDefinitionRegistry registry = context.getRegistry();
        if(property.contains("Windows 10")){
           return true;
        }
        return false;
    }
}
public class LiunxEnvironment implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        if (environment.containsProperty("Liunx")){
            return true;
        }
        return false;
    }
}

配置类

@Configuration
public class MyCondition {

    @Conditional({WindowsEnvironment.class})
    @Bean
    public ConfigurationBean configurationBean(){
        return new ConfigurationBean("张三");
    }
    @@Conditional({LiunxEnvironment.class})
    @Bean
    public ConfigurationBean configurationBean1(){
        return new ConfigurationBean("李四");
    }
}

测试方法

public class CoditionTest {
    @Test
    public void test(){
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MyCondition.class);
        String[] beanNamesForType = applicationContext.getBeanNamesForType(ConfigurationBean.class);
        for (String s:beanNamesForType){
            System.out.println(s);
        }
    }
}

运行结果

configurationBean

@Import 给容器快速导入一个组件

1、@Import(要导入的组件),容器就会自动注册这个组件,id默认是全类名。

2、ImportSelector , 自定义逻辑返回要导入的组件 编写的类学要实现 ImportSelector 接口,返回需要导入的主键的全类名数组。

3、ImportBeanDefinitionRegistrar,自定义逻辑需要编写类实现ImportBeanDefinitionRegistrar接口,手动注册bean到容器当中。

代码实例

public class Red {
}
public class Blue {
}
//
@Import({Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MyImport {
   
}
public class MyImportSelector implements ImportSelector {

    //AnnotationMetadata :当前标注@Import 注解类的所有注解信息
    //返回类的全类名
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        return new String[]{"zfcoding.bean.Blue"};
    }
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
     //importingClassMetadata:当前类的注解信息
    //BeanDefinitionRegistry  BeanDefinition注册类,把所有需要添加到容器中的bean,BeanDefinitionRegistry.registerBeanDefinition手工注册

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean red = registry.containsBeanDefinition("zfcoding.bean.Red");
        if (red){
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Blue.class);
            registry.registerBeanDefinition("blue",rootBeanDefinition);
        }

    }
}

测试类

public class ImportTest {
    @Test
    public void test(){
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MyImport.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String s:beanDefinitionNames){
            System.out.println(s);
        }
    }
}

运行结果

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactorymyImport
zfcoding.bean.Red
zfcoding.bean.Blue
blue

总结:

给容器中导入组件:

​ 1、@Configuration&@Bean给容器中注册组件

​ 2、@ComponentScan&@Configuration(@Controller ,@Service, @Repository, @Component)

​ 3、@Import 快速的给容器中导入一个组件

@Value赋值 和@PropertySource 加载外部配置文件

@Value

​ 1、基本数值

​ 2、SPEL #{}

​ 3、 可以写${},去配置文件中的值(运行环境中的值)

使用@PropertySource去取外部配置文件中的值保存到运行的环境当中。加载完尾部的配置文件之后,使用${}取出配置文件中的值.

代码实例:

代码省略setter(),getter(),toString()方法。

public class Person {
    @Value("张三")
    private String name;
    @Value("#{20-1}")
    private Integer age;
    @Value("${person.sex}")
    private String sex;
}
//<context:property-placeholder location="person.properties"></context:property-placeholder>
@PropertySource("classpath:person.properties")
@Configuration
public class MyConfigValue {
      @Bean
      public Person person(){
          return new Person();
      }
}

person.properties

person.sex=1

测试类

@Test
    public void test1(){
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MyConfigValue.class);
        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);
    }

运行结果

Person{name='张三', age=19, sex='1'}

@Autowired &@Qualifier&@Primary

自动装配:

@Autowired自动注入,单个默认优先按照类型去容器中找到对应的组件,默认一定要将属性赋值好,没有找到就会报错,可以使用@Autowired (required=false),非必须的 ;

@Qualifier ,指定需要装配的组件id ;

@Primary 让Spring 进行自动装配的时候, 首选装配的组件(配置类中),也可以使用@Qualifier指定需要装配的bean的名字.

@Resource &@Inject

Spring还支持@Resource(JSR250)和@Inject (JSR330){java规范的注解}

@Resource 和@Autowired一样实现自动装配的功能,默认按照组件的名称进行装配。

@Inject需要导入javax.inject的包,和@Autowired功能一样,没有required=false的功能。

@Profile 环境搭建

Spring 为我们提供可以根据当前环境,动态的激活和切换一系列组件的功能

下面我们来使用数据源说明这个问题:

@Profile指定哪个环境下才能被注册到容器当中,加了@Profile,只有这个环境激活的时候才能注册到spring容器当中,默认是defalut

代码实例:

@PropertySource("classpath:db.properties")
@Configuration
public class MyConfigProfile  implements EmbeddedValueResolverAware {
    @Value("${db.username}")
    private String username;
    @Value("${db.password=}")
    private String password;

    private StringValueResolver stringValueResolver;

    private String driveClass;

    @Profile("default")
    @Bean("testDataSource")
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource=new ComboPooledDataSource();
        dataSource.setUser(username);
        dataSource.setPassword(password);
        dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/springboot?serverTimezone=UTC&useSSL=false&autoReconnect=true&tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8");
        dataSource.setDriverClass(driveClass);
        return dataSource;
    }
    @Profile("dev")
    @Bean("devDataSource")
    public DataSource devDataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource=new ComboPooledDataSource();
        dataSource.setUser(username);
        dataSource.setPassword(password);
        dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/springboot?serverTimezone=UTC&useSSL=false&autoReconnect=true&tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8");
        dataSource.setDriverClass(driveClass);
        return dataSource;
    }

    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.stringValueResolver=resolver;
        driveClass = stringValueResolver.resolveStringValue("db.driverClass");
    }
}

db.properties

db.username=root
db.password=root
db.driverClass=com.mysql.jdbc.Driver

测试代码:

public class DataSourceProfileTest {
    @Test
    public void test() {
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MyConfigProfile.class);
        String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class);
        for (String s:beanNamesForType){
            System.out.println(s);
        }
    }
    
    /* 使用无参构造器
     * 设置激活的环境
     * 注册主配置类
     * 启动刷新容器
     */
    @Test
    public void test1(){
        //使用无参构造器 
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext();
        //设置激活的环境
        applicationContext.getEnvironment().setActiveProfiles("dev");
        //注册主配置类
        applicationContext.register(MyConfigProfile.class);
        //启动刷新容器
        applicationContext.refresh();
        String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class);
        for (String s:beanNamesForType){
            System.out.println(s);
        }
    }
}

测试案例运行结果

testDataSource

devDataSource

小结:

@Profile还可以标记在类上,表示只有指定环境的时候,整个配置文件里面的配置才能开始生效。没有标注环境表示的bean,在任何环境下都是加载的(前提是配置类是生效的),还可以通过命令行参数:-Dspring.profiles.active=test激活对应的环境。

我是阿福,公众号「阿福聊编程」作者,对后端技术保持学习爱好者,我会经常更新JAVA技术文章,在进阶的路上,共勉!

欢迎大家关注我的公众号,后台回复666,领取福利,你懂的。

阅读 820
57 声望
40 粉丝
0 条评论
57 声望
40 粉丝
文章目录
宣传栏