Spring设计哲学
- 提供任意层级的选择。Spring 允许你尽可能的推迟设计决策。例如,你可以在不改变代码的情况下通过配置来切换持久层提供者;
- 适应广泛的场景。Spring 提供强大的弹性,不拘泥于事物的具体实现。提供对基于不同场景的广泛的需求的支持。
- 保持较强的向后兼容性, Spring 精心的设计迭代来使尽量减少不同版本之间不兼容性变化。Spring 支持一系列精心选择的 JDK 版本 和 第三方库来简化对基于Spring 的 应用和库的维护;
- 专注与 API 设计。Spring 团队花费大量的时间和精力在设计 APIs 上,以使得这些 APIs 能够简单直观,并能够在开发后能够运行很多年以及跨越很多个版本;
一、控制反转IOC
1.1 Spring容器类型
容器接口:
- BeanFactory:BeanFactory是Spring容器中的顶层接口,它可以对Bean对象进行管理;
- ApplicationContext:ApplicationContext是BeanFactory的子接口。它除了继承 BeanFactory的所有功能外,还添加了对国际化、资源访问、事件传播等方面的良好支持;
Spring提供了几个ApplicationContext接口的实现类,在独立应用程序汇总通常创建一个类型为如下三种的实例对象:
ApplicationContext的三种实现类:
- ClassPathXmlApplicationContext:该类可以从项目中读取配置文件(相对路径);
- FileSystemXmlApplicationContext:该类从磁盘中读取配置文件(绝对路径);
- AnnotationConfigApplicationContext:使用该类不读取配置文件,而是会读取注解;
虽然XML是用于定义配置元数据的传统格式,但也可以通过使用Java注解或Java代码作为元数据格式,但是要通过少量XML配置来声明启用对这些附加元数据格式的支持。
下图展示了Spring是如何工作的。应用中的所有类都有元数据组到一起,所以当ApplicationContext创建和实例化后,你就有了一个完全可配置和可执行系统或应用。
1.2 配置元数据Bean
- 基于XML配置;
- 基于注解配置: 在Spring2.5中有过介绍支持基于注解的配置元数据
- 基于Java配置: 从Spring3.0开始,由Spring JavaConfig提供的许多功能已经成为Spring框架中的核心部分。这样你可以使用Java程序而不是XML文件定义外部应用程序中的bean类。使用这些新功能,可以查看@Configuration,@Bean,@Import和@DependsOn这些注解;
1.2.1 基于XML配置
Spring配置由必须容器管理的一个或通常多个定义好的bean组成。基于XML配置的元数据中,这些bean通过标签定义在顶级标签内部。在Java配置中通常在使用@Configuration注解的类中使用@Bean注解方法;
这些bean的定义所对应的实际对象就组成了你的应用。通常你会定义服务层对象,数据访问层对象(DAO),展现层对象比如Struts的Action
实例,底层对象比如Hibernate的SessionFactories
,JMSQueues
等等。通常在容器中不定义细粒度的域对象,因为一般是由DAO层或者业务逻辑处理层负责创建和加载这些域对象。但是,你可以使用Spring集成Aspectj来配置IoC容器管理之外所创建的对象。
<?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="..." class="...">
<!-- 在这里写 bean 的配置和相关引用 -->
</bean>
<bean id="..." class="...">
<!-- 在这里写 bean 的配置和相关引用 -->
</bean>
<!-- 在这里配置更多的bean -->
</beans>
id属性用来使用标识每个独立的bean定义的字符串。class
属性定义了bean的类型,这个类型必须使用全路径类名(必须是包路径+类名)。id属性值可以被依赖对象引用。
1.2.2 基于Java配置
Spring支持全新的Java配置,例如@Configuration注解的类和@Bean注解的方法。
@Bean注解用来说明通过Spring IoC容器来管理时一个新对象的实例化,配置和初始化的方法;
@Configuration注解的类说明这个类的主要是作为一个bean定义的资源文件;
进一步的讲,被@Configuration注解的类通过简单地在调用类中其他的@Bean方法来定义bean之间的依赖关系;
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
上面的AppConfig类和Spring XML 的配置是等价的:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
1.3 对象创建的方式
Spring调用什么方法来对容器中的对象进行创建呢?
- 默认Ctor:spring默认会使用对象的默认构造函数进行对象的创建(如果定义了有参ctor记得补上默认无参ctor);
- 使用工厂类非静态方法:
- 使用工厂类的静态方法:
1.4 对象创建的策略
Spring通过配置<bean>中的scope属性设置对象的创建策略,共有五种创建策略:
- singleton:单例(默认)。整个项目只会创建一个对象,通过<bean>中的lazy-init属性可以红色纸单例对象的创建时机;
-
- false:立即创建,即容器启动时会创建配置文件中的所有Bean对象;
- true:延迟创建,即Bean对象第一次被使用时才会创建;
- prototype:多例。每次从容器中获取对象时都会顺便创建一次所有Bean对象;
- request:每次请求创建一个对象,只在web环境有效;
- session:每次会话创建一个对象,只在web环境有效;
- gloabal-session:一次集群环境的会话创建一个对象,只在web环境有效;
1.5 对象销毁时机
- singleton:对象随着容器的销毁而销毁;
- prototype:使用JAVA垃圾回收机制销毁对象;
- request:当处理请求结束,bean实例将被销毁;
- session:当HTTP Session最终被废弃的时候,bean也会被销毁掉;
- gloabal-session:集群环境下的session销毁,bean实例也将被销毁;
1.6 获取Bean对象的方式
- 通过id/name获取;
- 通过类型获取:这种方式下可以不需要对获得的对象进行强制转换(但当配置文件中对同一接口有多个实现类的情况下进行获取会找不到对象);
- 通过类型+id/name获取:解决了上述问题;
二、依赖注入DI
依赖注入(Dependency Injection,简称DI),它是Spring控制反转思想的具体实现,用以实现管理Bean之间的依赖关系。
控制反转将对象的创建交给了Spring,但是对象中可能会依赖其他对象。比如service类中要有dao类的属性,我们称service依赖于dao。
- 传统:需要手动注入对象的,如在service层中的方法中需要自行创建dao层的类对象,并且当想要使用另一个dao的实现类时还需要修改java源码,造成了代码的可维护性降低。
- Spring框架:Spring管理Service对象与Dao对象,此时它能够为Service对象注入依赖的Dao属性值。
简单来说,控制反转是创建对象,依赖注入是为对象的属性赋值。
依赖注入的类型:
序号 | 依赖注入类型 & 描述 |
1 | Constructor-based dependency injection 当容器调用带有多个参数的构造函数类时,实现基于构造函数的 DI,每个代表在其他类中的一个依赖关系。 |
2 | Setter-based dependency injection 基于 setter 方法的 DI 是通过在调用无参数的构造函数或无参数的静态工厂方法实例化 bean 之后容器调用 beans 的 setter 方法来实现的。 |
你可以混合这两种方法,基于构造函数和基于 setter 方法的 DI,然而使用有强制性依存关系的构造函数和有可选依赖关系的 setter
是一个好的做法。
2.1 基于Setter的依赖注入
- 被注入类(StuService)中编写属性的setter方法
持久层:
// StuDao 持久层接口
public interface StuDao {
public Stu selByName(String name);
}
// StuDaoImpl 持久层接口实现类
public class StuDaoImpl implements StuDao{
public StuDaoImpl(){
System.out.println("StuDaoImpl.Ctor...");
}
// 生命周期方法
public void init()
{
System.out.println("init....");
}
public void destroy(){
System.out.println("destroy...");
}
@Override
public Stu selByName(String name) {
return new Stu("kk","男",21);
}
}
业务层:
// StuService 此处没有区分接口和实现类
public class StuService {
// 无参Ctor
public StuService() {
System.out.println("StuService.Ctor...");
}
private StuDao stuDao;
// 创建setter方法
public void setStuDao(StuDao stuDao) {
System.out.println("StuService.setter方法调用...");
this.stuDao = stuDao;
}
// 按姓名查询
public Stu selByName(String name){
return stuDao.selByName("kk");
}
}
- 配置<bean>中的property属性
<!-- 配置StuDao的bean -->
<bean id="stuDao" class="com.kk.dao.StuDaoImpl"></bean>
<!-- 配置StuService的bean -->
<bean id="stuService" class="com.kk.service.StuService">
<!--基于setter方法 将stuDao注入当前bean-->
<property name="stuDao" ref="stuDao"></property>
</bean>
要点:
- 在基于设值函数的注入中,我们使用的是<bean>标签中的<property>元素;
- 如果你要把一个引用传递给一个对象,那么你需要使用 标签的 ref 属性;而如果你要直接传递一个值,那么你应该使用 value 属性;
- 测试
public class TestContainer {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
StuService stuService = (StuService) context.getBean("stuService");
System.out.println(stuService.selByName("kk"));
}
}
- 输出结果
StuDaoImpl.Ctor...
StuService.Ctor...
StuService.setter方法调用...
Stu{name='kk', sex='男', age=21}
分析:
- 首先是被依赖的类B对象被创建(此处是StuService依赖StuDao);
- 紧接着依赖的类A对象被创建;
- Spring会根据配置文件的依赖注入关系调用依赖类A中的Setter方法将被依赖类对象注入;
- 执行相应的方法;
2.2 基于Ctor的依赖注入
此处我们在上述例子的基础上进行修改,新增了Classes的Pojo类及其对应的持久层接口和实现类,我们模拟1个类依赖至少2个类的情况,基于Ctor方式进行依赖注入;
- 编写类方法
持久层:
新增Classes的相关实现:
// ClassesDao 持久层接口
public interface ClassesDao {
public Classes selById(int id);
}
// ClassesDaoImpl 持久层接口实现类
public class ClassesDaoImpl implements ClassesDao{
public ClassesDaoImpl(){
System.out.println("ClassesDaoImpl.Ctor...");
}
@Override
public Classes selById(int id) {
return new Classes(1,"教学一班");
}
}
业务层:
public class StuService {
private StuDao stuDao;
private ClassesDao classesDao;
// 无参构造
public StuService() {
System.out.println("StuService.Ctor...");
}
// 有参构造
public StuService(StuDao stuDao, ClassesDao classesDao){
this.stuDao = stuDao;
this.classesDao = classesDao;
System.out.println("StuService.Ctor with params...");
}
// 创建setter方法
public void setStuDao(StuDao stuDao) {
System.out.println("StuService.setter方法调用...");
this.stuDao = stuDao;
}
// 按姓名查询学生
public Stu selByName(String name){
return stuDao.selByName("kk");
}
// 按id查询班级
public Classes selById(int id){
return classesDao.selById(1);
}
}
2.配置<bean>中的constructor-arg属性
<bean id="stuDao" class="com.kk.dao.StuDaoImpl"></bean>
<bean id="classesDao" class="com.kk.dao.ClassesDaoImpl"></bean>
<bean id="stuService" class="com.kk.service.StuService">
<constructor-arg name="stuDao" ref="stuDao"></constructor-arg>
<constructor-arg name="classesDao" ref="classesDao"></constructor-arg>
</bean>
要点:
- 在基于设值函数的注入中,我们使用的是<bean>标签中的constructor-arg元素;
- 如果存在不止一个参数时,当把参数传递给构造函数时,可能会存在歧义。要解决这个问题,那么只要构造函数的参数在 bean 定义中的顺序就是把这些参数提供给适当的构造函数的顺序就可以了;
2.3 自动注入
自动注入不需要在<bean>标签中添加其他标签注入属性值,而是自动从容器中找到相应的bean对象设置为属性值。
自动注入有两种配置方式:
- 全局配置:在<beans>中设置default-autowire属性可以定义所有bean对象的自动注入策略;
- 局部配置:在<bean>中设置autowire属性可以定义当前bean对象的自动注入策略;
autowire
的取值如下:
- no:不会进行自动注入;
- default:全局配置default相当于no,局部配置default表示使用全局配置;
- byName:在Spring容器中查找id与属性名相同的bean,并进行注入,需要提供set方法;
- byType:在Spring容器中查找类型与属性类型相同的bean,并进行注入,需要提供set方法;
- constructor:在Spring容器中查找id与属性名相同的bean,并进行注入,需要提供构造方法;
三、注解实现IOC
3.1 @Component
作用:用于创建对象,放入Spring容器,相当于<bean id="" class="" />
;
位置:类上方;
步骤:
- 在需要放入Spring容器中的类上方加上@Component注解,注解配置bean的默认id是首字母小写的类名。也可以手动设置bean的id值;
- 配置文件中配置包扫描;
3.2 @Repository、@Service、@Controller
作用:这三个注解和@Component的作用一样,使用它们是为了区分该类属于什么层;
位置:
- @Repository用于Dao层;
- @Service用于Service层;
- @Controller用于Controller层;
@Scope:
作用:指定bean的创建策略;
位置:类上方;
取值:singleton prototype request session globalsession;
3.3 AutoWired
作用:从容器中查找符合属性类型的对象自动注入属性中。用于代替<bean>中的依赖注入配置。
位置:属性上方、setter方法上方、构造方法上方。
注意:
- @Autowired写在属性上方进行依赖注入时,可以省略setter方法;
- 容器中没有对应类型(bean id)的对象会报错;
- 容器中有多个对象匹配类型时,会找beanId等于属性名的对象,找不到会报错;
3.4 @Qualifer
作用:在按照类型注入对象的基础上,再按照bean的id注入;
位置:属性上方;
注意:@Qualifier必须和@Autowired一起使用;
@Component
public class StudentService {
@Autowired
@Qualifier("studentDaoImpl2")
private StudentDao studentDao;
public Student findStudentById(int id){
return studentDao.findById(id);
}
}
3.5 @Scope
作用:指定bean的创建策略;
位置:类上方;
取值:singleton prototype request session globalsession;
3.6 @Value
作用:注入String类型和基本数据类型的属性值。
位置:属性上方
用法:
- 直接设置固定的属性值:
@Service
public class StudentService {
@Value("1")
private int count;
@Value("hello")
private String str;
}
- 获取配置文件中的属性值:
配置文件.properties中:
jdbc.username=root
jdbc.password=123456
Spring配置文件进行包扫描:
<context:property-placeholder location="db.properties"></context:property-placeholder>
类中采用注解对配置文件中的字段进行获取:
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
3.7 @Configuration、@ComponentScan
此时基于注解的IOC配置已经完成,但是我们依然离不开Spring的xml配置文件。接下来我们脱离bean.xml,使用纯注解实现IOC。
@Configutation:
纯注解实现IOC需要一个Java类代替xml文件。这个Java类上方需要添加@Configuration,表示该类是一个配置类,作用是代替配置文件。
@Configuration
public class SpringConfig {
}
@ComponentScan:
作用:指定spring在初始化容器时扫描的包。
位置:配置类上方
/**
* 配置类
*/
@Configuration
@ComponentScan("com.kk") // Spring容器的包扫描
public class SpringConfig {
}
3.8 @PropertySource
作用:代替配置文件中的<context:property-placeholder>扫描配置文件
位置:配置类上方
注意:配置文件位置前要加关键字classpath
@Configuration
@PropertySource("classpath:db.properties")
public class JdbcConfig {
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
}
3.9 @Bean
作用:将方法的返回值对象放入Spring容器中。常用于将第三方类的对象放入Spring容器中(由于第三方类的原码我们不能修改,那么在用Spring容器管理类对象时没办法在其源码上加上注解);
位置:位于配置类的上方;
属性:name-给bean对象设置id;
注意:@Bean修饰的方法如果有参数,Spring会根据参数类型从容器中查找可用的对象;
实例:
情境:如果将jdbc连接对象放入Spring容器中,我们无法修改其源码来添加@Component,此时就需要使用@Bean将该对象放入Spring容器中。
实现步骤如下:
- 在Pom.xml中添加驱动的依赖:
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
- 添加配置类,编写getConnnection()方法获取数据库连接对象:
/**
* 配置类
*/
@Configuration
@ComponentScan("com.kk") // Spring容器的包扫描
public class SpringConfig {
/**
* 获取数据库连接
* @return
*/
@Bean(name = "conn")
public Connection getConnection(){
try{
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test",
"root", "root");
return conn;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
- 测试
@Test
public void test2(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); // 配置类
Connection conn = (Connection) context.getBean("conn");
System.out.println(conn);
}
- 输出
com.mysql.jdbc.JDBC4Connection@14f9390f
3.10 @Import
作用:如果配置过多,会有多个配置类,该注解可以为主配置类导入其他配置类
位置:主配置类上方
// Jdbc配置类
@Configuration
public class JdbcConfig {
/**
* 获取数据库连接
* @return
*/
@Bean(name = "conn")
public Connection getConnection(){
try{
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test",
"root", "root");
return conn;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
// 主配置类
@Configuration
@ComponentScan("com.kk")
@Import(JdbcConfig.class)
public class SpringConfig {
}
致谢&部分资源出处:百战程序员