当前位置: 代码网 > it编程>编程语言>Java > Spring全面详解

Spring全面详解

2026年09月22日 Java 我要评论
1. 命名空间在 spring 的发展历程中,p、c 【用于简化xml配置】和 util【配置复用】 命名空间曾经非常常,尤其是在 spring xml 配置盛行的时期。然而,随着 spring bo

1. 命名空间

spring 的发展历程中,pc 【用于简化xml配置】和 util【配置复用】 命名空间曾经非常常,尤其是在 spring xml 配置盛行的时期。然而,随着 spring bootjava 配置【@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 中,自动装配autowiringioc 容器的一个核心功能,它能够自动解析并注入 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(); // 手动释放资源
        }
    }
}

requestsessionwebsocketapplication:仅适用于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@predestroyjava ee 规范中定义的注解,它在 spring framework 中被广泛支持和使用。

@postconstruct:标记一个方法,使其在 bean 的依赖注入完成之后,但在 bean 投入使用之前执行,简单来说就是一个初始化回调。

  • 使用场景:建立数据库连接池、初始化缓存、加载配置文件等,这些操作都需要在bean完全准备好后才能进行,而构造函数可能无法满足这些要求,因为构造函数执行时,依赖可能尚未注入。
  • 替代xml配置中的init-method,在xml中可以使用 <bean init-method="myinitmethod"/> 来指定初始化方法。
  • 使用规则
    • 一个类中只有一个方法能使用@postconstruct注解。
    • 被注解的方法不能有任何参数。
    • 被注解的方法不能是静态的。

@predestroy标记一个方法,使其在 bean 被销毁之前执行,简单来说就是一个销毁回调。

  • 使用场景:关闭数据库连接和连接池、释放文件句柄、清除缓存等,这些操作需要在 bean 实例被垃圾回收之前完成,以避免资源泄漏。
  • 替代xmldestroy-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
}

📊 beanfactoryfactorybean的区别?

  • beanfactoryspring 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() 方法,将创建该 beanbeanfactory 实例传递给 bean

如果 bean 实现了 applicationcontextaware 接口,spring 容器会调用 setapplicationcontext() 方法,将创建该 beanapplicationcontext 实例传递给 beanapplicationcontextbeanfactory 的超集,提供了更多企业级功能。

进行前置处理: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的作用域是prototypespring容器只负责将该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 dependencyspring 框架中一个常见但又比较复杂的问题,指的是两个或多个 bean 之间相互依赖,形成一个闭环。

⚠️ spring 框架能够自动解决**单例作用域下,并且通过 setter 注入【或字段注入】**的循环依赖问题。主要原因是spring对单例bean + setter的管理主要分为以下两个阶段:

  • spring容器加载的时候,实例化bean,只要其中任意一个bean实例化后就马上曝光,此时还未进行属性赋值。
  • bean曝光之后,再进行属性赋值。

⚠️ 当两个beanscope都是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表现层 / webweb 控制器,用于处理 http 请求配合 @requestmapping / @getmapping 等使用
@component("myutil")
public class utility { }
@service("userservice")
public class userservice { }
@repository("userdao")
public class userrepository { }
@controller("homecontroller")
public class homecontroller { }

🧾 @componentscanspring 用来自动扫描指定包中的组件类的注解,它会将带有如 @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 {
}
属性名类型作用说明
basepackagesstring[]指定要扫描的包名【最常用】
valuestring[]basepackages 一样,是其别名
basepackageclassesclass[]根据类所在包进行扫描【更类型安全】
includefilterscomponentscan.filter[]指定只扫描某些类型的类
excludefilterscomponentscan.filter[]排除不想扫描的类
usedefaultfiltersboolean默认是 true,是否扫描 @component 等注解;如果是false,表示包下所有带有@component注解的bean失效,结合includefiltersexcludefilters使用

到此这篇关于spring全面详解的文章就介绍到这了,更多相关spring命名空间、自动配置内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com