关于java:欢迎光临Spring时代一-上柱国IOC列传

37次阅读

共计 19286 个字符,预计需要花费 49 分钟才能阅读完成。

本篇咱们就开始学习 Java 畛域赫赫有名的 Spring Framework,本篇介绍的 Spring Framework 中的 IOC(管制反转,也称依赖注入),原本想用初遇篇,这个题目的,然而一想这个题目不太见名知义,就改成上柱国列传了。为什么叫上柱国 IOC 列传呢,因为感觉上柱国这个名字挺好听。

上柱国: 原义为自春秋起为军事武装的高级统帅,引申义为勋绩的荣誉称号,战国时楚、赵置,位令尹、相国下,甚尊。原为捍卫国都之官

缘起

2002 年 Rod Johnson 在 2002 年编著的《Expert One-On-One J2EE Design And Development》一书中对,对 Java EE 正统框架 (EJB) 臃肿、低效、脱离现实的学院派提出了质疑,而后以该书为指导思想,编写了 interface21 框架。而后在 interface21 框架的根底上,通过从新设计,于 2004 年公布。到当初开始曾经十八年左右了。

开发者: Spring Framework 请答复

在《欢迎光临 Spring 时代 - 绪论》中咱们提出了几个对于如何取对象的问题,这里咱们再回顾一下:

  • 对象之间有简单的依赖关系的时候,在不心愿是硬编码的状况下,如何取对象能力做到优雅和可配置化?
  • 晓得如何创建对象,然而无奈把握创建对象的机会。我当初就心愿把如何创建对象代码交给 ” 负责什么时候创立的代码 ”。后者在对应的机会,就调用对应的创建对象函数。
  • 尽管 Java 不必操心内存回收问题,然而我还是心愿能节俭资源一下,在控制器这一层,不心愿解决的每个申请都 new 一下对应的 service 实现类,我心愿能将该 Service 做成单例模式,是否在做成单例的同时又做到让取对象优雅呢?

Spring Framework: IOC 容器就是答案

在我刚学 Spring 框架的时候,不少视频都会说,咱们并不在 new 对象,而是将对象放进 IOC 容器中,你要取的时候,向 IOC 容器中取即可。这里咱们先从实例动手再来解释 IOC。联合着例子来解释 IOC 会更加易懂。

筹备工作

本篇咱们还基于 maven 来做示例,基本上开发 Spring Framework 的程序基本上就只须要五个 jar 包就够了,别离是上面这五个:

  • spring-context
  • spring-core
  • spring-aop
  • spring-beans
  • spring-expression

这次咱们选的版本都是 5.2.8.RELEASE。
如果你不会用 maven,而后请参考我的这篇博客:

  • Maven 学习笔记

如果临时还不想学 maven,还想做 jar 包下载。那么有两种模式能够下载到对应的 jar 包:

  • Spring Framework 官网 maven 库

浏览器拜访这个网址: https://maven.springframework…

下载完粘贴到对应的 lib 目录下即可。

  • 去 maven 的地方仓库下载

输出下面的 jar 包名字,粘贴对应的依赖即可。
开发工具我这里用的是 IDEA,提醒比拟弱小,用着比拟棘手。
如果你不习惯用 IDEA,是 Eclipse 党,这边举荐你下载 Spring 官网出品的开发工具 STS,也是在 Eclipse 的根底开发的,开发 Spring 框架的程序更加疾速。
浏览器输出: https://spring.io/tools/

本文的示例都是基于 IDEA 来做的,如果不会用 IDEA,想用 STS,能够参考文末前面的参考资料,是视频。

第一个 Spring Framework 程序


个别 Spring 框架的这个配置文件,咱们都命名为 applicationContext.xml,这是一种大抵的约定。
下面的问题是如何优雅的取对象,在取之前你首先就得存,向 applicationContext.xml 放对象。怎么放?
像上面这样放:

我建了一个 Student 类,而后外面有 name 和 age 属性,无参和有参构造函数,get 和 set 函数,重写了 toString 办法。
bean 里的 id 是惟一的,class 是 Student 类的全类名 (包名 + 类名)。
如何取:

public class SpringDemo {public static void main(String[] args) {
        // 加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) applicationContext.getBean("student");
        System.out.println(student);
    }
}

打印后果:

这就叫 DI 和 IOC

咱们间接 new,在 Spring 的配置文件配置参数,而后间接取对象的形式,咱们称之为管制反转 (IOC Inversion of Control) 或依赖注入 (DI Dependency Injection)。首先解释一下为什么有两个称说,刚开始是只有管制反转,起初发现管制反转这个概念有点难以了解,就在一次大会上讲管制反转改为依赖注入。
那什么是管制反转,咱们能够认为 new 对象是一种控制权,而后管制反转就是咱们将 new 对象的控制权交给 Spring Framework。依赖注入呢? 其实也是一样,咱们能够了解为在 Spring 的配置文件配置完对象之后,Spring 将该对象给须要该对象的对象,此时就答复了下面咱们提出的第二个问题:

对象之间有简单的依赖关系的时候,在不心愿是硬编码的状况下,如何取对象能力做到优雅和可配置化?
如果对象之间有简单的依赖关系,那么就请将这种简单的依赖关系当做配置参数一样,放在 Spring 的配置文件中,而后再从 Spring IOC 容器中取值,咱们此时权且就能够将配置文件当做 IOC 容器,搁置了对象。

对象之间有依赖关系该如何配置呢?

用下面的配置只能解决简略的值,那如果某个对象的某个属性也是对象类型的呢?Spring 也想到了,当对象的属性是属性用 ref 注入,像上面这样:

<bean id = "studentCard" class = "org.example.StudentCard">
        <property name = "id" value="1"></property>
        <property name = "cardNo" value="code01"></property>
     </bean>

    <bean id="student" class="org.example.Student">
        <property name = "name" value = "zs"></property>
        <property name = "age" value = "23"></property>
        <property name = "studentCard" ref = "studentCard"></property>
    </bean>

一般属性用 value,那么援用类型就用 ref,ref 的值是配置文件中 bean 标签的 id 属性,所以在 applicationContext.xml 中 id 禁止反复。

通过注解将对象放入 IOC 容器中

咱们下面的第三个问题,咱们 Dao 层的对象在对应的 Service 只须要一份就能够了,Spring 容器中的对象默认都是单例的。
那 Dao 层有的时候都没有属性,咱们还要写在配置文件中吗?Spring 也想到了,提供了上面几个注解:

@Controller 对应管制层
@Service 对应服务层
@Component 通用注解,如果你不确定这个对象属于那一层的话,就用这个。@Repository 对应 dao 层

而后在配置文件中退出:

 <!-- 配置扫描器,base-package 放须要扫描的包, 指定了包之后,Spring 会扫描。该包上面的类,如果有以上四个注解,那么就会将对应的类退出到容器中,id 默认为类名首字母转小写。多个包也能够写,用逗号隔开即可。如果写是一个包上面有多个包,比方 org.example.service,org.example.dao。写到二级包:org.example。Spring 会主动扫描所有的子包。-->
    <context:component-scan base-package="org.example"/>



所以咱们下面的第三个问题就失去了答复,咱们写 Service 层须要对应的 dao 层对应的时候就能够这么写:

@Service
public class StudentService {
    // 伪装 Student 是 dao 层, 被打上 @Autowired 的属性,Spring 在扫描的时候会主动去容器去寻找对应的类型
    // 而后给该属性注入值,所以如果你有两个 IOC 容器中有两个 Student 对象,那么可能就会报错
    // Spring 官网并不举荐如此注入
    @Autowired
    private Student student;
    
    public void print(){System.out.println(student);
    }
}

官网举荐的注入形式是 @Autowired 呈现在 set 办法或构造方法上:

@Service
public class StudentService {
    private Student student;
    @Autowired
    public StudentService(Student student) {this.student = student;}
    public void print(){System.out.println(student);
    }
}

至此咱们下面提出的第一个问题和第三个问题失去了解决:

  • 对象有简单的依赖关系,咱们在配置文件中佩,在调用的类中,用 Autowired 主动注入,这很优雅。
  • 对应的 Serveice 层通过注解就能将 dao 层注入,就不必再每个业务办法中,反复 new 了。

而后有不懂 SpringMVC 框架的同学这里可能就会问了,那我在 Servlet 中该如何取 IOC 容器中的对象啊,Servlet 的初始化又不像 main 函数,有个明确的入口,用户是能够从任意一个网页进入的。对于这个问题能够参看:

Spring 视频教程的 P11、P12、P13、P14。

接着咱们答复第二个问题,难以把握对象的创立机会的这个问题,对于这个问题,Spring 框架的答案是条件注解。
IOC 容器有两种模式,一种是基于配置文件(咱们下面用的就是),一种是基于注解。条件注解是基于注解模式的,查了一些材料还是没找到如何用配置文件实现条件注解的。然而基于配置文件的 IOC 还须要再补充一些,所以上面是先将配置文件模式的解说结束后,才会讲基于注解的,条件注解也是基于注解。

基于 XML 的依赖注入

不同的注入形式

咱们晓得创立一个对象是有几种不同的形式的:

  1. 通过无参构造函数,而后通过 set 函数设置值
  2. 通过有参构造函数
  3. 通过反射
  4. 通过序列化

同样的在配置文件中配置对象参数的也有几种模式,下面的 property 的配置的模式就是通过第一种形式来创建对象的。有兴致的同学能够测试下。
接下来咱们介绍的就是通过第二种形式将对象放入 IOC 容器中:

<bean id = "studentCard" class = "org.example.StudentCard">
    <constructor-arg value="1" index = "0"/>
    <constructor-arg value="11" index = "1"/>
 </bean>

constructor-arg 有四个属性:

  • value (具体的值,不加 index 的话,具体的值和构造函数要求的参数类型要保持一致,默认状况下标签的先后顺序和构造函数保持一致)
  • index 用于指定给构造函数的第几个参数
  • type 指定参数类型
  • name 用于指定参数名
  • ref 这个讲过是指援用类型

通过反射产生对象:

 <bean id = "studentCard" class = "org.example.StudentCard" p:id="1" p:cardNo="23">
 </bean>


援用类型通过 p: 属性名 -ref 来设定 IOC 容器中 bean 的 ID

将汇合放入对应的 IOC 容器中

首先咱们筹备一个汇合的类,构造函数省略,get 和 set 办法省略:

public class CollectionDemo {
    private List<String> list;
    private String[] arrayString;
    private Set<String> set;
    private Map<String, Object> map;
    private Properties properties;
}

Spring 配置文件:

 <bean id = "collectionDemo"  class = "org.example.CollectionDemo">
            <property name="list">
                <value>
                    14
                </value>
            </property>
            <property name="arrayString">
                <array>
                    <value>ddd</value>
                </array>
            </property>
            <property name = "set">
                <set>
                    <value>aaa</value>
                </set>
            </property>
            <property name="map">
                <map>
                    <entry>
                        <key>
                            <value>zs</value>
                        </key>
                         <value>zs</value>
                    </entry>
                </map>
            </property>
            <property name = "properties">
                <props>
                    <prop key="aa">bb</prop>
                    <prop key="cc">dd</prop>
                </props>
            </property>
    </bean>

示例:

public class SpringDemo {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        CollectionDemo collectionDemo = (CollectionDemo)applicationContext.getBean("collectionDemo");
        System.out.println(collectionDemo);
    }
}


刚学 Spring 框架的时候,视频上说管制反转,咱们本人不再 new 对象,我认为就是真的不再 new 了,就在配置文件外面配就行了,起初随着编码量的回升,才发现这是对 Spring 框架的一种误会,是有简单依赖关系的咱们在配置文件外面配,像你要是想用个 HashMap,就不用了。

非凡值的注入问题

咱们在配置文件配置对象的时候,用的值都和 XML 预约义的符号值不抵触,什么意思呢?假如我给对象的值是 <,就会报错。

咱们下面配置 Student 对象的参数的时候,咱们用的是这种:

<bean id="student" class="org.example.Student">
    <property name = "name" value = "zs"></property>
    <property name = "age" value = "23"></property>
    <property name = "studentCard" ref = "studentCard"></property>
</bean>

咱们称之为 value 属性注入,其实还能够这么写:

<bean id="student" class="org.example.Student">
        <property name = "name" >
            <value type="java.lang.String">zs</value>
        </property>
        <property name = "age" value = "23"></property>
        <property name = "studentCard" ref = "studentCard"></property>
    </bean>

咱们称之为 value 子标签注入。两者的区别如下:

所以当咱们配置的属性值是 < 这个符号的时候咱们就能够这么写:

<bean id="student" class="org.example.Student">
    <property name = "name" >
        <value type="java.lang.String">z&lt;s</value>
    </property>
</bean>

也能够这么写:

 <bean id="student" class="org.example.Student">
        <property name = "name" >
            <value type="java.lang.String"><![CDATA[z<3]]></value>
        </property>
    </bean>

那我要给属性的值注入 null 怎么办? 办法有两个

  • 不给值(property 标签都不写, 写标签 value 外面啥都不写,如果是 String 的话,默认给的是空字符串)
  • 用 null 标签
<bean id="student" class="org.example.Student" autowire = "byName">
        <property name = "name" >
                <null/>
        </property>
  </bean>

各种类型的主动注入

下面咱们提到在 Spring 的配置文件中配置对象的属性值的时候,如果属性值是对象类型的,那么用 ref 就能够了,其实这个也能够不写,用主动注入就能够了,用这种主动注入也有两种形式:

  1. byName Spring 会主动的去寻找容器中 id 为对象属性类名首字母转小写的对象。
<bean id = "studentCard" class = "org.example.StudentCard">
        <property name="id" value="1"></property>
        <property name="cardNo" value="zs"></property>
     </bean>

    <bean id="student" class="org.example.Student" autowire = "byName">
        <property name = "name" >
            <value type="java.lang.String"><![CDATA[z<3]]></value>
        </property>
        <property name="age">
            <value>34</value>
        </property>
    </bean>

运行后果不再展现,假如你把第一个 bean 标签的 id 改为 studentCard1,那么就注入不了。

  1. byType 按类型,主动去寻找匹配的类型,如果你有两个 StudentCard 类型,也是无奈注入。
<bean id="student" class="org.example.Student" autowire = "byType">
        <property name = "name" >
            <value type="java.lang.String"><![CDATA[z<3]]></value>
        </property>
        <property name="age">
            <value>34</value>
        </property>
    </bean

基于注解的依赖注入

@Bean+ 办法

在 xml 外面限度还是挺多的,如果你不小心写错了属性名,那么也是到运行时能力发现错误,如果你不小心给错了类型值,也是到运行时能力发现错误,比方属性是数字类型,你给了一个字符串。很多时候咱们都心愿尽可能早的发现错误,那么咱们的配置文件能不能变换一种模式呢? 用代码做配置文件怎么样呢? 好啊,很好的想法啊,那咱们就用代码做配置文件吧。

@Configuration
public class SpringConfig {@Bean(name = "studentCard")
    public StudentCard studentCard(){return  new StudentCard(11,"22");
    }
    @Bean
    public Student student(@Qualifier("studentCard") StudentCard studentCard){return new Student(20,"zs",studentCard);
    }
    @Bean
    public StudentCard studentCard2(){return  new StudentCard(11,"22");
    }
}
private static void annotationTest() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        Student student = applicationContext.getBean("student", Student.class);
        System.out.println(student);
    }

运行后果:

  1. 问如何让一个类成为一个配置文件?

给一个类打上注解 @Configuration

  1. 如何将 bean 标签移植到配置类中?

将原先的 bean 标签变成办法,办法返回对象类型即可。同时办法要加上 @Bean 注解。办法名默认为 id。

  1. 那我想自定义 id 名,能够吗?

能够,在 @bean 标签中,用 name 属性自定义就好

  1. 那援用类型的属性注入怎么办?

像下面一样,在办法参数中写对应的参数即可。
也能够调用对应的办法来实现注入。
你能够本人 new,然而为什么不间接用 IOC 容器的呢。

  1. 那我有两个对象,都属于一个类。我想指定一个对象注入怎么办?

@Qualifier 中指定对象名即可。

@import、FactoryBean、ImportBeanDefinitionRegistrar、ImportSelector

@import(注解)、FactoryBean(接口)、ImportBeanDefinitionRegistrar(接口)、ImportSelector(接口)是 Spring 提供的将对象退出 IOC 容器的另外形式。

  • @import
@Configuration
@ComponentScan(basePackages = "org.example")
// ImportTest 是我建的一个空类,用来测试 @import,value 是一个数组
@Import(value = {ImportTest.class})
public class SpringConfig {}

测试一下:

 private static void annotationPrintAllBean() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {System.out.println(beanName);
        }
    }

测试后果:

这种形式加进来的 bean 名是全类名。

  • ImportSelector 简述

首先实现 ImportSelector 接口:

public class MyImportSelector implements ImportSelector {
    // 最初返回的即为须要退出到容器的类名
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{ImportTest.class.getName()};
    }
}

而后在配置类上引入:

@Configuration
@ComponentScan(basePackages = "org.example")
@Import(value = MyImportSelector.class)
public class SpringConfig {}

测试代码:

 private static void annotationPrintAllBean() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {System.out.println(beanName);
        }
    }

测试后果:

AnnotationMetadata 中携带打上 @import 注解的配置类上的元信息。

  • ImportBeanDefinitionRegistrar 概述

先实现接口:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // BeanDefinitionRegistry 能够将 bean 注册进 IOC 工厂
        // 咱们须要筹备一个 BeanDefinition。BeanDefinition beanDefinition = new RootBeanDefinition(ImportTest.class);
        registry.registerBeanDefinition("importTest",beanDefinition);
    }
}

配置类引入:

@Configuration
@ComponentScan(basePackages = "org.example")
@Import(value = MyImportBeanDefinitionRegistrar.class)
public class SpringConfig {}

测试代码:

private static void annotationPrintAllBean() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {System.out.println(beanName);
        }
    }

测试后果:

  • FactoryBean

FactoryBean 是一个接口,还有一个接口叫 BeanFactory。
反射是框架的灵魂,有的时候,某个 bean 重复性的属性太多,在配置文件外面配置也是一件让人烦心的事件,然而程序不是善于做反复工作吗?咱们是否少写点,从配置文件中读,而后用程序来做这种重复性工作呢?这也就是 FactoryBean 接口做的事件。

@Component
public class MyFactory implements FactoryBean<Car> {

    private String cardInfo;

    public MyFactory() {
        // 伪装从 Spring 的配置文件中读到了值。this.cardInfo = "brand,100,200.12";;
    }

    /**
     * 向 IOC 容器中放入对象
     * @return
     * @throws Exception
     */
    @Override
    public Car getObject() throws Exception {Car car = new Car();
        String[] cardInfoArray = cardInfo.split(",");
        car.setBrand(cardInfoArray[0]);
        car.setMaxSpeed(Integer.parseInt(cardInfoArray[1]));
        car.setPrice(Double.parseDouble(cardInfoArray[2]));
        return car;
    }

    /**
     * 向 IOC 容器返回指定的类型
     * @return
     */
    @Override
    public Class<?> getObjectType() {return Car.class;}

    /**
     * 设置是否是单例模式
     * @return
     */
    @Override
    public boolean isSingleton() {return false;}
}

测试代码:

  private static void testFactoryBean() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        // 代表取 MyFactory
        MyFactory myFactory = applicationContext.getBean("&myFactory", MyFactory.class);
        // 不加 & 代表取工厂中放入的 bean
        Car car = applicationContext.getBean("myFactory", Car.class);
        System.out.println(myFactory);
        System.out.println(car);
    }

测试后果:

扫描包移植 -@ComponentScan

排除某些类

在配置类上加上:

@ComponentScan(basePackages = "org.example", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Service.class})})
  • basePackages: 默认扫描本包及其子包。
  • excludeFilters : 排除的类,要求两个属性,第一个是 type,能够了解为策略。

是一个枚举类型,值都在 FilterType 中,一共有六种:

  • ANNOTATION 过滤打上指定注解 (@Controller,@Component,@Repository,@Service) 上的类。
  • ASSIGNABLE_TYPE: 指定的类型,曾经写在配置类中的,比如说 Student 类,无奈排除。默认该当是先加载配置文件中的类,而后在依据扫描的包,扫描类,去将要退出到 IOC 容器的对象,退出到 IOC 容器中。
  • ASPECTJ 依照 Aspectj 的表达式
  • REGEX 依照正则表达式
  • CUSTOM 自定义规定

我建了一个类,打上了 Service 注解,当初咱们来测试下:

@Configuration
@ComponentScan(basePackages = "org.example", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Service.class})})
// @ComponentScan(basePackages = "org.example", excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {StudentDao.class})})
// @ComponentScan(basePackages = "org.example", includeFilters= {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {StudentDao.class})},useDefaultFilters = false)
// @ComponentScan(basePackages = "org.example", includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,value = {MyFilter.class})},useDefaultFilters = false)
public class SpringConfig {@Bean(name = "studentCard")
    public StudentCard studentCard(){return  new StudentCard(11,"22");
    }
    @Bean
    public Student student(@Qualifier("studentCard") StudentCard studentCard){return new Student(20,"zs",studentCard);
    }
    @Bean
    public StudentCard studentCard2(){return  new StudentCard(11,"22");
    }
}
private static void annotationTest() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        // 获取 IOC 容器中所有的 bean
        String[] beanNameArray = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanNameArray) {System.out.println(beanDefinitionName);
        }
    }

测试后果:

没有 service 了。
上面解除第二个 @ComponentScan 的正文,将第一个 @ComponentScan 的正文解除掉。再度测试:

会发现 StudentDao 没了。

只蕴含某些类

与排除指定的类是相似的,只不过 Spring 默认会加载子包上须要退出到 IOC 容器中的类,也就是说你想只蕴含的类在 basePackages 上面,那么这个蕴含就是有效的。所以咱们须要通过 useDefaultFilters 来禁止 Spring 的默认行为。
咱们正文掉其余的 @ComponentScan,只让第三个 @ComponentScan 解除正文。测试一下:

会发现打上 @Service 类的对象没了。

自定义规定排除或蕴含某些类

自定义规定要实现 TypeFilter,像上面这样:

public class MyFilter implements TypeFilter {
    // 返回 true 退出到 IOC 容器中
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 获取扫描的元数据类型
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 每扫到子包下的一个类,这个办法就会被调用。String className = classMetadata.getClassName();
        // 只有类名中蕴含 Student 的,我就退出到容器中
        if (className.contains("Student")){return true;}else{return false;}
    }
}

还是下面的配置类,咱们解除第四个 @ComponentScan 的正文,其余的全副正文。测试一下。

HomeStudentCondition 是我本人建的类,没加咱们介绍的 Spring 提供的注解,也加进来了。

条件注解

条件注解能够让某些对象在某些条件满足的状况下,才退出到 IOC 容器中 (等价于创立该对象),如果该条件不满足则该对象不创立,也就是不退出到对应的 IOC 容器中。那条件该怎么通知 Spring 框架呢? 也是通过一个类,这个类要求实现 Condition 接口。
顺带提一下 Spring Boot 很大水平上也依赖于条件注解。
首先两个 bean:

public class HomeStudent extends Student {
    /**
     * 出入证 无参和构造函数 get set 办法不再列出
     */
    private String pass;    
}
public class BoardStudent extends Student {
    /**
     * 宿舍号 无参和构造函数 get set 办法不再列出
     */
    private String dormitoryNumber;
}

而后筹备条件, 须要实现 Condition 接口:

public class HomeStudentCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 获取以后的环境, 个别开发环境分成三套: 开发、测试、生产。Environment environment = conditionContext.getEnvironment();
        // 从环境中获取学生类型
        String studentType = environment.getProperty("studentType");
        // 如果是住宿学生就退出到 IOC 容器中
        if ("HomeStudent".equals(studentType)){return true;}
        return false;
    }
}
public class BoardStudentCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Environment environment = context.getEnvironment();
        String studentType = environment.getProperty("studentType");
        if ("BoardStudent".equals(studentType)){return true;}
        return false;
    }
}

而后在对应的 bean 上加上条件变量:

@Configuration
@ComponentScan(basePackages = "org.example")
public class SpringConfig {
    @Bean
    @Conditional(HomeStudentCondition.class)
    public HomeStudent homeStudent() {return new HomeStudent("出入证");
    }
    @Bean
    @Conditional(BoardStudentCondition.class)
    public BoardStudent boardStudent() {return new BoardStudent("宿舍 200");
    }
}

在 IDEA 中配置环境。

测试代码:

 private static void annotationTest() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {System.out.println(beanName);
        }
    } 

测试后果:

会发现 boardStudent 没了。

bean 的作用域

  • singleton 单例(默认值),在每个 Spring IOC 容器中,一个 bean 仅对应一个对象实例
  • prototype 原型,每次从 IOC 容器中获取对应的对象的时候,都会返回一个新的对象实例。
  • request 一次 HTTP 申请中,一个 bean 定义对应一个实例,即每次 HTTP 申请会将有各自的 bean 实例,他们根据某个 bean 定义创立而成。仅在基于 WEB 的 Spring ApplicationContext 的状况才失效
  • session 在一个 HTTP Session 中,一个 bean 对应一个实例,仅在基于 WEB 的 Spring ApplicationContext 的状况才失效。
  • 在一个全局的的 HTTP Session 中,一个 bean 定义对应一个实例。典型状况下仅在应用 porlet(一个 Tomcat 容器)的时候无效。

仅在基于 WEB 的 Spring ApplicationContext 的状况才失效。
咱们次要罕用的是 singleton 和 prototype,上面咱们来测试一下:

public class SpringConfig {@Bean(name = "studentCard")
    public StudentCard studentCard(){return  new StudentCard(11,"22");
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Student student(@Qualifier("studentCard") StudentCard studentCard){return new Student(20,"zs",studentCard);
    }

    @Bean
    public StudentCard studentCard2(){return  new StudentCard(11,"22");
    }
}
  private static void annotationTest() {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        Student student1 = (Student) applicationContext.getBean("student");
        Student student2 = (Student) applicationContext.getBean("student");
        System.out.println(student1 == student2);
    }

后果:

ConfigurableBeanFactory 不是枚举类型,只是有两个常量字符串: singleton 和 prototype。你间接写这两个字符串中任意一个也行。
配置文件中进行测试:

<bean id="student" class="org.example.Student" scope="prototype">
        <property name = "name" >
            <value type="java.lang.String"><![CDATA[z<3]]></value>
        </property>
        <property name="age">
            <value>34</value>
        </property>
    </bean>
 private static void xmlTest() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student1 = (Student) applicationContext.getBean("student");
        Student student2 = (Student) applicationContext.getBean("student");
        System.out.println(student1 == student2);
    }

一点点细节的补充

在 singleton 作用域下,容器在启动的时候就会创立该对象,然而也反对懒加载,即在首次向容器中获取该对象的时候创立。
那么怎么告知 IOC 容器,在启动的时候,先不要创立呢? 通过 @Lazy 注解。如何测试呢? 你能够在对应对象的构造函数上,打断点或者输入点货色测试,也能够在启动的时候,打印 IOC 容器来所有对象的名字来打印。
这里是在对应对象的构造函数上输入一点货色来测试。

  • 配置类懒加载
@Configuration
public class SpringConfig {@Bean(name = "studentCard")
    public StudentCard studentCard(){return  new StudentCard(11,"22");
    }
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Student student(@Qualifier("studentCard") StudentCard studentCard){return new Student(20,"zs",studentCard);
    }
    @Bean
    @Lazy
    public StudentCard studentCard2(){return  new StudentCard(11,"22");
    }
}
  • 配置文件懒加载
  <bean id="student" class="org.example.Student" lazy-init="true">
        <property name="age">
            <value>34</value>
        </property>
    </bean>

这里测试后果就不再展现了。

主动拆卸的几种模式

下面咱们曾经介绍了,主动拆卸的两个注解了:

  • @Autowired 默认依据类型去查找,如果找不到,默认会报错。

@Autowired(required = false),就能够防止在容器中找不到对应类型时抛出谬误。

  • @Qualifier 依照名字进行拆卸

这里咱们再介绍几个:

  • @Primary

优先,能够配合 @Autowired 应用,假如容器里有两个 @Autowired 须要的对象,在拆卸时,被打上 @Primary 的对象,优先被选中

  • @Resource 并非来自 Spring,来自 Java 的 JSR250 提案,默认依照 bean 名进行匹配,如果没找到对应的 bean 名,则去匹配对应的类型,JDK 曾经自带。
  • @Inject 并非来自 Spring,来自于 Java 的 JSR330 提案。须要引入 jar 包。

Spring 帝国简介

从刚开始的 Spring framework,到当初 Spring 家族曾经有很多产品了:

前面咱们将会介绍 SpringMVC,接管 MVC 的 C 的一个框架。Java 畛域的问题,在 Spring 帝国,简直都能够找到解决方案(一个是计划整合(Spring Cloud),一个是本人提供(Spring MVC)。)

总结一下

最开始我是从视频开始学习 Spring 框架的,看视频也是最快学框架的形式,其实看视频的时候,心里还是有些疑难的,然而又找不到人去问。感觉视频中讲的有的时候很牵强,不成零碎,零零碎碎的。我不是很喜爱零零碎碎的知识点,我喜爱零碎一点的,于是就又零碎的整顿了一下本人对 Spring 的了解,也算是入门教程,也算是总结。心愿对各位学习 Spring 有所帮忙。

参考资料:

  • 《精通 Spring 4.x 企业应用开发实战》陈雄华 林开雄 文建国著
  • Spring 进阶
  • Spring 视频教程
  • springboot 高级实战课第一期

正文完
 0