一.引言
1.EJB(Enterprise Java Bean)存在的问题
运行环境苛刻
运行环境就是所谓的服务器,我们常用的就是Tomcat。Tomcat最核心的部分就是Servlet引擎这个组件,所有的Tomcat最核心的功能都是交给Servlet引擎来完成,那这样Tomcat就能让我们的代码处理web相关的操作,所以我们也将Tomcat称为webServer,即web服务器。
但是EJB代码必须运行在EJB容器中,而不能在Servlet引擎,但Tomcat没有EJB容器。有一个叫Weblogic的服务器,可以实现Tomcat的所有功能,当然也包含了Servlet引擎,但同时它又增加了EJB容器。我们常把这个服务器称之为ApplicationServer。但是它是收费的,是闭源的。除他之外,还有一个Websphere服务器,也有Servlet引擎和EJB容器,但也收费。
所以说EJB运行环境苛刻,造价高昂
代码移植性差
当EJB运行在Weblogic里面时,就要实现Weblogic提供的接口;但要是想运行在Websphere中,就又要重新实现Websphere的接口,所以说代码移植性差
总结
EJB是一个重量级框架
2.什么是spring
spring是一个轻量级的JavaEE的解决方案,整合了众多优秀的设计模式
轻量级
对运行环境没有额外要求,可以运行在开源免费的服务器,也可运行在闭源收费的服务器,在服务器的Servlet引擎中就可以运行。
代码移植性高,不需要实现额外的接口。
JavaEE的解决方案
JavaEE开发是分层开发:Controller->Service->Dao->DB。
我们常说的struts2,Mybatis等这些框架,都只能完成开发中的某一层,比如struts2主要是解决Controller控制器问题,Mybatis主要是完成Dao问题。
而spring最大的优势在于它可以解决分层开发中的每一层。比如控制器问题,有springMVC;spring中的AOP技术,可以解决Service中的事务控制、日志处理技术;还可以和Mybatis整合,解决Dao问题……
整合了设计模式
1.工厂设计模式
2.代理设计模式
3.模板设计模式
……
3.设计模式
广义概念:
设计模式就是面向对象设计中,解决特定问题的经典代码
狭义概念:
GOF四人帮所定义的23种设计模式:工厂,适配器,装饰器,门面,代理,模板……
4.工厂设计模式
概念:
通过工厂类创建对象。
这种模式下,不是通过new来创建对象
好处:
解耦合。
耦合指的是代码间的强关联关系,一方的改变会影响到另一方。不利于代码的维护。
就比如:
public class Controller{
UserService us=new UserServiceImpl();
}
这个代码中,我们用UserService这个接口实现的是UserServiceImpl这个实现类,但要是某一天不想用UserServiceImpl这个类来实现接口了,就要修改代码,就导致Controller这个类也受到影响(其他使用到Controler类的代码也要跟着修改),然后就会要重新编译重新部署。
简单理解为把接口的实现类硬编码在了代码中。
简单工厂的设计:
这是项目结构,
首先是一个UserService接口
接着是它的实现类
然后是UserDao接口
还有一个是他的实现类,用于完成数据库操作
首先用传统方式测试一下:
我们来仔细分析一下,就发现new UserServiceImpl这里出现了之前所说的耦合问题,所以这时候就需要工厂类来创建UserService对象。还是在basic包中创建一个BeanFactory类:
然后将测试类中的代码改为:
这样,test中的耦合就解决掉了。但是,有人会说,BeanFactory中不也就有了耦合了吗?下面就来解决这个问题
简单工厂的关键:反射
创建对象不仅仅可以通过new,也可以通过反射创建对象。这似乎可以解耦合
那这样真的可以解耦合吗?在获得class对象时,我们还是传入了这个类的全限定类名,后续要是有一个新的实现类,我们还是需要去修改代码,最后还是需要进行重新编译和部署。这样其实还是有耦合。
不过这个还是有办法解决,我们可以将有耦合的这个代码放到一个小的配置文件中,后续想要修改,就只要修改这个配置文件即可,就不会进行重新编译部署了。
我们一般是将它放到一个.properties文件中。要将这个文件放到resources这个目录中。然后在这个文件中书写可能出现耦合的字符串。这个文件是一个key value形式的文件,如下:
那如何在代码中引用这个字符串呢?
spring提供了Properties集合,该集合存储的就是properties文件中的key和value。Properties集合其实就是一个特殊的Map,特殊在哪里?它里面的key和value必须都是String类型。
那么我们就要在BeanFactory中创建一个Properties对象,并让它存储.properties文件(其实就是让它读取文件。读取文件就涉及到了文件IO。而文件操作的输入流输出流是一个重量级资源,它们最好在代码中只有一份,所以我们将它封装成BeanFactory的一个静态成员,对他的操作就在静态代码块中完成:
如上,先获取输入流,再让properties区这个输入流中加载对应的内容(也就是读取其中的内容),最后通过getProperty方法读取到那个全限定类名。
最后测试一下。至此,UserService就再也没有耦合了。有朝一日,我想换一个实现类,只要修改文件即可。
现在,我们再来看一下这些类,会发现UserDao这里也有耦合:
那我们也可以用上述方法来处理这个耦合
测试一下发现完全可以,至此,简单工厂的设计就完成了。关键就是通过反射创建对象,并将有耦合的代码放到那个小的配置文件中
通用工厂的设计
简单工厂代码有一些冗余,日后要是需要获得100和不同的对象,就给写100个get方法,但是里面代码的结构是相同的。所以我们应该想一个办法将这些冗余代码放到一个方法中,日后只需要一个方法就可以得到所有的对象。
看上面的代码,俩个get方法有共同之处,也有不同之处:首先方法修饰符都是public static;返回值就是要创建的对象,所以在通用工厂中应该返回Object类型;方法名随便,我们就称他为getBean;那么参数是什么呢?我们的目的是要创建对象,但要知道创建什么对象,这个就要通过小配置文件中的key来区分了,所以参数应该是个String类型,放的就是小配置文件中对应的key,传入不同的key就能得到不同的对象:
如上,直接都改为getBean方法,并传入对应的参数即可。这样,使用一个方法,就可以创建一切想要的对象了
通用工厂的使用方式
1.定义类型
2.通过小的配置文件(applicationContext.properties)告知工厂有这样一个类
3.通过工厂获得类对象,参数就是key
比如想要有一个Person对象:
1.准备Person类
2.告知工厂
3.获取对象
成功获取到了Person对象,同时也没有耦合
5.总结
通过上面的讲解,我们就了解了工厂设计模式
那么,我们接下来要学习的Spring,最本质的东西就是工厂。它的工厂有一个专门的实现类ApplicationContext,并且有一个专门的配置文件:applicationContext.xml
二.第一个Spring程序
1.软件版本
jdk 1.8+
Maven 3.5+
IDEA 2018+
Springframework 5.1.4
2.搭建环境
1.Spring的jar包:通过pom.xml引入依赖即可
2.Spring配置文件
配置文件放置的位置:没有硬性要求,我就放在resources目录下
配置文件命名:没有硬性要求,但一般是applicationContext.xml
创建好后,就会自动生成如下信息:
3.Spring核心API
ApplicationContext
这是一个接口
作用:作为Spring工厂,用于创建对象
好处:解耦合
为什么要使用接口:为了屏蔽实现的差异,在不同环境下的使用会有差异
实现类:
非web环境下是ClassPathXmlApplicationContext(主要是main函数和junit测试,这时是不需要服务器的)
web环境下是XmlWebApplicationContext
我们来看一下这个接口的实现类,把鼠标放在这个接口上,点击ctrl+h,就可以看到它的继承关系。但是没有发现XmlWebApplicationContext,这时因为我们没有引入web环境下的依赖Spring Web。引入后就都可以看到了:
重量级资源
一个对象,占用内存比较多,我们就把他叫做重量级资源。
既然是重量级资源,就不能总是new,每一个应用只会创建一个工厂。
但是一个工厂会被多个地方使用到,所以它一定是线程安全的。
4.程序开发
它的使用和我们涉设计的通用工厂的使用是一样的
1.实现类 2.配置文件配置 3.获得对象
创建类型
我们还是使用Person类
配置文件
如上,添加一个bean标签,id属性,就是一个名字,id必须是唯一的,后续会根据id来获取对象;class属性是去配置要获取的对象的全限定类名
通过工厂类获取对象
注意,这是junit单元测试,所以使用ClassPathXmlApplicationContext实现类。注意传入到参数是配置文件的文件名,因为我们要告知Spring工厂去哪里获取对象。最后调用getBean方法,传入到参数是我能在文件中指定的那个id
5.细节分析
bean:
Spring工厂创建的对象,就叫做bean/组件
getbean重载以及类似方法:
我们也可以在getBean中传入类对象,即person.class,但前提是配置文件中只有一个bean标签是Person类的
但如果配置文件中有两个person类,就不能使用Person.class了。如下:
抛出了异常
还有一个方法:getbeanDefineName,返回的是id的集合(在配置文件中的bean标签就相当于是一个bean定义,id就是这个bean标签的名字)
还有一个方法是getBeanNamesForType(),这是根据类型来获取id
还有一个是containsBeanDefinition,判断是否存在指定id的bean
还有一个是containsBean,暂时同上(会面会讲到两者的区别
配置文件
只配置class属性,也可完成对象的创建
此时有默认的id值吗?是有的,我们可以通过getBeanNamesForType来观察
应用场景:这个bean只使用一次
name属性:给bean定义别名
1.这个别名可以不唯一,而且可以通过别名获取对象
2.一个bean标签的别名可以有多个,中间通过逗号进行分割
3.containsBeanDefinition()的参数只能是id;但containsBean的参数既可以是id又可以是name
6.Spring工厂底层实现原理
实际是通过反射创建对象,它是会调用该类的无参构造方法的。这怎么证明?写一个无参构造,并随便打印一句话,看看是否在控制台中体现
即使这个无参构造是私有的,也能构造出person对象,因为反射可以调用私有的构造方法
三.Spring与日志框架的整合
导入以下jar包
然后创建一个log4j的配置文件log4j.properties,在里面输入以下内容:
四.注入
1.什么是注入
注入就是通过Spring工厂及配置文件,为所创建的对象的成员变量进行赋值
为什么需要注入?
使用set方法进行初始化会有耦合,只要代码一修改,就得进行重新编译。也就是说,通过代码队成员变量进行赋值会有耦合
如何注入?
在一个bean标签内部创建新标签。示例如下:
首先提供两个属性及其get和set方法,然后进行如下配置
一个成员变量对应一个property标签,name属性就是成员变量的名字,value标签就是成员变量的值
好处:解耦合
2.注入原理
首先要明白,Spring一定是操作配置文件,将配置文件译成代码。
Spring是通过底层调用对象的属性对应的set方法。来完成对成员变量的赋值。所以当把set方法删除后,注入赋值就会失败。
这种输入也称为set注入
五.注入详解
注入的成员变量类型不同,property标签内部的赋值标签就不同
1.JDK内置类型
String+8种基本类型
均使用<value>xxx</value>
数组
例如我们给person添加一个emalis成员,用数组存储:
外面就是list标签,里面是什么标签就看每个元素是什么类型,如果每个元素也是数组,那么就要嵌套list
set集合
插一下:set集合的遍历:
list集合
插一句:list集合也可以如下遍历,拿到每一个元素:
map集合
注意,map是键值对形式,一个键值对在map中是以一个entry对象存储的,所以map标签中要嵌套entry标签,然后对于一个entry,它的key要用key标签,key的赋值标签则要看key是什么类型,如果key是String以及八种基本类型,就用value标签,如果是数组,就用list标签;接着是一个entry中的value,也就是上图中entry标签内部的第二行,如果value是String类型,就用value标签,如果是set集合,就用set标签
map集合的遍历:
或者:
properties类型
peoperties是特殊的map,他也是keyvalue模型,只不过这两个都必须是String类型
每一对keyvalue就是一个prop标签,key属性就是键值对中的key,prop标签内部就是value
复杂的JDK类型
例如Date类型,需要程序员自定义类型转换器。后面会讲解到
2.用户自定义类型
比如这个UserserviceImpl中有一个成员变量是UserDao类型,那这个怎么赋值呢?
方式一.
其实就是new一个UserDaoIml对象,所以也要用到bean标签
方式二.
方式一会有代码冗余:因为可能会有10个UserService对象都要用到UserDao,但一个bean标签代表一个对象,所以就要有10个Bean标签,对应了10个UserDao对象,这很明显是浪费JVM内存资源的
那我们就使用方式二
先创建一个UserDao对象,然后在需要这个对象的类中加ref标签,其中的bean属性对应的就是UserDao对象的id
3.set注入的简化写法
基于属性简化
jdk类型简化
就是把value标签删掉,给properties标签加一个value属性。
注意:这个value标签只能简化String加8种基本类型。
用户自定义类型简化
六.构造注入
就是Spring调用构造方法,通过Spring配置文件,为成员变量赋值
1.步骤
提供有参数的构造方法,进行文件配置
如下:
用到了constructor-arg标签,每一个标签代表了有参构造的一个参数,且要一一对应
2.构造方法重载
构造方法参数个数不同
此时,constructor-arg标签有几个,就使用哪个构造方法
构造方法参数个数相同但类型不同
只要在constructor-arg标签内部加上一个type属性即可
构造方法参数个数相同且类型相同
这是不可能出现的,因为方法的重载没有这一种
七.反转控制与依赖注入
1.反转控制(IOC Inverse of Control)
控制:对于成员变量赋值的控制权
反转(转移):把对于成员变量赋值的控制权从代码中反转转移到Spring工厂和配置文件
好处:解耦合
2.依赖注入:
注入:通过Spring工厂及配置文件,为对象的成员变量进行赋值
依赖:对该成员有很大作用。比如UserService要操作数据库,就要依赖UserDao。所以就要将UserDao作为UserService的一个成员变量,进而通过Spring工厂及配置文件为其赋值
好处:解耦合