Spring深入浅出IoC(中)

简介: 基于注解的配置、基于Java的配置


基于注解的配置



bean.xml中配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample"/>
    <bean id="beanProvider" class="com.ajn.spring.beanfactory.bean.BeanProvider"/>
</beans>

配置只能在该配置所在容器的bean中寻找注解,如果你在DispatcherServlet中一个WebApplicationContext中配置,它只能寻找到Controller中的注解,不会找到Service中的注解。


3.1 @Autowired


@Autowired注解提供的功能和前面讲的自动注入(autowire)功能一样。重新修改两个类:

// 被注入的bean
public class BeanProvider {
    public void sayHello(String name, Integer age) {
        System.out.println("name = [" + name + "], age = [" + age + "]");
    }
}
// 通过属性注入
public class BeanExample {
    @Autowired
    private BeanProvider beanProvider;
    public void run() {
        String name = "ajn";
        Integer age = 24;
        this.beanProvider.sayHello(name, age);
    }
}
// 通过构造器方法注入
public class BeanExample {
    private BeanProvider beanProvider;
    @Autowired
    public BeanExample(BeanProvider beanProvider) {
        this.beanProvider = beanProvider;
    }
    public void run() {
        String name = "ajn";
        Integer age = 24;
        this.beanProvider.sayHello(name, age);

运行主方法类输出结果可知,beanProvider已经被注入了。@Autowired注解不仅能在字段上注入bean,还可以用其他方式:

  • 在构造器方法上使用
  • 在任何方法上使用,只要该方法参数中有被注入的bean
  • 在任何方法的参数前使用,该参数为被注入的bean

所以,总的来讲就是使用该注解可以把bean注入到另一个的bean的字段中,或方法的参数中,这些方法不局限于构造器方法或setter方法。

默认情况下自动注入的bean没有候选者,程序就会因为自动注入失败而报异常。当一个bean注入是否成功对程序没有什么影响,你可以在该注解上添加属性:@Autowired(required = false) 来避免报异常。


3.2 @Qualifier


前面说过通过类型自动注入时,当有多个候选者bean都符合注入的要求时,比如一个接口有多个实现类,这时Spring不知道会注入哪一个,这时可以在一个bean上的 标签上添加 primary="true" 属性来确定主要候选者(参考:2.1.3)。

这是一种解决方案,还有另一种方案就是在@Autowired注解后使用@Qualifier注解来指定候选者,也就是指定某个需要注入的bean。

可以指定bean的名称:

public class BeanExample {
    @Autowired
    @Qualifier("beanProvider")
    private BeanProvider beanProvider;
    // ...
}

可以自定义Qualifier名称:

<bean class="example.SimpleMovieCatalog">
    <qualifier value="main"/>
</bean>
public class MovieRecommender {
    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;
    // ...
}

使用@Qualifier可以更细粒度地选择bean的注入候选者,上面演示的是在XML中标签下配置子标签来配置名称,当基于注解配置bean时,可以在类上面使用该注解来配置qualifier名称,如下:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}


3.3 @Resource


@Resource注解作用和@Autowired作用类似,它是JDK自带的注解,只不过它默认是通过bean名称注入,也就是我们前面讲过的byName自动注入方式。

public class SimpleMovieLister {
    private MovieFinder movieFinder;
    @Resource(name="myMovieFinder")
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果不指定name属性,注解在字段上则会取字段名,注解在setter方法上则会取bean的属性名称:

public class SimpleMovieLister {
    private MovieFinder movieFinder;
    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

@Resource默认会通过名称注入bean,如果找不到对应名称的bean,才会通过类型注入,但一但指定name属性,则只会通过名称注入。@Autowired注解只能通过类型注入,要想实现通过名称注入,则要结合@Qualifier注解使用。

新版本的Spring中的 @Autowired 也支持字段名称注入。


3.4 @Value


在1.7节我们讲了使用外部属性值,使用@Value在基于注解的情况下注入属性值:

@Component
public class BeanExample {
    @Value("${name}")
    private String name;
    @Value("${age}")
    private Integer age;
    @Resource
    private BeanProvider beanProvider;
    public void run() {
        this.beanProvider.sayHello(this.name, this.age);
    }
}


3.5 @PostConstruct和@PreDestroy


在前面讲生命周期的时候说到了配置自定义初始化方法init-method和销毁方法destroy-method,在基于注解配置的情况下,我们使用@PostConstruct@PreDestroy来指定自定义初始化方法和销毁方法:

public class BeanExample {
    // ...
    @PostConstruct
    public void init() {
        System.out.println("BeanExample.init");
    }
    @PreDestroy
    public void destroy() {
        System.out.println("BeanExample.destroy");
    }
}


3.6 @Component


前面我们都是使用XML方式来配置元数据,定义一个bean的,即便前面讲了好多使用注解方式配置,但bean的定义依然是在XML文件中配置的,本节介绍通过注解指定并通过扫描类路径来检测候选组件。只需将bean.xml配置改写成:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd">
    <!--包名指定自己代码的包全路径-->
    <context:component-scan base-package="com.ajn.spring"/>
</beans>

包含了 的功能,所以我们只需配置一个。

修改类为:

import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class BeanProvider {
    public void sayHello(String name, Integer age) {
        System.out.println("name = [" + name + "], age = [" + age + "]");
    }
}
@Component
public class BeanExample {
    @Resource
    private BeanProvider beanProvider;
    public void run() {
        String name = "ajn";
        Integer age = 24;
        this.beanProvider.sayHello(name, age);
    }
}

Spring还提供了同样功能的注解@Repository@Service@Controller,它们用于区分分层开发模型中bean的类型,比如:@Repository用于数据持久层,它可以自动转换异常,@Service用于业务层,@Controller用于控制层。

使用这些注解创建的bean的名称默认是类名转化成小驼峰的形式,例如:BeanProvider的bean的名称是beanProvider,也可以自己指定bean的名称:

@Component("beanProvider")
public class BeanProvider {
    public void sayHello(String name, Integer age) {
        System.out.println("name = [" + name + "], age = [" + age + "]");
    }
}


3.7 @Scope


基于注解配置bean时也可以使用注解@Scope来指定作用域,例如使用原型模式:

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class BeanProvider {
    public void sayHello(String name, Integer age) {
        System.out.println("name = [" + name + "], age = [" + age + "]");
    }
}


基于Java的配置



4.1 @Configuration


这两个注解用于基于Java方式注册bean,下面是基于XML配置的bean:

<beans>
    <bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample"/>
    <bean id="beanProvider" class="com.ajn.spring.beanfactory.bean.BeanProvider"/>
</beans>


替换成基于Java配置的形式如下:


@Configuration
public class AppConfig {
    @Bean
    public BeanExample beanExample() {
        return new BeanExample();
    }
    @Bean
    public BeanProvider beanProvider() {
        return new BeanProvider();
    }
}


修改主方法类:

  1. 通过AnnotationConfigApplicationContext简单启动
public static void main(String[] args) {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    BeanExample beanExample = applicationContext.getBean(BeanExample.class);
    beanExample.run();
    applicationContext.close();
}

2、通过register(Class…)方法构建容器

public static void main(String[] args) {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.register(AppConfig.class);
    applicationContext.refresh();
    BeanExample beanExample = applicationContext.getBean(BeanExample.class);
    beanExample.run();
    applicationContext.close();
}


4.2 @Bean


前面说过@Bean注解可以用于基于Java定义bean,下面是该注解的详细用法:

  1. 参数自动注入,使用注解的方法可以有任意个参数,依赖的bean都会通过参数注入,比如:BeanExample依赖BeanProvider,可以通过如下方式使用参数自动依赖
@Configuration
public class AppConfig {
    @Bean
    public BeanExample beanExample(BeanProvider beanProvider) {
        BeanExample beanExample = new BeanExample();
        beanExample.setBeanProvider(beanProvider);
        return beanExample;
    }
    @Bean
    public BeanProvider beanProvider() {
        return new BeanProvider();
    }
}


  1. 添加属性initMethoddestroyMethod使用生命周期方法
@Configuration
public class AppConfig {
    @Bean(initMethod = "init",  = "destroy")
    public BeanExample beanExample() {
        return new BeanExample();
    }
}
// 在类中添加方法
public class BeanExample {
    // ...
    private void init() {
        System.out.println("BeanExample.init");
    }
    private void destroy() {
        System.out.println("BeanExample.destroy");
    }
}


  1. 默认情况下使用该注解所在的方法名作为bean的名称,添加属性name可以另行配置
@Configuration
public class AppConfig {
    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
}


  1. 可以结合@Profile@Scope@Lazy@DependsOn@Primary等注解使用
@Configuration
public class AppConfig {
    @Bean
    @DependsOn("beanProvider")
    @Scope(BeanDefinition.SCOPE_SINGLETON)
    @Primary
    @Profile("prod")
    @Lazy
    public BeanExample beanExample() {
        return new BeanExample();
    }
    @Bean
    public BeanProvider beanProvider() {
        return new BeanProvider();
    }
}


  • @Profile用来区分环境,我们后面会讲到,表示只有在某个环境下才实例化当前bean
  • @Scope可以用来配置单例模式还是原型模式(见1.5)
  • @Lazy表示懒加载(见1.6)
  • @DependsOn指定依赖关系(见1.4)
  • @Primary定义该bean为依赖的主要候选者(见1.3)


4.3 @ComponentScan


基于XML方式配置使用注解@Component@Repository@Service@Controller创建bean的配置如下:

<beans>
    <context:component-scan base-package="com.ajn.spring"/>
</beans>

修改为使用@ComponentScan注解配置:

@Configuration
@ComponentScan("com.ajn.spring")
public class AppConfig {
}

也可以在主方法中使用scan(String…)方法来实现这个功能:

public static void main(String[] args) {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.scan("com.ajn.spring");
    applicationContext.refresh();
    BeanExample beanExample = applicationContext.getBean(BeanExample.class);
    beanExample.run();
    applicationContext.close();
}


4.4 @Import


在基于XML配置时,我们使用标签来导入另一个bean.xml文件,在程序设计的时候,可以根据模块来定义不同的bean.xml文件。在使用基于Java配置时,可以使用@Import注解来导入另一个bean配置类:

@Configuration
public class ConfigA {
    @Bean
    public A a() {
        return new A();
    }
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
    @Bean
    public B b() {
        return new B();
    }
}

这样处理的话,就不需要在主方法里面实例化容器的时候配置两个类了,上面的例子只需要配置ConfigB就可以。


4.5 @Conditional


有时我们需要会有条件的启用或禁用@Configuration注解的类和@Bean注解的方法,基于一些系统状态而对bean是否注册的控制。此时可以使用@Conditional来完成这个功能,新建一个类实现Spring提供的Condition接口:

public class TestCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

@Configuration注解下或@Bean注解下使用@Conditional注解并使用前面的类:

@Configuration
@Conditional(TestCondition.class)
public class AppConfig {
    @Bean
    public BeanExample beanExample() {
        return new BeanExample();
    }
    @Bean
    public BeanProvider beanProvider() {
        return new BeanProvider();
    }
}

此时,matches()方法返回falseAppConfig类就不会生效,两个bean也不会被注册进容器,返回true时才能正常注册。


4.6 @ImportResource


在Spring项目中可以组合使用基于Java和基于XML的配置。


4.6.1 基于XML配置为主


基于XML配置的Spring容器要使用@Configuration注解,只需把声明该注解的类当作普通的bean即可,例如存在如下类:

@Configuration
public class AppConfig {
    @Autowired
    private DataSource dataSource;
    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}

要想使上面的配置类生效,只要system-test-config.xml在配置文件中声明bean:

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
    <bean class="com.acme.AppConfig"/>
    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

此时主方法加载XML配置:

public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

查看@Configuration源码,可以看出它使用了元注解@Component,所以我们可以使用,通过扫描配置类所在包来创建bean。将XML文件修改如下:

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>


4.6.2 基于Java配置为主


基于Java配置的Spring容器要使用XML文件配置,很简单,只需使用@ImportResource注解来导入XML文件,例如:

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

此时主方法加载注解启动:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
目录
相关文章
|
1月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
121 26
|
3月前
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
118 69
|
3月前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
67 21
|
3月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
3月前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
7月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
3月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
80 2
|
4月前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
6月前
|
XML Java 数据格式
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
Spring 第二节内容补充 关于Bean配置的更多内容和细节 万字详解!
368 18
|
6月前
|
XML Java 测试技术
spring复习01,IOC的思想和第一个spring程序helloWorld
Spring框架中IOC(控制反转)的思想和实现,通过一个简单的例子展示了如何通过IOC容器管理对象依赖,从而提高代码的灵活性和可维护性。
spring复习01,IOC的思想和第一个spring程序helloWorld