avatar

Spring注解驱动开发-IOC

Spring IOC容器

我们创建一个maven工程,并且将spring核心组件依赖到pom文件中,这里使用4.3.12版本,需要其他版本请到maven库中找

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>

IOC组件注册

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

传统方式
  • 编写一个javabean
    public class Person {
    private String name;
    private Integer age;

    public person(String name, Integer age) {
    this.name = name;
    this.age = age;
    }

    @Override
    public String toString() {
    return "person{" +
    "name='" + name + '\'' +
    ", age=" + age +
    '}';
    }

    public String getName() {
    return name;
    }

    public person() {
    }

    public void setName(String name) {
    this.name = name;
    }

    public Integer getAge() {
    return age;
    }

    public void setAge(Integer age) {
    this.age = age;
    }
    }
  • 创建一个beans.xml
    <bean id="person" class="beans.Person">
    <property name="name" value="zhangsan"></property>
    <property name="age" value="18"></property>
    </bean>
  • 引入beans.xml,创建容器
    public class MainClass {
    public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    Person person = (Person) applicationContext.getBean("person");
    System.out.println(person);
    }
    }
  • 从容器中拿到bean
    /**
    * 得到输出结果
    * person{name='zhangsan', age=18}
    /
    注解方式
  • 我们创建一个配置文件类,并标记注解@Configuration
  • 我们创建一个方法,方法返回值为一个bean,方法上配置@Bean
    //配置类等同于配置文件
    @Configuration//告诉Spring这是一个配置类
    public class MainConfig {

    @Bean//给容器中注册一个bean
    public Person GetPerson()
    {
    return new Person("lisi",20);
    }
    }
    Bean的类型为返回值的类型,id默认为方法名作为id。
  • 使用注解后的容器
    public class MainClass {
    public static void main(String[] args) {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
    Person person = (Person) applicationContext.getBean(Person.class);
    System.out.println(person);
    String[] names = applicationContext.getBeanNamesForType(Person.class);
    for (String name: names) {
    System.out.println(name); //输出的是方法名
    }
    }
    }
    /**
    * 输出
    * Person{name='lisi', age=20}
    * GetPerson
    * /
    输出是方法名,如果不想用方法名的话,可以使用 @Bean(value = "person")指定注入容器中的Bean的名字。

@ComponentScan-自动扫描组件&指定扫描规则

xml方式
<!--包扫描,只要标注了@Controller、@Service、@Repository、@Component,都会被扫描到容器里面-->
<context:component-scan base-package="com.zenshin"></context:component-scan>
注解方式

在原有的配置类上加@ComponentScan注解

//配置类等同于配置文件
@Configuration//告诉Spring这是一个配置类
@ComponentScan(value = "com.zenshin")//配置扫描制定包
public class MainConfig {

@Bean(value = "person")//给容器中注册一个bean
public Person GetPerson()
{
return new Person("lisi",20);
}
}

我们可以编写一个dao,一个service,一个controller测试一下。

@Test
public void test01()
{
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] names = applicationContext.getBeanDefinitionNames();//获取所有的Bean
for (String name:names) {
System.out.println(name);
}
}
/**
* 输出如下
* ...
* mainConfig
* bookController
* bookDao
* bookService
* person
* /

自己注册的类都扫描到了容器中

  • @ComponentScan利用excludeFilters可以指定排除规则,能够根据filter排除掉一些组件不加入容器

    @ComponentScan(value = "com.zenshin",excludeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
    })

    这样就排除掉了Controller和Service两个注解标注的类,被这俩注解标注的bean不会加入到容器中。

  • @ComponentScan利用includeFilters可以指定哪些bean加入到容器中

    @ComponentScan(value = "com.zenshin",includeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
    },useDefaultFilters = false)

    想要使用includeFilters就必须把useDefaultFilters置为false,这样才会生效。

  • @ComponentScans可以指定多个ComponentScan

    @ComponentScans(value = {
    @ComponentScan(value = "com.zenshin",includeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}),
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {BookService.class}),

    },useDefaultFilters = false)
    })
  • FilterType类型

    • FilterType.ANNOTATION : 按照注解方式@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
    • FilterType.ASSIGNABLE_TYPE : 按照给定的类型@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {BookService.class})
    • FilterType.ASPECTJ : 使用ASPECTJ表达式,基本上用不到
    • FilterType.REGEX : 使用正则表达式
    • FilterType.CUSTOM : 使用自定义拦截器
  • 使用自定义拦截器步骤

    • 需要实现TypeFilter接口
      public class MyTypeFiler implements TypeFilter {
      /**
      * metadataReader:当前正在扫描的类的信息
      * metadataReaderFactory:可以获取其他任何类的信息
      */
      @Override
      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
      //获取当前类注解的信息
      AnnotatedTypeMetadata annotatedTypeMetadata = metadataReader.getAnnotationMetadata();
      //获取当前正在扫描类的类信息
      ClassMetadata classMetadata = metadataReader.getClassMetadata();
      //获取当前类的资源信息(类路径等等)
      Resource resource = metadataReader.getResource();

      String className = classMetadata.getClassName();
      System.out.println("--->"+className);
      return false;
      }
      }
    • 使用只需要在扫描类的时候指定一下拦截规则即可
      @ComponentScan(value = "com.zenshin",includeFilters = {
      @ComponentScan.Filter(type = FilterType.CUSTOM,classes = MyTypeFiler.class)
      },useDefaultFilters = false)

@Scope设置组件的作用域

xml方式
<!--scope="prototype" bean创建的作用域-->
<bean id="person" class="com.zenshin.beans.Person" scope="prototype">
<property name="name" value="zhangsan"></property>
<property name="age" value="18"></property>
</bean>
注解方式
/**
* * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE prototype 原型模式
* * @see ConfigurableBeanFactory#SCOPE_SINGLETON singleton 单例的(默认值)
* * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request
* * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION session
* prototype:多实例的
* singleton:单实例的
* request:每一次请求创建一个实例
* session:同一个session创建一个实例
*
*/
@Scope(value = "prototype")
@Bean("person")//默认都是单实例的
public Person person()
{
return new Person("zhulonghao",24);
}

Scope有四种作用域:

  • prototype:基于原型模式,每次从容器拿的时候都会创建一个实例
  • singleton: 单例的,全局只会有一个实例,默认值
  • request: 在web中每一次请求创建一个实例
  • session: 在web中同一个session有一个实例

在单实例的情况下,IOC容器启动的会调用方法创建对象放到IOC容器中,以后每次获取就是直接从容器中拿。
在多实例情况下,IOC容器启动的时候并不会调用方法创建对象,放到ioc容器中,以后每次获取的时候才会调用方法创建对象。

@Lazy-bean 懒加载

单实例bean,默认在容器启动的时候创建对象,我们可以对其进行懒加载,容器启动的时候不会创建对象,第一次获取(使用)Bean的时候,才会创建对象并初始化

@Bean("person")//默认都是单实例的
@Lazy //懒加载bean,只用于单实例情况下
public Person person()
{
System.out.println("person创建");
return new Person("zhulonghao",24);
}

@Conditional-按照条件注册bean

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

  • 首先我们创建两个bean来注入到容器中
     /**
    * @Conditional(): 按照一定的条件进行判断,满足条件给容器中注册bean
    */
    @Bean("bill")
    public Person person01()
    {
    return new Person("Bill Gates",60);
    }
    @Bean("linus")
    public Person person02()
    {
    return new Person("linus",48);
    }
    如果我们想根据系统类型来注册容器
    1、如果是windows,就注册bill
    2、如果是linux就注册linus
    @Conditional注解里面传的是一个Condition数组,Condition是一个接口,我们需要自己去实现
    //创建一个windowsCondition
    /**
    * 判断是不是windos系统
    */
    public class WindowsCondition implements Condition {
    /**
    *
    * @param context 判断条件能使用到的上下文(环境)
    * @param metadata 注释信息
    * @return
    */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    //1、能获取到ioc使用的bean工厂
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    //2、获取类加载器
    ClassLoader classLoader = context.getClassLoader();
    //3、获取当前环境的信息
    Environment environment = context.getEnvironment();
    //4、获取bean定义的注册类
    BeanDefinitionRegistry registry = context.getRegistry();

    //我们需要environment来判断当前操作系统、
    String environmentProperty = environment.getProperty("os.name");
    if(environmentProperty.contains("Windows"))
    {
    return true;
    }
    return false;
    }
    }
    //创建一个linuxCondition
    public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment environment = context.getEnvironment();
    String property = environment.getProperty("os.name");
    if(property.contains("Linux"))
    {
    return true;
    }
    return false;
    }
    }
    然后在注册bean的地方加上注解即可
     /**
    * @Conditional(): 按照一定的条件进行判断,满足条件给容器中注册bean
    */
    @Conditional({WindowsCondition.class})
    @Bean("bill")
    public Person person01()
    {
    return new Person("Bill Gates",60);
    }
    @Conditional({LinuxCondition.class})
    @Bean("linus")
    public Person person02()
    {
    return new Person("linus",48);
    }
    这样在windows下就会注册billbean,在linux下就会注册linusbean。
    Condition中可以做很多的判断,包括通过BeanDefinitionRegistry判断是否注册了什么Bean等功能。
    @Condition可以放在方法上,也可以放在类上,放在类上,只有条件为true的时候整个类才会生效,包括类里面注册的bean。

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

首先我们创建一个类,随便一个类不加任何注解

public class Color {
}

我们需要在配置类头加一个Import注解,就可以将这个类导入到容器中

@Configuration
@Import(Color.class)//快速导入组件,id默认组件全类名
public class MainConfig2 {

Import支持数据,可以导入多个组件,id默认组件全类名

@Import中使用importSelector

importSelector是一个接口,接口中有方法,用于返回需要注册的类,是一个字符串数组,也就是说可以注册很多的类IOC容器中。

  • 首先我们需要编写一个类实现importSelector接口
    //自定义逻辑返回需要导入的组件
    public class MyImportSelector implements ImportSelector {
    //返回值就是导入到容器中的组件全类名
    //AnnotationMetadata:当前标注@Import注解类的所有注解信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

    //方法不要返回null
    return new String[]{"com.zenshin.beans.Color","com.zenshin.beans.Person"};
    }
    }
    然后用Import注解用一下这个类即可
    @Import(MyImportSelector.class)
    @Import-使用ImportBeanDefinitionRegistrar
    这个也是一个接口,使用方法与importSelector相同
  • 首先我们创建一个类实现ImportBeanDefinitionRegistrar
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
    *
    * @param importingClassMetadata 当前类的信息
    * @param registry BeanDefinition注册类
    * 把所有需要添加到容器的bean,调用BeanDefinitionRegistry.registerBeanDefinition手动注册进来
    */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    boolean color = registry.containsBeanDefinition("Color");
    if(!color)
    {
    //指定bean名,实现BeanDefinition接口的类
    RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Color.class);
    registry.registerBeanDefinition("Color", rootBeanDefinition);
    }
    }
    }
    依然是利用Import实现
    @Import(MyImportBeanDefinitionRegistrar.class)

使用FactoryBean注册组件

FactoryBean是一个接口,实现这个接口后,有三个方法,getObject()返回的对象会直接添加到容器中,一般用于创建比较复杂的Bean

public class ColorFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
//是否是单例
@Override
public boolean isSingleton() {
return true;
}
}

还需要将ColorFactoryBean放到容器中,

@Bean
public ColorFactoryBean colorFactoryBean()
{
return new ColorFactoryBean(); //实际上添加到容器中的是Color
}

这样就把Color对象放到了容器中,但是容器中的id是colorFactoryBean
我们来获取一下试试

@Test
public void testFactoryBean()
{
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
Object color = applicationContext.getBean("colorFactoryBean") ;
System.out.println(color.getClass());//class com.zenshin.beans.Color
Object object = applicationContext.getBean("&colorFactoryBean");
System.out.println(object.getClass());//class com.zenshin.beans.ColorFactoryBean
}
  • applicationContext.getBean(“colorFactoryBean”):获取的是Color对象
  • applicationContext.getBean(“&colorFactoryBean”):获取的是ColorFactoryBean对象
    默认创建的是工厂bean调用getObject创建的对象,需要获取工厂bean本身,我们需要在id前面加一个&。

注册方法总结

给容器中注册组件:
1、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[我们自己写的类]
2、@Bean 导入第三方包里面的注解
3、@Import 快速给容器中导入一个组件
4、使用Spring提供的FeactoryBean(工厂Bean)

Bean的生命周期

Bean的生命周期指的是:bean创建—初始化—销毁的过程。
初始化: 对象创建完成,并赋值好,调用初始化方法(多实例是获取的时候,单实例是容器创建的时候)
销毁:容器关闭的时候执行(多实例容器不会管理这个bean。容器不会调用销毁方法,只能我们手动去调用)
现在容器管理bean的生命周期,我们可以自定义初始化和销毁方法,容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法

  • 指定初始化和销毁方法自定义
    • 使用@Bean指定initMethoddestroyMethod方法
  • 通过让Bean实现接口InitializingBeanDisposableBean
  • 使用jsr250的两个注解@PostConstruct和@PreDestroy
  • 实现BeanPostProcessor接口,作为Bean的后置处理器

    @Bean-指定初始化和销毁方法

    在xml中我们可以用init-methoddestroy-method指定初始化和销毁方法。
    <bean id="person" class="com.zenshin.beans.Person" scope="prototype" init-method="指定方法" destroy-method="指定方法">
    <property name="name" value="zhangsan"></property>
    <property name="age" value="18"></property>
    </bean>
    用注解的方法
  • 如果我们创建一个Car类,编写无参构造函数,以及init(),destory()方法
    public class Car {

    public Car()
    {
    System.out.println("Car constructor...");
    }
    public void init()
    {
    System.out.println("car....init....");
    }
    public void destroy()
    {
    System.out.println("car.....destory....");
    }
    }
  • 然后加入到容器中去
    @Configuration
    public class MainConfigOfLifeCycle {

    @Bean(initMethod = "init",destroyMethod = "destroy") //加入容器中的时候指定初始化和销毁方法
    public Car car()
    {
    return new Car();
    }
    }
  • 初始化容器,然后打印输出信息
    @Test
    public void test01()
    {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
    System.out.println("容器创建完成");
    applicationContext.close();//容器关闭
    }
    //输出信息为
    //Car constructor...
    //car....init....
    //容器创建完成
    //car.....destory....
  • 这个时候就会调用Car类的初始化和销毁方法。

InitializingBean和DisposableBean

通过继承InitializingBeanDisposableBean,实现afterPropertiesSetdestroy方法

public class Cat implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("car...init...");
}

@Override
public void destroy() throws Exception {
System.out.println("car...destroy...");
}

public Cat() {
System.out.println("cat...const");
}
}

然后加入到容器中,加入到容器中以后,我们初始化容器,就能看到打印的消息了。

@PostConstruct和@PreDestroy

这两个注解是JSR250规范中的注解,

  • @PostConstruct:在bean创建完成并且属性赋值完成以后,来执行初始化方法
  • @PreDestroy:在bean将要被移除之前通知@PreDestroy,进行调用。
    public class Dog {
    public Dog() {
    System.out.println("Dog constructor...");
    }
    //对象创建并赋值之后调用
    @PostConstruct
    public void init()
    {
    System.out.println("Dog ...PostConstruct");
    }

    //容器移除对象之前
    @PreDestroy
    public void destroy()
    {
    System.out.println("Dog...destroy");
    }
    }
    我们将Dog加入到容器中测试一下
    @Test
    public void test02()
    {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
    Dog dog = applicationContext.getBean(Dog.class);//Dog constructor... Dog ...PostConstruct
    applicationContext.removeBeanDefinition("dog");//Dog...destroy
    }

BeanPostProcessor-后置处理器

在bean初始化前后,进行一些处理工作

  • postProcessBeforeInitialization在初始化之前进行工作。
  • postProcessAfterInitialization在初始化之后进行工作。
    /**
    * 后置处理器,初始化前后进行处理工作
    * 将后置处理器加入到容器中
    */
    @Component
    public class MyBeanPostProessor implements BeanPostProcessor {
    /**
    * @param bean 创建的bean实例
    * @param beanName bean的名字
    * @return 返回值就是bean实例
    * @throws BeansException
    */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("postProcessBeforeInitialization...."+beanName);
    return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("postProcessAfterInitialization,...."+beanName);
    return bean;
    }
    }
    在初始化之前和之后就是调用这俩个方法。
    这个的实现类似于一个切面,就是在调用初始化方法的前后调用这两个方法
    protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
    if (System.getSecurityManager() != null) {
    AccessController.doPrivileged(new PrivilegedAction<Object>() {
    @Override
    public Object run() {
    invokeAwareMethods(beanName, bean);
    return null;
    }
    }, getAccessControlContext());
    }
    else {
    invokeAwareMethods(beanName, bean);
    }
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); //前
    }
    try {
    invokeInitMethods(beanName, wrappedBean, mbd); //初始化方法
    }
    catch (Throwable ex) {
    throw new BeanCreationException(
    (mbd != null ? mbd.getResourceDescription() : null),
    beanName, "Invocation of init method failed", ex);
    }
    if (mbd == null || !mbd.isSynthetic()) {
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); //后
    }
    return wrappedBean;
    }
    applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);指定初始化方法之前
    获取所有的后置处理器,依次执行,一旦有返回null的处理器就不会执行后面的处理器了。
    invokeInitMethods(beanName, wrappedBean, mbd)执行初始化方法,
    applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); 初始化方法之后

初始化方法之前,调用populateBean(beanName, mbd, instanceWrapper);给bean属性进行赋值。赋值完以后在执行initializeBean(beanName, exposedObject, mbd);

Spring底层对BeanPostProcessor的应用

我们可以找到BeanPostProcessor接口,然后找到他的实现类,这里举例ApplicationContextAwareProcessor

//这是他的postProcessBeforeInitialization方法
@Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
if (System.getSecurityManager() != null &&
(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}
if (acc != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareInterfaces(bean);
return null;
}
}, acc);
}
else {
invokeAwareInterfaces(bean);
}
return bean;
}
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
}

这里可以看到,postProcessBeforeInitialization调用了invokeAwareInterfaces方法,这里面有一个ApplicationContextAware接口,这个接口有一个setApplicationContext方法,这个方法把IOC容器注入到了bean中,所以我们可以得出,如果我们想要在bean中加入ioc容器,我们可以实现ApplicationContextAware接口然后可以实现setApplicationContext方法得到IOC容器。

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

这种装配只能是在初始化之前无感的注入,不然初始化完毕自己的bean再注入那就不叫初始化了,哈哈哈。
Spring底层还有很多地方用到了这个方式,包括自动注入也是利用这种方式去注入的。

属性赋值

我们定义的bean中有属性,属性如何获取呢我们在xml中可以使用value去赋值,那么同样注解版也是有@Value可以对属性进行赋值的。

@Value赋值

1、基本数值
2、可以写SpEL,#{}
3、可以${},取出配置文件中的值(在运行环境变量里面的值)

public class Person {
@Value("#{systemEnvironment}")
private String name;
@Value("20")
private Integer age;
}

@PropertySource加载外部配置文件

  • 使用xml的方式
    <!--使用context来将配置文件放到beans.xml中,然后${}调用-->
    <context:property-placeholder location="person.properties"/>
    <bean id="person" class="com.zenshin.beans.Person" scope="prototype">
    <property name="name" value="${person.name}"></property>
    <property name="age" value="18"></property>
    </bean>
  • 使用@PropertySource注解
    • 在配置类中加入如下注解
      //使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中
      @PropertySource(value = {"classpath:/person.properties"})//配置文件的路径
      @Configuration
      public class MainConfigOfPropertyValues {
      @Bean
      public Person person()
      {
      return new Person();
      }
      }
    • 在需要的地方使用${}取出配置
      @Value("${person.Nickname}")
      private String NickName;
    • 配置文件中的值都加载到了环境变量中,我们可以从环境变量中取出来
      ConfigurableEnvironment environment = applicationContext.getEnvironment();
      System.out.println(environment.getProperty("person.Nickname")); //从环境变量中获取值

这样就可以获取到配置文件中的信息了。

自动装配

Spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值;
1、使用@Autowired注解实现注入
2、使用XXXAware来获取Spring底层的注入类

@Autowired & @Qualifier & @Primary

@Service
public class BookService {
@Qualifier("bookDao")
@Autowired(required = false)//没有从容器中找到就会是null
BookDao bookDao;//这样就会将容器中的BookDao这个类自动注入
public void test()
{
bookDao.test();
}
}
  • 默认优先按照类型到容器中找对应的组件,如果找到就进行赋值
  • 如果该类型的组件在容器中有多个,再将属性的名称作为组件的id去容器中查找
  • 我们可以使用@Qualifier("bookDao")明确指定注入的是哪个对象,根据id获取,而不是使用属性名
  • Autowired自动装配默认一定要将属性赋值好,如果容器中没有这个组件,那么注入的时候就会报错,@Autowired有一个属性required是否必须,默认是true,改为false可以不报错也获取不到注入的对象。
  • @Primary:让Spring进行自动装配的时候默认使用首选的bean。这个注解是放在bean上的,如果有bean加上了这个注解,那么在自动装配的时候就会首选这个bean

@Resource & @Inject

@Resource是JSR250规范中的注解,@Inject是JSR330规范的注解,这两个都是java规范的注解

  • @Resource注解使用

    @Service
    public class BookService {
    @Resource(name="bookDao")
    BookDao bookDao;
    public void test()
    {
    bookDao.test();
    }
    }

    Resource可以和Autowired一样能实现自动装配功能,默认是组件名称进行装配的,也可以使用name属性指定注入的对象,不支持@Primary功能也不支持@Autowired(required = false)功能

  • @Inject注解使用
    这个注解的使用需要导入一个依赖

    <!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
    <dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
    </dependency>

    使用方法与Autowried注解使用方式一样,但是没有required属性

  • @Resource与@inject都是java规范,脱离了spring依旧可以使用其他框架支持,但是@Autowired是Spring的框架中的注解,是脱离Spring就不支持了

自动装配功能的实现是通过AutowiredAnnotationBeanPostProcessor后置处理器去解析注解然后自动装配的。

方法、构造器位置的自动装配

@Autowired注解不只是能标注在属性上,还能标注在构造器,参数,方法上

  • 标注在set方法上
    @Service
    public class BookService {
    public BookDao getBookDao() {
    return bookDao;
    }
    @Autowired //标注在方法上面,Spring容器创建当前对象,就会调用方法去赋值
    //方法使用的参数,自定义类型的值从ioc容器中进行获取。
    public void setBookDao(BookDao bookDao) {
    this.bookDao = bookDao;
    }

    private BookDao bookDao;
    public void test()
    {
    bookDao.test();
    }
    }
  • 标注在构造器上实现构造器注入
    在IOC容器中,我们默认加到IOC容器中欸得组件,容器启动的时候会调用无参构造器创建对象,再进行初始化赋值等操作。
    @Autowired标注以后,IOC容器就会在实例化得时候将构造器中用到得类注入进去。如果组件有且只有一个有参构造器,这个有参构造器的@Autowired可以省略。
    @Service
    public class BookService {
    @Autowired
    public BookService(BookDao bookDao) {
    this.bookDao = bookDao;
    }
    private BookDao bookDao;
    public void test()
    {
    bookDao.test();
    }
    }
  • @Autowired也可以放在参数位置,也是从容器中获取
    @Service
    public class BookService {
    public void test(@Autowired BookDao bookDao)
    {
    bookDao.test();
    }
    }
  • @Bean标注的方法在创建对象的时候,方法参数的值也是从容器中取得。

自定义组件使用Spring底层组件

如:ApplicationContext,BeanFactory
自定义组件使用这些得时候,只需要实现 XXXAware接口实现方法即可,在创建对象的时候,会调用接口规定的方法注入相关组件;
使用方式在BeanPostProcessor Spring底层应用讲到过。

@Service
public class BookService implements EmbeddedValueResolverAware {
public void test(@Autowired BookDao bookDao)
{
bookDao.test();
}

@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String s = resolver.resolveStringValue("你好${os.name} 我是#{20+40}");
System.out.println(s);
}
}

EmbeddedValueResolverAware能够处理字符串里面的数值,解析成正确的字符串,这样输出为:你好Windows 10 我是60

XXXAware是通过XXXProcessor来处理的,一一对应。

@Profile环境搭配

@Profile:Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能。
因为我们在开发过程中往往会有开发环境,测试环境和生产环境,需要在这三个里面进行切换,所以需要@Progile注解来实现。

@Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定在任何环境下都能注册这个组件

  • 加了环境表示的bean,只有这个环境被激活的时候才会注册到容器中
  • 注解也可以加到整个类上面,效果是一样的,也是当环境一致的时候我们的类才能生效。如果加到整个配置类上,只有指定环境的时候,整个配置类里面的配置才能生效。
  • 没有标注环境表示的bean在任何环境下都是加载的。
  • @Profile("default")是默认环境。
  • @Profile直接标记在需要添加的Bean上面即可
    @Configuration
    public class MainConfigOfProfile {
    @Profile("test")
    @Bean("testDataSource")
    public DataSource dataSourceTest() throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser("root");
    dataSource.setPassword("123456");
    dataSource.setJdbcUrl("jdbc:mysql://192.163.1.8:3306/test");
    dataSource.setDriverClass("com.mysql.jdbc.Driver");
    return dataSource;
    }
    @Profile("dev")
    @Bean("devDataSource")
    public DataSource dataSourceDev() throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser("root");
    dataSource.setPassword("123456");
    dataSource.setJdbcUrl("jdbc:mysql://192.163.1.8:3306/dev");
    dataSource.setDriverClass("com.mysql.jdbc.Driver");
    return dataSource;
    }
    @Profile("pro")
    @Bean("proDataSource")
    public DataSource dataSourcePro() throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser("root");
    dataSource.setPassword("123456");
    dataSource.setJdbcUrl("jdbc:mysql://192.163.1.8:3306/pro");
    dataSource.setDriverClass("com.mysql.jdbc.Driver");
    return dataSource;
    }
    }
    //定义了三个数据源,分别对应三个环境,@Profile()就标注在bean上面
    我们现在运行容器会发现什么都没有加进入。
  • 如何定义环境,使得 @Profile注解能够生效
    • 使用命令行动态参数:在vm的参数里面添加-Dspring.profiles.active=test指定运行环境
    • 使用代码的方式,其实就是将applicationContext的创建过程增加了一个设置环境的步骤。
       @Test
      public void test01()
      {
      //1、创建一个applicationContext,使用无参构造器,有参构造器来不及设置就直接创建容器了
      AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
      //2、设置需要激活的环境
      applicationContext.getEnvironment().addActiveProfile("test");
      //3、注册主配置类
      applicationContext.register(MainConfigOfProfile.class);
      //4、启动刷新容器
      applicationContext.refresh();
      String[] names = applicationContext.getBeanDefinitionNames();
      for (String name:names) {
      System.out.println(name);
      }
      }

IOC小结

Spring最主要的就是容器,在IOC容器这一章主要讲的是组件的添加、组件的赋值、组件的注入
我们来看一下图,看一下这里面有哪些主要内容

这就是本篇文章的全部内容,IOC容器中还有AOP和声明式事务,这个在后面进行讲解。

这里仅仅算一个概览,更多的参照一下Spring的官方文档

文章作者: zenshin
文章链接: https://zlh.giserhub.com/2020/05/02/spring/ioc/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zenshin's blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论