Spring MVC -- Spring框架入门(IoC、DI以及XML配置文件)
Spring MVC是Spring框架中用于Web应用开发的一个模块。Spring MVC的MVC是Model-View-Controller的缩写。它是一个广泛应用于图像化用户交互开发中的设计模式,不仅常见于Web开发,也广泛应用于如Swing和JavaFX等桌面开发。
Spring MVC基于Spring框架、Servlet和JSP(JavaServer Page),在掌握这3门技术的基础上学习Spring MVC将非常容易。
Spring框架是一个开源的企业应用开发框架,作为一个轻量级的解决方案,它包含20多个不同的解决方法。我们主要关注Core、Spring Bean、Spring MVC和Spring MVC Test模块。
一 下载Spring
1、Spring下载
Spring框架下载官网:http://spring.io/projects,具体可以参考博客Spring框架下载方法。
也可以直接下载:URL为最Spring最新5.1.6版本地址,获取其他版本只需修改下面链接的5.1.6的版本号信息成想要的版本即可:https://repo.spring.io/webapp/#/artifacts/browse/tree/General/libs-release-local/org/springframework/spring/5.1.6.RELEASE。
将下载好的zip解压到任意目录,在解压的目录中,包含相应的文档和Java源代码,其中libs文件下为基于Spring框架开发应用所需要的jar文件。
2、Spring依赖包下载
关于spring的spring-framework-3.0.2.RELEASE-dependencies.zip包的下载:http://s3.amazonaws.com/dist.springframework.org/release/SPR/spring-framework-3.0.2.RELEASE-dependencies.zip
3、下载Spring源码
Spring框架是一个开源项目,如果你想要尚未发布的最新版本的Spring,可以使用在github上下载源代码:https://github.com/spring-projects/spring-framework
二 IoC和DI
Spring框架有两个重要的概念:控制翻转、依赖注入。
1、IoC是什么
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:
- 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等);
- 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
用图例说明一下,传统程序设计如图,都是主动去创建相关对象然后再组合起来:
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了:
2 IoC能做什么
IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
3、IoC和DI
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
- 谁依赖于谁:当然是应用程序依赖于IoC容器;
- 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
- 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
- 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
注:如果想要更加深入的了解IoC和DI,请参考大师级人物Martin Fowler的一篇经典文章《Inversion of Control Containers and the Dependency Injection pattern》,原文地址:http://www.martinfowler.com/articles/injection.html。
4、依赖注入具体案例
有两个组件A和B,A依赖于B。假设A是一个类,且A有一个方法importantMethod用到了B,如下:
class B{ public void usefulMethod() {} } public class A{ public void importantMethod() { //get an instance of B B b = new B(); b.usefulMethod(); } }
要使用B,类A必须先获得组件B的实例引用。若B是一个具体类,则可通过new 关键字之间创建组件B的实例。但是,如果B是接口,且有多个实现,则问题就变得复杂了。我们固然可以任意选择接口B的一个实现类,但是这意味着A的可重用性大大降低了,因为无法采用B的其他实现。
依赖注入是这样处理此类情景的:接管对象的创建工作,并将该对象的引用注入到需要该对象的组件中。以上述情况为例,依赖注入框架会分别创建对象A和对象B,然后将对象B注入到对象A中。
为了能让框架进行依赖注入,程序员需要编写特定的set方法或者构造方法。例如,为了能将B注入到A中,类A会被修改成如下形式:
public class A{ private B b; public void importantMethod() { b.usefulMethod(); } public void setB(B b) { this.b = b; } }
修改后的类A新增了一个set方法,该方法将会被框架调用,以注入B的一个实例。由于对象依赖由依赖注入,类A的importantMethod()方法不再需要在调用B的usefulMethod()方法前去创建B的一个实例。
当然,也可以采用构造器方式注入,如下所示:
public class A{ private B b; public A(B b) { this.b = b; } public void importantMethod() { b.usefulMethod(); } }
本例中,Spring会先创建B的实例,再创建A的实例,然后把B注入到实例中。
注:Spring管理的对象称为beans。
通过提供一个Ioc容器(或者说DI容器),Spring为我们提供一种可以“聪明”的管理Java对象依赖关系的方法。其优雅之处在于,程序员无需了解Spring框架的存在,更不需要引入任何Spring类型。
5、ApplicationContext接口
使用Spring,程序几乎将所有重要对象的创建工作移交给Spring,并配置如何注入依赖。Spring支持XML或注解两种配置方式。此外,还需要创建一个ApplicationContext对象,代表一个Spring IoC容器,org.springframework.context.ApplicationContext接口有很多实现类,包括ClassPathXmlApplicationContext和FileSystemXmlApplicationContext。这两个实现都需要至少一个包含beans信息的XML文件。
- ClassPathXmlApplicationContext:尝试在类加载路径中加载配置文件;
- FileSystemXmlApplicationContext:从文件系统中加载配置路径。

/* * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.context; import org.springframework.beans.factory.HierarchicalBeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.lang.Nullable; /** * Central interface to provide configuration for an application. * This is read-only while the application is running, but may be * reloaded if the implementation supports this. * * <p>An ApplicationContext provides: * <ul> * <li>Bean factory methods for accessing application components. * Inherited from {@link org.springframework.beans.factory.ListableBeanFactory}. * <li>The ability to load file resources in a generic fashion. * Inherited from the {@link org.springframework.core.io.ResourceLoader} interface. * <li>The ability to publish events to registered listeners. * Inherited from the {@link ApplicationEventPublisher} interface. * <li>The ability to resolve messages, supporting internationalization. * Inherited from the {@link MessageSource} interface. * <li>Inheritance from a parent context. Definitions in a descendant context * will always take priority. This means, for example, that a single parent * context can be used by an entire web application, while each servlet has * its own child context that is independent of that of any other servlet. * </ul> * * <p>In addition to standard {@link org.springframework.beans.factory.BeanFactory} * lifecycle capabilities, ApplicationContext implementations detect and invoke * {@link ApplicationContextAware} beans as well as {@link ResourceLoaderAware}, * {@link ApplicationEventPublisherAware} and {@link MessageSourceAware} beans. * * @author Rod Johnson * @author Juergen Hoeller * @see ConfigurableApplicationContext * @see org.springframework.beans.factory.BeanFactory * @see org.springframework.core.io.ResourceLoader */ public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { /** * Return the unique id of this application context. * @return the unique id of the context, or {@code null} if none */ @Nullable String getId(); /** * Return a name for the deployed application that this context belongs to. * @return a name for the deployed application, or the empty String by default */ String getApplicationName(); /** * Return a friendly name for this context. * @return a display name for this context (never {@code null}) */ String getDisplayName(); /** * Return the timestamp when this context was first loaded. * @return the timestamp (ms) when this context was first loaded */ long getStartupDate(); /** * Return the parent context, or {@code null} if there is no parent * and this is the root of the context hierarchy. * @return the parent context, or {@code null} if there is no parent */ @Nullable ApplicationContext getParent(); /** * Expose AutowireCapableBeanFactory functionality for this context. * <p>This is not typically used by application code, except for the purpose of * initializing bean instances that live outside of the application context, * applying the Spring bean lifecycle (fully or partly) to them. * <p>Alternatively, the internal BeanFactory exposed by the * {@link ConfigurableApplicationContext} interface offers access to the * {@link AutowireCapableBeanFactory} interface too. The present method mainly * serves as a convenient, specific facility on the ApplicationContext interface. * <p><b>NOTE: As of 4.2, this method will consistently throw IllegalStateException * after the application context has been closed.</b> In current Spring Framework * versions, only refreshable application contexts behave that way; as of 4.2, * all application context implementations will be required to comply. * @return the AutowireCapableBeanFactory for this context * @throws IllegalStateException if the context does not support the * {@link AutowireCapableBeanFactory} interface, or does not hold an * autowire-capable bean factory yet (e.g. if {@code refresh()} has * never been called), or if the context has been closed already * @see ConfigurableApplicationContext#refresh() * @see ConfigurableApplicationContext#getBeanFactory() */ AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException; }
下面是从类加载路径中加载config1.xml和config2.xml的ApplicationContext创建的一个代码示例:
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"config1.xml","config2.xml"});
可以通过调用ApplicationContext的getBean()方法从IoC容器中获得对象:
Product product = context.getBean("product",Product.class);
genBean()方法会在xml配置文件中查询name(或id)为product且类型为Product的bean对象。
注:理想情况下,我们只需在测试代码中创建一个ApplicationContext,应用程序本身无需处理。对于Spring MVC应用,可以通过一个Spring Servlet来处理ApplicationContext,而无需直接处理。
三 XML配置文件
从1.0版本开始,Spring就支持基于XML的配置;从2.5版本开始,增加了通过注解的配置文件。下面介绍如何配置XML文件,配置文件的根元素通常为beans:
<?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 "> ... </beans>
如果需要更强的Spring配置能力,可以在schema location属性中添加相应的schema,也可以指定schema版本:http://www.springframework.org/schema/beans/spring-beans-5.1.xsd,不过推荐使用默认的schma,以便升级spring库时无需修改配置文件。
配置文件既可以是一份,也可以分解为多份,以支持模块化配置。ApplicationContext的实现类支持读取多份配置文件。另一种选择是,通过一份主配置文件,将该文件导入到其他配置文件。
下面是导入其他配置文件的一个示例:
<?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 "> <import resource="config1.xml"/> <import resource="module2/config2.xml"/> <import resource="/resources/config3.xml"/> ... </beans>
bean元素的配置在后面将会详细介绍。
四 Spring控制反转(IoC)容器的使用
本节将介绍Spring如何管理bean。
1、通过无参构造器创建一个bean实例
前面已经介绍,通过调用ApplicationContext的getBean()方法可以获取一个bean的实例。
下面我们将创建一个名为spring-intro的Java Project项目,然后我们需要导入5个Spring Jar包:
- 从spring-framework-5.1.6.RELEASE\libs下复制
spring-beans-5.1.6.RELEASE.jar
spring-context-5.1.6.RELEASE.jar
spring-core-5.1.6.RELEASE.jar
spring-expression-5.1.6.RELEASE.jar
到当前项目中,右键当前项目JRE System Library——>Build Path——>Configure Build Path——>Add External JARs把这四个包导入;
- 从spring-framework-3.0.2.RELEASE-dependencies\org.apache.commons\com.springsource.org.apache.commons.logging\1.1.1下复制
com.springsource.org.apache.commons.logging-1.1.1.jar
到当前项目中,右键当前项目JRE System Library——>Build Path——>Configure Build Path——>Add External JARs把这一个包导入;
如果是Dynamic Java Web项目,直接将这5个Jar包导入到WebContent/WEB-INF/lib即可。
下面为代码的编写,我们先创建一个Product类,位于包springintro.bean中:
package springintro.bean; import java.io.Serializable; public class Product implements Serializable { private static final long serialVersionUID = 748392348L; private String name; private String description; private float price; public Product() { } public Product(String name, String description, float price) { this.name = name; this.description = description; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } }
下面创建一个名为spring-config.xml的配置文件,其中定义了一个名为product的bean,该配置文件位于src文件夹下:
<?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 name="product" class="springintro.bean.Product"/> </beans>
该bean的定义告诉Spring,通过默认无参的构造器来初始化Product类。如果不存在该构造器则会抛出一个异常。此外,该无参数的构造器并不要求是public签名。
注意:应采用id或者name属性标识一个bean。为了让Spring创建一个Product实例,应将bean定义的name值"product"和Product类型作为参数传给ApplicationContext的getBean()方法。
在包springintro下创建Main.java文件:
package springintro; import java.time.LocalDate; import java.util.Calendar; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import springintro.bean.Employee; import springintro.bean.Product; public class Main { public static void main(String[] args) { //创建IoC容器对象 从类路径下(/src)加载xml配置文件 容器启动时就会创建容器中配置的所有对象 ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"spring-config.xml"}); Product product1 = context.getBean("product", Product.class); product1.setName("Excellent snake oil"); System.out.println("product1: " + product1.getName()); } }
输出如下:
product1: Excellent snake oil
2、通过静态工厂方法创建一个bean实例
大部分类可以通过构造器来实例化。然而,Spring还同样支持通过调用一个工厂的静态方法来初始化类。
下面的bean定义展示了通过静态工厂方法来实例化Java.time.LocalData,调用java.time.LocalDate的静态方法now()创建LocalDate 对象:
<bean id="localDate" class="java.time.LocalDate" factory-method="now"/>
本例中采用了id属性而非name属性来标识bean,采用了getBean()方法来获取LocalData实例:
LocalDate localDate = context.getBean("localDate", java.time.LocalDate.class); System.out.println("today:" + localDate);
输出如下:
today:2019-04-29
3、销毁方法的使用(bean元素的destroy-method属性)
有时,我们希望一些对象在被销毁前能执行一些方法。Spring考虑到这样的需求,可以在bean定义中配置destroy-method属性,来指定在销毁前要执行的方法。
下面的例子中,我们配置Spring通过java.util.concurrent.Exceutors的静态方法newCachedThreadPool()来创建一个java.util.concurrent.ExceutorService实例,并指定了destroy-method属性值为shutdown()方法。这样,Spring会在销毁ExceutorService实例前调用shutdown()方法:
<bean id="executorService" class="java.util.concurrent.Executors" factory-method="newCachedThreadPool" destroy-method="shutdown"> </bean>
ExecutorService executorService = context.getBean("executorService", ExecutorService.class); //强制关闭IoC容器,在容器关闭之前会销毁容器中所有对象 ((ClassPathXmlApplicationContext)context).close();
在程序中我们强制关闭IoC容器,这样就会销毁ExceutorService实例,从而会触发executorService.shutdown()方法的执行。
4、初始化方法的使用(bean元素的init-method属性)
与销毁方法相对应的还有一个初始化方法,会在对象实例创建之后调用,可以在bean定义中配置init-method属性,来指定初始化要执行的方法:
<bean id="executorService" class="java.util.concurrent.Executors" factory-method="newCachedThreadPool" init-method="shutdown"> </bean>
5、bean元素的scope属性
- singleton(默认值):单例模式,被标识为单例的对象,在IoC容器中只会存在一个实例;
-
prototype:多例,被标识为多例的对象,每次在获取时才会创建,每次创建都是新的对象,整合struct2时,ActionBean必须配置为多例的;
- request(了解):web环境下,与request声明周期一致;
- session(了解):web环境下,与session声明周期一致;
如下案例:
<bean name="product" class="springintro.bean.Product" scope="singleton"/>
五 Spring依赖注入(DI)方式
本节将详细介绍Spring的依赖注入方式。
1、构造器方式依赖注入
前面已经介绍了使用无参构造函数来初始化类,此外,Spring支持通过带参数的构造器来初始化类。
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了