Spring介绍

1. Spring是什么?

Spring是一个开放源代码的设计层面框架,解决业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。

Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。

2. Spring框架特点

  • Spring是一个轻量级且非侵入式的开源框架 。
  • Spring是包含并管理应用对象的配置和生命周期的一种容器。
  • Spring是可以将简单的组件配置、组合成为复杂的应用的框架。
  • Spring通过控制反转(IoC) 促进了低耦合。
  • Spring提供了面向切面编程(AOP) 的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发。

3. Spring框架组成

Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式。

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

Spring Core(核心容器):核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

Spring Context(上下文):Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。

Spring AOP(面向切片):通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。

Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

重要拓展:Spring Boot与Spring Cloud

  • Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务。
  • Spring Cloud是基于Spring Boot实现的。
  • Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架。
  • Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
  • SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。

4. 控制反转(IOC)

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转(IOC)后将对象的创建转移给第三方调用者,控制反转可认为是获得依赖对象的方式反转

通俗来讲,以前所有东西都是由程序去进行控制创建(代码写死,直接实例化对象), 而现在是由调用者自行控制创建对象(由调用者按需求传入对象),把主动权交给了调用者,程序不用去管怎么创建,怎么实现了,它只负责提供一个接口。

4.1 Spring IOC容器

官方解释:

The org.springframework.context.ApplicationContext interface represents the Spring IoC container and is responsible for instantiating, configuring, and assembling the beans.

翻译:Spring IOC容器就是一个org.springframework.context.ApplicationContext的实例化对象,容器负责了实例化,配置以及装配一个bean

从代码层次来看:Spring IOC容器就是一个实现了ApplicationContext接口的对象,

从功能上来看: Spring 容器是 Spring 框架的核心,是用来管理对象的。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。

4.1.1 Spring IOC容器如何工作

IoC是Spring框架的核心内容,可以通过使用XML配置,也可以使用注解来实现了IoC,新版本的Spring也可以零配置实现IoC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离开的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

4.2 如何实例化Bean

从官网上来看,主要有以下三种方法:

  1. 构造方法
  2. 通过静态工厂方法
  3. 通过实例工厂方法 

简单分析下创建bean实例的方法

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// 1.获取这个bean的class属性,确保beanDefinition中beanClass属性已经完成解析
    	// 我们通过xml从<bean>标签中解析出来的class属性在刚刚开始的时候必定是个字符串
		Class<?> beanClass = resolveBeanClass(mbd, beanName);

		// 省略异常判断代码.....
    	
    	// 2.通过beanDefinition中的supplier实例化这个bean
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
		
    	// 3.通过FactoryMethod实例化这个bean
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

        // 4.下面这段代码都是在通过构造函数实例化这个Bean,分两种情况,一种是通过默认的无参构造,一种是通过推断出来的构造函数
		boolean resolved = false;
		boolean autowireNecessary = false;
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
    
    
		if (resolved) {
			if (autowireNecessary) {
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				return instantiateBean(beanName, mbd);
			}
		}

		// Candidate constructors for autowiring?
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}

		// Preferred constructors for default construction?
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}

		// No special handling: simply use no-arg constructor.
		return instantiateBean(beanName, mbd);
	}

通过上述代码可以发现实例化bean的方法主要分为三类:

1. 通过instanceSupplier获取,这个方法一般不常用,平常我们也使用不到,就不做过多探究,笔者认为,这应该是Spring提供的一种方便外部扩展的手段,让开发者能够更加灵活的实例化一个bean。

2. FactoryMethod,通过断点调试可以发现@compent、@Service、普通XML的方式(同@compent注解)、@Configuration等方式创建bean都是走instantiateUsingFactoryMethod(beanName, mbd, args)方法,即通过工厂方法实例化bean(包括静态工厂方法和实例工厂方法)。

public static void main(String[] args) {
    ClassPathXmlApplicationContext cc =
        new ClassPathXmlApplicationContext("application.xml");
    System.out.println(cc.getBean("service"));
}
<?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="myServiceImpl" class="com.dmz.official.service.Service"/>-->

	<!-- the factory bean, which contains a method called get() -->
	<bean id="myFactoryBean" class="com.dmz.official.service.MyFactoryBean">
		<!-- inject any dependencies required by this locator bean -->
	</bean>

	<!-- 测试实例工厂方法创建对象-->
	<bean id="clientService"
		  factory-bean="myFactoryBean"
		  factory-method="get"/>

	<!--测试静态工厂方法创建对象-->
	<bean id="service"
		  class="com.dmz.official.service.MyFactoryBean"
		  factory-method="staticGet"/>
</beans>

通过静态工厂方法这种方式特殊之处在于,包含这个静态方法的类,不需要实例化,不需要被Spring管理。Spring的调用逻辑大概是:

  1. 通过<bean>标签中的class属性得到一个Class对象
  2. 通过Class对象获取到对应的方法名称的Method对象
  3. 最后反射调用Method.invoke(null,args)获取bean

3. 构造方法

4.3 实例化总结

1. 对象实例化,只是得到一个对象,还不是一个完全的Spring中的Bean,我们实例化后的这个对象还没有完成依赖注入,没有走完一系列的声明周期,这里需要大家注意。

2. Spring官网上指明了,在Spring中实例化一个对象有三种方式:

  • 构造函数
  • 实例工厂方法
  • 静态工厂方法

3. 流程图

5. 依赖注入(DI)

5.1 依赖注入概念

依赖注入(Dependency Injection,DI):

  • 依赖 : 指Bean对象的创建依赖于容器。
  • 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配。

5.2 依赖注入方式

依赖注入主要分为两种方式:

官方解释:

  • 构造函数注入
  • Setter方法注入

5.3 代码测试

public class Main02 {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new 
            // config类主要完成对类的扫描
            AnnotationConfigApplicationContext(Config.class);
		Service service = (Service) ac.getBean("service");
		service.test();
	}
}

// setter方法注入
@Component
public class Service {

	private LuBanService luBanService;

	public Service() {
		System.out.println("service create");
	}

	public void test(){
		System.out.println(luBanService);
	}

	// 通过autowired指定使用set方法完成注入
	@Autowired
	public void setLuBanService(LuBanService luBanService) {
		System.out.println("注入luBanService by setter");
		this.luBanService = luBanService;
	}
}

// 构造方法注入
@Component
public class Service {

	private LuBanService luBanService;
    
    public Service() {
		System.out.println("service create by no args constructor");
	}
	
    // 通过Autowired指定使用这个构造函数,否则默认会使用无参
	@Autowired
	public Service(LuBanService luBanService) {
		System.out.println("注入luBanService by constructor with arg");
		this.luBanService = luBanService;
		System.out.println("service create by constructor with arg");
	}

	public void test(){
		System.out.println(luBanService);
	}
}

@Component
public class LuBanService {
	LuBanService(){
		System.out.println("luBan create ");
	}
}

上述代码中,@Autowired 注解加在setter 方法和属性字段上的区别:

1. 直接添加@Autowired注解到字段上,不需要提供setter方法也能完成注入。以上面的例子来说,Spring会通过反射获取到Service中luBanService这个字段,然后通过反射包的方法,Filed.set(Service,luBanService)这种方式来完成注入。

2. 将@Autowired添加到setter方法时,我们可以通过断点看一下方法的调用栈,如下:

对于这种方式来说,最终是通过Method.invoke(object,args)的方式来完成注入的,这里的method对象就是我们的setter方法。

同时采用构造注入加属性注入会怎么样呢?

Spring虽然能在构造函数里完成属性注入,但是这属于实例化对象阶段做的事情,那么在后面真正进行属性注入的时候,属性注入会将构造函数注入的属性覆盖

总结:构造函数注入和属性注入使用官方推荐:

1. 构造函数注入跟setter方法注入可以混用,对于一些强制的依赖,我们最好使用构造函数注入,对于一些可选依赖我们可以采用setter方法注入。

2. Spring团队推荐使用构造函数的方式完成注入。但是对于一些参数过长的构造函数,Spring是不推荐的。

5.3 方法注入

5.3.1 Bean的作用域

类别说明

Singleton

在Spring IoC容器中仅存在一个Bean实例,Bean以单例方式存在,Spring IoC容器中所有对象默认是单例的

Prototype

原型模式,每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行new XxxBean()

Request

每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境

Session

同一个Http Session 共享一个Bean,不同的Session 使用不用Bean,仅适用于WebApplicationContext环境

5.3.2 方法注入作用

有了上面Bean的作用域基础知识之后,就能理解方法注入的作用。

@Component
public class MyService {

	@Autowired
	private LuBanService luBanService;

	public void test(int a){
		luBanService.addAndPrint(a);
	}

}

@Component
// 原型对象
@Scope("prototype")
public class LuBanService {
	int i;

	LuBanService() {
		System.out.println("luBan create ");
	}
	// 每次将当前对象的属性i+a然后打印
	public void addAndPrint(int a) {
		i+=a;
		System.out.println(i);
	}
}

public class Main02 {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
		MyService service = (MyService) ac.getBean("myService");
		service.test(1);
		service.test(2);
		service.test(3);
	}
}

如上述代码,我们目的是每次都获取到不同的 LuBanService 对象,然而实际是每次调用到的LuBanService是同一个对象。当然,这也很好理解,因为在依赖注入阶段我们就完成了LuBanService的注入,之后我们在调用测试方法时,不会再去进行注入,所以我们一直使用的是同一个对象。

方法注入的作用就是解决此问题,每次使用Bean时都重新去获取。

实现方式:

1. 通过注入上下文(applicationContext对象)获取Bean

// 实现org.springframework.context.ApplicationContextAware接口
@Component
public class MyService implements ApplicationContextAware {

	private ApplicationContext applicationContext;

	public void test(int a) {
		LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));
		luBanService.addAndPrint(a);
	}

	@Override
	public void setApplicationContext(@Nullable ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}

// 直接注入上下文
@Component
public class MyService{
	@Autowired
	private ApplicationContext applicationContext;

	public void test(int a) {
		LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));
		luBanService.addAndPrint(a);
	}
}

2. 通过@LookUp的方式(也分为注解跟XML两种方式,这里只演示注解的)

@Component
public class MyService{
	public void test(int a) {
		LuBanService luBanService = lookUp();
		luBanService.addAndPrint(a);
	}

	@Lookup
	public LuBanService lookUp(){
		return null;
	}
}

3. 方法注入 之 replace-method(使用很少)

5.4 依赖注入总结

Spring中的依赖注入就是属性注入。

  • 我们知道一个对象由两部分组成:属性+行为(方法),可以说Spring通过属性注入+方法注入的方式掌控的整个bean。
  • 属性注入跟方法注入都是Spring提供给我们用来处理Bean之间协作关系的手段。
  • 属性注入有两种方式:构造函数,Setter方法。
  • 方法注入(LookUp Method跟Replace Method)需要依赖动态代理完成。
  • 方法注入对属性注入进行了一定程度上的补充,因为属性注入的情况下,原型对象可能会失去原型的意义。

6. 自动注入

官方说明:

翻译:

Spring可以自动注入互相协作的bean之间的依赖。自动注入有以下两个好处:

  • 自动注入能显著的减少我们指定属性或构造参数的必要,即可以不用依赖注入的方式为类注入相关依赖对象。
  • 自动装配可以随着对象的演化更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而不需要修改配置。因此,自动装配在开发过程中特别有用,但是当我们的代码库变的稳定时,自动装配也不会影响我们将装配方式切换到精确注入。

自动注入四种模型:

模型解释
no

这是目前Spring默认的注入模型,也可以说默认情况下Spring是关闭自动注入,必须要我们通过setter方法或者构造函数完成依赖注入,并且Spring也不推荐修改默认配置。建议我们使用精确的方式注入依赖。

byName

这种方式,我们为了让Spring完成自动注入需要提供两个条件:

  1. 提供setter方法
  2. 如果需要注入的属性为xxx,那么setter方法命名必须是setXxx,也就是说,命名必须规范

在找不到对应名称的bean的情况下,Spring也不会报错,只是不会给我们完成注入。

byType

测试代码跟之前唯一不同的就是修改配置autowire="byType",这里我们测试以下三种异常情况:

  1. 找不到合适类型的bean,发现不报异常,同时不进行注入
  2. 找到了多个合适类型的bean,Spring会直接报错Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.dmz.official.service.DmzService' available: expected single matching bean but found 2: dmzService,dmzService2
  3. set方法中有两个参数,切两个参数都能找到唯一一个类型符合的bean,不报异常,也不进行注入

另外需要说明的是,我在测试的过程,将set方法仅仅命名为set,像这样public void set(DmzService dmzService),这种情况下Spring也不会进行注入。

我们可以发现,对于这两种注入模型都是依赖setter方法完成注入的,并且对setter方法命名有一定要求(只要我们平常遵从代码书写规范,一般也不会踩到这些坑)。第一,不能有多个参数;第二,不能仅仅命名为set

constructor当我们使用这种注入模型时,Spring会根据构造函数查找有没有对应参数名称的bean,有的话完成注入(跟前文的byName差不多),如果根据名称没找到,那么它会再根据类型进行查找,如果根据类型还是没找到,就会报错。

自动注入的缺陷:

1. 精确注入会覆盖自动注入。并且我们不能注入基本数据类型,字符串,Class类型(这些数据的数组也不行)。而且这是Spring故意这样设计的;

2. 自动注入不如精确注入准确。而且我们在使用自动注入时,对象之间的依赖关系不明确;

3. 对于一些为Spring容器生成文档的工具,无法获取依赖关系;

4. 容器中的多个bean定义可能会与自动注入的setter方法或构造函数参数指定的类型匹配。对于数组、集合或映射实例,这可能不会产生什么问题。但是,对于期望单个值的依赖项,我们无法随意确定到底有谁进行注入。如果没有唯一的bean定义可用,则会抛出异常。

补充一些注解的知识:

@Autowired 与 @Qualifier

1. @Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。

2. @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配。

3. @Qualifier不能单独使用。

@Resource

1. @Resource如有指定的name属性,先按该属性指定的name进行byName方式查找装配;其次再进行默认的byName方式进行装配。
2. 如果以上都不成功,则按byType的方式自动装配。
3. 都不成功,则报异常。

@Autowired与@Resource异同:

1. @Autowired与@Resource都可以用来装配bean。都可以写在属性上,或写在setter方法上。
2. @Autowired 默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用。
3. @Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
4. 它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

总结:

此篇文章只是简单介绍了Spring重要的知识点,很多地方并没有深挖,对Spring感兴趣的同学可以参考Spring官网阅读 | 总结篇_程序员DMZ的博客-CSDN博客,阅读Spring官方文档。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值