揭秘Spring依赖注入和SpEL表达式
来源 | OSCHINA 社区
作者 | 华为云开发者联盟-砖业洋_
原文链接:https://my.oschina.net/u/4526289/blog/10023664
1. setter 属性注入
1.1 使用 XML 进行 setter 方法注入
<bean id="userSetter" class="com.example.demo.bean.User">
<property name="username" value="example-username-setter"/>
<property name="age" value="25"/>
</bean>
@Bean
public User user() {
User user = new User();
user.setUsername("example-username-anno-setter");
user.setAge(25);
return user;
}
使用 XML 进行 setter 方法注入
public class User {
private String username;
private Integer age;
public User() {
}
// 为了节省篇幅,getter和setter方法省略......
@Override
public String toString() {
return "User{username='" + username + "', age=" + age + "}";
}
}
对于 XML 方式的 setter 注入和构造器注入,我们需要创建一个配置文件,比如叫 applicationContext.xml。
<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">
<!-- setter方法注入 -->
<bean id="userSetter" class="com.example.demo.bean.User">
<property name="username" value="example-username-setter"/>
<property name="age" value="25"/>
</bean>
</beans>
然后,我们需要创建一个 DemoApplication 类,使用 ApplicationContext 来加载配置文件并获取 Bean:
import com.example.demo.bean.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User userSetter = (User) context.getBean("userSetter");
System.out.println(userSetter);
}
}
使用 @Bean 注解进行 setter 方法注入
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public User userSetter() {
User user = new User();
user.setUsername("example-username-anno-setter");
user.setAge(25);
return user;
}
}
package com.example.demo;
import com.example.demo.bean.User;
import com.example.demo.configuration.AppConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
User userSetter = (User) context.getBean("userSetter");
System.out.println(userSetter);
}
}
2. 构造器注入
public User(String username, Integer age) {
this.username = username;
this.age = age;
}
2.1 使用 XML 进行构造器注入
<bean id="userConstructor" class="com.example.demo.bean.User">
<constructor-arg index="0" value="example-username-constructor"/>
<constructor-arg index="1" value="25"/>
</bean>
<bean id="userConstructor" class="com.example.demo.bean.User">
<constructor-arg name="username" value="example-username-constructor"/>
<constructor-arg name="age" value="25"/>
</bean>
2.2 使用 @Bean 注解进行构造器属性注入
@Bean
public User user() {
return new User("example-username-anno-constructor", 25);
}
使用 XML 进行构造器注入
public class User {
private String username;
private Integer age;
public User() {
}
public User(String username, Integer age) {
this.username = username;
this.age = age;
}
// 为了节省篇幅,getter和setter方法省略......
@Override
public String toString() {
return "User{username='" + username + "', age=" + age + "}";
}
}
对于 XML 方式的构造器注入,我们需要创建一个配置文件,比如叫 applicationContext.xml,这里保留 setter 注入方便大家对比
<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">
<!-- setter方法注入 -->
<!-- setter方法注入 -->
<!-- <bean id="userSetter" class="com.example.demo.bean.User">-->
<!-- <property name="username" value="example-username-setter"/>-->
<!-- <property name="age" value="25"/>-->
<!-- </bean>-->
<!-- 构造器注入 -->
<bean id="userConstructor" class="com.example.demo.bean.User">
<constructor-arg name="username" value="example-username-constructor"/>
<constructor-arg name="age" value="25"/>
</bean>
</beans>
然后,我们需要创建一个 DemoApplication 类,使用 ApplicationContext 来加载配置文件并获取 Bean:
package com.example.demo;
import com.example.demo.bean.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// User userSetter = (User) context.getBean("userSetter");
// System.out.println(userSetter);
User userConstructor = (User) context.getBean("userConstructor");
System.out.println(userConstructor);
}
}
使用 @Bean 注解进行构造器属性注入
import com.example.demo.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
// @Bean
// public User userSetter() {
// User user = new User();
// user.setUsername("example-username-anno-setter");
// user.setAge(25);
// return user;
// }
@Bean
public User userConstructor() {
return new User("example-username-anno-constructor", 25);
}
}
同样,我们需要创建一个 DemoApplication 类,使用 AnnotationConfigApplicationContext 来加载配置类并获取 Bean:
import com.example.demo.bean.User;
import com.example.demo.configuration.AppConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// User userSetter = (User) context.getBean("userSetter");
// System.out.println(userSetter);
User userConstructor = (User) context.getBean("userConstructor");
System.out.println(userConstructor);
}
}
3. 注解式属性注入
3.1 @Value 注解式属性注入的应用
@Component
public class White {
@Value("white-value-annotation")
private String title;
@Value("1")
private Integer rank;
@Override
public String toString() {
return "White{" + "title='" + title + '\'' + ", rank=" + rank + '}';
}
}
要实现注解式属性注入,我们可以直接在需要注入的字段上添加 @Value 注解:
@Value("white-value-annotation")
private String title;
@Value("1")
private Integer rank;
要注意的是,如果使用 @Value 注解来注入一个不存在的属性,那么应用程序会在启动时抛出异常。
然后,我们将通过组件扫描方式将这个 White 类扫描到 IOC 容器中,并将其取出并打印:
public class DemoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(White.class);
White white = ctx.getBean(White.class);
System.out.println("Injected value : " + white);
}
}
运行 main 方法会看到 White 的字段已经成功注入:
Injected value : White{
title='white-value-annotation', rank=1}
创建 Bean 和配置文件
blue.title=blue-value-properties
blue.rank=2
引入配置文件
@Configuration
@ComponentScan("com.example")
@PropertySource("classpath:blue.properties")
public class InjectValueConfiguration {
}
Spring Boot 的默认配置文件(application.properties 或 application.yml)。
通过 @PropertySource 注解加载的属性文件。
系统环境变量。
Java 系统属性(可以通过 -D 命令行参数设置)。
@Component
public class BlueConfig {
@Value("${blue.title}")
private String title;
@Value("${blue.rank}")
private int rank;
// getters and setters...
}
在 Spring 应用中使用 @PropertySource 注解来加载一个 .properties 文件时,这个文件中的所有配置项都会被读取,并存储在一个内部的 Map 结构中。这个 Map 的键是配置项的名称,值是配置项的值。Spring 中的一些内置配置项也会被添加到这个 Map 中。
当我们使用 ${...}`占位符语法来引用一个配置项时,`Spring`会查找这个`Map`,取出与占位符名称相应的配置项的值。例如有一个配置项`blue.title=blue-value-properties`,我们可以在代码中使用`${blue.title} 占位符来引用这个配置项的值。
如果想通过 Environment 类的方法来获取属性值,可以像下面这样做:
@Component
public class SomeComponent {
@Autowired
private Environment env;
public void someMethod() {
String title = env.getProperty("blue.title");
int rank = Integer.parseInt(env.getProperty("blue.rank"));
// ...
}
}
appTest:
name: MyApp
version: 1.0.0
可以使用 @ConfigurationProperties 来加载这些属性:
@Configuration
@ConfigurationProperties(prefix = "appTest")
public class AppConfig {
private String name;
private String version;
// getters and setters...
}
@Component
public class AppConfig {
@Value("${appTest.name}")
private String name;
@Value("${appTest.version}")
private String version;
// getters and setters...
}
Blue 类的属性注入
@Value("${blue.title}")
private String title;
@Value("${blue.rank}")
private Integer rank;
测试启动类
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(InjectValueConfiguration.class);
Blue blue = ctx.getBean(Blue.class);
System.out.println("Properties value : " + blue);
}
运行 main 方法会看到控制台已经成功打印出了配置文件的属性:
Properties value : Blue{
title='blue-value-properties', rank=2}
<?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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
xmlns:context="http://www.springframework.org/schema/context">
<!-- 相当于注解中的 @PropertySource("classpath:blue.properties") -->
<context:property-placeholder location="classpath:blue.properties"/>
<bean class="com.example.demo.bean.Blue">
<property name="title" value="${blue.title}"/>
<property name="rank" value="${blue.rank}"/>
</bean>
</beans>
@Value 注解式属性注入的应用
package com.example.demo.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class White {
@Value("white-value-annotation")
private String title;
@Value("1")
private Integer rank;
@Override
public String toString() {
return "White{" + "title='" + title + '\'' + ", rank=" + rank + '}';
}
}
创建启动类 InjectValueAnnotationApplication:
package com.example.demo;
import com.example.demo.bean.White;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(White.class);
White white = ctx.getBean(White.class);
System.out.println("Injected value : " + white);
}
}
引入外部配置文件 @PropertySource
package com.example.demo.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Blue {
@Value("${blue.title}")
private String title;
@Value("${blue.rank}")
private Integer rank;
@Override
public String toString() {
return "Blue{" + "title='" + title + '\'' + ", rank=" + rank + '}';
}
}
resources 目录下的 blue.properties 文件:
blue.title=blue-value-properties
blue.rank=2
创建配置类 InjectValueConfiguration:
package com.example.demo.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.example")
@PropertySource("classpath:blue.properties")
public class InjectValueConfiguration {
}
修改启动类,引入配置类:
package com.example.demo;
import com.example.demo.bean.Blue;
import com.example.demo.configuration.InjectValueConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(InjectValueConfiguration.class);
Blue blue = ctx.getBean(Blue.class);
System.out.println("Properties value : " + blue);
}
}
在 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"
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"
xmlns:context="http://www.springframework.org/schema/context">
<!-- 相当于注解中的 @PropertySource("classpath:blue.properties") -->
<context:property-placeholder location="classpath:blue.properties"/>
<bean class="com.example.demo.bean.Blue">
<property name="title" value="${blue.title}"/>
<property name="rank" value="${blue.rank}"/>
</bean>
</beans>
package com.example.demo.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Blue {
@Value("${blue.title}")
private String title;
@Value("${blue.rank}")
private Integer rank;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Integer getRank() {
return rank;
}
public void setRank(Integer rank) {
this.rank = rank;
}
@Override
public String toString() {
return "Blue{" + "title='" + title + '\'' + ", rank=" + rank + '}';
}
}
然后,我们需要修改启动类,使用 XmlApplicationContext 代替 AnnotationConfigApplicationContext:
package com.example.demo;
import com.example.demo.bean.Blue;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@ComponentScan("com.example")
public class DemoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:injectValueContext.xml");
Blue blue = ctx.getBean(Blue.class);
System.out.println("Properties value : " + blue);
}
}
4.1 使用 @Value 注解和 SpEL 表达式实现属性注入
@Component
public class Azure {
@Value("#{'spel-for-azure'}")
private String name;
@Value("#{10}")
private Integer priority;
}
我们修改启动类,从 IOC 容器中获取 Azure 并打印,可以看到属性被成功注入:
Azure{
name='spel-for-azure', priority=10}
SpEL 的功能远不止这些,它还可以获取 IOC 容器中其他 Bean 的属性,让我们来展示一下。
我们已经注册了 Azure Bean,现在我们再创建一个 Bean,命名为 Emerald。我们按照上述方法对字段和方法进行声明,然后使用 @Component 注解标注。
我们希望 name 属性直接复制 Azure 的 name 属性,而 priority 属性则希望比 Azure 的 priority 属性大 1,我们可以这样编写:
@Component
public class Emerald {
@Value("#{'copy of ' + azure.name}")
private String name;
@Value("#{azure.priority + 1}")
private Integer priority;
}
use spel bean property : Emerald{
name='copy of spel-for-azure', priority=11}
SpEL 表达式不仅可以引用对象的属性,还可以直接引用类的常量,以及调用对象的方法。下面我们通过示例进行演示。
我们新建一个 Bean,命名为 Ivory。我们按照上述方法初始化属性、toString () 方法、注解。
假设我们有一个需求,让 name 取 azure 属性的前 3 个字符,priority 取 Integer 的最大值。那么我们可以使用 SpEL 这样写:
@Component
public class Ivory {
@Value("#{azure.name.substring(0, 3)}")
private String name;
@Value("#{T(java.lang.Integer).MAX_VALUE}")
private Integer priority;
}
注意,直接引用类的属性,需要在类的全限定名外面使用 T () 包围。
我们修改启动类,测试运行,可以看到 Ivory 的属性已经是处理之后的值:
use spel methods : Ivory{
name='spe', priority=2147483647}
<bean id="ivory" class="com.example.demo.bean.Ivory">
<property name="name" value="#{azure.name.substring(0, 3)}" />
<property name="priority" value="#{T(java.lang.Integer).MAX_VALUE}" />
</bean>
使用 @Value 注解和 SpEL 表达式实现属性注入
package com.example.demo.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Azure {
@Value("#{'spel-for-azure'}")
private String name;
@Value("#{10}")
private Integer priority;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
@Override
public String toString() {
return "Azure{" +
"name='" + name + '\'' +
", priority=" + priority +
'}';
}
}
package com.example.demo.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Emerald {
@Value("#{'copy of ' + azure.name}")
private String name;
@Value("#{azure.priority + 1}")
private Integer priority;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
@Override
public String toString() {
return "Emerald{" +
"name='" + name + '\'' +
", priority=" + priority +
'}';
}
}
Ivory.java:
package com.example.demo.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Ivory {
@Value("#{azure.name.substring(0, 3)}")
private String name;
@Value("#{T(java.lang.Integer).MAX_VALUE}")
private Integer priority;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
@Override
public String toString() {
return "Ivory{" +
"name='" + name + '\'' +
", priority=" + priority +
'}';
}
}
MyBean.java
@Component
public class MyBean {
@Autowired
private Azure azure;
@Autowired
private Emerald emerald;
@Autowired
private Ivory ivory;
public void init() {
System.out.println(azure);
System.out.println(emerald);
System.out.println(ivory);
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
MyBean myBean = applicationContext.getBean(MyBean.class);
myBean.init();
}
}
在 XML 中使用 SpEL 表达式实现属性注入
<?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.example" />
<bean id="azure" class="com.example.demo.bean.Azure">
<property name="name" value="#{
'spel-for-azure'}" />
<property name="priority" value="#{10}" />
</bean>
<bean id="emerald" class="com.example.demo.bean.Emerald">
<property name="name" value="#{
'copy of ' + azure.name}" />
<property name="priority" value="#{azure.priority + 1}" />
</bean>
<bean id="ivory" class="com.example.demo.bean.Ivory">
<property name="name" value="#{azure.name.substring(0, 3)}" />
<property name="priority" value="#{T(java.lang.Integer).MAX_VALUE}" />
</bean>
</beans>
package com.example.demo.bean;
public class Azure {
private String name;
private Integer priority;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
@Override
public String toString() {
return "Azure{" +
"name='" + name + '\'' +
", priority=" + priority +
'}';
}
}
package com.example.demo.bean;
public class Emerald {
private String name;
private Integer priority;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
@Override
public String toString() {
return "Emerald{" +
"name='" + name + '\'' +
", priority=" + priority +
'}';
}
}
Ivory.java
package com.example.demo.bean;
public class Ivory {
private String name;
private Integer priority;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
@Override
public String toString() {
return "Ivory{" +
"name='" + name + '\'' +
", priority=" + priority +
'}';
}
}
package com.example.demo;
import com.example.demo.bean.MyBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ImportResource;
@SpringBootApplication
@ImportResource("classpath:app-config.xml")
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
MyBean myBean = applicationContext.getBean(MyBean.class);
myBean.init();
}
}
END
点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦
微信扫码关注该文公众号作者