Spring驱动注解①

该文档是:Spring加深理解...

博客连接:https://www.loveuluo.cn

日期:2021-01-20

1. 组件注册

1.1 @Configurration&@Bean给容器中注册组件

(1)准备一个Bean实体类:

public class Person {
    private String name;
    private Integer age;
}

配置类:

//配置类==配置文件
@Configuration//告诉Spring这是一个配置类
public class MainConfig {

    //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
    @Bean//默认是单实例的
    public Person person01(){
        return new Person("zhangsan",10);
    }
}

测试类:

public class AnoTest {

    @Test
    public void Test01(){
        //加载配置类,会把@Bean标注的自动加到容器中
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        //获得所有组件名
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        //根据组件名拿到组件
        //默认是单实例的 无论多少次获取 获取的都是之前new的
        Person person01 = (Person) context.getBean("person01");
        System.out.println(person01);
    }
}

测试结果:

image-20210120150208396

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

包扫描、只要标注了@Controller、@Service、@Repository、@Component都会自动加到容器中。

@ComponentScan可以自定义TypeFilter指定过滤规则:

//配置类==配置文件
@Configuration  //告诉Spring这是一个配置类
@ComponentScans(//@ComponentScans 里可以写多个@ComponentScan
        value = {
                @ComponentScan(value="com.atguigu",includeFilters = {
/*                        @Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
                        @Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}) BookService类和子类都加载进容器,*/
                        @Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
                },useDefaultFilters = false)    
        }
        )
//@ComponentScan  value:指定要扫描的包
//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
//FilterType.ANNOTATION:按照注解
//FilterType.ASSIGNABLE_TYPE:按照给定的类型;
//FilterType.ASPECTJ:使用ASPECTJ表达式
//FilterType.REGEX:使用正则指定
//FilterType.CUSTOM:使用自定义规则
public class MainConfig {
    
    //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
    @Bean("person")
    public Person person01(){
        return new Person("lisi", 20);
    }

}

MyTypeFilter自定义规则类:

public class MyTypeFilter implements TypeFilter {

    /**
     * metadataReader:读取到的当前正在扫描的类的信息
     * metadataReaderFactory:可以获取到其他任何类信息的
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
            throws IOException {
        // TODO Auto-generated method stub
        //获取当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前正在扫描的类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();
        
        //获取当前正在扫描的类的类信息的 包名+类名
        String className = classMetadata.getClassName();
        
        System.out.println("--->"+className);
        if(className.contains("er")){
            //返回true代表匹配成功,会加入容器中
            return true;
        }
        //返回false匹配失败
        return false;
    }

}

1.3 @Scope设置组件作用域&@Lazybean懒加载

singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。以后每次获取就是直接从容器( 类似map.get() )中拿。

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

懒加载(针对于单实例使用的):单实例bean:默认在容器启动的时候创建对象;懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化,下次再要用的时候直接从容器中拿;

配置类:

//默认是单实例的
/**
     * ConfigurableBeanFactory#SCOPE_PROTOTYPE    
     * @see ConfigurableBeanFactory#SCOPE_SINGLETON  
     * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST  request
     * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION     sesssion
     * @return\
     * @Scope:调整作用域
     * prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。
     *                     每次获取的时候才会调用方法创建对象;
     * singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。
     *             以后每次获取就是直接从容器(map.get())中拿,
     * request:同一次请求创建一个实例
     * session:同一个session创建一个实例
     * 
     * 懒加载(针对于单实例使用的):
     *         单实例bean:默认在容器启动的时候创建对象;
     *         懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化;
     * 
     */
//@Scope("prototype") 这是多实例
@Scope("singleton")
@Lazy//懒加载
@Bean
public Person person(){
    System.out.println("给容器中添加person");
    return new Person("lisi",20);
}

@Bean//默认就是单实例的
public Person person2(){
    System.out.println("给容器中添加person2");
    return new Person("liwu",30);
}

测试类:

@Test
public void Test02(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
    System.out.println("ioc容器创建....");
    Person person1 = (Person) context.getBean("person");
    System.out.println(person1);
    Person person2 = (Person) context.getBean("person2");
    System.out.println(person2);

}

测试结果:

image-20210120152820340

1.4 @Conditional按照条件注册bean

配置类:

/**
 * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean,可以传入多个Condition
 * 也可以放在类上:类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效;
 * 如果系统是windows,给容器中注册比尔盖茨
 * 如果是iphone系统,给容器中注册乔布斯
 */
@Conditional({WindowsCondition.class})
@Bean
public Person windows(){
    return new Person("比尔盖茨",55);
}

@Conditional(IphoneCondition.class)
@Bean
public Person iphone(){
    return new Person("乔布斯",50);
}

实现Condition接口的类:

判断是否是Windows:

//判断是否是Windows系统
public class WindowsCondition implements Condition {
    /**
     * ConditionContext:判断条件能使用的上下文(环境)
     * AnnotatedTypeMetadata:注释信息
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //1、能获取到ioc使用的beanfactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //2、获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        //3、获取当前环境信息
        Environment environment = context.getEnvironment();
        //4、获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        //获取系统名
        String property = environment.getProperty("os.name");
        //可以判断容器中的bean注册情况,也可以给容器中注册bean
        boolean definition = registry.containsBeanDefinition("person");

        //如果包含Windows那就返回true也就是条件通过
        if(property.contains("Windows")){
            return true;
        }

        return false;
    }

}

判断是否是Iphone:

public class IphoneCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        //如果包含iphone那就返回true也就是条件通过
        if(property.contains("iphone")){
            return true;
        }
        return false;
    }
}

测试类:

@Test
public void Test03(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
    //拿到运行环境
    ConfigurableEnvironment environment = context.getEnvironment();
    //拿到环境变量的值 - Windows10
    String property = environment.getProperty("os.name");
    System.out.println(property);

    //拿到所有类型为Person.class的组件名
    String[] beanNamesForType = context.getBeanNamesForType(Person.class);
    for (String name : beanNamesForType) {
        System.out.println(name);
    }
    //拿到所有类型为Person.class组件
    Map<String, Person> persons = context.getBeansOfType(Person.class);
    System.out.println(persons);
}

测试结果:

image-20210120160753098

1.5 @Import

(1) @Import快速给容器中导入一个组件

示范用的实体类:

public class Color {
}
public class Red {
}

配置类:

@Configuration
@Import({Color.class, Red.class})//组件名默认是全类名
public class MainConfig2 {

    @Bean
    public Person zhangsan(){
        return new Person("张三",25);
    }
}

测试类:

//抽取一个快速打印所有组件名的方法
public void daYin(AnnotationConfigApplicationContext context){
    String[] names = context.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println(name);
    }
}

@Test
public void Test04(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
    daYin(context);
}

测试结果:

image-20210120162727637

(2)@Import使用ImportSelector

示范用的实体类:

public class Blue {
}
public class Yellow {
}

配置类:

@Configuration
@Import({Color.class, Red.class, MyImportSelector.class})//组件名默认是全类名
public class MainConfig2 {

    @Bean
    public Person zhangsan(){
        return new Person("张三",25);
    }
}

实现了ImportSelector的类:

//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {

    //返回值,就是要导入到容器中的组件全类名
    //AnnotationMetadata:当前标注@Import注解的类的所有注解信息
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {

        return new String[]{"com.Luo.bean.Blue","com.Luo.bean.Yellow"};
    }
}

测试类:

@Test
public void Test04(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
    daYin(context);//调用打印所有组件名的自定义方法
}

测试结果:

image-20210120163653138

(3) @Import使用ImportBeanDefinitionRegistrar

示范用的实体类:

public class CaiHong {
}

配置类:

@Configuration
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})//组件名默认是全类名
public class MainConfig2 {

    @Bean
    public Person zhangsan(){
        return new Person("张三",25);
    }
}

实现了ImportBeanDefinitionRegistrar的类:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * AnnotationMetadata:当前类的注解信息
     * BeanDefinitionRegistry:BeanDefinition注册类;
     *        把所有需要添加到容器中的bean;调用
     *        BeanDefinitionRegistry.registerBeanDefinition手工注册进来
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        //判断容器中是否有名字叫"XXX"的组件
        boolean definition = registry.containsBeanDefinition("com.Luo.bean.Red");
        boolean definition2 = registry.containsBeanDefinition("com.Luo.bean.Blue");
        //如果都有
        if(definition && definition2){
            //指定Bean定义信息;(Bean的类型,Bean的作用域等等....)
            RootBeanDefinition beanDefinition = new RootBeanDefinition(CaiHong.class);
            //注册一个Bean,可以自己指定bean名
            registry.registerBeanDefinition("rainBow", beanDefinition);
        }
    }

}

测试结果:

image-20210120165201779

1.6 使用Spring提供的FactoryBean

ColorFactoryBean工厂类:

//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {

    //返回一个Color对象,这个对象会添加到容器中
    @Override
    public Color getObject() throws Exception {
        return new Color();
    }

    //返回对象类型
    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }

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

配置类:

@Bean
public ColorFactoryBean colorFactoryBean(){
    return new ColorFactoryBean();
}

测试类:

@Test
public void Test05(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
    daYin(context);//调用打印所有组件名的自定义方法
    //看样子是获取的colorFactoryBean工厂对象,其实得到的是Color对象
    Color color = (Color) context.getBean("colorFactoryBean");
    System.out.println(color);
    //也可以得到colorFactoryBean工厂对象
    ColorFactoryBean colorFactoryBean = (ColorFactoryBean) context.getBean("&colorFactoryBean");
    System.out.println(colorFactoryBean);
}

测试结果:

image-20210120184226901

1.7 组件注册总结

  • 给容器中注册组件:
  • 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类]
  • 2)、@Bean[导入的第三方包里面的组件]
  • 3)、@Import[调用对象的无参构造快速给容器中导入一个组件]

    • 3.1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名
    • 3.2)、ImportSelector:返回需要导入的组件的全类名数组;(SpringBoot源码用的非常多)
    • 3.3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中
  • 4)、使用Spring提供的 FactoryBean(工厂Bean);

    • 4.1)、默认获取到的是工厂bean调用getObject创建的对象
    • 4.2)、要获取工厂Bean本身,我们需要给id前面加一个&(&colorFactoryBean)

2. 生命周期

  • bean的生命周期:

    • bean创建---初始化----销毁的过程
    • 容器管理bean的生命周期;
    • 我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法
  • 构造(对象创建)

    • 单实例:在容器启动的时候创建对象
    • 多实例:在每次获取的时候创建对象\
  • BeanPostProcessor.postProcessBeforeInitialization (后置处理器)
  • 初始化:对象创建完成,并赋值好,调用初始化方法。。
  • BeanPostProcessor.postProcessAfterInitialization (后置处理器)
  • 销毁:

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

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

Car实体类:

public class Car {
   
   public Car(){
      System.out.println("car constructor...");
   }
   
   public void init(){
      System.out.println("car ... init...");
   }
   
   public void detory(){
      System.out.println("car ... detory...");
   }

}

配置类:

@Configuration
public class MainConfigOfLifeCycle {
    //指定初始化方法和销毁方法
   @Bean(initMethod="init",destroyMethod="detory")
   public Car car(){
      return new Car();
   }

}

测试类:

public class LifeTest {

    @Test
    public void Test01(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器创建...");
        context.close();
    }
}

测试结果:

image-20210120195817323

2.2 实现InitializingBeanDisposableBean接口

Cat实体类:

@Component//这里我们用Component放入容器中,不用@Bean,都用用加深一下印象
//实现InitializingBean和DisposableBean接口
public class Cat implements InitializingBean,DisposableBean {
   
   public Cat(){
      System.out.println("cat constructor...");
   }

   @Override
   public void destroy() throws Exception {
      // 销毁方法
      System.out.println("cat...destroy...");
   }

   @Override
   public void afterPropertiesSet() throws Exception {
      // 初始方法
      System.out.println("cat...afterPropertiesSet...");
   }

}

配置类:

@ComponentScan("com.Luo.bean")//扫描Cat实体类
@Configuration
public class MainConfigOfLifeCycle {
}

测试类:

@Test
public void Test01(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
    System.out.println("容器创建...");
    context.close();
}

测试结果:

image-20210120195857833

2.3 @PostConstruct&PreDestroy

Dog实体类:

@Component
public class Dog implements ApplicationContextAware {
   
   //@Autowired
   private ApplicationContext applicationContext;
   
   public Dog(){
      System.out.println("dog constructor...");
   }

   //对象创建并赋值之后调用
   @PostConstruct
   public void init(){
      System.out.println("Dog....@PostConstruct...");
   }

   //容器移除对象之前
   @PreDestroy
   public void detory(){
      System.out.println("Dog....@PreDestroy...");
   }

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.applicationContext = applicationContext;
   }

}

测试结果:

image-20210120195936699

2.4 @BeanPostProcessor*

(1)@BeanPostProcessor后置处理器

后置处理器类:

/**
 * 后置处理器:初始化前后进行处理工作
 * 将后置处理器加入到容器中
 * @author lfy
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

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

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

}

测试结果:

image-20210120200002173

(2)@BeanPostProcessor原理*

前边都是获取单例对象创建对象之类的,接下来到下图这一步:

可以从图中看出显示调用populateBean(beanName, mbd, instanceWrapper);给对象的属性值都赋好,然后才是调用initializeBean(初始化)方法。

image-20210120205417984

进入initializeBean方法看看:

先执行初始化之前的后置处理器,再执行自定义初始化方法例如【@Bean(initMethod="init",destroyMethod="detory"))这里的init】,然后再执行初始化之后执行的后置处理器。

image-20210120211228652

进入applyBeanPostProcessorsBeforeInitialization方法中看看:

遍历得到容器中所有的BeanPostProcessor(后置处理器);挨个执行beforeInitialization

一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization

image-20210120210735229

(3)BeanPostProcessor在Spring底层的使用*

普通类拿到Spring的IOC容器的方法。

我们的Dog类只需要实现ApplicationContextAware接口,它可以帮我们拿到IOC容器。这个接口有一个setApplicationContext方法,这个方法中,我们的IOC容器就会传进來。通过在方法外声明一个IOC容器然后把传过来的IOC容器赋值给它。这个功能是谁做的呢?

就是ApplicationContextAwareProcessor而它是BeanPostProcessor的实现类。

@Component
public class Dog implements ApplicationContextAware {
    //在外边声明一个ioc容器,以便拿到传过来的。
    private ApplicationContext applicationContext;
    
    @Override
    //这个方法上的applicationContext参数就是ioc容器, this.把拿到的ioc容器赋值给外边的。
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    } 
}

还有别的类也用到了BeanPostProcessor,例如:

  • BeanValidationPostProcessor可以做数据校验,在Web里用的特别多。
  • InitDestroyAnnotationBeanPostProcessor就是用来处理@PostConstructPreDestroy的。
  • AutowireAnnotationBeanPostProcessor实现@Autowired,为什么能自动装配进来值呢?就是它干的。

2.5 生命周期总结

  • 1)、指定初始化和销毁方法;

    • 通过@Bean指定init-method和destroy-method;
  • 2)、通过让Bean实现InitializingBean(定义初始化逻辑),DisposableBean(定义销毁逻辑);
  • 3)、可以使用JSR250;

    • @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法
    • @PreDestroy:在容器销毁bean之前通知我们进行清理工作
  • 4)、BeanPostProcessor【interface】:bean的后置处理器;

    • 在bean初始化前后进行一些处理工作;
    • postProcessBeforeInitialization:在初始化之前工作
    • postProcessAfterInitialization:在初始化之后工作

3. 属性赋值

3.1 @Value赋值

实体类:

public class Person {
    @Value("张三")
    private String name;
    @Value("#{20-2}")
    private Integer age;

配置类:

@Configuration
public class MyConfigValue {

    @Bean
    public Person person(){
        return new Person();
    }
}

测试类:

public class ValueTest {
    @Test
    public void Test01(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfigValue.class);
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        Person person = (Person) context.getBean("person");
        System.out.println(person);
    }
}

测试结果:

image-20210121094515801

3.2 @PropertySource 加载外部配置文件

使用配置文件时的做法:

<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> 
    <property name="driverClassName" value="${prop.driverClass}"></property> 
    <property name="url" value="${prop.url}"></property> 
    <property name="username" value="${prop.userName}"></property>
    <property name="password" value="${prop.password}"></property>
</bean>

使用注解的做法:

实体类:

public class Person {
    @Value("张三")
    private String name;
    @Value("#{20-2}")
    private Integer age;
    @Value("${person.nickName}")
    private String nickName;

配置类:

//使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部文件后使用${}取出配置文件的值
@PropertySource(value = {"classpath:/person.properties"}) //可以导入多个配置文件
@Configuration
public class MyConfigValue {

    @Bean
    public Person person(){
        return new Person();
    }
}

测试类:

public class ValueTest {
    @Test
    public void Test01(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfigValue.class);
        Person person = (Person) context.getBean("person");
        System.out.println(person);

        //获取运行时的环境变量
        ConfigurableEnvironment environment = context.getEnvironment();
        //这里也能得到配置文件中的值,因为它加载到了环境变量中
        String property = environment.getProperty("person.nickName");
        System.out.println(property);
    }
}

测试结果:

image-20210121095458280

4. 自动装配

4.1 @Autowired&@Qualifier&@Primary

(1)@Autowired

1)、默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值
2)、如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找:applicationContext.getBean("bookDao")
3)、自动装配一定要在容器中找到对应的组件然后赋上值,没有就会报错。可以使用@Autowired(required=false),找到就赋值,没找到就拉倒。

配置类:

@Configuration
@ComponentScan({"com.Luo.service","com.Luo.dao",
   "com.Luo.controller"})
public class MainConifgOfAutowired {
   
   @Bean("bookDao2") //注入一个组件 id为bookDao2
   public BookDao bookDao(){
      BookDao bookDao = new BookDao();
      bookDao.setLable("我是bookDao2");
      return bookDao;
   }

}

Service类:

@Service
public class BookService {
   /*     1)、默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值
    *     2)、如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找
    *         applicationContext.getBean("bookDao")                                              */
   @Autowired(required=false) //此时容器中有两个BookDao.class类型的,所以会按照属性名称(bookDao)找到对应的id的组件进行注入
   private BookDao bookDao;

   @Override
   public String toString() {
      return "BookService [bookDao=" + bookDao + "]";
   }

}

BookDao类:

//注入容器中,id默认是类名首字母小写
@Repository 
public class BookDao {
   
   private String lable = "我是bookDao";
   //lable GET/SET方法
   @Override
   public String toString() {
      return "BookDao [lable=" + lable + "]";
   }
   
}

测试类:

public class ValueTest {
    @Test
    public void Test01(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConifgOfAutowired.class);
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        //获取BookService
        BookService bookService = (BookService) context.getBean("bookService");
        //打印bookService,它的toString方法自动把它的bookdao打印出来
        System.out.println(bookService);
    }
}

测试结果:

image-20210121103335579

(2)@Qualifier

1)、@Qualifier("bookDao2"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名

Service类:

@Service
public class BookService {
   //@Qualifier+@Autowired
   @Qualifier("bookDao2")  //使用@Qualifier指定需要装配的组件的id,而不是使用属性名
   @Autowired 
   private BookDao bookDao;

   @Override
   public String toString() {
      return "BookService [bookDao=" + bookDao + "]";
   }

}

测试结果:

image-20210121104006271

(3)@Primary

1)、@Primary:让Spring进行自动装配的时候,没有用@Qualifier指定装哪个的话默认使用首选的bean;也可以继续使用@Qualifier指定需要装配的bean的名字

Service类:

@Service
public class BookService {
   @Autowired
   private BookDao bookDao;

   @Override
   public String toString() {
      return "BookService [bookDao=" + bookDao + "]";
   }

}

配置类:

@Configuration
@ComponentScan({"com.Luo.service","com.Luo.dao", "com.Luo.controller"})
public class MainConifgOfAutowired {

   @Primary
   @Bean("bookDao2") //注入一个组件 id为bookDao2
   public BookDao bookDao(){
      BookDao bookDao = new BookDao();
      bookDao.setLable("我是bookDao2");
      return bookDao;
   }

}

测试结果:

image-20210121104940293

4.2 @Resource&@Inject

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

(1)@Resource

和@Autowired一样实现自动装配功能,默认按照属性名称进行装配,不能支持@Primary或者@Autowired的required=false)功能。

@Service
public class BookService {
   //和@Autowired一样实现自动装配功能,默认按照属性名称进行装配,不能支持@Primary或者@Autowired的required=false)功能
   @Resource(name = "bookDao2")//也可以自己指定按照什么id进行注入
   private BookDao bookDao;

   @Override
   public String toString() {
      return "BookService [bookDao=" + bookDao + "]";
   }

}

(2)@Inject

需要导入javax.inject的jar包,也能自动注入。有@Primary功能,但是没有required=false的功能。

@Service
public class BookService {
   @Inject
   private BookDao bookDao;

   @Override
   public String toString() {
      return "BookService [bookDao=" + bookDao + "]";
   }

}

4.3 @Autowired用在构造器,参数,方法上

@Autowired:构造器,参数,方法,属性;都是从容器中获取参数组件的值:

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

2)、[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取

3)、[放在参数位置]:效果一样,也是从容器中获取

Boss类:

//默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作
@Component
public class Boss {

   private Car car;

   //如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取
   public Boss(Car car){
      this.car = car;
      System.out.println("Boss...有参构造器");
   }

   public Car getCar() {
      return car;
   }

   // @Autowired(如果是只有无参构造没有有参构造的情况下,先用无参构造创建对象,再调用set方法赋值,参数会从容器中获取)
   //标注在方法,Spring容器创建当前对象,就会调用方法,完成赋值;
   //方法使用的参数,自定义类型的值从ioc容器中获取
   public void setCar(Car car) {
      this.car = car;
   }

   @Override
   public String toString() {
      return "Boss [car=" + car + "]";
   }

}

@Bean标注的方法创建对象的时候,方法参数的值从容器中获取:

Color类:

public class Color {
    private Car car;
    //get set方法
}

配置类:

@Configuration
@ComponentScan({"com.Luo.bean"})
public class MainConifgOfAutowired {

   @Bean //这里的car会自动从容器中获取(也就是参数上默认加了个@Autowired,默认按照参数名找到容器中对应的名字 也就是car,也就是说方法的参数名car要和容器中的实例名一致)
   public Color color(Car car){
      Color color = new Color();
      color.setCar(car);
      return color;
   }

4.4 Aware注入Spring底层组件&原理

Aware是ApplicationContextAware、BeanNameAware之类的接口的总接口。为我们bean提供了使用spring容器底层的一些对象的功能。只要实现了xxxAware接口,这些Aware接口规定的那些方法,在对象创建的时候就会被调用,可以把Spring底层一些组件注入到我们自定义的Bean中。
每个Aware都有他对应的Processor也就是后置处理器。

自定义bean类:

@Component
//  4)、自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx);
//        自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware;
//        把Spring底层一些组件注入到自定义的Bean中;
//            xxxAware:功能使用xxxProcessor;
//           ApplicationContextAware==》ApplicationContextAwareProcessor;
public class Red implements ApplicationContextAware,BeanNameAware,EmbeddedValueResolverAware {

   private ApplicationContext applicationContext;

   //以下三个方法都是在对象创建的时候就会被调用
   @Override
   //这个可以帮我们注入IOC容器
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      System.out.println("传入的ioc:"+applicationContext);
      this.applicationContext = applicationContext;
   }

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

   @Override
   //传过来一个值解析器,可以解析String的值,例如占位符之类的。
   public void setEmbeddedValueResolver(StringValueResolver resolver) {
      String resolveStringValue = resolver.resolveStringValue("你好 ${os.name} 我是 #{20*18}");
      System.out.println("解析的字符串:"+resolveStringValue);
   }

}

测试类:

@Test
public void Test01(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConifgOfAutowired.class);
    Red red = (Red) context.getBean("red");
    System.out.println(red);
}

测试结果:

image-20210121150010259

4.5 @Profile根据环境注册bean

  • Profile:

    • Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;
    • 开发环境、测试环境、生产环境;
    • 数据源:(/A)(/B)(/C);

配置文件:

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

配置类:

根据某种环境只激活某个数据源,例如开发环境只用开发的数据源。

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

    • 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
    • 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
    • 3)、没有标注环境标识的bean在,任何环境下都是加载的;
@PropertySource("classpath:/dbconfig.properties")//加载外部配置文件
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware{
    //有三种从配置文件中获取数据的方法,一一演示:
    @Value("${db.user}")//(1)取出配置文件中的值
    private String user;
    private StringValueResolver valueResolver;
    private String  driverClass;

    @Profile("test")
    @Bean
    public Yellow yellow(){
        return new Yellow();
    }

    @Override
    //(2)配置类在启动的时候,把值解析器拿到,用值解析器解析表达式
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.valueResolver = resolver;
        driverClass = valueResolver.resolveStringValue("${db.driverClass}");
    }

    @Profile("test")//加上标识
    @Bean("testDataSource")
    //测试的数据源  [ (3)也可以在这里直接在参数上@Value从配置文件中取出密码的值赋值上去 ]
    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;
    }
}

测试类:

这里如果直接用有参构(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfProfile.class);)造创建容器的话是这三步:1.调用无参创建对象(无参构造里做了默认操作),2.注册,3.刷新。

image-20210121155656374

我们这里直接手动写出来,因为要在注册主配置类之前先设置环境:

public class LifeTest {

    //1.使用命令行动态参数: 在虚拟机参数位置加载-Dspring.profiles.active=test
    //2.使用代码的方式(这里我们选用这种用代码进行测试)
    @Test
    public void Test01(){
        //2.1创建一个ApplicationContext
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //2.2在我们容器还没启动创建其它bean之前先设置环境
        context.getEnvironment().setActiveProfiles("test");//可以设置多个
        //2.3注册主配置类
        context.register(MainConfigOfProfile.class);
        //2.4启动刷新容器
        context.refresh();

        String[] names = context.getBeanNamesForType(DataSource.class);//根据类型拿到所有对应bean名称
        for (String name : names) {
            System.out.println(name);
        }
    }
}

测试结果:

image-20210121160204164

4.6 自动装配总结

  • Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值;
  • 1)、@Autowired:自动注入:

    • 1.1)、默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值
    • 1.2)、如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找:applicationContext.getBean("bookDao")
    • 1.3)、@Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名
    • 1.4)、自动装配一定要在容器中找到对应的组件然后赋上值,没有就会报错。可以使用@Autowired(required=false),找到就赋值,没找到就拉倒。
    • 1.5)、@Primary:让Spring进行自动装配的时候,默认使用首选的bean;也可以继续使用@Qualifier指定需要装配的bean的名字
  • 2)、Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]

    • @Resource:

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

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

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

AutowiredAnnotationBeanPostProcessor:解析完成自动装配功能;

  • 3)、@Autowired:构造器,参数,方法,属性;都是从容器中获取参数组件的值

    • 3.1)、[标注在方法位置]:@Bean+方法参数;参数从容器中获取;默认不写@Autowired效果是一样的;都能自动装配
    • 3.2)、[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取
    • 3.3)、放在参数位置:效果一样,也是从容器中获取
    • 3.4)、@Bean标注的方法创建对象的时候,方法参数的值从容器中获取,也就是参数上默认加了个@Autowired,默认按照参数名找到容器中对应的名字 ,也就是说方法的参数名要和容器中的实例名一致
  • 4)、Aware:自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx);

自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware;

把Spring底层一些组件注入到自定义的Bean中;

xxxAware:功能使用xxxProcessor;

ApplicationContextAware==》ApplicationContextAwareProcessor;

  • 5)、Profile:

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

开发环境、测试环境、生产环境;

数据源:(/A)(/B)(/C);

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

  • 5.1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
  • 5.2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
  • 5.3)、没有标注环境标识的bean在,任何环境下都是加载的;
最后修改:2021 年 03 月 19 日 10 : 54 AM
如果觉得我的文章对你有用,请随意赞赏