Spring(上)

发布于:2024-04-22 ⋅ 阅读:(190) ⋅ 点赞:(0)

一.引言

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工厂及配置文件为其赋值

好处:解耦合


网站公告

今日签到

点亮在社区的每一天
去签到