【Spring学习笔记】7:装配bean的三种方式(自动装配,JavaConfig,XML),配置导入

版权声明:本文为博主原创学习笔记,如需转载请注明来源。 https://blog.csdn.net/SHU15121856/article/details/81779232

跟着《Spring实战》彻底系统地学习一下Spring。

对Spring的新认识

Spring的关键

  • 基于POJO的轻量级和最小侵入性编程。
  • 通过DI和面向接口实现松耦合
  • 基于切面和惯例进行声明式编程。
  • 通过切面和模板减少样板式代码

所谓侵入性,就是指很多框架要求使用者继承它们的类或实现它们的接口,这样应用程序就和框架绑定死了,Spring的侵入性很小。

注意,DI或者说IoC绝不是Spring框架的特权,Java就有一个JDI规范,很多有DI框架的功能都可以去实现它。

在单元测试时,可以直接用mock框架(如Mockito)去创建接口的mock实现,注入进去做测试。

Spring容器

Spring容器不止一个,Spring自带了多个容器实现,常见的有bean工厂

org.springframework.beans.factory.BeanFactory

该接口是最简单的容器,提供基本的DI支持。

最广泛使用的是应用上下文

org.springframework.context.ApplicationContext

它基于bean工厂,提供了应用框架级别的服务。

ApplicationContext的几种实现

不同的实现,主要区别在于装载应用上下文的渠道不同。

  • AnnotationConfigApplicationContext:从基于注解的配置类中加载上下文。
  • AnnotationConfigWebApplicationContext:从基于注解的配置类中加载Spring Web上下文。
  • ClassPathXmlApplicationContext:从类加载路径下的xml配置文件中加载上下文。
  • FileSystemXmlApplicationContext:从文件系统中的xml配置文件加载上下文。
  • XmlWebApplicationContext:从web应用下的xml配置文件中加载Spring Web上下文。

其中第二个和第六个是和Spring的Web应用有关的,不在Spring核心容器的范畴内。

装配bean的三种方式

创建应用对象之间协作关系的行为称为装配(wiring),在类中表现为组合,所以依赖的装配也就是DI的目的。

[1]自动装配

关键:组件扫描、自动装配。

标识要被创建bean的类

给类加上@Component注解,表示该类是一个组件类,Spring会为这个类创建bean,并将创建的bean(对象)纳入Spring容器管理。

@Component注解可以被JDI规范的@Named注解代替,他们功能基本一样。

组件类生成的bean默认id是类名第一个字母小写,可以在注解中显式指明id:

@Component(value = "lzh")
public class SbLzh {
    //...
}
启用组件扫描
[1]JavaConfig方式

使用@Configuration注解,表示该类是一个配置类;再使用@ComponentScan注解,默认扫描与该配置类相同的包及其子包,以发现组件类。

扫描某个包及其子包,将这”某个包”称为基础包

如果想扫描其它包而不是默认的所在包,或者想指定多个扫描的基础包,可以在@ComponentScan注解上指定:

@ComponentScan(basePackages = {"org.lzh", "org.sb"})

这种方式是类型不安全的,因为这些基础包是用String指定的,没法在编译期检查其是否正确(存在)。可以用这种方式:

@ComponentScan(basePackageClasses = {LzhConfig.class, SbConfig.class})

这里所指定的类或者接口所在的包将作为扫描的基础包。不过程序中的类经常可能会变的,所以不妨额外设置空标记接口,仅仅用来标识所在包:

@ComponentScan(basePackageClasses = {LzhMark.class, SbMark.class})
[2]XML方式

在xml文件中添加context命名空间,使用:

<context:component-scan base-package="org.lzh"/>

开启组件扫描,并指明要在哪个包及其子包下扫描组件类。

为bean添加注解实现自动装配

使用Spring的@Autowired或者JDI的@Inject注解,添加到属性上时,不必提供setter方法就会按类型寻找合适的bean自动注入;添加到方法上时,Spring都会尝试满足方法参数上所声明的依赖,不论是什么方法。

当没有找到合适的bean来注入时,会抛出异常,可以使用:

@Autowired(required = false)

说明这个自动注入不是必须的,如果找不到就算了(null)。

[2]通过JavaConfig装配

JavaConfig是配置代码,不应该侵入到业务逻辑代码中,通常将JavaConfig放到单独的包中。使用@Configuration就创建了一个配置类。

声明bean的方法

在方法上使用@Bean注解来声明一个bean:

//@Configuration指明是一个JavaConfig配置类
@Configuration
public class LzhConfig {

    //@Bean修饰的方法用来声明一个bean
    @Bean
    //返回值使用了抽象接口BookService(面向抽象),也可以面向具体
    //方法名bkSrvc将成为bean的id
    //方法内将对bean进行组装,最后返回一个具体的实现类对象即组装好的bean
    public BookService bkSrvc() {
        BookService bookService = new BookServiceImp();
        //组装这个bean...
        //返回组装好的bean对象
        return bookService;
    }
}
组装bean的细节

前面那个例子里的bean什么依赖都没有,如果一个bean依赖另一个bean,在装配时最简单的方式就是引用配置类中创建bean的方法,如:

@Configuration
public class LzhConfig {

    @Bean
    public Book book() {
        return new Book();
    }

    @Bean
    public BookService bkSrvc() {
        //引用配置类中创建bean的方法
        BookService bookService = new BookServiceImp(book());
        return bookService;
    }
}

注意这种方式和直接new一个传进去的区别!Spring中的bean默认都是单例的,对于添加了@Bean注解的方法,Spring会拦截所有对它的调用,并确保直接返回该方法所创建的单例bean,而不是每次都真的调用它!


另一种更好的方式是,从@Bean方法的参数传入bean:

@Bean
public BookService bkSrvc(Book bk) {
    BookService bookService = new BookServiceImp(bk);
        return bookService;
}

这种方式既能保证单例bean,又不要求该bean的声明同在该配置类中。这个bean可以通过组件扫描自动发现,也可以通过XML来配置,也可以声明在任何一个配置类中。

JavaConfig往往是最好的选择

JavaConfig是装配bean的最灵活的方式,因为它本质就是一个Java类,只受到Java语言的限制。Java毕竟是图灵完备的,所以这种方式几乎没有功能上的限制。

通过XML装配

这种方式已经用很多次了,只记录一些新学到的知识。

bean的id

<bean>元素声明一个bean,如果没有给出id,那么默认的id就是类名#计数,如:

<bean class="org.lzh.model.Book"/>
<bean class="org.lzh.model.Book"/>

相当于:

<bean class="org.lzh.model.Book" id="org.lzh.model.Book#0"/>
<bean class="org.lzh.model.Book" id="org.lzh.model.Book#1"/>

在使用时为了简洁,尽量只对那些需要将它ref注入到其它bean中的bean明确指明id。

使用合适的IDE

XML配置的一大缺点就是,像class这种属性也只能用字符串,所以XML配置不能从编译期的类型检查中受益,可以用能够感知Spring功能的IDE(如STS和IDEA)来解决这个问题(编码时就能检查出来)。

c-命名空间

Spring的c-命名空间可在一定程度上代替<constructor-arg>为构造器注入bean,如:

<bean class="org.lzh.service.imp.BookServiceImp">
    <constructor-arg ref="org.lzh.model.Book#0"/>
</bean>

可以替换为:

<bean class="org.lzh.service.imp.BookServiceImp" c:book-ref="org.lzh.model.Book#0"/>

其中,c:后跟的book是构造器参数的名称,-ref表示是注入引用而不是字面量。如果不想使用构造器参数名称,也可以使用参数的索引,即c:_索引号配合-ref

<bean class="org.lzh.service.imp.BookServiceImp" c:_0-ref="org.lzh.model.Book#0"/>
装配集合类型

当要把集合装配到构造器参数中时,c-命名空间做不了,只能用<constructor-arg>元素。

注意<set><list>用来装配集合时没什么区别,都可以用来装配List、Set和数组。区别主要是在装配到哪种集合类型里,如果是Set那么会忽略重复值。

<set><list>使用子元素<ref bean="..."/>来实现引用集;使用<value>...</value>(里面的东西不需要双引号)来实现字面量集。

p-命名空间

Spring的p-命名空间可在一定程度上代替<property>使用setter注入bean,如:

<bean class="org.lzh.service.imp.BookServiceImp">
    <property name="book" ref="org.lzh.model.Book#0"/>
</bean>

可以替换为:

<bean class="org.lzh.service.imp.BookServiceImp" p:book-ref="org.lzh.model.Book#0"/>

当注入字面量时同样是把-ref去掉就行。

配置导入

在前面学习使用参数注入到@Bean修饰的方法中实现注入时,虽然不要求该bean的声明同在该配置类中,但只要不是自动发现bean,就需要手动导入配置,才能使用导入的配置中所配置的bean。

因此,两种手动配置的方式——JavaConfig和XML之间必定存在相互导入的方式,下面逐个列举。

JavaConfig中导入JavaConfig

使用@Import注解:

@Configuration
@Import(value = {SbConfig.class, CatConfig.class})
public class LzhConfig {
    @Bean
    public BookService bkSrvc(Book bk) { //这里使用SbConfig中配置的Book类的bean
        BookService bookService = new BookServiceImp(bk);
        return bookService;
    }
}

JavaConfig中导入XML

使用@ImportResource注解:

@Configuration
@ImportResource(value = {"classpath:CatContext.xml", "classpath:SbContext.xml"})
public class LzhConfig {
    @Bean
    public BookService bkSrvc(Book bk) { //这里使用CatContext.xml中配置的Book类的bean
        BookService bookService = new BookServiceImp(bk);
        return bookService;
    }
}

XML中导入XML

使用<import>元素:

<import resource="classpath:CatContext.xml"/>

XML中导入JavaConfig

使用<bean>元素:

<bean class="org.cat.CatConfig"/>

不管使用哪种手动配置方式,不管如何导来导去,通常都应创建一个根配置,导入所有的JavaConfig和XML,在之前实习的时候就发现公司系统里也都是这样做的。

猜你喜欢

转载自blog.csdn.net/SHU15121856/article/details/81779232