Spring注解驱动开发(一)IOC和DI

1、背景介绍

       随着现在SpringBoot的越来越流行,Spring的注解开发就应该得到重视。因为SpringBoot中很多的注解,对于之前进行xml配置进行开发的人来说,会显得比较陌生。而SpringBoot其实就是更好的封装了Spring,是基于Spring的。所以了解Spring的注解开发,对于学习SpringBoot有很好的帮助,并且Spring的注解开发可以提高开发效率,可以免除繁杂的xml配置的烦恼。

2、注解开发实现IOC和DI

2.1 创建Maven工程

1、创建maven工程

File——》Project——》选择Maven:

下一步如下:

点击finish即可。

2、添加对应的依赖

在pom.xml文件中,添加Spring对应的坐标,以及junit坐标:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.spring.annotation</groupId>
    <artifactId>spring-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

2.2 IOC

       以前我们的使用xml配置文件applicatonContext.xml来管理Spring的Bean,现在我们不再需要xml的配置文件,通过Spring的相关注解就能实现。

2.2.1 IOC的入门案例

1、创建pojo

创建一个Bean,该Bean需要交给Spring进行管理。

public class User {
    private String name;
    private int age;
    //get/set方法,有参无参构造,以及toString方法
}

2、创建配置类

       创建一个Spring的配置类,这个配置类使用注解:@Configuration  修饰,表明该类是Spring的配置类,相当于是一个applicationContext.xml配置文件,里面可以定义Spring管理的Bean,定义Bean的时候要使用注解:@Bean

package com.spring.annotation.config;

import com.spring.annotation.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Configuration:表示这个类是配置类,相当于配置文件开发的时候的applicationContext.xml文件
 */
@Configuration
public class SpringConfig {

    /**
     * @Bean:给容器中注册一个Bean;类型为返回值的类型,id默认是用:方法名作为id
     *         也可以使用value属性来为它指定一个 id
     */
    @Bean(value = "user")
    public User user(){
        return new User("张三",23);
    }

}

3、测试类,从Spring的配置类中获取实例对象

package com.spring.annotation.test;

import com.spring.annotation.bean.User;
import com.spring.annotation.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringConfigTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = applicationContext.getBean(User.class);
        System.out.println(user);
        String[] names = applicationContext.getBeanNamesForType(User.class);
        for(String name : names){
            System.out.println(name);
        }
    }
}

      注意:我们使用的是 AnnotationConfigApplicationContext 对象来获取 ApplicationContext (IOC容器对象),xml配置开发的时候,使用的是:ClassPathXmlApplicationContext。

打印结果:

User{name='张三', age=23}
user

       以上通过简单的几步,就实现了把Bean交给Spring管理,IOC的功能就已经得到了实现,只是使用了几个简单的注解,根本就不需要复杂的xml配置,这样很明显能提高我们的开发效率。

2.2.2 IOC相关的其他注解

1、@ComponentScans和@ComponentScan

        @ComponentScans 注解相当于xml配置文件中的标签:<context:component-scan base-package="com.springmvc,com.test" />,在标签中可以配置扫描多个包,包名之间用逗号分隔,同理,在@ComponentScans中可以配置多个@ComponentScan(在JDK8以后,可以在配置类上配置同级的多个@ComponentScan 也是同样的效果)。

useDefaultFilters=false:不使用默认的扫描规则(默认是扫描所有@Component和@Controller @Service、@Repository注解)
includeFilters:配置只扫描哪些
excludeFilters:配置不扫描哪些

a、在SpringConfig类上添加@ComponentScans和@ComponentScan注解:

package com.spring.annotation.config;

import com.spring.annotation.bean.User;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

/**
 * @Configuration:表示这个类是配置类,相当于配置文件开发的时候的applicationContext.xml文件
 */
@Configuration
/**
 *  @ComponentScans 相当于标签:<context:component-scan base-package="com.springmvc,com.test" />
 *                  useDefaultFilters=false:不使用默认的扫描规则(默认是扫描所有@Component和@Controller
 *                                             @Service、@Repository注解)
 *                  includeFilters:配置只扫描哪些
 *                  excludeFilters:配置不扫描哪些
 */
@ComponentScans(value = {
        @ComponentScan(value = "com.spring.annotation",
                       includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})},
                       useDefaultFilters = false),
        @ComponentScan(value = "com.spring.test",
                       excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})}
                       )
                       }
               )
public class SpringConfig {

    /**
     * @Bean:给容器中注册一个Bean;类型为返回值的类型,id默认是用:方法名作为id
     *         也可以使用value属性来为它指定一个 id
     */
    @Bean(value = "user")
    public User user(){
        return new User("张三",23);
    }

}

b、创建UserController,类上添加@Controller注解,创建UserService,类上添加@Service注解,创建UserDao,类上添加@Repository注解。

c、在src/test下面创建测试类,测试Spring扫描了哪些类:

package com.spring.annotation.test;

import com.spring.annotation.config.SpringConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class IOCTest {

    @Test
    public void test(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for(String beanName : beanDefinitionNames){
            System.out.println(beanName);
        }
    }
}
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
springConfig
userController
userService
user

并没有扫描道UserDao,因为我们指定了只扫描@Controller和@Service。

FilterType.ANNOTATION:按照注解

FilterType.ASSIGNABLE_TYPE:按照给定的类型;

@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = UserDao.class)

FilterType.ASPECTJ:使用ASPECTJ表达式

FilterType.REGEX:使用正则指定

FilterType.CUSTOM:使用自定义规则

2、@Scope注解

可以在创建Bean实例的方法上使用@Scope,指定作用域,默认是单实例的:singleton:

    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    @Bean(value = "user")
    public User user(){
        return new User("张三",23);
    }
可取值以及解释:
@see ConfigurableBeanFactory#COPE_PROTOTYPE
@see ConfigurableBeanFactory#SCOPE_SINGLETON
@see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST  request
@see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION    sesssio
prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象;
singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。以后每次获取就是直接从容器(map.get())中拿,
request:同一次请求创建一个实例
session:同一个session创建一个实例

3、@Lazy注解

       @Lazy注解可以实现懒加载;
       懒加载只针对单实例bean(Scope作用域是singleton的):默认在容器启动的时候创建对象;
       懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化;第二次获取的也是同一个对象,因为是单实例的。这其实就是单例模式的懒汉式和饿汉式

4、@Conditional注解

       @Conditional注解在SpringBoot中被大量的使用,条件化注册Bean实例。

       @Conditional({Condition}) : 该注解接受一个Condition(是一个接口)的数组,按照一定的条件进行判断,满足条件才会给容器中注册bean。如果不满足Condition条件,便不会创建@Conditional注解修饰的Bean。

       需求:当前环境是Linux系统,就创建Linux之父的User对象linus,如果是windows环境,就创建windows之父的User对象Bill Gates。

步骤:

第一步:创建两个Bean,分别返回linus的User对象和Bill Gates的User对象,但是创建Bean的方法上要加上@Conditional注解,注解里面分别添加自定义的的Condition接口的实现类:LinuxConditiion和WindowsCondition。

 @Conditional(LinuxCondition.class)
    @Bean
    public User linus(){
        return new User("linus",50);
    }

    @Conditional(WindowsCondition.class)
    @Bean
    public User bill(){
        return new User("bill gates",65);
    }

第二步:定义Condition接口的实现类:LinuxConditiion和WindowsCondition,通过Condition接口的方法来获取当前系统的环境名称,然后返回true;

package com.spring.annotation.condition;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

//判断是否linux系统
public class LinuxCondition implements Condition {

    /**
     * ConditionContext:判断条件能使用的上下文(环境)
     * AnnotatedTypeMetadata:注释信息
     */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //1、能获取到ioc使用的beanfactory,BeanFactory的有创建和注入Bean的信息
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //2、获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        //3、获取当前环境信息
        Environment environment = context.getEnvironment();
        //4、获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();
        //获取当前环境的操作系统的名称
        String osName = environment.getProperty("os.name");

        //可以判断容器中的bean注册情况,也可以给容器中注册bean
        boolean definition = registry.containsBeanDefinition("person");
        if(osName.contains("linux")){
            return true;
        }
        return false;
    }
}
package com.spring.annotation.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
//判断是否是windows操作系统
public class WindowsCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        if(property.contains("Windows")){
            return true;
        }
        return false;
    }
}

第三步:测试

 @Test
    public void test2(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
        //动态获取环境变量的值;Windows 10
        String property = environment.getProperty("os.name");
        System.out.println(property);

        String[] namesForType = applicationContext.getBeanNamesForType(User.class);
        for (String name : namesForType) {
            System.out.println(name);
        }

        Map<String, User> persons = applicationContext.getBeansOfType(User.class);
        System.out.println(persons);
    }

在windows环境下,执行测试方法,打印:

Windows 10
user
bill
{user=User{name='张三', age=23}, bill=User{name='bill gates', age=65}}

IDEA修改VM参数,模拟Linux环境:Edit Configuration,添加:-Dos.name=linux

再次执行,结果打印如下:

linux
user
linus
{user=User{name='张三', age=23}, linus=User{name='linus', age=50}}

注意:@Conditional({WindowsCondition.class}) 也可以放到类上面,类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效;

5、@Import注解

之前我们已经学过两种把Bean注册到IOC容器的方法:

方式一:包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类],但是这样只能注册自己写的类,如果是第三方jar包里面的类,我们就无法添加注解了,就需要使用方式二。

方式二:@Bean[导入的第三方包里面的组件],比如我们需要导入JdbcTemplate,就可以使用@Bean注解进行注册。

其实@Import也是向IOC容器注册组件的,它可以快速给容器中导入一个组件,它的使用方式有如下三种:

5.1 @Import(要导入到容器中的组件的Class对象)

在配置类(比如之前的SpringConfig)上面添加@Import注解,容器中就会自动注册这个组件,id默认是全类名。

定义个新的pojo:

public class Car {
}

在SpringConfig配置类上添加@Import注解:

@Import({Car.class})
public class SpringConfig {
    //。。。省略了其他注册Bean的方法
}

测试方法,打印IOC容器中所有的Bean:

@Test
    public void testImport(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        //获取IOC容器中所有注册好的Bean
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }

5.2 ImportSelector

       可以定义一个实现接口ImportSelector接口的实现类,自定义导入注解的规则,然后把该实现类的Class对象也放到@Import注解中。ImportSelector:返回需要导入的组件的全类名数组

第一步,创建两个类Audi和Jili(在包com.spring.annotation.bean下面创建)

public class Car {
}

public class Jili {
}

第二步,自定义MyImportSelector

package com.spring.annotation.condition;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
	//返回值,就是到导入到容器中的组件全类名
	//AnnotationMetadata:当前标注了@Import注解的类(比如SpringConfig)的所有注解信息
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		//importingClassMetadata
		//方法不要返回null值,返回null会直接报空指针异常
		return new String[]{"com.spring.annotation.bean.Audi","com.spring.annotation.bean.Jili"};
	}
}

第三步,把MyImportSelector的Class对象添加到SpringConfig配置类的@Import注解上:

@Import({Car.class, MyImportSelector.class})
public class SpringConfig {
}

第四步,执行上面的测试方法,查看结果:

ImportSelector 的这种方式,在SpringBoot中使用的比较多。

5.3 ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar可以手动注册bean到容器中:

第一步,创建类Dazhong

package com.spring.annotation.bean;

public class Dazhong {
}

第二步,自定义MyImportBeanDefinitionRegistrar,实现ImportBeanDefinitionRegistrar接口

package com.spring.annotation.condition;

import com.spring.annotation.bean.Dazhong;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * AnnotationMetadata:当前类的注解信息
     * BeanDefinitionRegistry: BeanDefinition注册类;
     * 		把所有需要添加到容器中的bean;调用
     * 		BeanDefinitionRegistry.registerBeanDefinition手工注册进来
     */
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata,
                                        BeanDefinitionRegistry beanDefinitionRegistry) {
        boolean definition = beanDefinitionRegistry.containsBeanDefinition("com.spring.annotation.bean.Audi");
        boolean definition2 = beanDefinitionRegistry.containsBeanDefinition("com.spring.annotation.bean.Jili");
        if(definition && definition2){
            //指定Bean定义信息;(Bean的类型,Bean。。。)
            RootBeanDefinition beanDefinition = new RootBeanDefinition(Dazhong.class);
            //注册一个Bean,指定bean名
            beanDefinitionRegistry.registerBeanDefinition("dazhong", beanDefinition);
        }
    }
}

第三步,在SpringConfig配置类的@Import注解上加上MyImportBeanDefinitionRegistrar.class

@Import({Car.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class SpringConfig {
}

第四步测试:

6、通过FactoryBean注册组件

       之前注册Bean实例到IOC容器的方式,注册的都是普通Bean,普通Bean得到的就是Bean本身,而FactoryBean注册的getObject方法返回值的对象。

6.1 自定义CarFactoryBean

package com.spring.annotation.bean;

import org.springframework.beans.factory.FactoryBean;

public class CarFactoryBean implements FactoryBean {
    //返回一个Car对象,这个对象会添加到IOC容器中
    public Object getObject() throws Exception {
        return new Car();
    }

    public Class<?> getObjectType() {
        return Car.class;
    }

    //true:这个bean是单实例,在容器中保存一份
    //false:多实例,每次获取都会创建一个新的bean;
    public boolean isSingleton() {
        return true;
    }
}

6.2 在SpringConfig配置类中注册CarFacotryBean

 @Bean
    public CarFactoryBean carFactoryBean(){
        return new CarFactoryBean();
    }

6.3 测试

/**
     * 4)、使用Spring提供的 FactoryBean(工厂Bean);
     * 		1)、默认获取到的是工厂bean调用getObject创建的对象
     * 		2)、要获取工厂Bean本身,我们需要给id前面加一个&&colorFactoryBean
     */
    @Test
    public void testFactoryBean(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        //工厂Bean获取的是调用getObject创建的对象
        Object bean2 = applicationContext.getBean("carFactoryBean");
        Object bean3 = applicationContext.getBean("carFactoryBean");
        System.out.println("bean的类型:"+bean2.getClass());
        System.out.println(bean2 == bean3);
        //如果要获取到真正的FactoryBean对象,要使用 &
        Object bean4 = applicationContext.getBean("&carFactoryBean");
        System.out.println(bean4.getClass());
    }

2.2.3 Spring中Bean的生命周期

       Spring管理的Bean的生命周期,就是Bean从创建,初始化再到销毁的过程。我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法。可以有如下3种方式来为我们的Bean指定初始化和销毁方法:

方式一:在@Bean注解上执行initMethod和destoryMethod

1、在Car类中定义初始化和销毁方法

package com.spring.annotation.bean;

public class Car {
    public Car() {
        System.out.println("Car对象创建了");
    }
    //初始化方法,该方法不能有参数
    public void init(){
        System.out.println("init method...");
    }
    //初始化方法,该方法不能有参数
    public void destroy(){
        System.out.println("destroy method...");
    }
}

2、创建新的配置类,在配置类中注册Car

package com.spring.annotation.config;

import com.spring.annotation.bean.Car;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringLifeConfig {

    @Bean(initMethod="init",destroyMethod="destroy")
    public Car car(){
        return new Car();
    }
}

3、测试

package com.spring.annotation.test;

import com.spring.annotation.config.SpringLifeConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringLifeTest {

    @Test
    public void testLife(){
        AnnotationConfigApplicationContext applicationContext =
                            new AnnotationConfigApplicationContext(SpringLifeConfig.class);
        System.out.println("IOC容器创建完成...");

        //关闭容器
        applicationContext.close();
    }
}

注意:

构造(对象创建)
        单实例:在容器启动的时候创建对象
        多实例:在每次获取的时候创建对象

初始化:
        对象创建完成,并赋值好,调用初始化方法。。。

销毁:
        单实例:容器关闭的时候
        多实例:容器不会管理这个bean;容器不会调用销毁方法;可以手动调用销毁方法

方式二:让自定义的Bean实现InitializingBean(定义初始化逻辑),DisposableBean(定义销毁逻辑)

package com.spring.annotation.bean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

@Component
public class Person implements InitializingBean, DisposableBean {
    public void destroy() throws Exception {
        System.out.println("DisposableBean --- 销毁方法");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean --- 初始化方法");
    }

    public Person() {
        System.out.println("Person构造方法执行,创建完成");
    }
}

       类上添加了注解:@Component,交给Spring管理。 

       在SpringLifeConfig配置类上加上:@ComponentScan("com.spring.annotation.bean")//配置包扫描,会自动扫描到下面的所有的类,并注册到IOC容器中,测试效果如下:

方式三:可以使用JSR250的两个注解

       @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法
       @PreDestroy:在容器销毁bean之前通知我们进行清理工作

package com.spring.annotation.bean;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class Dog {
    public Dog() {
        System.out.println("Dog构造完成");
    }
    @PostConstruct
    public void postCOnstruct(){
        System.out.println("在对象创建完成后会调用该方法。。。");
    }

    @PreDestroy
    public void preDestory(){
        System.out.println("Dog的销毁方法");
    }
}

2.2.4 BeanPostProcessor【interface】:bean的后置处理器

在bean初始化前后进行一些处理工作,针对所有的IOC容器中的Bean:
postProcessBeforeInitialization:在初始化之前工作
postProcessAfterInitialization:在初始化之后工作

1、BeanPostProcessor的使用

自定义后置处理器:添加@Component注解,交给Spring管理

package com.spring.annotation.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component//交给Spring管理
public class MyBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization..."+beanName+"=>"+bean);
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean);
        return bean;
    }
}

此时再执行上面的测试方法,查看控制台的打印:

2、BeanPostProcessor的原理

源码中:populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值

initializeBean()方法中:
{
applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

Spring会遍历得到IOC容器中所有的BeanPostProcessor;挨个执行beforeInitialization,一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization

Spring底层对 BeanPostProcessor 的使用;bean赋值,注入其他组件,@AutowiredAnnotationBeanPostProcessor,生命周期注解功能,@AsyncAnnotationBeanPostProcessor;

2.2.5 与属性相关注解

1、@Value

       @Value注解的作用就相当于xml配置文件中,配置<bean>标签里面的 value属性。@Value注解的value属性可以有以下三种方式赋值:

@Value(vaue="张三")   直接写字面(数字,字符串等)值

@Value(value="#{20+2}")   可以使用SpEL表达式

@Value(value="${key}")    可以从外部配置文件中获取值

a、创建Employee

package com.spring.annotation.bean;

import org.springframework.beans.factory.annotation.Value;

public class Employee {
    @Value("张三")
    private String name;
    @Value("#{6000+3000}")
    private int salary;
    @Value("${employee.addr}")
    private String addr;
    //get/set以及无参有参构造和toString方法
}

b、创建配置类

package com.spring.annotation.config;

import com.spring.annotation.bean.Employee;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
//使用@PropertySource 引入类路下面的 employee.properties 文件
@PropertySource(value = {"classpath:employee.properties"})
public class SpringPropertyConfig {

    @Bean
    public Employee employee(){
        return new Employee();
    }
}

使用@PropertySource 引入类路下面的 employee.properties 文件
@PropertySource(value = {"classpath:employee.properties"})

c、在类路径下创建employee.properties

employee.addr=湖南长沙

d、测试类

package com.spring.annotation.test;

import com.spring.annotation.bean.Employee;
import com.spring.annotation.config.SpringLifeConfig;
import com.spring.annotation.config.SpringPropertyConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class SpringPropertyTest {
    @Test
    public void testProperty(){
        AnnotationConfigApplicationContext applicationContext =
                            new AnnotationConfigApplicationContext(SpringPropertyConfig.class);
        Employee employee = (Employee) applicationContext.getBean("employee");
        System.out.println(employee);

        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        String property = environment.getProperty("employee.addr");
        System.out.println(property);
    }
}

通过这两句:

ConfigurableEnvironment environment = applicationContext.getEnvironment();
String property = environment.getProperty("employee.addr");

也可以获取配置文件中的值。

 

2.3 DI相关注解

Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值;完成自动装配。

2.3.1 @Autowired

       默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值;

       如果找到多个相同类型(要保证每个组件的名称不一样,否则就报错)的组件,再将属性的名称作为组件的id去容器中查找;

       可以配合注解@Qualifier("bookDao")一起使用,可以在注入组件的时候,指定需要装配的组件的id,而不是使用属性名。

       默认情况下,使用了@Autowired注解进行自动装配,默认一定要将组件注入进来,没有找到就会报错。可以使用@Autowired(required=false);  指定该组件不是必须要注入的,那就不会报错了;

       可以配合注解@Primary:让Spring进行自动装配的时候,默认使用首选的bean(没有明确指定使用哪个Bean的时候);同时,也可以继续使用@Qualifier指定需要装配的bean的名字,他的优先级高于@Primary;

       @Autowired:可以在 构造器,参数,方法,属性 上使用该注解;都是从容器中获取参数组件的值。

               [标注在方法位置]:@Bean+方法参数;参数从容器中获取,默认不写@Autowired效果是一样的;都能自动装配;

@Autowired 
//标注在方法,Spring容器创建当前对象,就会调用方法,完成赋值;
//方法使用的参数,自定义类型的值从ioc容器中获取
public void setCar(Car car) {
	this.car = car;
}

               [标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取;(默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作)

//构造器要用的组件,都是从容器中获取
//@Autowired可以省略
public Boss(Car car){
	this.car = car;
	System.out.println("Boss...有参构造器");
}

               [放在参数位置]:@Bean注解标注的方法,在创建对象的时候,方法参数的值也是从容器中获取的,在参数前面可以使用@Autowired注解,也可以省略。

/**
	 * @Bean标注的方法创建对象的时候,方法参数的值从容器中获取
	 * @param car
	 * @return
	 */
	@Bean
	public Color color(Car car){
		Color color = new Color();
		color.setCar(car);
		return color;
	}

2.3.2 @Resource(JSR250)和@Inject(JSR330)[都java规范的注解]

@Resource:
            可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的;
            不能支持@Primary功能,不支持@Autowired(reqiured=false);

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

@Autowired:Spring定义的; @Resource、@Inject都是java规范

2.3.3 自定义组件中注入Spring容器底层的组件

       自定义组件想要使用Spring容器底层的一些组件(例如:ApplicationContext,BeanFactory,xxx);只需要让 自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware;

       把Spring底层一些组件注入到自定义的Bean中:Bean实现xxxAware:使用xxxProcessor功能;比如,如果在我们自定义的组件中,需要使用ApplicationContext 容器对象,那么就让Bean实现ApplicationContextAware接口,重写接口中的方法,Spring就会通过对应的 ApplicationContextAwareProcessor(后置处理器)把我们需要的组件注入到自定义组件中。自定义的组件就可以使用一个变量来存储并使用他。

package com.atguigu.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.stereotype.Component;
import org.springframework.util.StringValueResolver;

@Component
public class Red implements ApplicationContextAware,BeanNameAware,EmbeddedValueResolverAware {
	
	private ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println("传入的ioc:"+applicationContext);
		this.applicationContext = applicationContext;
	}

	@Override
	public void setBeanName(String name) {
		System.out.println("当前bean的名字:"+name);
	}

	@Override
	public void setEmbeddedValueResolver(StringValueResolver resolver) {
		String resolveStringValue = resolver.resolveStringValue("你好 ${os.name} 我是 #{20*18}");
		System.out.println("解析的字符串:"+resolveStringValue);
	}

}

2.4 动态加载环境@Profile

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

       开发,测试和生产环境存在不同,需要程序可以动态的去加载不同的配置,比如数据源的不同,为了解决这个问题,Spring提供了@Profile注解,可以根据当前配置,动态的激活不同的组件。

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

  * 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境。
  * 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
  * 3)、没有标注环境标识的bean在,任何环境下都是加载的;

代码演示:

数据资源文件dbconfig.properties

db.user=root
db.password=123456
db.driverClass=com.mysql.jdbc.Driver

涉及到数据源,需要引入下面的连接池和数据库驱动的jar包:

<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
		<dependency>
			<groupId>c3p0</groupId>
			<artifactId>c3p0</artifactId>
			<version>0.9.1.2</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.44</version>
		</dependency>

 

配置类如下:

package com.spring.annotation.config;


import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.util.StringValueResolver;

import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * Profile:
 * 		Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;
 * 
 * 开发环境、测试环境、生产环境;
 * 数据源:(/A)(/B)(/C);
 * 
 * 
 * @Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件
 * 
 * 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
 * 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
 * 3)、没有标注环境标识的bean在,任何环境下都是加载的;
 */
//使用@PropertySource引入类路径下的dbconfig.properties资源文件, / 表示从根目录开始
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware{
	
	@Value("${db.user}")
	private String user;
	
	private StringValueResolver valueResolver;
	
	private String  driverClass;
	
	@Bean
	public Yellow yellow(){
		return new Yellow();
	}
	
	@Profile("test")
	@Bean("testDataSource")
	public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setUser(user);
		dataSource.setPassword(pwd);
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
		dataSource.setDriverClass(driverClass);
		return dataSource;
	}
		
	@Profile("dev")
	@Bean("devDataSource")
	public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setUser(user);
		dataSource.setPassword(pwd);
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud");
		dataSource.setDriverClass(driverClass);
		return dataSource;
	}
	
	@Profile("prod")
	@Bean("prodDataSource")
	public DataSource dataSourceProd(@Value("${db.password}")String pwd) throws Exception{
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setUser(user);
		dataSource.setPassword(pwd);
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515");
		
		dataSource.setDriverClass(driverClass);
		return dataSource;
	}

    //实现了EmbeddedValueResolverAware接口,可以通过该方法手动注册
	@Override
	public void setEmbeddedValueResolver(StringValueResolver resolver) {
		this.valueResolver = resolver;
            //通过值解析器直接共配置文件中解析得到驱动类
		driverClass = valueResolver.resolveStringValue("${db.driverClass}");
	}

}

测试类:

public class IOCTest_Profile {
	
	//1、使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test
	//2、代码的方式激活某种环境;
	@Test
	public void test01(){
		AnnotationConfigApplicationContext applicationContext = 
				new AnnotationConfigApplicationContext();
		//1、创建一个applicationContext
		//2、设置需要激活的环境
		applicationContext.getEnvironment().setActiveProfiles("dev");
		//3、注册主配置类
		applicationContext.register(MainConfigOfProfile.class);
		//4、启动刷新容器
		applicationContext.refresh();
		
		
		String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
		for (String string : namesForType) {
			System.out.println(string);
		}
		
		Yellow bean = applicationContext.getBean(Yellow.class);
		System.out.println(bean);
		applicationContext.close();
	}

}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值