依赖注入和依赖查找
应该说IoC的工作方式有两种,一种是依赖查找,通过资源定位,把对应的资源查找出来,例如通过JNDI找到数据源,依赖查找被广泛使用在第三方的资源注入上,比如在Web项目中,数据源往往是通过服务器配置的,例如Tomcat的数据源配置,这种场景下便可以用JNDI的形式通过接口将其注入Spring IoC容器;另一种则是依赖注入,其主要是在容器内通过类型或者名称查找资源来管理Bean之间的依赖关系,而就依赖注入而言又可分为构造器注入和setter注入两种方式,setter的形式是Spring推荐的也是应用更广泛的形式
构造器注入
构造器注入依赖于构造方法的实现,而构造方法可以是有参数的或者是无参数的,在大部分情况下,我们通过类的构造方法创建类对象,Spring也可以采用反射的方式,通过使用构造方法完成注入,这便是构造器注入的原理
要让Spring更好的完成对应的构造注入,有必要描述具体的类、构造方法、设置对应的参数,如此Spring就会通过对应的信息用反射的形式创建对象
如下代码所示,首先定义一个新的Role pojo
package com.ssm.pojo;
/*** 角色类,用于表示角色信息*/
public class RoleII {private Long id; // 角色编号private String roleName; // 角色名称private String note; // 备注信息/*** 无参数构造方法,用于创建一个空的角色对象。*/public RoleII() {}/*** 带参数的构造方法,用于创建一个具有指定角色信息的角色对象。** @param id 角色的唯一标识符。* @param roleName 角色的名称。* @param note 对角色的备注说明。*/public RoleII(Long id, String roleName, String note) {this.id = id;this.roleName = roleName;this.note = note;}// 提供对id属性的访问和修改public Long getId() {return id;}public void setId(Long id) {this.id = id;}// 提供对roleName属性的访问和修改public String getRoleName() {return roleName;}public void setRoleName(String roleName) {this.roleName = roleName;}// 提供对note属性的访问和修改public String getNote() {return note;}public void setNote(String note) {this.note = note;}
}
要使用带参数的构方法创建对象,需要在配置文件spring-cfg.xml中进行适当的配置,如下所示
<!-- 定义一个RoleII类型的bean,通过构造器进行初始化 --><bean id="role1" class="com.ssm.pojo.RoleII"><!-- 构造器参数1:角色ID,这里设置为1 --><constructor-arg index="0" value="1" /><!-- 构造器参数2:角色名称,这里设置为"总经理" --><constructor-arg index="1" value="总经理" /><!-- 构造器参数3:角色描述,这里设置为"公司管理者" --><constructor-arg index="2" value="公司管理者" /></bean>
其中constructor-arg元素用于定义构造方法的参数,index用于定位参数的位置,从0开始,通过这样的定义,Spring便会使用
RoleII(Long id, String roleName, String note)
方法创建对象
参数少的时候还好,参数多了这种构造形式可读性会比较差
setter注入
setter注入是Spring中最主流的注入方式,它利用JavaBean规范定义的setter方法完成注入,不但灵活而且可读性高,消除了使用构造器注入时出现多个参数可读性变差的问题,在RoleII的pojo里有个无参数的构造函数,通过配置Spring也可以通过Java反射技术注入
<!-- 定义一个RoleII类型的bean实例,id为2,角色名为"高级工程师",备注为"重要人员" --><bean id="role2" class="com.ssm.pojo.RoleII" scope="prototype"><!-- 设置角色id为2 --><property name="id" value="2" /><!-- 设置角色名为"高级工程师" --><property name="roleName" value="高级工程师" /><!-- 设置备注为"重要人员" --><property name="note" value="重要人员" /></bean>
scope属性定义了Spring容器管理的Bean的实例化策略,即Bean的作用域。不同的作用域会影响Bean的创建和生命周期管理。以下是Spring支持的主要作用域:
- Singleton (单例): 这是默认的作用域。当一个Bean被配置为单例时,Spring容器只会创建该Bean的一个实例,并在第一次请求时初始化。之后的每次请求都将返回同一个实例。适合那些在整个应用中需要共享同一状态的对象。
- Prototype (原型): 原型作用域表示每次请求都会创建一个新的Bean实例。这适用于那些需要有各自独立状态的对象,比如用户会话或者并发操作中的对象。
- Request: 在Web应用中,每个HTTP请求都会创建一个新的Bean实例。这意味着对于每个请求,都会有一份独立的Bean副本。
- Session: 也是在Web应用中,每个HTTP session会创建一个新的Bean实例。这意味着每个用户session都有自己的Bean副本,可以用于存储用户特定的信息。
- Application: 在Web应用中,这个作用域的Bean在整个ServletContext(应用上下文)中是唯一的,类似于单例,但作用于整个Web应用而不是容器本身。
- Global Session: 在Portlet应用中,全局session作用域的Bean在一个全局portlet session中是唯一的。这在多portlet环境中用于跨portlet共享状态
选择正确的scope有助于优化应用性能并确保正确管理对象的状态
这样Spring就会通过反射调用没有参数的构造方法生成对象,同时通过发射对应的setter方法注入配置的值
property 和 constructor-arg 是Spring框架中用于XML配置文件中进行依赖注入(Dependency Injection, DI)的两个不同方式。
- constructor-arg 用于通过构造函数来注入依赖。它指定调用构造函数时传递给构造函数的参数。
- 当类有无参构造函数或有参构造函数时,可以使用此标签来指定哪个构造函数应该被调用,并提供对应的参数值。
- 参数可以通过value、ref、index等属性来设置,value用于直接注入基本类型或字符串,ref用于引用其他bean,index或name用于指定构造函数参数的位置或名称(如果构造函数有多个相同类型的参数)。
- property 用于通过setter方法来注入依赖。它对应于Java对象的属性,即调用setter方法来设置对象的属性值。
- 这种方式适用于类中有getter/setter方法的属性。
- 和constructor-arg一样,property也可以通过value或ref来设置值。
主要区别:
- 注入方式:constructor-arg是构造函数注入,而property是setter方法注入。
- 初始化顺序:构造函数注入通常在对象实例化时发生,而setter注入可以在对象创建后任何时候进行。
- 强制性:构造函数注入可能是强制性的,如果类只有一个无参构造函数,那么必须使用setter注入;如果有带参数的构造函数,Spring会根据提供的constructor-arg调用相应的构造函数。
- 安全性:构造函数注入通常被认为更安全,因为它保证了对象在创建时就处于一致和完整状态,而setter注入的对象可能在初始化后才被完全设置好
依赖查找
有些资源并非来自系统,例如数据库连接资源完全可以在Tomcat下配置,然后通过JNDI形式获取,这种数据库连接资源属于工程外资源,就可以采用接口注入的形式获取它
顺便讲一下Tomcat配置JNDI(Java Naming and Directory Interface),主要涉及在Tomcat的配置文件中定义资源和资源引用
方式1:在context.xml中配置,找到conf/context.xml文件,添加以下元素来定义JNDI资源,例如一个数据源:
<Resource name="jndi/ssm" auth="Container"type="javax.sql.DataSource"driverClassName="com.mysql.jdbc.Driver"url="jdbc:mysql://localhost:3306/ssm?useUnicode=true;characterEncoding=utf8""username="root" password="xxxx"maxActive="20" maxIdle="10" maxWait="10000"/>
方式2:在server.xml中配置全局资源,打开conf/server.xml文件,在元素内添加元素,这样配置的资源可以被所有应用共享
<GlobalNamingResources>...<Resource name="jndi/globalSsm" auth="Container"type="javax.sql.DataSource"driverClassName="oracle.jdbc.driver.OracleDriver"url="jdbc:oracle:thin:@127.0.0.1:1521:ORCL"username="scott" password="tiger"maxActive="20" maxIdle="10" maxWait="10000"/>...
</GlobalNamingResources>
然后在每个应用的context.xml文件中,通过元素引用全局资源, 如下所示
<ResourceLink name="jndi/localDemo" global="jndi/globalSsm" type="javax.sql.DataSource"/>
也可以在应用的WEB-INF/web.xml文件中,添加元素来声明应用将要使用的资源,完成资源引用:
<resource-ref><description>DB Connection</description><res-ref-name>jndi/localDemo</res-ref-name><res-type>javax.sql.DataSource</res-type><res-auth>Container</res-auth>
</resource-ref>
一旦在全局或某个应用的context.xml中定义了资源,接下来需要在具体的应用上下文中引用这些资源。这通常通过在应用的WEB-INF/web.xml文件中添加元素来完成,或者在应用的特定context.xml(如果有的话)使用来链接到全局资源
返回来,假设在Tomcat的context.xml文件中有如下JNDI配置
<Resource name="jndi/ssm" auth="Container"type="javax.sql.DataSource"driverClassName="com.mysql.cj.jdbc.Driver"url="jdbc:mysql://localhost:3306/ssm?useUnicode=true;characterEncoding=utf8""username="root" password="xxxx"maxActive="20" maxIdle="10" maxWait="10000"/>
如果Tomcat的Web项目使用了Spring,那么可以通过Spring的机制,用JNDI获取Tomcat启动的数据库连接池,在webapp--->WEB-INF--->applicationContext.xml
文件中加入配置
<!-- 定义一个数据源bean,通过JNDI技术从应用程序服务器中获取数据源 -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"><!-- 设置JNDI名称,用于在Java EE服务器中查找对应的DataSource --><property name="jndiName"><value>java:comp/env/jdbc/ssm</value></property>
</bean>
这样就可以在Spring的IoC容器中获得Tomcat管理的数据库连接池了,这就是一种接口注入的方式
装配Bean
Spring提供了3种配置方式,在XML中显示配置、在Java的接口和类中实现配置、隐式Bean的发现机制和自动装配原则,在实际工作中,这三种方式都会被用到混合使用
- 基于约定优于配置的原则,最优先的应该是通过隐式Bean的发现机制和基于自动装配的原则,这样的好处是减小程序开发者的决定权,简单又不失灵活
- 在没有办法使用自动装配原则的情况下,应该优先考虑在Java接口和类中实现配置,这样的好处是减少XML配置的泛滥,例如一个父类有多个子类,通过IoC容器初始化一个父类,容器将无法知道使用哪个子类初始化,这个时候使用Java的注解配置指定
- 在上述方法都无法使用的情况下,可以使用XML进行配置,也可以使用Java配置文件的new关键字创建对象配置,例如在实际工作中常常会用到第三方类库,常常无法修改里面的代码,这个时候可以通过这样的方式配置
如果配置的类是开发者自身正在开发的项目,那么应该考虑以Java配置为主,而Java配置又分为自动装配和Bean名称配置,优先使用自动装配可以减少大量的XML配置,如果所需配置的类是第三方的建议使用XML或者Java配置文件的方式
通过XML配置装备Bean
在使用XML装配Bean需要定义对应的XML,这里需要引入对应的XML模式(XSD)文件
<?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">
<?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"><!-- 定义一个BeanPostProcessor的实现类,用于bean创建后的处理 --><bean id="beanPostProcessor" class="com.ssm.bean.BeanPostProcessorImpl" /><!-- 定义一个DisposableBean的实现类,用于bean销毁前的处理 --><bean id="disposableBean" class="com.ssm.bean.DisposableBeanImpl" /><!-- 配置一个具有属性的bean,表示豆浆的原材料配置 --><bean id="source" class="com.ssm.pojo.Source"><property name="bean" value="黑豆" /><property name="sugar" value="少糖" /><property name="size" value="大杯" /></bean><!-- 配置一个延迟初始化的bean,表示豆浆机II,使用了初始化和销毁方法 --><bean id="soybeanMilkMakerII" class="com.ssm.pojo.SoybeanMilkMakerII" lazy-init="true" init-method="init" destroy-method="destroy"><property name="beverageShop" value="贡茶" /><property name="source" ref="source" /></bean><!-- 定义一个RoleII类型的bean,通过构造器进行初始化 --><bean id="role1" class="com.ssm.pojo.RoleII"><!-- 构造器参数1:角色ID,这里设置为1 --><constructor-arg index="0" value="1" /><!-- 构造器参数2:角色名称,这里设置为"总经理" --><constructor-arg index="1" value="总经理" /><!-- 构造器参数3:角色描述,这里设置为"公司管理者" --><constructor-arg index="2" value="公司管理者" /></bean><!-- 定义一个RoleII类型的bean实例,id为2,角色名为"高级工程师",备注为"重要人员" --><bean id="role2" class="com.ssm.pojo.RoleII" scope="prototype"><!-- 设置角色id为2 --><property name="id" value="2" /><!-- 设置角色名为"高级工程师" --><property name="roleName" value="高级工程师" /><!-- 设置备注为"重要人员" --><property name="note" value="重要人员" /></bean>
</beans>
在文件的头部,引入了一个元素的定义,它是一个根元素,同时XSD文件也被引入,使用该文件所定义的元素可以定义对应的Spring Bean
装配简单值
<!-- 定义一个RoleII类型的bean实例,id为2,角色名为"高级工程师",备注为"重要人员" --><bean id="role2" class="com.ssm.pojo.RoleII"><!-- 设置角色id为2 --><property name="id" value="2" /><!-- 设置角色名为"高级工程师" --><property name="roleName" value="高级工程师" /><!-- 设置备注为"重要人员" --><property name="note" value="重要人员" /></bean>
对应pojo里实体类的构造函数,就很清楚配置项的涵义
/*** 带参数的构造方法,用于创建一个具有指定角色信息的角色对象。** @param id 角色的唯一标识符。* @param roleName 角色的名称。* @param note 对角色的备注说明。*/public RoleII(Long id, String roleName, String note) {this.id = id;this.roleName = roleName;this.note = note;}
id
,它是Spring找bean的时候会找它的编号,也就是这个id,但id不是一个必须的属性,如果没有声明它,Spring将采用"全限定名#{number}"的格式生成编号,例如上边这个例子,如果没有id="role2"
,那么Spring将会为这个bean生成一个编号"com.ssm.pojo.RoleII#0"
那么下一个没有声明id的bean就是"com.ssm.pojo.RoleII#1"
,很显然自动生成id并没有自己定义更清晰class
是pojo类的全限定名
这样定义很简单,稍微复杂一点的例如,在bean的配置中,需要注入一些自定义的类
<!-- 配置一个具有属性的bean,表示豆浆的原材料配置 --><bean id="source" class="com.ssm.pojo.Source"><property name="bean" value="黑豆" /><property name="sugar" value="少糖" /><property name="size" value="大杯" /></bean><!-- 配置一个延迟初始化的bean,表示豆浆机II,使用了初始化和销毁方法 --><bean id="soybeanMilkMakerII" class="com.ssm.pojo.SoybeanMilkMakerII" lazy-init="true" init-method="init" destroy-method="destroy"><property name="beverageShop" value="贡茶" /><property name="source" ref="source" /></bean>
如代码所示,首先定义了一个id
为source
的bean
,然后再通过ref
被id
为soybeanMilkMakerII
的bean
引入
看一下对应的Pojo类
package com.ssm.pojo;
/*** Source类用于表示饮品的来源信息。*/
public class Source {private String bean; // 饮品的类型private String sugar; // 饮品的糖分描述private String size; // 饮品的大小杯/*** 无参构造方法,用于创建一个新的Source对象。*/public Source() {System.out.println("构造方法.....");}public Source(String bean, String sugar, String size) {this.bean = bean;this.sugar = sugar;this.size = size;}/*** 获取饮品类型。** @return 返回当前饮品的类型。*/public String getBean() {return bean;}/*** 设置饮品类型。** @param bean 需要设置的饮品类型。*/public void setBean(String bean) {this.bean = bean;}/*** 获取饮品的糖分描述。** @return 返回当前饮品的糖分描述。*/public String getSugar() {return sugar;}/*** 设置饮品的糖分描述。** @param sugar 需要设置的饮品糖分描述。*/public void setSugar(String sugar) {this.sugar = sugar;}/*** 获取饮品的大小杯。** @return 返回当前饮品的大小杯。*/public String getSize() {return size;}/*** 设置饮品的大小杯。** @param size 需要设置的饮品大小杯。*/public void setSize(String size) {this.size = size;}
}
package com.ssm.pojo;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;/*** SoybeanMilkMakerII类实现了多种Spring接口,包括BeanNameAware, BeanFactoryAware,* ApplicationContextAware, InitializingBean,用于展示如何与Spring容器交互。* 这个类模拟了一个豆奶制作机,能够制作豆奶并提供自定义初始化和销毁逻辑。*/
public class SoybeanMilkMakerII implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean{private String beverageShop = null; // 饮品店品牌private Source source = null; // 果汁原料描述// Getter 方法public String getBeverageShop() {return beverageShop;}// Setter 方法,确保参数类型与getter返回类型匹配public void setBeverageShop(String beverageShop) {this.beverageShop = beverageShop;}//Getter methodpublic Source getSource() {return this.source;}// Setter methodpublic void setSource(Source source) {this.source = source;}/*** SoybeanMilkMakerII的构造方法,打印构造信息。*/public SoybeanMilkMakerII() {System.out.println("SoybeanMilkMakerII的构造方法");}/*** 制作豆奶的方法。* @return 返回一杯具有特定品牌、大小、糖分和水果的豆奶描述。*/public String makeSoybeanMilk() {String soybeanMilk = "这是一杯由" + beverageShop + "饮品店,提供的" + source.getSize() + source.getSugar() + source.getBean();return soybeanMilk;}/*** 自定义初始化方法,展示如何在Bean初始化后执行特定逻辑。*/public void init() {System.out.println("【" + this.getClass().getSimpleName() + "】执行自定义初始化方法");}/*** 自定义销毁方法,展示如何在Bean销毁前执行特定逻辑。*/public void destroy() {System.out.println("【" + this.getClass().getSimpleName() + "】执行自定义销毁方法");}/*** 实现BeanNameAware接口的方法,用于获取Bean的名称。* @param beanName Bean的名称。*/@Overridepublic void setBeanName(String beanName) {System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanNameAware接口的setBeanName方法");}/*** 实现BeanFactoryAware接口的方法,用于获取BeanFactory。* @param bf BeanFactory实例。* @throws BeansException 如果设置过程中发生错误。*/@Overridepublic void setBeanFactory(BeanFactory bf) throws BeansException {System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware接口的setBeanFactory方法");}/*** 实现ApplicationContextAware接口的方法,用于获取ApplicationContext。* @param ctx ApplicationContext实例。* @throws BeansException 如果设置过程中发生错误。*/@Overridepublic void setApplicationContext(ApplicationContext ctx) throws BeansException {System.out.println("【" + this.getClass().getSimpleName() + "】调用ApplicationContextAware接口的setApplicationContext方法");}/*** 实现InitializingBean接口的方法,用于在所有属性设置完成后执行初始化操作。* @throws Exception 如果初始化过程中发生错误。*/@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("【" + this.getClass().getSimpleName() + "】调用InitializingBean接口的afterPropertiesSet方法");}}
装配集合
有些时候要做复杂的装配工作,比如Set、Map、List、Array和Properties等,定义如下pojo
package com.ssm.pojo;import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/*** ComplexAssembly类用于演示装配复杂数据类型。* 该类包含了一系列属性,分别是:id、list、map、props、set和array。* 这些属性通过对应的setter和getter方法进行访问和设置。*/
public class ComplexAssembly {private Long id; // 唯一标识private List<String> list; // 字符串列表private Map<String, String> map; // 字符串键值对映射private Properties props; // 属性集合,常用于配置信息存储private Set<String> set; // 不含重复元素的集合private String[] array; // 字符串数组/*** 获取id属性。* @return 返回当前对象的唯一标识。*/public Long getId() {return id;}/*** 设置id属性。* @param id 欲设置的唯一标识。*/public void setId(Long id) {this.id = id;}/*** 获取list属性。* @return 返回当前对象的字符串列表。*/public List<String> getList() {return list;}/*** 设置list属性。* @param list 欲设置的字符串列表。*/public void setList(List<String> list) {this.list = list;}/*** 获取map属性。* @return 返回当前对象的字符串键值对映射。*/public Map<String, String> getMap() {return map;}/*** 设置map属性。* @param map 欲设置的字符串键值对映射。*/public void setMap(Map<String, String> map) {this.map = map;}/*** 获取props属性。* @return 返回当前对象的属性集合。*/public Properties getProps() {return props;}/*** 设置props属性。* @param props 欲设置的属性集合。*/public void setProps(Properties props) {this.props = props;}/*** 获取set属性。* @return 返回当前对象的不含重复元素的集合。*/public Set<String> getSet() {return set;}/*** 设置set属性。* @param set 欲设置的不含重复元素的集合。*/public void setSet(Set<String> set) {this.set = set;}/*** 获取array属性。* @return 返回当前对象的字符串数组。*/public String[] getArray() {return array;}/*** 设置array属性。* @param array 欲设置的字符串数组。*/public void setArray(String[] array) {this.array = array;}}
要装配这个pojo类,其xml配置如下
<!-- 定义一个复杂组件装配的bean -->
<bean id="complexAssembly"class="com.ssm.pojo.ComplexAssembly"><!-- 设置组件的id为1 --><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-map-1" /><entry key="key2" value="value-map-2" /><entry key="key3" value="value-map-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>
上面代码是对字符串的各个集合的装载,有些时候可能需要更复杂的装载,例如一个List可以是一个系列类的对象,又如一个Map集合类,键是一个类对象,值也是一个类对象
package com.ssm.pojo;public class RoleIII {private Long id; // 角色编号private String roleName; // 角色名称private String note; // 备注信息/*** 无参数构造方法,用于创建一个空的角色对象。*/public RoleIII() {}/*** 带参数的构造方法,用于创建一个具有指定属性的角色对象。* @param id 角色的唯一标识符。* @param roleName 角色的名称。* @param note 关于角色的备注信息。*/public RoleIII(Long id, String roleName, String note) {this.id = id;this.roleName = roleName;this.note = note;}/**** setters and getters ****/// 获取角色的IDpublic Long getId() {return id;}// 设置角色的IDpublic void setId(Long id) {this.id = id;}// 获取角色的名称public String getRoleName() {return roleName;}// 设置角色的名称public void setRoleName(String roleName) {this.roleName = roleName;}// 获取角色的备注信息public String getNote() {return note;}// 设置角色的备注信息public void setNote(String note) {this.note = note;}
}
package com.ssm.pojo;/*** User类用于表示用户信息。*/
public class User {private Long id; // 用户的唯一标识private String userName; // 用户名private String note; // 用户备注信息/*** 获取用户的唯一标识。* @return 用户的ID。*/public Long getId() {return id;}/*** 设置用户的唯一标识。* @param id 要设置的用户ID。*/public void setId(Long id) {this.id = id;}/*** 获取用户的用户名。* @return 用户的用户名。*/public String getUserName() {return userName;}/*** 设置用户的用户名。* @param userName 要设置的用户名。*/public void setUserName(String userName) {this.userName = userName;}/*** 获取用户的备注信息。* @return 用户的备注信息。*/public String getNote() {return note;}/*** 设置用户的备注信息。* @param note 要设置的用户备注信息。*/public void setNote(String note) {this.note = note;}}
package com.ssm.pojo;
import java.util.List;
import java.util.Map;
import java.util.Set;
/*** UserRoleAssembly 类用于组装和管理用户角色关系。* 它包含了对用户角色关系的不同类型集合的管理,如列表、映射和集合。*/
public class UserRoleAssembly {private Long id; // 唯一标识符private List<RoleIII> list; // 角色列表private Map<RoleIII, User> map; // 角色到用户的映射private Set<RoleIII> set; // 角色集合/**** setters and getters ****//*** 获取UserRoleAssembly的唯一标识符。* @return 返回此实例的唯一标识符。*/public Long getId() {return id;}/*** 设置UserRoleAssembly的唯一标识符。* @param id 要设置的唯一标识符。*/public void setId(Long id) {this.id = id;}/*** 获取角色列表。* @return 返回此实例的角色列表。*/public List<RoleIII> getList() {return list;}/*** 设置角色列表。* @param list 要设置的角色列表。*/public void setList(List<RoleIII> list) {this.list = list;}/*** 获取角色到用户的映射。* @return 返回此实例的角色到用户映射。*/public Map<RoleIII, User> getMap() {return map;}/*** 设置角色到用户的映射。* @param map 要设置的角色到用户映射。*/public void setMap(Map<RoleIII, User> map) {this.map = map;}/*** 获取角色集合。* @return 返回此实例的角色集合。*/public Set<RoleIII> getSet() {return set;}/*** 设置角色集合。* @param set 要设置的角色集合。*/public void setSet(Set<RoleIII> set) {this.set = set;}}
则XML需要如下配置
<!-- 定义Role实体 Bean -->
<bean id="role_1" class="com.ssm.pojo.RoleIII"><property name="id" value="1" /><property name="roleName" value="role_name_1" /><property name="note" value="role_note_1" />
</bean><bean id="role_2" class="com.ssm.pojo.RoleIII"><property name="id" value="2" /><property name="roleName" value="role_name_2" /><property name="note" value="role_note_2" />
</bean><!-- 定义User实体 Bean -->
<bean id="user_1" class="com.ssm.pojo.User"><property name="id" value="1" /><property name="userName" value="user_name_1" /><property name="note" value="role_note_1" />
</bean><bean id="user_2" class="com.ssm.pojo.User"><property name="id" value="2" /><property name="userName" value="user_name_2" /><property name="note" value="role_note_1" />
</bean><!-- 定义UserRoleAssembly实体 Bean,用于组装用户和角色的关系 -->
<bean id="userRoleAssembly"class="com.ssm.pojo.UserRoleAssembly"><property name="id" value="1" /><property name="list"><list><ref bean="role_1" /><ref bean="role_2" /></list></property><property name="map"><map><entry key-ref="role_1" value-ref="user_1" /><entry key-ref="role_2" value-ref="user_2" /></map></property><property name="set"><set><ref bean="role_1" /><ref bean="role_2" /></set></property>
</bean>
命名空间装配
Spring还提供了对应的命名空间的定义,在使用命名空间的时候要先引入对应的命名空间和XML模式(XSD)文件, 如下代码所示
<?xml version="1.0" encoding="UTF-8"?>
<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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">
xmlns:c="http://www.springframework.org/schema/c"xmlns:p="http://www.springframework.org/schema/p"
这两行定义了XML的命名空间,这样才能在内容里面使用p和c这样的前缀,如下代码所示
<!-- 使用构造方法注入创建Role对象 --><bean id="c_role" class="com.ssm.pojo.RoleIII" c:_0="8"c:_1="role_name_c" c:_2="role_note_c" /><!-- 使用setter注入创建Role对象 --><bean id="p_role" class="com.ssm.pojo.RoleIII" p:id="9"p:roleName="role_name_p" p:note="role_note_p" />
在这段XML配置中,我们通过标签创建了两个Role对象。这里涉及到了两种不同的属性注入方式:C风格和P风格。
- C风格的属性注入:
- c:_0=“8” 表示注入第一个属性,根据Role类的定义,这可能是id属性。
- c:_1=“role_name_c” 表示注入第二个属性,可能是roleName属性。
- c:_2=“role_note_c” 表示注入第三个属性,可能是note属性。
- P风格的属性注入:
- p:id=“9” 明确指出了注入的属性名为id,其值为9。
- p:roleName=“role_name_p” 明确指出了注入的属性名为roleName,其值为role_name_p。
- p:note=“role_note_p” 明确指出了注入的属性名为note,其值为role_note_p。
这种方式在配置时更加直观,可以根据属性名直接确定注入的值,便于理解和维护
如下是通过命名空间定义UserRoleAssembly
类实例
<?xml version='1.0' encoding='UTF-8' ?>
<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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util.xsd"><!-- 使用构造方法注入创建RoleIII对象,通过构造函数参数初始化对象 --><bean id="c_role" class="com.ssm.pojo.RoleIII"c:_0="8" c:_1="role_name_c" c:_2="role_note_c" /><!-- 使用setter方法注入创建RoleIII对象,通过调用setter方法初始化对象属性 --><bean id="p_role" class="com.ssm.pojo.RoleIII"p:id="9" p:roleName="role_name_p" p:note="role_note_p" /><!-- 装配多个RoleIII和User对象示例,演示了通过构造方法和setter方法的混合注入 --><bean id="role1" class="com.ssm.pojo.RoleIII"c:_0="1" c:_1="role_name_1" c:_2="role_note_1" /><bean id="role2" class="com.ssm.pojo.RoleIII"p:id="2" p:roleName="role_name_2" p:note="role_note_2" /><bean id="user1" class="com.ssm.pojo.User"p:id="1" p:userName="role_name_1" p:note="user_note_1" /><bean id="user2" class="com.ssm.pojo.User"p:id="2" p:userName="role_name_2" p:note="user_note_2" /><!-- 装配一个List对象,包含已定义的role1和role2两个Bean --><util:list id="list"><ref bean="role1" /><ref bean="role2" /></util:list><!-- 装配一个Map对象,将role和user Bean以键值对形式装配 --><util:map id="map"><entry key-ref="role1" value-ref="user1" /><entry key-ref="role2" value-ref="user2" /></util:map><!-- 装配一个Set对象,包含已定义的role1和role2两个Bean --><util:set id="set"><ref bean="role1" /><ref bean="role2" /></util:set><!-- 创建UserRoleAssembly对象,并将其与之前定义的List、Map、Set对象关联 --><bean id="userRoleAssembly"class="com.ssm.pojo.UserRoleAssembly"p:id="1" p:list-ref="list" p:map-ref="map" p:set-ref="set" /></beans>
通过注解装配Bean
实际上并不推荐使用XML的形式装配Bean,避免XML文件泛滥,使用注解的方式可以减少XML,注解的方式既能够实现XML的功能,也能够提供自动装配的功能,采用自动装配后,开发者所需做的决策就少了,更有利于程序的开发,这便是约定优于配置的理念
Spring提供了两种方式让Spring IoC容器发现Bean,大部分的项目都可以用Java配置完成,而不是XML,这样可以有效减少配置并避免引入大量XML,解决了在Sping3之前的版本需要配置大量XML的问题
- 组件扫描:通过定义资源的方式,让SpringIoC容器扫描对应的包,从而把Bean装配起来
- 自动装配:通过注解定义,使一些依赖关系可以通过注解完成
使用注解为主XML为辅的方式更加合理,比如当系统存在多个公共的配置文件时候(properties和xml),如果都写到注解里,显然代码里就分散了各种公共配置,不利于管理,这个时候XML的配置就更合理一些;又或者一些类来自第三方,而不是系统开发的配置文件,这时利用XML的方式会更加明确
使用注解@Component
装配Bean
package com.ssm.pojo;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** RoleIV类表示一个角色实体,它包含了角色的基本信息。* 通过Spring的@Component注解将其标记为一个Bean,名称为"role"。*/
@Component(value = "role")
public class RoleIV {// 通过@Value注解注入角色ID,这里硬编码为1。@Value("1")private Long id;// 通过@Value注解注入角色名称,这里硬编码为"admin"。@Value("admin")private String roleName;// 通过@Value注解注入角色备注信息,这里硬编码为"administrator"。@Value("administrator")private String note;/*** 无参构造函数,打印构造函数名称。*/public RoleIV(){System.out.println("RoleIV()");}/*** 带参数的构造函数,用于初始化角色信息。* * @param id 角色ID* @param roleName 角色名称* @param note 角色备注信息*/public RoleIV(Long id, String roleName, String note){System.out.println("RoleIV(Long id, String roleName, String note)");this.id = id;this.roleName = roleName;this.note = note;}/*** 获取角色名称。* * @return 角色名称*/public String getRoleName(){return roleName;}/*** 设置角色名称。* * @param roleName 要设置的角色名称*/public void setRoleName(String roleName){this.roleName = roleName;}/*** 获取角色备注信息。* * @return 角色备注信息*/public String getNote(){return note;}/*** 设置角色备注信息。* * @param note 要设置的角色备注信息*/public void setNote(String note){this.note = note;}/*** 获取角色ID。* * @return 角色ID*/public Long getId(){return id;}/*** 设置角色ID。* * @param id 要设置的角色ID*/public void setId(Long id){this.id = id;}/*** 重写toString方法,便于打印角色信息。* * @return 描述角色信息的字符串*/@Overridepublic String toString(){return "RoleIV [id=" + id + ", roleName=" + roleName + ", note=" + note + "]";}
}
@Component 是 Spring 框架中的一个注解,它是 Spring 的核心组件之一,用于标记一个 Java 类作为 Spring 容器管理的 Bean。当 Spring 容器启动时,它会扫描带有 @Component 注解的类,并将这些类实例化为 Bean,然后存储在 Spring 容器中,以便在需要时可以自动注入到其他依赖于它们的类中。
在代码中,@Component(value = “role”) 告诉 Spring 容器这个 RoleIV 类是一个 Bean,并且它的名称是 “role”。这样,其他类可以通过 @Autowired 或者 @Resource 注解来注入这个名为 “role” 的 Bean 实例,也可以简写成@Component(“role”),甚至不给value值,直接写成@Component,如果不写,SpringIoC容器就默认类名,以首字母小写的形式作为id,为其生成对象,装配到容器中
例如,如果你有一个其他类需要使用 RoleIV,可以这样做:
import org.springframework.beans.factory.annotation.Autowired;
import com.ssm.pojo.RoleIV;public class SomeClass {private RoleIV role;@Autowiredpublic void setRole(RoleIV role) {this.role = role;}// 其他方法...
}
Spring 会自动将 “role” Bean 注入到 SomeClass 的 role 字段中,无需手动创建 RoleIV 实例
然而有了这个pojo类还不够,SpringIoC并不知道去哪里扫描对象,这个时候可以使用一个java
Config告诉他
package com.ssm.pojo;import org.springframework.context.annotation.ComponentScan;/*** PojoConfig类用于配置POJO相关的设置。* 该类通过使用@ComponentScan注解来自动扫描并注册所有的组件,使得这些组件能够被Spring容器管理。**/
@ComponentScan
public class PojoConfig {
}
@ComponentScan
表示启动扫描,默认扫描当前包的路径,然后便可以通过Spring定义好的Spring IoC容器的实现类AnnotationConfigApplicationContext
初始化容器并生成Bean
了
package com.ssm.main;import com.ssm.pojo.RoleIV;
import com.ssm.pojo.PojoConfig;
import com.ssm.config.ApplicationConfig;
import java.sql.SQLException;import com.ssm.service.RoleIVService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;/*** 主函数类,用于演示基于注解的IoC容器的使用。*/
public class AnnotationMain {/*** 程序入口点。* @param args 命令行参数* @throws SQLException 抛出SQLException异常*/public static void main(String[] args) throws SQLException {testAnnotation();}/*** 测试注解配置的IoC容器功能。* 该方法不接受参数,也不返回任何值。* 主要步骤包括:* 1. 创建基于注解的IoC容器,并指定配置类;* 2. 通过容器获取RoleIV类型的Bean实例;* 3. 打印获取的Bean实例的id属性;* 4. 关闭容器。*/public static void testAnnotation() {// 创建基于注解的IoC容器,并指定配置类AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);// 通过容器获取RoleIV类型的Bean实例RoleIV role = context.getBean(RoleIV.class);// 打印获取的Bean实例的id属性System.out.println(role.getId());// 关闭容器context.close();}
}
以上代码只是一个简单的例子,@Component
存在几个非常实用的配置项,例如basePackages
,用于配置一个Java包的数组,Spring会根据它扫描对应的包和子包;例如basePackageClasses
,用于配置多个Java类,Spring会根据这个配置扫描,通过如下代码可以看到效果
首先定义接口,并定义接口方法,代码如下
package com.ssm.service;import com.ssm.pojo.RoleIV;/*** RoleService接口定义了角色服务的相关操作。*/
public interface RoleIVService {/*** 打印角色信息。* @param roleIV 角色信息实体,包含角色相关的详细信息。*/public void printRoleIVInfo(RoleIV roleIV);
}
Spring推荐使用接口定义方法,它可以将定义和实现分离,然后用集体的实现类实现
package com.ssm.service.iml;import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.stereotype.Component;/*** RoleIVService的实现类,提供具体的角色信息打印服务。*/
@Component
public class RoleIVServiceImpl implements RoleIVService{/*** 打印角色信息。* @param roleIV 角色信息对象,包含角色的ID、名称和备注。*/@Overridepublic void printRoleIVInfo(RoleIV roleIV){// 打印角色信息的方法实现System.out.println("RoleIVServiceImpl.printRoleInfo");System.out.println(roleIV.getId());System.out.println(roleIV.getRoleName());System.out.println(roleIV.getNote());}
}
这里注解@Component
表明它是一个Spring需要装配的Bean,而且也实现了对应的接口方法,然后使用@ComponentScan
加上对应的扫描配置,如下代码所示
package com.ssm.config;/*** ApplicationConfig类用于配置Spring框架的组件扫描。* 通过@ComponentScan注解来告诉Spring容器去扫描指定包下的所有组件。*/import org.springframework.context.annotation.ComponentScan;
import com.ssm.pojo.RoleIV; // 引入RoleIV类,用作示例
import com.ssm.service.iml.RoleIVServiceImpl; // 引入RoleIVServiceImpl类,用作示例@ComponentScan(basePackageClasses = {RoleIV.class, RoleIVServiceImpl.class})
// 通过指定具体的类,来让Spring容器扫描包含这些类的包及其子包下的所有组件。@ComponentScan(basePackages = {"com.ssm.pojo", "com.ssm.service"})
// 通过指定包的名称,来让Spring容器扫描这些包及其子包下的所有组件。@ComponentScan(basePackages = {"com.ssm.pojo"}, basePackageClasses = {RoleIVServiceImpl.class})
// 组合使用basePackages和basePackageClasses,让Spring容器扫描指定包及其子包下以及指定类所在包的组件。public class ApplicationConfig {}
// ApplicationConfig类结束
应该尽量避免多个注解配置,扫描混乱,尤其重复扫描,避免没必要的异常出现;另外如果包名经常变动的场景应尽量避免使用包来扫,往往包名改了如果配置没跟着都改IDE不报错,这时候用类来扫会更好IDE会报错
使用如下代码来验证效果
/*** 测试组件扫描功能。* 该方法通过注解配置的应用上下文来启动Spring容器,从容器中获取RoleIV和RoleIVService的实例,* 然后调用服务方法处理角色信息,最后关闭应用上下文。*/public static void testComponentScan() {// 创建注解配置的应用上下文,并指定应用配置类ApplicationConfigAnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);// 从应用上下文中获取RoleIV类型的Bean实例RoleIV role = context.getBean(RoleIV.class);// 从应用上下文中获取RoleIVService类型的Bean实例RoleIVService roleIVService = context.getBean(RoleIVService.class);// 调用服务方法,打印角色信息roleIVService.printRoleIVInfo(role);// 关闭应用上下文context.close();}
自动装配@Autowired
SpringIoC先完成Bean的定义,再初始化和寻找需要注入的资源,所谓自动装配就是由Spring发现对应的Bean,自动完成装配工作的方式,如下代码所示:
package com.ssm.service;/*** RoleIVServiceII接口定义了角色IV的相关服务操作*/public interface RoleIVServiceII {/*** 打印角色IV的信息* 该方法没有参数和返回值*/public void printRoleIVInfo();
}
其实现类代码如下
package com.ssm.service.iml;import com.ssm.service.RoleIVServiceII;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ssm.pojo.RoleIV;/*** RoleIVServiceIIImpl类实现了RoleIVServiceII接口,用于打印RoleIV对象的信息。* 通过@Autowired注解自动注入RoleIV对象。*/
@Component(value = "roleIVServiceII")
public class RoleIVServiceIIImpl implements RoleIVServiceII{@Autowiredprivate RoleIV roleIV; // 自动注入的RoleIV对象/*** 打印RoleIV对象的信息,包括ID、角色名和备注。* 该方法没有参数和返回值。*/@Overridepublic void printRoleIVInfo() {System.out.println(roleIV.getId() + " " + roleIV.getRoleName() + " " + roleIV.getNote());}
}
这里使用了@Autowired
,表示在SpringIoC定位所有的Bean后,这个字段需要按类型注入,然后Spring IoC容器会寻找资源找到后将其注入,也就是将实例roleIV注入进来,这里需要注意的是@Autowired
会按照类型进行注入
SpringIoC容器有时候会寻找失败,在默认情况下会抛异常,因为SpringIoC容器认为一定要找到对应的Bean注入这个属性,如果要改变这个特性,可以通过@Autowired
的配置项required改变它,类似这样@Autowired(required=false)
, 也就是说默认情况下值必须注入成功的,也就是这个required的值默认为true,声明修改为false后,表明如果在已经定义好的Bean中找不到对应的类型,则允许不注入,也不会抛异常,这个时候这个字段可能为空值,需要开发者自己校验,从而避免类似空指针异常等异常出现
注解@Autowired除可以配置在属性外,还允许方法配置,常见的Bean的setter方法也可以使用它来完成注入,如下代码所示:
package com.ssm.service.iml;import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVServiceII;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** 这是一个实现RoleIVServiceII接口的服务类,用于操作RoleIV实体。*/
@Component(value = "roleIVServiceII")
public class RoleIVServiceIIImplII implements RoleIVServiceII{private RoleIV roleIV; // RoleIV实体对象/*** 通过@Autowired注解自动注入RoleIV实体。* @param roleIV 要注入的RoleIV实体*/@Autowiredpublic void setRoleIV(RoleIV roleIV){this.roleIV = roleIV;}/*** 打印RoleIV实体的信息。* 该方法没有参数和返回值。*/public void printRoleIVInfo() {System.out.println(roleIV.getId() + " " + roleIV.getRoleName() + " " + roleIV.getNote());}
}
使用
@Autowired
这是SpringIoC自动装配完成的,使得配置大幅度减少,满足约定优于配置的原则,也不会降低程序的健壮性
自定义装配的歧义性(注解@Primary
和注解@Qualifier
)
Spring建议在大部分情况下使用接口编程,但定义一个接口并不一定只有一个实现类,例如接口
package com.ssm.service;import com.ssm.pojo.RoleIV;/*** RoleService接口定义了角色服务的相关操作。*/
public interface RoleIVService {/*** 打印角色信息。* @param roleIV 角色信息实体,包含角色相关的详细信息。*/public void printRoleIVInfo(RoleIV roleIV);
}
有了一个实现类
package com.ssm.service.iml;import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.stereotype.Component;/*** RoleIVService的实现类,提供具体的角色信息打印服务。*/
@Component
public class RoleIVServiceImpl implements RoleIVService{/*** 打印角色信息。* @param roleIV 角色信息对象,包含角色的ID、名称和备注。*/@Overridepublic void printRoleIVInfo(RoleIV roleIV){// 打印角色信息的方法实现System.out.println("RoleIVServiceImpl.printRoleInfo");System.out.println("id="+roleIV.getId());System.out.println("roleName="+roleIV.getRoleName());System.out.println("note="+roleIV.getNote());}
}
还有另一个实现类
package com.ssm.service.iml;import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.stereotype.Component;/*** RoleIVServiceImplII类实现了RoleIVService接口,* 用于打印RoleIV对象的相关信息。*/
@Component("RoleIVServiceImplII")
public class RoleIVServiceImplII implements RoleIVService {/*** 打印RoleIV对象的信息。** @param roleIV RoleIV对象,包含角色的id、角色名和备注信息。* 该方法会打印出这些信息的字符串表示。*/@Overridepublic void printRoleIVInfo(RoleIV roleIV) {// 通过字符串拼接的方式,将roleIV对象的id、roleName和note属性打印出来System.out.println("{id=" + roleIV.getId() + ", roleName=" + roleIV.getRoleName() + ", note=" + roleIV.getNote() + "}");}
}
这个时候新建一个类,代码如下
package com.ssm.controller;import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** RoleIVController类用于处理与RoleIV相关的请求。* 它通过注入RoleIVService来实现对RoleIV业务逻辑的调用。*/
@Component
public class RoleIVController {// 自动注入RoleIVService,以便在controller中使用@Autowiredprivate RoleIVService roleIVService;/*** 打印RoleIV的信息。* 该方法会调用roleIVService中的printRoleIVInfo方法,将roleIV的信息打印出来。* * @param roleIV 角色信息对象,包含角色的详细信息。*/public void printRoleIVInfo(RoleIV roleIV){roleIVService.printRoleIVInfo(roleIV);}
}
它有一个字段是RoleService类型,并且自动注入的属性是roleService,RoleService是接口类型,但RoleService有两个实现类,这个时候SpringIoC容器就会混乱了,它是无法判断入住哪个对象的,于是就会抛出异常,这样通过@Autowired
注入就会失败
究其原因,发生这样的情况是因为注解@Autowired
采用的是按类型注入对象的方式,一个接口可以有多个实现类,一个抽象类也可以有多个非抽象子类,但类型相同,无法获取唯一的实例,导致了异常情况
在SpringIoC底层容器接口BeanFactory的定义,存在一个通过类型获取Bean的方法即:
<T> T getBean(Class<T> requiredType) throws BeansException;
这样仅仅通过类型(RoleIVService.class)作为参数无法判断使用哪个具体类实例返回,这便是自动装备的歧义,为了解决歧义,Spring提供了注解
@Primary
和注解@Qualifier
用于消除歧义
@Primary
@Primary
注解代表优先的,表示当Spring IoC通过一个接口或者抽象类注入对象,发现多个实现类,不能做出决策时,注解@Primary
会告诉Spring IoC容器,优先将该类实例注入,如下代码所示
package com.ssm.service.iml;import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;/*** 这是一个实现RoleIVService接口的类,用于处理关于RoleIV实体的相关业务逻辑。* 通过实现printRoleIVInfo方法,打印RoleIV实体的信息。** @Component("roleIVServiceIII") 标注此对象是一个Bean,并且在Spring容器中的名称为"roleIVServiceIII"。* @Primary 标注此Bean在同一个接口的多个实现中具有优先权。*/
@Component("roleIVServiceIII")
@Primary
public class RoleIVServiceImplIII implements RoleIVService{/*** 打印RoleIV实体的信息。** @param roleIV RoleIV实体,包含id、角色名和备注信息。* 该方法不返回任何内容,仅将实体信息以特定格式打印到控制台。*/public void printRoleIVInfo(RoleIV roleIV){// 格式化输出RoleIV实体的详细信息System.out.println("{id=" + roleIV.getId() + ", roleName=" + roleIV.getRoleName() + ", note=" + roleIV.getNote() + "}");}
}
@Primary
告诉Spring IoC容器,当存在多个RoleIVService
的实现类时,优先将RoleIVServiceImplIII
的实例注入,然而@Primary
只能解决优先问题,解决不了选择问题,例如同一个接口两个实现类,都挂上@Primary
的话,代码可以这样写,但是注入的时候就会抛异常了
@Qualifier
出现歧义的一个重要原因是Spring在注入时是按类型,除了按类型查找Bean,SpringIoC容器底层的接口BeanFactory,也定义了按名称查找的方法<T> T getBean(String name, Class<T> requiredType) throws BeansException;
,注解@Qualifier
就是用来按名称查找的,修改RoleIVController
代码如下
package com.ssm.controller;import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;/*** RoleIVController类用于处理与RoleIV相关的请求。* 它通过注入RoleIVService来实现对RoleIV业务逻辑的调用。*/
@Component
public class RoleIVController {// 自动注入RoleIVService,以便在controller中使用@Autowired@Qualifier("roleIVServiceIII")private RoleIVService roleIVService;/*** 打印RoleIV的信息。* 该方法会调用roleIVService中的printRoleIVInfo方法,将roleIV的信息打印出来。** @param roleIV 角色信息对象,包含角色的详细信息。*/public void printRoleIVInfo(RoleIV roleIV){roleIVService.printRoleIVInfo(roleIV);}
}
这个时候IoC容器不会再按照类型的方式注入,而是按照名称的方式注入,就不会存在歧义,再明确一下
@Autowired
的注入规则,先按照类型匹配,如果只有一个满足的Bean,则直接将其注入,结束;如果找到多个匹配的Bean类型,那么会按属性名查找Bean,例如上边这个代码private RoleIVService roleIVService;
,就会先找roleIVService
这个字符串,如果可以找到则结束注入过程,在这两个情况都找不到,并且又不允许注入为空时则抛异常
装载带有参数的构造方法类
通常构造方法都是带参数的,而带参数的构造方法也允许通过注解注入,如下代码所示
package com.ssm.controller;import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleIVService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;/*** RoleIVController类,负责处理RoleIV相关的控制器逻辑。* 依赖于RoleIVService来执行具体的服务逻辑。*/
@Component
public class RoleIVController {// RoleIVService的实例,用于执行角色IV相关的服务操作。private RoleIVService roleIVService;/*** RoleIVController的构造函数,通过依赖注入的方式初始化roleIVService。** @param roleIVService 一个标记为@Qualifier("roleIVServiceIII")的RoleIVService实例,用于具体的服务逻辑处理。*/public RoleIVController(@Autowired @Qualifier("roleIVServiceIII") RoleIVService roleIVService){this.roleIVService = roleIVService;}/*** 打印RoleIV的信息。* 该方法会调用roleIVService中的printRoleIVInfo方法,将roleIV的信息打印出来。** @param roleIV 角色信息对象,包含角色的详细信息。*/public void printRoleIVInfo(RoleIV roleIV){roleIVService.printRoleIVInfo(roleIV);}
}
使用注解@Bean
装配
以上都是通过@Component
装配Bean,但注解@Component
只能注解在类上,不能注解到方法上,对于Java而言大部分的开发都需要引入第三方的包,而且往往并没有这些包的资源,这时候将无法为这些包的类加入注解@Component
,从而让它成为开发环境的Bean
这种情况开发者可以使用新类扩展(extends)其包内的类,然后在新类上使用注解@Component
,但这样又会显得很奇怪,这个场景中Spring给了一个注解@Bean
,它可以注解到方法上,并将方法返回的对象作为Spring的Bean,存放在IoC的容器中
例如如我需要使用DBCP数据源,就要引入关于它的包,然后装配数据源的Bean,如下代码所示
package com.ssm.config;import java.util.Properties;
import javax.sql.DataSource;import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;/*** 数据源配置类,用于配置数据库连接池。*/
public class DataSourceConfig {// 通过注解方式注入MySQL驱动类名@Value("com.mysql.jdbc.Driver")private String driverClassName = null;// 通过注解方式注入数据库URL@Value("jdbc:mysql://localhost:3306/ssm")private String url = null;// 通过注解方式注入数据库用户名@Value("root")private String username = null;// 通过注解方式注入数据库密码@Value("Ms123!@#")private String password = null;/*** 创建并返回DataSource Bean。** @return 返回配置好的DataSource实例。*/@Bean("dataSource")public DataSource getDataSource(){Properties props = new Properties();// 设置数据库连接池的属性props.setProperty("driverClassName", driverClassName);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;}
}
这样就能够装配一个Bean,当SpringIoC容器扫描它的时候,就会为其生成对应的Bean,和其他的Bean一样,它可以通过@Autowired
或者@Qualifier
等注解注入别的Bean中
注解自定义Bean的初始化和销毁方法
在Spring框架中,@Bean注解告诉Spring容器这个方法将会返回一个需要被管理的对象,即Bean,在该注解中可以自定义初始化方法和销毁方法等一系列操作,只需要运用注解@Bean
的配置项
- Bean的名称:默认情况下,
@Bean
注解的方法名就是Bean的ID。如果需要自定义Bean的名称,可以使用name
属性, 如@Bean(name = "myBean")
。如果有多个@Bean方法返回相同类型,可以通过@Qualifier注解来区分它们。 - 初始化和销毁方法:
initMethod
和destroyMethod
属性可以用来指定Bean初始化和销毁时要调用的方法。例如,@Bean(initMethod = "init", destroyMethod = "cleanup")
。 - 依赖注入:Spring会自动处理
@Bean
方法之间的依赖关系,通过方法调用来注入。例如,如果Bean A
需要Bean B
,可以直接在A的@Bean
方法中调用B的@Bean方法
。如果需要注入其他已经注册的Bean,可以使用@Autowired
注解,或者通过@Qualifier
来指定特定的Bean。 - 作用域:默认情况下,
@Bean
创建的Bean是单例(Singleton)
的。如果你想创建原型(Prototype)
或者其他作用域的Bean,可以使用scope
属性,如@Bean(scope = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
。 - autowireCandidate属性是一个布尔值,它用于控制Spring容器在自动装配其他Bean时是否考虑当前Bean。默认情况下,autowireCandidate是true,意味着这个Bean可以作为自动装配的候选者。如果设置为false,那么这个Bean将被排除在自动装配的候选列表之外。当你有一个Bean,你并不希望它在自动装配过程中被其他组件使用,或者你有多个相同类型的Bean,但只想让其中一个参与自动装配,这时就可以使用autowireCandidate。
- 属性值注入:可以使用
@Value
注解来注入属性值,比如常量或环境变量。对于复杂的属性配置,可以使用@ConfigurationProperties
注解配合YAML
或Properties
文件来绑定。 - Profile:
@Profile
注解允许你在特定的环境中激活或禁用Bean
。例如,@Bean(@Profile("dev"))
将只在开发环境下创建Bean。 - 懒加载:使用
@Lazy
注解标记的@Bean
表示该Bean将在第一次请求时才创建,而不是在容器启动时立即创建。 - 复合Bean:
@Bean
方法可以返回另一个@Bean
方法的引用,实现Bean的组合。 - 自定义初始化逻辑:
@Bean
方法体中的代码会作为Bean的初始化逻辑执行。 - 代理模式:Spring支持JDK动态代理和CGLIB代理,你可以通过
proxyMode
属性来控制。
@Bean
注解通常与@Configuration
注解一起使用,后者标识一个类作为配置源,提供了声明式的方式来定义Bean。这种方式比XML配置更简洁且易于维护
/*** 创建并配置 SoybeanMilkMakerII 实例的 Bean。** @return SoybeanMilkMakerII 实例,配置了特定的饮料店名称和制作原料来源。* @see SoybeanMilkMakerII*/@Bean(name = "soybeanMilkMakerII", initMethod = "init", destroyMethod = "Mydestroy")public SoybeanMilkMakerII getSoybeanMilkMakerII() {// 创建 SoybeanMilkMakerII 实例SoybeanMilkMakerII soybeanMilkMakerII = new SoybeanMilkMakerII();// 设置所属饮料店为"贡茶"soybeanMilkMakerII.setBeverageShop("贡茶");// 创建原料来源并配置其属性Source source = new Source("豆子", "少糖", "中杯");soybeanMilkMakerII.setSource(source);return soybeanMilkMakerII;}
混合使用装配Bean
在使用注解的前提下,有些场景使用XML会更合适,这就产生了两个共同存在的情况,例如当引入了第三方包或者服务的时候,如果使用注解,那么这个第三方的配置可能会散落在各个地方,难于管理,这种情况下用XML会更合适
例如如下代码
package com.ssm.config;import java.util.Properties;
import javax.sql.DataSource;import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;/*** 数据源配置类,用于配置数据库连接池。*/
public class DataSourceConfig {// 通过注解方式注入MySQL驱动类名@Value("com.mysql.jdbc.Driver")private String driverClassName = null;// 通过注解方式注入数据库URL@Value("jdbc:mysql://localhost:3306/ssm")private String url = null;// 通过注解方式注入数据库用户名@Value("root")private String username = null;// 通过注解方式注入数据库密码@Value("xxxxxx")private String password = null;/*** 创建并返回DataSource Bean。** @return 返回配置好的DataSource实例。*/@Bean("dataSource")public DataSource getDataSource(){Properties props = new Properties();// 设置数据库连接池的属性props.setProperty("driverClassName", driverClassName);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 version='1.0' encoding='UTF-8' ?>
<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"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.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd "><bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver" /><property name="url" value="jdbc:mysql://localhost:3306/ssm" /><property name="username" value="root" /><property name="password" value="xxxxx" /></bean></beans>
假设有这个xml,xml定义了一个bean,其内容是数据库连接配置,就可以将这个xml引入到注解的体系中,如下代码所示
package com.ssm.config;/*** ApplicationConfig类用于配置Spring框架的组件扫描。* 通过@ComponentScan注解来告诉Spring容器去扫描指定包下的所有组件。*/import org.springframework.context.annotation.ComponentScan;
import com.ssm.pojo.RoleIV; // 引入RoleIV类,用作示例
import com.ssm.service.iml.RoleIVServiceImpl; // 引入RoleIVServiceImpl类,用作示例
import org.springframework.context.annotation.ImportResource;@ComponentScan(basePackageClasses = {RoleIV.class, RoleIVServiceImpl.class})
// 通过指定具体的类,来让Spring容器扫描包含这些类的包及其子包下的所有组件。@ComponentScan(basePackages = {"com.ssm.pojo", "com.ssm.service"})
// 通过指定包的名称,来让Spring容器扫描这些包及其子包下的所有组件。@ComponentScan(basePackages = {"com.ssm.pojo"}, basePackageClasses = {RoleIVServiceImpl.class})
// 组合使用basePackages和basePackageClasses,让Spring容器扫描指定包及其子包下以及指定类所在包的组件。
/*** 导入资源文件到Spring上下文中。* 该注解用于指定Spring配置文件的路径,以便Spring容器在启动时加载该配置文件。* 具体路径为classpath下的'spring-data.xml'文件。* 该注解通常用于配置与数据访问相关的bean,例如Hibernate的sessionFactory或JPA的entityManagerFactory。*/
@ImportResource({"classpath:spring-data.xml"})public class ApplicationConfig {}
// ApplicationConfig类结束
@ImportResource
中配置的内容是个数组,它可以配置多个XML配置文件,这样就可以引入多个XML定义的Bean了
先定义一个接口,如下所示
/*** RoleDataSourceService接口定义了角色数据源服务的相关操作。* 该接口主要负责通过ID获取角色信息的具体实现。*/
package com.ssm.service;import com.ssm.pojo.RoleIV;public interface RoleDataSourceService {/*** 根据指定ID获取角色信息。** @param id 角色的唯一标识符,类型为long。* @return 返回对应ID的角色信息,类型为RoleIV。*/public RoleIV getRoleIV(Long id);
}
其实现类代码如下:
package com.ssm.service.iml;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;import javax.sql.DataSource;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleDataSourceService;/*** 实现RoleDataSourceService接口,提供获取角色信息的功能*/
@Service
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class RoleDataSourceServiceImpl implements RoleDataSourceService {// 通过自动装配获取数据源@AutowiredDataSource dataSource = null;/*** 根据角色ID获取角色信息* @param id 角色的ID* @return 返回对应的角色信息,如果没有找到则返回null*/@Overridepublic RoleIV getRoleIV(Long id) {Connection conn = null;ResultSet rs = null;PreparedStatement ps = null;RoleIV roleIV = null;try {// 获取数据库连接conn = dataSource.getConnection();// 构造查询SQL语句String sql = "select id, role_name, note from t_role where id = ?";ps = conn.prepareStatement(sql);// 设置查询参数ps.setLong(1, id);// 执行查询rs = ps.executeQuery();// 处理查询结果while (rs.next()) {// 构建角色对象roleIV = new RoleIV();roleIV.setId(rs.getLong("id"));roleIV.setRoleName(rs.getString("role_name"));roleIV.setNote(rs.getString("note"));}} catch (SQLException e) {e.printStackTrace();} finally {// 关闭数据库连接及相关资源if (conn != null) {try {conn.close();} catch (SQLException e) {e.printStackTrace();}}}return roleIV;}
}
通过这样的形式就把XML配置的dataSource注入
RoleIVDataSourceServiceImpl
了,而有的时候所有的配置都放在一个ApplicationConfig类里会造成配置过多且复杂,因此开发者可能希望有多个类似于ApplicationConfig的配置类,例如ApplicationConfig2、ApplicationConfig3等,Spring也提供了这个机制,使用@Import
的方式注入这些配置类
package com.ssm.config;/*** ApplicationConfig类用于配置Spring框架的组件扫描。* 通过@ComponentScan注解来告诉Spring容器去扫描指定包下的所有组件。*/import com.ssm.pojo.PojoConfig;
import org.springframework.context.annotation.ComponentScan;
import com.ssm.pojo.RoleIV; // 引入RoleIV类,用作示例
import com.ssm.service.iml.RoleIVServiceImpl; // 引入RoleIVServiceImpl类,用作示例
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;@ComponentScan(basePackageClasses = {RoleIV.class, RoleIVServiceImpl.class})
// 通过指定具体的类,来让Spring容器扫描包含这些类的包及其子包下的所有组件。@ComponentScan(basePackages = {"com.ssm.pojo", "com.ssm.service"})
// 通过指定包的名称,来让Spring容器扫描这些包及其子包下的所有组件。@ComponentScan(basePackages = {"com.ssm.pojo"}, basePackageClasses = {RoleIVServiceImpl.class})
// 组合使用basePackages和basePackageClasses,让Spring容器扫描指定包及其子包下以及指定类所在包的组件。
/*** 导入资源文件到Spring上下文中。* 该注解用于指定Spring配置文件的路径,以便Spring容器在启动时加载该配置文件。* 具体路径为classpath下的'spring-data.xml'文件。* 该注解通常用于配置与数据访问相关的bean,例如Hibernate的sessionFactory或JPA的entityManagerFactory。*/
@ImportResource({"classpath:spring-data.xml"})
/*** 使用@Import注解引入配置类* 该注解用于指定应用在启动时需要加载的配置类。在这里,我们引入了两个配置类:* 1. DataSourceConfig.class:用于数据源的配置,例如数据库连接池的配置。* 2. PojoConfig.class:用于POJO(Plain Old Java Object)的配置,例如实体类的映射配置。* 这两个配置类会被Spring上下文加载,以便应用在运行时可以使用其中定义的配置。*/
@Import({DataSourceConfig.class, PojoConfig.class})public class ApplicationConfig {}
// ApplicationConfig类结束
在XML中也已使用这个机制,如下代码所示,在spring-bean.xml中引入spring-datasource.xml,就可以在spring-bean.xml中使用import元素来加载,如代码所示<import resource="spring-datasource.xml">
;Spring不支持使用XML加载Java配置类,但Spring支持通过XML的配置扫描注解的包,如代码所示<context:component-scan base-package="com.ssm"/>
使用Profile
实际开发中,都存在多个环境,这样就有了在不同的系统中进行切换的需求,Spring也支持这样的场景,在Spring中我们可以定义Bean的Profile
使用注解@Profile
配置
package com.ssm.bean;
import java.util.Properties;import javax.sql.DataSource;import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
/*** ProfileDataSource类用于根据不同的环境配置数据源。* 通过@Profile注解,实现了根据Spring环境(dev或test)动态配置数据源。*/
@Component
public class ProfileDataSource {/*** 当环境为dev时,创建并返回一个数据源。** @return DataSource 返回一个数据源实例。*/@Bean(name = "devDataSource")@Profile("dev")public DataSource getDevDataSource() {System.out.println("dev datasource");// 配置数据库连接属性Properties props = new Properties();props.setProperty("driver", "com.mysql.jdbc.Driver");props.setProperty("url", "jdbc:mysql://localhost:3306/ssm");props.setProperty("username", "root");props.setProperty("password", "123456");DataSource dataSource = null;// 尝试根据配置创建数据源try {dataSource = BasicDataSourceFactory.createDataSource(props);} catch (Exception e) {e.printStackTrace();}return dataSource;}/*** 当环境为test时,创建并返回一个数据源。** @return DataSource 返回一个数据源实例。*/@Bean(name = "testDataSource")@Profile("test")public DataSource getTestDataSource() {System.out.println("test datasource");// 配置数据库连接属性,此处与dev环境配置相同,实际应用中可能不同Properties props = new Properties();props.setProperty("driver", "com.mysql.jdbc.Driver");props.setProperty("url", "jdbc:mysql://localhost:3306/ssm");props.setProperty("username", "root");props.setProperty("password", "123456");DataSource dataSource = null;// 尝试根据配置创建数据源try {dataSource = BasicDataSourceFactory.createDataSource(props);} catch (Exception e) {e.printStackTrace();}return dataSource;}
}
如代码所示,使用注解@Profile
配置了两个环境的数据库连接池
使用XML定义Profile
<?xml version='1.0' encoding='UTF-8' ?>
<!-- 定义Spring配置文件,指定配置文件的版本和命名空间 -->
<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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsd"profile="dev"><!-- 定义数据源bean,使用Apache Commons DBCP2提供的BasicDataSource --><bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"><!-- 设置数据库驱动类名 --><property name="driverClassName" value="com.mysql.jdbc.Driver" /><!-- 设置数据库连接URL --><property name="url" value="jdbc:mysql://localhost:3306/ssm" /><!-- 设置数据库用户名 --><property name="username" value="root" /><!-- 设置数据库密码 --><property name="password" value="123456" /></bean>
</beans>
如上代码配置了一个Profile为dev的数据源,也可以配置多个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:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsd"><!-- 定义在测试环境下的数据源配置 --><beans profile="test"><bean id="devDataSource" class="org.apache.commons.dbcp2.BasicDataSource"><!-- 数据库驱动类名 --><property name="driverClassName" value="com.mysql.jdbc.Driver" /><!-- 数据库连接URL --><property name="url" value="jdbc:mysql://localhost:3306/ssm" /><!-- 数据库用户名 --><property name="username" value="root" /><!-- 数据库密码 --><property name="password" value="123456" /></bean></beans><!-- 定义在开发环境下的数据源配置,配置内容与测试环境相同 --><beans profile="dev"><bean id="devDataSource" class="org.apache.commons.dbcp2.BasicDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver" /><property name="url" value="jdbc:mysql://localhost:3306/ssm" /><property name="username" value="root" /><property name="password" value="123456" /></bean></beans>
</beans>
如果使用的IDE是Idea,会弹出通知,点开后便可以选择配置
启动Profile
当启动Java或者XML配置的Profile时,这些Bean并不会被加载到Spring IoC容器中,需要自行激活Profile,常见激活Profile的方法有5种:
- 在使用Spring MVC的情况下,可以配置Web上下文参数或者配置DispatchServlet参数
- 作为JNDI条目
- 配置环境变量
- 配置JVM启动参数
- 在集成测试环境中使用注解@ActiveProfile
package com.ssm;import javax.sql.DataSource;import com.ssm.config.ApplicationConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class) // 使用Spring提供的JUnit运行器
@ContextConfiguration(classes= ApplicationConfig.class) // 指定Spring配置类
@ActiveProfiles("test") // 指定使用的Spring Profile为"test"
public class ProfileTest {@Autowiredprivate DataSource dataSource; // 自动注入数据源/*** 测试方法,用于验证在“test”环境下数据源是否能被正确注入。* 该方法没有参数和返回值。*/@Testpublic void test() {System.out.println(dataSource.getClass().getName()); // 输出数据源类的名称}
}
以上代码是通过@ActiveProfiles
注解来指定加载哪个Profile,有些时候程序要在一些服务器上运行,这个时候可以配置Java虚拟机的启动项,例如程序在Tomcat服务器上或者main方法上运行,Java虚拟机的参数如下:
- spring.profiles.active:如果配置了该参数,那么spring.profiles.default配置项将失效
- spring.profiles.default:默认的配置,如果没有配置关于Profile的参数,就使用这个默认配置
在上边这个例子中,配置应该是JAVA_OPTS="-Dspring.profiles.active=test"
都可以配置启动项,也可以根据自己的项目属性自行配置
如果是使用Spring MVC的Web项目,也可以设置Web环境参数或者DispatcherServlet参数,选择对应的Profile,例如修改web.xml配置Profile
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><!-- 配置Spring IoC配置文件路径 --><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/applicationContext.xml</param-value></context-param><!-- 设置Spring环境配置参数 --><context-param><!-- 参数名称:指定Spring环境的活动配置文件 --><param-name>spring.profiles.active</param-name><!-- 参数值:定义当前活动的环境配置,此处为'test'环境 --><param-value>test</param-value></context-param><!-- 配置ContextLoaderListener用以初始化Spring IoC容器 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- 配置DispatcherServlet --><servlet><!-- 注意:Spring MVC框架会根据servlet-name配置,找到/WEB-INF/dispatcher-servlet.xml作为配置文件载入Web工程中 --><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 初始化参数配置 --><init-param><!-- 参数名称,用于指定Spring环境配置的激活 profil --><param-name>spring.profiles.active</param-name><!-- 参数值,此处设置为"test",表示激活的环境配置为test --><param-value>test</param-value></init-param><!-- 使得Dispatcher在服务器启动的时候就初始化 --><load-on-startup>2</load-on-startup></servlet><!-- Servlet拦截配置 --><servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
</web-app>
注意顺序,顺序反了要飘红的
加载属性文件
使用注解方式加载属性文件
同样是在ApplicationConfig.java文件中写入如下配置
/*** 使用该注解来向Spring Boot应用添加一个属性源。它会尝试从类路径下的"database-config.properties"文件中加载属性,* 并将其命名为"database.properties"。如果文件找不到,则不会报错,因为设置了ignoreResourceNotFound为true。* 该属性源的编码格式为UTF-8。** 该注解通常用于配置类上,以在Spring应用启动时加载额外的配置属性。*/
@PropertySource(value = "classpath:database-config.properties",name = "database.properties",ignoreResourceNotFound = true,encoding = "UTF-8"
)
这样还不够,Spring还无法将属性读入,因为缺少属性文件的解析配置器,即PropertySourcesPlaceHolderConfigurer
,需在继续添加如下代码
package com.ssm.config;/*** ApplicationConfig类用于配置Spring框架的组件扫描。* 通过@ComponentScan注解来告诉Spring容器去扫描指定包下的所有组件。*/import com.ssm.condition.DataSourceCondition;
import com.ssm.pojo.PojoConfig;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import com.ssm.pojo.RoleIV; // 引入RoleIV类,用作示例
import com.ssm.service.iml.RoleIVServiceImpl; // 引入RoleIVServiceImpl类,用作示例
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;import javax.sql.DataSource;
import java.util.Properties;@ComponentScan(basePackageClasses = {RoleIV.class, RoleIVServiceImpl.class})
// 通过指定具体的类,来让Spring容器扫描包含这些类的包及其子包下的所有组件。@ComponentScan(basePackages = {"com.ssm.pojo", "com.ssm.service"})
// 通过指定包的名称,来让Spring容器扫描这些包及其子包下的所有组件。@ComponentScan(basePackages = {"com.ssm.pojo"}, basePackageClasses = {RoleIVServiceImpl.class})
// 组合使用basePackages和basePackageClasses,让Spring容器扫描指定包及其子包下以及指定类所在包的组件。
/*** 导入资源文件到Spring上下文中。* 该注解用于指定Spring配置文件的路径,以便Spring容器在启动时加载该配置文件。* 具体路径为classpath下的'spring-data.xml'文件。* 该注解通常用于配置与数据访问相关的bean,例如Hibernate的sessionFactory或JPA的entityManagerFactory。*/
@ImportResource({"classpath:spring-data.xml"})
/*** 使用@Import注解引入配置类* 该注解用于指定应用在启动时需要加载的配置类。在这里,我们引入了两个配置类:* 1. DataSourceConfig.class:用于数据源的配置,例如数据库连接池的配置。* 2. PojoConfig.class:用于POJO(Plain Old Java Object)的配置,例如实体类的映射配置。* 这两个配置类会被Spring上下文加载,以便应用在运行时可以使用其中定义的配置。*/
@Import({DataSourceConfig.class, PojoConfig.class})/*** 使用该注解来向Spring Boot应用添加一个属性源。它会尝试从类路径下的"database-config.properties"文件中加载属性,* 并将其命名为"database.properties"。如果文件找不到,则不会报错,因为设置了ignoreResourceNotFound为true。* 该属性源的编码格式为UTF-8。** 该注解通常用于配置类上,以在Spring应用启动时加载额外的配置属性。*/
@PropertySource(value = "classpath:database-config.properties",name = "database.properties",ignoreResourceNotFound = true,encoding = "UTF-8"
)public class ApplicationConfig {/*** 创建并返回一个PropertySourcesPlaceholderConfigurer的实例。* 这个配置器是用来处理属性文件占位符的,它可以在Spring配置文件中启用属性文件的引用。* 通过这个方法,可以在Spring Bean的配置中使用占位符来引用属性文件中的属性值。** @return PropertySourcesPlaceholderConfigurer 返回一个配置了属性文件解析器的实例。*/@Beanpublic PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {return new PropertySourcesPlaceholderConfigurer();}/*** 创建并配置数据源 bean。** @param driver JDBC驱动程序的类名。* @param url 数据库连接的URL。* @param username 连接数据库所需的用户名。* @param password 连接数据库所需的密码。* @return 配置好的数据源实例。*/@Bean(name = "dataSource")public DataSource getDataSource(// 从属性文件中注入的JDBC配置@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;}}
测试配置代码如下
/*** 测试应用程序配置属性的方法。* 该方法通过IoC容器获取应用程序配置中的数据库连接信息,并打印出来。** @throws SQLException 如果获取数据库连接或元数据时发生错误*/public static void testProperties() throws SQLException {// 创建IoC容器,使用注解配置ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);// 获取应用程序的环境配置Environment env = context.getEnvironment();// 从环境配置中获取jdbc数据库urlString url = env.getProperty("jdbc.database.url");// 从IoC容器中获取DataSource BeanDataSource ds = context.getBean(DataSource.class);// 打印数据库连接的URLSystem.out.println(ds.getConnection().getMetaData().getURL());}
在ApplicationConfig.java文件中写了如下方法,并加上了@Bean注解
/*** 创建并返回一个PropertySourcesPlaceholderConfigurer的实例。* 这个配置器是用来处理属性文件占位符的,它可以在Spring配置文件中启用属性文件的引用。* 通过这个方法,可以在Spring Bean的配置中使用占位符来引用属性文件中的属性值。** @return PropertySourcesPlaceholderConfigurer 返回一个配置了属性文件解析器的实例。*/@Beanpublic PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {return new PropertySourcesPlaceholderConfigurer();}
其作用是让Spring能够解析属性占位符,能够解析属性占位符,Spring提供了@Value
注解和占位符的形式,来使用占位符
先看一下属性文件database-config.properties
情况
jdbc.database.driver=com.mysql.cj.jdbc.Driver
jdbc.database.url=jdbc:mysql://localhost:3306/ssm
jdbc.database.username=root
jdbc.database.password=Ms123!@#
package com.ssm.config;import java.util.Properties;
import javax.sql.DataSource;import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;/*** 数据源配置类,用于配置数据库连接池。*/
@Component
public class DataSourceConfigII {// 通过注解方式注入MySQL驱动类名@Value("${jdbc.database.driver}")private String driverClassName = null;// 通过注解方式注入数据库URL@Value("${jdbc.database.url}")private String url = null;// 通过注解方式注入数据库用户名@Value("${jdbc.database.username}")private String username = null;// 通过注解方式注入数据库密码@Value("${jdbc.database.password}")private String password = null;/*** 创建并返回DataSource Bean。** @return 返回配置好的DataSource实例。*/@Bean("dataSource")public DataSource getDataSource(){Properties props = new Properties();// 设置数据库连接池的属性props.setProperty("driverClassName", driverClassName);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的方式也可以加载属性文件,只需要使用<comtext: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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsd"><!-- 配置属性占位符,指定属性文件位置并设置是否忽略未找到的资源 --><context:property-placeholderignore-resource-not-found="false"location="classpath:database-config.properties" /><!-- 数据源配置,使用Apache Commons DBCP2提供的BasicDataSource实现 --><bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"><!-- 通过属性文件中的配置动态设置数据源的属性 --><property name="driverClassName" value="${jdbc.database.driver}" /><property name="url" value="${jdbc.database.url}" /><property name="username" value="${jdbc.database.username}" /><property name="password" value="${jdbc.database.password}" /></bean>
</beans>
特别要注意ignore-resource-not-found="false"
, 表示是否允许文件不存在,为false时不允许文件不存在,如果不存在Spring会抛出异常location="classpath:database-config.properties"
location是文件路径,可以配置单个或多个,用逗号隔开即可
也可以使用如下方式,避免配置多个属性文件的时候过长,可读性差
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsd"><!-- 配置属性占位符,指定属性文件位置并设置是否忽略未找到的资源 --><!--<context:property-placeholderignore-resource-not-found="false"location="classpath:database-config.properties" />--><!-- 数据源配置,使用Apache Commons DBCP2提供的BasicDataSource实现 --><bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"><!-- 通过属性文件中的配置动态设置数据源的属性 --><property name="driverClassName" value="${jdbc.database.driver}" /><property name="url" value="${jdbc.database.url}" /><property name="username" value="${jdbc.database.username}" /><property name="password" value="${jdbc.database.password}" /></bean><!-- 定义一个bean来配置属性源占位符解析器 --><bean id="propertySourcesPlaceholderConfigurer"class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"><!-- 配置属性文件的位置 --><property name="locations"><array><!-- 指定数据库配置文件的位置 --><value>classpath:database-config.properties</value><!-- 指定日志配置文件的位置 --><value>log4j.properties</value></array></property><!-- 配置是否忽略资源未找到的错误 --><property name="ignoreResourceNotFound" value="true" /></bean>
</beans>
条件化装配Bean
在某些条件下,不需要装配某些Bean,比如当没有database-config.properties
属性配置时,就不需要创建数据源,需要有个条件判断,Spring提供了注解@Conditional去配置,通过它可以配置一个或者多个类,只需要这些类实现实现Condition接口即可(org.springframework.context.annotation.Condition
),代码如下:
package com.ssm.condition;import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.stereotype.Component;/*** 数据源条件类,用于根据环境配置决定是否加载特定的Bean。* 实现了Spring的Condition接口,重写了matches方法来判断条件是否满足。*/
@Component
public class DataSourceCondition implements Condition {/*** 判断条件是否满足。* @param context 条件上下文,提供了环境和类型元数据的信息。* @param metadata 注解类型元数据,用于获取类上的注解信息。* @return boolean 返回true表示条件满足,false表示条件不满足。*/@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Environment env = context.getEnvironment();// 检查环境配置中是否包含了必要的jdbc属性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否则不创建,接着就可以配置数据源了
package com.ssm.config;/*** ApplicationConfig类用于配置Spring框架的组件扫描。* 通过@ComponentScan注解来告诉Spring容器去扫描指定包下的所有组件。*/import com.ssm.condition.DataSourceCondition;
import com.ssm.pojo.PojoConfig;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import com.ssm.pojo.RoleIV; // 引入RoleIV类,用作示例
import com.ssm.service.iml.RoleIVServiceImpl; // 引入RoleIVServiceImpl类,用作示例
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;import javax.sql.DataSource;
import java.util.Properties;@ComponentScan(basePackageClasses = {RoleIV.class, RoleIVServiceImpl.class})
// 通过指定具体的类,来让Spring容器扫描包含这些类的包及其子包下的所有组件。@ComponentScan(basePackages = {"com.ssm.pojo", "com.ssm.service"})
// 通过指定包的名称,来让Spring容器扫描这些包及其子包下的所有组件。@ComponentScan(basePackages = {"com.ssm.pojo"}, basePackageClasses = {RoleIVServiceImpl.class})
// 组合使用basePackages和basePackageClasses,让Spring容器扫描指定包及其子包下以及指定类所在包的组件。
/*** 导入资源文件到Spring上下文中。* 该注解用于指定Spring配置文件的路径,以便Spring容器在启动时加载该配置文件。* 具体路径为classpath下的'spring-data.xml'文件。* 该注解通常用于配置与数据访问相关的bean,例如Hibernate的sessionFactory或JPA的entityManagerFactory。*/
@ImportResource({"classpath:spring-data.xml"})
/*** 使用@Import注解引入配置类* 该注解用于指定应用在启动时需要加载的配置类。在这里,我们引入了两个配置类:* 1. DataSourceConfig.class:用于数据源的配置,例如数据库连接池的配置。* 2. PojoConfig.class:用于POJO(Plain Old Java Object)的配置,例如实体类的映射配置。* 这两个配置类会被Spring上下文加载,以便应用在运行时可以使用其中定义的配置。*/
@Import({DataSourceConfig.class, PojoConfig.class})/*** 使用该注解来向Spring Boot应用添加一个属性源。它会尝试从类路径下的"database-config.properties"文件中加载属性,* 并将其命名为"database.properties"。如果文件找不到,则不会报错,因为设置了ignoreResourceNotFound为true。* 该属性源的编码格式为UTF-8。** 该注解通常用于配置类上,以在Spring应用启动时加载额外的配置属性。*/
@PropertySource(value = "classpath:database-config.properties",name = "database.properties",ignoreResourceNotFound = true,encoding = "UTF-8"
)public class ApplicationConfig {/*** 创建并返回一个PropertySourcesPlaceholderConfigurer的实例。* 这个配置器是用来处理属性文件占位符的,它可以在Spring配置文件中启用属性文件的引用。* 通过这个方法,可以在Spring Bean的配置中使用占位符来引用属性文件中的属性值。** @return PropertySourcesPlaceholderConfigurer 返回一个配置了属性文件解析器的实例。*/@Beanpublic PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {return new PropertySourcesPlaceholderConfigurer();}/*** 创建并配置数据源 bean。** @param driver JDBC驱动程序的类名。* @param url 数据库连接的URL。* @param username 连接数据库所需的用户名。* @param password 连接数据库所需的密码。* @return 配置好的数据源实例。*/@Bean(name = "dataSource")// 根据特定条件决定是否创建该bean@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;}
}
Bean的作用域
所谓Bean的作用域是指Bean在应用中的有效范围,默认情况下,SpringIoC容器只会对Bean创建唯一实例,然后在Spring IoC容器的生命周期中有效,用如下代码测试一下
/*** 测试作用域* 该方法通过创建一个注解配置的应用上下文,并从该上下文中获取RoleDataSourceService类型的bean实例,* 以此来验证Spring容器中bean的作用域特性。* 该方法不接受参数且没有返回值。*/public static void testScope() {// 创建一个注解配置的应用上下文,并指定配置类ApplicationConfigAnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);// 从上下文中获取RoleDataSourceService类型的bean实例RoleDataSourceService service1 = context.getBean(RoleDataSourceService.class);RoleDataSourceService service2 = context.getBean(RoleDataSourceService.class);// 打印两个bean实例是否为同一个对象的引用System.out.println(service1 == service2);// 关闭应用上下文context.close();}
这里Spring IoC容器通过类型的方式获取Bean,然后通过==
比较两次获取的Bean的结果,这是一个位比较,比较service1
和service2
是否为同一个对象,很显然结果会返回true
,换句话说在默认情况下,SpringIoC容器只会为配置的Bean生成一个实例,而不是多个
而在互联网对性能有基本要求的场景下,有时候我们希望每请一次就产生一个独立的对象,这样多个实例可以在不同的线程运行,在对性能有要求的场景中就能发挥作用,这些是由Spring的作用域决定的,Spring IoC容器提供了2种作用域(单例和原型),单例(singleton)是默认选项,在整个应用中,Spring只为其生成一个Bean的实例;原型(prototype)当每次从Spring IoC容器获取Bean时,Spring都会为它创建一个新的实例
在Spring中,可以用注解@Scope
指定作用域,如下代码所示
package com.ssm.service.iml;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;import javax.sql.DataSource;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import com.ssm.pojo.RoleIV;
import com.ssm.service.RoleDataSourceService;/*** 实现RoleDataSourceService接口,提供获取角色信息的功能*/
@Service
/*** RoleDataSourceServiceImpl 类实现了 RoleDataSourceService 接口,* 用于提供角色相关的数据源服务。该类的作用范围被注解为原型作用域(SCOPE_PROTOTYPE),* 意味着每次请求都会创建一个新的实例。*/
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RoleDataSourceServiceImpl implements RoleDataSourceService {// 通过自动装配获取数据源@AutowiredDataSource dataSource = null;/*** 根据角色ID获取角色信息* @param id 角色的ID* @return 返回对应的角色信息,如果没有找到则返回null*/@Overridepublic RoleIV getRoleIV(Long id) {Connection conn = null;ResultSet rs = null;PreparedStatement ps = null;RoleIV roleIV = null;try {// 获取数据库连接conn = dataSource.getConnection();// 构造查询SQL语句String sql = "select id, role_name, note from t_role where id = ?";ps = conn.prepareStatement(sql);// 设置查询参数ps.setLong(1, id);// 执行查询rs = ps.executeQuery();// 处理查询结果while (rs.next()) {// 构建角色对象roleIV = new RoleIV();roleIV.setId(rs.getLong("id"));roleIV.setRoleName(rs.getString("role_name"));roleIV.setNote(rs.getString("note"));}} catch (SQLException e) {e.printStackTrace();} finally {// 关闭数据库连接及相关资源if (conn != null) {try {conn.close();} catch (SQLException e) {e.printStackTrace();}}}return roleIV;}
}
然后再执行测试代码
/*** 测试作用域* 该方法通过创建一个注解配置的应用上下文,并从该上下文中获取RoleDataSourceService类型的bean实例,* 以此来验证Spring容器中bean的作用域特性。* 该方法不接受参数且没有返回值。*/public static void testScope() {// 创建一个注解配置的应用上下文,并指定配置类ApplicationConfigAnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);// 从上下文中获取RoleDataSourceService类型的bean实例RoleDataSourceService service1 = context.getBean(RoleDataSourceService.class);RoleDataSourceService service2 = context.getBean(RoleDataSourceService.class);// 打印两个bean实例是否为同一个对象的引用System.out.println(service1 == service2);// 关闭应用上下文context.close();}
就会返回false了,也就是当我们从SpringIoC容器中获取对象时,获取的都是新的实例,不同的对象
在互联网应用中,Spring会使用实现了WebApplicationContext接口(该接口继承了ApplicationContext)的实现类作为IoC容器,此容器有两种常见的作用域(会话和请求),会话(Session)在Web应用中使用,在会话过程中只创建一个实例;请求(request)在Web应用中使用,在一次请求中Spring会创建一个实例,该配置可以在Java代码中进行,也可以在XML配置文件中进行,如下所示
import org.springframework.web.context.WebApplicationContext;/*** 实现RoleDataSourceService接口,提供获取角色信息的功能*/
@Service
/*** 为Spring Web应用程序定义会话级别的作用域。* 使用此注解的bean将被存储在HTTP会话中,意味着它们在同一个会话内的所有HTTP请求中都是可用的。* 这是针对需要在多个相关请求之间共享状态的情况而设计的,例如用户的登录状态。** 注意:此注解仅适用于Web应用程序上下文。*/
@Scope(WebApplicationContext.SCOPE_SESSION)public class RoleDataSourceServiceImpl implements RoleDataSourceService {// 通过自动装配获取数据源@AutowiredDataSource dataSource = null;
<!-- 定义一个RoleII类型的bean实例,id为2,角色名为"高级工程师",备注为"重要人员" --><bean id="role2" class="com.ssm.pojo.RoleII" scope="prototype"><!-- 设置角色id为2 --><property name="id" value="2" /><!-- 设置角色名为"高级工程师" --><property name="roleName" value="高级工程师" /><!-- 设置备注为"重要人员" --><property name="note" value="重要人员" /></bean>
使用Spring表达式
Spring还提供了更灵活的方式,那就是Spring表达式(Spring EL), 它远比其他注入方式更强大,其大致内容如下
Spring Expression Language (Spring EL) 是Spring框架中的一种表达式语言,它主要用于在运行时查询和操作对象图。Spring EL设计的目标是简化数据绑定和表达式评估,特别是在Spring应用程序上下文中。以下是一些关于Spring EL的关键点:
- 对象导航:Spring EL允许你通过点号.来导航对象的属性,例如 person.name 获取 person 对象的 name 属性。
- 方法调用:支持调用对象的方法,例如 list.sort() 或 date.format(‘yyyy-MM-dd’)。
- 集合操作:可以索引和遍历集合,如 list[0] 或 list[0…2] 选取前三个元素。
- 条件和逻辑运算:支持 if、and、or、not 等逻辑运算符,以及比较运算符(==、>、< 等)。
- 算术运算:支持基本的数学运算,如加减乘除 (+, -, *, /) 和取余数 (%)。
- 类型转换:可以显式地转换类型,例如 (int)number。
- 变量和参数:在表达式中可以引用变量和方法参数。
- 上下文访问:可以访问Spring容器中的bean,例如 @myBean 引用名为 myBean 的bean。
- 表达式结果:表达式的结果可以是任何Java类型,包括null。
- ** spel:expression**:在XML配置中,使用 #{} 语法来包含Spring EL表达式,例如
<property name="someProperty" value="#{myBean.someMethod()}" />
。
Spring EL相关的类
/*** 测试表达式解析功能。* 该方法演示了如何使用Spring表达式语言(SpEL)解析一个简单的字符串表达式并获取其值。**/public static void testExpression(){// 创建SpEL表达式解析器ExpressionParser parser = new SpelExpressionParser();// 解析一个字符串表达式,并输出其值Expression expression = parser.parseExpression("'Hello World'");String str = (String) expression.getValue();System.out.println(str);// 解析并执行字符串的charAt方法,输出第一个字符expression = parser.parseExpression("'Hello World'.charAt(0)");char ch = (char) expression.getValue();System.out.println(ch);// 解析字符串并获取其字节表示expression = parser.parseExpression("'Hello World'.bytes");byte[] bytes = (byte[]) expression.getValue();System.out.println(bytes);// 获取字符串的字节长度expression = parser.parseExpression("'Hello World'.bytes.length");int length = (int) expression.getValue();System.out.println(length);// 使用SpEL构造一个新的字符串对象expression = parser.parseExpression("new String('abc')");String str2 = (String) expression.getValue();System.out.println(str2);}
如代码所示,是一个简单的使用Spring EL的例子, 通过表达式可以创建对象、调用对象的方法或者获取属性
SpringEL还支持变量的解析,使用变量解析时常常用到一个接口EvaluationContext
,可以有效解析表达式中的变量,它有一个实现类StandardEvaluationContext
,用变量解析表达式会使表达式更加灵活,在针对SpringIoC容器进行解析的时候,可以直接获取配置的属性值,但使用这些表达式会降低可读性,不适合处理复杂的问题
/*** 测试SpEL表达式的求值功能。* 该方法首先演示了如何使用SpEL获取和设置对象属性的值,接着展示了如何对列表元素进行操作。*/public static void testEvaluation() {// 创建SpelExpressionParser实例以解析表达式ExpressionParser parser = new SpelExpressionParser();// 创建RoleIV实例,用于在表达式求值时提供属性值RoleIV roleIV = new RoleIV(1L, "admin", "administrator");// 解析表达式以获取"note"属性的值Expression expression = parser.parseExpression("note");String note = (String) expression.getValue(roleIV);System.out.println(note);// 创建表达式求值的上下文环境,并将roleIV设置为上下文中的根对象EvaluationContext ctx = new StandardEvaluationContext(roleIV);// 设置"note"属性的新值为"new_administrator"parser.parseExpression("note").setValue(ctx, "new_administrator");// 获取更新后的"note"属性值note = parser.parseExpression("note").getValue(ctx, String.class);System.out.println(note);// 获取RoleIV实例的"getRoleName()"方法返回值String roleName = parser.parseExpression("getRoleName()").getValue(ctx, String.class);System.out.println(roleName);// 初始化并创建一个字符串列表,用于演示如何在SpEL中操作集合List<String> list = new ArrayList<String>();list.add("value1");list.add("value2");// 将列表绑定到上下文中,以在表达式中使用ctx.setVariable("list", list);// 更新列表的第一个元素的值为"new_value1"parser.parseExpression("#list[0]").setValue(ctx, "new_value1");// 获取更新后的列表的第一个元素的值System.out.println(parser.parseExpression("#list[0]").getValue(ctx));}
EvaluationContext使用了它的实现类StandardEvaluationContext进行实例化,在构造方法中将角色对象传递给它,它就会基于这个类进行解析
Bean的属性和方法
使用注解的方式需要用到@Value
,在属性文件中的读取需要$
,在Spring EL中需要#
,在角色类中使用使用Spring EL初始化,如下代码所示
package com.ssm.pojo;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** ELRole类用于演示通过Spring的注解方式注入属性值。* 该类被标记为@Component,表示它是一个Spring组件,可以被其他组件依赖。* 其中使用的@Value注解用于注入具体的属性值。*/
@Component("elRole")
public class ELRole {// 使用@Value注解注入id的值,此处使用SpEL表达式的方式注入,示例值为1。@Value("#{1}")private Long id;// 使用@Value注解注入roleName的值,此处使用字符串方式注入。@Value("#{'role_name_1'}")private String roleName;// 使用@Value注解注入note的值,此处使用字符串方式注入。@Value("#{'note_1'}")private String note;/*** 获取角色ID* @return 返回角色的ID值*/public Long getId() {return id;}/*** 设置角色ID* @param id 角色的ID值*/public void setId(Long id) {this.id = id;}/*** 获取角色名称* @return 返回角色名称字符串*/public String getRoleName() {return roleName;}/*** 设置角色名称* @param roleName 角色名称字符串*/public void setRoleName(String roleName) {this.roleName = roleName;}/*** 获取角色备注信息* @return 返回角色的备注信息字符串*/public String getNote() {return note;}/*** 设置角色备注信息* @param note 角色的备注信息字符串*/public void setNote(String note) {this.note = note;}}
这样就定义了一个BeanName为elRole
的角色类,同时给它所有的属性都进行了初始化,然后可以通过另一个Bean引用它的属性或者调用它的方法,如下代码所示
package com.ssm.pojo;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** ELBean类用于演示和展示Spring Expression Language (EL)的使用。* 通过注解@Value从Spring表达式语言中获取值并注入到类的属性中。*/
@Component("elBean")
public class ELBean {// 从Java Math类中获取PI值@Value("#{T(java.lang.Math).PI}")private double pi;// 获取一个随机数@Value("#{T(Math).random()}")private double random;// 根据elRole的id值加1赋值给num@Value("#{elRole.id+1}")private int num;// 将elRole的roleName和note拼接成一个字符串@Value("#{elRole.roleName + elRole.note}")private String str;// 判断elRole的id是否等于1@Value("#{elRole.id == 1}")private boolean equalNum;// 判断elRole的note是否等于'note_1'@Value("#{elRole.note eq 'note_1'}")private boolean eqaulString;// 判断elRole的id是否大于2@Value("#{elRole.id > 2}")private boolean greater;// 判断elRole的id是否小于2@Value("#{elRole.id < 2}")private boolean less;// 根据条件判断选择赋值5或1给max@Value("#{elRole.id > 1 ? 5 : 1}")private int max;// 如果elRole的note存在,则赋值note,否则赋值'hello'@Value("#{elRole.note?: 'hello'}")private String defaultString;// 通过beanName获取ELRole类型的bean并注入@Value("#{elRole}")private ELRole elRole;// 获取ELRole的id属性@Value("#{elRole.id}")private Long id;// 调用ELRole的getNote方法,获取note属性的值@Value("#{elRole.getNote().toString()}")private String note;/*** 获取ELRole对象* @return 返回ELRole对象*/public ELRole getElRole() {return elRole;}/*** 设置ELRole对象* @param elRole 要设置的ELRole对象*/public void setElRole(ELRole elRole) {this.elRole = elRole;}/*** 获取实体的ID* @return 返回实体的ID值*/public Long getId() {return id;}/*** 获取圆周率π的值* @return 返回圆周率π的近似值*/public double getPi() {return pi;}/*** 设置圆周率π的值* @param pi 要设置的圆周率π的近似值*/public void setPi(double pi) {this.pi = pi;}/*** 获取一个随机数* @return 返回一个随机数值*/public double getRandom() {return random;}/*** 设置一个随机数* @param random 要设置的随机数值*/public void setRandom(double random) {this.random = random;}/*** 设置实体的ID* @param id 要设置的实体ID*/public void setId(Long id) {this.id = id;}/*** 获取备注信息* @return 返回备注信息字符串*/public String getNote() {return note;}/*** 设置备注信息* @param note 要设置的备注信息字符串*/public void setNote(String note) {this.note = note;}
}
可以通过BeanName注入,也可以通过OGNL获取其属性或者调用其方法注入其他的Bean,注意表达式"#{elRole.getNote().toString()}"
的注入,getNote()
方法可能返回null
,然后导致toString()
抛异常,可以写成"#{elRole.getNote()?.toString()}"
,表达式中的问号事先判断是否非空,如果不是非空则不再调用toString方法
使用类的静态常量和方法
// 从Java Math类中获取PI值@Value("#{T(java.lang.Math).PI}")private double pi;// 获取一个随机数@Value("#{T(Math).random()}")private double random;
Math是java.lang.*
包下的Math类,在Java代码中使用该类不需要import,对于Spring EL也是,也可以像代码所示给全限定名