Spring 现在毫无疑问是 Java 中最受欢迎的框架。Spring 已经成为事实上的 Java 企业级开发的标准。Spring 为何有这么大的魅力,经过这篇文章来简单聊聊。java
本文基于 Spring5程序员
在 Java 在早期用来作企业级开发很困难,各种开发框架及其繁琐复杂,很差管理。而后 Spring 拯救了 Java,由于 Spring 能够简化 Java 开发。传统 Java 开发的困难之一在于代码的耦合度很高,各个组件的侵入性很强。在这里耦合度高和侵入性表达的是一样的问题。看下这个简单的例子,士兵和武器的关系:spring
public class Soldier {
private Weapon weapon;
public void Soldier() {
weapon = new Knife();
}
}
public class Knife implements Weapon {
}
public interface Weapon {
}
复制代码
上面的代码坏在哪里?没错,就是代码的耦合度过高了,也能够说 Weapon 的侵入性太强。这样一来 Soldier 只能持有 Knife,想换一把武器就得修改一次代码,这对于运行的系统来讲确定是不能被接收了。实际要解决这个问题其实也很简单,只要作一点小小的改动:编程
public class Soldier {
private Weapon weapon;
public void Soldier(Weapon weapon) {
this.weapon = weapon;
}
}
public class Gun implements Weapon{
}
Weapon gun = new Gun();
Soldier s = new Soldier(gun);
复制代码
作了一点小小的改动以后,好多了,这下 Soldier 想用什么武器就用什么武器,而不用换武器都须要修改 Solider 的代码。这个其实就是 Spring 的核心特性之一:依赖注入。但这样仍是挺麻烦,每次都须要手动的去 new 一个对象,而后做为一个参数传进去,这个问题天然也有解决的办法,咱们后面再讲。再来讲一下引发代码复杂度的另外一个缘由。安全
有时候除了写业务代码以外,咱们还须要作不少的其余的工做,好比安全检查,打日志等等,若是按照正常咱们经常使用的写代码的方式,以下:微信
public class Soldier {
private Weapon weapon;
public void Soldier(Weapon weapon) {
this.weapon = weapon;
}
public void fight() {
FightLog.fightLogBefore();
weapon.attack();
FightLog.fightLogAfter();
}
}
public class FightLog {
public static void fightLogBefore() {
System.out.println("Fight begin");
}
public static void fightLogAfter() {
System.out.println("Fight after");
}
}
复制代码
若是咱们要记录 Soldier 打斗的全过程,就不得不在打斗开始和结束的地方进行记录。这些记录打斗日志的代码会出如今系统的各个地方,并且和实际的业务逻辑无关,因此这样就会使代码变得很混乱,并且也有不少重复的代码。Spring 为这个问题也准备了一个解决方法:面向切面编程(AOP)。架构
这两个问题也是平常开发中最容易遇到的问题,Spring 的核心目标就是为了解决这两个问题,以此来简化 Java 的开发,并且 Spring 也作到了。框架
很难精确的使用语言去描述什么是 Spring 容器。Spring 容器就像是一台机器,你须要往里放两种原料,而后这台机器就能把你想要的系统给你生产出来。两种原料一种是业务类(POJOs)和配置数据。就像下面这个图所示:ide
POJO: Plain old Java Object,表明的就是普通的 Java 类 配置数据: 配置各个 POJO 之间的依赖关系函数
Spring 容器说究竟是装东西的,里面装的是 Spring Bean,也就是 POJO,这些 Bean 的生命周期都由 Spring 来管理。
在 Spring 中,容器的实现不止一种。每种不一样的容器都实现了 BeanFactory
。这么多的容器实现是为了应对不一样的状况,好比 Web 开发就须要一些额外的特性。还有为了方便不一样的配置文件,也实现了不一样的容器,这些容器最核心的目标都是用来管理 Bean。容器的继承关系很复杂,就不完整列出了。下面是其中几个比较关键的容器实现:
上面的每个容器均可以直接使用,可是不多会直接使用 BeanFactory,通常都会使用 ApplicationContext 及其子类。ApplicationContext 及其子类提供了更多的服务,好比从配置文件中加载信息等等,能够构建不一样类型的 Spring 应用。
以 ClassPathXmlApplicationContext 容器配置为例:
<?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" 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">
<bean id="gun" class="cn.rayjun.springdemo.springcontainer.Gun">
</bean>
<bean id="solider" class="cn.rayjun.springdemo.springcontainer.Soldier">
<constructor-arg name="weapon" ref="gun"/>
</bean>
</beans>
复制代码
上面咱们说到了 Spring 容器须要两种原料,上面这个 xml 配置文件就是配置数据,Soldier 以及 Weapon 和其子类就是 POJOs。在这个文件中,咱们将各个 POJO 之间的依赖关系配置好。而后 Spring 容器就会根据这些配置生成一个可用的系统。配置数据除了使用 xml 以外,还可使用注解和 Java 代码来进行配置,这部分后续再详聊。
这些容器也都提供了获取 Bean 的 API,以下:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Soldier s = context.getBean("soldier");
复制代码
可是永远不要用上面那种方式来获取 Bean,容器原本就是用来解耦的,若是用上面那种方式,根本就没有起到解耦的目的。具体使用方式在后续文章中详解。
Spring Bean 是容器管理的基本单位。Spring Bean 与普通的 Java 对象没有什么区别。
每个 Java 对象都有其生命周期,从对象被建立出来,到最后被回收,Spring Bean 天然也不例外,只是 Spring Bean 的生命周期要比普通的类稍微复制一点,但这些复杂的过程都是由容器来进行管理的,不须要开发者作额外的事情。并且开发者能够经过配置来决定一个 Bean 的生命周期。
Spring 有两大特性:依赖注入(DI) 和 面向切面编程(AOP)。
上面其实已经聊到了,Spring 的这两大特性其实就是用来解决 Java 编程的两大痛点。
依赖注入的目的是代码解耦。固然彻底的解耦是不可能的,类之间没有交互,程序就没法跑起来了。
依赖注入的实现方式很简单,上文中将 Weapon 的实例经过构造函数传入就是依赖注入的一种实现。另外一种注入的方式就是经过 Setter 方法来进行注入。依赖注入自己除了名字比较高端,其实比较简单,可是有一点须要注意,依赖注入常常会和面向接口编程一块儿出现,也就是说依赖注入须要使用多态的特性来使得解耦更加完全。假如不使用接口编程,上面的代码会编程什么样呢:
// 用刀的 Soldier
public class Soldier {
private Knife knife;
public void Soldier(Knife knife) {
this.knife = knife;
}
public void fight() {
knife.attack();
}
}
复制代码
假如说我要把刀换成枪,只能经过该代码来完成。
// 用枪的 Soldier
public class Soldier {
private Gun gun;
public void Soldier(Gun gun) {
this.gun = gun;
}
public void fight() {
gun.attack();
}
}
复制代码
也就是说,若是不使用多态,依赖注入的威力顿时没了 90%。
在 Spring 中,注入这个操做是 Spring 容器来完成的,须要开发者将各个 Bean 之间的依赖关系在配置中定义好,代码中基本不会出现 new 这个关键字,可是整个程序却跑起来了。
依赖注入常常会和控制反转一块儿出现,这两个概念之间有什么关系吗?实际上能够说依赖注入是控制反转的一种实现。在没有 Spring 以前,若是须要对象,咱们都须要手动 new 一个,而如今,全部的对象都不要本身来 new 了,这些事情都交给 Spring 容器来作了,也就是把生成对象的权利交给了 Spring 容器,因此 Spring 容器也称之为 IOC 容器。
Spring 的另外一大特性叫面向切面编程(AOP)。DI 的目标是使得代码之间的耦合度更低 (低耦合),那么 AOP 的目标就是使代码的职责更加单一,提升代码的可复用性 (高内聚)。
AOP 中引入了一个新的概念:切面 和 切点。咱们能够把每个 Bean 都配置为一个切面,Bean 中的每个方法均可以配置为一个切点。
FightLog 类改造以下,那么在 Soldier 在 fight 方法执行以后,战斗的过程也会被记录下来。这就是 AOP 的威力,不须要修改代码,就能够增长切面的方式来使代码变得整洁。
@Aspect
public class FightLog {
@Before("execution(* cn.rayjun.springdemo.springcontainer.Soldier.fight(..))")
public static void fightLogBefore() {
System.out.println("Fight begin");
}
@After("execution(* cn.rayjun.springdemo.springcontainer.Soldier.fight(..))")
public static void fightLogAfter() {
System.out.println("Fight after");
}
}
public class Soldier {
private Weapon weapon;
public void Soldier(Weapon weapon) {
this.weapon = weapon;
}
public void fight() {
weapon.attack();
}
}
复制代码
若是不使用 AOP,这些与业务无关的代码会处处都是,并且会破坏代码职责单一性。常见的场景就是日志,我须要给某些点打日志,可是又不想代码中处处都是日志代码,那么使用 AOP 就是一个比较好的选择了,具体的原理后续的文章中再详细说。
Spring的这两大特性将面向对象的思想实践的淋漓尽致。学会了 Spring,也就真懂面向对象编程了。
Spring 已经演化到了第 5 个大版本了,因此 Spring 远远不仅有容器了。实际上,Spring 已经为建设企业级应用的各方面都给出了解决方案,能够用 Spring 来建设多种类型的应用。
Spring 的核心架构图:
好比为了让开发者使用 Spring 更加便利,SpringBoot 就诞生了,SpringBoot 并非一个新的技术,而是官方对 Spring 框架作了一个封装。
Spring 框架强大,使用简单,能够应对各种应用。在面对一个复杂的系统时,Spring 的依赖注入和 AOP 特性能够简化系统架构,让普通程序员也能够构建出不差的系统。
关注微信公众号,聊点其余的