在Spring-IOC基础中我们学习了核心的bean装配技术,但是bean装配所涉及的领域并不仅仅局限于Spring-IOC基础中我们学习的内容,Spring提供了多种技巧,借助他们可以实现更为高级的bean装配功能
环境与profile
Spring提供了profile解决多个环境之间的切换问题
-
JavaConfig中使用profile
在Java配置中可以使用@Profile注解指定某个bean属于哪个profile,@Profile注解源码如下:
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Profiles;
@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();
}
为了测试效果,我们可以定义一个DataSource类如下:
package com.tp.datasource;
/**
* FileName: DataSource
* Author: TP
* Description:
*/
public class DataSource {
public DataSource() {
System.out.println("无参构造:DataSource被实例化");
}
private String userName;
private String passWord;
private String 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;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "DataSource{" +
"userName='" + userName + '\'' +
", passWord='" + passWord + '\'' +
", url='" + url + '\'' +
'}';
}
}
通过JavaConfig声明这个bean:
@Bean("dataSource")
@Profile("dev")
DataSource devDataSource(){
DataSource dataSource = new DataSource();
dataSource.setUserName("root");
dataSource.setUserName("tp123456");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev");
return dataSource;
}
@Bean("dataSource")
@Profile("test")
DataSource testDataSource(){
DataSource dataSource = new DataSource();
dataSource.setUserName("root");
dataSource.setUserName("tp123456");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_test");
return dataSource;
}
@Bean("dataSource")
@Profile("prod")
DataSource prodDataSource(){
DataSource dataSource = new DataSource();
dataSource.setUserName("root");
dataSource.setUserName("tp123456");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_prod");
return dataSource;
}
如上我们分别为dev、test、prod环境设置了对应的数据源配置,测试类如下:
public class ProfileTestMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.getEnvironment().setActiveProfiles("dev");
System.out.println("=============================");
ctx.refresh();
Object dataSource = ctx.getBean("dataSource");
System.out.println("=============================");
System.out.println(dataSource);
}
}
测试结果:
=============================
无参构造:DataSource被实例化
=============================
DataSource{userName='tp123456', passWord='null', url='jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev'}
Process finished with exit code 0
由此可以看出,dev环境的DataSource被实例化了
-
Spring XML中使用profile
在Spring xml中使用profile你可以选择为每个环境设置一个单独的xml配置文件,在xml的<beans> 标签下指定profile属性,类似这样:
<?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" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
profile="test">
<!--针对test环境的配置信息-->
<!--...-->
</beans>
这样配置下来可能配置文件从命名看来显得更明确,但是引发的问题是重复定义会很多。
另外一种方式是在<bean></beans>内部再添加<bean profile="dev"></beans>,这样你就可以不必为每个环境单独创建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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="course" class="com.tp.beans.Course">
<property name="name" value="Java"/>
<property name="durations" value="24"/>
<property name="id" value="222"/>
</bean>
<!--测试多环境-->
<beans profile="dev">
<bean id="dataSource"
class="com.tp.datasource.DataSource"
p:userName="root"
p:passWord="tp123456"
p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev"/>
</beans>
<beans profile="test">
<bean id="dataSource"
class="com.tp.datasource.DataSource"
p:userName="root"
p:passWord="tp123456"
p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_test"/>
</beans>
<beans profile="prod">
<bean id="dataSource"
class="com.tp.datasource.DataSource"
p:userName="root"
p:passWord="tp123456"
p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_prod"/>
</beans>
</beans>
再次运行上面的测试类,效果是一致的。
条件化的Bean
假设你希望一个bean或者多个bean在某种特定条件下才被创建,例如应用的类路径下包含特定的库才创建,或者某个bean在另一个bean已经存在时才被创建,在Spring4之前很难实现这种级别的条件化配置,Spring4之后引入了@Conditional注解,它可以用到带有@Bean注解的方法上,如果给定的条件计算为true,那么Spring就会为我们创建这个bean,否则这个bean会被忽略。
@Conditional可以给定一个任意实现了org.springframework.context.annotation.Condition
的类,Condition的源码如下
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
这个接口只有一个matches()方法,返回一个boolean类型表示是否满足条件。
为了测试条件化的bean,我们定义3个类实现Condition接口,用来判断当前系统属于哪种操作系统,并为不同的操作系统创建不同的bean:
package com.tp.conditon;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* FileName: LinuxCondition
* Author: TP
* Description:
*/
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("linux");
}
}
package com.tp.conditon;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* FileName: MacCondition
* Author: TP
* Description:
*/
public class MacCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("mac os x");
}
}
package com.tp.conditon;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* FileName: WindowsCondition
* Author: TP
* Description:
*/
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");
}
}
我们有个类Cat如下:
package com.tp.beans.condition;
/**
* FileName: Cat
* Author: TP
* Description:用于条件注解的bean
*/
public class Cat {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
在JavaConfig声明bean如下:
@Bean("cat")
@Conditional(MacCondition.class)
Cat macCat(){
Cat cat = new Cat();
cat.setName("Mac Cat");
return cat;
}
@Bean("cat")
@Conditional(WindowsCondition.class)
Cat windowsCat(){
Cat cat = new Cat();
cat.setName("Windows Cat");
return cat;
}
@Bean("cat")
@Conditional(LinuxCondition.class)
Cat linuxCat(){
Cat cat = new Cat();
cat.setName("Linux Cat");
return cat;
}
测试:
package com.tp.test;
import com.tp.beans.condition.Cat;
import org.junit.Test;
/**
* FileName: OnConditionalBeanStatement
* Author: TP
* Description:基于特定条件的JavaBean声明测试
*/
public class OnConditionalBeanStatementTest extends BaseTestCase{
@Test
public void conditionalBeanTest(){
Cat cat = (Cat) applicationContext.getBean("cat");
System.out.println(">>>基于JavaConfig的JavaBean声明:" + cat);
}
}
运行结果:
由于本人的操作系统是Mac,所以从控制台可以看出为我们创建类Mac对应的bean。
Condition接口的matches()方法会得到ConditionContext和
AnnotatedTypeMetadata这两个对象,我们可以通过这两个对象用来做决策,其中ConditionContext是一个接口,源码如下:
public interface ConditionContext {
/**
* Return the {@link BeanDefinitionRegistry} that will hold the bean definition
* should the condition match.
* @throws IllegalStateException if no registry is available (which is unusual:
* only the case with a plain {@link ClassPathScanningCandidateComponentProvider})
*/
BeanDefinitionRegistry getRegistry();
/**
* Return the {@link ConfigurableListableBeanFactory} that will hold the bean
* definition should the condition match, or {@code null} if the bean factory is
* not available (or not downcastable to {@code ConfigurableListableBeanFactory}).
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
/**
* Return the {@link Environment} for which the current application is running.
*/
Environment getEnvironment();
/**
* Return the {@link ResourceLoader} currently being used.
*/
ResourceLoader getResourceLoader();
/**
* Return the {@link ClassLoader} that should be used to load additional classes
* (only {@code null} if even the system ClassLoader isn't accessible).
* @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
*/
@Nullable
ClassLoader getClassLoader();
}
通过ConditionContext,我们可以做到如下几点:
- 借助getRegistry()方法返回的BeanDefinitionRegistry检查bean定义
- 借助getBeanFactory()方法返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性
- 借助getEnvironment()方法返回的Environment检查当前应用所在的运行环境信息检查环境变量是否存在
- 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源
- 借助getClassLoader()返回的ClassLoader加载并检查类是否存在
AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上个还有什么其他的注解,像AnnotatedTypeMetadata一样AnnotatedTypeMetadata也是一个接口:
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String var1);
@Nullable
Map<String, Object> getAnnotationAttributes(String var1);
@Nullable
Map<String, Object> getAnnotationAttributes(String var1, boolean var2);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String var1);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
}
- 借助isAnnotated()方法我们可以判断带有@Bean注解的方法是不是还有其他特定的注解
- 借助其他的那些方法我们能够检查@Bean注解的方法上其他注解的属性。
其实前面介绍的@Profile也是基于@Conditional实现的
处理自动装配的歧义性
在IOC基础的学习中我们知道Spring的自动装配能够为我们减少显式配置的数量,不过仅有一个bean匹配所需的结果时,自动装配才是有效的,如果有不止一个bean能够匹配结果的话,这种歧义性会阻碍Sprig自动装配属性、构造器参数、方法参数。
歧义性例子:
为了演示这种歧义性,我们新建一个类AnimalServiceImpl2,同AnimalServiceImpl一样,它也实现了上节课的AnimalService接口
具体如下:
@Service
public class AnimalServiceImpl2 implements AnimalService {
@Override
public void eat() {
System.out.println("AnimalServiceImpl2:动物在吃饭....");
}
}
我们的User类中引入了AnimalService接口:
@Component("componentUser")
public class User {
public User() {
System.out.println("无参构造:User被实例化");
}
// 构造器注入
// @Autowired
public User(Book book) {
System.out.println("带参构造:User被实例化");
this.book = book;
}
public User(String name, String[] favorites) {
System.out.println("带参构造:User被实例化");
this.name = name;
this.favorites = favorites;
}
private Integer id;
private String name;
private Integer age;
private String[] favorites;
private Book book;
// 接口注入
@Autowired
private AnimalService animalService;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String[] getFavorites() {
return favorites;
}
public void setFavorites(String[] favorites) {
this.favorites = favorites;
}
public Book getBook() {
return book;
}
// Setter注入
// @Autowired
public void setBook(Book book) {
this.book = book;
}
// 非Setter注入
// @Autowired
public void injectBook(Book book) {
this.book = book;
}
public AnimalService getAnimalService() {
return animalService;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", favorites=" + Arrays.toString(favorites) +
", book=" + book +
'}';
}
}
这个时候我们再运行一次之前正常的测试类:
/**
* 打开User成员变量上的@Autowired注解,演示属性注入
*/
@Test
public void simpleComponentScanInterfaceInject() {
User user = (User) applicationContext.getBean("componentUser");
System.out.println(">>>基于注解扫描的JavaBean声明:" + user);
System.out.println(">>>基于注解扫描的JavaBean声明,注入的book:" + user.getBook());
System.out.println(user.getAnimalService());
if(null != user.getAnimalService()){
user.getAnimalService().eat();
}
}
发现报错 :
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.tp.service.AnimalService' available: expected single matching bean but found 2: animalServiceImpl,animalServiceImpl2
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1229)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1171)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593)
... 42 more
大概意思是期望只有一个匹配的bean,但是Spring在容器中找到了2个符合条件的bean,因此报错。
发生这个问题的原因是,@Autowired注解是按照类型注入,因为AnimalServiceImpl和AnimalServiceImpl2都实现了AnimalService这个类,所以Spring不知道用哪个好,解决的办法有3种:
设置首选Bean(不推荐)
我们可以利用@Primary
注解将其中一个bean设置为首选的bean:
@Service
@Primary
public class AnimalServiceImpl2 implements AnimalService {
@Override
public void eat() {
System.out.println("AnimalServiceImpl2:动物在吃饭....");
}
}
这个时候再次运行测试类,效果如下:
但是这种方式是不推荐的,@Primary只能设置优先,但是无法保证唯一,因为可能多个bean中有不止一个bean设置了@Primary,那样的话程序还是会发生错误
使用@Qualifier配合@Autowired
@Autowired
@Qualifier("animalServiceImpl2")
private AnimalService animalService;
@Qualifier注解是使用限定符的主要方式,它可以与@Autowired和@Inject注解协同使用,@Qualifier注解的参数就是想要注入的bean的ID
在JavaConfig中,@Qualifier也可以与@Bean一起使用
再次运行测试类:
使用@Resource注解
@Resource有2个参数:name、type,它默认按照名称进行装配
@Resource装配顺序
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
- 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
- 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配
修改User.java:
@Resource(name = "animalServiceImpl2")
private AnimalService animalService;
运行测试类:
bean的作用域
默认情况下,Spring上下文中的beaen都是以单例(singleton)的形式创建的,也就是说不管给定的一个bean被注入到其他bean多少次,每次注入的都是同一个实例。
但是在某些情况下你使用的类是易变的,他们会保持一些状态,因此重用是不安全的,这种情况下将class声明为单例bean就不是什么好主意了,因为对象会被污染,稍后重用的时候会出现意想不到的问题,Spring针对了多种作用域,可以基于这些作用域创建bean,包括:
-
单例(Singleton):在整个应用中,只创建bean的一个实例,
Spring默认的作用域
- 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例
- 会话(Session):在Web应用中,为每个会话创建一个bean实例
- 请求(Request):在Web应用中,为每个请求创建一个bean实例
如何设置bean的作用域?
-
组件扫描声明bean
如果我们使用组件扫描声明的bean,那么我们可以使用注解@Scope("xxx")
来指定bean的作用域
@Scope的值我们可以用ConfigurableBeanFactory提供的常量,当然也可以自己手写
例如声明为原型:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component("componentUser")
public class User {
}
-
xml声明bean
在XMl中声明bean的时候,我们可以使用<bean>元素的scope属性来设置:
<!--构造器注入:构造器参数为bean情形-->
<bean id="xmlUser0" class="com.tp.beans.componentscan.User" scope="prototype">
<constructor-arg name="book" ref="javaConfigBook"/>
</bean>
- JavaConfig声明bean
@Bean("javaConfigBook")
@Scope("prototype")
public Book book() {
Book book = new Book();
book.setId(222);
book.setPrice(33.88);
book.setName("简书");
return book;
}
运行时值注入
Spring提供了2种在运行时
求值的方式:
- 属性占位符(Property placeholder)
- Spring表达式(SpEL)
1.注入外部的值
在Spring中处理外部值最简单的方式是声明属性源并通过Spring的Environment来检索属性
声明属性源可以使用@PropertySource注解,其值为属性文件路径信息,例如:
@Configuration
@PropertySource("classpath:config/app.properties")
public class ValueInjectConfig {
@Autowired
Environment environment;
@Bean
public Jaguar jaguar(){
Jaguar jaguar = new Jaguar();
jaguar.setName(environment.getProperty("jaguar.name"));
jaguar.setColor(environment.getProperty("jaguar.color"));
return jaguar;
}
}
Jaguar.java:
package com.tp.beans.valueinject;
/**
* FileName: Jaguar
* Author: TP
* Description:
*/
public class Jaguar {
private String name;
private String color;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Jaguar{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
在上述代码中,我们使用@PropertySource注解并指定类属性源为类路径下的config/app.properties文件,文件内容如下:
jaguar.name=捷豹XEL
jaguar.color=white
civic.name=思域
civic.color=yellow
这个文件会被加载到Spring的Environment中,我们就可以借助Environment的getPropery()方法获取出对应的值,Environment的getPropery()方法有4种:
- String getPropery(String key);
- String getPropery(String key, String defaultValue);
- T String getPropery(String key, Class<T> type);
- T String getPropery(String key, Class<T> type, T defaultValue);
运行测试类:
public class ValueInjectTest extends BaseTestCase {
/**
* "@Properties"注解测试
*/
@Test
public void propertyResourceInjectTest() {
Jaguar jaguar = (Jaguar) applicationContext.getBean("jaguar");
System.out.println(">>>>@PropertyResource:" + jaguar);
}
}
测试结果:
2.解析属性占位符
直接从Environment中获取属性是比较方便的,特别是在Java配置中装配bean的时候,但是Spring也为我们提供了占位符装配bean属性的方法,这些占位符的值来源于一个属性源,为了使属性占位符生效,我们需要配置一个PropertySourcesPlaceholderConfigurer,它能够基于Spring Environment及其属性源来解析占位符,我们可以在Spring的配置文件中配置PropertySourcesPlaceholderConfigurer:
<context:property-placeholder location="classpath*:/config/*.properties"
file-encoding="utf-8"/>
准备就绪后,我们分别以注解扫描和xml形式声明一个bean
- xml声明bean使用属性占位符:
<bean id="xmlPlaceholderJaguar" class="com.tp.beans.valueinject.Jaguar">
<property name="name" value="${jaguar.name}"/>
<property name="color" value="${jaguar.color}"/>
</bean>
测试类:
/**
* 属性占位符xml形式测试
*/
@Test
public void xmlPlaceholderInjectTest() {
Jaguar jaguar = (Jaguar) applicationContext.getBean("xmlPlaceholderJaguar");
System.out.println(">>>>@PropertyPlaceholder of xml:" + jaguar);
}
结果:
- 组件扫描声明bean使用占位符
package com.tp.beans.valueinject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* FileName: Honda
* Author: TP
* Description:
*/
@Component
public class Civic {
@Value("${civic.name}")
private String name;
@Value("${civic.color}")
private String color;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Civic{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
测试类:
@Test
public void scanPlaceholderInjectTest() {
Civic civic = (Civic) applicationContext.getBean("civic");
System.out.println(">>>>@PropertyPlaceholder of componentScan:" + civic);
}
运行结果:
SpEl
Spring 3引入了Spring表达式(Spring Expression Language),它能够以一种强大和简洁的方式将值装配到bean的属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算到值。
SpEl拥有很多特性,包括:
- 使用bean的ID来引用bean
- 调用方法和访问对象的属性
- 对值进行算术、关系和逻辑运算
- 正则表达式匹配
- 集合操作
SpEL表达式需要放在:#{}
之中,形为#{xxx}
,这与属性占位符类似,属性占位符为${...}
之中。
SpEl用法示例
-
表示字面值
#{3.1415926}
表示一个浮点值
#{'Hello'}
表示一个String类型的字面值
#{false}
表示一个boolean类型的值 -
引用bean属性、方法
SpEl能够通过ID获取到这个bean,我们就可以使用SpEL对bean的属性或者方法实现引用
例如:
1.获取属性
#{jaguar.name}
:这个表达式会获取ID为jaguar的这个bean的name属性
2.方法引用
#{jaguar.getSlogan()}
:这个表达式会获取ID为jaguar的这个bean,并返回其getSlogan()方法的返回值
对于方法的返回值我们还可以继续调用返回值的函数,例如:#{jaguar.getSlogan().toUpperCase()}
,当然方法的返回值可能为null,这样就有可能引发空指针问题,为了避免空指针问题,我们可以使用类型安全的运算符:#{jaguar.getSlogan()?.toUpperCase()}
,这里我们使用了"?",这个运算符能够在访问它右边的内容之前确保左侧内容不为null,如果为null则不会执行后面的内容,直接返回null。 -
在表达式中使用类型
如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关键的运算符,例如:为了在SpEL中使用Java的Math类,需要按照如下的方式使用T()运算符:T(java.lang.Math),此时T()运算符会得到一个Class对象,代表了java.lang.Math,假如你想将PI值装配到bean的属性中,可以这样写:
${T(java.lang.Math).PI}
,当然我们不仅可以获取访问目标的常量,我们还可以调用其方法:${T(java.lang.Math).random()*10}
-
SpEL运算符
SpEL提供了多种运算符,这些运算符可以用在SpEL表达式上:
运算符类型 | 运算符 |
---|---|
算术运算 | +、-、*、/、%、^ |
比较运算符 | <、>、==、<=、>=、lt、gt、eq、le、ge |
逻辑运算符 | and、or、not、| |
条件运算符 | 三元运算符 |
正则表达式 | matches |
-
计算集合
SpEL还可以操作集合和数组
例如:#{jukebox.songs[4].tile}
,这个表达式会计算jukebox这个bean的songs属性(是个集合,集合装的Song这个javabean)中第五个元素Song的title属性,[]
用来从集合或者数组中按照索引获取数据
抛出几个简单的测试类,其他用法测试读者自行测试吧:
package com.tp.beans.valueinject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* FileName: Benz
* Author: TP
* Description:用于测试SpEl的实体
*/
@Component
public class Benz {
//SpEL引用bean属性
@Value("#{xmlSpelBenz.name}")
private String name;
//SpEL引用bean方法
@Value("#{xmlSpelBenz.getColor()}")
private String color;
//SpEL表达式中使用类型
@Value("#{T(java.lang.Math).random()*10}")
private int carAge;
//字面常量
@Value("#{'买奔驰吗?这次不漏油哦~'}")
private String slogan;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getCarAge() {
return carAge;
}
public void setCarAge(int carAge) {
this.carAge = carAge;
}
public String getSlogan() {
return slogan;
}
public void setSlogan(String slogan) {
this.slogan = slogan;
}
@Override
public String toString() {
return "Benz{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", carAge=" + carAge +
", slogan='" + slogan + '\'' +
'}';
}
}
app.properties:
jaguar.name=捷豹XEL
jaguar.color=white
civic.name=思域
civic.color=yellow
benz.slogan=买奔驰吗?漏油的那种!!!
applicationContext.xml:
<!--############################SpEL############################-->
<bean id="xmlSpelBenz" class="com.tp.beans.valueinject.Benz">
<property name="name" value="C260L"/>
<property name="color" value="富士白"/>
<property name="carAge" value="#{T(Math).random()*10}"/>
<property name="slogan" value="${benz.slogan}"/>
</bean>
测试类:
//=========================SpEl=========================
@Test
public void xmlSpelInjectTest() {
Benz benz = (Benz) applicationContext.getBean("xmlSpelBenz");
System.out.println(">>>>SpEL of xml:" + benz);
}
@Test
public void componentScanSpelInjectTest() {
Benz benz = (Benz) applicationContext.getBean("benz");
System.out.println(">>>>SpEL of componentScan:" + benz);
}
测试结果: