1. 命名空间
在 spring 的发展历程中,p、c 【用于简化xml配置】和 util【配置复用】 命名空间曾经非常常,尤其是在 spring xml 配置盛行的时期。然而,随着 spring boot 和 java 配置【@configuration 和 @bean】的普及,它们的使用频率有所下降,但仍然是理解 spring xml 配置的重要组成部分。
✅ p命名空间:用于简化set注入
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/p http://www.springframework.org/schema/p/spring-p.xsd">
<bean id="datasource" class="org.apache.commons.dbcp2.basicdatasource"
p:driverclassname="com.mysql.cj.jdbc.driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="password"/>
<bean id="userservice" class="com.example.userservice"
p:userrepository-ref="userrepositoryimpl"/>
<bean id="userrepositoryimpl" class="com.example.userrepositoryimpl"/>
</beans>✅ c命名空间:用于简化构造注入
<?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:c="http://www.springframework.org/schema/c"
xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/c http://www.springframework.org/schema/c/spring-c.xsd">
<bean id="mybean" class="com.example.mybean"
c:name="john doe"
c:age="30"
c:anotherbean-ref="anotherbeaninstance"/>
<bean id="myotherbean" class="com.example.myotherbean"
c:_0="value 1"
c:_1-ref="dependencybean"/>
<bean id="anotherbeaninstance" class="com.example.anotherbean"/>
<bean id="dependencybean" class="com.example.dependencybean"/>
</beans>✅ util命名空间:用于定义一些实用的独立 bean,特别是集合list, set, map和属性properties以及常量。
<?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:util="http://www.springframework.org/schema/util"
xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:list id="mystringlist">
<value>item 1</value>
<value>item 2</value>
<ref bean="someotherbean"/>
</util:list>
<util:map id="mymap">
<entry key="key1" value="valuea"/>
<entry key="key2" value-ref="someotherbean"/>
</util:map>
<util:properties id="appprops" location="classpath:app.properties"/>
<util:constant id="mymaxconnections"
static-field="com.example.appconstants.max_connections"/>
<bean id="configreader" class="com.example.configreader">
<property name="items" ref="mystringlist"/>
<property name="settings" ref="mymap"/>
<property name="properties" ref="appprops"/>
<property name="maxconn" ref="mymaxconnections"/>
</bean>
<bean id="someotherbean" class="java.lang.string">
<constructor-arg value="another item"/>
</bean>
</beans>2. 自动装配
在 spring framework 中,自动装配autowiring是 ioc 容器的一个核心功能,它能够自动解析并注入 bean 之间的依赖关系,而无需你手动在 xml 中使用 <ref>
🧾 xml形式的自动装配:在 <bean> 标签上设置 autowire 属性来启用自动装配。
byname:根据 bean 的属性名称自动装配。spring 容器会尝试在容器中查找与 bean 属性名相同的 bean,并注入。
bytype:根据 bean 的属性类型自动装配。spring 容器会尝试在容器中查找与 bean 属性类型匹配的 bean,并注入。
// src/main/java/com/example/service/userservice.java
package com.example.service;
public interface userservice {
void registeruser(string username);
}
// src/main/java/com/example/service/userserviceimpl.java
package com.example.service;
import com.example.repository.userrepository;
public class userserviceimpl implements userservice {
private userrepository userrepository; // setter for bytype/byname
// constructor for constructor autowiring
public userserviceimpl(userrepository userrepository) {
this.userrepository = userrepository;
}
public void setuserrepository(userrepository userrepository) { // setter for bytype/byname
this.userrepository = userrepository;
}
@override
public void registeruser(string username) {
system.out.println("userservice: registering " + username);
userrepository.save(username);
}
}
// src/main/java/com/example/repository/userrepository.java
package com.example.repository;
public interface userrepository {
void save(string username);
}
// src/main/java/com/example/repository/userrepositoryimpl.java
package com.example.repository;
public class userrepositoryimpl implements userrepository {
@override
public void save(string username) {
system.out.println("userrepository: saving " + username + " to db.");
}
}<?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">
<bean id="userrepository" class="com.example.repository.userrepositoryimpl"/>
<bean id="userservicebyname" class="com.example.service.userserviceimpl" autowire="byname"/>
<bean id="userservicebytype" class="com.example.service.userserviceimpl" autowire="bytype"/>
<bean id="userservicebyconstructor" class="com.example.service.userserviceimpl" autowire="constructor"/>
<bean id="myapp" class="com.example.apprunner">
<property name="userservice" ref="userservicebytype"/> </bean>
----------------------------------------------------------------------------
// 如果按类型匹配有歧义,使用qulifier子标签
<bean id="qualifieremailservice" class="com.example.notificationserviceimpl">
<qualifier value="email"/>
</bean>
-----------------------------------------------------------------------------
</beans>🧾 java 注解形式的自动装配:使用@autowired或@resource注解。
3. 外部属性配置文件
在 spring 框架中,引入外部的 .properties 文件是管理应用程序配置的常见做法。这允许你将配置与代码分离,方便在不同环境【开发、测试、生产】中部署和修改。
xml方式引入外部.properties文件:在 xml 配置中,你需要使用 <context:property-placeholder> 标签来引入 .properties 文件,并将其中的属性暴露为 spring 表达式语言spel可以解析的占位符${...}。
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
// ant风格的路径匹配,如 classpath*:*.properties
<context:property-placeholder location="classpath:application.properties"/>
<bean id="datasource" class="org.springframework.jdbc.datasource.drivermanagerdatasource">
<property name="driverclassname" value="com.mysql.cj.jdbc.driver"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<bean id="appconfig" class="com.example.xmlappconfig">
<property name="appname" value="${app.name}"/>
</bean>
</beans>注解方式注入外部.properties:在注解配置中,你可以使用 @propertysource 注解来引入外部的 .properties 文件,然后使用 @value 注解来注入其中的属性。
package com.example;
import org.springframework.beans.factory.annotation.value;
import org.springframework.context.annotation.configuration;
import org.springframework.context.annotation.propertysource;
import org.springframework.jdbc.datasource.drivermanagerdatasource; // for illustration
@configuration // 这是一个spring配置类
@propertysource("classpath:application.properties") // 引入properties文件
// 也可以引入多个文件@propertysource({"classpath:application.properties", "classpath:db.properties"})
public class appconfig {
// 通过 @value 注入属性值
@value("${db.url}")
private string dburl;
@value("${db.username}")
private string dbusername;
@value("${db.password}")
private string dbpassword;
@value("${app.name}")
private string appname;
// 可以定义一个 bean 来封装数据源配置
// @bean
public drivermanagerdatasource datasource() {
drivermanagerdatasource datasource = new drivermanagerdatasource();
datasource.setdriverclassname("com.mysql.cj.jdbc.driver");
datasource.seturl(dburl);
datasource.setusername(dbusername);
datasource.setpassword(dbpassword);
return datasource;
}
public void printconfig() {
system.out.println("annotation app name: " + appname);
system.out.println("annotation db url: " + dburl);
}
}4.bean的作用域
- 在
spring framework中,bean的作用域scope定义了spring ioc容器如何管理bean实例的生命周期以及在每次请求bean时返回哪种类型的实例。理解bean的作用域对于正确设计和优化 spring 应用程序至关重要。spring提供了多种内置的作用域,你也可以定义自定义作用域。singleton:单例,也是默认的作用域。在整个spring ioc容器生命周期内,只创建bean的一个实例。所有对该bean的请求都将返回同一个实例。- 生命周期:容器启动时或第一次请求时【懒加载】时创建,容器关闭时销毁。
- 适用场景:适用于无状态的
bean,它们通常不包含可变的状态,如数据库连接池、线程池、配置对象等。
- 配置方式
xml:<bean id="myservice" class="com.example.myservice" scope="singleton"/>,可以省略scope="singleton",因为是默认值。java注解:@scope("singleton")或直接使用@service、@repository、@controller、@component,它们默认就是单例。prototype:每次从spring容器中请求该bean时,都会创建一个新的实例。
- 生命周期:每次请求时创建,
spring容器创建并配置好原型bean后,不再管理后续生命周期。销毁回调方法不会被调用,需要你自己手动释放资源。 - 适用场景:适用于有状态的
bean,需要在每次使用时都初始化新状态的bean。 - 配置方式
xml:<bean id="mybean" class="com.example.mybean" scope="prototype"/>java注解:@scope("prototype")
// 手动释放资源
@component
@scope("prototype")
public class resourceholder {
private connection dbconnection; // 昂贵资源
@postconstruct
public void init() throws sqlexception {
this.dbconnection = drivermanager.getconnection("jdbc:mysql://localhost/db");
}
// 显式释放资源的方法
public void release() {
if (dbconnection != null) {
dbconnection.close();
}
}
}
// 调用方
@service
public class clientservice {
@autowired
private provider<resourceholder> holderprovider; // 使用provider获取新实例
public void executetask() {
resourceholder holder = holderprovider.get();
try {
// 使用资源
} finally {
holder.release(); // 手动释放资源
}
}
}request、session、websocket、application:仅适用于web环境。
自定义scope【几乎不使用】:实现一个跨线程是不同实例。
// src/main/java/com/example/scope/threadscope.java
package com.example.scope;
import org.springframework.beans.factory.objectfactory;
import org.springframework.beans.factory.config.scope;
import java.util.hashmap;
import java.util.map;
public class threadscope implements scope {
// threadlocal 用于存储每个线程的 bean 实例和销毁回调
private final threadlocal<map<string, object>> threadscopemap =
threadlocal.withinitial(hashmap::new);
private final threadlocal<map<string, runnable>> destructioncallbacks =
threadlocal.withinitial(hashmap::new);
@override
public object get(string name, objectfactory<?> objectfactory) {
// 获取当前线程的 bean 实例 map
map<string, object> currentthreadmap = threadscopemap.get();
object bean = currentthreadmap.get(name);
if (bean == null) {
// 如果 bean 不存在,通过 objectfactory 创建新实例
bean = objectfactory.getobject();
currentthreadmap.put(name, bean);
system.out.println(thread.currentthread().getname() + ": created new bean '" + name + "' for this thread.");
} else {
system.out.println(thread.currentthread().getname() + ": reused existing bean '" + name + "' for this thread.");
}
return bean;
}
@override
public object remove(string name) {
// 从当前线程的 map 中移除 bean 实例
map<string, object> currentthreadmap = threadscopemap.get();
object removedbean = currentthreadmap.remove(name);
// 移除并执行销毁回调
map<string, runnable> currentdestructioncallbacks = destructioncallbacks.get();
runnable callback = currentdestructioncallbacks.remove(name);
if (callback != null) {
system.out.println(thread.currentthread().getname() + ": executing destruction callback for '" + name + "'.");
callback.run();
}
system.out.println(thread.currentthread().getname() + ": removed bean '" + name + "'.");
return removedbean;
}
@override
public void registerdestructioncallback(string name, runnable callback) {
// 注册销毁回调到当前线程的 map 中
map<string, runnable> currentdestructioncallbacks = destructioncallbacks.get();
currentdestructioncallbacks.put(name, callback);
system.out.println(thread.currentthread().getname() + ": registered destruction callback for '" + name + "'.");
}
@override
public object resolvecontextualobject(string key) {
// 在此作用域中没有特定的上下文对象可解析
return null;
}
@override
public string getconversationid() {
// 使用线程的名称作为会话id,确保每个线程有独立的上下文
return thread.currentthread().getname();
}
// !! 重要 !!
// 此方法用于在线程结束时清理 threadlocal 变量
// spring 容器本身不会自动调用这个方法,需要外部机制来触发, 例如自定义拦截器或aop
public void cleanup() {
system.out.println(thread.currentthread().getname() + ": cleaning up thread scope resources.");
map<string, runnable> callbacks = destructioncallbacks.get();
for (runnable callback : callbacks.values()) {
callback.run(); // 执行所有注册的销毁回调
}
destructioncallbacks.remove(); // 清理 threadlocal
threadscopemap.remove(); // 清理 threadlocal
system.out.println(thread.currentthread().getname() + ": thread scope resources cleaned.");
}
}// xml配置
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="threadscope" class="com.example.scope.threadscope"/>
<bean class="org.springframework.beans.factory.config.customscopeconfigurer">
<property name="scopes">
<map>
<entry key="thread" value-ref="threadscope"/>
</map>
</property>
</bean>
<bean id="mythreadscopedbean" class="com.example.service.myservice" scope="thread">
<qualifier value="threadscoped"/> </bean>
</beans>// java配置方式
// src/main/java/com/example/config/appconfig.java
package com.example.config;
import com.example.scope.threadscope;
import org.springframework.beans.factory.config.customscopeconfigurer;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.context.annotation.scope;
import org.springframework.context.annotation.componentscan;
import org.springframework.beans.factory.annotation.qualifier; // for @qualifier
import java.util.hashmap;
import java.util.map;
// 确保扫描到 myservice bean
@configuration
@componentscan(basepackages = "com.example.service")
public class appconfig {
// 定义 threadscope bean
@bean
public threadscope threadscope() {
return new threadscope();
}
// 注册自定义 scope 到容器
@bean
public static customscopeconfigurer customscopeconfigurer(threadscope threadscope) {
customscopeconfigurer configurer = new customscopeconfigurer();
map<string, object> scopes = new hashmap<>();
scopes.put("thread", threadscope); // "thread" 是作用域的名称
configurer.setscopes(scopes);
return configurer;
}
// 定义一个使用自定义作用域的 bean
// 注意这里我们使用 proxymode,因为主线程可能是一个单例 bean,会注入这个线程作用域的 bean
// 如果不使用代理,单例 bean 在创建时会直接获取一次 threadscopedbean,但后续线程中获取的仍是最初那个实例
@bean
@scope(value = "thread", proxymode = scope.scopedproxymode.target_class)
@qualifier("threadscoped") // 可以添加 qualifier 方便注入
public myservice mythreadscopedbean() {
return new myservice();
}
}// 使用
// src/main/java/com/example/service/myservice.java
package com.example.service;
import org.springframework.stereotype.component;
import javax.annotation.postconstruct;
import javax.annotation.predestroy;
// 不加 @component,因为我们通过 @bean 方法定义它
// 如果使用 @component,则需要在 @component 上添加 @scope("thread")
public class myservice {
private final long creationtime;
public myservice() {
this.creationtime = system.currenttimemillis();
system.out.println(thread.currentthread().getname() + ": myservice instance created (id: " + system.identityhashcode(this) + ", time: " + creationtime + ")");
}
@postconstruct
public void postconstruct() {
system.out.println(thread.currentthread().getname() + ": myservice postconstruct called (id: " + system.identityhashcode(this) + ")");
}
@predestroy
public void predestroy() {
system.out.println(thread.currentthread().getname() + ": myservice predestroy called (id: " + system.identityhashcode(this) + ")");
}
public void dowork() {
system.out.println(thread.currentthread().getname() + ": myservice is doing work. instance id: " + system.identityhashcode(this));
}
}// src/main/java/com/example/demoapplication.java
package com.example;
import com.example.config.appconfig;
import com.example.scope.threadscope;
import com.example.service.myservice;
import org.springframework.context.applicationcontext;
import org.springframework.context.annotation.annotationconfigapplicationcontext;
import java.util.concurrent.executorservice;
import java.util.concurrent.executors;
import java.util.concurrent.timeunit;
public class demoapplication {
public static void main(string[] args) throws interruptedexception {
// 初始化 spring 上下文
applicationcontext context = new annotationconfigapplicationcontext(appconfig.class);
// 获取 threadscope 实例,以便手动清理
threadscope threadscope = context.getbean(threadscope.class);
system.out.println("--- main thread ---");
myservice mainthreadservice = context.getbean("mythreadscopedbean", myservice.class);
mainthreadservice.dowork(); // 第一次获取,创建
myservice mainthreadservice2 = context.getbean("mythreadscopedbean", myservice.class);
mainthreadservice2.dowork(); // 第二次获取,重用
system.out.println("--- other threads ---");
executorservice executor = executors.newfixedthreadpool(2); // 两个工作线程
// 任务 1
executor.submit(() -> {
try {
system.out.println("\n" + thread.currentthread().getname() + ": task 1 started.");
myservice service1 = context.getbean("mythreadscopedbean", myservice.class);
service1.dowork(); // 第一次获取,创建
service1.dowork(); // 第二次获取,重用
} finally {
// !! 重要:在线程结束时手动清理 threadlocal !!
threadscope.cleanup();
}
});
// 任务 2
executor.submit(() -> {
try {
system.out.println("\n" + thread.currentthread().getname() + ": task 2 started.");
myservice service2 = context.getbean("mythreadscopedbean", myservice.class);
service2.dowork(); // 第一次获取,创建
service2.dowork(); // 第二次获取,重用
} finally {
// !! 重要:在线程结束时手动清理 threadlocal !!
threadscope.cleanup();
}
});
executor.shutdown();
executor.awaittermination(5, timeunit.seconds);
// 主线程结束后也清理自己的 threadlocal
system.out.println("\n--- main thread cleanup ---");
threadscope.cleanup();
system.out.println("\n--- application shutdown ---");
((annotationconfigapplicationcontext) context).close(); // 关闭spring上下文
}
}5.@postconstruct和@predestroy
@postconstruct 和 @predestroy是 java ee 规范中定义的注解,它在 spring framework 中被广泛支持和使用。
✅ @postconstruct:标记一个方法,使其在 bean 的依赖注入完成之后,但在 bean 投入使用之前执行,简单来说就是一个初始化回调。
- 使用场景:建立数据库连接池、初始化缓存、加载配置文件等,这些操作都需要在
bean完全准备好后才能进行,而构造函数可能无法满足这些要求,因为构造函数执行时,依赖可能尚未注入。 - 替代
xml配置中的init-method,在xml中可以使用<bean init-method="myinitmethod"/>来指定初始化方法。 - 使用规则
- 一个类中只有一个方法能使用
@postconstruct注解。 - 被注解的方法不能有任何参数。
- 被注解的方法不能是静态的。
- 一个类中只有一个方法能使用
✅ @predestroy:标记一个方法,使其在 bean 被销毁之前执行,简单来说就是一个销毁回调。
- 使用场景:关闭数据库连接和连接池、释放文件句柄、清除缓存等,这些操作需要在
bean实例被垃圾回收之前完成,以避免资源泄漏。 - 替代
xml中destroy-method,在xml配置中,你可以使用<bean destroy-method="mydestroymethod"/>来指定销毁方法。 - 使用规则
- 一个类中只有一个方法能使用
@predestroy注解。 - 被注解的方法不能有任何参数。
- 被注解的方法不能是静态的。
- 不能用于
prototype。
- 一个类中只有一个方法能使用
6.bean的四种实例化方式
在 spring framework 中,bean 的实例化是指 spring ioc 容器如何创建 bean 实例的过程。spring 提供了多种方式来实例化 bean,主要可以归结为构造器实例化、静态工厂方法实例化、实力工厂方法实例化、factorybean接口实例化。
构造器实例化:这是最常见、最基本也是 spring 默认的 bean实例化方式。spring 容器通过调用 bean 类的构造函数来创建实例。你可以使用默认的无参构造函数,也可以使用带参数的构造函数并通过依赖注入来提供参数。
静态工厂方法实例化:这种方式通过调用类中的一个静态方法来创建 bean 实例。工厂方法本身可以是任意名称,但必须是 static 的。
特点:当你无法直接访问类的构造函数如第三方库提供的类,但它提供了一个静态工厂方法来创建实例时;又或者是创建对象的过程比较复杂,需要封装在一个工厂方法中时。
适用场景:日期格式化、其他需要通过静态方法进行初始化的对象。
// com.example.factory.dateformatterfactory.java
package com.example.factory;
import java.text.simpledateformat;
import java.util.date;
public class dateformatterfactory {
// 静态工厂方法
public static simpledateformat createdateformatter(string pattern) {
system.out.println("dateformatterfactory: createdateformatter static method called with pattern: " + pattern);
return new simpledateformat(pattern);
}
public static date getcurrentdate() {
system.out.println("dateformatterfactory: getcurrentdate static method called.");
return new date();
}
}// xml配置
<beans>
<bean id="shortdateformatter" class="com.example.factory.dateformatterfactory"
factory-method="createdateformatter">
<constructor-arg value="yyyy-mm-dd"/>
</bean>
<bean id="currentdate" class="com.example.factory.dateformatterfactory"
factory-method="getcurrentdate"/>
</beans>// java注解
// com.example.config.appconfig.java
package com.example.config;
import com.example.factory.dateformatterfactory;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import java.text.simpledateformat;
import java.util.date;
@configuration
public class appconfig {
// 通过静态工厂方法创建 simpledateformat bean
@bean
public simpledateformat shortdateformatter() {
return dateformatterfactory.createdateformatter("yyyy-mm-dd");
}
@bean
public date currentdate() {
return dateformatterfactory.getcurrentdate();
}
}实例工厂方法实例化:这种方式通过调用容器中另一个 bean 的非静态方法来创建 bean 实例。你需要先定义一个工厂 bean,然后通过它的某个方法来创建目标 bean。
特点:当创建对象的过程需要依赖于工厂 bean 的状态或配置时;允许在工厂 bean 中封装更复杂的实例化逻辑。
适用场景:当工厂方法需要访问工厂类的非静态成员或状态时;数据库连接池的工厂类,可能会根据内部配置创建不同类型的连接。
// com.example.factory.connectionfactory.java
package com.example.factory;
public class connectionfactory {
private string connectiontype;
public connectionfactory(string connectiontype) {
this.connectiontype = connectiontype;
system.out.println("connectionfactory: instance created with type: " + connectiontype);
}
// 实例工厂方法
public connection createconnection() {
system.out.println("connectionfactory: createconnection instance method called for type: " + connectiontype);
return new connection(connectiontype);
}
// com.example.model.connection.java
public static class connection {
private string type;
public connection(string type) {
this.type = type;
system.out.println("connection: instance created for type: " + type);
}
@override
public string tostring() { return "connection [type=" + type + "]"; }
}
}// xml配置
<beans>
<bean id="connectionfactory" class="com.example.factory.connectionfactory">
<constructor-arg value="mysql"/>
</bean>
<bean id="myconnection" factory-bean="connectionfactory" factory-method="createconnection"/>
</beans>// java注解
// com.example.config.appconfig.java
package com.example.config;
import com.example.factory.connectionfactory;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
@configuration
public class appconfig {
// 定义工厂 bean
@bean
public connectionfactory connectionfactory() {
return new connectionfactory("mysql");
}
// 通过实例工厂方法创建 connection bean
@bean
public connectionfactory.connection myconnection() {
// 直接调用工厂 bean 的方法
return connectionfactory().createconnection();
}
}factorybean接口实例化:factorybean 是 spring 框架提供的一个特殊接口,它允许你自定义 bean 的创建逻辑。实现了 factorybean 接口的类本身是一个 bean,但它的 getobject() 方法返回的才是你真正想要获取的 bean 实例。
- 特点
- 高度灵活:可以在
getobject()方法中实现非常复杂的实例化逻辑,包括代理、装饰、条件创建等。 - 封装复杂性:将
bean的创建细节隐藏在factorybean内部。 - 适用场景:创建代理对象、集成第三方库、对
bean的实例化过程进行深度定制化。
- 高度灵活:可以在
// com.example.factory.encryptionservicefactorybean.java
package com.example.factory;
import org.springframework.beans.factory.factorybean;
import com.example.service.encryptionservice;
import com.example.service.caesarcipherencryptionservice; // 假设有一个实现
public class encryptionservicefactorybean implements factorybean<encryptionservice> {
private boolean useadvancedencryption; // 工厂bean的配置属性
public void setuseadvancedencryption(boolean useadvancedencryption) {
this.useadvancedencryption = useadvancedencryption;
system.out.println("encryptionservicefactorybean: useadvancedencryption set to " + useadvancedencryption);
}
@override
public encryptionservice getobject() throws exception {
system.out.println("encryptionservicefactorybean: getobject() called.");
if (useadvancedencryption) {
return new com.example.service.advancedencryptionservice(); // 假设有这个实现
} else {
return new caesarcipherencryptionservice();
}
}
@override
public class<?> getobjecttype() {
return encryptionservice.class;
}
@override
public boolean issingleton() {
// 返回 true 表示 getobject() 返回的是单例对象,false 则每次调用都返回新对象
// 这是指 getobject() 返回的对象的作用域,而不是 factorybean 自身的作用域
return true;
}
}
// com.example.service.encryptionservice.java
package com.example.service;
public interface encryptionservice {
string encrypt(string data);
}
// com.example.service.caesarcipherencryptionservice.java
package com.example.service;
public class caesarcipherencryptionservice implements encryptionservice {
@override
public string encrypt(string data) {
system.out.println("caesarcipherencryptionservice: encrypting " + data);
return "caesar_" + data; // 模拟加密
}
}
// com.example.service.advancedencryptionservice.java
package com.example.service;
public class advancedencryptionservice implements encryptionservice {
@override
public string encrypt(string data) {
system.out.println("advancedencryptionservice: encrypting " + data);
return "advanced_" + data; // 模拟高级加密
}
}// xml配置
<beans>
<bean id="encryptionservicefactory" class="com.example.factory.encryptionservicefactorybean">
<property name="useadvancedencryption" value="false"/>
</bean>
</beans>// java配置
// com.example.config.appconfig.java
package com.example.config;
import com.example.factory.encryptionservicefactorybean;
import com.example.service.encryptionservice;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
@configuration
public class appconfig {
// 定义 factorybean 本身
@bean
public encryptionservicefactorybean encryptionservicefactory() {
encryptionservicefactorybean factory = new encryptionservicefactorybean();
factory.setuseadvancedencryption(false); // 配置工厂行为
return factory;
}
// 虽然你定义的是 factorybean,但当你从容器中获取 encryptionservice.class 时,
// spring 会自动通过 factorybean 的 getobject() 方法来获取实例
// 你不需要额外声明一个 @bean 方法来获取 encryptionservice,spring 会识别 factorybean
}📊 beanfactory和factorybean的区别?
beanfactory是spring ioc容器的最核心接口【容器】。它是spring bean容器的根基,提供了最基本的ioc功能,负责bean的创建、配置、管理和依赖注入。通常我们更常用其子接口applicationcontext,因为applicationcontext包含了beanfactory的所有功能并进行了扩展。factorybean是一个特殊的bean【一个工厂bean】,其目的是充当一个工厂,用于辅助spring实例化其它bean对象。
7.bean的生命周期
spring 框架中 bean 的生命周期指的是从 spring ioc 容器创建 bean 实例到销毁 bean 实例的整个过程。理解 bean 的生命周期对于有效管理资源和确保 bean 正确初始化和清理非常重要。spring bean 的生命周期通常包括以下几个阶段:
实例化:spring ioc容器根据配置创建bean的实例,这通常是调用bean的构造函数完成的。
属性赋值:实例化之后,spring 容器会为 bean 注入其所依赖的属性。
感知bean的名称:如果 bean 实现了 beannameaware 接口,spring 容器会调用 setbeanname() 方法,将 bean 在容器中的 id/名称传递给 bean。
感知bean的类加载器:如果 bean 实现了 beanclassloaderaware 接口,spring 容器会调用 setbeanclassloader() 方法,将加载此 bean 的类加载器传递给 bean。
感知 beanfactory / applicationcontext:
如果 bean 实现了 beanfactoryaware 接口,spring 容器会调用 setbeanfactory() 方法,将创建该 bean 的 beanfactory 实例传递给 bean。
如果 bean 实现了 applicationcontextaware 接口,spring 容器会调用 setapplicationcontext() 方法,将创建该 bean 的 applicationcontext 实例传递给 bean。applicationcontext 是 beanfactory 的超集,提供了更多企业级功能。
进行前置处理:spring 容器会调用所有注册的 beanpostprocessor 实现类的 postprocessbeforeinitialization() 方法。这个方法允许在 bean 初始化之前进行一些自定义处理,例如修改 bean 实例。
- 初始化
@postconstruct注解:如果bean中有方法被@postconstruct注解标记,spring容器会调用这些方法。initializingbean接口:如果bean实现了initializingbean接口,spring容器会调用其afterpropertiesset()方法。- 自定义
init-method:如果在 bean 定义中配置了init-method属性,spring容器会调用指定的方法。 - 进行后置处理:
spring容器会调用所有注册的beanpostprocessor实现类的postprocessafterinitialization()方法。这个方法允许在bean初始化之后进行一些自定义处理,例如aop代理的创建就发生在这个阶段。 bean就绪:经过以上步骤,bean已经完全初始化并配置完成,可以被应用程序使用了。它会一直驻留在spring容器中,直到容器关闭或被销毁。
- 销毁
- 当
spring容器关闭时,或者bean实例不再需要时,spring容器会销毁bean。 @predestroy注解:如果 bean 中有方法被@predestroy注解标记,spring容器会调用这些方法。disposablebean接口**:如果 bean 实现了disposablebean接口,spring容器会调用其destroy()方法。**
- 当
自定义 destroy-method:如果在 bean 定义中配置了 destroy-method 属性,spring 容器会调用指定的方法。
销毁方法通常用于释放 bean 持有的资源,例如关闭数据库连接、文件句柄等。
如果bean的作用域是prototype,spring容器只负责将该bean初始化完毕,也就是说spring不再继续管理该对象的生命周期了。
在spring中,自己new的对象是不会被spring ioc容器所管理,如果想要实现这一需求,可以注册自己new的对象:
student stu = new student();
defaultlistablebeanfactory factory = new defaultlistablebeanfactory();
factory.registersingleton("studentbean", stu);
// 从spring容器中获取
factory.getbean("studentbean");8. 循环依赖问题
循环依赖circular dependency是 spring 框架中一个常见但又比较复杂的问题,指的是两个或多个 bean 之间相互依赖,形成一个闭环。
⚠️ spring 框架能够自动解决**单例作用域下,并且通过 setter 注入【或字段注入】**的循环依赖问题。主要原因是spring对单例bean + setter的管理主要分为以下两个阶段:
- 在
spring容器加载的时候,实例化bean,只要其中任意一个bean实例化后就马上曝光,此时还未进行属性赋值。 bean曝光之后,再进行属性赋值。
⚠️ 当两个bean的scope都是prototype且通过setter注入时就会出现循环依赖问题,解决方式就是将其中一个bean适用单例作用域。
⚠️ 基于构造注入的方式产生的循环依赖无法解决,只能避免。
9. 注解式开发
spring 框架提供了大量注解来简化开发,替代繁琐的 xml 配置。这些注解广泛应用于各个模块,如 ioc 容器、aop、事务管理、mvc、数据访问等。下面列举了 spring 中一些最常用和重要的注解:
🧾 @component、@service、@registoty、@controller:@component、@service、@repository、@controller 都是用来声明 bean 的注解,表示某个类是由 spring 容器托管的组件。在使用这些注解时,如果你不写任何名字,spring 会使用类名首字母小写作为默认的 bean 名称
| 注解 | 适用层/模块 | 语义用途 | 特点与说明 |
|---|---|---|---|
@component | 通用组件类 | 基础组件,通用的 bean | 最通用,其他注解都是它的细化形式 |
@service | 业务逻辑层 | 标识 service 业务处理类 | 表示这是个业务逻辑类,利于开发者阅读和管理 |
@repository | 数据访问层 | dao 层组件(数据库操作) | 自动进行异常转换 |
@controller | 表现层 / web层 | web 控制器,用于处理 http 请求 | 配合 @requestmapping / @getmapping 等使用 |
@component("myutil")
public class utility { }
@service("userservice")
public class userservice { }
@repository("userdao")
public class userrepository { }
@controller("homecontroller")
public class homecontroller { }🧾 @componentscan:spring 用来自动扫描指定包中的组件类的注解,它会将带有如 @component、@service、@controller、@repository 等注解的类自动注册到 spring 容器中。
@configuration
@componentscan(basepackages = "com.example.app")
public class appconfig {
// 配置类,可配合 @bean 使用
}// 扫描多个包
@configuration
@componentscan(basepackages = {
"com.example.service",
"com.example.dao",
"com.example.util"
})
public class appconfig {
}| 属性名 | 类型 | 作用说明 |
|---|---|---|
basepackages | string[] | 指定要扫描的包名【最常用】 |
value | string[] | 与 basepackages 一样,是其别名 |
basepackageclasses | class[] | 根据类所在包进行扫描【更类型安全】 |
includefilters | componentscan.filter[] | 指定只扫描某些类型的类 |
excludefilters | componentscan.filter[] | 排除不想扫描的类 |
usedefaultfilters | boolean | 默认是 true,是否扫描 @component 等注解;如果是false,表示包下所有带有@component注解的bean失效,结合includefilters、excludefilters使用 |
到此这篇关于spring全面详解的文章就介绍到这了,更多相关spring命名空间、自动配置内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论