一、Spring简介
1、Spring介绍
Spring 是一款主流的 Java EE 轻量级开源框架 ,Spring 由“Spring 之父”Rod Johnson 提出并创立,其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring 框架除了自己提供功能外,还提供整合其他技术和框架的能力。
Spring 自诞生以来备受青睐,一直被广大开发人员作为 Java 企业级应用程序开发的首选。时至今日,Spring 俨然成为了 Java EE 代名词,成为了构建 Java EE 应用的事实标准。
官网:https://spring.io/
2、使用Spring的原因
官方解释:https://spring.io/why-spring
Spring使Java编程对每个人来说更快、更容易、更安全。 Spring对速度、简单性和生产率的关注使它成为世界上最流行的Java框架。 spring给整个行业带来等了春天,为我们软件的开发带来了极大的便利。
参考:snyk发布的2021年JVM 生态系统报告(2022年的还没有发布)
Java 世界仍然是 Spring 主导的世界,超过一半的市场使用 Spring Boot,几乎三分之一使用 Spring MVC。总的来说,我们看到我们生活在一个高度由 Spring 主导的宇宙中,这似乎表明 Spring 的人们在为社区服务方面做得很好。
- Spring is everywhere
Spring 框架提供灵活的类库受到世界各地开发人员的信任。无论是流媒体电视、在线购物、还是无数其他创新的解决方案,Spring每天都为数百万终端用户提供愉快的体验。Spring也有来自所有科技巨头的贡献,包括阿里巴巴、亚马逊、谷歌、微软等。
- Spring is flexible
Spring 灵活而全面的扩展能力和第三方库让开发人员可以构建几乎任何可以想象到的应用程序。 Spring框架的控制反转(IoC)和依赖注入(DI)特性为一系列广泛的特性和功能提供了基础。 无论您是在为web构建安全的、响应式的、基于云的微服务,还是为企业构建复杂的流数据流,Spring都有工具可以提供帮助。
- Spring is productive
Spring Boot 改变了您处理Java编程任务的方式,从根本上简化了您的体验。 Spring Boot结合了应用程序上下文和自动配置的嵌入式web服务器等必要条件,使microservice开发变得轻而易举。 为了更快,您可以将Spring Boot与Spring Cloud丰富的支持库、服务器、模式和模板组合在一起,以创纪录的时间将整个基于微服务的架构安全地部署到云中。
- Spring is fast
我们的工程师非常关心性能。 在Spring中,默认情况下,您会注意到快速启动、快速关闭和优化执行。 Spring项目也越来越多地支持reactive(nonblocking)编程模型,以获得更高的效率。 开发人员的生产力是Spring的超级力量。 Spring Boot帮助开发人员轻松地构建应用程序,而且比其他竞争范式要轻松得多。
- Spring is secure
Spring在处理安全问题方面十分可靠。 Spring代码的贡献者与安全专业人员一起修补和测试任何报告的漏洞。 第三方依赖关系也被密切监控,并定期发布更新,以帮助您的数据和应用程序尽可能安全。 此外,Spring Security使您更容易集成行业标准的安全方案,并交付可靠的默认安全解决方案。
- Spring is supportive
Spring社区是一个庞大的、全球性的、多样化的社区,涵盖了所有年龄和能力的人,从完全的初学者到经验丰富的专业人士。 无论你处在人生的哪个阶段,你都能找到帮助你进入下一个阶段的支持和资源。
3、Spring提供的功能
总结:Spring发展到现在已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、SpringBoot、Spring Cloud、Spring Data、Spring Security 等。
Spring提供的所有项目组成:https://spring.io/projects
这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。
Spring Framework 是其它子项目的基础,可以理解成 Spring 的项目也只是在 Spring Framework 的基础上对其进行扩展实现的。故我们也称 Spring Framework 为 Spring 框架。框架本身的好处是重用性,其它项目不用重复开发Spring Framework已经实现的功能,对其添砖加瓦,形成自己特色的项目就好。
而且我们所说的Spring MVC其实也属于Spring Framework核心项目之一;只是因为使用频率最高且强调web功能,所以我们会拆出来成为Spring MVC 框架。
4、Spring Framework组成
官网介绍:https://spring.io/projects/spring-framework#overview
上图中包含了 Spring 框架的所有模块,这些模块可以满足一切企业级应用开发的需求,在开发过程中可以根据需求有选择性地使用所需要的模块。下面分别对这些模块的作用进行简单介绍。
- Spring Core(核心容器)
spring core提供了IOC,DI,Bean配置装载创建的核心实现。核心概念: Beans、BeanFactory、、ApplicationContext。
- spring-core :IOC和DI的基本实现
- spring-beans:BeanFactory和Bean的装配管理(BeanFactory)
- spring-context:Spring context上下文,即IOC容器(AppliactionContext)
- spring-expression:spring表达式语言
- Spring AOP
- spring-aop:面向切面编程的应用模块,整合ASM,CGLib,JDK Proxy
- spring-aspects:集成AspectJ,AOP应用框架
- spring-instrument:动态Class Loading模块
- Spring Data Access
- spring-jdbc:spring对JDBC的封装,用于简化jdbc操作
- spring-orm:java对象与数据库数据的映射框架
- spring-oxm:对象与xml文件的映射框架
- spring-jms: Spring对Java Message Service(java消息服务)的封装,用于服务之间相互通信
- spring-tx:spring jdbc事务管理
- Spring Web
- spring-web:最基础的web支持,建立于spring-context之上,通过servlet或listener来初始化IOC容器
- spring-webmvc:实现web mvc
- spring-websocket:与前端的全双工通信协议
- spring-webflux:Spring 5.0提供的,用于取代传统java servlet,非阻塞式Reactive Web框架,异步,非阻塞,事件驱动的服务,Netty,Undertow和Servlet 3.1+容器等服务器上运行
- Spring Message
- Spring-messaging:spring 4.0提供的,为Spring集成一些基础的报文传送服务
- Spring test
- spring-test:集成测试支持,主要是对junit的封装
5、Spring Framework特点
- 非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。
- 控制反转:IoC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。
- 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。
- 容器:Spring IoC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。
- 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。
- 一站式:在 IoC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且 Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。
Spring框架版本:Spring6要求JDK最低版本是JDK17
二、IoC概述
1、背景介绍
介绍IOC之前我们先对其背景进行介绍,也即对象的创建史!
1) 普通new实例
在实际项目设计过程中,接口是一个重要组成元素,不同层之间的操作需要通过接口来调用。利用接口,可以实现子类的隐藏,也可以更好地描述不同层之间的操作标准。Java中要想获得接口对象,需要通过关键字new来实现。下面的案例不要研究其业务意义,只是一个测试demo!
public interface ConnectionMessage {
public String select();
}
public class MySQLMessageImpl implements ConnectionMessage {
@Override
public String select() {
System.out.println("连接---查询MySQL数据库信息");
return "";
}
}
public class MyTest {
public static void main(String[] args) {
ConnectionMessage message = new MySQLMessageImpl();
message.select();
}
}
以上是使用MySQL操作的模拟程序,如果切换为PostgreSQL就需要修改其使用类!
public class MyTest {
public static void main(String[] args) {
//ConnectionMessage message = new MySQLMessageImpl();
ConnectionMessage message = new PostgreSQLMessageImpl();
message.select();
}
}
这就是所谓的耦合,调用的时候要修改代码,即直接new , 显然不符合面向对象的设计原则---------开闭原则
2.1)普通工厂
针对上面的情况我们可以使用工厂方式,创建一个BeanFactory.java文件
public class BeanFactory {
public static ConnectionMessage select(){
return new MySQLMessageImpl();
}
}
这样使用的时候就变成下面这样
public class MyTest {
public static void main(String[] args) {
// ConnectionMessage message = new MySQLMessageImpl();
// ConnectionMessage message = new PostgreSQLMessageImpl();
ConnectionMessage message = BeanFactory.select();
message.select();
}
}
上图的思想没问题,但是我们的代码还尚有问题,即BeanFactory里面还是有代码耦合
2.2)反射工厂1.0
我们接下来可以利用反射完善一下!
public class BeanFactory {
private static ConnectionMessage message;
public static ConnectionMessage select() throws Exception {
Class<?> clazz = Class.forName("com.ty.demo.dao.impl.MySQLMessageImpl");
message = (MySQLMessageImpl) clazz.getDeclaredConstructor().newInstance();
return message;
}
}
这样比直接new好多了,减少了工厂内获取对象的耦合,但是获取类限定名的时候还是需要进行修改!
2.3)反射工厂2.0
下面我们可以用properties属性配置文件存储文件限定名,把具体的类限定名和代码相分离,未来只需要修改此配置文件即可!
public class BeanFactory {
private static ConnectionMessage message;
//创建Properties对象来加载properties属性配置文件
private static Properties environment = new Properties();
private static InputStream resource;
static {
//加载bean.properties配置文件
resource = BeanFactory.class.getResourceAsStream("/bean.properties");
try {
environment.load(resource);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static ConnectionMessage select() throws Exception {
Class<?> clazz = Class.forName(environment.getProperty("mysqlMessage"));
message = (MySQLMessageImpl) clazz.getDeclaredConstructor().newInstance();
return message;
}
}
2.3)通用工厂
上面我们的工厂方法基本设计完成了,但是还有点小问题,那就是每次都需要在BeanFactory工厂类写一个与其对应的对象创建工厂方法,这时我们设计一个通用的工厂方法
public class BeanFactory {
//创建Properties对象来加载properties属性配置文件
private static Properties environment = new Properties();
private static InputStream resource;
static {
//加载bean.properties配置文件
resource = BeanFactory.class.getResourceAsStream("/bean.properties");
try {
environment.load(resource);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* @param key 配置文件对应的mysqlMessage和postgresqlMessage
* @return
*/
public static Object getBean(String key) {
Object returnedObject = null;
try {
Class<?> clazz = Class.forName(environment.getProperty(key));
returnedObject = clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return returnedObject;
}
}
一个通用工厂就完成了,但是这种方式你还需要先自己提供工厂类和方法,我们如果用Spring的话,工厂类不用我们手动实现,Spring提供了工厂,使我们可以将更多精力放在核心代码的开发处理上。
2、IoC---控制反转
简介:控制反转(Inversion of Control,缩写为IoC),它不是一门技术,而是面向对象编程中的一种设计思想,可以用来降低代码之间的耦合度,能够指导我们如何设计出松耦合、更优良的程序。
核心: 将对象的创建权交出去,将对象和对象之间关系的管理权交出去,由第三方容器来负责创建与维护 。
目标:降低程序耦合度,提高程序扩展力
控制反转,反转的是什么?
- 将对象的创建权利交出去,即不在程序中采用硬编码的方式来new对象,而是交给第三方容器负责
- 将对象和对象之间关系的维护权交出去,交给第三方容器负责
实现方式:依赖注入(Dependency Injection)与依赖查找(Dependency Lookup)
- 依赖注入:Spring创建对象的过程中,将对象依赖属性通过配置进行注入,常见的实现方式:
- set注入
- 构造注入
总结:
- IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现
- Bean管理:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)
相关名词
容器:可以管理对象的生命周期、对象与对象之间的依赖关系
实体类:
- POJO(Plain Old Java Object):是Martin Fowler、Rebecca Parsons和Josh MacKenzie在2000年的一次演讲的时候提出来的。按照Martin Fowler的解释是“Plain Old Java Object”,从字面上翻译为“纯洁老式的Java对象”,但大家都使用“简单java对象”来称呼它。POJO的内在含义是指:那些没有继承任何类、也没有实现任何接口,更没有被其它框架侵入的java对象。不允许有业务方法,也不能携带connection之类的方法,实际就是普通JavaBeans。
- JavaBean:是一种JAVA语言写成的可重用组件。JavaBean符合一定规范编写的Java类,不是一种技术,而是一种规范。大家针对这种规范,总结了很多开发技巧、工具函数。符合这种规范的类,可以被其它的程序员或者框架使用。它的方法命名,构造及行为必须符合特定的约定:
1、所有属性为private。
2、这个类必须有一个公共的缺省构造函数。即是提供无参数的构造器。
3、这个类的属性使用getter和setter来访问,其他方法遵从标准命名规范。
4、这个类应是可序列化的。实现serializable接口。
因为这些要求主要是靠约定而不是靠实现接口,所以许多开发者把JavaBean看作遵从特定命名约定的POJO。
POJO与JavaBean的区别:
POJO | JavaBean | |
---|---|---|
权限控制 | 没有对成员提供太多控制 | 提供对成员的完全控制 |
可序列化 | 可以实现 | 应该实现 |
字段访问 | 具有任何可见性,可以直接访问 | 字段只有私人可见性,只能由getter和setter访问 |
无参构造 | 可以没有 | 必须具有 |
使用场景 | 当您不想限制成员并让用户完全访问您的实体时使用它 | 当您要向用户提供您的实体,但仅向实体的一部分提供服务时,将使用它 |
POJO类和Bean均用于定义Java对象,以提高其可读性和可重用性。POJO没有其他限制,而bean是具有某些限制的特殊POJO。
- SpringBean:受Spring管理的对象,所有能受Spring容器管理的对象都可以成为SpringBean。Spring中的bean,是通过配置文件、javaconfig等的设置,由Spring自动实例化,用完后自动销毁的对象。
SpringBean和JavaBean的区别:
JavaBean | SpringBean | |
---|---|---|
用处不同 | 更多地作为值传递参数 | 用处几乎无处不在,任何组件都可以被称为bean |
写法不同 | 作为值对象,要求每个属性都提供getter和setter方法 | 只需为接受设值注入的属性提供setter方法 |
生命周期不同 | 传统javabean作为值对象传递,不接受任何容器管理其生命周期 | spring管理其生命周期行为 |
Entity Bean:是域模型对象,用于实现O/R映射,负责将数据库中的表记录映射为内存中的Entity对象,事实上,创建一个Entity Bean对象相当于新建一条记录,删除一个 Entity Bean会同时从数据库中删除对应记录,修改一个Entity Bean时,容器会自动将Entity Bean的状态和数据库同步。
三、入门程序
1、引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.4</version>
</dependency>
当引入Spring-webmvc依赖之后,表示将Spring的基础依赖也一起引入了
有的教程可能引得是spring-context依赖,此依赖是除了web部分其余的基础依赖都会引进来,它们一般是在Spring MVC的时候再引入spring-webmvc依赖。
2、创建java类
public class HelloWorld {
public void sayHello(){
System.out.println("hello world");
}
}
3、创建配置文件
在resources目录创建一个 Spring 配置文件 bean.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">
<!--
配置HelloWorld所对应的bean,表示将HelloWorld的对象交给Spring的IOC容器管理
通过bean标签配置IOC容器所管理的bean
属性:
id:设置bean的唯一标识
class:设置bean所对应类型的全限定名
-->
<bean id="hello" class="com.ty.demo.bean.HelloWorld"></bean>
</beans>
4、测试程序
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
context.getBean("hello", HelloWorld.class).sayHello();
}
}
5、简单分析
这里我们这是简单介绍一下基本原理,Spring整个Bean的创建过程很复杂,之后再一点点介绍!
- 底层利用通过反射机制调用无参数构造方法
- Spring创建对象就像之前我们使用Properties的步骤类似,它使用的是dom4j解析beans.xml文件,从中获取class属性值,类的全类名;然后通过反射机制调用无参数构造方法创建对象
Class.forName("com.ty.demo.bean.HelloWorld").getDeclaredConstructor().newInstance()
- 创建好的对象存储在ConcurrentHashMap中
bean对象最终存储在spring容器中,在spring源码底层就是一个map集合,存储bean的map在DefaultListableBeanFactory类中:
Spring容器加载到Bean类时 , 会把这个类的描述信息, 以包名加类名的方式存到beanDefinitionMap 中,
Map<String,BeanDefinition>
- 其中 String是Key , 默认是类名首字母小写 ,
- BeanDefinition , 存的是类的定义(描述信息) ,
- 我们通常叫BeanDefinition接口为 : bean的定义对象。
6、SpringTest测试
前面通过ApplicationContext启动了Spring容器,并实现了配置文件的加载,但这样处理并不能体现出Spring的运行特征。为了更好地还原现实的开发场景,可利用SpringTest依赖库和JUnit实现测试环境下的Spring容器启动,且可以使用@Autowired代替getBean方法实现自动注入。
引入依赖
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.4</version>
</dependency>
测试程序
@ContextConfiguration(locations = {"classpath:bean.xml"})
@ExtendWith(SpringExtension.class)
public class MyTest {
@Autowired
private HelloWorld helloWorld;
@Test
void testHelloWorld() {
helloWorld.sayHello();
}
}
- @ContextConfiguration:表示Spring配置文件所在的目录。本程序通过classpath进行加载,由于src/main/resources属于源目录,所以目录中保存的所有资源将自动设置在CLASSPATH之中
- @ExtendWith(SpringExtension.class):表示要使用的测试工具类型。
- @Autowired:自动注入
7、整合logback日志
在项目开发中,日志十分的重要,不管是记录运行情况还是定位线上问题,都离不开对日志的分析。日志记录了系统行为的时间、地点、状态等相关信息,能够帮助我们了解并监控系统状态,在发生错误或者接近某种危险状态时能够及时提醒我们处理,同时在系统产生问题时,能够帮助我们快速的定位、诊断并解决问题。
引入依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
<scope>test</scope>
</dependency>
测试程序