装配Spring Bean
依赖注入的三种方式
实现IoC容器的方式分为两大类,一类是依赖查找,依赖查找是通过资源定位,把对应的资源查找回来;另一类是依赖注入,而Spring主要使用的是依赖注入。一般而言,依赖注入可以分为3种方式:
- 构造器注入
- setter注入
- 接口注入
构造器注入和setter注入是主要的方式,而接口注入是从别的地方注入的方式,比如在Web工程种,配置的数据源往往是通过服务器去配置的,这个时候可以用JNDI的形式通过接口将它注入Spring IoC容器中来。
构造器注入
构造器注入依赖于构造方法实现,而构造方法可以是有参数或者是无参数的。在大部分的情况下,通过类的构造方法来创建类对象,Spring也可以采用反射的方式,通过使用构造方法来完成注入,这就是构造器注入的原理。
为了让Spring完成对应的构造注入,有必要去描述具体的类、构造方法并设置对应的参数,这样Spring就会通过对应的信息用反射的形式去创建对象。例如:
public class Role {
private Long id;
private String roleName;
private String note;
public Role(String roleName, String note) {
this.roleName = roleName;
this.note = note;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
这个时候是没有办法利用无参数的构造方法去创建对象的,因此,可以使用如下代码:
<bean id="role1" class="com.ssm.learn.pojo.Role">
<constructor-arg index="0" value="总经理"/>
<constructor-arg index="1" value="公司管理者"/>
</bean>
constructor-arg元素用于定义类构造方法的参数,其中index用于定义参数的位置,而value则是设置值,通过这样的定义Spring便知道使用Role这样的构造方法去创建对象了。
使用setter注入
setter注入是Spring中最主流的注入方式,它利用Java Bean规范所定义的setter方法来完成注入,灵活且可读性高。它消除了使用构造器注入时出现多个参数的可能性,首先可以把构造方法声明为无参数的,然后使用setter注入为其设置对应的值。其实也是通过Java反射技术得以实现的。首先假设在Role类中加入了一个没有参数的构造方法,即:
public Role() {
}
然后修改配置:
<bean id="role_2" class="com.ssm.learn.pojo.Role">
<property name="roleName" value="高级工程师" />
<property name="note" value="重要人员" />
</bean>
这样Spring就会通过反射调用没有参数的构造方法生成对象,同时通过反射对应的setter注入配置的值了。这在实际工作中使用广泛。
接口注入
有时候资源并非来自自身系统,而是来自外界,比如数据库连接资源。这样数据库资源属于开发工程外的资源,可以采用接口注入的形式来获取它。比如在Tomcat中可以配置数据源,又如在Eclipse中配置了Tomcat后,可以打开服务器的context.xml文件,在这个XML文件元素context中加入自己的一个资源,例如:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!--
name为JNDI名称
url是数据库的jdbc连接
username用户名
password数据库密码
-->
<Resource name="jdbc/ssm" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/ssm?zeroDateTimeBehavior=convertToNUll" username="root" password="123456" />
</Context>
如果已经配置了相应的数据库连接,那么Eclipse会把数据库的驱动包复制到对应的Tomcat的lib文件夹下,否则就需要自己手工将对应的驱动包复制到Tomcat的工作目录下,它位于{Tomcat_Home}\lib。然后启动Tomcat,这个时候数据库资源也会在Tomcat启动的时候被其加载进来。
如果Tomcat的Web工程使用了Spring,那么可以通过Spring的机制,用JNDI获取Tomcat启动的数据库连接池。代码如下:
<!--通过JNDI获取的数据源,通过Spring的接口注入实现-->
<bean id = "dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jdbc/ssm</value>
</property>
</bean>
装配Bean概述
将自己开发的Bean装配到Spring IoC容器中,大部分场景下,使用ApplicationContext的具体实现类,因为对应的Spring IoC容器功能相对强大,而在Spring中提供了3种方法进行配置:
- 在XML中显示配置
- 在Java的接口和类中实现配置
- 隐士Bean的发现机制和自动装配原则
我们应该怎么选择使用哪种方式去把Bean发布到Spring IoC容器中?
- 基于约定优于配置的原则,最优先的应该是通过隐式Bean的发现机制和自动装配的原则。
- 在没有办法使用自动装配原则的情况下应该优先考虑Java接口和类中实现配置,可以避免XML配置的泛滥。
- 在上述方法都无法使用的情况下,只能选择XML去配置Spring IoC容器。
通俗来讲,当配置的类是自身正在开发的工程,那么应该考虑Java配置为主,并且优先自动装配,如果不是自己的工程开发的,建议使用XML的方式。
通过XML配置装配Bean
使用XML装配Bean需要定义对应的XML,这里需要引入对应的XML模式(XSD)文件,这些文件会定义配置Spring 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-4.0.xsd">
<!--Spring Bean配置代码-->
</beans>
装配简易值
以一个最简单的装配为例:
<bean id="role_2" class="com.ssm.chapter9.pojo.Role">
<property name="id" value="1" />
<property name="roleName" value="高级工程师" />
<property name="note" value="重要人员" />
</bean>
其中:
- id属性是Spring找到这个Bean的编号,但是它不是一个必需的属性,如果没有声明它,那么Spring将会采用“全限定名#{number}”的格式生成编号。
- class显然是一个类全限定名
- property元素是定义类的属性,其中name属性定义的是属性名称,而value是其值。
有时候需要注入一些自定义的类,可以使用ref属性在另一个Bean的property属性中引用对应的Bean。
装配集合
有些时候需要做一些复杂的装配工作,比如Set、Map、List、Array和Properties等,可以采用如下形式装配这些属性:
<bean id="complexAssembly" class="com.ssm.chapter10.pojo.ComplexAssembly">
<property name="id" value="1" />
<property name="list">
<list>
<value>value-list-1</value>
<value>value-list-2</value>
<value>value-list-3</value>
</list>
</property>
<property name="map">
<map>
<entry key="key1" value="value-key-1" />
<entry key="key2" value="value-key-2" />
<entry key="key3" value="value-key-3" />
</map>
</property>
<property name="props">
<props>
<prop key="prop1">value-prop-1</prop>
<prop key="prop2">value-prop-2</prop>
<prop key="prop3">value-prop-3</prop>
</props>
</property>
<property name="set">
<set>
<value>value-set-1</value>
<value>value-set-2</value>
<value>value-set-3</value>
</set>
</property>
<property name="array">
<array>
<value>value-array-1</value>
<value>value-array-2</value>
<value>value-array-3</value>
</array>
</property>
</bean>
有时候可能需要更为复杂的装载,示例如下:
先建立两个POJO:
public class Role {
private Long id;
private String roleName;
private String note;
public Role() {
}
public Role(Long id, String roleName, String note) {
this.id = id;
this.roleName = roleName;
this.note = note;
}
}
public class User {
private Long id;
private String userName;
private String note;
/*setter and getter*/
}
然后建立一个复杂的POJO:
import java.util.List;
import java.util.Map;
import java.util.Set;
public class UserRoleAssembly {
private Long id;
private List<Role> list;
private Map<Role, User> map;
private Set<Role> set;
/**setter and getter*/
}
这里可以看到对于List、Map和Set等集合类使用的是类对象,Spring IoC容器提供了对应的配置方法,代码如下:
<bean id="role1" class="com.ssm.learn.pojo.Role">
<property name="id" value="1" />
<property name="roleName" value="role_name_1" />
<property name="note" value="role_note_1" />
</bean>
<bean id="role2" class="com.ssm.learn.pojo.Role">
<property name="id" value="2" />
<property name="roleName" value="role_name_2" />
<property name="note" value="role_note_2" />
</bean>
<bean id="user1" class="com.ssm.learn.pojo.User">
<property name="id" value="1" />
<property name="userName" value="user_name_1" />
<property name="note" value="role_note_1" />
</bean>
<bean id="user2" class="com.ssm.learn.pojo.User">
<property name="id" value="2" />
<property name="userName" value="user_name_2" />
<property name="note" value="role_note_1" />
</bean>
<bean id="userRoleAssembly" class="com.ssm.learn.pojo.UserRoleAssembly">
<property name="id" value="1" />
<property name="list">
<list>
<ref bean="role1" />
<ref bean="role2" />
</list>
</property>
<property name="map">
<map>
<entry key-ref="role1" value-ref="user1" />
<entry key-ref="role2" value-ref="user2" />
</map>
</property>
<property name="set">
<set>
<ref bean="role1" />
<ref bean="role2" />
</set>
</property>
</bean>
其中:
- List属性使用<List>元素注入,使用多个<ref>元素的Bean属性去引用之前定义好的Bean。
- Map属性使用<map>元素定义注入,使用多个<entry>元素的key-ref属性去引用之前定义好的Bean作为键,用value-ref属性去引用之前定义好的Bean作为值。
- Set属性使用<set>元素定义注入,使用多个<ref>元素的Bean去引用之前定义好的Bean。
命名空间装配
Spring还提供了对应的命名空间的定义。只是在使用命名空间的时候要先引入对应的命名空间和XML模式(XSD)文件。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
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 id="role1" class="com.ssm.chapter10.pojo.Role" c:_0="1" c:_1="role_name_1" c:_2="role_note_1" />
<bean id="role2" class="com.ssm.chapter10.pojo.Role" p:id="2" p:roleName="role_name_2" p:note="role_note_2" />
</beans>
其中:
- 第二行和第三行定义了XML的命名空间,这样才能在内容里面使用p和c这样的前缀定义。
- id为role1的角色定义,c:_0代表构造方法的第1个参数,c:_1代表的是第2个,c:_2代表的是第3个,以此类推。
- id为role2的角色定义,p代表引用属性,其中p:id="2"以2为值,使用setId方法设置,roleName、note属性也是一样的道理。
类似的,借助引入XML文档(XSD)文件的方法,把UserRoleAssembly类实例注册给Spring IoC容器:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c" xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util" 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/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="role1" class="com.ssm.chapter10.pojo.Role"
c:_0="1" c:_1="role_name_1" c:_2="role_note_1" />
<bean id="role2" class="com.ssm.chapter10.pojo.Role"
p:id="2" p:roleName="role_name_2" p:note="role_note_2" />
<bean id="user1" class="com.ssm.chapter10.pojo.User"
p:id="1" p:userName="role_name_1" p:note="user_note_1" />
<bean id="user2" class="com.ssm.chapter10.pojo.User"
p:id="2" p:userName="role_name_2" p:note="user_note_2" />
<util:list id="list">
<ref bean="role1" />
<ref bean="role2" />
</util:list>
<util:map id="map">
<entry key-ref="role1" value-ref="user1" />
<entry key-ref="role2" value-ref="user2" />
</util:map>
<util:set id="set">
<ref bean="role1" />
<ref bean="role2" />
</util:set>
<bean id="userRoleAssembly" class="com.ssm.chapter10.pojo. UserRoleAssembly"
p:id="1" p:list-ref="list" p:map-ref="map" p:set-ref="set" />
</beans>
引入的命名空间和XSD文件,然后定义了两个角色类对象(role1和role2)和两个用户类对象(user1和user2)。通过命名空间util去定义Map、Set和List对象,跟着就是定义id为userRoleAssembly的Bean,这里的list-ref代表采用list属性,显然就是util命名空间定义的List,同理Map和Set也是如此。
通过注解装配Bean
更多的时候考虑使用注解(annotation)的方式去装配Bean。在Spring中,它提供了两种方式来让Spring IoC容器发现Bean。
- 组件扫描:通过定义资源的方式,让Spring IoC容器扫描对应的包,从而把Bean装配进来。
- 自动装配:通过注解定义,使得一些依赖关系可以通过注解完成。
通过扫描和自动装配,大部分的工程都可以用Java配置完成,而不是XML。目前企业所流行的方式是,以注解为主,以XML为辅。
使用@Component装配Bean
首先定义一个POJO:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "role")
public class Role {
@Value("1")
private Long id;
@Value("role_name_1")
private String roleName;
@Value("role_note_1")
private String note;
/* setter and getter */
}
其中:
- 注解@Component代表Spring IoC会把这个类扫描生成Bean实例,而其中的value属性代表这个类在Spring中的id,这就相当于XML方式定义的Bean的id,也可以简写成@Component(“role”),甚至直接携程@Component,对于不写的,Spring IoC容器就默认类名,但是是以首字母小写的形式作为id,为其生成对象,配置到容器中。
- 注解@Value代表的是值的注入,这里只是简单注入一些值,其中id是一个long型,注入的时候Spring会为其转化类型。
然后Spring IoC需要使用一个JavaConfig来告诉容器去哪里扫描对象,代码如下:
package com.ssm.chapter10.annotation.pojo;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class PojoConfig {
}
注意两点:
- 包名和POJO保持一致
- @ComponentScan代表扫描,默认是扫描当前包的路径,POJO的包名和它保持一致才能扫描,否则是没有的。
然后就可以通过Spring定义好的Spring IoC容器的实现类——AnnotationConfigApplicationContext去生成IoC容器。代码如下:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.ssm.chapter10.annotation.pojo.PojoConfig;
import com.ssm.chapter10.annotation.pojo.Role;
public class AnnotationMain {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
Role role = context.getBean(Role.class);
System.err.println(role.getId());
}
}
这里使用了AnnotationConfigApplicationContext类去初始化Spring IoC容器,这里有两个弊端:
- 对于@ComponentScan注解,它只是扫描所在包的Java类,但是更多的时候真正需要的是可以扫描所指定的类;
- 上面只注入了一些简单的值,而没有注入对象,同样在现实的开发中可以注入对象是十分重要的。
@ComponentScan存在着两个配置项:第1个是basePackages,它是由base和package两个单词组成,而且package还使用了复数,意味着它可以配置一个Java包的数组,Spring会根据它的配置扫描对应的包和子包,将配置好的Bean装配起来;第2个是basePackageClasses,它由base、package和class三个单词组成的,采用复数,意味着它可以配置多个类,Spring会根据配置的类所在的包,为包和子包进行扫描装配对应配置的Bean。
首先定义一个接口RoleService,如下:
package com.ssm.chapter10.annotation.service;
import com.ssm.chapter10.annotation.pojo.Role;
public interface RoleService {
public void printRoleInfo(Role role);
}
使用接口来编写一些操作类是Spring所推荐的,它可以将定义和实现相分离。然后开发一个实现类去实现接口:
package com.ssm.chapter10.annotation.service.impl;
import org.springframework.stereotype.Component;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService;
@Component
public class RoleServiceImpl implements RoleService {
@Override
public void printRoleInfo(Role role) {
System.out.println("id =" + role.getId());
System.out.println("roleName =" + role.getRoleName());
System.out.println("note =" + role.getNote());
}
}
这里的@Component表明它是一个Spring所需要的Bean,而且也实现了对应的RoleService接口所定义的printRoleInfo方法。为了装配RoleServiceImpl和Role的两个Bean,需要给@ComponentScan注解加上对应的配置。代码如下:
package com.ssm.chapter10.annotation.config;
import org.springframework.context.annotation.ComponentScan;
import com.ssm.chapter10.annotation.service.impl.RoleServiceImpl;
@ComponentScan(basePackageClasses = { Role.class, RoleServiceImpl.class },
// @ComponentScan(basePackages = {"com.ssm.chapter10.annotation.pojo",
// "com.ssm.chapter10.annotation.service"})
// @ComponentScan(basePackages = {"com.ssm.chapter10.annotation.pojo",
// "com.ssm.chapter10.annotation.service"},
// basePackageClasses = {Role.class, RoleServiceImpl.class})
public class ApplicationConfig {
}
注意:
- 这是对扫描包的定义,可以采用任意一个@ComponentScan去定义,也可以取消代码中的注释
- 如果采用多个@ComponentScan去定义对应的包,但是每定义一个@ComponentScan,Spring就会为所定义的类生成一个新的对象,也就是所配置的Bean将会生成多个实例,这往往不是我们的需要
- 对于已定义了basePackages和basePackageClasses的@ComponentScan,Spring会进行专门的区分,也就是说在同一个@ComponentScan中即使重复定义相同的包或者存在其子包定义,也不会造成因同一个Bean的多次扫描,而导致一次配置生成多个对象。
因此,建议不要采用多个@ComponentScan注解进行配置。对于basePackages和basePackageClasses的选择问题,basePackages的可读性会更好一点,但是在大量重构的工程中,尽量不要使用basePackages定义,因为很多时候重构修改包名需要反复地配置,而IDE不会给任何提示。而采用basePackageClasses,当对包移动的时候,IDE会报错,并且可以轻松处理这些错误。
测试代码如下:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
Role role = context.getBean(Role.class);
RoleService roleService = context.getBean(RoleService.class);
roleService.printRoleInfo(role);
context.close();
自动装配——@Autowired
通过学习Spring IoC容器,我们知道Spring是先完成Bean的定义和生成,然后寻找需要注入的资源。也就是当Spring生成所有的Bean后,如果发现这个注解,它就会在Bean中查找,然后找到对应的类型,将其注入进来,这样就完成依赖注入了。所谓自动装配技术是一种由Spring自己发现对应的Bean,自动完成装配工作的方式,它会应用到一个十分常用的注解@Autowired上,这个时候Spring会根据类型去寻找定义的Bean然后将其注入。示例如下
首先定义一个接口:
public interface RoleService2{
public void printRoleInfo();
}
实现接口方法:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService2;
@Component("RoleService2")
public class RoleServiceImpl2 implements RoleService2 {
@Autowired
private Role role = null;
public Role getRole() {
return role;
}
// @Autowired
public void setRole(Role role) {
this.role = role;
}
@Override
public void printRoleInfo() {
System.out.println("id =" + role.getId());
System.out.println("roleName =" + role.getRoleName());
System.out.println("note =" + role.getNote());
}
}
这里的@Autowired注解,表示在Spring IoC定位所有的Bean后,这个字段需要按类型注入,这样IoC容器就会寻找资源,然后将其注入。
IoC容器有时候会寻找失败,在默认的情况下寻找失败它就会抛出异常,也就是说默认情况下,Spring IoC容器会认为一定要找到对应的Bean来注入这个字段,有时候这并不是一个真实的需求,这个时候可以通过@Autowired的配置项required来改变它,比如@Autowired(required=false)。
@Autowired除了可以配置在属性之外,还允许方法配置,常见的Bean的setter方法也可以使用它来完成注入,例如:
public class RoleServiceImpl2 implements RoleService2{
private Role role = null;
...
@Autowired
public void setRole(Role role){
This.role =role;
}
}
在大部分的配置中都推荐使用@Autowired注解,这是Spring IoC自动装配完成的,使得配置大幅度减少,满足约定优于配置的原则,增强程序的健壮性。
自动装配的歧义性(@Primary和@Qualifier)
按照Spring的建议,在大部分情况下使用接口编程,但是定义一个接口,并不一定只有与之对应的一个实现类。换句话说,一个接口可以有多个实现类,这样就会出现有时候自动装配不能使用。
原因在于@Autowired采用的是按类型注入对象,而在Java中接口可以有多个实现类,同样的抽象类也可以有多个实例化的类,这样就会造成通过类型(by type)获取Bean的不唯一,从而导致Spring IoC类似于按类型的方法无法获得唯一的实例化类。这就是自动装配的歧义性。BeanFactory的定义如下,它存在一个通过类型获取Bean的方法:
<T> T getBean(Class<T> requiredType) throws BeansException;
为了消除歧义性,Spring提供了两个注解(@Primary和@Qualifer),这是两个不同的注解:
1. 注解@Primary
注解@Primary代表首要,当Spring IoC通过一个接口或者抽象类注入对象的时候,由于存在多个实现类或者具体类,就会犯糊涂,不知道采用哪个类注入为好。注解@Primary则是告诉Spring IoC容器,请优先使用该类注入。示例如下:
...
import org.springframework.context.annotation.Primary;
@Component("roleService3")
@Primary
public class RoleServiceImpl3 implements RoleService{
......
}
但是还存在这样一个问题,可以将@Primary注解加入到多个实现类中,这样在Spring中也是允许的,只是在注入的时候将抛出异常。但是无论如何,@Primary只能解决首要性的问题,不能解决选择性的问题。简而言之,它不能选择使用接口具体的实现类去注入。
2. 注解@Qualifier
歧义性的一个重要原因是Spring在寻找依赖注入的时候采用按类型注入引起的。除了按类型查找Bean,Spring IoC容器最底层的接口BeanFactory,也定义了按名称查找的方法,如果采用名称查找的方法,而不是采用按类型查找的方法,那么就可以消除歧义性了。注解@Qualifier正是这样的一个注解。代码修改如下所示:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService;
@Component
public class RoleController {
@Autowired
@Qualifier("roleService3")
private RoleService roleService = null;
public void printRole(Role role) {
roleService.printRoleInfo(role);
}
}
这个时候IoC容器不会再按照类型的方式注入了,而是按照名称的方式注入,IoC容器的底层接口——BeanFactory,它所定义的方法如下:
Object getBean(String name) throws BeansException;
使用@Qualifier注解后就可以使用这个方法通过名称从IoC容器中获取对象进行注入。
装载带有参数的构造方法类
角色类的构造方法都是没带参数的,而事实上在某些时候构造方法是带参数的,对于一些带有参数的构造方法,也允许我们通过注解进行注入。示例如下:
@Component
public class RoleController2{
private RoleService roleService = null;
public RoleController2(RoleService roleService){
this.roleService = roleService;
}
......
}
以及如下代码:
public RoleController2(@Autowired RoleService roleService){
this.roleService = roleService;
}
使用@Bean装配Bean
@Component只能注解在类上,不能注解到方法上。这个时候Spring给予一个注解@Bean,它可以注解到方法之上,并且将方法返回的对象作为Spring的Bean,存放在IoC容器中。示例如下:
@Bean(name="dataSource")
public DataSource getDataSource(){
Properties props = new Properties();
props.setProperty("driver","com.mysql.jdbc.Driver");
props.setProperty("url","jdbc:mysql://localhost:3306/chapter12");
props.setProperty("username","root");
props.setProperty("password","123456");
DataSource dataSource = null;
try{
dataSource = BasicDataSourceFactory.createDataSource(props);
}catch(Exception e){
e.printStackTrace();
}
return dataSource;
}
这样就能够装配一个Bean,当Spring IoC容器扫描它的时候,就会为其生成对应的Bean。而且这里还配置了@Bean的name选项为dataSource,这意味着Spring生成该Bean的时候就会使用dataSource作为其BeanName。另外,它也可以通过@Autowired或者@Qualifier等注解注入别的Bean中。
注解自定义Bean的初始化和销毁方法
Bean的初始化可以通过实现Spring所定义的一些关于生命周期的接口来实现,这样BeanFactory或者其他高级容器ApplicationContext就可以调用这些接口所定义的方法了,这和使用XML是一样的。另外使用注解@Bean的配置项就可以实现自定义的初始化方法和销毁方法。
注解@Bean不能使用在类的标注上,它主要使用在方法上,@Bean的配置项中包含4个配置项:
- name:是一个字符串数组,允许配置多个BeanName
- autowire:标志是否是一个引用的Bean对象,默认值是Autowire.NO。
- initMethod:自定义初始化方法
- destroyMethod:自定义销毁方法
示例代码如下,其中指明了它的初始化方法和销毁方法:
@Bean(name="juiceMaker2", initMethod="init", destroyMethod="myDestroy")
public JuiceMaker2 initJuiceMaker2(){
JuiceMaker2 juiceMaker2 = new JuiceMaker2();
juiceMaker2.setBeverageShop("贡茶");
Source source = new Source();
source.setFruit("橙子");
source.setSize("大杯");
source.setSugar("少糖");
juiceMaker2.setSource(source);
return juiceMaker2;
}
装配的混合使用
在现实中,使用XML或者注解各有道理,建议在自己的工程中所开发的类尽量使用注解方式,因为使用它并不困难,更为简单,而对于引入第三方包或者服务的类,尽量使用XML方式,这样的好处是可以尽量对第三方包或者服务的细节减少理解,也更加清晰和明朗。
Spring同时支持这两种形式的装配,所以可以自由选择,只是无论采用XML还是注解方式的装配都是将Bean装配到Spring IoC容器中,这样就可以通过Spring IoC容器去管理各类资源了。
以数据库池的配置为例,首先DBCP数据库连接池是通过第三方去定义的,没有办法给第三方加入注解,因此可以选择通过XML给出。假设它配置XML文件——spring-data.xml,需要通过引入它达到注解的体现当中,而注解的体系则需要完成对角色编号(id)为1的查询功能。
首先,使用注解@ImportResource,引入spring-data.xml所定义的内容:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
@ComponentScan(basePackages={"com.ssm.learn.annotation"})
@ImportResource({"classpath:spring-dataSource.xml"})
public class ApplicationConfig {
}
@ImportResource中配置的内容是一个数组,也就是可以配置多个XML配置文件,这样就可以引入多个XML所定义的Bean了。
然后通过@Autowired注入去实现对数据库连接池的注入:
import com.ssm.chapter10.annotation.pojo.Role;
public interface RoleDataSourceService {
public Role getRole(Long id);
}
这是一个简单的接口,需要一个实现类,这个实现类要用到数据库连接池,这时就可以使用@Autowired进行注入,代码如下:
@Component
public class RoleDataSourceServiceImpl implements RoleDataSourceService {
@Autowired
DataSource dataSource = null;
@Override
public Role getRole(Long id) {
Connection conn = null;
ResultSet rs = null;
PreparedStatement ps = null;
Role role = null;
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement("select id, role_name, note from t_role where id = ?");
ps.setLong(1, id);
rs = ps.executeQuery();
while(rs.next()) {
role = new Role();
role.setId(rs.getLong("id"));
role.setRoleName(rs.getString("role_name"));
role.setNote(rs.getString("note"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
/**********close database resources************/
}
return role;
}
}
通过这样的形式就能够把XML所配置的dataSource注入RoleDataSourceServiceImpl中了,同样也可以注入其他的资源。
有时候所有的配置放在一个ApplicationConfig类里面会造成配置复杂,因此会有多个类似于ApplicationConfig配置类。Spring也提供了注解@Import的方式注入这些配置类。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
@ComponentScan(basePackages={"com.ssm.learn.annotation"})
@Import({ApplicationConfig2.class,ApplicationConfig3.class})
public class ApplicationConfig {
}
通过这样的形式加载了多个配置文件。
如果有多个XML文件,而希望通过其中的一个XML文件去引入其他的XML文件,假设目前有了spring-bean.xml,需要引入spring-datasource.xml,那么可以在spring-bean.xml使用import元素来加载它:
<import resource="spring-datasource.xml" />
如果希望使用XML加载java配置类,但是目前spring是不能支持的,不过spring可以支持通过XML的配置扫描注解的包,只需要通过<context:component-scan>定义扫描的包就可以了:
@ComponentScan(basePackages={"com.ssm.learn.annotation"})的功能
<context:component-scan base-package="com.ssm.learn.annotation" />
无论是使用XML方式,还是使用注解方式,各有利弊,推荐把第三方包、系统外的接口服务和通用的配置使用XML配置,对于系统内部的开发则以注解方式为主。
使用Profile
存在需求:在不同的环境中切换以执行不同的任务,比如测试和开发。
使用注解@Profile配置
假设存在两个数据库连接池,一个用于开发(dev),一个用于测试(test),代码如下:
/*****************imports********************/
@Component
public class ProfileDataSource{
@Bean(name="devDataSource")
@Profile("dev")
public DataSource getDevDataSource(){
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/learn");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
@Bean(name="testDataSource")
@Profile("test")
public DataSource getTestDataSource(){
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/learndemo");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
这里定义了两个Bean,分别定义了@Profile,一个是dev,一个是test,同样也可以使用XML进行定义。
使用XML定义Profile
使用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-4.0.xsd"
profile="dev">
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/chapter10" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
</beans>
在一个XML文件里面配置多个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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<beans profile="test">
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/ssm1" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
</beans>
<beans profile="dev">
<bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/ssm2" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
</beans>
</beans>
启动Profile
当启动Java配置或者XML配置Profile时,可以发现这两个Bean并不会被加载到Spring IoC容器中,需要自行激活Profile。激活Profile的方法有5种:
- 在使用Spring MVC的情况下可以配置Web上下文参数,或者DispatchServlet参数。
- 作为JNDI条目
- 配置环境变量
- 配置JVM启动参数
- 在集成测试环境中使用@ActiveProfiles。
首先,在测试代码中激活Profile,可以使用注解@ActiveProfiles进行定义。代码如下:
import javax.sql.DataSource;
/**************imports**************/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ProfileConfig.class)
@ActiveProfiles("dev")
public class ProfileTest{
@Autowired
private DataSource dataSource;
@Test
public void test(){
System.out.println(dataSource.getClass().getName());
}
}
在测试代码中加入@ActiveProfiles来指定加载哪个Profile,这样程序就会自己去加载对应的Profile了。
有时候要在一些服务器上运行,这个时候可以配置Java虚拟机的启动项。比如在Tomcat服务器上或者在main方法上,那么可以启用Java虚拟机的参数来实现它,关于制定Profile的参数存在两个:
- spring.profiles.active:启动的Profile,如果配置了它,那么spring.profiles.default配置项将失效
- spring.profiles.default:默认启动的Profile,如果系统没有配置关于Profile参数的时候,那么它将启动。
这个时候可以配置JVM的参数来启用对应的Profile,比如这里需要启动test,那么就可以配置为:
JAVA_OPTS="-Dspring.profiles.active=test"
有时候也可以给运行的类加入虚拟机参数,这样在IDE运行代码的时候,Spring就知道采用哪个Profile进行操作了。
在大部分情况下需要启动Web服务器,如果使用的是Spring MVC,那么也可以设置Web环境参数或者DispatcherServlet参数来选择对应的Profile,比如在web.xml中进行配置:
<!-- 使用Web环境参数-->
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>test</param-value>
</context-param>
<!--使用SpringMVC的DispatcherServlet环境参数-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
<init-param>
<param-name>spring.profiles.active</param-name>
<param-name>test</param-name>
</init-param>
</servlet>
这样也可以在Web工程启动的时候来启用对应的Profile。通过编码去实现也是可以的。
加载属性(properties)文件
在开发的过程中,配置文件往往就是那些属性(properties)文件,比如使用properties文件配置数据库文件,例如:
jdbc.database.driver=com.mysql.jdbc.Driver
jdbc.database.url=jdbc:mysql://localhost:3306/chapter10
jdbc.database.username=root
jdbc.database.password=123456
在Spring中可以使用注解或者XML的方式进行加载属性文件。
使用注解方式加载属性文件
首先Spring提供了注解@PropertySource来加载属性文件,它的配置项有:
- name:字符串,配置这次属性配置的名称。
- value:字符串数组,可以配置多个属性的文件。
- ignoreResourceNotFound:boolean值,默认为false,其含义为如果找不到对应的属性文件是否进行忽略处理,所以默认会抛出异常。
- encoding:编码,默认为“”.
注意:如果只有@PropertySource的加载,Spring只会把对应文件加载进来。
示例如下,首先定义Java配置类:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcePlaceholderConfigurer;
@Configuration
@PropertySource(value={"classpath:database-config.properties"},ignoreResourceNotFound=true)
public class ApplicationConfig {
}
@PropertySource的配置,首先加载了database-config.properties文件,然后定义选项为ignoreResourceNotFound=true,找不到该文件就会忽略掉它。
然后对其进行测试:
Application context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
String url = context.getEnvironment().getProperty("jdbc.database.url");
System.out.println(url);
Spring推荐使用一个属性文件解析类进行处理,它就是PropertySourcesPlaceholderConfigurer,使用它就意味着允许Spring解析对应的属性文件,并通过占位符去引用对应的配置。示例如下:
/*****************imports****************/
@Configuration
@ComponentScan(basePackages={"com.ssm.chapter10.annotation"})
@PropertySource(value={"classpath:database-config.properties"},ignoreResourceNotFound=true)
public class ApplicationConfig{
@Bean
public PropertySourcePlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
return new PropertySourcePlaceholderConfigurer();
}
}
这里定义了一个PropertySourcePlaceholderConfigurer类的Bean,作用是为了让Spring能够解析属性占位符。另外既然属性文件已经定义了关于数据库连接所需要的配置,那么还需要知道如何去引用已经定义好的配置,这里可以使用注解@Value和占位符,示例如下:
/*************imports****************/
@Component
public class DataSourceBean {
@Value("${jdbc.database.driver}")
private String driver = null;
@Value("${jdbc.database.url}")
private String url = null;
@Value("${jdbc.database.username}")
private String username = null;
@Value("${jdbc.database.password}")
private String password = null;
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
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;
}
@Bean(name = "dataSource1")
public DataSource getDataSource() {
Properties props = new Properties();
props.setProperty("driver", driver);
props.setProperty("url", url);
props.setProperty("username", username);
props.setProperty("password", password);
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
使用XML方式加载属性文件
使用XML文件进行加载属性文件,只需要使用<context:property-placeholder>元素加载一些配置项即可,例如:
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.ssm.chapter10.annotation" />
<context:property-placeholder
ignore-resource-not-found="true" location="classpath:database-config.properties" />
</beans>
其中属性location是一个配置属性文件路径的选项,它可以配置单个文件或者多个文件,多个文件之间要使用逗号分隔。如果系统中存在很多文件,那么属性location就要配置常常的字符串了,不过还有其他XML的方式也可以进行配置,示例如下:
<!--字符串数组,可配置多个属性文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<array>
<value>classpath:database-config.properties</value>
<value>classpath:log4j.properties</value>
</array>
</property>
<property name="ignoreResourceNotFound" value="true" />
</bean>
条件化装配Bean
在某些条件下不需要去装配Bean。这个时候就需要通过条件化去判断。Spring提供了注解@Conditional去配置,通过它可以配置一个或者多个类,只是这些类都需要实现接口Condition(org.springframework.context.annotation.Condition)。示例如下:
先修改关于DBCP数据源的Bean:
@Bean(name="dataSource")
@Conditional({DataSourceCondition.class})
public DataSource getDataSource(
@Value("${jdbc.database.driver}") String driver,
@Value("${jdbc.database.url}") String url,
@Value("${jdbc.database.username}") String username,
@Value("${jdbc.database.password}") String password){
Properties props = new Properties();
props.setProperty("driver", driver);
props.setProperty("url", url);
props.setProperty("username", username);
props.setProperty("password", password);
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
这里通过@Value往参数里注入了对应属性文件的配置,但是没有办法确定这些数据源连接池的属性是否在属性文件中已经配置完整,如果是不充足的属性配置,则会引起创建失败,为此要判断属性文件的配置是否充足才能继续创建Bean。通过@Conditional去引入了一个类——DataSourceCondition,由它来进行判断。代码如下:
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class DataSourceCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取上下文环境
Environment env = context.getEnvironment();
//判断是否存在关于数据源的基础配置
return env.containsProperty("jdbc.database.driver")
&& env.containsProperty("jdbc.database.url")
&& env.containsProperty("jdbc.database.username")
&& env.containsProperty("jdbc.database.password");
}
}
这里要求DataSourceCondition实现接口Condition的matches方法,该方法有两个参数,一个是ConditionContext,通过它可以获得Spring的运行环境,一个是AnnotatedTypeMetadata,通过它可以获得关于该Bean的注解信息。
代码中先获取了运行上下文环境,然后判断在环境中属性文件是否配置了数据库的相关参数,如果配置了,则返回为true,那么Spring会去创建对应的Bean,否则是不会创建的。
Bean的作用域
在默认的情况下,Spring IoC容器只会对一个Bean创建一个实例,比如:
ApplicationContext ctx = new ClassPathXmlApplicationContext("Spring-props.xml");
RoleDataSourceService RoleService = ctx.getBean(RoleDataSourceService.class);
RoleDataSourceService RoleService2 = ctx.getBean(RoleDataSourceService.class);
System.out.println(RoleService==RoleService2);
通过类型两次从Spring IoC容器中取出Bean,然后通过==比较,是否为同一个对象,经测试它们是同一个对象。
有时候希望能够通过Spring IoC容器中获取多个实例,而这些可以通过Spring的作用域来实现。Spring提供了4种作用域,它会根据情况来决定是否生成新的对象:
- 单例(singleton):它是默认的选项,在整个应用种,Spring只为其生成一个Bean的实例。
- 原型(prototype):当每次注入,或者通过Spring IoC容器获取Bean时,Spring都会为它创建一个新的实例。
- 会话(session):在web应用中使用,就是在会话过程中Spring只创建一个实例
- 请求(request):在Web应用中使用,就是在一次请求中Spring会创建一个实例,但是不同的请求会创建不同的实例。
示例如下:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RoleDataSourceImpl implements RoleDataSourceService{
......
}
这里使用了注解@Scope,并且声明为原型。每当我们从Spring IoC容器中获取对象,它就会生成一个新的实例。
使用Spring表达式(Spring EL)
Spring还提供了更灵活的注入方式,那就是Spring表达式。
Spring EL拥有很多功能:
- 使用Bean的id来引用Bean
- 调用指定对象的方法和访问对象的属性
- 进行运算
- 提供正则表达式进行匹配
- 集合配置
Spring EL相关的类
Spring EL的相关类首先是ExpressionParser接口,它是一个表达式的解析接口,Spring会提供很多的实现类。例如:
//表达式解析器
ExpressionParser parser = new SpelExpressionParser();
//设置表达式
Expression exp = parser.parseExpression("'hello world'");
String str = (String) exp.getValue();
System.out.println(str);
//通过EL访问普通方法
exp = parser.parseExpression("'hello world'.charAt(0)");
char ch = (Character) exp.getValue();
System.out.println(ch);
//通过EL访问的getter方法
exp = parser.parseExpression("'hello world'.bytes");
byte[] bytes = (byte[]) exp.getValue();
System.out.println(bytes);
//通过EL访问属性,相当于"hello world".getBytes().length
exp = parser.parseExpression("'hello world'.bytes.length");
int length = (Integer)exp.getValue();
System.out.println(length);
exp = parser.parseExpression("new String('abc')");
String abc = (String)exp.getValue();
System.out.println(abc);
通过表达式可以创建对象,调用对象的方法获取属性。Spring还支持变量的解析,只是使用变量解析的时候常常用到一个接口——EvaluationContext,它可以有效解析表达式中的变量,它也有一个实现类——StandardEvaluationContext:
//创建角色对象
Role role = new Role(1L, "role_name", "note");
exp = parser.parseExpression("note");
//相当于从role中获取备注信息
String note = (String) exp.getValue(role);
System.out.println(note);
//变量环境类,并且将角色对象role作为其根节点
EvaluationContext ctx = new StandardEvaluationContext(role);
//变量环境类操作根节点
parser.parseExpression("note").setValue(ctx, "new_note");
//获取备注,这里的String.class指明,我们希望返回的是一个字符串
note = parser.parseExpression("note").getValue(ctx, String.class);
System.out.println(note);
//调用getRoleName方法
String roleName = parser.parseExpression("getRoleName()").getValue(ctx, String.class);
System.out.println(roleName);
//新增环境变量
List<String> list = new ArrayList<String>();
list.add("value1");
list.add("value2");
//给变量环境增加变量
ctx.setVariable("list", list);
//通过表达式去读写环境变量的值
parser.parseExpression("#list[1]").setValue(ctx, "update_value2");
System.out.println(parser.parseExpression("#list[1]").getValue(ctx));
EvaluationContext使用了他的实现类StandardEvaluationContext,进行了实例化,在构造方法中将角色传递给它了,那么估值内容就会基于这个类进行解析。
Spring EL最重要的功能就是对Bean属性进行注入。
Bean的属性和方法
使用注解的方式需要用到注解@Value,在属性文件的读取中使用的是“$”,而在Spring EL中则使用“#”。示例代码如下:
/*************imports*************/
@Component("role")
public class Role {
// 赋值long型
@Value("#{1}")
private Long id;
// 字符串赋值
@Value("#{'role_name_1'}")
private String roleName;
// 字符串赋值
@Value("#{'note_1'}")
private String note;
/*********** setter and getter *********/
这样就可以定义一个BeanName为role的角色类了,同时给予它所有的属性赋值,这个时候可以通过另外一个Bean去引用它的属性或者调用它的方法。比如新建一个类:
@Component("elBean")
public class ElBean {
//通过beanName获取bean,然后注入
@Value("#{role}")
private Role role;
//获取bean的属性id
@Value("#{role.id}")
private Long id;
//调用bean的getNote方法,获取角色名称
@Value("#{role.getNote()?.toString()}")
private String note;
/**********setter and getter**********/
通过BeanName进行注入,也可以通过OGNL获取其属性或者调用其方法来注入其他的Bean中。这里的问号(?)的含义是先判断是否返回非null,如果不是则不再调用toString方法。
使用类的静态变量和方法
有时候希望使用一些静态方法和常量,比如圆周率Π,可以按照如下方式注入:
@Value("#{T(Math).PI}")
private double pi;
这里的Math代表的是java.lang.*包下的Math类,在java代码中使用该包是不需要使用import关键字引入的。如果在Spring中使用一个非该包的内容,那么要给出该类的全限定名。如下所示:
@Value("#{T(java.lang.Math).PI}")
private double pi;
Spring EL运算
Spring EL运算,比如在EIBean上增加一个数子num,其值默认为要求是角色编号(id)+1,可以写成:
@Value("#{role.id+1}")
private int num;
有时候“+"号也可以用在字符串的连接上,比如:
@Value("#{role.roleName+role.note}")
private String str;
比较两个值是否相等,数字和字符串都可以使用“eq"或者“=="进行相等比较,除此之外,还有大于、小于等数学运算。另外,还有三目运算符
@Value("#{role.id==1}")
private boolean equalNum;
@Value("#{role.note.eq 'note_1'}")
private boolean equalString;
@Value("#{role.id >2}")
private boolean greater;
@Value("#{role.id} <2")
private boolean less;
@Value("#{role.id>1?5:1}")
private int max;
@Value("#{role.note?:'hello'}")
private String defaultString;