您的位置:首页 > 编程语言 > Java开发

Spring 核心技术 IoC & DI 详解

antoniopeng 2021-01-13 22:34 48 查看 https://www.cnblogs.com/antoni

IoC 概念

IoC(Inversion of Control),中文叫做控制反转。这是一个概念,也是一种思想。控制反转,实际上就是指对一个对象的控制权的反转。如下代码:

public class Book {
private Integer id;
private String name;
private Double price;
//省略getter/setter
}

public class User {
private Integer id;
private String name;
private Integer age;

public void doSth() {
Book book = new Book();
book.setId(1);
book.setName("故事新编");
book.setPrice((double) 20);
}
}

在这种情况下,Book 对象的控制权在 User 对象里边,这样,Book 和 User 高度耦合,如果在其他对象中需要使用 Book 对象,得重新创建,也就是说,对象的创建、初始化、销毁等操作,统统都要开发者自己来完成。如果能够将这些操作交给容器来管理,开发者就可以极大的从对象的创建中解脱出来。

使用 Spring 之后,我们可以将对象的创建、初始化、销毁等操作交给 Spring 容器来管理。就是说,在项目启动时,所有的 Bean 都将自己注册到 Spring 容器中去(如果有必要的话),然后如果其他 Bean 需要使用到这个 Bean ,则不需要自己去 new,而是直接去 Spring 容器去要。

通过一个简单的例子看下这个过程。

IoC 初体验

首先创建一个普通的 Maven 项目,然后引入 spring-context 依赖,如下:

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
</dependencies>

XML 配置

接下来,在 resources 目录下创建一个

applicationContext.xml
配置文件(注意,一定要先添加依赖,后创建配置文件,否则创建配置文件时,没有模板选项),在这个文件中,我们可以配置所有需要注册到 Spring 容器的 Bean,此处配置一个 Book 对象:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean class="com.antoniopeng.hello.spring.Book" id="book"/>
</beans>

class 属性表示需要注册的 bean 的全路径,id 则表示 bean 的唯一标记,也开可以 name 属性作为 bean 的标记,在超过 99% 的情况下,id 和 name 其实是一样的,特殊情况下不一样。

接下来,加载这个配置文件,执行 main 方法,配置文件就会被自动加载,进而在 Spring 中初始化一个 Book 实例。此时,我们显式的指定 Book 类的无参构造方法,并在无参构造方法中打印日志,可以看到无参构造方法执行了,进而证明对象已经在 Spring 容器中初始化了,然后通过其 getBean 方法,可以从容器中去获取对象。

public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Book book = (Book) ctx.getBean("book");
System.out.println(book);
}
}

加载方式,除了ClassPathXmlApplicationContext 之外(去 classpath 下查找配置文件),另外也可以使用 FileSystemXmlApplicationContext ,FileSystemXmlApplicationContext 会从操作系统路径下去寻找配置文件。

public class Main {
public static void main(String[] args) {
FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext("F:\\workspace5\\workspace\\spring\\spring-ioc\\src\\main\\resources\\applicationContext.xml");
Book book = (Book) ctx.getBean("book");
System.out.println(book);
}
}

Bean 的获取

在上一小节中,我们通过 ctx.getBean 方法来从 Spring 容器中获取 Bean,传入的参数是 Bean 的 name 或者 id 属性。除了这种方式之外,也可以直接通过 Class 去获取一个 Bean。

public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Book book = ctx.getBean(Book.class);
System.out.println(book);
}
}

这种方式有一个很大的弊端,如果存在多个实例,这种方式就不可用,例如,xml 文件中存在两个 Bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean class="com.antoniopeng.hello.spring.Book" id="book"/>
<bean class="com.antoniopeng.hello.spring.Book" id="book2"/>
</beans>

此时,如果通过 Class 去查找 Bean,会报如下错误,所以,一般建议使用 name 或者 id 去获取 Bean 的实例。

Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.antoniopeng.hello.spring.Book' available: expected single matching bean but found 2: book,book2

属性的注入

在 XML 配置中,属性的注入(DI)存在多种方式。

构造方法注入

通过 Bean 的构造方法给 Bean 的属性注入值。

1. 第一步首先给 Bean 添加对应的构造方法

public class Book {
private Integer id;
private String name;
private Double price;

public Book() {
System.out.println("-------book init----------");
}

public Book(Integer id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
}

2. 在 xml 文件中注入 Bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean class="com.antoniopeng.hello.spring.Book" id="book">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="三国演义"/>
<constructor-arg index="2" value="30"/>
</bean>
</beans>

这里需要注意的是,

constructor-arg
中的 index 属性和 Book 中的构造方法参数一一对应。写的顺序可以颠倒,但是 index 的值和 value 要一一对应。

另一种构造方法中的属性注入,则是通过直接指定参数名来注入:

<bean class="com.antoniopeng.hello.spring.Book" id="book2">
<constructor-arg name="id" value="2"/>
<constructor-arg name="name" value="红楼梦"/>
<constructor-arg name="price" value="40"/>
</bean>

如果有多个构造方法,则会根据给出参数个数以及参数类型,自动匹配到对应的构造方法上,进而初始化一个对象。

Set 方法注入

除了构造方法之外,我们也可以通过 set 方法注入值。

<bean class="com.antoniopeng.hello.spring.Book" id="book3">
<property name="id" value="3"/>
<property name="name" value="水浒传"/>
<property name="price" value="30"/>
</bean>

Set 方法注入,有一个很重要的问题,就是属性名。很多人会有一种错觉,觉得属性名就是你定义的属性名,这个是不对的。在所有的框架中,凡是涉及到反射注入值的,属性名统统都不是 Bean 中定义的属性名,而是通过 Java 中的内省机制分析出来的属性名,简单说,就是根据 get/set 方法分析出来的属性名。

P 名称空间注入

P 名称空间注入,使用的比较少,它本质上也是调用了 Set 方法。

<bean class="com.antoniopeng.hello.spring.Book" id="book4" p:id="4" p:bookName="西游记" p:price="33"></bean>

外部 Bean 的注入

有时候,我们使用一些外部 Bean,这些 Bean 可能没有构造方法,而是通过 Builder 来构造的,这个时候,就无法使用上面的方式来给它注入值了。

例如在 OkHttp 的网络请求中,原生的写法如下:

public class OkHttpMain {
public static void main(String[] args) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.build();
Request request = new Request.Builder()
.get()
.url("http://b.hiphotos.baidu.com/image/h%3D300/sign=ad628627aacc7cd9e52d32d909032104/32fa828ba61ea8d3fcd2e9ce9e0a304e241f5803.jpg")
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
System.out.println(e.getMessage());
}

@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
FileOutputStream out = new FileOutputStream(new File("E:\\123.jpg"));
int len;
byte[] buf = new byte[1024];
InputStream is = response.body().byteStream();
while ((len = is.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.close();
is.close();
}
});
}
}

这个 Bean 有一个特点,OkHttpClient 和 Request 两个实例都不是直接 new 出来的,在调用 Builder 方法的过程中,都会给它配置一些默认的参数。这种情况,我们可以使用 静态工厂注入或者实例工厂注入来给 OkHttpClient 提供一个实例。

1. 静态工厂注入

首先提供一个 OkHttpClient 的静态工厂:

public class OkHttpUtils {
private static OkHttpClient OkHttpClient;
public static OkHttpClient getInstance() {
if (OkHttpClient == null) {
OkHttpClient = new OkHttpClient.Builder().build();
}
return OkHttpClient;
}
}

在 xml 文件中,配置该静态工厂:

<bean class="com.antoniopeng.hello.spring.OkHttpUtils" factory-method="getInstance" id="okHttpClient"></bean>

这个配置表示 OkHttpUtils 类中的 getInstance 是我们需要的实例,实例的名字就叫 okHttpClient。然后,在 Java 代码中,获取到这个实例,就可以直接使用了。

public class OkHttpMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OkHttpClient okHttpClient = ctx.getBean("okHttpClient", OkHttpClient.class);
Request request = new Request.Builder()
.get()
.url("http://b.hiphotos.baidu.com/image/h%3D300/sign=ad628627aacc7cd9e52d32d909032104/32fa828ba61ea8d3fcd2e9ce9e0a304e241f5803.jpg")
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
System.out.println(e.getMessage());
}

@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
FileOutputStream out = new FileOutputStream(new File("E:\\123.jpg"));
int len;
byte[] buf = new byte[1024];
InputStream is = response.body().byteStream();
while ((len = is.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.close();
is.close();
}
});
}
}

2. 实例工厂注入

实例工厂就是工厂方法是一个实例方法,这样,工厂类必须实例化之后才可以调用工厂方法。

这次的工厂类如下:

public class OkHttpUtils {
private OkHttpClient OkHttpClient;
public OkHttpClient getInstance() {
if (OkHttpClient == null) {
OkHttpClient = new OkHttpClient.Builder().build();
}
return OkHttpClient;
}
}

此时,在 xml 文件中,需要首先提供工厂方法的实例,然后才可以调用工厂方法:

<bean class="com.antoniopeng.hello.spring.OkHttpUtils" id="okHttpUtils"/>
<bean class="okhttp3.OkHttpClient" factory-bean="okHttpUtils" factory-method="getInstance" id="okHttpClient"></bean>

自己写的 Bean 一般不会使用这两种方式注入,但是,如果需要引入外部 jar,外部 jar 的类的初始化,有可能需要使用这两种方式。

复杂属性的注入

对象注入

<bean class="com.antoniopeng.hello.spring.User" id="user">
<property name="cat" ref="cat"/>
</bean>

<bean class="com.antoniopeng.hello.spring.Cat" id="cat">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
</bean>

可以通过 xml 注入对象,通过 ref 来引用一个对象。

数组注入

数组注入和集合注入在 xml 中的配置是一样的。如下:

<bean class="com.antoniopeng.hello.spring.User" id="user">
<property name="favorites">
<array>
<value>足球</value>
<value>篮球</value>
<value>乒乓球</value>
</array>
</property>
</bean>

注意,array 节点,也可以被 list 节点代替。当然,array 或者 list 节点中也可以是对象。

<bean class="com.antoniopeng.hello.spring.User" id="user">
<property name="cat" ref="cat"/>
<property name="cats">
<list>
<ref bean="cat"/>
<ref bean="cat2"/>
</list>
</property>
</bean>

<bean class="com.antoniopeng.hello.spring.Cat" id="cat">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
</bean>

<bean class="com.antoniopeng.hello.spring.Cat" id="cat2">
<property name="name" value="小黑"/>
<property name="color" value="黑色"/>
</bean>

注意,即可以通过 ref 使用外部定义好的 Bean,也可以直接在 list 或者 array 节点中定义 bean。

Map 注入

<property name="map">
<map>
<entry key="age" value="99"/>
<entry key="name" value="antoniopeng"/>
</map>
</property>

Properties 注入

<property name="info">
<props>
<prop key="age">99</prop>
<prop key="name">antoniopeng</prop>
</props>
</property>

Java 配置

在 Spring 中,想要将一个 Bean 注册到 Spring 容器中,整体上来说,有三种不同的方式。

  • XML 注入,如前文所说
  • Java 配置(通过 Java 代码将 Bean 注册到 Spring 容器中)
  • 自动化扫描

这里我们来看 Java 配置。Java 配置这种方式在 Spring Boot 出现之前,其实很少使用,自从有了 Spring Boot,Java 配置开发被广泛使用,因为在 Spring Boot 中,不使用一行 XML 配置。例如我有如下一个 Bean:

public class SayHello {
public String sayHello(String name) {
return "hello " + name;
}
}

在 Java 配置中,我们用一个 Java 配置类去代替之前的

applicationContext.xml
文件。

@Configuration
public class JavaConfig {
@Bean
SayHello sayHello() {
return new SayHello();
}
}

首先在配置类上有一个

@Configuration
注解,这个注解表示这个类不是一个普通类,而是一个配置类,它的作用相当于
applicationContext.xml
。 然后,定义方法,方法返回对象,方法上添加
@Bean
注解,表示将这个方法的返回值注入的 Spring 容器中去。也就是说,
@Bean
所对应的方法,就相当于
applicationContext.xml
中的 bean 节点。

既然是配置类,我们需要在项目启动时加载配置类。

public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
SayHello hello = ctx.getBean(SayHello.class);
System.out.println(hello.sayHello("antonio"));
}
}

注意,配置的加载,是使用

AnnotationConfigApplicationContext
来实现。

关于 Java 配置,这里有一个需要注意的问题:Bean 的名字是什么?

Bean 的默认名称是方法名。以上面的案例为例,Bean 的名字是 sayHello。如果想自定义方法名,也是可以的,直接在 @Bean 注解中进行过配置。如下配置表示修改 Bean 的名字为 antonio:

@Configuration
public class JavaConfig {
@Bean("antonio")
SayHello sayHello() {
return new SayHello();
}
}

自动化配置

在我们实际开发中,大量的使用自动配置。自动化配置既可以通过 Java 配置来实现,也可以通过 xml 配置来实现。

准备工作

例如我有一个 UserService,我希望在自动化扫描时,这个类能够自动注册到 Spring 容器中去,那么可以给该类添加一个

@Service
,作为一个标记。

@Service
注解功能类似的注解,一共有四个:

  • @Component
  • @Repository
  • @Service
  • @Controller

这四个中,另外三个都是基于

@Component
做出来的,而且从目前的源码来看,功能也是一致的,那么为什么要搞三个呢?主要是为了在不同的类上面添加时方便。

  • 在 Service 业务层上,添加注解时使用
    @Service
  • 在 Dao 数据持久层,添加注解时使用
    @Repository
  • 在 Controller 控制器层,添加注解时使用
    @Controller
  • 在其他组件上,添加注解时使用
    @Component
@Service
public class UserService {

}

添加完成后,自动化扫描有两种方式,一种就是通过 Java 代码配置自动化扫描,另一种则是通过 xml 文件来配置自动化扫描。

Java 代码配置自动扫描

@Configuration
@ComponentScan(basePackages = "com.anotniopeng.hello.spring.service")
public class JavaConfig {

public String helloSpring() {
return "Hello Spring";
}
}

然后,在项目启动中加载配置类,在配置类中,通过

@ComponentScan
注解指定要扫描的包(如果不指定,默认情况下扫描的是配置类所在的包下载的 Bean 以及配置类所在的包下的子包下的类),然后就可以获取 UserService 的实例了:

public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
UserService userService = ctx.getBean(UserService.class);
System.out.println(userService.helloSpring());
}
}

这里有几个问题需要注意:

  1. Bean 的名字叫什么?

    默认情况下,Bean 的名字是类名首字母小写。例如上面的 UserService,它的实例名,默认就是 userService。如果开发者想要自定义名字,就直接在 @Service 注解中添加即可。

  2. 有几种扫描方式?

    上面的配置,我们是按照包的位置来扫描的。也就是说,Bean 必须放在指定的扫描位置,否则,即使你有 @Service 注解,也扫描不到。除了按照包的位置来扫描,还有另外一种方式,就是根据注解来扫描。例如如下配置:

    @Configuration
    @ComponentScan(basePackages = "com.antoniopeng.hello.spring.javaconfig",useDefaultFilters = true,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)})
    public class JavaConfig {
    }

    这个配置表示扫描

    com.antoniopeng.hello.spring.javaconfig
    下的所有 Bean,但是除了 Controller。

XML 配置自动化扫描

<context:component-scan base-package="com.antoniopeng.hello.spring.javaconfig"/>

上面这行配置表示扫描

com.antoniopeng.hello.spring.javaconfig
下的所有 Bean。当然也可以按照类来扫描。XML 配置完成后,在 Java 代码中加载 XML 配置即可。

public class XMLTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = ctx.getBean(UserService.class);
System.out.println(userService.helloSpring());
}
}

也可以在 XML 配置中按照注解的类型进行扫描:

<context:component-scan base-package="com.antoniopeng.hello.spring.javaconfig" use-default-filters="true">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

对象注入

自动扫描时的对象注入有三种方式:

  1. @Autowired
  2. @Resources
  3. @Injected

@Autowired
是根据类型去查找,然后赋值,这就有一个要求,这个类型只可以有一个对象,否则就会报错。如果有多个对象还非要使用
@Autowired
,也是可以的,此时需要配合另外一个注解
@Qualifier
,在
@Qualifier
中可以指定变量名,两个一起用(
@Autowired
@Qualifier
)就可以实现通过变量名查找到变量。

@Resources
是根据名称去查找,默认情况下,定义的变量名,就是查找的名称,当然开发者也可以在
@Resources
注解中手动指定。所以,如果一个类存在多个实例,那么就应该使用
@Resources
去注入。

@Service
public class UserService {

@Autowired
UserDao userDao;

public String hello() {
return userDao.hello();
}

}

条件注解

条件注解就是在满足某一个条件的情况下,生效的配置。

使用示例

比如:在 Windows 中如何获取操作系统信息?Windows 中查看文件夹目录的命令是 dir,Linux 中查看文件夹目录的命令是 ls,我现在希望当系统运行在 Windows 上时,自动打印出 Windows 上的目录展示命令,Linux 运行时,则自动展示 Linux 上的目录展示命令。

1. 首先定义一个显示文件夹目录的接口

public interface ShowCmd {
String showCmd();
}

2. 分别实现 Windows 下的实例和 Linux 下的实例

public class WinShowCmd implements ShowCmd {

@Override
public String showCmd() {
return "dir";
}
}

public class LinuxShowCmd implements ShowCmd {

@Override
public String showCmd() {
return "ls";
}
}

3. 接下来,定义两个条件,一个是 Windows 下的条件,另一个是 Linux 下的条件

public class WindowsCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");
}
}

public class LinuxCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("linux");
}
}

4. 最后,在定义 Bean 的时候,就可以去配置条件注解了

@Configuration
public class JavaConfig {

@Bean("showCmd")
@Conditional(WindowsCondition.class)
ShowCmd winCmd() {
return new WinShowCmd();
}

@Bean("showCmd")
@Conditional(LinuxCondition.class)
ShowCmd linuxCmd() {
return new LinuxShowCmd();
}
}

注意:这里,一定要给两个 Bean 取相同的名字,这样在调用时,才可以自动匹配。然后,给每一个 Bean 加上条件注解,当条件中的 matches 方法返回 true 的时候,这个 Bean 的定义就会生效。

public class JavaMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
ShowCmd showCmd = (ShowCmd) ctx.getBean("showCmd");
System.out.println(showCmd.showCmd());
}
}

条件注解有一个非常典型的使用场景,就是多环境切换。

多环境切换

开发中,如何在 开发/生产/测试 环境之间进行快速切换?Spring 中提供了 Profile 来解决这个问题,Profile 的底层就是条件注解。这个从

@Profile
注解的定义就可以看出来:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();

}
class ProfileCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}

}

1. 首先我们定义一个 DataSource

public class DataSource {
private String url;
private String username;
private String password;

@Override
public String toString() {
return "DataSource{" +
"url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

2. 然后,在配置 Bean 时,通过

@Profile
注解指定不同的环境

@Bean("ds")
@Profile("dev")
DataSource devDataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/dev");
dataSource.setUsername("root");
dataSource.setPassword("123");
return dataSource;
}

@Bean("ds")
@Profile("prod")
DataSource prodDataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:mysql://192.158.222.33:3306/dev");
dataSource.setUsername("jkldasjfkl");
dataSource.setPassword("jfsdjflkajkld");
return dataSource;
}

3. 最后,再加载配置类,注意,需要先设置当前环境,然后再去加载配置类

public class JavaMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(JavaConfig.class);
ctx.refresh();
DataSource ds = (DataSource) ctx.getBean("ds");
System.out.println(ds);
}
}

这个是在 Java 代码中配置的环境的切换,也可以在 XML 文件中配置,如下配置在 XML 文件中,必须放在其他节点后面。

<beans profile="dev">
<bean class="org.javaboy.DataSource" id="dataSource">
<property name="url" value="jdbc:mysql:///devdb"/>
<property name="password" value="root"/>
<property name="username" value="root"/>
</bean>
</beans>

<beans profile="prod">
<bean class="org.javaboy.DataSource" id="dataSource">
<property name="url" value="jdbc:mysql://111.111.111.111/devdb"/>
<property name="password" value="jsdfaklfj789345fjsd"/>
<property name="username" value="root"/>
</bean>
</beans>

启动类中设置当前环境并加载配置:

public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.getEnvironment().setActiveProfiles("prod");
ctx.setConfigLocation("applicationContext.xml");
ctx.refresh();
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
}
}

其他

Bean 的作用域

在 XML 配置中注册的 Bean,或者用 Java 配置注册的 Bean,如果我多次获取,获取到的对象是否是同一个?

public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = ctx.getBean("user", User.class);
User user2 = ctx.getBean("user", User.class);
System.out.println(user == user2);
}
}

如上,从 Spring 容器中多次获取同一个 Bean,默认情况下,获取到的实际上是同一个实例。当然我们可以自己手动配置获取不同的实例:

<bean class="com.antoniopeng.hello.spring.User" id="user" scope="prototype" />

这是在 XML 中的配置,在 Java 代码中,我们可以通过

@Scope
注解指定 Bean 的作用域:

@Configuration
public class JavaConfig {
@Bean
@Scope("prototype")
SayHello sayHello() {
return new SayHello();
}
}

通过设置

scope
属性,我们可以调整默认的实例个数。
scope
的默认值为
singleton
,表示这个 Bean 在 Spring 容器中,是以单例的形式存在,如果
scope
的值为
prototype
,表示这个 Bean 在 Spring 容器中不是单例,多次获取将拿到多个不同的实例。

除了

singleton
prototype
之外,还有两个取值,
request
session
。这两个取值在 web 环境下有效。当然,在自动扫描配置中,也可以指定 Bean 的作用域。

@Repository
@Scope("prototype")
public class UserDao {

public String hello() {
return "userdao";
}
}

id 和 name 的区别

在 XML 配置中,我们可以看到,即可以通过 id 给 Bean 指定一个唯一标识符,也可以通过 name 来指定,大部分情况下这两个作用是一样的,有一个小小区别:

name 支持取多个。多个 name 之间,用 , 隔开:

<bean class="com.antoniopeng.hello.spring.User" name="user,user1" scope="prototype"/>

此时,通过 user、user1都可以获取到当前对象:

public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = ctx.getBean("user", User.class);
User user1 = ctx.getBean("user1", User.class);
System.out.println(user);
System.out.println(user1);
}
}

而 id 不支持有多个值。如果强行用 , 隔开,它还是一个值。

混合配置

混合配置就是 Java 配置+XML 配置。混用的话,可以在 Java 配置只通过

@ImportResource
注解可以导入一个 XML 配置。

@Configuration
@ImportResource("classpath:applicationContext.xml")
public class JavaConfig {

}

本文发于:https://antoniopeng.com

标签: