前言
Spring是什么?它是怎么诞生的?有哪些主要的组件和核心功能呢? 本文通过这几个问题帮助你构筑Spring和Spring Framework的整体认知。
Spring简介
Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 Spring MVC 和业务层事务管理等众多的企业级应⽤技术,还能整合开源世界众多著名的第三⽅框架和类库,已经成为使⽤最多的 Java EE 企业应⽤开源框架。
Spring 官⽅⽹址:http://spring.io/
Spring 中文网址:https://springdoc.cn/spring/
我们经常说的 Spring 其实指的是Spring Framework(spring 框架)。
Spring 发展历程
-
1997年 IBM 提出了EJB的思想;1998年,SUN 制定开发标准规范EJB1.0;1999年,EJB 1.1发 布;2001年,EJB 2.0发布;2003年,EJB 2.1发布;2006年,EJB 3.0发布;
-
Rod Johnson(spring之⽗)
-
-
Expert One-to-One J2EE Design and Development(2002) 阐述了J2EE使⽤EJB开发设计的优点及解决⽅案
-
Expert One-to-One J2EE Development without EJB(2004) 阐述了J2EE开发不使⽤EJB的解决⽅式(Spring雏形)
-
-
2017 年 9 ⽉份发布了 Spring 的最新版本 Spring 5.0 通⽤版(GA)
Spring优势
整个 Spring 优势,传达出⼀个信号,Spring 是⼀个综合性,且有很强的思想性框架,每学习⼀ 天,就能体会到它的⼀些优势。
-
方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的 过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更 专注于上层的应⽤。
-
AOP编程的⽀持
通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过 AOP轻松应付。
-
声明式事务的⽀持
@Transactional 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式⽅式灵活的进⾏事务的管理,提⾼ 开发效率和质量。
-
方便程序的测试
可以⽤⾮容器依赖的编程⽅式进⾏⼏乎所有的测试⼯作,测试不再是昂贵的操作,⽽是随⼿可做的 事情。
-
方便集成各种优秀框架
Spring可以降低各种框架的使⽤难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、 Quartz等)的直接⽀持。
-
降低JavaEE API的使⽤难度
Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤ 难度⼤为降低。
-
源码是经典的 Java 学习范例
Spring的源代码设计精妙、结构清晰、匠⼼独⽤,处处体现着⼤师对Java设计模式灵活运⽤以及对 Java技术的⾼深造诣。它的源代码无疑是Java技术的最佳实践的范例。
Spring组件
上图中包含了 Spring 框架的所有模块,这些模块可以满足一切企业级应用开发的需求,在开发过程中可以根据需求有选择性地使用所需要的模块。下面分别对这些模块的作用进行简单介绍(并且结合SpringFramework5.x源码模块帮助你对应好各模块关系)。
Core Container(Spring的核心容器)
容器是Spring框架最核⼼的部分,它管理着Spring应⽤中 bean的创建、配置和管理。在该模块中,包括了Spring bean工厂,它为Spring提供了DI的功能。基于bean⼯⼚,我们还会发现有多种Spring应⽤上下⽂的实现。所有的Spring模块都构建于核心容器之上。
-
Beans 模块:提供了框架的基础部分,包括控制反转和依赖注入。
-
Core 核心模块:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类。
-
Context 上下文模块:建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。
-
SpEL 模块:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。
对应的源码模块如下:
Data Access/Integration(数据访问/集成)
数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。
-
JDBC 模块:提供了一个 JDBC 的样例模板,使用这些模板能消除传统冗长的 JDBC 编码还有必需的事务控制,而且能享受到 Spring 管理事务的好处。
-
ORM 模块:提供与流行的“对象-关系”映射框架无缝集成的 API,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且还可以使用 Spring 事务管理,无需额外控制事务。
-
OXM 模块:提供了一个支持 Object /XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。将 Java 对象映射成 XML 数据,或者将XML 数据映射成 Java 对象。
-
JMS 模块:指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
-
Transactions 事务模块:支持编程和声明式事务管理。
对应的源码模块如下:
Web模块
Spring 的 Web 层包括 Web、Servlet、WebSocket 和 Webflux 组件,具体介绍如下。
-
Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。
-
Servlet 模块:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。
-
WebSocket 模块:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯。
-
Webflux 模块:Spring WebFlux 是 Spring Framework 5.x中引入的新的响应式web框架。与Spring MVC不同,它不需要Servlet API,是完全异步且非阻塞的,并且通过Reactor项目实现了Reactive Streams规范。Spring WebFlux 用于创建基于事件循环执行模型的完全异步且非阻塞的应用程序。
此外Spring4.x中还有Portlet 模块,在Spring 5.x中已经移除
-
Portlet 模块:提供了在 Portlet 环境中使用 MVC 实现,类似 Web-Servlet 模块的功能。
对应的源码模块如下:
AOP、Aspects、Instrumentation和Messaging
在 Core Container 之上是 AOP、Aspects 等模块,具体介绍如下:
-
AOP 模块:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态地把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
-
Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
-
Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
-
messaging 模块:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。
-
jcl 模块:Spring 5.x中新增了日志框架集成的模块。
对应的源码模块如下:
Test模块
Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。
包含Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient。
对应的源码模块如下:
Spring框架如何应用
上文中,我们展示了Spring和Spring Framework的组件, 这里对于开发者来说有几个问题:
-
首先,对于Spring进阶,直接去看IOC和AOP,存在一个断层,所以需要整体上构建对Spring框架认知上进一步深入,这样才能构建知识体系。
-
其次,很多开发者入门都是从Spring Boot开始的,他对Spring整体框架底层,以及发展历史不是很了解;特别是对于一些老旧项目维护和底层bug分析没有全局观。
-
再者,Spring代表的是一种框架设计理念,需要全局上理解Spring Framework组件是如何配合工作的,需要理解它设计的初衷和未来趋势。
如下是官方在解释Spring框架的常用场景的图
我加上一些注释后,是比较好理解的;引入这个图,重要的原因是为后面设计一个案例帮助你构建认知。
设计一个Spring的Hello World
结合上面的使用场景,设计一个查询用户的案例的两个需求,来看Spring框架帮我们简化了什么开发工作:
-
查询用户数据 - 来看DAO+POJO-> Service 的初始化和装载。
-
给所有Service的查询方法记录日志
创建一个Maven的Java项目
引入Spring框架的POM依赖
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring.version>5.3.9</spring.version>
<aspectjweaver.version>1.9.6</aspectjweaver.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.25</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
User对象
/**
* @description
* @author
* @date 2023/8/31 0031 9:49
* @description
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
/**
* user's name.
*/
private String name;
/**
* user's age.
*/
private int age;
}
DAO 获取 POJO, UserDaoServiceImpl (mock 数据)
/**
* @description
* @author
* @date 2023/8/31 0031 9:52
* @description
*/
public class UserDaoImpl {
public List<User> list(){
return Arrays.asList(new User("",18));
}
}
增加daos.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.bcst.dao.xml.UserDaoImpl">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
业务层 UserService(调用DAO层)
/**
* @description
* @author
* @date 2023/8/31 0031 9:52
* @description
*/
public class UserService {
private UserDaoImpl userDao;
public void setUserDao(UserDaoImpl userDao) {
this.userDao = userDao;
}
public List<User> list(){
return userDao.list();
}
}
增加services.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="com.bcst.service.xml.UserService">
<property name="userDao" ref="userDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
拦截所有service中的方法,并输出记录
/**
* @description
* @author
* @date 2023/8/31 0031 10:32
* @description
*/
@Aspect
public class LogAspect {
@Around("execution(* com.bcst.service.*.*.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
System.out.println("execute method:"+method.getName());
return joinPoint.proceed();
}
}
增加aspects.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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.bcst" />
<aop:aspectj-autoproxy/>
<bean id="logAspect" class="com.bcst.aspect.LogAspect">
<!-- configure properties of aspect here as normal -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
测试
/**
* @description
* @author
* @date 2023/8/31 0031 9:58
* @description xml方式测试
*/
public class UserServiceTest {
@Test
public void test1(){
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring/daos.xml", "spring/services.xml");
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/daos.xml", "classpath:spring/services.xml","classpath:spring/aspects.xml");
UserService userService = applicationContext.getBean(UserService.class);
List<User> userList = userService.list();
System.out.println(JSON.toJSON(userList));
}
}
Spring核心思想
上面的例子,体现了Spring IOC和AOP两个核心思想。
IOC和AOP不是spring提出的,在spring之前就已经存在,只不过更偏向于理论化,spring在技术层次把这两个思想做了⾮常好的实现(Java)
控制反转-IOC
什么是IoC?
IoC Inversion of Control (控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现。
描述的事情:Java开发领域对象的创建,管理的问题 。
传统开发方式:⽐如类A依赖于类B,往往会在类A中new⼀个B的对象 。
IoC思想下开发方式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对 象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可 。
我们丧失了⼀个权利(创建、管理对象的权利),得到了⼀个福利(不⽤考虑对象的创建、管理等⼀系列 事情)。
为什么叫做控制反转?
控制:指的是对象创建(实例化、管理)的权利
反转:控制权交给外部环境了(spring框架、IoC容器)
IoC和DI的区别
DI:Dependancy Injection(依赖注⼊)
怎么理解:IOC和DI描述的是同⼀件事情,只不过⻆度不⼀样罢了
案例分析
来看第一个需求:查询用户(service通过调用dao查询pojo),本质上如何创建User/Dao/Service等;
-
如果没有Spring框架,我们需要自己创建User/Dao/Service等,比如:
UserDaoImpl userDao = new UserDaoImpl();
UserService userService = new UserService();
userService.setUserDao(userDao);
List<User> userList = userService.list();
-
有了Spring框架,可以将原有Bean的创建工作转给框架, 需要用时从Bean的容器中获取即可,这样便简化了开发工作
Bean的创建和使用分离了:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/daos.xml", "classpath:spring/services.xml","classpath:spring/aspects.xml");
UserService userService = applicationContext.getBean(UserService.class);
List<User> userList = userService.list();
System.out.println(JSON.toJSON(userList));
更进一步,你便能理解为何会有如下的知识点了:
-
Spring框架管理这些Bean的创建工作,即由用户管理Bean转变为框架管理Bean,这个就叫控制反转 - Inversion of Control (IoC)
-
Spring 框架托管创建的Bean放在哪里呢?这便是IoC Container;
-
Spring 框架为了更好让用户配置Bean,必然会引入不同方式来配置Bean?这便是xml配置,Java配置,注解配置等支持
-
Spring 框架既然接管了Bean的生成,必然需要管理整个Bean的生命周期等;
-
应用程序代码从Ioc Container中获取依赖的Bean,注入到应用程序中,这个过程叫 依赖注入(Dependency Injection,DI) ;所以说控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是IoC是设计思想,DI是实现方式
-
在依赖注入时,有哪些方式呢?这就是构造器方式,@Autowired, @Resource, @Qualifier... 同时Bean之间存在依赖(可能存在先后顺序问题,以及循环依赖问题等)
这边引入我们后续的相关文章:Spring基础 - Spring之控制反转(IOC)
面向切面 - AOP
什么是AOP
AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程。
AOP是OOP的延续,从OOP说起
OOP三⼤特征:封装、继承和多态 。
OOP是⼀种垂直继承体系 。
OOP编程思想可以解决⼤多数的代码重复问题,但是有⼀些情况是处理不了的,比如下⾯的在顶级⽗类 Animal中的多个⽅法中相同位置出现了重复代码,OOP就解决不了。
横切逻辑代码 :
横切逻辑代码存在什么问题:
-
横切代码重复问题
-
横切逻辑代码和业务代码混杂在⼀起,代码臃肿,维护不⽅便
AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
代码拆分容易,那么如何在不改变原有业务逻辑的情况下,悄⽆声息的把横切逻辑代码应⽤到原有的业 务逻辑中,达到和原来⼀样的效果,这个是⽐较难的 。
AOP在解决什么问题
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复 。
为什么叫做面向切面编程
「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑 。
「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个 ⾯的概念在⾥⾯ 。
案例分析
来看第二个需求:给Service所有方法调用添加日志(调用方法时),本质上是解耦问题;
-
如果没有Spring框架,我们需要在每个service的方法中都添加记录日志的方法,比如:
public List<User> list(){
System.out.println("execute method list");
return userDao.list();
}
-
有了Spring框架,通过@Aspect注解 定义了切面,这个切面中定义了拦截所有service中的方法,并记录日志;可以明显看到,框架将日志记录和业务需求的代码解耦了,不再是侵入式的了
@Around("execution(* com.bcst.service.*.*.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
System.out.println("execute method:"+method.getName());
return joinPoint.proceed();
}
更进一步,你便能理解为何会有如下的知识点了:
-
Spring 框架通过定义切面, 通过拦截切点实现了不同业务模块的解耦,这个就叫面向切面编程 - Aspect Oriented Programming (AOP)
-
为什么@Aspect注解使用的是aspectj的jar包呢?这就引出了Aspect4J和Spring AOP的历史渊源,只有理解了Aspect4J和Spring的渊源才能理解有些注解上的兼容设计
如何支持更多拦截方式来实现解耦, 以满足更多场景需求呢?这就是@Around, @Pointcut... 等的设计
那么Spring框架又是如何实现AOP的呢?这就引入代理技术,分静态代理和动态代理,动态代理又包含JDK代理和CGLIB代理等
这边引入我们后续的相关文章:Spring基础 - Spring之面向切面编程(AOP)
Spring框架设计如何逐步简化开发的
通过上述的框架介绍和例子,已经初步知道了Spring设计的两个大的要点:IOC和AOP;从框架的设计角度而言,更为重要的是简化开发,比如提供更为便捷的配置Bean的方式,直至0配置(即约定大于配置)。这里我将通过Spring历史版本的发展,来帮你理解Spring框架是如何逐步简化开发的。
Java配置方式改造
在上文的例子中, 通过xml配置方式实现的,这种方式实际上比较麻烦;我通过Java配置进行改造:
User,UserDaoImpl, UserService,LogAspect不用改,将原通过.xml配置转换为Java配置
/**
* @description
* @author
* @date 2023/8/31 0031 11:06
* @description
*/
@Configuration
@EnableAspectJAutoProxy
public class BeanConfig {
@Bean(value = "userDao")
public UserDaoImpl userDao(){
return new UserDaoImpl();
}
@Bean(value = "userService")
public UserService userService(){
UserService userService = new UserService();
userService.setUserDao(userDao());
return userService;
}
@Bean(value = "logAspect")
public LogAspect logAspect(){
return new LogAspect();
}
}
测试
/**
* @description
* @author
* @date 2023/8/31 0031 11:10
* @description java配置方式改造
*/
public class UserServiceTest {
@Test
public void test1(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
List<User> userList = userService.list();
System.out.println(JSON.toJSON(userList));
}
}
注解配置方式改造
更进一步,Java 5开始提供注解支持,Spring 2.5 开始完全支持基于注解的配置并且也支持JSR250 注解。在Spring后续的版本发展倾向于通过注解和Java配置结合使用
BeanConfig 不再需要Java配置
/**
* @description
* @author
* @date 2023/8/31 0031 11:06
* @description
*/
@Configuration
@EnableAspectJAutoProxy
public class BeanConfig {
}
UserDaoImpl 增加了 @Repository注解
/**
* @description
* @author
* @date 2023/8/31 0031 9:52
* @description 注解配置改造
*/
@Repository
public class UserDaoImpl {
public List<User> list(){
return Arrays.asList(new User("",18));
}
}
UserService 增加了@Service 注解,并通过@Autowired注入userDao
/**
* @description
* @author
* @date 2023/8/31 0031 9:52
* @description 注解配置改造
*/
@Service
public class UserService {
@Autowired
private UserDaoImpl userDao;
public List<User> list(){
return userDao.list();
}
}
LogAspect增加@Component注解
/**
* @description * @author
* @date 2023/8/31 0031 10:32
* @description 注解改造
*/
@Aspect
@Component
public class LogAspect {
@Around("execution(* com.bcst.service.*.*.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
System.out.println("注解方式 execute method:"+method.getName());
return joinPoint.proceed();
}
}
测试
/**
* @description
* @author
* @date 2023/8/31 0031 11:10
* @description 注解配置方式改造
*/
public class UserServiceTest {
@Test
public void test1(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.bcst");
UserService userService = applicationContext.getBean(UserService.class);
List<User> userList = userService.list();
System.out.println(JSON.toJSON(userList));
}
}