目录
1. IoC的提出
2. Spring容器
2.1. Spring容器实现原理
2.2. Spring组件
2.2.1 XML标签方式
2.2.2. 类注解方式
2.2.3. 方法注解方式
2.3. Spring容器分类
2.3.1. BeanFactory容器
2.3.2. ApplicationContext容器
2.3.3. WebApplicationContext容器
3. Spring中的常用注解
1. IoC的提出
在采用面向对象方法设计的软件系统中,底层实现是由一组对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑。对象之间的耦合关系就像手表齿轮的啮合,彼此之间存在复杂依赖关系,往往牵一发而动全身,如图1所示。因此,如何降低系统之间、模块之间和对象之间的耦合度,是软件工程追求的目标之一。1996年,软件专家Michael mattson首次提出了控制反转(Inversion of Control, IoC)概念。控制反转的核心思想是:借助于“第三方(IoC容器)” 实现具有依赖关系的对象之间的解耦,如图2所示。
图1. 传统面向对象系统 | 图2. 使用IoC容器的面向对象系统 |
在使用IoC容器的面向对象系统中,全部对象的控制权都交给IoC容器来管理,所以,IoC容器成为整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用。
2004年,Martin Fowler探讨了同一个问题,既然IoC是控制反转,那么到底是“哪些方面的控制被反转了呢”?最后,他得出结论是“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为由IoC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection, DI)”。所以,依赖注入(DI)和控制反转(IoC)是从不同的角度来描述同一件事情,就是指通过引入IoC容器,利用依赖关系注入的方式,实现对象之间的解耦。
2. Spring容器
Spring容器是Spring框架的核心,它是一种IoC容器,负责管理应用程序中对象的生命周期和依赖关系。Spring容器通过读取Spring组件的元数据((XML文件、注解或Java配置))来创建、配置、装配并管理应用程序中的对象。
2.1. Spring容器实现原理
实现Spring容器的最主要技术是Java反射编程。Java的反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。简单来说,只要给定类的名字,就可以通过反射机制来获得类的所有信息。Spring容器的工作模式可以看作是工厂模式的升华。可以把Spring容器看作是一个对象工厂,这个工厂里要生产的对象都在元数据中给出定义,然后利用反射编程,根据元数据(XML文件、注解或Java配置)中给出的类名生成相应的对象。从实现来看,Spring是把工厂模式里写死的对象生成代码,改变为由元数据来定义,也就是把工厂和对象生成这两者独立离开来,目的是提高灵活性和可维护性。Spring容器的实现原理如图3所示。
图3. Spring容器实现原理
2.2. Spring组件
纳入Spring容器管理的Java类称为Spring组件(简称组件),Spring容器对组件进行实例化所创建的对象称为Bean。一个Java类可以标识为多个Spring组件,每个Spring组件可以创建一个或多个Bean。定义Spring组件主要有三种方式:XML标签、类注解和方法注解。
2.2.1 XML标签方式
该方式通过在XML配置文件中使用<bean>标签来定义Spring组件。下面通过一个例子来说明如何通过<bean>标签来定义组件和创建Bean。
(1)类的定义
在包com.my.examples.example1中定义Java类Login和User,类的声明如下:
package com.my.examples.example1;public class Login {String username;String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}
package com.my.examples.example1;public class User {Login login;public void print(){System.out.println("login user: "+login.getUsername());}public Login getLogin() {return login;}public void setLogin(Login login) {this.login = login;}
}
(2)Spring组件定义
在项目的resources目录下创建spring-config.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"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 定义Spring组件 --><bean id="login" class="com.my.examples.example1.Login"><!--为属性code和name注入初始值 --><property name="username" value="admin"></property><property name="password" value="123456"></property></bean><bean id="currentUser" class="com.my.examples.example1.User"><!-- 为属性login注入引用值(Spring组件login) --><property name="login" ref="login"></property></bean>
</beans>
(3)配置Spring应用上下文
根据XML配置文件创建Spring容器,利用容器所提供的getBean()方法获取相应的Bean,从容中获取Bean后就可以调用其方法来执行。
package com.my.examples.example1;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test {public static void main(String[] args) {//根据XML配置文件创建Spring容器ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");//获取Spring容器中的BeanLogin login= context.getBean("login", Login.class);User user=context.getBean("currentUser", User.class);user.print();}
}
运行类Test,在控制台中输出如下信息:
login user: admin |
2.2.2. 类注解方式
该方式通过@Component、@Respository、@Service、@Controller注解来定义Spring组件,利用Java配置类(用@Configuration注解定义的类)代替XML配置文件。@Component注解用于标识一个类为Spring组件,当Spring框架扫描到该注解时,会创建该类的实例并纳入Spring容器中管理。@Respository、@Service、@Controller都是@Component注解的特殊形式,分别用于标识数据访问层、服务层和控制层的Spring组件。
下面将2.1.1节中的例子改为类注解方式。
(1)Spring组件的定义
在包com.my.examples.example2中定义Java类Login和User,在类上加入@Component注解使其成为Spring组件。
package com.my.examples.example2;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;@Component //定义Spring组件
@PropertySource("classpath:my-config.properties") //引入外部属性文件
public class Login {@Value("${my.username}") //注入属性文件中的account值String username;@Value("${my.password}") //注入属性文件中的password值String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}
package com.my.examples.example2;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component //定义Spring组件
public class User {@Autowired //注入注入依赖的Spring组件的BeanLogin login;public void print(){System.out.println("login user: "+login.getUsername());}
}
@PropertySource注解的功能是引入外部属性文件:my-config.properties,该文件位于项目的resources目录下,文件的内容如下:
my.username=admin my.password=123456 |
(2)Java配置类的定义
在包com.my.examples.example2中定义类Config,在该类上加入@Configuraton注解使其成为Java配置类。在该配置类上加入@ComponentScan注解,其功能是让Spring容器扫描com.my.examples.example2包及其子包下面所有的Spring组件,并将这些组件进行例化为Bean。
package com.my.examples.example2;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration //定义Java配置类
//扫描包com.my.examples.example2及其子包下面的所有Spring组件
@ComponentScan("com.my.examples.example2")
public class Config {
}
(3)配置Spring应用上下文
根据Java配置类创建Spring容器,利用容器提供的getBean()方法获取相应的Bean,调用Bean提供的方法来执行任务。
package com.my.examples.example2;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Test {public static void main(String[] args) {//根据Java配置类创建Spring容器AnnotationConfigApplicationContext context = newAnnotationConfigApplicationContext(Config.class);//获取容器中的BeanLogin login=context.getBean(Login.class);User user=context.getBean(User.class);user.print();}
}
运行类Test,在控制台中输出如下内容:
login user: admin |
2.2.3. 方法注解方式
该方式不需要在类上加入@Component等注解来定义Spring组件,而是通过在Java配置类中的方法上加入@Bean注解,该方法的返回值是一个Spring组件的Bean。
下面把第2.1.2节中的应用改为采用Java配置的方式来实现。
(1)类的定义
在包com.my.examples.example3中定义Java类Login和User。
package com.my.examples.example3;public class Login {String username;String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}
package com.my.examples.example3;public class User {Login login;public void print(){System.out.println("login user: "+login.getUsername());}public Login getLogin() {return login;}public void setLogin(Login login) {this.login = login;}
}
(2)定义Java配置类
package com.my.examples.example3;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;@Configuration //定义Java配置类
//扫描包com.my.examples.example3及其子包下面的所有Spring组件
@ComponentScan("com.my.examples.example3")
@PropertySource("classpath:my-config.properties") //引入外部属性文件
public class Config {@Value("${my.username}")String username;@Value("${my.password}")String password;@Bean //定义Spring组件并返回Beanpublic Login login(){Login login = new Login();//注入属性值login.setUsername(username);login.setPassword(password);return login;}@Bean //定义Spring组件并返回Beanpublic User user(){User user = new User();//注入组件Login的Bean时,直接调用方法login()user.setLogin(login());return user;}
}
(3)配置Spring应用上下文
根据Java配置类创建Spring容器,根据容器提供的getBean()方法获取相应的Bean,调用Bean的方法执行任务。
package com.my.examples.example3;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Test {public static void main(String[] args) {//根据Java配置类创建Spring容器AnnotationConfigApplicationContext context = newAnnotationConfigApplicationContext(Config.class);//获取容器中的BeanLogin login = context.getBean(Login.class);User user = context.getBean(User.class);user.print();}
}
运行类Test,在控制台中输出如下内容:
login user: admin |
2.3. Spring容器分类
Spring框架主要提供三种类型的容器:BeanFactory容器、ApplicationContext容器和WebApplicationContext。
2.3.1. BeanFactory容器
BeanFactory是Spring框架中用于管理 Bean的最基本接口,它提供了一系列用于获取Bean和管理 Bean依赖的方法。BeanFactory使用懒加载方式,即只有在需要时才创建和配置bean。
BeanFactory接口包含的的主要方法:
- Object getBean(String name):根据bean的名称在Spring容器中获取对应的Bean实例。
- Object getBean(String name, Object... args):根据 Bean 的名称和给定的构造函数参数获取一个 Bean 实例。
- boolean containsBean(String name):判断Spring容器中是否包含指定名称的bean。
- boolean isSingleton(String name):判断指定名称的bean是否是单例。
- boolean isPrototype(String name):判断指定名称的bean是否是原型。
BeanFactory接口的主要实现类:
- XmlBeanFactory:这是一个简单的实现,它从 XML 文件中加载 Bean 定义,从 Spring 3.1 开始,该类已被标记为过时。
- DefaultListableBeanFactory:它提供了一套完整的服务来管理不同的 Bean,包括注册 Bean、解析依赖、管理 Bean 生命周期、以及提供不同的 Bean 作用域等。
2.3.2. ApplicationContext容器
ApplicationContext是BeanFactory的子接口,它在BeanFactory的基础上增加了更多的功能。除了提供BeanFactory的所有功能外,ApplicationContext还提供了更多企业特定的功能,如国际化支持、事件发布、应用层特定的上下文等。ApplicationContext在容器启动时就创建和配置所有的单例bean,提供了更多的高级特性。
ApplicationContext接口的主要实现类:
- ClassPathXmlApplicationContext:从类路径下的XML文件加载Spring应用上下文。这是在传统的 Spring 应用中常见的一种方式,适合那些配置存储在项目内部的XML文件中的应用。
- FileSystemXmlApplicationContext:从文件系统指定的位置的XML文件加载Spring应用上下文。这种方式适用于需要从外部文件系统(而非类路径)加载配置的场景。
- AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文。这种方式专门为基于Java注解的配置提供支持。
2.3.3. WebApplicationContext容器
WebApplicationContext是专门为 Web 应用程序设计的,它扩展了 ApplicationContext 接口,添加了一些针对Web环境的特定功能。WebApplicationContext通常在基于Spring的Web应用程序中使用,特别是在使用Spring MVC框架时,它通常在web.xml文件中配置,或者通过Spring 的WebApplicationInitializer接口在Java配置中启动。
WebApplicationContext接口的主要实现类:
- XmlWebApplicationContext:从XML文件加载配置的Web应用上下文,这是一个传统的方法,适合那些已经使用XML配置文件的项目。
- AnnotationConfigWebApplicationContext:支持基于Java注解的配置,允许通过Java代码直接配置Spring,使得配置更加直观和类型安全。
3. Spring中的常用注解
在Spring框架中,与Spring容器相关的注解如下表。
注解 | 功能 |
@Component | 通用注解,用于标识一个类为Spring组件,当Spring框架扫描到该注解时,会创建该类的实例并纳入Spring容器管理。 @Respository、@Service、@Controller都是这个注解的特殊形式,分别用于标识数据访问层、服务层和控制层的Spring组件。 |
@Respository | 该注解用于标注数据访问层组件,数据访问层组件通常用于数据操作。 |
@Service | 该注解用于标注服务层组件,服务层组件通常用于业务逻辑处理。 |
@Controller | 该注解用于标注控制层组件,控制层组件通常用于处理HTTP请求。 |
@Autowired | 自动装配注解,它可以放在类的成员变量、方法以及构造方法上,用于自动注入依赖,@Autowired是按照类型注入依赖,如果想要按照名称来注入依赖,则需要结合@Qualifier注解一起使用。 |
@Resource | 与@Autowired功能一样,区别在于该注解默认是按照名称来注入依赖,只有当找不到与名称匹配的Bean时才会按照类型来注入依赖 |
@Qualifier | 与@Autowired一起使用,用于指定要注入的Bean的名称 |
@Configuration | 用于定义配置类,可替代XML文件,类中的方法可以使用@Bean注解,以声明Spring容器管理的Bean。 |
@Bean | 用于标识一个方法,该方法返回一个Bean实例,并将其注册到Spring容器中。 |
@Scope | 用于指定Bean的作用域,分为如下几种: 1)singleton(单例):只创建一个Bean,这是默认的作用域,适用于无状态Bean,比如服务层Bean、数据访问层Bean; 2)prototype(原型):每次请求(通过getBean方法)都会创建一个新的Bean,适用于有状态的Bean; 3)request(请求):在Web应用中,每一次HTTP请求都会产生一个新的Bean,该Bean仅在当前HTTP请求内有效; 4)session(会话):在Web应用中,每个HTTP会话都会创建一个新的Bean,该请求仅在当前HTTP会话内有效; 5)global-session(全局会话):仅在Portlet应用中使用,每个全局HTTP会话会创建一个Bean,适用于需要在全局HTTP会话期间保持状态的Bean。 6)application(应用):在整个ServletContext范围内,Bean都是单例的,适用于需要在整个Web用用范围内共享的Bean。 |
@Value | 用于注入简单类型的属性值,如字符串、数字等。 |
@Async | 用于标识一个方法或类,表示其中的操作应该异步执行。 |
@PostConstruct | 用于标识一个方法,该方法在Bean实例化后自动执行,用于初始化操作。 |
@PreDestroy | 用于标识一个方法,该方法在Bean销毁前自动执行,用于清理操作。 |