spring+spring_mvc

spring

spring框架是Java程序员使用的最常用的框架,也是功能最强大的后端框架,所以需要程序员对于spring框架具有全面的了解

简单介绍

spring实际上就是一套解决方案,企业级开发中的一些问题,spring提供了解决思虑,并提供了解决的具体方法

spring最大的目的就是让javaEE开发的容易一些,spring的理论部分实际上是根据Rod jahanson写的一个<< Export One-on-One J2EE Design and Development>> 2004年的三月份发布的1.0版本

spring的轻量级,开源的javaEE解决方案

  1. 轻量级 指的是spring的jar包比较小
  2. 运行环境没有要求
  3. 代码移植性高
  4. 非侵入式设计

涉及到java开的的各个层次

image-20240115105846207

Spring中的 工厂模式

工厂模式就是我们的业务代码向工厂申请一个对象,而不是业务代码自己去new一个对象,如果是new对象,那么从一个刚刚出生的基本对象,到业务代码所需要的对象,还需要一些列的操作,我们的工厂就提供了对象从出生到可以直接上岗的一些列操作

对于业务代码来说,向工厂要人,只要求了这个人能够干什么工作,而这个人的具体信息就被省略了,这样工厂可以给这个业务代码不同的院校毕业的人,这样业务代码的动态性就更强了,随时可以换院校

从软件工程的角度看,这个就是生产和使用解耦了,通过唯一的接口进行控制耦合

所以实际上spring的工厂模式就是为了将对象的生产给承包了,将业务逻辑代码中的生产代码全部剔除,由spring来负责,这样业务逻辑代码就更清晰了

二次理解

工厂就是对于 对象 生成的高度重用

工厂实现要解决的问题

<><>首先的一个问题是,接受业务逻辑代码的请求参数,判断返回哪一类对象

我们可以通过字符串,枚举变量,固定的函数来为业务逻辑代码提供选择

最为通用的无疑是使用字符串来表示,这样可以表示任意一个类,但是,类全名实在太长了,可以搞一个映射表来缩短一个类的具体名称

<><>第二个问题就是,要生产这个类,我们得先找到这个类的构造方法,选用一个构造方法(是使用构造方法,还是使用setter来构造一个对象)进行构造,这个时候进行初始化也是最好的时候…

我们需要定位到这个类的class信息,这个信息我们可以通过类的全路径名找到

如果找不到这个类我们就无法生产

<><>第三个问题就是,我们这个对象如何培养才能毕业?

如果要求不高,我们可以自己提供几种毕业路径,供业务代码选

更好的是由业务代码自己去给出毕业路线,工厂根据路线,对对象进行培养再交给业务代码

工厂的具体实现

第一步 做个类名映射表

让业务代码只需要简单的字符,表示我们需要的参数,我们从映射表中找出具体代表的类名返回

第二步 做个构造方法的初始化方案

这个方案主要就是传入构造的参数

或者通过set方法来初始化一些字段的值

第三步

spring中的设计

实际上这个功能是spring的核心功能,也就是其他spring组件都需要使用到这个功能

需要在maven的pom.xml文件中添加依赖,叫做spring-context

context的介绍

context被翻译成上下本环境

这个环境很好的表达了这个意思,就像是环境变量一样,一直存在这个域中,生命周期也你的业务代码要长

所以业务代码实际上是在这个环境之中的,业务代码可以直接获取环境中的变量

  1. spring中使用一个配置文件,提供了映射类名,初始化配置等等配置功能

bean.xml模版要使用spring-config

image-20240115124228282

  1. 项目模块中,在对应模块中添加spring配置文件位置设置为自己配置的bean.xml

使用spring中的工厂模式

spring工厂的核心是:

  1. 工厂配置文件 bean.xml
  2. BeanFactory 工厂接口,静态方法
  3. ApplicationContext 接口 实际上继承了BeanFacroty接口,比其功能更强大
ApplicationContext实现类

这个接口实际上使我们使用比较多,功能比较强大的一个生产对象的工厂

这个接口实际上有很多的实现类,

比较常用的就是类路径和xml的应用上下文环境,这个就是通过类路径参数,和xml文件进行工作的工厂

也就是 ClassPathXmlApplicationContext

<><>核心方法:

  1. getBean(“yourname”) :Object
  2. 这个方法有很多重载形式

例如getBean(Class<?> aClass) 这个是根据配置中的class信息来寻找对象,实际上一个类可能有很多的bean对象,这种情况会报错,所以最好使用下面这种

  1. getBean(“yourrname”, aClass)

这种相当于就是比第一种直接传入name的多了一个自动的类型转换

这个参数字符串就是我们的映射键名,这个映射自己在配置文件中去设置

注意返回的类型是Object,需要自行强制转化

<><>核心配置:

  1. 配置一套某个类的毕业流程

所以一个类可以配置多个毕业流程,通过不同的id,name来区分

想要在factory中找到一个类,我们就需要配置这个类

在bean.xml中配置一个需要生产的类通过固定的标签

1
<bean id="userDao" name="userDao" class="com.wzy.Main" ></bean>

这就是一个类的配置

需要注意,实际上工厂接受的是业务传递的name参数,并不是id参数,所以工厂是按照name来寻找类的,而不是id,如果没有设置name的话,会自动设一个与id同名的name

  1. 初始化类 的具体配置

初始化类有两种途径:
1 通过构造方法初始化,这种初始化是最早的初始化,不过问题就是初始化收到构造方法的影响,构造方法怎么写,那么我们也只能这样初始化

2 通过set方法实现初始化,set方法先怎么初始化就怎么初始化,但是问题是有些字段没有set方法

在配置文件中,初始化标签的语法是这样的

1
<property name="这个字段用于寻找set方法,而不是字段" value="字符串,在传参的时候会进行paser转换"></property>

ClassPathXmlApplicationContext的策略 – 单例模式

spring和java开发者想的是一样的,认为我们大部分的数据都是可以同时给一些应用复用的,没必要创建新的对象

java所以默认是引用的,想要创建新的使用clone方法来完成

而spring工厂默认取得的也是工厂一开始就创建好的对象,而不是你去调用就新创建一个对象来给你

上下文环境在创建这个上下文环境对象的时候,就会自动根据配置文件生成对应的对象,然后全部放在一个Map中,到时候业务代码去申请对象,申请的就是这个map中现成的对象的引用

如果别的也传入同样的参数,那么获得的也是哪一个对象的引用

所以这个就有一个并发问题

每个ApplicationContext实例都会维护一个自己的Map不与其他实例共享

工厂模式的初始化技术

由于我们需要初始化的可能不只是一个基本数据类型,根据我们的 <property标签中的value传递的是一个字符串就可以知道,实际上能够传递的值有限

所以需要一种技术来解决初始化非基本数据类型,也就是对象类型的技术(还有数组,集合等结构化数据)

控制反转 Spring Ioc 以及 依赖注入 DI

这个是spring的核心,也是初始化技术的核心

Ioc 全称就是Inverse Of Control 控制反转

实际上我认为这个翻译并不好,叫做 对象初始化统一管理还差不多

DI依赖注入实际上是控制反转的一种实现手段,依赖注入就是把别的对象传入到另一对象内部,也就是一个对象依赖于另一个类的对象,我们再通过自己的初始化方案自动生产初始化一个对象出来传入这个对象即可

我认为依赖注入还不如叫做 引用对象方案 还差不多

配置文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 具体做法就是 我们配置一个类 然后初始化就是引用一个我们配置的 对象方案 -->
<bean id="girl" class="com.wzy.Gril">
<property name="name" value="default"></property>
<property name="height" value="170"></property>
<property name="weight" value="60"></property>
</bean>
<bean id="boy" class="com.wzy.Boy">
<property name="name" value="default"></property>
<property name="height" value="170"></property>
<property name="weight" value="60"></property>
</bean>
<!-- 直接引用上面定义的一个对象方案 -->
<bean id="parent_default" class="com.Parent">
<!-- 注意是 ref 属性 而不是 value属性 -->
<property name="wife" ref="girl"></property>
<property name="husband" ref="boy"></property>
</bean>
<bean id="parent_reverse" class="com.Parent">
<!-- 注意是 ref 属性 而不是 value属性 -->
<property name="wife" ref="boy"></property>
<property name="husband" ref="girl"></property>
</bean>

id与name的区别

id可以唯一的区分一个bean,一个配置方案

实际上就是保存在Map中的名字

而name是映射到Map中的一个key,可以有多个name映射到同一个id上

name的值语法是这样的

name=”name1,name2,…,…”

也就是通过逗号分隔不同的name

属性注入

实际上还是属于工厂模式中的一部分,也就是属性配置语法

数组和List类型的注入

1
2
3
4
5
6
7
<!-- 也就是property的子标签有变化 -->
<property name="setname" >
<list>
<value>具体值,是字符串类型的</value>
<value>具体值,是字符串类型的</value>
</list>
</property>

set类型的注入(加上 非基本数据类型的成员类型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 也就是property的子标签有变化 -->
<property name="setname" >
<set>
<value>具体值,是字符串类型的</value>
<value>具体值,是字符串类型的</value>
</set>
</property>
<!-- 如果内容并不是基本数据类型 -->
<property name="setname" >
<set>
<bean class="classpath" >
<property name="setname"> ...</property>
</bean>
</set>
</property>
<!-- 也可以引用外部定义好的 -->
<property name="setname">
<set>
<ref bean="id"></ref>
</set>
</property>

如果不需要引用的话,就不需要为bean标签设置id

如果需要引用,使用即可

Map类型

1
2
3
4
5
6
<property name="somename">
<map>
<entry key="keystring" value="String value"></entry>
<entry key="keystring" ref="bean_id"></entry>
</map>
</property>

构造方法注入

相比于使用set系列方法注入,构造方法可以为一些没有set方法的字段赋值,但是受到构造方法本身参数限制

1
2
3
4
5
6
7
8
9
<!-- 默认根据顺序走,也可以设置type属性,用来确定使用哪一个constructor而property直接使用name -->
<contructor-arg type="int" value="20"></contructor-arg>
<contructor-arg type="string" value="张三"></contructor-arg>
<!-- 可以根据参数名确定 -->
<contructor-arg name="age" value="20"></contructor-arg>
<contructor-arg name="name" value="张三"></contructor-arg>
<!-- 可以根据index设置参数传递对应关系 -->
<contructor-arg index="1" value="20"></contructor-arg>
<contructor-arg index="0" value="张三"></contructor-arg>

一些模版库的引用

就像jstl这种一样,xml文件有一些标准模版库,导入这些标准模版库可以拓展我们的xml语法

spring.xml中比较常用的就是p这个库,用于简化配置property属性标签的

xmlns:P=”url”

xml namespace 就是这个意思

语法

可以直接通过bean的 属性来设置语法,而不是嵌套property标签

1
2
3
4
5
6
7
8
<bean id="some_id" class="classpath" p:name="defaultvalue" p:age="17"></bean>
<!-- 相当于下面的代码 -->
<bean id="some_id" class="classpath">
<property name="name" value="deafultvalue"></property>
<property name="age" >
<value>17</value>
</property>
</bean>

对于construcor-arg标签

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="some_id" class="classpath" c:name="defaultvalue" c:age="17"></bean> 	
<bean id="libraryAdmin" class="task02.LibraryAdmin">
<constructor-arg type="java.util.List" >
<list>
<!-- 注意简化写法的引用类型就是加上-ref -->
<bean class="task02.Student" c:_0="张三" c:_1-ref="cardRedA"></bean>
<bean class="task02.Student" c:_0="李四" c:_1-ref="cardBlueA"></bean>
<bean class="task02.Student" c:_0="王五" c:_1-ref="cardRedB"></bean>
<bean class="task02.Student" c:_0="赵六" c:_1-ref="cardBlueB"></bean>
</list>
</constructor-arg>
</bean>

自动注入

也就是我们不在配置文件中指明某些引用类型,引用那个bean,让这个bean的所有引用字段自动注入

如何自动寻找想要的bean?
  1. 某个bean的id和我们的set方法名匹配了

autoWire=”byName”

  1. 发现某个bean的类型和set方法的参数匹配上了

autoWire=”byType”

但是容易出现问题,也就是和getBean(Class<?> aclass)这个一样的问题,如果容器中有超过一个的类的对象,那么选取哪一个呢?要求必须要只有一个bean

如果是子类的对象,在容器中我们也认为是父类的一个对象,进行匹配

如果是接口的实现类,那么我们也认为他就是这个接口的对象,进行匹配

也就是能兼容我们就兼容,要求不严格

注解 配置注入信息

想要使用spring知道你配置的注解信息,需要在bean.xml文件中添加组件扫描

1
2
3
<context:component-scan base-package="包的名字,设置的足够的小可以提高最开始的扫描性能">
<context:component-scan base-package="other-package-path,other,other"></context:component-scan>
<!-- 当然这个也是需要导入 xmlns 命名空间的 -->

注解配置方式好处是方便开发

但是坏处很明显,

  1. 配置信息耦合在源代码中
  2. 查看整体信息不方便,只能分别查看类各自的配置信息

spring的注解配置的原理就是加入组件扫描元素,对所有的spring组件进行扫描,然后就能读取到配置信息了

注解分为两类:

  1. 类注解
  2. 字段注解

类注解

这种注解就一个作用,告诉spring扫描器,这个类就是你要找的配置了spring注解的类

@Component 组件的意思

@Respository

@Service

@Contorller

他们就是一个标记注解

为什么出现他们四个,而不是一个,主要的目的就是告诉开发者,这个类在spring配置中的地位

@Component

组件的意思,相当于是spring容器中的一个组件(就是一个类的毕业路线图)Bean标签

可以给一个value=”id”

1
@Component("这里可以设置id,实际上不设置默认就是类名小写首字母")

@Repository

表示持久存,Dao啊什么的

@Service

表示业务逻辑层

@Contoller

控制层

字段上的注解 (不配置成bean也可以注入)

如果直接说是将值写死在注解上,和写死在源代码中是一样的作用

spring的注解支持一种类似EL表达式的语法,从外部问文件中读取值放在这个位置

这个语法支持也需要在bean.xml中配置,并且配置文件地址

1
2
<context:property-placeholder location="classpath:文件名"></context:property-placeholder>
<!-- 这个placeholder就是占位的意思 classpath:会转变成具体的类路径 -->
@value

主要是用在基本数据类型上给字段赋值的

如果是自定义类型,想要使用字符串进行赋值,需要自己去设置一下value的自动类型转换匹配

使用 占位符语法

1
2
@value("${bean.name}")
private String name;
@Autowired(require = true)

自动注入,去spring容器内部寻找对象进行注入

默认这个required指的是找不到对象进行注入的时候抛出异常,我们允不允许,允许那么会赋值null

两种模式byName,byType

需要使用另一个注解做补充

@Qulifier(“beanName”)这种就是通过bean名字进行自动注入,而不使用这个注解就是使用类型自动注入

spring实际上推荐使用构造方法的形式进行自动注入,所以在使用这个注解的时候会出现一些横线

也就是推荐我们写在构造方法上,这个注解

1
2
3
4
5
6
7
public class Student{
private Teacher teacher;
@Autowired
Student(@Qualifier("teacher")Teacher teacher){
this.teacher = teacher;
}
}
@Resource(name=”BeanName”)

这个注解实际上也是对引用类型的注入

这个注解和上面的三种注解不同之处在于,这个注解不是spring框架提供的,所以不依赖@component等注解

这个注解是jdk提供的,也就是说实际上jdk也有自动注入的操作,只不过spring的注入更加强大而已

这个注解在javax.annotatiion这个包中,需要添加依赖才能找到这个包,或者<artifactId>javaee-api</artifactId>

这个注解也支持byName和byType两种策略默认情况使用byName,这个就和spring的autowired不一样

并且如果byName失败,会尝试使用bytype方式,和spring注入方式不同的是,这个是通过字段注入的,而不是set方法

autowired和resource的差别

autowired默认情况下是ByType操作,也就是最低要求操作,不存在则抛出空指针(和required参数有关)

如果需要byName策略需要配合使用@Qulifier注解一起使用

resource注解默认情况下是byName高级要求操作,如果高级要求无法满足,再去尝试最低要求,查找类型匹配

但是这个注解没有required参数,一旦找不到注入类就一定会报错

spring4开始推荐使用构造方法注入,但是构造方法注入就只能使用@autowired注解,@resource注解并不支持构造方法注入

总结:

​ 如果使用set注入,推荐使用@resource,如果使用构造方法注入就是常用@autowired注解

多spring配置文件机制

分成多个配置文件这个很好理解,但是如果想要将多个配置文件的信息联合在一起,这个怎么搞?

如果本来是一个配置文件的内容我们分成多个配置文件,这样的好处就是配置信息更加清晰,不同部分的配置逻辑分明

记得在redis的学习中,redis的配置文件就有一个include的语法,这里也有相同的一个语法

1
2
3
4
<import resource="bean.xml.filepath"></import>
<!-- 有一个小技巧,写class:path 这个会自动转换为 类路径下 这样我我们只用加上相对于类路径下的相对路径即可 -->
<import resource="classpath:bean_*.xml"></import>
<!-- 这个路径也可以包含通配,这个语法是shell下的通配符语法 而不是正则表达式的 -->

复杂对象的创建

有一些对象实在过于复杂,无法使用简单的配置文件描述创建过程,而spring又想要通过beanFactory创建,所以spring提供了一个接口,spring直接通过接口的方法来获得这个类的对象

这个接口就是FactoryBean

使用固定接口方式

1
2
3
4
//一共有三个方法
default boolean isSingleton();//spring用来创建对象的时候判断这个对象是否使用单例
Object getObject();//spring使用这个来获取对象
Class<?> getObjectType();//spring用这个来获得对象的类型信息

只要我们的复杂类实现了这个接口,那么spring就能够通过beanFactory获取到这个对象的完整配置.

所以实际上我们的对象是通过 factoryBean这个类的getObject创建的而spring只不过是一个代为管理的而已

配置文件中直接配置这个类成为一个bean标签即可,class就是我们的实现了beanFactory接口的类全路径

**疑惑?**如果我们想获取实现了FactoryBean接口的这个类而不是想去调用它的getObject方法该如何?

spring可以根据不同的传入参数进行判断,如果参数是以&符号开头的,我们就忽略FactoryBean的getObject函数,直接创建这个类

1
context.getBean("&name");

spring还提供了一种方法,不用我们的工厂类实现预定的接口,也就是说所有的普通工厂spring也能接管

这个方式是通过配置文件来找到类似之前接口中的三个方法函数

使用配置文件方式

1
2
3
<bean id="one name" class="factory class path"></bean>
<!-- 注意 factory-bean 的值是一个 bean 而是不是类路径!!! 所以需要先配置一个bean -->
<bean id='some' factory-bean="a factory bean name" factory-method="your get object method all name"></bean>

也就是通过 factory-bean 和 factory-method两个方法来确定结构

实际上spring所做的工作就是通过先创建工厂类实例,在调用工厂实例的工厂方法获取到对象

这个避免了spring框架的侵入

如果你的那个getObject方法是一个静态方法

这样我们就不需要那个工厂类的实例对象了,直接通过类名调用即可

所以就直接不需要 factory-bean这个属性了

1
<bean id="some id" class=" factory class path" factory-method="method name"></bean>

类型转换器

这个是用来干嘛的?

记得之间说过,xml配置中的value属性传入的是一个字符串的值,但是set方法可能需要的值并不是字符串类型,

像我们的基本数据类型,传入字符串spring会自动做出转换

这个功能就是通过类型转换器做到的

如果我们也想要通过字符串转换成我们需要的类型

我们就需要把自己的类型转换器放进配置

实现接口

spring提供了实现转换的接口,叫做converter,实际上我们也知道,为了保证spring的非侵入式,一定会提供不实现接口的办法

这个接口是一个函数式接口,也就是只有一个方法

1
2
3
4
5
6
7
public interface Converter<S,T>{
//s表示源类型,T表示目标类型
//第一个方法 convert
T convert(S source);
//第二个方法是一个default方法

}

我们实现了这个转换类之后,需要把这个类的对象添加到转换的set中去,这样在进行转换的时候才能找到

我们必须要这样写

1
2
3
4
5
6
7
8
9
10
<bean id="convertser or other name" class="your converter class path"></bean>
<!-- 如果显式配置了这个类的bean 那么spring就会 去用这个对象进行自动类型转换 -->
<!-- 如果不使用spring的Converter接口,那么就需要使用下面的方式自行
<bean id="conversionService" class="org.springframwork.context.support.ConversionServiceFactoryBean">
<property name="converters"> <!-- 这个字段就是我们的转换器set -->
<set>
<ref bean="converter's name"></ref>
</set>
</property>
</bean>

为什么这个容纳 转换器的对象叫做 FactoryBean呢?

这个也是一个复杂对象

AOP 面向切面编程

什么是切面?

相当于是我们的程序就像一片一片的面包,竖着排成一列 ||||||||| 这样似得

我们将我们自己写好的面包夹住 别人写的面包,而不破害别人写的面包这就是切面编程

很明显的特点就是 别人的代码不需要改动

主要用于解耦

怎么解耦呢?

在实际编程中,我们代码中包含业务逻辑,日志打印语句,控制语句,等等语句

如果理解起来思维会被打断,人不可能同时考虑到这么多的东西

所以我们就先只关心一件事情,实现业务逻辑,ok

| | | 这三条线就代表了业务逻辑代码

但是很明显业务逻辑代码 也是需要调试日志信息的 我们每执行一个业务逻辑代码,我们就需要打印引一条日志信息

通常的做法是我再去添加 实际上 这就是面向切面编程的雏形

| + | + | +

现在的面向切面编程我们并不需要显示的添加代码让他们组长到一起

我们边写两段代码,一段代码是业务逻辑代码,一段代码是日志打印逻辑

我们告诉程序把他两的代码组装到一起,ok

这就完毕了

我在编写的时候只需要考虑一方面的事情,最后把各个方面的代码组装到一起即可

所以面向切面编程他讲究的就是一个:

思路专一

最后合并

代理模式

这个代理就是我们的组装器,被代理对象就是核心代码,我们可以在核心代码的两边组装我们额外的代码上去

实际上也可以叫做中间商赚差价

组装器:

​ 零件部分

​ 主体部分

​ 零件部分

组装器一种是不可更换零件的模式,叫做静态代理

一种是可以动态换零件的,叫做动态代理

静态代理

零件是写死在源码中的,你只能选择加或者不加

实际上就是重用了静态组装器的零件而已

缺点:

​ 不符合开闭原则 就是不修改代码的情况下对软件的功能进行扩展

​ 与代理对象的耦合性强,每个代理方法都需要去重新写一下

动态代理

动态代理一共有两种实现的技术

一个是JDK实现的动态代理

一个是CGLIB实现的动态代理

JDK实现的方式

JDK实现的方法 是利用java提供的一个代理类 Proxy ,这个类会监控被代理类的调用,转交给代理对象

这里就需要我们的代理对象实现一个Proxy知道的接口,这样当proxy检测到被代理对象被调用方法的时候,会调用代理对象的接口方法,拔掉用的信息都传过去

实际上就是动态生成类,代理类能够通过接口知道被代理类提供了哪些服务,从而自己也去实现这些接口即可模拟为代理对象,但是如果代理对象有的方法不是接口中的方法,那么代理对象就没法模仿,就没法提供这个服务

这个缺陷CGLIB解决了,他使用的是通过继承来进行模拟,将被代理类作为父类,然后代理对象就能够模拟出提供出和被代理这一样的方法.

InvocationHandler 接口

1
2
3
4
public interface InvocationHandler{
//这个接口的名字就是 调用处理器
Object invoke(Object proxy, Method method, Object[] args);
}

实际上在Proxy调用组装器的invoke方法的时候,只会将自己生成的代理对象(也就是newInstance的结果对象)传过去,以及被调用的方法,和方法的参数,并不会将被代理(主体代码传过去),你这个组装器当然要自己知道你的客户啊,我只是负责通风报信的

所以一般的组装器这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyInvocationHandler implements InvocationHandler{
Object 核心对象;
MyInvocationHandler(Object mainObject){核心对象 = mainObject;}
@override
public Object invoke(Proxy proxy, Method method, Object[] args){
//组装代码
...

//核心代码
Object result = method.invoke(核心对象, args);
//组装代码
...
return result;
}
}

除了需要知道组装类是怎么写的,还需要知道Proxy这个拦截调用的类的怎么工作的

我们知道,实际上的代理对象是Proxy通过newInstance生成的

1
public static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h)

实际上,这个newInstance生成的代理对象,也只能够拦截interface中的方法,也就是说,想要拦截一个方法,那么必须要先创建包含这个方法的接口,这样你调用这个方法,我才能够拦截

就像Proxy使用interface作为一张大网一样,只能拦截一部分在网上的方法

其中classloader用处是在于,我们的生成的代理对的实际上是一个动态生成的类,这个类通过这个指定的类加载器加载进入内存,类加载器可以避免一些冲突

最后就是调用处理器,这个是我们代理类的核心

CGLIB实现方式

全称叫做 Code Generation Library,这个是开源项目,性能很高,质量也很高

这个是基于继承机制,来使得生成的代理类和被代理类有一样的方法提供.

在这里我们的代理对象并不是通过函数生成的,而是我们自己组装生成的

看一看下面的代码吧

1
2
3
4
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(被代理对象.getClass().getClassLoader());
enhancer.setSuperclass(被代理对象.getClass());
enhancer.setCallback(实际上是一个实现了MethodInterceptor接口的对象)

这个MethodInterceptor接口和InvocationHandler接口实现的是一个功能

1
2
3
public interface MethodInterceptor{
Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy);
}

实际上,这个传入的object也是生成的那个代理对象,Method参数是调用时候的方法,args是调用的方法传入的参数,

methodProxy 这个对象是用来调用父类的方法的,如果我们只是需要增强一个类的功能而不是一个对象的话,就可以使用这个methodProxy

AOP的一些术语

  1. 增强 Adivce 也就是在代理对象之上添加更多我的功能

  2. 连接点JoinPoint 也就是不可被拆分的代码块 |||| ,在这些方法的前后都可以使用代理增强

  3. 切点 PointCut 实际上能够切入增强功能代码的地方

  4. 切面Aspect 切点加上增强,这个就是切面

    1. 在什么位置执行增强功能
    2. 执行什么增强功能
    3. 在什么时间执行
  5. 引入introduction 允许现有的类添加新的功能属性

  6. 目标target 原始的对象,专注以原始的业务逻辑

  7. 代理Proxy 生成代理对象,玩成对于目标对象代理

  8. 织入weaving 也就是创建代理的过程,也就是编制在一起,我觉得还不如叫做组装

Spring AOP开发

以上的AOP是传统的方法,spring为了简化AOP的开发流程,将组装这个具体的信息提取了出来,并利用JDk和CGLIB对于动态代理的实现技术,选择生成了代理类(有接口的使用JDK,没接口的使用CGLIB)

Spring对于AOP的组装信息提取

aspectJ对于AOP的组装信息提取

主要使用的就是aspectJ的这种,spring也提供了这个包,在spring-aop-aspects这个包里面,需要添加依赖

都说了组装可以在前边或者在后边组装,但是那一部分的代码装在前边,那一部分的代码装在后边这个也是一个问题

@before 装在连接点的前边

@after 装在连接点的后边 ,有错误也不会跳过执行这个部分,就像finally部分一样

@afterReturning 装在连接点的后面,但是只有正常返回才回去执行这个增强部分

@around 装在两头

@afterThrowing 抛出异常后

除了围绕一个切点之外我们还可以将全部的切点看做是一个整体,围绕整体做切入

主要看还是分为静态代理和动态代理

静态代理主要的实现原理就是通过读取注解,调用注解中配置的方法,相当于是改变了这个类的字节码文件

动态代理,也就是可以实现对于一个已存在的对象进行代理

前言

我们通过注解声明了一个类,这个类的方法就是增强方法,这个增强方法组装到哪里就看注解信息

@Aspect

只有这个类被配置成bean被spring构建的时候才能够解析到这个@Aspect发现这个是切面类

并且,拦截也只能拦截到配置在同一个spring容器中的bean

例如

1
2
3
4
5
6
7
8
9
@Componont
@Aspect
public class MyMain{

@before(value = "execution(* com.wzy.aspect.*.*showMenu())")
public void monit(){
System.out.println("监控开启中...");
}
}

所以关键就是增强表达式

切入表达式(增强表达式)

因为这个表达式是用来放在切入注解中的,所以叫做切入表达式

主要就是通过这个表达式监控 具体某个方法的调用,就像是JDK实现需要提供接口一样,从而拦截这个方法

execution( [访问权限] <返回值类型> [包名类名] <方法名(参数列表)> [异常列表])

这个并不考虑这个方法到底是静态方法还是实例方法,只有这个方法是属于spring配置的bean范围内,spring就能够对其方法进行拦截

这个就是表达式的基本模板了

一些特殊符号

* 表示任意的字符串,限制长度

.. 表示任意多个参数,或者表示当前包及其任意的子包

+ 放在类名或者接口名之后表示当前类以及子类

例如想要拦截所有以do开头的方法

execution(* do*(..))

以及任意类任意方法

execution(* com..*.*(..))

这个*.*表示的就是任意的类,的,任意方法

在Spring的AOP注解中,可以使用逻辑操作符“||”(或)和“&&”(与),将多个切点表达式组合在一起

使用说明

要想使用apectJ的注解,就像使用sprig-bean的注解一样,添加扫描,这样才能够解析你的注解信息

1
2
<!-- bean.xml文件中 添加这个-->
<aop:apectJ-autoproxy></aop:apectJ-autoproxy>

@Before @After注解

这个两个注解因为注解中都住需要一个拦截名单而已,所以放在一起介绍

@Before(value=”execution()”)

也就是在拦截方法执行之前执行增强功能

@After(value=”execution()”)

这个是在拦截方法执行之后去执行,并且不管拦截方法是否抛出异常,就像finally一样也会执行增强方法

增强方法的返回值,会被丢弃

@AfterReturning

样式就像这样

@AfterReturning(value = “execution()”. returning= “param-name”)

public void funcitonName(JoinPoint point, Object param-name ){

}

在拦截到方法之后,先执行方法,然后再去执行这个增强方法

这个会自动传入JoinPoint参数给到增强方法,这个参数对象就是我们具体捕获到的方法的具体信息,以及一些实际参数

这个resturning属性是用来配置我们的增强方法接受被拦截方法的运行结果的,spring会根据retruning的值将拦截方法的执行结果对应传入增强方法的一个参数

这个增强方法中,如果被拦截方法的返回值是一个引用类型,就可以通过修改result的值来对最终的结果返回值进行修改

增强方法的返回值也会被丢弃

如果被拦截方法不能正常执行完毕,那么这个后置通知就不能正常执行

@Around

环绕通知

这个就类似于JDK的invocationHandler

这个拦截到方法之后,所有的执行权全部在自己,掉不调用被拦截的方法都是自己说了算

为了去调用这个被拦截的方法,这下就不得不去了解一下JoinPoint这个类了

JoinPoint
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//实际上就这么几个方法
1. 方法签名:joinPoint.getSignature()

返回当前连接点签名,包括方法名、参数类型、声明类型等信息。

2. 方法参数列表:joinPoint.getArgs()

返回当前方法的参数列表。

3. 方法返回值:joinPoint.proceed()

执行被代理类的方法,并返回方法的返回值。

4. 目标类的实例:joinPoint.getTarget()

返回当前连接点所在的目标对象,即被代理的类实例。

5. 连接点类型:joinPoint.getKind()

返回连接点类型,包括方法调用、方法执行、构造方法调用等。

6. 连接点静态部分:joinPoint.getStaticPart()

返回连接点的静态部分,包括方法名、参数类型、声明类型等信息。

7. 连接点所在源码的位置:joinPoint.getSourceLocation()

返回连接点在源代码中的位置。

8. 连接点所在的类:joinPoint.getThis()

返回连接点所在的类实例。对于静态方法,返回目标类的类对象。

实际上这个@Around返回的是一个JoinPoint的子类,叫做ProceedingJoinPoint

这个增强方法的返回值会作为最终的的返回值

@AfterThrowing

记得@AfterReturning在被拦截方法抛出异常之后是无法执行的

这里有一个就是专门等到被拦截方法抛出异常才去执行的增强方法

但是注意,这个是出现异常后执行,而不是对异常进行了处理,所以该停止运行还是停止运行,不能当做catch

增强方法可以有一个Exception的参数,需要配置在注解中,throwing属性

例如

@AfterTrhowing(value=”execution()”, throwing=’e’)

public void catchExecption(Execption e){

}

@PointCut

这个注解是用来对execution进行拦截的方法进行复用的

@Pointcut(value=”execution()”)

private void functionName(){

}

这个样别的想要复用这个PointCut中的execution,在自己的execution中写成这个方法的调用例如这里写成functionName()来代替execution的字符串即可

其他信息

我们可以在配置文件中配置是否让代理对象从属于被代理对象类,也就是是否是子类

如果配置成true那么就只能使用CGLIB了

如果配置成false,那么spring会灵活选择,如果有接口那么一定会选择使用JDK的技术

1
<aop:aspectJ-autoproxy proxy-target-class="true"></aop:aspectJ-autoproxy>

使用xml文件进行aop配置

第一步先把对应的被代理类,以及增强类配置成bean

第二步配置 AOP

1
2
3
4
5
6
7
8
9
<!-- 这个标签和bean标签同级 -->
<aop:config>
<aop:pointcut id="what id" expression="execution()"></aop:pointcut>
<aop:aspect ref=" bean">
<aop:before method="more method"pointcut-ref="waht id"></aop:before>
<!-- 可以看到,切点和增强方法是分离的,先有切点后有增强方法的调用 -->
<!-- 而注解是通过增强方法进行定位,添加注解,先有方法后来切点配置 -->
</aop:aspect>
</aop:config>

spring整合mybatis

主要就是mybatis配置文件,整合进入spring

mybatis全局配置文件

mybatis的Maper配置文件,接口,实体类等等

sqlSessionFactory,sqlsession,service整合

需要导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!-- spring上下文环境 -->
<dependency>
<article>spring-context</article>
</dependency>
<!-- spring事务 -->
<dependency>
<greopId>org.springframework</greopId>
<artifactId>spring-tx</articleId>
<version></version>
</dependency>
<dependency>
<greopId>org.springframework</greopId>
<artifactId>spring-jdbc</articleId>
<version></version>
</dependency>
<!-- 连接池数据源 -->
<dependency>
<artifactId>druid</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis以及spring的整合依赖 -->
<dependency>
<artifact>mybatis</artifact>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifact>mybatis-spring</artifact>
</dependency>
<!-- 编译源文件中的xml文件 -->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resource>
</build>

配置部分

mybatis-config.xml的配置

1
2
3
4
5
6
7
<!-- environments部分的配置整合在spring配置中了,不需要再进行配置 -->
<typeAliases>
<package name="com..."></package>
</typeAliases>
<mappers>
<package name="包名" ></package>
</mappers>

别名和mappers还是需要在mybatis-config.xml中进行配置

ApplicationContext.xml 配置 (spring配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- properties 文件引用 -->
<context:property-placeholder location="classpath: file name">></context:property-placeholder>
<!-- 注解扫描 -->
<context:component-scan base-package="扫描包"></context:component-scan>
<!-- 配置数据源的自动注入 -->
<bean id="dataSource" class="com.alibaba.drruid.pool.DruidDataSource" init-method="初始化方法名字(调用这个类中的某个方法)" destory-method="销毁方法">
<property name="url" value="${jdbc.url}"></property>
...
</bean>
<!-- 配置mybatis-spring整合对象的注入 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 下面两个成员变量的名字可不能变 -->
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml file path"></property>
</bean>-
<!-- 配置mapper扫描 sqlSessionFactory会使用这个对象进行mapper的扫描,不需要再使用mapper标签配置mapper.xml-->
<bean class="rg.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="SqlSessionFactoryBeanName" value="sqlSessionFactoryBeanName 没错就是上边配置的bean的id"></property>
<property name="basePackage" value="mapper.xml所在的包"></property>
</bean>

在spring中使用mybatis操作数据库

编写mapper和mapper.xml

使用mybatis中的mapper方式操作数据库,编写interface,使用mapper.xml

spring事务管理

使用jdbc技术实现事务管理的时候就是通过commit和rollback函数的调用手动实现事务管理

mybatis通过设置自动提交来自动管理事务,sqlSession自动调用commit和rollback

spring的事务管理:
spring想要屏蔽不同的数据库操作事务的差异,提供统一的接口

spring提供统一接口 PlatformTransactionManager来完成commit和rollback

不同的框架提供不同的这个接口的实现类,例如

  1. mybatis提供了DataSourceTransacationManager 来实现这个接口
  2. hibernate提供了HibernateTransactionManager 来实现这个接口

事物的隔离级别

也就是满足事务隔离性的程度,因为事务的隔离性很影响性能,所以不需要隔离性的时候就最好不用使用隔离要求,这样数据库就能愉快的执行

ISOLATION_DEFAULT 不同数据库的默认隔离级别程度不同,虽然都是叫做一个名

ISOLATION_READ_UNCOMMITED 读未提交,没有解决并发的问题

ISOLATION_READ_COMMITED 读已提交,解决脏读

IOSLATION_REPEATABLE_READ 可重复度,就解决了脏读,不可重复读,可重复读

IOSLATION_SERIALIZABLE 串行化,不会有并发问题

事务超时时间

如果执行时间超过,实物会认为失败,进行回滚

默认超时时间是-1

执行sql的事务可选项

一共有四种,在spring中提供

  1. 有则加入无则创建 PROPAGATION_REQUIED
  2. 有则加入,无则不用 PROPAGATION_SUPPORTS
  3. 有则另起,无则新建 ,旧的事物被挂起而不是消除了 PROPAGATION_REQUIRED_NEW
  4. 有则加入,无则报错 PROPAGATION_MANDATORY
  5. 有则挂起,我不会用 PROPAGATION_NOT_SUPPORTS
  6. 有则异常,无则不用 PROPAGATION_NEVER
  7. 有则加入,成立附属,无则新建 PROPAGATION_NESTED

事务提交和回滚的时机

如果业务方法没有异常,事务提交

如果java事务部分中途出现运行时异常,则回滚

如果是事务方法内部出现检查时异常,数据库立马提交事务

方法交给spring做事务管理

先保证事务所在方法是一个public方法,spring才能进行管理

实现注解

@Transactional

注解在方法上

配置事务管理器 bean对象

要在spring.xml中配置

1
2
3
4
5
<bean id="transactionManager" class="具体是那个框架提供的实现类 factotyBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 注意使用这个注解自动导入的是后缀为tx的那个命名空间 不要导入其他的命名空间了 -->
<tx:annotiaton-driver transactionManager="transactionManager"></tx:annotiaton-driver>
传入事务管理选择

@Transactional(

​ propagation=Rropagation.required,

​ isolation = Isolation.REPEATABLE_READ,

​ timeout = 5,

​ rollbackFor={ 异常列表,如果抛出列表中异常就回滚 }//运行时异常一定会回滚,

​ noRollbackFor={ 忽略异常列表 }

)

使用xml进行配置

注解配置实际上不适合于大量的操作,这个时候用xml进行配置更加方便有效

1
2
3
4
5
6
7
8
9
10
<!-- 在spring.xml文件中进行配置 -->
<tx:advice id="onece transacation " transactionManager="transcationManager bean id">
<tx:attribute>
<!-- method name属性可以使用通配符 -->
<tx:method name="method name " propagation="..." timeout="..."></tx:method>
</tx:attribute>
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(拦截方法 然后在有tx:anme进行匹配)"></aop:pointcut>
</aop:config>
</tx:advice>

Spring MVC

这个是一个在MVC层次上的框架,之前的框架有struts struts2 然后就是MVC 后来是 Spring Boot

先学习spring mvc 以后在使用spring boot的时候才会更好理解

MVC

Model模型层

实体类 mybatis等

View 视图层

jsp html css js等等

controller 控制层

Servlet类

应用spring mvc

创建maven的webapp工程

注意依赖中添加上servlet-api ,jsp-api如要使用的话

以及文件目录结构:src/main/java ,以及src/main/resources 以及/src/main/webapp

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.6</version>
</dependency>
<!-- spring事务 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.6</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- mybatis以及spring的整合依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.15</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<finalName>wzy_experiment4</finalName>
<!-- 编译源文件中的xml文件 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

lombok 可以需要

servlet-api, jsp-api要用就自己添加

spring-webmvc这个需要,这个本身依赖了spring-context,spring-beans等项目.所以我们添加了他的依赖,其他spring的核心就有了

配置web.xml文件

spring-mvc结构中,有一个servlet是用来作为前端拦截器的,最为海关一样的东西,进出都得经过他

这个需要在web.xml中配置,这样服务器才知道这个servlet,其他像我们自己写的servlet,都是通过注解的形式配置,让服务器知道的,而第三方库我们就不能添加注解

1
2
3
4
5
6
7
8
9
<!-- 在web.xml中配置 -->
<servlet>
<servlet-name>some name</servlet-name>
<servlet-class>org.springframework.web.servlet.DispathcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>some name</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

另外需要为spring配置文件

springmvc.xml实际上就是之前spring的bean.xml

先在项目结构中,将xml配置进入项目

另外需要在web.xml中配置一下springmvc.xml的地址,当服务器去调用的时候,想要使用bean,这个时候调用的ApplicationContext是通过contextConfigLocation这个参数去找的,我们需要服务器通过这个参数找到我们的springmvc.xml,所以在web.xml中配置

1
2
3
4
5
6
7
8
9
<!-- 因为这个参数就位于前端拦截器这里,所以在这里配置 -->
<servlet>
<servlet-name>some name</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>

sprinvmvc.xml配置

spring-mvc有自己的注解,想要spring识别这些注解就需要配置注解扫描

1
2
3
4
5
<mvc:annotion-driven></mvc:annotion-driven>
<context:component-scan base-package="com"></context:component-scan>
<!-- 注意,自动导入的命名空间是否正确,如果导入的命名空间不正确会导致错误 -->
xmlns:context="http://www.springframework.org/schema/c"
xmlns:mvc="http://www.springframework.org/schema/p"

编写controller

controller实际上就是一个servlet的作用,在springmvc拦截了所有的请求之后,这个DispatcherServlet就会去检查映射表,如果有配置的映射的对象,就去调用这个配置的方法

如果请求的处理方法是

public ModelAndView functionName() 作为contorller方法

处理请求的controller方法可以有不同类型的返回值类型和参数列表,DispatcherServlet会根据不同的情况,使用不同的调用方法,传入不同的参数,比较常用的就是ModelAndView返回值

通过注解到方法,DispathcerServlet会根据请求的路径,匹配不同的controller的某个方法

这个注解就是@RequestMapping(“path”)

@RequestMapping注解

这个注解就是类似于@webservlet注解,告诉了DispatcherServlet这是一个请求路径的映射

这个注解可以放在类上也可以放在方法上,

实际上映射的值是一个方法,所以如果配置在类上的话,可以看做是对着各类中的所有方法的路径添加上一个前缀

例如

@RequestMapping(“stu”)

public class StuController{

​ @RequestMapping(“view”)

​ public ModelAndView selectView(){

}

}

这个selectView方法匹配的路径就是/stu/view路径

例如

1
2
3
4
5
6
7
//必须要配置为bean DispatcherServlet才能找到这个类对象
@Controller
public class StudentController{
@RequestMapping("stuSelect")
public ModelAndView selectList(){
}
}

实际上在@RequestMapping中的path并不需要添加斜线或者什么格式的要求,而@webservlet中的路径必须要以/开头

ModelAndView这个类的用法

这个类实际上需要设置一个视图页面,这个页面是用于显示给用户的,可以是动态页面也可以是静态页面

如果需要设置参数可以直接调用这个对象中addXX方法,这个实际上就是对于request对象的包装,参数设置之后这个ModelAndView被返回给DispatcherServlet,他会拿去视图解析器把最终的视图解析出来,返回给用户

modelAndView.setViewName(“view.jsp”);//设置视图

modelAndView.addObject(“list”,list);//向requst对象中添加键值对

如果请求的处理方法是

public String conrotllerFunction(HttpServletRequest request)作为controller的参数

此时DispatcherServlet会将返回值string类型的值,作为转发路径去访问该view资源

会传入一个request对象,想要给view资源传递一点值,就只能通过request进行设置键值对

自动解析请求参数

DispatcherSevlet在拿到请求之后会自动解析请求中包含的参数

在调用请求处理的方法的时候,检查方法的参数列表中参数的名字,通过这个名词从解析出来的请求中查找参数值,找到后直接传入

相当于我们不用手动的去写代码查找请求中的某个参数了,我们只需要写在方法的参数列表中即可

public String loginSubmit(String username, String pwd)

相当于会先reqeust.getParameter(key),再去传入参数进行调用,如果找不到这个值会直接传入null

如果方法的参数是一个引用类型

DispatcherServlet会尝试通过new一个对象,然后通过set方法找到参数的值,封装到对象中再进行传入

配置视图解析器

视图解析器一般并不需要人为配置

springmvc使用这个类来进行视图的解析

org.springframework.web.servlet.view.InternalResourceViewResovler

也就是InternalResourceViewResovler

我们可以通过springmvc.xml配置文件,给这个对象的一些参数赋值,例如

给所有接收到的view路径都加上前缀

1
2
3
4
<bean class="org.springframework.web.servlet.view.InternalResourceViewResovler">
<property name="prefix" value="/WEB-INF/pages/"></property>
<!-- 后缀的参数名字是 suffix -->
</bean>

静态资源的管理

之前在配置DispatcherServlet,在 webxml中的时候,设置了这个servlet拦截的请求路径/

这个路径会拦截到静态资源请求,DispatcherServlet按照流程会将路径拿到HandlerMapping进行controller的匹配,静态路径匹配不到所以就直接到不 到资源了

所以我们需要配置一下静态资源路径,让我们的DispatcherSErvlet放行访问

1
2
3
4
<!-- 在springmvc.xml中,使用mvc命名空间的标签 -->
<mvc:resources mapping="/static/**" location="/static/"></mvc:resources>
<!-- mapping的值就是放行请求的url特征 location就是映射到的一个目录/目录指的是 从webapp目录下开始,设置这个值而不是直接通过url进行文件的查找是为了安全这样url不直接和本地的目录对应,访问者就不知道这个url是不是敏感路径 -->
</mvc:resources>

@RequestMapping的其它参数

可以通过method参数,来设置方法相应哪种请求方式

1
2
@RequestMapping(value="/getTest", method=RequestMethod.GET)
//这个就只响应get方法的请求

可以使用另一个注解代替这种写法

@GetMapping(“path”)相当于就是默认了method=RequestMethod.GET的@RequestMapping

@PostMapping(path)相当于默认了method=RequestMethod.POST的@RequestMapping

@PutMapping使用put请求响应

@DeleteMapping使用Delete请求响应

RESTFul风格支持

这个风格就是URL中不要包含一些特殊字符=,&,?这些东西全部都使用/来进行分隔

springmvc提供了解析这样的url的支持

需要在@RequestMapping中的path具有一定的语法格式:

例如:

@RequestMapping(“delete/{id}”)

也就是通过{}的语法

DispatcherServlet将这个路径交给HandlerMapping去做映射,HandlerMapping发现这个路径和这个”delete/{id}”匹配了,然后根据delete/{id}这个意思,把后面url解析成了一个键名为 id的键值对,这个键值对是临时的,和DispatcherServlet从请求中解析出来的参数不一样,所以需要处理方法在参数上通过注解注明,这个方法参数是通过临时解析出来的键值对进行映射填充的,然后HandlerMapping才会去将临时解析出的键值对通过注解传入方法

如果没有写注解,那么参数是使用DispatcherServlet中解析出的键值对进行映射的

具体写法如下

1
2
3
@GetMapping("select/{start}/{end}")
public String selectByPage(@PathVariable("start") int start, @PathVariable end);
//如果方法参数的名字和url中的名字一样就可以不用写@PathVariable的参数

再次学习REST风格

REST实际上是Representational state transform的缩写,意思是表现形式状态转换

其实代表的是网络资源访问格式

传统的访问资源格式都是get请求方式的url,例如http://…./user/getById?id=1

而REST风格的访问资源url格式:http://…/user/1

这个风格就有一些优点:

  1. 隐藏了访问行为,无法通过url得知具体是什么操作(需要在前端页面中进行分析)
  2. url书写简化

如何实现简化的url区分不同的操作?

我们可以通过请求头进行区分,get,post,delete,put行为之类的

image-20240324155415712

rest风格只是一种风格,而不是规范

在描述模块的时候通常使用复数,表示此类资源,而非单个资源,例如users,books

根据REST风格进行资源访问称为RESTful

spring提供的REST风格支持

也就是spring需要根据请求的方法是get还是post,delete之类的匹配不同的方法

在@RequestMapping注解参数中有一个参数method,这个参数就是对于RequestMethod枚举类进行筛选的

image-20240324160145134

从RESTful的url中获取请求参数

使用spring提供的注解@PathVariable来注解我们需要传入值得变量

image-20240324160435477

需要在匹配路径中通过{变量名}的形式标注出参数所在位置,然后使用@PathVariable将值注入我们需要注入的变量中

需要注意的是,如果你的变量名和mapping中标注的变量名不一样,你需要在@pathVariable中传入value表示在url中取哪一个变量

image-20240324190120478

向客户端返回Json数据

现在很多情况下都是前后端分离开发,我们后端提供给前端的更多是Json数据

因为DispatcherServlet会解析我们controller的返回如果是ModelAndView会去调用view解析,是string也会去尝试调用View解析

我们想把JSON数据直接发送给客户端,你DispatcherServlet去把返回值转成Json就行了

需要在Controller设置一个注解,标注这个处理方法不需要你去找view解析

@ResponseBody

添加这个注解,springmvc会尝试将返回的Object解析成JSON格式返回给客户端

实际上的意思就是表示这个函数的返回值就是我们响应体

简化手段

REST风格的controller往往注解都是一样的,所以为了减少一些重复性的工作,spring提供了一些注解

  1. @RestController

这个注解就是RESTful风格经常需要的@ResponseBody和@Controller的结合

  1. @postMapping / @getMapping /…

这样我们就不需要写,@RequestMapping(method=Request….)

  1. @RequestMapping

写在类上,作为类中所有方法的url前缀

@ControllerAdvice

这个是利用AOP编程,将Controller生成了一个代理类,用于处理controller中的异常抛出

实际上这些方法都是Around方法进行的环绕代理,所以也要为这些函数添加上@ResponseBody标签


spring+spring_mvc
https://wainyz.online/wainyz/2024/01/15/spring+spring_mvc/
作者
wainyz
发布于
2024年1月15日
许可协议