从本节开始,我将要学习作为java高级阶段最最最最重要的一个框架体系,名为Spring。Spring是整个Java生态中最重要的一环。因为我也是初学,所以我的概括也不一定全面和精炼。写这一章只是为自己以后复习。
一.Spring IoC容器与Bean管理
这一小节,要学习Spring入门、Spring XML的配置、对象实例化配置、依赖注入配置、注解与Java Config、Spring实现单元测试。
1.Spring快速入门
IoC控制反转
◈ IoC控制反转,全称为Inversion of Control,是一种设计理念。
◈ 它并不是一种技术,而是一种宏观的设计理念,是现代程序设计遵循的标准,由代理人来创建与管理对象,调用者通过代理人来获取对象。IoC的目的是用来降低对象(或计算机代码)之间的直接耦合。
◈ 加入IoC容器将对象统一管理,让对象关联变为弱耦合。
下面举一个生活中的案例:
我非常喜欢吃苹果,但是摆在我面前有一件非常苦恼的事情,市场上各种各样的苹果琳琅满目,苹果有很多品种,口感和价格。假如现在在我面前放有各种各样的苹果,但我并不知道他们是什么品种,多少钱。我平时喜欢吃甜的而且脆的苹果,那么问题来了,如果不在他人介入的情况下,我想得到摆在我面前的几种苹果中最符合我口味的,该怎么做呢?是不是我就得翻页大量的资料去了解每一种水果的类型和场地或者把他们全部品尝一遍啊?这是很麻烦的一件事情。这个控制权是由我这个客户主动发起的,我必须要掌握所有对象的细节特征以后,才可以做出正确的选择。如果摆在我面前的是100种呢?除非我是专业人士,否则肯定蒙圈。
但幸运的是,有的聪明人就看到了其中的商机,随着市场经济的不断发展,各大水果超市和水果店孕育而生。水果摊是有老板的,我们去买水果的时候,我们并不需要知道这些苹果具体的特征。只需要找到水果摊的老板,和他说”哪个苹果是脆甜的啊?“ 然后老板就会告诉我”这种这种和这种苹果是脆甜的,他们的甜度各有不同,口感也各有不同。“ 由于水果摊老板常年从事这个行业,他当然比我要了解得多。我只需要听从他的意见进行采购就可以了。正是由于有水果摊老板的存在,我就把我获取对象的权力交给了水果摊老板。是由他替我做出了相应的决定。水果摊老板使我们日常生活得到了极大的便利。消费者和产品对象通过代理人进行了解耦,这种解耦带来的直接的好处就是对象之间可以灵活的变化,假如水果店老板发现市场上有了一种更脆甜的苹果,我们再去向老板购买苹果时,它就会向我推荐这种更好的苹果了,我完全不用关心它的其他特性,因为这是老板的事情,我唯一的目的就是获取到最好吃的脆甜的苹果,这就是IoC的控制反转的目的。诸如这样的例子其实还有很多。比如你来到一个全新的城市需要安居,这时要找的是中介,而不是随便地去乱找房东。通过中介的信息来选择在哪住,解决了找房子的各种各样的麻烦。这些都是软件工程里面IoC控制反转的核心体现。
·
DI依赖注入
IoC是一种宏观的设计理念,与编程语言无关,是现代程序设计遵循的标准。
DI(Dependency Injection)是具体技术实现,是微观实现。编程环境下到底使用哪些编程技术来完成在程序运行过程中对象的创建与绑定的工作。DI在不同语言中使用到的具体技术是不一样的。
DI在Java中利用反射技术实现对象注入(Injection)
Spring概述
Spring的含义:Spring可以从狭义和广义两个角度看待。
狭义的Spring是指Spring框架(Spring Framework) ,广义的Spring是指Spring生态体系。
狭义的Spring框架:
Spring框架是企业开发复杂性的一站式解决方案。也就是说,通过Spring框架,帮我们把以前在实际开发中那些开发体验不太友好,功能缺失的部分通过Spring的补充让整个体系更加的完整。Spring框架的核心是IoC容器与AOP面向切面编程。 IoC容器是所有对象管理的基础,AOP是建立在IoC容器的基础上才得到的。Spring IoC负责创建与管理系统对象,并在此基础上拓展功能。
广义的Spring生态体系:
Spring IoC是整个Spring生态体系中最核心,也是最基础的一部分。这是因为系统中所有的对象被统筹管理,在此基础上生根发芽,拓展出来了若干不同功能的子项目。可以说,目前的spring是一站式的综合解决方案,无论要做web应用开发,还是做安卓开发,或者是分布式应用,都是可以的。
以上就是Spring能做到的事情,在这些事情的基础上派生出来了几十种不同的项目,有Spring Boot 、Spring Framework、spring data 、spring cloud 、spring cloud data flow、spring security 、spring session、SpringGraphQL等等等,具体参考https://spring.io/projects
现阶段,我要学习的是Spring Framework这一部分。
传统的开发方式:对象直接引用导致对象硬性关联,程序难以拓展维护。
假如有一个使用者,要使用A这个对象,但是A对象实现某个功能,要额外的去new一个B对象。可以把A对象看成是一个service服务,B对象是一个Dao。这个过程是我们以前标准的开发方式。但是它也有一个巨大的弊端,当这个Dao随着我们开发的过程不断地演化,已经不适合了,由其他程序员开发了一个C对象,这是如果A要抛弃B,选择C,就要修改源代码,去new C对象才可以。这就意味着我们的程序就需要经过编译以后重新上线,才能够生效。就会涉及重新测试,重新发版,重新审批等等等,这一套流程是非常繁琐的。所以在我们的项目实践的过程中,并不推荐由具体的使用者来去new创建对应的对象。
那就要用到Spring IoC 容器了,采用被动的形式,来创建和管理这些对象,而我们使用者只是单纯的将容器中的对象进行提取就可以了。IoC是Spring生态的低基,用于统一创建与管理对象依赖。
就拿刚才的例子,如果换成Spring来做的话,首先,作为Spring,它会提供一个Spring IoC容器,这个容器其实是一个抽象的东西,相当于在我们Java的运行的内存中开辟了一段空间,这个空间是由Spring进行管理的。所有的对象都不是由我们使用者或A对象来创建,而都是由Spring IoC容器统一负责创建。创建好了以后,A对象是依赖于B对象的,当然也不会通过A对象去new B对象,而是通过反射技术,将A对象的依赖 B对象 注入到A对象中。作为使用者来说,我并不需要关注在容器内部到底有几个对象,对象之间的关系是什么样的,我只要关注在什么地方将我需要的对象提取出来就行了。也就是说不再面向具体的对象,而是面向容器,通过容器获取到需要的对象。
再拿那个水果摊的例子来比喻一下,spring IoC就是水果的仓库,spring框架就是水果摊老板,使用者就是顾客,顾客告诉水果摊老板给我来红的大的天的水果,老板就将对应符合要求的水果提取出来。
Spring IoC容器职责:
将对象的控制权交给第三方控制管理(IoC控制反转),利用java反射技术实现运行时对象创建与关联(DI依赖注入),基于配置提高应用程序的可维护性与可拓展性。
Spring IoC初体验
案例如下:
针对于孩子们的口味不同,我们如何让孩子直接得到自己喜欢的苹果呢?
下面通过传统的程序来演示一下:
打开Idea,新建一个空的Maven项目,项目名为s01
在entity里面创建两个java实体类,分别是苹果类Apple和孩子类Child,代码如下:
Apple.java
package com.haiexijun.ioc.entity; public class Apple { private String title; private String color; private String origin; public Apple() { } public Apple(String title, String color, String origin) { this.title = title; this.color = color; this.origin = origin; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getOrigin() { return origin; } public void setOrigin(String origin) { this.origin = origin; } }
Child.java
package com.haiexijun.ioc.entity; public class Child { private String name; private Apple apple; public Child() { } public Child(String name, Apple apple) { this.name = name; this.apple = apple; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Apple getApple() { return apple; } public void setApple(Apple apple) { this.apple = apple; } public void eat(){ System.out.println(name+"吃到了"+apple.getOrigin()+"种植的"+apple.getTitle()); } } ```java 然后创建一个Application来编写逻辑代码: package com.haiexijun.ioc; import com.haiexijun.ioc.entity.Apple; import com.haiexijun.ioc.entity.Child; public class Application { public static void main(String[] args) { //创建3个苹果 Apple apple1=new Apple("红富士","红色","欧洲"); Apple apple2=new Apple("青苹果","绿色","中亚"); Apple apple3=new Apple("金帅","黄色","中国"); //创建孩子,完成了苹果与孩子两个对象的关联 Child lily=new Child("莉莉",apple1); Child andy=new Child("安迪",apple2); Child luna=new Child("露娜",apple3); //孩子吃苹果 lily.eat(); andy.eat(); luna.eat(); } }
运行后可以得到我们需要的结果。
但是,上面的这些代码在实际工作中会有很多的弊端,下面依依来分析:
首先,苹果的这些描述,都写死在程序代码中了,而我们知道,苹果的这些属性随着季节和时间会相应的变化。一旦属性发生变化后,我们就必须去修改程序的源代码,就要对应用程序进行重新发布和重新上线。而且我程序里写了3个对象,那么它就只会创建3个对象。如果新加入一个孩子,就要修改源代码,程序的可维护性和可扩展性非常不足。而且是硬关联,通过构造方法参数来创建对象进行设置,者意味着我们程序运行以后,这个孩子和苹果的关系就已经是确定了,这个确定关系是在程序编译时就完成的,这是一件非常死板的事情。举个例子,现在露娜长大了,不再喜欢此软软的金帅了,他也开始喜欢吃红富士,而且莉莉也想尝尝金帅的味道,如果按上面传统的写法,就得去修改源代码进行调整。
下一小节就用Spring IoC来实现一下这个案例。
使用XML方式实现Spring IoC
下面就将上面的代码修改为Spring IoC管理的程序,体验一下Spring IoC的强大。
在这之前,我必须要强调一下,因为这是我们第一次接触spring,这个案例的目的是为了让我们对Spring有一个感性的认知,针对于里面的各个配置以及属性,会在后面的小节里详细进行学习。
1.配置Maven依赖
spring-context是spring IoC容器最小的依赖范围,只有引入了spring-context,我们才可以对程序进行管理。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.13</version> </dependency>
引入依赖后,可以点击External Libraries看看导入了那些依赖:
2.在resources目录下创建一个xml文件,applicationContext.xml
applicationContext.xml这个文件是spring IoC的核心配置文件,所有对象的创建和关联的设置,都是在这个xml来进行的。
那么如何对这个xml进行配置呢?我们先打开spring官网,然后进入到里面的Project里的Spring Framework的帮助文档的Core部分。然后寻找到1.2.1的xml配置,复制其schema约束到配置文件中。
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-metadata
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
然后idea也非常智能,会马上显示是否创建context。
我们点击创建,然后点击OK确定
书写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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--在IoC容器启动时,自动由Spring实例化Apple,取名为sweetApple放入到容器中--> <bean id="sweetApple" class="com.haiexijun.ioc.entity.Apple"> <property name="title" value="红富士"></property> <property name="origin" value="欧洲"></property> <property name="color" value="红色"></property> </bean> <bean id="sourApple" class="com.haiexijun.ioc.entity.Apple"> <property name="title" value="青苹果"></property> <property name="origin" value="中亚"></property> <property name="color" value="绿色"></property> </bean> <bean id="softApple" class="com.haiexijun.ioc.entity.Apple"> <property name="title" value="金帅"></property> <property name="origin" value="中国"></property> <property name="color" value="黄色"></property> </bean> </beans>
3.创建一个有main方法的java类,编写如下代码:
package com.haiexijun.ioc; import com.haiexijun.ioc.entity.Apple; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringApplication { public static void main(String[] args) { //加载指定的xml文件,来初始化IoC容器,context本身就指代了spring IoC容器 ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); Apple sweetApple = context.getBean("sweetApple", Apple.class); System.out.println(sweetApple.getTitle()); } }
运行后打印红富士。
我们对比就会发现一个非常大的好处,就是把原本的代码变成了可配置的文本,在xml文本中更改配置,不用动任何一行的源代码,就能对内容进行调整。
下面继续来写代码,演示一下对象关联。
4.在applicationContext.xml中增加3个孩子对象的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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--在IoC容器启动时,自动由Spring实例化Apple,取名为sweetApple放入到容器中--> <bean id="sweetApple" class="com.haiexijun.ioc.entity.Apple"> <property name="title" value="红富士"/> <property name="origin" value="欧洲"/> <property name="color" value="红色"/> </bean> <bean id="sourApple" class="com.haiexijun.ioc.entity.Apple"> <property name="title" value="青苹果"/> <property name="origin" value="中亚"/> <property name="color" value="绿色"/> </bean> <bean id="softApple" class="com.haiexijun.ioc.entity.Apple"> <property name="title" value="金帅"/> <property name="origin" value="中国"/> <property name="color" value="黄色"/> </bean> <bean id="lily" class="com.haiexijun.ioc.entity.Child"> <property name="name" value="莉莉"/> <property name="apple" ref="sweetApple"/> </bean> <bean id="andy" class="com.haiexijun.ioc.entity.Child"> <property name="name" value="安迪"/> <property name="apple" ref="sourApple"/> </bean> <bean id="luna" class="com.haiexijun.ioc.entity.Child"> <property name="name" value="露娜"/> <property name="apple" ref="softApple"/> </bean> </beans>
里面的Child通过ref属性来关联苹果。
下面编写测试代码
package com.haiexijun.ioc; import com.haiexijun.ioc.entity.Apple; import com.haiexijun.ioc.entity.Child; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringApplication { public static void main(String[] args) { //加载指定的xml文件,来初始化IoC容器,context本身就指代了spring IoC容器 ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); Apple sweetApple = context.getBean("sweetApple", Apple.class); System.out.println(sweetApple.getTitle()); //从IoC容器中提取beanId=lily的对象 Child lily=context.getBean("lily",Child.class); lily.eat(); Child andy=context.getBean("andy",Child.class); andy.eat(); Child luna=context.getBean("andy",Child.class); luna.eat(); } }
运行后,都吃到了水果。
可能这里你并没有完全发现IoC的好处。但是通过xml的配置,在以后的项目中确实可以提高程序的可维护性和可拓展性。
到这里,对spring IoC的算是有了基本的认识。下面会对里面的各项配置进行详细的讲解。