Spring
前言(Preface)
- 学会了java基础,JavaSE,JDBC,WebServer等相关内容,如果是10年前(2005年左右),到这就已经可以毕业了,可以下山了,好比少林寺学功夫啊,已经可以下山,可以闯荡江湖了,但现在我们开发,比以前有点不一样了,就那个年代写代码,老老实实一行行写,而现在呢,有大量的一些重复性的,或者说,比较复杂的,有难度的代码,已经有人帮你写好了,所以框架已经帮你写好了。
- 那么,我们要做的事情是什么呢,就利用这些框架怎么样,在上面再堆砌一些代码,什么代码啊,一般是业务逻辑啊,也就是说,很多复杂性的那种代码,重复性的代码,已经由框架帮我们解决了,而我们需要做的事情是什么呢,是利用这些框架,再开发,这样做的好处就是什么啊,提高我们的开发的效率吧。于是你们的春天来了,是吧。Spring是一个框架,还有这个MyBatis,也是一个框架,还有一个是Ajax,很有用的一个改善用户体验的一个技术。
- 关于Spring,MyBatis,ajax,怎么去学,框架的学习呢,比起之前,学的那些基础,又有点不一样,不一样在哪个地方呢,框架啊,我们重在会用,而不是说研究它怎么写出来的啊。这个Spring怎么写的啊,你们可以下载源代码,可以看,但是呢,我相信看不懂啊,而且没有时间看,原因有两个,一个是什么,代码量太大,我们介绍的Spring这个框架,用java语言写的啊,有2200个类,是吧,你去看吧,一个类少说几百行,是吧,而且关键是有时间看,还不一定能看得懂,因为什么呢,它这里面用到了大量的设计方面的知识,你不懂设计,不懂一些模式,你去看,硬看是吧,绝对是看不懂的啊,只会打击你的信心是吧,所以框架呢,我们重在会用啊,那么有关于它底层的实现,我们呢,可能点到即止啊,稍微提一下。不然的话呢,学框架的时候啊,如果老在想这个背后,底层是怎么回事,这样的话,你很可能什么啊,花了很多时间,还是不会用,这样就本末倒置啊。所以我们的重点在于会用。
- 当然也许几年以后,发展的很快啊,成为公司的骨干,架构师,所谓的资深的程序员啊,这个时候有可能需要什么啊,让你对框架做一些改动啊,让你怎么样,甚至是重写个新的框架吧,这个时候,也许我们需要去研究它的实现,但就现在而言,大家的定位不在这个地方,是会用是吧,那么,怎能达到这个目标呢,怎么会用它呢,是吧,我们课程里面有两个框架啊,一个是MyBatis,一个是Spring,那么Spring相对而言呢,要复杂的多啊,Mybatis相对来讲简单一点啊,不管是Spring还是MyBatis,还是其它的框架,要会用呢,我们把它分这么两个阶段,那不管是什么框架,我们要会用呢,我都把它分这么两个阶段。
- 那么第一个阶段,一般来说,我们会把这个框架啊,会划分成更多小的模块,我们先掌握什么,每个模块的知识,那么模块怎么学呢,很简单啊,就是我们通过模拟一些小的案例,每一个案例掌握了,足矣啊。我掌握这个小的模块有什么用啊,这个问题不用想,因为整个这个框架太大了,如果我们只掌握其中一个小的模块,你看不到全貌吧,就好像那个瞎子摸象一样,你像他摸到尾巴,根本不知道大象有多大啊,就不要去想,跟我们没关系,当你把各个模块都学会了啊,都掌握了,这个时候,我们就到阶段二了啊。
- 那这个第一阶段你怎么学啊,就是掌握一些典型的案例啊,一些小的案例啊,举的例子都非常短小,可能一个类里面就几行代码是吧,没有关系啊,就一个个来啊,我记得,古时候有一个哲学家叫老子是吧,天下难事,必作于易,天下大事,必作于细。就是一个小的地方掌握了,积少成多,我觉得这个非常对啊。
- 阶段二呢,模块都掌握了以后,我们就要利用各个模块搭建一个具体的项目,我们要将模块要结合在一起,我们要做一个具体的项目啊,这个项目不仅要将一些的模块要搭建起来,要联系起来,而且还要扩展,我们要用以前讲的一些模块的知识啊,我们把它扩展开来,甚至会增加新的知识,把这个项目做完以后,我们不仅对以前的模块的知识,扩展了,加深了,而且呢,我们还会什么学到新的东西,新的知识,那么会用,就是这么两个阶段啊。当然,你要想不仅会用,还会设计,还会怎么样,修改,还会改进这个框架,这是以后的事情了啊,我们现在怎么样,做不到,会用就可以了,是吧。
一、Spring介绍
- 下面我们就先说说什么是Spring,Spring是个什么东西呢,一句话啊,它是一个开源的,用来简化企业级应用开发的框架。开源就是源代码公开,其实这个开源的东西啊,作为java程序员呢,以后啊,都天天遇到的,我们很多项目中,都会遇到一些开源的产品啊,包括那个tomcat服务器,也是开源的啊;用来简化企业级应用开发,就是企业级应用啊,有很多复杂的问题啊。
1. Spring对常用的API做了简化和封装
- 那么呢,框架,比如说Spring是吧,它可以帮我们简化,比如说使用jdbc访问数据库,直接使用jdbc访问数据库啊,这个代码其实很啰嗦的吧,比如说,我要做个查询是吧,先要加载驱动吧,或者是从连接池当中获取连接也可以啊,加载驱动,获取连接,然后创建Statement,然后执行查询,处理结果集,最后要关闭连接,有异常还得处理异常吧,这些操作其实是很繁琐的啊,因为我们开发的时候,部署一个类啊,有大量的jdbc代码都要写,我都这么写,这个代码非常重复啊,而且不仅重复,还会什么呀,代码质量也不能保证。
- 怎么讲呢,就是,因为我们在做一个项目的时候,往往不是一个人啊,是一个团队,好多人一块在开发,是吧,那么呢,我们知道这个jdbc里面呢,这个引用jdbc这个资源吧,用完以后,就得关闭,或者说把它返回给连接池吧,是吧,如果你不关闭,不返回给连接池呢,这个连接就,慢慢就没了吧,整个应用程序运行不起来了,是吧。连接丢失,connection丢失,这是很常见的一些错误啊。
- 在我以前啊,项目开发经历当中,会经常遇到这种情况,就是新来的程序员呢,他往往不注意关闭连接啊,放那不管了,而且关键是啊,在测试时候发现不了,因为测试时候,连接丢失,它是一个缓慢的过程啊,一个两个连接,肯定看不出来,只有多了以后,才发现这个问题是吧,所以当时,经常什么呀,一般新来的程序员啊,大家都像写诗一样自由发挥了啊,想怎么写怎么写。
- 你想一想啊,那现在呢,如果我们使用Spring来使用jdbc啊,Spring会做什么,对jdbc做封装吧,这个时候你就不用担心了,因为它会帮你关闭连接,你根本就不用管这个连接哪来的,也不用管这个连接怎么关闭,或者归还啊,都不用管了,程序员根本不接触这些东西,所以代码质量提高了吧,而且代码也简化了,当然,Spring框架不仅仅对jdbc做了简化啊,可以这么讲,常用的API,几乎都做了封装和简化,这是Spring啊。
- 我们说Spring,它是一个用来简化企业级应用开发的框架,那具体指的是什么,就是把常用的API做了简化和封装啊。简化开发啊,简单说一说,怎么去理解是吧,就是说啊,Spring对什么呢,常用的API,比如jdbc,是吧,做了封装,那么,这样可以呢,大大简化这些API的使用,比如啊,使用Springjdbc访问数据库,就不用考虑,如何获取连接 和关闭连接了,那么代码质量呢,也会什么啊,提高吧,这是第一个啊,简化开发,当然,后面会说Springjdbc这个模块,就会有更进一步的理解。这就是第一点啊,简化开发。
2. Spring帮我们管理对象以及管理对象的依赖关系,Spring让对象之间的耦合度会大大降低
- 关于这个Spring框架啊,如果只是简化开发,还不足以说明,它是一个应用开发框架啊,它不仅可以简化我的代码啊,它还可以帮我们解耦,我们知道,一个程序啊,特别是一些大型的应用程序,如果就几行代码,几个类搞定了,那就无所谓了啊,如果这个类,如果这个应用程序规模非常大啊,成千上百个类是吧,这么大一个应用,我们都知道啊,这个设计工作非常重要吧,而设计有一个基本的原则是什么啊,高内聚低耦合,低耦合是什么意思,就是两个类,或者是两个对象之间不要有依赖关系吧。
- 比如说,有一个类A,还有一个类叫B ,假设啊,B这个类里面有个f1()方法,A呢,要调B的f1()方法,那我在A类当中,我new一个B对象,然后再调它的f1()方法,可以么,
class A{ B b = new B(); b.f1(); } class B{ f1() }
- 这样做是可以的啊,可以实现对吧,肯定可以正常执行的,但这样做不好,为什么,A和B之间耦合度太高吧,发现了么,A是直接跟B打照面了吧,是吧,见面了啊,如果这个时候,说A不想调B的方法,我想换一个类,我想调C类的f1()方法,怎么办,A类要不要改啊,这一行,
B b = new B();
,这个代码得改了吧,改成什么,new一个C了吧。class A{ //B b = new B(); //b.f1(); C c = new C(); c.f1(); } class B{ f1() } class C{ f1() }
- 所以这是什么,典型的耦合度太高的一种场合吧,一种情况吧。这样做不好是吧,我们应该低耦合,A调B,在A类当中,根本就不应该出现B b = new B();,这一行代码,这跟大家以前的认知有点,可能不同是吧,以前经常这么写吧。
- 那么学完Spring以后,就不这么写了,这样写呢,不好,耦合度太高啊,因为如果换一个类,换成调用C类的方法,A得改吧。而Spring这个框架啊,它可以帮我们管理对象,管理对象的依赖关系,这样呢,就降低了对象之间的耦合度啊,低耦合是什么意思,就是对象之间不要直接依赖。那高内聚是个什么玩应,它指的是,这个类的指责要单一,就是一个类只干一件事,不要什么都做。
- Spring让对象之间的耦合度会大大降低,那么代码的维护性会大大提高,比如两个类,A要调B的方法,A类当中,就要去new一个B,去调用它,耦合度太高吧,那么在Spring当中怎么调的呢,它这样写的啊,A要调B啊,在A类当中根本就不用new一个B了,B从哪来的呢,B由容器,Spring当中一个模块啊,它会帮你把B传过来,你就不用管了是吧。用Spring来建立A和B之间的依赖关系的话,会发现呢,我换成C类,A的源代码根本不用动啊,这就降低了耦合度,降低耦合度为的是什么啊,便于代码的维护吧,维护性是非常重要的啊,特别是大型的应用。
- 我说一点项目当中的一些常识吧。早期的这个项目呢,是不大注意维护性的啊,大家都没有些概念,从上到下啊,大家都没怎么写过代码,十几年前(2005年左右)啊,其实整个软件系统啊,发展时间并不长啊,也就一二十年,是一个比较新型的产业啊,当时,我们写代码呢,其实也没太多设计方面的知识,随便写,只要这个项目最终怎么样,还能够正常运行啊,这个业务逻辑能够跑通了,就可以了,后来大家发现什么,是一个什么问题啊,业务越来越复杂吧,那么这个时候,我们就需要什么啊,对原有代码去做一些修改,这个时候才发现怎么样, 维护性太重要了。
- 当年我们写那个公交系统的,就是这么写啊,一个系统呢,大概有,我记得是2300多个类,写了一年半啊,写完以后,到现在已经10几年了吧,仍然还有那么15个人留下来了,还是在那做维护,因为什么,当时那个设计不好,如果那些人走了,这个软件就彻底的废了,根本别人看不懂啊,非常复杂,设计的不够好啊,别人改了不知道从哪里改,这个元老都待着是吧,都维护啊,所以呢,这个解耦一定要做的,不然的话以后,这个模块要改啊,代价太大,以至于怎么样,推倒重来,重新写一套新的啊。
3. Spring容器整合了一套功能接口,Spring可以通过IoC去集成其他继承了Spring这套接口的框架
- 那第3点,还是关于这个应用开发框架啊,作为一个应用开发框架呢,它还可以帮我们去集成其他框架,这个怎么去理解呢,我们说Spring啊,虽然很强大,但是它也不是什么万能的吧,有一些功能,它没有怎么办,肯定是其他的框架有吧,这个时候解决办法很简单啊,就是让Spring去集成它们,比如说啊,我们在开发一个软件的时候,有一个什么呢,有一个定时任务处理,有这样一个问题要解决啊,定时任务处理是什么东西啊,最简单的就是闹钟啊,就每天早上7点半,闹钟响是吧,这就是一个定时任务吧,但是,企业级当中的定时任务,比这个要复杂的多啊,这个闹钟千奇百怪是吧,各种周期的啊,各个时间段的,都不一样。
- 那么我们要解决这个问题啊,是不用写代码了,有现成的框架,叫什么呢,当然不是Spring啊,Spring没有这个功能,谁有呢,Quartz啊,它有这个框架,它是定时任务处理的什么,最强大的一个框架,但是使用它呢,还是比较麻烦,怎么办,我们可以让Spring跟它集成,那么集成之后呢,再去使用Quartz要方便一些啊,要方便的多啊,当然Spring不仅可以集成它啊,还有好多都可以集成,包括这个Mybatis啊,这个MyBatis访问数据库的,它也可以集成进来。那集成其他的框架,它指的是Spring可以将啊,其他的一些框架集成进来,比如定时任务处理的Quartz等,从而方便这些框架的使用,正因为Spring有这样一项功能,它可以把其他框架集成进来,所以啊,有人把Spring这个框架叫什么,叫所谓的一站式框架,一站式服务。
- 比如说,我们要办一家公司,我要注册是吧,现在呢,以我的了解啊,我们要办一家公司,还挺麻烦的吧,你得去工商啊,税务啊,好多地方要盖很多戳吧,是吧,盖几十个章,很麻烦吧,如果有一家公司代办啊,你只要怎么样,交点钱,它就帮你跑完了吧,这叫什么啊,一站式服务器吧,Spring就这样的功能啊,我们本来这个项目当中要用很多很多框架,但是有了Spring以后怎么样,那个框架可以不用了,因为什么,它可以集成进来吧,很方便的啊。
- Spring框架,由Rod Johnson创建的开源框架,是一个开放源代码的设计层面框架,解决的是业务逻辑层和其他各层的一个松耦合的问题。Spring通过IoC和面向接口,提倡我们面向接口去编程,然后和我们的实现类做一个分离,通过Spring容器去管理这些实现类,当我们使用的时候,我们可以面向接口,通过接口来注入一个具体的实现,那这个实现的整个过程都交给Spring容器去管理,如此,便解决了松耦合问题,同时也实现了分层的项目架构模式,因此Spring将面向接口的编程思想贯穿了整个系统应用,即面向接口编程。
- 我们习惯了在开发的时候,都会先写一个接口,然后再写一个实现类,在实现类里做具体的业务处理,然后,我们使用这个接口,通过注解@Autowired,自动注入给我们一个bean,即这个接口所对应的,由Spring框架管理的一个具体的实现类,这就是Spring框架整体的一个设计思想,通过依赖注入(DI)的方式,实现了控制反转(IoC),将对象的管理权交给了Spring容器,从而就解决了松耦合问题。同时,当你要启动另外一种实现的时候,因为是面向接口的编程,那么你的实现类就可以有多种,只要你在注入的时候,告诉我们这个Spring容器,你具体要的是哪一种实现,那这样的话,Spring就能够自动的帮你去注入具体的实现,因此,就能够通过面向这个接口,很灵活的去使用具体的一种实现。
- Spring是于2003年兴起的一个轻量级的Java开发框架,轻量级对应着重量级,那它们到底指的是,重在哪里,轻在哪里,一般我们从这种资源的占用角度来说,轻量级是指在它启动的时候,消耗CPU的内存很少,从开发包大小的层面来说,如果是一个重量级的开发框架,那么它的开发包会特别的多,那特别多的话,它初始化的时候,在我们JVM里面的内存,也会消耗的更多,不管是堆栈,还是其他的一些内存管理里面,都会有更多的消耗;而我们的Spring框架,因为它的jar包是特别的少,同时它拥有很多的模块,在我们开发时,只需要去使用自己想要用的模块即可,因此在我们的开发过程中,就能够减轻我们Spring引入这个开发包的大小。
- 简单来说,Spring是一个分层次的JavaSE/EE full-stack(一站式)的轻量级开发框架。那为什么说Spring是一站式的一个开发框架呢,因为在我们Spring这个大环境里面,其核心的一个模块是IOC,从控制反转层面讲,我们所有的这个业务的一些bean,一些业务对象,或者说一些元数据,都收集在我们这个Spring的容器里面。那实际开发的时候,如果你都需要使用某一个功能的话,就可以通过Spring容器中的这些元数据,找到这些功能的具体实现,就都可以从我们这个Spring里面去加。
- 那我们Spring已经收集完了这些元数据,这些业务对象,那以后如果你要再使用这些业务对象,再扩展出其它的功能,比如说,加一个MVC框架,比如说你去做微服务等等,这些都是基于我们Spring这个大环境的,包括Springboot也是基于我们的MVC框架,然后MVC是基于我们的Spring开发出来的一套框架,然后后面还有一些整体的,其他的一些模块。这样在我们开发的过程中,就能够通过不同的使用场景,然后集成不同的一个,基于Spring的一个框架到我们到Spring框架里,然后为我们解决某一个实际的问题。
- 那在整个过程中,不管从你的业务逻辑层啊,到你的数据交互层啊,或者说一些分离层啊,一些消息队列啊,一些微服务等等的一些框架,你都可以通过继承我们这个Spring的一个,已经设计好的一套接口,然后你再去实现,最后通过Spring去整合具体的这些,继承了Spring这套接口的框架,比如说我们的SpringJMS,你可以去整合我们那个IBMMQ等等的一些框架。
- 这样的话,我们的spring框架,在我们开发过程中,能够为我们解决业务所需要的,不同场景的所有问题,而整个开发下来,我们调用的东西就都是Spring框架它本身的,所以说,它为我们提供了一个一站式的解决方案,也可以这样说,它为我们就减轻了实际开发的一个工作量,因为很多框架的整合问题,我们的Springboot都已经帮我们去做好了。
4. Spring的两个核心机制IOC和AOP
- Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架,IOC和AOP是Spring框架的核心内容。Spring主要是为了解决,企业应用开发的复杂性而诞生的。Spring框架是非侵入性的,就是说,即使你不用Spring这一套框架自带的内容,也可以换一些类似的框架,集成到Spring框架里面用。Spring是通过依赖注入(DI),把我们的,对对象的控制权交给了Spring容器去管理的,那我们容器就能够管理我们,所有业务数据对象的创建,销毁等整个生命周期。Spring可以实现面向切面编程,不管是做日志输出,还是权限管理等等,Spring都能够使这些外加的功能,既不会侵入到我们原本的业务逻辑里面,同时又可以将这些功能在原本的业务逻辑前后做一些交互处理。Spring容器,管理着我们所有业务对象的所有元数据和整个生命周期。
- 下一个话题,什么是Spring容器,对于Servlet的运行也依赖于一个容器吧,那个容器干什么的,它有个基本功能是干什么呢,帮你管理对象吧,这个Servlet写好以后,我们有没有去new一个什么Servlet啊,写过么,没有吧,这个Servlet是由容器创建的吧,容器销毁的吧,它还要怎么样,帮我们做初始化,销毁,是吧,那Spring框架当中呢,也有一个类似这样的模块啊,帮我们管理对象,帮我们什么呢,建立对象之间的依赖关系。
二、如何使用Spring容器管理对象
- 这个Spring容器,它是Spring框架中的一个核心模块,用于什么呢,管理对象,这个管理对象指的是对象,它帮我们创建好,并且呢,它还帮我们把对象之间的依赖关系给建立起来了。
1. 创建Maven项目Spring01
- 那我们这容器,我们应该怎么去用它呢,我们接下来啊,动手启动Spring容器,把eclipse打开:
- 打开eclipse以后,有如下项目:
- 选择
Close Unrelated Projects
,将多余的项目移除:
- 点击OK,
- 选择
Run in Background
,后台执行:
- 如下:
- 我们先建一个maven工程,new一个maven工程啊:
- 把这个选中啊,创建一个简单的工程,
Create a simple project(skip archetype selection)
,下一步啊,
- 我们把这个工程的名字定义下,
Group Id : cn.tedu.spring,Artifact Id : spring01,Version : 0.0.1-SNAPSHOT,Packaging : war
。
- 工程的名字,我们就叫spring01吧,然后这个Packaging,导包的方式,我们选择这个war,完成啊,接下来呢,我们因为这边有个小叉啊,我们要生成那个部署描述文件吧,我们可以在这,在这
Deployment Descriptor...
,这块,按右键吧,生成,这样的话那个web.xml文件就有了,是吧,
- 然后我们在工程上面呢,按右键,点倒数第一项Properties,指定那个运行环境吧,Targeted Runtimes,Apache Tomcat v7.0,准备工作就做好了啊,我们先把这个工程啊,建好。
2. 添加Spring配置文件applicationContext.xml
- 我上传一个小的压缩文件啊,
applicationcontext.zip
,把它下载下来以后,解压之后,应该会看到一个applicationContext.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:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
</beans>
- 把这个xml文件,把它拷贝到这个Java Resources展开,有一个什么,
src/main/resources
吧,把它放到这个下面,
- 我们把这个文件打开看一下,这里面呢,有一个根元素,叫
<beans></beans>
,别的没有了吧,别的不用管它,稍微看一下就行了,里面根元素叫beans吧,别的没有,没关系啊,不管它了,关掉。
3. 添加spring依赖,创建FirstSpring.java类启动Spring容器
- 接下来呢,我们在这个src/main/java下面,新建一个java类,包名呢,我们就叫first吧,类名叫FirstSpring,我们可以把这个main方法加进来,
- 下面呢,我们在这个main方法里面呢,我们写上这样两行代码啊,
String config = "applicationContext.xml";
,这边我们定义了一个变量,它的值就是那个配置文件啊,叫applicationContext.xml
,那第二行代码啊,ApplicationContext ac = new ClassPathXmlApplicationContext(config);
,先写完再简单的解释一下啊,现在我写的啊,大家发现出来了,这不认识吧,是不是不认识啊,
- 这个时候我们需要导包了,导谁呢,导spring的包了啊,这个jdk里没有,spring框架是Sun公司提供的么,不是吧,是由别人写的啊,下面呢,我们要把这个框架的包导进来,我们导下包啊,可以用maven啊,我们把这
pom.xml
文件打开,然后呢,点一下这个添加,Add,我们搜索什么呢,spring-webmvc
,
- 如果没有反应,选择
Window/preferences/Maven:
,勾选中Download repository index updates on startup
,这个是如果Maven服务器有自建索引的话,便可以通过设置这里,通过Add添加的方式,以索引的方式搜索依赖,但注意的是,国内的Maven服务器,包括阿里云都是没有索引的,所以,一般通过复制粘贴的方式加载依赖,那默认也被选中的一般有第二个:Do you automatically update dependencies from remote repositories,
- 然后搜索spring-webmvc,往下走啊,找哪一个呢,org.springframework spring-webmvc,展开,
- 下面是版本啊,我们都统一选这个版本吧,3.2.8.RELEASE [jar]啊,这个是目前比较主流的一个版本啊,3.2.8,
- 如果有提示:叉掉即可,与项目代码无关。
(Waiting for Focus) Eclipse Error Reporting
Welcome to the Eclipse Error Reporting Service.
Do you want to help Eclipse?
With your permission Eclipse can inspect any error logged
inside the IDE and inform the affected projects about the issues
you experienced. Do you want to help out by enabling Error
Reporting?
Enable... Disable
- 选好了,ok,然后save一下啊,这个是个文件,实际上啊,save之后,看这个地方,按这个
Libraries/Maven Dependencies
,这个,是不是有一堆的jar包啊,应该是9个啊,
- 这边啊,一堆jar包啊,这些呢,
3.2.8
这些,属于spring框架的核心包啊,我们把它导过来了啊,导入进来了,回到这FirstSpring.java
来啊,我们就可以怎么样,按这个小叉导包了啊,注意导包的时候选哪一个呢,选第2个啊,org.springframework.context的ApplicationContext
,不要选上面那个,apache的那个org.apache.catalina.core的ApplicationContext
,
//注意:
//此处因为阿里云maven没有3.2.8,所以导入了spring-webmvc3.2.9 的版本jar包,
//包导好了,那后面再加一句话啊,我们把这个ApplicationContext对象ac输出一下。
package first;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public static void main(String[] args) {
/*
* ApplicationContext是接口
* ClassPathXmlApplicationContext是一个实现类,该类会依据类路径去查找spring配置文件,然后启动容器。
*/
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
System.out.println(ac);
}
- 我把这个,
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
,这行代码,我再简单解释一下啊。ApplicationContext
,这是一个接口啊,是什么,是我们要用的这个容器的接口啊,那么呢,ClassPathXmlApplicationContext
,它是谁呢,它是上面这个接口ApplicationContext
的实现,该类会依据类路径去搜索,或者说是查找吧,spring配置文件,然后呢,启动容器,这类路径是什么玩意呢,非常简单啊,它指的是,你可以这么去记啊,从哪找啊,从这个src/main/resources
文件夹下面去找啊。
简述Spring配置文件及其作用
- 如果我这个配置文件,这个
applicationContext.xml
文件啊,没有把它放到resources
下面啊,而是在resources
下面我再建了一个子文件夹,我再建个文件夹吧,比如说那个文件夹叫config
,我把那个配置文件呢,放到config文件夹底下,这要怎么写啊,猜一下,对,就这么写是吧,String config= "config/applicationContext.xml";
,我们现在不要把它放这底下啊,这样写什么意思,在哪找了,对,它就在这找啊,在resources下面去找一个子文件夹,叫什么config吧,然后再找下面的什么,配置文件吧。 - 那这个配置文件起什么作用呢,我先简单解释下啊,就这个玩应,这个
applicationContext.xml
起什么作用呢,我们知道,是这个Spring容器帮我们管理对象的吧,也就是说它帮我们创建对象,那么当我们需要某个对象,比如说,我需要一个学生对象,我们就不用去new 一个Student这样一个构造器了啊,那么在spring当中,你可以什么,让spring容器来帮你创建,那么容器怎么知道要创建哪个对象呢,答案是什么,你在配置文件里面配一下就可以了,就有点像什么,之前我们写的Servlet,你要不要在web.xml
里面去配啊,要吧,这一个也一样啊,因为什么,那个Servlet的对象谁创建的,以后Servlet容器创建的吧,我们这里也一样,我们这些对象都是由spring容器去帮我们创建的。 - 所以大家可以呢,这么去理解啊,配置文件,我打个比方,我们可以把它看作是一个菜单,比如说,我家里雇了个阿姨帮我做饭,这个阿姨就是容器,那么这个饭和菜呢,就是什么,对象吧,那么阿姨做什么饭,做什么菜,她不是随便做的吧,她有个依据,什么依据啊,我之前给她写的那个菜单,早上吃什么,蛋炒饭是吧,中午吃什么饭炒蛋,是吧,我得写啊,是这个意思吧,这个配置文件就起这样一个作用啊,当然现在我们是空的吧,什么都没有,那么一会我们就加点东西进来,它帮我们什么,创建吧。
4. 启动Spring容器
- 我们现在这两行代码干什么呢,就是启动spring容器对吧。
String config = "applicationContext.xml"; ApplicationContext ac = new ClassPathXmlApplicationContext(config); System.out.println(ac);
- 这最后一行代码
System.out.println(ac);
,不用管它啊,主要是为了什么,查看那个对象的信息,这样我们可以更确定什么,容器有没有启动成功是吧,我们运行一下啊,按右键,Run As/Java Application
吧,这是个main方法吧,可以直接执行吧,有反应么,看控制台Console,这个样子吧,有个输出啊。
七月 14, 2020 2:38:57 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@421faab1: startup date [Tue Jul 14 02:38:57 CST 2020]; root of context hierarchy
七月 14, 2020 2:38:57 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
七月 14, 2020 2:38:57 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6fadae5d: defining beans []; root of factory hierarchy
org.springframework.context.support.ClassPathXmlApplicationContext@421faab1: startup date [Tue Jul 14 02:38:57 CST 2020]; root of context hierarchy
- 再往前面看,这个红色的部分呢,是日志,这是我们的spring容器启动以后的日志啊,我们往前看看,有这么一句话,看一眼啊,
startup date [Tue Jul 14 02:38:57 CST 2020]; root of context hierarchy
,这个startup date
有么,startup是启动的意思吧 ,启动了日期啊,说明我们spring容器啊,已经启动成功了啊。 - 那我们把启动spring容器的整个步骤总结一下啊,我们要启动spring容器呢,分这么3步啊,首先第一步啊,我们需要导包,要把容器相关的包导进来啊,我们这边用的是哪个包啊,
spring-webmvc
,第一步啊,接下来呢,第二步,我们需要添加一个配置文件吧,添加spring配置文件,这个配置文件用来告诉容器,要创建哪些对象的,然后第3步启动容器,我们刚刚写这么两三行代码,
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
System.out.println(ac);
- 那么第一行代码,
String config = "applicationContext.xml";
,就是告诉spring容器,这个配置文件在哪个地方啊,接下来呢,我们就可以什么啊,利用这样一行代码,new ClassPathXmlApplicationContext(config);
,去启动容器了,ClassPathXmlApplicationContext
,这个对象,它负责启动容器啊,这个代码呢,并不复杂啊。
5. 利用Spring容器创建对象的3种方式
- 下面我们看看这个容器启动之后,我们应该怎么运用它来帮我们去创建对象,第3个问题啊,利用容器帮我创建对象啊,有这么3种方式,重点掌握第一种,后两种,我们只需要了解啊,我们先说一下第1种方式,叫利用无参构造器,利用无参构造器来创建对象,那么,具体怎么做呢,它分这个3小步啊,我先把这个步骤说一遍,然后我们再做一个就可以了啊。
方式一:无参构造器(重点)
- 首先第一小步,我们需要为这个类啊,添加无参构造器,是不是就是构造器没有任何参数吧,我们知道这个类里面,有一个什么缺省构造器,如果一个类,我不加任何构造器,它是不是自带构造器啊,那个自带的构造器,带不带参数啊,不带吧,加个说明啊,或者是缺省构造器也行啊,缺省的也就是一个默认的构造器啊,一个意思。
- 第一步,如果你想为这个类创建一个对象,怎么办,首先这个类呢,应该有一个无参构造器,或者是缺省构造器,第2步是什么呢,我们需要在配置文件里面,我们还打个比方,这个配置文件就是一个菜单吧,容器就是什么啊,阿姨,对吧,阿姨利用这个菜单呢,来做菜,来做饭吧,是这样的吧,所以啊,第二步呢,我们要在配置文件里面,添加一个bean元素,这个bean翻译成中文啊,豆豆,是吧,豆子,黄豆,绿豆,是吧,豆子啊。
- 那这个bean是什么东西呢,在spring这个环境里面呢,我解释一下,它跟我们javaBean,这个玩意不一样啊,是吧,今天我们讲的bean,跟他没关系啊。那么,咱们说bean是什么呢,bean就是容器所创建的对象,就是这个对象是谁创建的,这个对象是由容器所创建的,至于怎么配置啊,一会呢,我们会写一个啊,就可以了。
- 我们看第3步啊,第3步我们可以启动容器,然后呢,调用容器的getBean方法就可以了,把这个对象什么找到了啊,就好像怎么样,你给阿姨写了个菜单,中午我要吃西红柿炒鸡蛋,写好了是吧,阿姨按着这个要求,做好了吧,然后呢,我跟阿姨说上菜,端上来了吧,getBean就什么,上菜,是吧,就这个意思啊,但上菜说不定上什么菜吧,你可能做了好几个菜,说先上哪个啊,西红柿炒鸡蛋啊, 这样吧,我们还是看一个例子好吧,挺简单的啊,都不难啊。
- 我们想让容器啊,帮我创建一个学生对象啊,我们得先有一个学生类吧,对吧,我们就在first这个包下面,新建一个java类,类名就叫Student,学生类,如果这个类我不再往下写了,它里面有构造器么,有一个缺省的吧,这样呢,我们就加一个吧还是,因为缺省构造器啊,它没有任何的输出,不便于我们去分析啊,所以呢,我特意呢,加一个什么,无参构造器,特意加一个啊,里面添加一个什么呢,输出语句,这样,这个构造器,被调用的时候,那么在控制台呢,就会怎么样,输出吧,便于我们去分析啊。
package first;
public class Student {
public Student() {
System.out.println("Student()");
}
}
- 第一步啊,这个类应该有个无参构造器吧,都有了啊,接下来我们在spring的配置文件里面啊,第2步了吧,在spring的配置文件里面,我们要加上一个bean元素,就在这个里面啊,你们现在跟我是一样的吧,这个根元素叫beans,不用动啊,固定的,前面是固定的写法,不用管它,我在这个配置文件里面,怎么写呢,你看我先加个注释吧,这个配置是干什么的呢,是利用无参构造器,利用无参构造器创建对象啊。它的配置是这样子的啊,
<bean id="stu1" class="first.Student"/>
,这个bean里面要给它加个id,还有个class。 - 我解释一下,这个id和class分别是干什么的啊,还是加一个注释,id这是个属性啊,是这个bean元素的一个属性吧,我们都学过xml,xml这个标记啊,里面可能会有些属性,是吧,id这个属性的作用就是bean的名字啊,bean的名字有要求的啊,什么要求呢,要求唯一,在整个配置文件里面啊,每一个bean的id呢,都要求不一样,打个比方,就好像每一个人是吧,都有自己的什么,身份证号码一样吧,都不一样吧,要求唯一啊。
- 第二个属性叫class,它呢,是这个bean的完整的,这么说吧,bean这个类的完整的名字,它是完整的类名啊,包括什么包名一块写,因为spring容器啊,它其实底层用的是java反射机制,来创建的啊,那么java反射呢,需要依据什么,这个类名啊,来找类的字节码文件,完成类的加载,这个过程呢,我们在这里稍微提一下,具体的细节可以参考java反射的内容,所以要求类名要写完整吧,如果写的不完整,写错了,它就没有办法找到这个类的字节码文件啊,没有办法去创建这个对象,无参构造器重点掌握的啊。
- 我们这个bean对应的java类是谁呢,就是在first包下面有一个类叫Student啊,id呢,要求唯一,随便写一个stu1啊。
<bean id="stu1" class="first.Student"/>
,写好了是吧, - 接下来我看第3步了啊,这个菜单就写好了吧,我们就可以什么,让容器帮我们创建了啊,那么容器创建好之后,我怎么获得这个对象呢,我们要启动容器吧,那么这行代码是干什么的啊,
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
,启动容器的啊,那么启动之后,我们就可以呢,调容器提供的一个方法,我们把上面这行代码,System.out.println(ac);
,先暂时注释掉啊。 - 我们现在干什么呢,调容器的一个方法,这个方法叫getBean,get就是获取吧,获得bean啊,这里面要传一个参数,什么参数呢,猜一下,id吧,我刚刚说过每一个bean的id都不一样,起什么作用啊,就是这起作用了啊。就好像给那个菜起个名字是吧,你的阿姨做了好多菜啊,那么你跟阿姨说,要上哪道菜,你得告诉她菜名吧,id是什么stu1吧,还有一个小问题啊,这个getBean方法的返回值,是一个什么啊,Object啊,所以要做强转吧,要做强制转换啊,
Student stu = (Student) ac.getBean("stu1");
,我们这呢,先不做强制转换啊 。 - 我们再给大家另外一个方法,是吧,这个方法呢,是它的一个重载方法,后面再加上一个参数就可以了啊,什么参数呢,
Student.class
,这样我们就不用做强制转换了啊,Student stu = ac.getBean("stu1",Student.class);
,之前有没有接触过这个玩意,java反射接触过么,没有是吧,没有的话,这个呢,类加载器,方法区,这些概念有印象么,方法区有印象是吧,那么这个Student.class
,它指的是方法区当中的那个什么,class对象,知道这个就可以了啊。 - 也就是说,你可以把这个getBean方法,当成一个什么,API,去调用就行了,至于这个API内部的实现,不用管它,你需要什么啊,传这样一个对象
Student.class
给它,我们就不用强转了啊。这加个说明啊,这个Student.class
算是什么呢,它是方法区中的class对象,对于我们来讲啊,你只要知道什么啊,怎么去用这个方法啊,需要什么啊,首先需要传bean的id吧,还有呢,这个类所对应的那个什么,方法区里的class对象,最后,System.out.println(stu);
,我们把这个对象呢,输出一下,
public class FirstSpring {
public static void main(String[] args) {
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//System.out.println(ac);
//Student.class 方法区中的class对象。
Student stu = ac.getBean("stu1",Student.class);
System.out.println(stu);
}
}
- 写完了吧,我们就运行了啊。按右键
Run As ,Java Application
吧,有什么输出啊。
- 首先
Student()
,构造器被调用了吧,另外呢,底下还有什么,first.Student@7921b0a2
,有这个对象的信息吧,因为我们这个类啊,没有加toString方法啊,它默认的输出就这个样子吧。 - 故意的犯两个错啊,那如果啊,在
applicationContext.xml
,这个Spring的配置文件里,我把这个bean的id写错了,本来是什么啊,bean的id是stu1吧,Student stu = ac.getBean("stu1",Student.class);
,结果呢,我这写的是stu2,Student stu = ac.getBean("stu2",Student.class);
能不能取到,取不到,我们看,运行一下,走,报错了吧,报什么错看一眼,它说NoSuchBeanDefinitionException
,找不到这个bean,
- 或者你看后面也可以啊,No bean named ‘stu2’ is defined,很明显吧,说明你给的那个bean的id不对吧,
- 以后看到这个
No bean...
,这个玩意啊,你要想到什么啊,bean的id写错了吧,你的阿姨做这个小炒肉啊,结果呢,你让她上水煮鱼,阿姨没折吧,那刚把肉做完,没有鱼啊,是这个意思吧。再来一个错误啊,比如说,我把类名写错了,bean的class属性的值,<bean id="stu1" class="first.Student"/>
,本来是first.Student
吧,我把这个S小写了,<bean id="stu1" class="first.student"/>
,有这个类么,没有啊,类名是区分大小写的吧,我们再来一遍,故意写错了,我再运行一下啊,这个main方法报错了吧,报什么错,看这个错啊,NoClassDefFoundError: first/student.java
,
- 也是没有这个类的定义吧,错误的什么,名字啊,类名。赶紧跟它熟悉熟悉啊,以后它就是你的朋友了是吧,现在没事跟你打个招呼。现在我报这个错是因为类名写错了吧。这个类名不能写错啊,如果类名写错了,类加载器就没办法呢,找到类的字节码文件,完成类的加载。
- 那么创建对象的第一种方式,我们就说完了啊,这个不难吧,那我们就写一个啊,干什么呢,利用spring容器去创建一个什么呢,创建一个java.util包下面的一个Date类型的对象,
java.util.Date
,以前我们怎么写啊,new一个什么啊,Date,现在不能这么写,让容器去创建,你给我创建试试看,怎么做,注意步骤是吧。 - 既然可以new一个Date说明什么,这个Date类里面有没有无参构造器啊,有吧,这个类还需要写么,第一步还要写么,不用写了吧,因为jdk里面,已经有这个类了吧,所以我们只需要做第几步啊,第2步和第3步吧,也就是说,我们需要在配置文件当中配置个bean吧,然后呢,启动容器,调哪个方法,getBean方法吧。我要的这个类啊,来自哪个包啊,来自于
java.util
,这个包下面的啊,怎么做啊,再写一遍啊,我们要让容器帮我们创建这样一个对象。刚才说过了啊,这个类是现成的。 - 那我们可以做第2步吧,我们应该在这个配置文件里面呢,再加个bean吧,可以么,再增加一个bean,它的id,我叫date1吧,对应的java类呢,告诉你是哪个类,
java.util
下面的Date,所以配置文件里面呢,我们只需要加上这样的一个配置啊,<bean id="date1" class="java.util.Date"/>
,把这个配置好了以后,接下来第3步了吧,类不用写了,现成的,第2步添加配置文件里面那个bean元素吧。 - 然后第3步,我们应该启动容器,去获得这个对象吧,这怎么写啊,Date吧,跟上面是一样的,getBean,它的id啊,date1吧,
Date date = ac.getBean("date1",Date.class);
,我们可以把这个日期对象呢,输出一下,System.out.println(date);
,就这样吧。 - 但是啊,这个有可能,会有一个比较隐蔽的错误啊,我们的配置文件当中,对应的这个java类,是
java.util
包下面的吧,那么你要注意什么呢,在这导包的时候,这个类型应该也是什么,java.util
吧,你发现没有,这个它自动导哪个包啊,java.sql
,这个类型还一致么,不一致吧,你看容器给你的是java.util
这个下面的,而这的类型是什么,是java.sql
吧,类型一致么,不一致吧。我们得运行一下啊,会不会报错呢,走,报错了吧,往后看一看,怎么说的,
七月 14, 2020 4:59:07 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@421faab1: startup date [Tue Jul 14 04:59:07 CST 2020]; root of context hierarchy
七月 14, 2020 4:59:07 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
七月 14, 2020 4:59:07 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1554909b: defining beans [stu1,date1]; root of factory hierarchy
Student()
first.Student@174d20a
Exception in thread "main" org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'date1' must be of type [java.sql.Date], but was actually of type [java.util.Date]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:367)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1121)
at first.FirstSpring.main(FirstSpring.java:21)
- 它说bean啊,名叫什么的bean啊,date1的bean吧,必须是哪个类型的,
java.sql
吧,但实际返回来的是什么呀,是java.util
吧,类型不一致,我让阿姨给我做一道水煮鱼是吧,然后阿姨给我端上来的是什么,是这个酸菜鱼,就这个意思吧,都是鱼,但是类型不一样吧,所以我不吃,是吧,我把它解雇了啊,就这个道理啊,我们要怎么样,改一下吧,这是个隐蔽的错误啊,导包的时候,我们的工具啊,自做聪明吧,给我导了一个java.sql
啊,这个不对啊,改成这个java.util
。 - 做程序员啊,经常身边各种各样的坑,是吧,这一个,那一个,那么,解决办法是什么,就是一定要,首先要细心啊,绝对要细心,第2个呢,就要对各种情况啊,如果之前没有遇到过,遇到以后怎么样,下一次再遇到这样的错误,就有经验了吧,也是一个办法啊,遇到错误以后,可以增长我们经验是吧,多写写,这样呢,一积累呢,是吧,变成了经验,这么来的是吧。
方式二:静态工厂方法
- 这个呢,不说了啊,创建对象的第一种方式啊,重点掌握的啊,接下来两种,我们只需要了解一下,平时用的非常少,方式2,我们可以利用静态工厂方法,这个呢,加个说明啊,了解,什么叫了解啊,就是你看到这个配置,你能理解什么意思就行了啊,不用你去写,大概呢,能看懂啊,什么意思,这个静态工厂方法,是什么意思呢,我们开始举个例子啊,我们在这个spring的配置文件里面呢,添加这么个配置吧,我先加一个注释,使用静态工厂方法创建对象。
- 我们把下面这个配置看懂了,就行了啊,我是这样写的,
<bean id="cal1" class="java.util.Calendar" factory-method="getInstance"/>
,我先简单的解释一下啊,这个id跟class,我就不说了啊,我要说下这个,factory-method
,这个属性啊,它指定一个方法,一个什么方法,指定一个静态方法,静态方法是什么,就是用static修饰的方法吧,这个就都知道吧,static修饰的方法啊,静态方法,那么,整个这个配置是干什么的呢,它是告诉容器要调这个类的,这个静态方法,来创建这个对象。 - 相当于什么,相当于这个意思啊,调用了Calendar这个类的,这个静态方法
getInstance()
,那么这个方法会返回一个什么,对象吧。factory-method
指定一个静态方法。Calendar.getInstance()
,写过是吧,它会返回什么,返回一个Calendar对象的实例吧,写一下啊,容器会调用该类的静态方法,来创建一个对象,刚刚我们第一种方式,是让容器调哪一个,调那个无参构造器吧,这一次呢,是让容器去调这个类的静态方法来创建一个对象。
- 那配置写好了,下面我们来测试一下啊,我们启动容器以后啊,getBean谁啊,这个cal1,这个bean吧,试一下啊,我们在
FirstSpring.java
中,再增加这么一行代码吧,Calendar cal1 = ac.getBean("cal1",Calendar.class);
,然后,我们再把这个对象输出啊,System.out.println(cal1);
- 都写完了,运行一下这个main方法,看最后一行啊,这边我们看到,什么输出啊,
java.util
下面有一个,GregorianCalendar
,这个GregorianCalendar这个类啊,是谁啊,就是Calendar的一个实现类吧,是吧。
- Calendar这是个抽象类吧,有印象么,或者说Calendar这是父类吧,这个GregorianCalendar是谁啊,是子类吧,父类可以不可以指向子类啊,可以吧,Calendar指向GregorianCalendar啊,没有问题的。这是用静态工厂方法,以后看到这个配置啊,
factory-method
,前面是class吧,那么,你的第一反应是什么呢,就是要调用class这个类的这个静态方法,来创建这个对象就行了啊。
方式三:实例工厂方法
- 接下来,我们再看第3种方式,第3种方式也只需要了解就可以了啊,不是我们重点要介绍的,能看懂就可以了,用的非常少,方式3,我们使用实例工厂方法,我们举一个例子啊,在这个底下,我再增加一个注释啊,使用实例工厂方法创建对象,它的配置是这个样子啊,也是bean,它id只要求唯一就可以了吧,然后是
factory-bean
,跟factory-method
,即<bean id="time1" factory-bean="cal1" factory-method="getTime"/>
。 - 这里,我要把这个
factory-bean
说一下啊,大家有没有发现这个factory-bean
啊,它的值等于什么,正好跟上面这个静态方法创建对象的bean的,这个id一样吧,是吧,是不是一样的,
<bean id="cal1" class="java.util.Calendar" factory-method="getInstance"/>
<bean id="time1" factory-bean="cal1" factory-method="getTime"/>
- 那么,它的作用是什么呢,容器啊,会这么处理啊,它会根据这个id,找到对应的这个bean吧,然后调用它的这个方法
getTime()
,这是个什么方法,这是一个实例方法,也就是说这个方法前面,有没有加static修饰啊,没有吧,就是个普通的方法,所以,整个这个配置的含义是什么呢,让容器调上面这个bean的对象的什么,cal1的getTime方法啊,这个factory-bean
,它是指定一个什么呢,bean的id啊,指定一个bean的id,那么容器会调用该bean的实例方法来创建一个对象。这啊,我们这段配置的含义就是说啊,要调这个cal1这个对象的哪个方法,getTime方法,来创建什么,一个对象吧。 - 我们知道这个cal1,是个什么东西啊,是一个Calendar对象吧,那么这个Calendar对象里面有没有getTime方法,有吧,它的返回是什么东西,Date没错,非常好啊,基础学的可以啊,反应这么快啊,就应该这么熟是吧,因为这是一个比较常见的类啊,我们接下来啊,要启动容器,看看这个配置有没有问题啊,我们可以启动容器,就getBean一下,配置文件改完以后,记得保存啊,源代码也一样啊,即时存盘啊。我们在这,
FirstSpring.java
,这个java类里面啊,再添加一段测试代码,
Date time1 = ac.getBean("time1",Date.class);
System.out.println(time1);
- 这个bean的id,不能写错了啊,不然找不着了。我们再把这个main方法执行一遍,观察一下这个time啊,有吧,
- 我们回过头来,再看一下啊,实例工厂方法,其实也很简单吧,就调用这个bean,bean就是什么,刚才说过吧,是容器所创建的对象吧,就是这个对象cal1啊,调用这个对象哪个方法,getTime方法吧,没错。完整代码如下:
(0)学生类:Student.java:
package first;
public class Student {
public Student() {
System.out.println("Student()");
}
}
(1)配置文件:applicationContext.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:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
<!-- 使用无参构造器创建对象 -->
<!--
id : bean的名字,要求唯一。
class : 完整的类名 (包括包名)
-->
<bean id="stu1" class="first.Student"/>
<bean id="date1" class="java.util.Date"/>
<!-- 使用静态工厂方法创建对象 -->
<!--
factory-method : 指定一个静态方法。
Calendar.getInstance()
容器会调用给类的静态方法来创建一个对象。
-->
<bean id="cal1" class="java.util.Calendar" factory-method="getInstance"/>
<!-- 使用实例工厂方法创建对象 -->
<!--
factory-bean : 指定一个bean的id,容器会调用该bean的实例方法来创建一个对象。
-->
<bean id="time1" factory-bean="cal1" factory-method="getTime"/>
</beans>
(2)测试类FirstSpring.java:
package first;
import java.util.Calendar;
import java.util.Date;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FirstSpring {
/*
* ApplicationContext是接口
* ClassPathXmlApplicationContext是
* 一个实现类,该类会依据类路径去
* 查找spring配置文件,然后启动容器。
*/
public static void main(String[] args) {
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// System.out.println(ac);
//Student.class 方法区中的class对象。
Student stu = ac.getBean("stu1",Student.class);
System.out.println(stu);
Date date = ac.getBean("date1",Date.class);
System.out.println(date);
Calendar cal1 = ac.getBean("cal1",Calendar.class);
System.out.println(cal1);
Date time1 = ac.getBean("time1",Date.class);
System.out.println(time1);
}
}
- 那到现在为止呢,我们就把这容器,创建对象的三种方式都说完了,那我们重点掌握第一种,无参构造器,这个一定要会用啊,后面两种只需要了解,不难吧。接下来我们看下面,一个新的问题啊,一个什么问题呢,说说这个作用域。
6. Spring容器中Bean实例的作用域
- 这个作用域,在spring这个框架里面,它指的是啊,容器对于这个bean,它到底会创建几个bean,一个还是两个,比如说我们刚刚,这个有什么,有一个bean叫stu1吧,那么容器会创建几个对象呢,就好像我给阿姨写了个什么,菜单啊,晚饭,水煮肉,阿姨,做几份,做一份,还是做两份呢,默认是做一份啊,说的非常对,容器啊,只会做一份,那么如果想做多份,你要怎么样啊,指定作用域啊,指定作用域,那我们这样吧,还是看个例子啊,虽然这个不难啊,我们还得写一遍,这样更有印象啊。
- 我们在这个
src/main/java
下面啊,我准备一下,因为我们有很多很多小的类啊,为什么有这么多类呢,主要目的啊,因为我们的知识点比较细啊,很多小的知识点,如果这么多小知识点,我都写在一个类里面啊,看起来比较累啊,所以我尽量呢,把知识点啊,放在什么不同的类里面,一个知识点,尽量什么,一个类来说明啊。我们在这个src/main/java
下面啊,新建一个java类,把它放到basic这个包下面,这个包名叫basic啊,类名呢,我叫Teacher,老师吧。 - 我们再建一个新的包啊,basic啊,底下有一个类啊,给它也增加一个无参的构造器啊,不带什么,任何的参数的,你也可以自动生成吧,会用么,按右键吧,Source,用过吧,以前,往下走是不是有一个,
Generate Constructors from Superclass...
,有么,有吧。ok,这样就快一点啊,写一遍也没有关系啊,构造器里输出System.out.println("Teacher()");
,构造器写完了,就这么多啊,这个已经写完了。
package basic;
public class Teacher {
public Teacher() {
System.out.println("Teacher()");
}
}
- 下面我们再增加一个spring的配置文件,怎么弄呢,你可以把这个applicationContext复制,按右键,看到没有,按右键copy吧,拷贝啊,然后,就在当前这个位置啊,在哪,在
src/main/resources
文件夹下面,粘贴可以么,这重名了吧,改个名字啊,我们叫basic,跟我们包名也一样啊,那为什么跟咱们那个包名一样呢,主要的目的啊,是为了方便看啊,basic这个类,跟我们这个什么啊,basic包下面这个类,跟我们basic.xml
配置文件有关系吧,方便对照源代码啊,分析啊,回顾啊。 - 这个配置文件叫什么,没有关系的,我们把这个
basic.xml
这个配置文件打开啊,把里面的之前的这些bean全部都删掉,不要了,又变成什么,一个干净的配置文件了吧,在这啊,我们来配置谁啊,Teacher,前面加个说明啊,指定作用域,我们先呢,添加一个bean元素,这个bean的id啊,我们叫t1吧,对应的java类,应该是basic包下面的teacher吧,写完之后啊,你再把类名包名,再列一遍啊。<bean id="t1" class="basic.Teacher "/>
,
- 现在我还没有指定作用域啊,我们看默认情况下,容器会创建几个对象,几个bean啊,一块来看看啊。大家以前有没有用过这个玩意啊,junit,一个测试工具,这个有用过么,用过啊,我们接下来用它来测吧,要不然那个main方法越写越大,没法看了,是吧,我们先导个包,把这个
pom.xml
文件打开啊,我们再导一个包,哪个包,junit吧,以前应该导过吧,就这个包,junit,我们用它来做测试,方便一点啊。
- 我们用junit的
4.12
,这个版本啊,最新的一个版本就行了4.12[jar]
(2017年),应该有吧,这个junit都导进来了,(这里选的版本3.8.2[jar]
)。我们就用它啊,来做测试,
- 我们在这个
src/test/java
文件夹下面,新建一个测试类吧,new一个java类,对吧,包名呢,我们叫test,类名啊,TestCase吧,就写一个普通的java类啊,把它放到test包下面啊。 - 我们在这个
src/test/java
下面啊,建了包test,类名叫什么TestCase,这我们增加一个测试方法,在测试方法前,我有一个注解吧,叫@Test
,然后我先启动spring容器啊,我们把刚刚这行代码再写一遍,String config = "basic.xml";
,这个是spring配置文件的位置吧,叫它了,改名了,叫basic了,是这个配置文件,然后我们启动spring容器,我们刚刚写过这么一行代码吧,再写一遍,ApplicationContext ac = new ClassPathXmlApplicationContext(config);
,具体代码如下:
package test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestCase {
@Test
//测试 作用域 和 延迟加载
public void test1() {
String config = "basic.xml";
//启动spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//通过容器获得bean
}
}
- 我们先启动spring容器,然后呢,我们通过容器,获得一个对象吧,获得bean啊,刚才我们说过,要通过容器获得bean,可以通过容器哪个方法,getBean方法吧,我先不调啊,这行代码我先不写,我先不获得,那么我想干什么呢,我先运行一下,这个test1方法。
- 那么容器启动以后,你看啊,我们按右键,一般这个测试工具啊,我们要测试某个方法,尽量呢,这样测比较好啊,因为有可能,我们这个类当中,测试方法很多是吧,有好多,为了这个方便啊,我们可以这样,把这个大纲搜出来吧,window下面有个
show view
吧,我们选哪一个呢,Outline,就大纲的意思啊,把这个Outline点一下啊。 - 我们看啊,test1吧,可以在这个test1上面按右键,run,走,走哪一个,运行哪一个,
JUnit Test
吧,很神奇吧,我有没有去那个,通过容器获取bean啊,Teacher()
,没有吧,结果呢,这个对象怎么样,帮我创建好了吧,有没有发现这样一个问题啊,我们并没有去getBean吧,结果这个Teacher()
,已经帮我们创建好了,有没有发现呢,几个,就一个吧。这是个小知识啊,我说一下啊,就是默认情况下,容器一启动,它会怎么样,把这个对象先创建好。
- 下面呢,我们通过容器获得bean啊,
Teacher t1 = ac.getBean("t1",Teacher.class);
,我再来一个Teacher t2 = ac.getBean("t1",Teacher.class);
,我想知道到底容器创建了几个,我怕你不放心啊,我说一个吧,你不信,输出一下,System.out.println(t1 == t2);
,如果输出的值是true,说明创建几个,一个吧,如果这个值是true的话,说明就创建了一个。
- true还是false走一下,true吧,true就一个啊。
- 如果我想让容器,创建多个怎么办呢,就每当我调一次getBean方法,就创一个新的,这个时候,我们就要指定作用域了,怎么指定呢,在这啊,我们加上这么一个属性,叫scope,这个scope啊,我们要把它的值设置为什么呢,prototype,这个prototype的含义是什么呢,写一下啊,它指的是啊,容器,这么说啊,每调用一次getBean方法,就会呢,创建一个新的对象啊。
- 那么这个scope属性,用来指定作用域,那么它的缺省值是什么呢,单例,singleton,是这个值啊,如果你没有用这个scope属性,那么缺省值是哪一个呢,这个啊,singleton单例,
<bean id="t1" class="basic.Teacher " scope="prototype"/>
,即只会创建几个,一个吧,只会创建一个对象,如果值是prototype呢,就会每调用一次getBean方法,就会创建一个新的对象啊,这个prototype怎么翻译,一般把它翻译成原型啊,但是这个含义,懂了就可以了,翻译叫什么,无所谓啊,一般把它翻译成原型。
- 我建议大家不用记啊,记这个就可以了,它的含义就是每调用一次getBean方法呢,就创建了什么,一个新的吧。那我们把这个作用域改为原型之后,我们再来把刚刚的那个方法测试一遍,都写完了,测试一下啊。
public void test1() {
String config = "basic.xml";
//启动spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//通过容器获得bean
Teacher t1 = ac.getBean("t1",Teacher.class);
Teacher t2 = ac.getBean("t1",Teacher.class);
System.out.println(t1 == t2);
}
--------------------------------------------------------------------------------------
运行结果:
七月 14, 2020 9:37:42 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5679c6c6: startup date [Tue Jul 14 09:37:42 CST 2020]; root of context hierarchy
七月 14, 2020 9:37:43 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [basic.xml]
七月 14, 2020 9:37:43 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@271053e1: defining beans [t1]; root of factory hierarchy
Teacher()
Teacher()
false
- 重新编译这个test1这个方法,我们看这个测试结果是什么,false吧,而且我们看到了,构造器调用了几次,两次吧,那么这个作用域,我们就说完了。
- 总结一下啊,这个作用域是什么意思呢,我们用这么两句话来说一下,第一句话,默认情况下啊,容器啊,对于每个bean元素啊,只会创建一个实例,就是我们在配置文件当中啊,一个bean元素对应一个对象吧,一一对应的,没问题吧,第二句话是什么呢,如果将作用域设置为哪一个啊,设置为prototype,则每调用一次getBean方法,就会创建一个新的实例吧,实例就是对象啊,我们换一种表示方式而已,作用域呢,这个知识并不复杂啊,平时一般我们都使用缺省值,缺省值是什么啊,单例吧,对应为这个就是它,singleton,用缺省值就好了啊。
7. Spring容器中Bean的延迟加载
- 下面呢,我们再看第5个啊,小知识。还是关于容器的啊,第5个,我们说说这个延迟加载,延迟加载也只需要了解啊。首先解释一下这个延迟加载是怎么回事啊,刚刚说了我们这么一个场景,我们启动容器以后,我并没有调getBean方法吧,结果呢,容器就把那个bean,把这个对象创建好了吧,但是这个有个前提条件,是什么呢,这个bean的作用域啊,得为单例。
- 刚刚说过作用域吧,作用域有两种是吧,一种是单例,一种是原型吧,有什么区别啊,单例就只会创建一个吧,原型呢,有多个是吧,到底几个,就看你调了几次吧,下面说延迟加载,先说这么一句话啊,就是默认情况下,那么容器启动之后,会将作用域为单例的啊,为singleton,它的bean创建好,要求作用域为单例,但是我们知道啊,我们在配这个bean的时候,如果不加任何的说明,它的作用域默认就是单例吧,对吧,那这样吧,我们还是再演示一遍啊。
- 就刚刚这个类,
TestCase.java
,不用动啊,现在它的作用域为原型吧,
- 还是这个test1,这个测试类啊,如果我把这两行注释掉,
Teacher t1 = ac.getBean("t1",Teacher.class);
Teacher t2 = ac.getBean("t1",Teacher.class);
System.out.println(t1 == t2);
- 加注释啊,这快捷方式冲突了是吧,手动注释吧,
- 我只是启动容器吧,我有调getBean方法么,没有调getBean方法是吧,
- 没有调,现在呢,我启动容器,控制台这边有反应么,没有吧,没有那个构造器被调用吧,只有作用域为什么,单例啊,比如说我改回来,把这个作用域改成什么,单例 ,不写也可以吧,不写也是单例是吧,
basic.xml
,这个样子。<bean id="t1" class="basic.Teacher " scope="singleton"/>
,然后再看,有么,Teacher()
,有吧,
- 刚刚我说了什么,默认情况下啊,容器启动以后呢,会将作用域为单例的bean啊,创建好。
- 接下来看,延迟加载是什么意思呢,延迟加载指的是,不是默认这么做了,怎么做,默认会这么干吧,会把这bean创建好吧,延迟加载指的是,作用域为单例的bean啊,不创建了,在什么时候创建呢,就当我们去调getBean方法的时候,才会创建,延迟加载指的是,容器启动之后,对作用域为单例的bean不再创建,直到什么呢,调用了哪个方法啊,getBean方法,才会创建。
- 那么,要想让容器去延迟加载,要怎么处理呢,要这么写啊,默认它延迟加载么,容器,不会吧,默认不会,默认是怎么样,将所有作用域为单例的bean创建好吧。现在呢,我想让容器不这么做了啊,所以要告诉它延迟加载吧, 要这么处理啊,简单的演示一下,我们在
basic.xml
这个配置文件里面,这个作用域仍然是单例的吧,我加上一个什么属性呢,叫lazy-init=“true”,即<bean id="t1" class="basic.Teacher " scope="singleton" lazy-init="true"/>
,
- 写一下啊,这个lazy-init,如果值为true啊,表示延迟加载啊,lazy翻译成中文是懒惰吧,init表示初始化啊,那么lazy-init,很形象吧,延迟加载是不是很懒啊,容器启动以后什么都不做吧,不会创建了吧,直到怎么样,直到什么呀,你找它要的时候,它才会创建吧,对吧。调哪个方法,getBean方法,它才会创建,配置好了吧,那配置好了以后,我们再来看看这个测试类啊,现在我仍然只是启动容器啊,我有没有getBean啊,没有吧,那么这时容器会不会把刚刚那个bean,这个Teacher()创建好呢,不会吧,因为我们指定了延迟加载吧,你看我们运行一下,走,有创建么,没有吧,没有看到构造器被调用吧,
- 什么时候才会调用呢,只有调getBean的时候,调这个getBean方法的时候,它才会怎么样,做(创建对象)这件事吧,延迟加载,我们只要了解下就行了,一般不用啊,一般我们用哪一个啊,缺省值吧,容器启动以后,先把所有的作用域为单例的bean创建好,这个是比较好的一种策略啊,用默认值就可以了,所以这个延迟加载我们只需要了解。
8. Spring容器中Bean的生命周期
- 那最后呢,我们再说第6个小知识啊,生命周期的两个方法,我们知道这个Servlet容器,有一个什么,所谓生命周期的管理的问题吧,容器需要什么,先创建Servlet吧,叫实例化,对吧,然后再怎么样,初始化吧,然后,在使用完这个对象之前呢,要先调哪个方法,销毁方法吧,有印象么,我们今天讲的这个spring容器,也有类似这样的机制,什么呢,spring容器啊,帮我们把这个对象啊,创建好之后,它也可以帮我们去调哪个方法,初始化方法,在容器关闭之前呢,它也会去调哪个方法,销毁方法。
- 那先看这个初始化方法,这个初始化方法是在什么时候执行的,是在spring容器创建好bean的实例之后,会什么呢,立即调用初始化方法,这个方法会执行几次啊,一次啊,跟Servlet一样。我们来看个例子啊,这个初始化方法应该怎么写,再增加一个java类吧,这个Teacher,我就不用动它了。
- 我们在这个basic这个包下面,再新建一个java类,这个类叫什么呢,我们叫MessageService啊,类名叫什么其实没有关系啊,包名叫什么,都可以的啊,我给这个类呢,增加一个无参的构造器啊,加构造器的目的呢,是让大家能看到啊,这个对象在什么时候创建的,因为默认的构造器啊,没有任何的输出吧,所以特意加这样一个无参构造器啊。再为这个类增加一个方法,这个方法名,我叫init吧,在这个方法里面,我添加一个输出语句,好吧,
public MessageService() {
System.out.println("MessageService()");
}
public void init() {
System.out.println("init()");
}
- 我现在的目标是什么呢,我想把这个方法做成一个初始化方法,就希望什么,容器啊,一旦把这个对象,把这个MessageService对象创建好以后,希望它怎么样,马上调这个对象的init方法,我有这样一个需求,那么呢,这个代码呢,怎么写呢,我们可以在spring的配置文件里面,做一些配置就可以了啊,让容器知道这是一个什么,初始化方法吧。
- 现在我们在spring的配置文件
basic.xml
里面啊,再给它增加一个配置吧,我们这得有个bean,我给它加上id啊,叫ms1,对应的java类,是在这个basic包下面吧,有一个MessageService,即<bean id="sm1" class="basic.MessageService"/>
,如果就写这么多的话,容器肯定会调它的无参构造器去创建这个对象,它会不会调init方法现在,不会调的,不会自动调的啊,你得告诉它,有一个初始化方法需要调用吧。 - 这个时候,我们可以在后面加一个什么呢,
init-method
,这个属性啊,那么这个init-method
这个属性的作用是什么呢,就是告诉容器啊,这个初始化方法的名字,加个注释说一下啊,这个init-method
,它是指定啊,初始化方法名,<bean id="ms1" class="basic.MessageService" init-method="init"/>
,
- 我在这个TestCase里面再增加一个新的测试方法啊,之前那个方法就不再用了啊,你可以在这加一个说明啊,test1,这是测试作用域和什么呢,延迟加载啊,因为这个延迟加载用的比较少,我们只需要了解,我就放在一个里面测试了啊。
- 下面我们看看这个初始化方法会不会执行啊,我们再另加一个什么,测试方法test2来测试,就加在下面吧,我们测试生命周期相关的两个方法啊,就是初始化方法和销毁方法,我们先测呢,测试这个初始化方法,那我们需要先启动容器吧,
String config = "basic.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
- 可以把启动容器这两行代码拷过来啊,不用再写了,这两行代码,我们复制一下就行了啊,把它放到test2方法里,接下来呢,我们去调这个容器的getBean方法,来得到一个MessageService这个对象啊,
public void test2() {
String config = "basic.xml";
//启动spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
MessageService ms1 = ac.getBean("ms1",MessageService.class);
}
- 就这么多啊,写完了啊,我们来执行一下这个测试方法,可以把这个大纲视图打开吧,选中这个test2这个方法,是吧,按右键,走,首先构造器是这样的吧,有没有发现这个初始化方法被执行了,对吧,
七月 15, 2020 10:47:13 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7791a895: startup date [Wed Jul 15 10:47:13 CST 2020]; root of context hierarchy
七月 15, 2020 10:47:13 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [basic.xml]
七月 15, 2020 10:47:13 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@43814d18: defining beans [t1,ms1]; root of factory hierarchy
MessageService()
init()
- 为什么会执行呢,是因为我们告诉它了吧,这边有一个什么,有一个初始化方法吧,看到没有,init这个方法名不要写错了啊。
- 如果方法名写错了,找不着了吧,
init()
,这是什么,方法名吧,所以这个方法的名字是可以改的吧,你不一定叫这个方法名,叫什么没关系啊,你只要明确告诉init-method
就好了吧。 - 然后,我们再看一个啊,销毁方法,我们给这个MessageService呢,再添加一个方法,我们叫destroy,这个destroy方法啊,里面也添加一个输出语句,看看有没有执行,那同样,这个销毁方法呢,我们也需要指定它啊,在配置文件中告诉容器哪一个是销毁方法,那我们修改一下这个配置文件,再增加一个属性叫
destroy-method
,即<bean id="sm1" class="basic.MessageService" init-method="init" destroy-method="destroy"/>
,
- 那这个销毁方法,在什么时候执行呢,我们说初始化方法,是在对象创建好了以后,会立刻执行吧,那么销毁方法是在容器关闭之前,容器关闭之前会干什么,会把它所管理的这个对象销毁,对象被销毁之前呢,就会执行这个对象哪个方法啊,销毁方法吧,对吧,那有没有想过这样一个问题啊,为什么Servlet有生命周期,spring还有生命周期的问题,这个生命周期,这个初始化方法啊,销毁方法啊,到底做什么用的啊,对,管理资源啊。
- 打个比方,我们公司要招人,我们可以把公司想象是一个容器,那么你到了公司入职了以后呢,就相当于容器里面多了一个对象吧,这叫什么实例化,那么你想想,公司把你招进来以后,它会不会马上派你出差啊,不会吧,它会怎么样,给你分配些资源,比如说啊,第二天啊,给你发个笔记本啊,笔记本电脑,有可能吧,或者给你发一个什么,工卡,饭卡,给你分配个工位,是吧,甚至还可能进行一些企业化的培训吧,洗洗脑是吧,有可能吧。
- 然后呢,第3天,第4天,让你出差,这叫什么初始化吧,出差叫什么啊,调用是吧,等你哪一天要离职了,对不起,要什么,上交资源吧,你的笔记本要上交,所以资源的分配就是初始化吧,资源的回收就是销毁,而且销毁也好,分配也好啊,初始化,它只执行几次啊,一次吧,你离职的时候,要上交几次笔记本呢,一次,当然你入职的时候,也会给发一个笔记本吧,是吧,这个初始化,销毁啊,这个生命周期的管理啊,在很多技术当中都有体现的,都会用到的,所以只要理解到这个程度就非常好了啊。
- 那么这个销毁方法呢,我们指定好了以后啊,要想看到这个销毁方法被执行,我们需要把容器关掉吧,要把spring容器给它关了啊,怎么关呢,要把容器关掉,我们才能看到销毁方法是吧,ApplicationContext这个接口当中,它并没有定义这个关闭容器的方法啊,这个
ac.close();
,为什么报错,不认识,是因为这个ApplicationContext接口当中,有没有close这个方法啊,没有啊,
- 怎么办呢,我们可以这样做啊,这样处理,为了关闭容器啊,我们用它的子接口就行了啊,这么写,AbstractApplicationContext。
- 这个玩意是谁呢,你可以这么理解啊,这个ApplicationContext接口当中并没有提供什么呢,关闭容器的方法,谁啊,close方法啊,没有提供,需要什么呢,用其子接口,用它啊,AbstractApplicationContext来声明,这个有没有close方法,有啊,
- 那么,这个ClassPathXmlApplicationContext,这个类呢,它既实现了父接口当中的方法,也实现了什么,子接口当中的方法吧,所以现在要用这个子接口当中的方法,这个类型应该怎么写,就应该用AbstractApplicationContext吧,这里面有吧,这是面向对象的基本知识啊。
AbstractApplicationContext ac = new ClassPathXmlApplicationContext(config);
MessageService ms1 = ac.getBean("ms1",MessageService.class);
//关闭容器
ac.close();
- 那关闭容器之前,它都会调哪个方法,调那个destroy啊,我们试试看,测试一下啊,我们把这个test2这个方法执行一遍,有没有发现这个destroy方法被执行了啊,对吧,容器关闭之前啊,会调这个destroy方法啊,
- 还有一个小细节给大家提醒一下,这个销毁方法啊,还有一个前提条件啊,容器要关闭,这是肯定的,还有什么呢,要求这个bean的作用域,必须是单例的才起作用啊,比如说,我现在故意的啊,把他的作用域改成原型,
scope="prototype"
,把作用域改成原型了吧,那么这个销毁方法就会失效。 - 再来一遍这个test2这个方法,再执行一遍,走,有没有看见这个销毁方法啊,
- 没有吧,没有输出,因为什么呢,这个销毁方法啊,要求这个bean的作用域必须是单例啊,必须是单例。那么这个销毁方法要求呢,作用域,或者这么说啊,只对作用域为单例的bean起作用,或者有效,是吧,一个意思啊。
三、完整代码如下:
(1) spring01/Java Resources/src/main/java/basic/Teacher.java:
package basic;
public class Teacher {
public Teacher() {
System.out.println("Teacher()");
}
}
(2) spring01/Java Resources/src/main/java/basic/MessageService.java:
package basic;
public class MessageService {
public MessageService() {
System.out.println("MessageService()");
}
public void init() {
System.out.println("init()");
}
public void destroy() {
System.out.println("destroy()");
}
}
(3) spring01/Java Resources/src/main/resources/basic.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:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
<!-- 指定作用域 -->
<!--
scope : 用来指定作用域,缺省值是singleton(单例,即只会创建一个对象),
prototype : 每调用一次getBean方法,就会创建一个新的对象。
-->
<!-- <bean id="t1" class="basic.Teacher " scope="prototype"/> -->
<!-- <bean id="t1" class="basic.Teacher " scope="singleton"/> -->
<bean id="t1" class="basic.Teacher " scope="singleton" lazy-init="true"/>
<!-- 制定初始化方法 -->
<!--
init-method : 指定初始化方法名。
destroy-method:指定销毁方法名。
注意:销毁方法只对作用域为singleton的bean有效。
-->
<bean id="ms1"
class="basic.MessageService"
init-method="init"
destroy-method="destroy"/>
</beans>
(4) spring01/Java Resources/src/test/java/test/TestCase.java:
package test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import basic.MessageService;
import basic.Teacher;
public class TestCase {
@Test
//测试 作用域 和 延迟加载
public void test1() {
String config = "basic.xml";
//启动spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//通过容器获得bean
/* Teacher t1 = ac.getBean("t1",Teacher.class);
Teacher t2 = ac.getBean("t1",Teacher.class);
System.out.println(t1 == t2);
*/ }
@Test
//测试 生命周期相关的两个方法
public void test2() {
String config = "basic.xml";
//启动spring容器
/*
* ApplicationContext接口当中并没有
* 提供关闭容器的方法(close方法),
* 需要用其子接口
* AbstractApplicationContext
*/
AbstractApplicationContext ac = new ClassPathXmlApplicationContext(config);
MessageService ms1 = ac.getBean("ms1",MessageService.class);
//关闭容器
ac.close();
}
}
(5)目录结构如下:
四、总结:
- Spring是一个开源的,用来简化企业级应用的开发框架,简化指的是Spring对一些常用的API做了封装,比如SpringJDBC;同时Spring不仅可以简化开发,还可以帮我们管理对象,以及对象之间的依赖关系,从而实现了对象之间的解耦。比如对象A要调用B对象中的方法,则需要在A类中new一个对象,A与B产生了直接的依赖关系,耦合性太高,Spring可以就解决这个问题;还有,Spring可以帮我们集成其他的一些框架。一句话概括就是,简化开发,接偶和集成。
- 然后,Spring容器是Spring框架中的一个核心模块,用来管理对象,即管理对象的创建和销毁等,以及对象之间的依赖关系。单个对象的管理而言,具体有对象创建,初始化和销毁,以及作用域,延迟加载等;多个对象来说,比如管理两个对象之间的关系。
- Spring容器管理对象的具体操作,我们需要一个对象,可以在Spring的配置文件里面添加一个bean元素,然后Spring容器就会自动帮我们创建这个对象,而Spring容器创建对象有3种方式。
- 第一种利用无参构造器创建对象,分为3小步,首先,添加无参构造器或使用缺省构造器;然后在Spring配置文件中添加配置如下:
<bean id="" class=""/>
,id要求唯一,class为这个bean的全限定类名,即完整的类名+包名。因为Spring容器底层用的是java反射机制来创建的,而java反射需要依据类的全限定类名,去找这个类的字节码文件,完成类的加载,这个反射的过程这里点到为止,所以要求类名要写完整,如果写的不完整,或者写错了,它就没有办法找到这个类的字节码文件,没有办法去创建这个对象;最后第三步,我们配置好Spring的配置文件,就需要启动Spring容器,通过调用容器的getBean方法,方法里面指定bean的id和类型,指定了bean的类型,我们就不需要手动强转了,比如getBean(id,Student.class);。下面两种方式: - 一种是静态工厂方法,它指的是调一个类的静态方法,来创建这个对象吧,怎么做的呢,首先,我们要怎么配置,bean标签,属性id,然后是class,接下来是factory-method,这个factory-method属性,这个值是一个方法名吧,这个方法要求是一个静态方法,
<bean id="" class="" factory-method=""/>
,那么,整个这个配置的含义是什么呢,就让容器去调这个类的静态方法,来创建这个对象吧,我们写了一个,java.util
下面的一个Calendar类中,然后呢,有一个getInstance静态方法,就相当于我们写的那行java代码吧,Calendar.getInstance()
,它会返回一个对象吧,一个什么对象,一个Calendar对象吧,<bean id="cal1" class="java.util.Calendar" factory-method="getInstance"/>
。 - 第3种方式叫实例工厂方法,它指的是调一个对象的实例方法,来创建这个对象,配置
<bean id="" factory-bean="" factory-method=""/>
,bean标签,属性id,后面是factory-bean,这个factory-bean指的是什么,bean的id,我们说的一个bean,指的就是容器所管理的对象,这个对象是容器创建的吧,就是个对象,只不过这个对象是由容器帮我们创建的,然后是factory-method,指定一个方法吧,它的含义是什么,调上面这个对象啊,这个factory-bean属性指定的这个bean对象,它的这个实例方法,来创建这个对象吧,<bean id="time1" factory-bean="cal1" factory-method="getTime"/>
。
五、问题分析
- 那这里有个疑问,如下,
<bean id="cal1" class="java.util.Calendar" factory-method="getInstance"/>
<bean id="time1" factory-bean="cal1" factory-method="getTime"/>
- 问这个实例工厂方法,跟静态工厂方法有没有关系啊,有没有什么依赖性,没有任何关系啊,就
applicationContext.xml
这个配置啊,<bean id="time1" factory-bean="cal1" factory-method="getTime"/>
,这个配置的含义是什么,调这个cal1对象的,这个实例方法吧,对吧,它跟上面的这个静态工厂方法,有关系么,<bean id="cal1" class="java.util.Calendar" factory-method="getInstance"/>
,没任何关系吧,因为他正好看到了什么,上面这个id,跟下面这个id一样,就以为呢,实例工厂方法跟静态工厂方法有关联,它没有关联的吧,这个实例工厂方法可以依赖任意一个bean。 - 比如,我随便来个bean,
<bean id="aaa" class="java.util.Calendar"/>
,后面factory-method
,我不写了,我实例方法这边factory-bean
可不可以写aaa啊,<bean id="time1" factory-bean="aaa" factory-method="getTime"/>
,可以吧,现在是调用什么,调用id为aaa这个对象的,这个实例方法吧,跟静态方法有关系么,没有关系,因为这个cal1,还是aaa最终是什么的东西啊,就是一个对象吧,人家怎么来的,用管么,不用吧。
参考文献(References)
1..
2..
3..
4..
5..
3..
文中如有侵权行为,请联系me。。。。。。。。。。。。。