文章目录
一、Spring简介
1、Spring是什么以及特性?
一句话概括:Spring是一个轻量级的 控制反转(IoC)和面向切面(AOP) 的容器(框架)。
特性:
非侵入式
:基于Spring开发的应用中的对象可以不依赖于Spring的API控制反转
:IOC——Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。依赖注入
:DI——Dependency Injection,是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。面向切面编程
:Aspect Oriented Programming——AOP容器
:Spring 是一个容器,因为它包含并且管理应用对象的生命周期组件化
:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。一站式
:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)
2、Spring的组件
1、Core Container(Spring的核心容器)
Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。具体介绍如下
Beans 模块
:提供了框架的基础部分,包括控制反转和依赖注入。Core 核心模块
:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类。Context 上下文模块
:建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。SpEL 模块
:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。
2、Data Access/Integration(数据访问/集成)
JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
3、Web模块
Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作
4、AOP、Aspects、Instrumentation和Messaging
在 Core Container 之上是 AOP、Aspects 等模块,具体介绍如下:
AOP 模块
:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。Aspects 模块
:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。Instrumentation 模块
:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。messaging 模块
:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。 jcl 模块: Spring 5.x中新增了日志框架集成的模块
5、Test模块
Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。
包含Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient
二、Spring 控制反转(IOC)和依赖注入(DI)
1、概念理解
1.1、什么是IOC
Ioc—Inversion of Control
,即“控制反转”,不是什么技术,而是一种设计思想,以前我们获取一个对象时采用的是自己创建一个的方式,这是一个主动的过程;而控制反转后,当我们需要对象时就跟工厂要,而工厂来帮我们创建或者查找对象,这是一个被动的过程。
总结:控制反转就是把创建和管理 bean 的过程转移给了第三方。而这个第三方,就是 Spring IoC Container,对于 IoC 来说,最重要的就是容器。
Spring IOC 容器:Spring 框架的核心是 Spring 容器。容器创建对象,将它们装配在一起,配置它们并管理它们的完整生命周期。Spring 容器使用依赖注入来管理组成应用程序的组件。
Spring Bean:Bean 其实就是包装了的 Object(对象),无论是控制反转还是依赖注入,它们的主语都是 object,而 bean 就是由第三方包装好了的 object。(想一下别人送礼物给你的时候都是要包装一下的)
深入思考:
1) 谁控制谁?控制了什么?
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建。
谁控制谁? 当然是IoC 容器控制了对象(Bean)
控制什么? 那就是主要控制了外部资源获取(不只是对象包括比如文件等)
2)为何是反转,哪些方面反转了?
有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象。
为何是反转? 因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;
哪些方面反转了? 依赖对象的获取被反转了。
3)图例解析
传统程序设计下,都是主动去创建相关对象然后再组合起来:
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图
4)为什么要用 IoC 这种思想呢?换句话说,IoC 能给我们带来什么好处?
答:解耦!!!!
它把对象之间的依赖关系转成用配置文件来管理,由 Spring IoC Container 来管理。
在项目中,底层的实现都是由很多个对象组成的,对象之间彼此合作实现项目的业务逻辑。但是,很多很多对象紧密结合在一起,一旦有一方出问题了,必然会对其他对象有所影响,所以才有了解藕的这种设计思想。
如上图所示,本来 ABCD 是互相关联在一起的,当加入第三方容器的管理之后,每个对象都和第三方法的 IoC 容器关联,彼此之间不再直接联系在一起了,没有了耦合关系,全部对象都交由容器来控制,降低了这些对象的亲密度,就叫“解藕”。
1.2、什么是DI
DI:Dependency Injection,即“依赖注入”: 组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
深入思考
1)谁依赖于谁? 为什么需要依赖?
谁依赖于谁? 当然是应用程序依赖于IoC容器
为什么需要依赖? 应用程序需要IoC容器来提供对象需要的外部资源;
2) 谁注入谁? 注入了什么?
谁注入谁? 很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
注入了什么? 就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
1.3、IOC和DI的关系
其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。通俗来说就是IoC是设计思想,DI是实现方式。
2、IOC配置的三种方式
2.1、XML配置
将bean的信息配置.xml文件里,通过Spring加载文件为我们创建bean。这种方式出现很多早前的SSM项目中(现在项目基本不用),将第三方类库或者一些配置工具类都以这种方式进行配置,主要原因是由于第三方类不支持Spring注解(现在可以了)。
- 优点: 可以使用于任何场景,结构清晰,通俗易懂
- 缺点: 配置繁琐,不易维护,枯燥无味,扩展性差
示例:
1、配置xx.xml文件
2、声明命名空间和配置bean
<?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="mysqlImpl" class = "com.zhang.dao.UserDaoMysqlImpl"/>
<bean id="oracleImpl" class = "com.zhang.dao.UserDaoOracleImpl"/>
<bean id="UserServiceImpl" class = "com.zhang.service.UserServiceImpl">
<!-- id类似于class实例出来的对象-->
<!--
ref 引用Spring容器中创建好的对象
value: 具体的值
-->
<property name="userDao" ref="mysqlImpl"/>
</bean>
</beans>
获取Bean:
public static void main(String[] args) {
// 通过xml文件,获取ApplicationContext,拿到Spring的容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 通过IOC容器去拿Bean
UserServiceImpl userServiceimpl = (UserServiceImpl)context.getBean("UserServiceImpl");
// UserServiceImpl userServiceimpl = context.getBean("UserServiceImpl",UserServiceImpl.class)
userServiceimpl.getUser();
}
2.2、Java通过配置类配置
将类的创建交给我们配置的JavcConfig类来完成,Spring只负责维护和管理,采用纯Java创建方式。其本质上就是把在XML上的配置声明转移到Java配置类中
-
优点:适用于任何场景,配置方便,因为是纯Java代码,扩展性高,十分灵活
-
缺点:由于是采用Java类的方式,声明不明显,如果大量配置,可读性比较差
示例:
1)创建一个配置类, 添加@Configuration
注解声明为配置类
2)创建方法,方法上加上@bean
,该方法用于创建实例并返回,该实例创建后会交给spring管理,方法名建议与实例名相同(首字母小写)。注:实例类不需要加任何注解(即不需要加@Controller
,@Service
,@Repository
等)
// @Configuration 代表这是一个配置类(也会被Spring容器托管,注册到容器中),和我们之前的bean.xml作用是一样的
@Configuration
public class BeansConfig {
// 注册一个bean,就相当于我们之前写的bean标签
//这个方法的名字,就相当于bean标签中的id属性
//这个方法的返回值,就相当于bean标签中的class属性
@Bean("userDao")
public UserDaoImpl userDao() {
return new UserDaoImpl();
}
@Bean("userService") // 若只有 @Bean,则默认Bean的名字为方法名
public UserServiceImpl userService() {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(userDao());
return userService;
}
}
获取Bean:
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(myConfig.class);
UserServiceImpl userService = ( UserServiceImpl)context.getBean("userService");
// UserServiceImpl userService = context.getBean("userService",UserServiceImpl.class);
System.out.println(userService.findUserList());
}
2.3、注解配置
通过在类上加注解的方式,来声明一个类交给Spring管理,Spring会自动扫描带有@Component
,@Controller
,@Service
,@Repository
这四个注解的类,然后帮我们创建并管理,前提是需要先配置Spring的注解扫描器。
我们一般使用
@Autowired
注解自动装配 bean,要想把类标识成可用于@Autowired
注解自动装配的 bean 的类,采用以下注解可实现:
@Component
:通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。@Controller
: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
-
优点:开发便捷,通俗易懂,方便维护。
-
缺点:具有局限性,对于一些第三方资源,无法添加注解。只能采用XML或JavaConfig的方式配置。
示例:
1)对类添加@Component
相关的注解,比如@Controller
,@Service
,@Repository
2)设置ComponentScan的basePackage,共有三种方式,我们在后面一 一演示下
@Service
public class UserServiceImpl {
/**
* user dao impl.
*/
@Autowired
private UserDaoImpl userDao;
/**
* find user list.
*
* @return user list
*/
public List<User> findUserList() {
return userDao.findUserList();
}
}
要想拿到Bean,我们必须设置ComponentScan的basePackage,共有三种方式
第一种: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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.zhang"/>
</beans>
获取Bean:
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userService = ( UserServiceImpl)context.getBean("userService");
// UserServiceImpl userService = context.getBean("userService",UserServiceImpl.class);
System.out.println(userService.findUserList());
}
第二种:@ComponentScan(“com.zhang”)
在配置类中进行扫描
@Configuration
@ComponentScan("com.zhang")
public class MyConfig {
}
获取Bean
@Test
public void test2(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
UserServiceImpl userService = ( UserServiceImpl)context.getBean("userService");
// UserServiceImpl userService = context.getBean("userService",UserServiceImpl.class);
System.out.println(userService.findUserList());
}
第三种:直接在AnnotationConfigApplicationContext
中扫描所需要的包
直接扫描加获取包
@Test
public void test3(){
ApplicationContext context = new AnnotationConfigApplicationContext("com.zhang");
UserServiceImpl userService = ( UserServiceImpl)context.getBean("userService");
// UserServiceImpl userService = context.getBean("userService",UserServiceImpl.class);
System.out.println(userService.findUserList());
}
总结:通过以上三种扫描的方式,我们可以在IOC容器中创建Bean,并可以在IOC容器中拿到Bean。
若想看自己的IOC容器中有哪些Bean,可以通过以下方式查看
String[] names = context.getBeanDefinitionNames();
for(String name: names){
System.out.println(name);
}
3、依赖注入的三种方式
因为IOC配置的方式有三种,所以三种依赖注入也要基于这三种去分析。
3.1、Setter注入
Setter注入是通过set方法注入的,所以要保证我们的类(包括POJO、Serveice和Dao等,这些都是已经创建好的Bean)中设置set方法。
第一种情况:XML配置
针对不同的属性类别我们有不同的注入方法
<bean id="address" class="com.zhang.pojo.Address">
<property name="address" value="西安"/>
</bean>
<bean id="student" class="com.zhang.pojo.Student">
<!-- 第一种:普通值注入,value-->
<property name="name" value="章卫军"/>
<!-- 第二种:Bean注入,ref-->
<property name="address" ref="address"/>
<!-- 第三种: 数组注入,array-->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
<value>三国演义</value>
</array>
</property>
<!-- 第四种: list注入,list-->
<property name="hobbys">
<list>
<value>听歌梦</value>
<value>敲代码</value>
<value>看电影</value>
</list>
</property>
<!-- 第五种: map注入,map-->
<property name="card">
<map>
<entry key="身份证" value="342923199609135517"/>
<entry key="银行卡" value="44444444444444"/>
</map>
</property>
<!-- 第六种: set注入,set-->
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
<value>BOB</value>
</set>
</property>
<!-- 第七种: null值注入,null-->
<property name="wife">
<null/>
</property>
<!-- 第八种: Properties值注入,props-->
<property name="info">
<props>
<prop key="driver">E20201074</prop>
<prop key="url">男</prop>
<prop key="username">zwj</prop>
<prop key="password">zwj</prop>
</props>
</property>
</bean>
另外我们是可以通过p:
命名空间去依赖注入,这也是一种Setter注入,不过可以一次性注入全
<!-- p命名空间注入,可以直接注入属性的值:property -->
<bean id="user" class="com.zhang.pojo.User" p:name="章卫军" p:age="18"/>
第二和第三种情况:在注解和Java配置方式下
在这两种情况下,我们首先要保证Bean通过这两种方式进行创建了,其次,我们可以在需要注入的Bean的set方法中加@Autowired
注解
public class UserServiceImpl {
private UserDaoImpl userDao;
public List<User> findUserList() {
return this.userDao.findUserList();
}
@Autowired
public void setUserDao(UserDaoImpl userDao) {
this.userDao = userDao;
}
}
在Spring3.x刚推出的时候,推荐使用注入的就是这种, 但是这种方式比较麻烦,所以在Spring4.x版本中推荐构造函数注入
3.2、构造方法注入
和Setter注入通过set方法注入不同,构造方法是通过有参构造函数进行依赖注入的。接下来也要通过三种IOC配置进行分析。
1、XML配置
首先如果不指定有参构造函数,只是单纯的通过XML去创建一个Bean的话,如:<bean id="user" class="com.zhang.pojo.User">
,它会默认使用无参构造函数。
在XML配置下,针对有参构造方法进行依赖注入,也有三种方式,第一种是通过索引,byIndex。第二种是通过类别,byType。第三种是通过名称,byName。不同于Setter注入,构造方法注入可以一次性将依赖全部注入成功
首先我们看实体类User
public class User {
private String name;
public User(String name){
this.name = name;
}
public void show(){
System.out.println("name="+name);
}
}
然后根据三种有参构造函数进行依赖注入来分析
第一种:byIndex
<!-- 第一种,下标赋值! -->
<bean id="user" class="com.zhang.pojo.User">
<constructor-arg index="0" value="zhangweijun"/>
</bean>
第二种:byType(不建议使用)
<!-- 第二种,类型赋值! 不建议使用,当有两个String类型时,就会产生错误-->
<bean id="user" class="com.zhang.pojo.User">
<constructor-arg type="java.lang.String" value="zhangweijun2"/>
</bean>
第三种:byName(最常用)
<!-- 第三种,直接通过参数名来设置赋值! 最常用 -->
<bean id="user" class="com.zhang.pojo.User">
<constructor-arg name="name" value="章卫军"/>
</bean>
同样我们也可以使用有参构造函数运用到Bean的注入中
<?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">
<!-- services -->
<bean id="userService" class="tech.pdai.springframework.service.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
本质上是new UserServiceImpl(userDao)
创建对象, 所以对应的service
类是这样的:
public class UserServiceImpl {
/**
* user dao impl.
*/
private final UserDaoImpl userDao;
/**
* init.
* @param userDaoImpl user dao impl
*/
public UserServiceImpl(UserDaoImpl userDaoImpl) {
this.userDao = userDaoImpl;
}
/**
* find user list.
*
* @return user list
*/
public List<User> findUserList() {
return this.userDao.findUserList();
}
}
同样我们也可以通过c:
命名空间进行依赖注入
<!-- c命名空间注入,通过构造器注入:constructor-arg -->
<bean id="user2" class="com.zhang.pojo.User" c:name="章卫军" c:age="25" scope="prototype"/>
第二种和第三种情况:在注解和Java配置方式下
在这两种情况下,我们首先需要根据不同的配置创建Bean,然后和Setter注入相似,它是通过在set方法上加@Autowired
,构造方法注入是通过在有参构造函数上加@Autowired
@Service
public class UserServiceImpl {
/**
* user dao impl.
*/
private final UserDaoImpl userDao;
/**
* init.
* @param userDaoImpl user dao impl
*/
@Autowired // 这里@Autowired也可以省略
public UserServiceImpl(final UserDaoImpl userDaoImpl) {
this.userDao = userDaoImpl;
}
/**
* find user list.
*
* @return user list
*/
public List<User> findUserList() {
return this.userDao.findUserList();
}
}
在Spring4.x版本中推荐的注入方式就是这种, 具体原因看后续章节