依赖注入和依赖查找
应该说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 method
public Source getSource() {
return this.source;
}
// Setter method
public 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的名称。
*/
@Override
public void setBeanName(String beanName) {
System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanNameAware接口的setBeanName方法");
}
/**
* 实现BeanFactoryAware接口的方法,用于获取BeanFactory。
* @param bf BeanFactory实例。
* @throws BeansException 如果设置过程中发生错误。
*/
@Override
public void setBeanFactory(BeanFactory bf) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware接口的setBeanFactory方法");
}
/**
* 实现ApplicationContextAware接口的方法,用于获取ApplicationContext。
* @param ctx ApplicationContext实例。
* @throws BeansException 如果设置过程中发生错误。
*/
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName() + "】调用ApplicationContextAware接口的setApplicationContext方法");
}
/**
* 实现InitializingBean接口的方法,用于在所有属性设置完成后执行初始化操作。
* @throws Exception 如果初始化过程中发生错误。
*/
@Override
public 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 ****/
// 获取角色的ID
public Long getId() {
return id;
}
// 设置角色的ID
public 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/beans
http://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/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/util
http://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 描述角色信息的字符串
*/
@Override
public 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;
@Autowired
public 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、名称和备注。
*/
@Override
public 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() {
// 创建注解配置的应用上下文,并指定应用配置类ApplicationConfig
AnnotationConfigApplicationContext 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{
@Autowired
private RoleIV roleIV; // 自动注入的RoleIV对象
/**
* 打印RoleIV对象的信息,包括ID、角色名和备注。
* 该方法没有参数和返回值。
*/
@Override
public 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实体
*/
@Autowired
public 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、名称和备注。
*/
@Override
public 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、角色名和备注信息。
* 该方法会打印出这些信息的字符串表示。
*/
@Override
public 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中使用
@Autowired
private 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.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://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 {
// 通过自动装配获取数据源
@Autowired
DataSource dataSource = null;
/**
* 根据角色ID获取角色信息
* @param id 角色的ID
* @return 返回对应的角色信息,如果没有找到则返回null
*/
@Override
public 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/beans
http://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/beans
http://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 {
@Autowired
private DataSource dataSource; // 自动注入数据源
/**
* 测试方法,用于验证在“test”环境下数据源是否能被正确注入。
* 该方法没有参数和返回值。
*/
@Test
public 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 返回一个配置了属性文件解析器的实例。
*/
@Bean
public 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数据库url
String url = env.getProperty("jdbc.database.url");
// 从IoC容器中获取DataSource Bean
DataSource ds = context.getBean(DataSource.class);
// 打印数据库连接的URL
System.out.println(ds.getConnection().getMetaData().getURL());
}
在ApplicationConfig.java文件中写了如下方法,并加上了@Bean注解
/**
* 创建并返回一个PropertySourcesPlaceholderConfigurer的实例。
* 这个配置器是用来处理属性文件占位符的,它可以在Spring配置文件中启用属性文件的引用。
* 通过这个方法,可以在Spring Bean的配置中使用占位符来引用属性文件中的属性值。
*
* @return PropertySourcesPlaceholderConfigurer 返回一个配置了属性文件解析器的实例。
*/
@Bean
public 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/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:property-placeholder
ignore-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/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:property-placeholder
ignore-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表示条件不满足。
*/
@Override
public 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 返回一个配置了属性文件解析器的实例。
*/
@Bean
public 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() {
// 创建一个注解配置的应用上下文,并指定配置类ApplicationConfig
AnnotationConfigApplicationContext 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 {
// 通过自动装配获取数据源
@Autowired
DataSource dataSource = null;
/**
* 根据角色ID获取角色信息
* @param id 角色的ID
* @return 返回对应的角色信息,如果没有找到则返回null
*/
@Override
public 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() {
// 创建一个注解配置的应用上下文,并指定配置类ApplicationConfig
AnnotationConfigApplicationContext 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 {
// 通过自动装配获取数据源
@Autowired
DataSource 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也是,也可以像代码所示给全限定名