Spring主要提供了三大场景的功能,分别是控制反转IOC、属性注入DI、面向切面AOP;这三大场景的实现都有一个核心支撑,就是需要依赖Bean容器(也可以叫Spring容器、IOC容器),Spring的鲜明特点是轻量级、一站式、多组件支持的一个框架,他提供了Java开发的一站式解决方案,无论是MVC还是ORM,Spring都有很好的支持。
Spring是一个免费开源,轻量级非侵入式的一站式框架,他的核心是IOC、DI、AOP和事务的支持,他支持各种框架的整合,可以将Spring视为后端的一个脚手架。下面是一些Spring相关的网址,记在这里方便获取。
官网:https://spring.io/projects/spring-framework
github:https://github.com/spring-projects/spring-framework
maven地址:https://mvnrepository.com/artifact/org.springframework/spring/5.3.9
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>springartifactId>
<version>5.3.9version>
<type>pomtype>
dependency>
上面简单介绍了Spring的IOC、DI、AOP,他们都是Spring最核心的东西,不过Spring并不只是这一点东西,官方将Spring划分为成了如下几个模块,下面对各个模块做下简单的介绍,我们学习Spring最需要掌握的还是他的IOC、DI、AOP这三点,以下各个模块信息介绍来源为官网。

上面一通废话说完了,其实真正使用Spring时,我们就是使用Spring提供的四点特色功能:IOC、DI、AOP、声明式事务,其他的功能不算他的特色,也没有什么难度,我们着重掌握这四点即可,下面先介绍下IOC。
IOC(Inversion of Control)控制反转,也就是对象创建的控制权的反转,那为什么要反转呢?我们自己创建不是更好吗?控制反转最直观的效果就是可以解耦合,一个接口我们可以注入多种实现类,具体使用哪个就由调用方决定好了,但是若是自己创建一旦变动就需要更改实现,这是很麻烦的,一个程序优秀与否耦合度是一个重要指标。那Spring又是怎么实现控制反转的呢?事实上Spring通过反射加工厂模式实现了IOC的功能,这篇文章不会详细分析IOC的底层,我们只做使用层面的探究。总结一句话:控制反转是一种通过xml或者注解描述,同时通过第三方生产或获取对象的方式,在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入DI。
我们可以通过xml配置将对象的创建权交给IOC容器,我们只需要提供如下一个配置即可
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean name="student2" id="student" class="com.cheng.pojo.Studeng">
<property name="name" value="cheng"/>
<property name="age" value="18"/>
bean>
beans>
beans>
通过这样一个简单的配置我们就将实体类com.cheng.pojo.Studeng交给了IOC容器来管理,下面我们来测试下,如下:

我们发现正常拿到了注入的对象,说明IOC容器确实帮我们创建出了一个对象。那我们就来总结下这个过程吧。
导入依赖---->创建applicationcontext.xml配置文件---->创建测试类。就这么三步就完成了一个IOC的开发,是不是非常简单呢?虽然很简单,不过配置文件中的信息我们还是需要解读下的,一起来看下配置文件的信息吧。
上面一通介绍,我们应该会使用属性注入的方式为对象赋值了,但我们可以发现这些属性都是基本数据类型,要是对象的属性是一个自定义的引用类型怎么办呢?这时就需要使用property的其他属性了:
<beans>
<bean name="student2" id="student" class="com.cheng.pojo.Studeng">
<property name="name" value="cheng"/>
<property name="age" value="18"/>
<property name="studeng" ref="studeng"/>
bean>
<bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng">
<property name="name" value="jin"/>
<property name="age" value="3"/>
bean>
beans>
上面的代码中,我们就完成了引用数据类型的赋值,一个studeng对象中维护了另一个studeng对象,此时就可能会有另一个问题产生:若是这两个对象相互引用怎么办?就会有循环依赖的现象产生,这个问题后面会说,我们可以先一起思考思考。
此外,我们对象是创建出来了,也可以正常获取,那多次获取的对象会不会是一个呢?事实上当我们不作指定时,IOC容器中的对象都是单例的模式,我们可以验证下,如下所示,可以看到多次获取后的对象他们地址相等,说明了是IOC容器中的对象都是单例的。

那要是,就是不想让对象是单例的该怎么处理呢?此时需要引入bean的另一个属性了scope:
这样就基本介绍完了xml实现IOC时使用属性注入的实现,有一点需要特别注意属性注入底层需要调用get、set方法,这些是必须要提供的。
已经懂了属性注入,那么构造器注入其实就相当简单了,使用构造器注入无非就是将property标签换成constructor-arg而已,其他大致相同,不过使用构造器注入官方给出了三种可以使用的方式,如下所以:
方式一:name-value模式
该模式使用与属性注入完全一样,都是通过name来声明bean的属性名,通过value或者ref来声明属性值。
<bean id="studengContru" class="com.cheng.pojo.Studeng">
<constructor-arg name="name" value="mu" ></constructor-arg>
<constructor-arg name="age" value="0" ></constructor-arg>
<constructor-arg name="studeng" ref="studentNew" ></constructor-arg>
</bean>
<bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng">
<property name="name" value="jin"/>
<property name="age" value="3"/>
</bean>
方式二:type-value模式
该模式不推荐使用,该模式的原理是通过bean的类型与这里声明的类型进行匹配来映射的,若是多个参数是同种数据类型就会无法处理了,所以这种赋值的方式不推荐。
<bean id="studengContru2" class="com.cheng.pojo.Studeng">
<constructor-arg type="java.lang.String" value="mu1" >constructor-arg>
<constructor-arg type="java.lang.Integer" value="0" >constructor-arg>
<constructor-arg type="com.cheng.pojo.Studeng" ref="studentNew" >constructor-arg>
bean>
<bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng">
<property name="name" value="jin"/>
<property name="age" value="3"/>
bean>
方式三:index-value模式
该模式是通过下标来匹配参数的,使用起来也没有什么问题,只不过相对于通过属性名来映射来说,可读性并不是太高,综合以上三种实现构造器注入的方式,建议使用与property相同的name-value模式来进行属性的注入。
<bean id="studengContru3" class="com.cheng.pojo.Studeng">
<constructor-arg index="0" value="mu2" >constructor-arg>
<constructor-arg index="1" value="0" >constructor-arg>
<constructor-arg index="2" ref="studentNew" >constructor-arg>
bean>
<bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng">
<property name="name" value="jin"/>
<property name="age" value="3"/>
bean>
下面来测试下三种通过构造器注入对象属性的方式是否好用,如下所示,可以看到都是好用的,说明我们写的没有什么问题,不过日常开发使用中若是使用构造器注入建议使用name-value的模式,若是不是必须使用构造器建议还是使用属性注入的方式为bean的属性赋值。

从上面的代码中我们可以看出也就一个标签constructor-arg没有见过,此外还有他的index和type属性没有使用过,其他与property的属性是没有区别的,下面介绍下这些新出现的标签和属性吧。

事实上构造器注入并不是一种特别推荐的方式,推荐的还是使用属性注入,因为当发生循环依赖时,构造器注入就会报错,这是因为使用构造器注入,必须保证需要的所有的属性已经创建完了,才能使用构造器注入,但是循环依赖时因为有循环所以对象不可能全部创建就会报错,所以一般还是建议使用属性注入的方式,如下所示,这两个bean互相引用时,获取任何一个bean都是失败的,那为什么使用属性注入不会呢?这就必须说Spring的三级缓存了,这个单独一个章节讲这里就不细说了,先对这个概念有个了解即可。
<bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng">
<constructor-arg name="name" value="yu" index="0"></constructor-arg>
<constructor-arg name="age" value="0" index="1"></constructor-arg>
<constructor-arg name="studeng" ref="studengContru" index="2"></constructor-arg>
</bean>
<bean id="studengContru" class="com.cheng.pojo.Studeng">
<constructor-arg name="name" value="mu" index="0"></constructor-arg>
<constructor-arg name="age" value="0" index="1"></constructor-arg>
<constructor-arg name="studeng" ref="studentNew" index="2"></constructor-arg>
</bean>
component组件的意思,Spring中通过这个注解可以将对应的javaBean的对象创建权交给IOC容器,不过component需要与component-scan配合使用,这样我们就无需在手动的写bean标签了,在真正使用中也都是使用这种模式来将javaBean的创建权交给IOC容器的。需要说的是component还有三个衍生注解:controller、service、repository,这三个注解只是为了区分不同的业务场景而设定的,其实他们三个和component没有任何区别,都可以混用,因为没有任何区别,这里就只展示component与component-scan的使用饿了。
上面已经介绍了使用xml通过构造器或者属性来实现的IOC、通过component配合component-scan实现IOC,其实还有一种比较常用的方式就是通过Configuration和Bean注解来实现IOC,这种IOC的实现方式在SpringBoot中最为常见,SpringBoot的默认大于配置其实就是通过大量的Configuration+Bean注解来实现的,一起看下要怎么使用他们吧。
DI(Dependency Injection)依赖注入的意思,上个章节里介绍了IOC,其实里面已经使用过了DI了,我们使用property来进行属性注入时就是DI的过程。依赖注入顾名思义就是将依赖注入进来,这个依赖可能是常量也可能是来自于IOC容器,上面介绍了两种DI的方式,分别是通过构造器进行DI、通过set方法进行DI(property属性注入),其实还有其他方式可以实现DI,只不过这两种是我们最常用的方式,这个章节里就来详细说说DI的三大方式。
这块内容其实第二节介绍IOC时已经介绍过了,使用构造器进行DI时,我们有三种选择,使用name、type、index三种都可以完成构造器的DI,这里展示下这三种DI的代码,其余部分就不重复展示了:
方式一:name-value模式
该模式使用与属性注入完全一样,都是通过name来声明bean的属性名,通过value或者ref来声明属性值。
<bean id="studengContru" class="com.cheng.pojo.Studeng">
<constructor-arg name="name" value="mu" ></constructor-arg>
<constructor-arg name="age" value="0" ></constructor-arg>
<constructor-arg name="studeng" ref="studentNew" ></constructor-arg>
</bean>
<bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng">
<property name="name" value="jin"/>
<property name="age" value="3"/>
</bean>
方式二:type-value模式
该模式不推荐使用,该模式的原理是通过bean的类型与这里声明的类型进行匹配来映射的,若是多个参数是同种数据类型就会无法处理了,所以这种赋值的方式不推荐。
<bean id="studengContru2" class="com.cheng.pojo.Studeng">
<constructor-arg type="java.lang.String" value="mu1" >constructor-arg>
<constructor-arg type="java.lang.Integer" value="0" >constructor-arg>
<constructor-arg type="com.cheng.pojo.Studeng" ref="studentNew" >constructor-arg>
bean>
<bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng">
<property name="name" value="jin"/>
<property name="age" value="3"/>
bean>
方式三:index-value模式
该模式是通过下标来匹配参数的,使用起来也没有什么问题,只不过相对于通过属性名来映射来说,可读性并不是太高,综合以上三种实现构造器注入的方式,建议使用与property相同的name-value模式来进行属性的注入。
<bean id="studengContru3" class="com.cheng.pojo.Studeng">
<constructor-arg index="0" value="mu2" >constructor-arg>
<constructor-arg index="1" value="0" >constructor-arg>
<constructor-arg index="2" ref="studentNew" >constructor-arg>
bean>
<bean name="studeng" id="studentNew" class="com.cheng.pojo.Studeng">
<property name="name" value="jin"/>
<property name="age" value="3"/>
bean>
set方法进行DI才是我们的首选,也是我们开发中真正比较常用的方式,介绍依赖set方法进行DI时,笔者会较少所有常用的数据类型的注入包括基本数据类型、引用数据类型、数组、List、Map、set等集合,其实都是大同小异,这里使用Supplier和Shop两个自定义类型来演示这些数据的注入,Supplier和Shop的代码如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Supplier {
private int oid;
private Shop shop;
private String[] address;
private List<String> product;
private Map<String,String> detail;
private Set<String> bankCode;
private Properties properties;
}
@Data
public class Shop {
private String name;
}
下面开始正式各种数据类型的注入方式:
基本数据类型注入
<property name="oid" value="100001"/>
引用数据类型注入
<property name="shop" ref="shop"/>
数组数据类型注入
<property name="address">
<array>
<value>北京市value>
<value>上海市value>
<value>广州市value>
array>
property>
List数据类型注入
<property name="product">
<list>
<value>商品1号value>
<value>商品二号value>
<value>商品三号value>
list>
property>
Map数据类型注入
<property name="detail">
<map>
<entry key="keyOne" value="valueOne">entry>
<entry key="keyTwo" value="valueTwo">entry>
map>
property>
Set数据类型注入
<property name="bankCode">
<set>
<value>1111111222222333value>
<value>2222221111133333value>
<value>4444442222222111value>
set>
property>
Properties数据类型注入
<property name="properties">
<props>
<prop key="jdbc.driver">jdbc.mysql.Deriverprop>
<prop key="jdbc.url">mysql:3306prop>
<prop key="jdbc.user">rootprop>
<prop key="jdgc.pwd">1111prop>
props>
property>
以上展示了七种不同的数据类型的注入,不过在真正的开发环节我们大多使用的都是基本数据类型的注入和引用数据类型注入,但是其他类型的数据类型我们也是要会的,指不定啥时候会用到,下面测试下这七种数据类型依赖注入是否成功:

下面是详细输出,可以看到我们设置的所有属性都拿到了,说明以上操作都没有问题。
Supplier(oid=100001, shop=Shop(name=8号店铺), address=[北京市, 上海市, 广州市], product=[商品1号, 商品二号, 商品三号], detail={keyOne=valueOne, keyTwo=valueTwo}, bankCode=[1111111222222333, 2222221111133333, 4444442222222111], properties={jdbc.url=mysql:3306, jdgc.pwd=1111, jdbc.driver=jdbc.mysql.Deriver, jdbc.user=root})
除了我们常用的构造器注入和set方法注入以外,spring还提供了c命名空间和p命名空间两种依赖注入的方式,其实这两种依赖注入底层依然是构造器注入和set方法注入,只不过是简化了操作而已,下面展示下这两种操作。
c namespace
使用c命名空间需要导入其对应的约束:xmlns:c=“http://www.springframework.org/schema/c”,然后我们就可以通过如下方式在bean标签中使用即可,c命名空间的原理是使用bean的构造器来实现DI的,所以要求我们使用c命名空间时必须提供对应的有参构造器。
<bean id="shop" class="com.cheng.pojo.Shop" c:name="张三的店铺">
bean>
p namespace
使用p命名空间需要导入其对应的约束:xmlns:p=“http://www.springframework.org/schema/p”,然后我们就可以通过如下方式在bean标签中使用即可,p命名空间的原理是使用bean的set方法来实现DI的,所以要求我们使用p命名空间时必须提供对应的set方法才行。
<bean id="shopNew" class="com.cheng.pojo.Shop" p:name="李四的店铺">
bean>
可以发现当我们使用c命名空间和p命名空间来完成DI时,是相对来说较为简单的,无需在单独写property标签和construcotr-arg标签,其实c命名空间和p命名空间也就是省略了这两个标签的书写而已,底层还是一样的。
bean标签提供了autowired属性,用来支持自动注入,何为自动注入呢,就是不需要我们显示的指示使用哪个引用,Spring会自动帮我们寻找。autowired属性支持两个参数:byName、byType,下面就根据这两种不同的自动注入方式来聊聊bean标签的自动装配吧。
<bean id="people" class="com.cheng.pojo.People" autowire="byName">
<property name="name" value="学习ing"/>
<property name="age" value="28"/>
bean>
<bean id="dog" class="com.cheng.pojo.Dog">
<property name="name" value="阿花"/>
bean>
<bean id="computer" class="com.cheng.pojo.Computer">
<property name="category" value="HuaWei"/>
bean>
<bean id="people" class="com.cheng.pojo.People" autowire="byType">
<property name="name" value="学习ing"/>
<property name="age" value="28"/>
bean>
<bean class="com.cheng.pojo.Dog">
<property name="name" value="阿花"/>
bean>
<bean class="com.cheng.pojo.Computer">
<property name="category" value="HuaWei"/>
bean>
这就是通过bean标签的autowired属性来实现的自动DI了,不过真正的开发中我们都是使用Autowired、Qualifier、Resource这三种注解来实现自动DI,而不是使用bean标签的autowired,以上两种比较简单,就不重复测试了。
这里要介绍的才是工作中真正使用的部分,工作中最为常用的就是Autowired或者Resource了,至于Qualifier必须得与Autowired配合使用,所以我们要么是使用Autowired要么是使用Resource了,这三种注解描述的DI都是针对引用数据类型来说的而不是基本数据类型这个必须要清晰,下面来介绍下他们三个。
@Autowired根据类型自动装配
Autowired注解与bean标签中的autowired属性是不同的,他们只是名称类似,Autowired模式是byType的方式去寻找对象,使用如下所示:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class People {
private String name;
private Integer age;
@Autowired
private Dog dog;
@Autowired
private Computer computer;
}
以上是java代码,下面是xml配置文件部分,这部分必须要说的是,要想使用注解,必须有两个前提
①导入context约束
②开启注解配置支持:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="people" class="com.cheng.pojo.People">
<property name="name" value="学习ing"/>
<property name="age" value="28"/>
bean>
<bean id="dog111" class="com.cheng.pojo.Dog">
<property name="name" value="阿花"/>
bean>
<bean id="computer111" class="com.cheng.pojo.Computer">
<property name="category" value="HuaWei"/>
bean>
beans>
下面测试下使用Autowired注入属性是否正常,如下图可见应用类型使用Autowired注解注入正常。

@Qualifier配合Autowired实现根据名称自动装配
若是IOC容器中同一类型的bean存在多个会出现什么情况?此时我们直接使用Autowired就会报这个异常:NoUniqueBeanDefinitionException,要想解决这个问题有两种办法,要么再加一个注解Qualifier,通过Qualifier来指定使用IOC容器中的哪个bean,要么直接使用Resource直接,这里先说下Qualifier的使用。
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class People {
private String name;
private Integer age;
@Autowired
@Qualifier(value = "dog222")
private Dog dog;
@Autowired
private Computer computer;
}
以上是java代码,与上一个例子,相比就是多了一个Qualifier直接,该注解有一个value属性,我们通过value来执行想要使用IOC容器中的哪个bean,value对应容器中bean的id。下面是xml文件部分,与上一个例子相比只多了一个Dog类的实例。
<context:annotation-config/>
<bean id="people" class="com.cheng.pojo.People">
<property name="name" value="学习ing"/>
<property name="age" value="28"/>
bean>
<bean id="dog111" class="com.cheng.pojo.Dog">
<property name="name" value="阿花"/>
bean>
<bean id="dog222" class="com.cheng.pojo.Dog">
<property name="name" value="阿花"/>
bean>
<bean id="computer111" class="com.cheng.pojo.Computer">
<property name="category" value="HuaWei"/>
bean>
加上Qualifier注解后就不会提示NoUniqueBeanDefinitionException这个问题了。
@Resource默认根据类型类型装配,加name则根据名称装配
Autowired和Qualifier都是Spring提供的注解,Resource则是java提供的注解,这与前面两个注解是本质的不同,那么为什么java提供的注解可以在Spring的容器中找得到对应的Bean呢,这就需要说下JavaEE的JNDI规范了,这里不深入探讨,只简略说下原委帮助理解,JNDI主要是为服务和资源提供一个间接层的模块,他是一种规范,Spring就实现了这种规范,所以我们可以使用Resource来获取IOC容器中的bean。下面是Resource的部分源码
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
/**
* The JNDI name of the resource. For field annotations,
* the default is the field name. For method annotations,
* the default is the JavaBeans property name corresponding
* to the method. For class annotations, there is no default
* and this must be specified.
*/
String name() default "";
......
......
笔者英语也不好,我们可以借助翻译软件翻译一下,再结合原文看下就可以理解name方法的大致意思:这个值应该是资源的JNDI名称,当注解加在属性上时,那么这个默认的名称是属性名。。。。
这样是不是就理解了Resource的用法了呢?也就是说当我们只加了==Resource不指定name时,则默认根据属性的名称去IOC容器中找bean,若是找不到才会根据类型去找,若是我们显示指定了name值,则会直接根据名称去IOC容器中查找对应的bean。==这段话其实也很好验证,要是想要验证是不是默认根据属性名查找,我们只需要两步:
①不为Resource指定name,在配置文件中配置属性名对应的bean和另外两个同类型不同id的bean,测试Resource注入正常没问题。
②我们删掉与属性名相同的bean,然后再测试就会发现报了这个错:NoUniqueBeanDefinitionException,这是因为根据默认的name值找不到bean,然后根据类型查找发现了多个同类型的bean,所以报错了,这样就可以验证Resource默认的name就是属性名了,同时也可以验证名称不匹配时会根据类型查找。
上面一直都是在说Resource的原理,来看下他的使用吧,使用其实很简单:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class People {
private String name;
private Integer age;
@Resource
private Dog dog;
@Autowired
private Computer computer;
}
上面是使用Resource的代码,下面是对应的配置文件核心代码
<context:annotation-config/>
<bean id="people" class="com.cheng.pojo.People">
<property name="name" value="学习ing"/>
<property name="age" value="28"/>
bean>
<bean id="dog111" class="com.cheng.pojo.Dog">
<property name="name" value="阿花"/>
bean>
<bean id="dog" class="com.cheng.pojo.Dog">
<property name="name" value="阿花"/>
bean>
<bean id="computer111" class="com.cheng.pojo.Computer">
<property name="category" value="HuaWei"/>
bean>
下面提供测试截图:

下面展示下以上部分的总结导图:

未完待续。。。。。。。。
四、AOP
1.静态代理
2.动态代理-jdk
3.动态代理-cglb
4.xml配置实现AOP
5.注解实现AOP
6.AOP的应用展示
五、事务
1.声明式事务
2.编程式事务
3.Spring事务与MyBatis事务整合
六、Spring的其他关键点
上面五个小节基本介绍完了Spring的核心,掌握了这些其实已经可以说熟练掌握Spring了,不过还有一些小功能可能没有阐述全面,这个小节过个补充。
1.bean的作用域
介绍IOC时其实提过bean的作用域,我们是通过bean标签的scope属性来控制bean的作用域的,在说IOC时提到过scope的值可以是singleton、prototype两种,其实除了这两种还支持另外三种值的设定request、session、application,乍一看就会发现这三个值都是与web有关的,事实上也是如此只有在web环境下才支持这三种值的设定(SpringMvc中),日常使用中基本全是singleton模式,不过其他的也是要了解的。
- singleton:单例模式,使用单例模式创建bean
~~~xml
~~~
- prototype:原型模式,每次请求都会创建一个bean,多线程场景中可能会用
~~~xml
~~~
- request:在一次request共用一个bean
~~~xml
~~~
- session:在session中共用一个bean
~~~xml
~~~
- application:在一个上下文中共用一个bean,其实和单例有些类似了
~~~xml
~~~
2.Spring的缓存
七、总结Spring注解