Spring
1. Spring简介
1.1. Spring简单概念
-
什么是Spring?
- Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IOC(inverse Of Control:控制反转)和AOP(Aspect Oriented Programming:面向切面编程)为内核。
- 提供了展现层SpringMVC和持久层Spring JDBCTemplate以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的JavaEE企业应用开源框架;
-
Spring理念:简化企业级应用开发
-
SSH:Struct2 + Spring + Hibernate
-
SSM:SpringMvc + Spring + Mybatis
-
官方下载地址:https://repo.spring.io/release/org/springframework/spring/
-
GitHub地址:https://github.com/spring-projects/spring-framework
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.0</version>
</dependency>
1.2. Spring优点
- 方便解耦,简化开发
- 通过Spring提供的IOC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
- AOP编程的支持
- 通过Spring的AOP功能,方便进行面向切面编程,许多不容易用传统OOP实现的功能可以通过AOP轻松实现。
- 声明式事务的支持
- 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量。
- 方便程序的测试
- 可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的才做,而是随手可做的事情。
- 方便集成各种优秀的框架
- Spring对各种优秀框架(Structs、Hibemate、Hessian、Quartz等)的支持。
- 降低JavaEE API的使用难度
- Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。
- Java源码是经典学习范例
- Spring的源码设计精妙、结构清晰、匠心独用,可以作为Java源码的优秀的学习平台。
1.3. Spring组成
![[Pasted image 20211214195722.png]]
1.4. 拓展
-
Spring Boot
-
一个快速开发的脚手架
-
基于SpringBoot可以快速的开发单个微服务
-
约定大于配置
-
-
SpringCloud
- 基于SpringBoot实现的
学习流程:
$$
Spring + SpringMVC \to SpringBoot \to SpringCloud
$$
弊端:因为发展太久之后,违背了原来的理念,配置十分繁琐。
2. Spring核心原理
2.1 IOC控制反转
- IOC(Inversion Of Control):控制反转,是一个理念;
- 把对象的创建、赋值、管理工作都交给代码之外的容器实现,也就是对象的创建是由其他外部资源完成;
- 所谓控制:就是创建对象、对象的属性赋值、对象之间的关系管理;
- 所谓反转:是针对正向来说,由开发人员在代码中,使用new构造方法创建对象,开发人员主动来管理叫做正向操作;而反转就是将原来由开发人员创建、管理对象的权限交由代码之外的容器实现,由容器代替开发人员来创建、管理对象;
- 所谓容器:可以是一个服务器软件,也可以是一个框架(spring);
- 控制反转的作用:
- 可以实现在改动少量代码的情况下实现不同的功能;
- 实现业务对象之间的解耦合,例如service和dao对象之间的解耦合;
2.2 Java中创建对象的方式
- 构造方法:new
- 反射创建
- 序列化
- 克隆
- IOC:容器创建对象
- 动态代理
2.3 IOC的体现
- servlet:创建类集成HttpServlet
- 在web.xml中注册servlet,使用
<servlet-name>MyServlet</servlet-name> <servlet-class>com.yancey.controller.MyServlet</servlet-class>
- 实际上并没有创建Servlet对象,没有MyServlet myservlet = new MyServlet;
- Servlet是Tomcat服务器创建的,Tomcat也是容器;
- Tomcat作为容器,存放有servlet、listener、filter对象;
2.4 IOC的技术实现
- DI(Dependency Injection,依赖注入)是IOC的技术实现;
- DI只需要在程序中提供要使用的对象名称就可以,至于对象如何在容器中创建、赋值、查找都由容器内部实现;底层原理是反射机制;
3 Spring项目的实现
- 创建maven项目
- 加入maven依赖
- spring依赖
- 单元测试依赖
- 创建类
- 接口和实现类
- 创建spring需要使用的配置文件
- 声明类的信息,这些类由String创建和管理
<!-- 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 声明bean -->
<!-- id:对象的自定义名称,唯一值。spring通过这个名称找到对象 -->
<!-- class:类的全限定名称(必须是类,spring通过反射机制创建对象,因此不能是接口) -->
<bean id="demoServiceImpl1" class="com.yancey.impl.DemoServiceImpl" scope="singleton"/>
<!-- scope是定义Spring如何创建bean的实例的,Singleton:单例/Prototype:每个线程创建一个实例
Request:一个request创建一个实例/Session:一个session创建一个实例/GlobalSession:这个只在porlet的web应用程序中才有意义,它映射到
porlet的global范围的session,如果普通的web应用使用了这个scope,容器会把它作为普通的session作用域的scope创建。 -->
<!-- 在上面的标签中,spring完成了对象的创建(new),然后放入spring框架内的一个专门存放对象的map中;map的key为id,value为对象 -->
<!-- 同一个类可以被创建多个bean,但是此时将不能使用类名获取bean,只能通过id -->
<!-- <bean id="demoServiceImpl2" class="com.yancey.impl.DemoServiceImpl" />-->
<!-- spring也能创建一个非自定义的类,创建一个已经存在的类的对象 -->
<bean id="MyString" class="java.lang.String" />
</beans>
<!--
这是spring的配置文件:
1. beans:根标签,spring把java对象称为bean
2. spring-beans.xsd为约束文件,为mybatis中的dtd作用相同,约束在当前xml文件中出现的标签和属性
-->
// 使用spring容器创建的对象
// 1. 指定spring配置文件的名称,默认根路径为target/classes下
String configName = "beans.xml";
// 2. 创建表示spring容器的对象ApplicationContext,通过该容器获取对象;Ctrl+H查看实现类
// 在这一步spring就完成了对象的创建并存放到map中
ApplicationContext context = new ClassPathXmlApplicationContext(configName);
// 3. 获取对象方法,参数为配置文件中配置的id或者类名
// 这一步仅是从map中取出对象,spring调用的默认是无参构造方法
IDemoService demoServiceImpl = (IDemoService) context.getBean(IDemoService.class);
demoServiceImpl.hello();
4 DI:依赖注入
4.1 基于XML的DI
- 注入:赋值
- 简单类型:spring规定java的基本数据类型和string都是简单类型;
- DI:给属性赋值;
4.1.1 注入分类
- bean实例在调用无参构造器创建对象后,就要对bean对象的属性进行初始化;初始化是由容器自动完成的,称为注入。
DI的实现方式有两种:
- 在spring的配置文件中,使用标签和属性完成,叫做基于XML的DI实现;
- 使用spring中的注解,完成属性赋值,叫做基于注解的DI实现;
DI的语法分类: - set注入:spring调用类的set方法,在set方法中实现属性的赋值;
- 构造注入:spring调用类的有参构造方法,创建对象;在构造方法中完成赋值;
4.1.1.1 set注入
set注入:spring调用类的setter方法,给属性赋值
- 简单类型的set注入:
- set注入必须要有set方法,没有set方法无法注入;
- set注入的本质是执行了set方法,假如set方法中没有赋值语句,则不会进行赋值;
- set方法是在调用无参构造方法后调用;
- 属性标签
<property>
的name值本质上只拿来组装set方法,就算没有该属性值,依旧可以执行该name的set方法; - 无论属性的值是什么数据类型,bean的value值只能为string,放入双引号中;
- 无论类是否是由开发者代码实现的,只要包含set方法就可以通过
<property>
标签进行赋值;
<bean id="xx" class="xx.xxx.xx">
<property name="属性名" value="value" />
</bean>
- 引用类型的set注入:
<bean id="xx" class="xx.xxx.xx">
<property name="属性名" ref="bean's id" />
</bean>
4.1.1.2 构造注入
- 构造注入:在构造调用者实例的同时,完成被调用者的实例化;即使用构造器设置依赖关系;
- spring调用类有参构造方法,在创建对象的同时,在构造方法中给属性赋值;
- 通过标签
<constructor-arg>
实现;
<!--
<constructor-arg/>标签:一个标签表示构造方法的一个参数
<constructor-arg/> 标签属性:
name:表示构造方法的形参名;
index:表示构造方法的参数位置,从左往右以0为始;没有name和index参数时默认为index
value:如果形参为简单类型,使用value
ref:如果形参为引用类型,使用ref
-->
<bean id="xxx" class="xxx.xxx.xx">
<constructor-arg index="0" value="Internet of Things Technology" />
<constructor-arg index="1" value="Mr.Lee" />
<constructor-arg index="2" value="48" />
</bean>
4.1.2 引用类型属性自动注入
- 在实际的项目中,一个类可能包含多个引用类型的属性;
- 如果通过配置文件的方式逐个进行引用赋值,将会有很多冗余的不必要的操作;
- spring框架根据某些规则可以给引用类型赋值;
- 常用的规则:
4.1.2.1 byName方式自动注入
- java类中引用类型的属性名和spring容器中(配置文件)
<bean>
的id名称一样,且数据类型是一致的,这样的容器中的bean,spring能够直接赋值给引用类型;
<bean id="xxx" class="xx.xxx.xxx" autowire="byName" />
4.1.2.2 byType方式自动注入
- java类中引用类型的数据类型和spring容器中(配置文件)
<bean>
的class属性是同源关系,这样的bean能够赋值给引用类型 - 同源关系:
- java类中引用类型的数据类型和
<bean>
的class的值是一样的 - java类中引用类型的数据类型和
<bean>
的class的值是父子关系的 - java类中引用类型的数据类型和
<bean>
的class的值是接口和实现类的
- java类中引用类型的数据类型和
<!-- 当存在多个同源关系的bean时,则会抛出异常 -->
<bean id="xxx" class="xx.xxx.xxx" autowire="byType" />
4.1.3 为应用指定多个Spring配置文件
优势:
- 每个文件的大小比一个文件要小很多,提高效率
- 避免多人竞争带来的冲突
- 多种分配方式减少耦合
多文件的分配方式: - 按项目模块
- 按类的功能
当存在多个Spring配置文件时,会存在一个主配置文件来包含其他的配置文件,主配置文件一般不定义对象;
<!-- 在psring的配置文件中要指定其他文件的位置,需要使用classpath属性 -->
<import resource="classpath:其它配置文件的路径" />
<!-- resource也可以通过通配符,使用通配符时注意不要包含主配置文件引起死循环 -->
<!-- 注:classpath*是在resource文件夹根路径下使用,存在多层路径时使用classpath,或者直接写文件名 -->
<import resource="classpath*:其它配置文件的路径" />
4.2 基于注解的DI
- 使用注解注入之前,需要通过spring-context的maven依赖间接引入spring-aop的依赖;
- 使用注解注入,要在资源路径下的spring配置文件中声明组件扫描器
<context:component base-package="xxx" />
;- 组件扫描器的本质还是Java对象;
- 组件扫描器工作方式:把包中和子包中的所有类,按照类注解的功能创建对象,或给属性赋值;
- 加入了组件扫描器标签后,配置文件的最外层标签中会加入新的约束文件
spring-context.xsd
- 方式是通过context的参数指定一个命名空间,命名空间表达一个url;
- ![[Pasted image 20220110205342.png]]
- 指定多个包的三种方式
- 多次使用组件扫描器
- 使用分隔符
;
或,
- 指定父包
- 存在标签
<context:property-placeholder location="classpath:middlewire.properties" />
通过配置文件来读取属性- 该标签做扫描配置文件用,配置文件为
key=value
的格式; - 使用属性的方式为
${key}
;
- 该标签做扫描配置文件用,配置文件为
4.2.1 基础注解介绍
4.2.1.1 @Component
- 作用域:class
- 作用:创建对象,等同于
<bean>
; - 原理:使用无参构造方法
- 属性:
- value:对象名称,等同于bean的id值;没有指定时默认类名首字母;
4.2.1.2 @Repository
- 作用域:DAO持久层
- 作用:创建dao对象,可以访问数据库;
- 原理:使用无参构造方法
- 属性:
- value:对象名称,等同于bean的id值;没有指定时默认类名首字母;
4.2.1.3 @Service
- 作用域:Service业务层
- 作用:创建service对象,做业务处理,可以使用事务等功能;
- 原理:使用无参构造方法
- 属性:
- value:对象名称,等同于bean的id值;没有指定时默认类名首字母;
4.2.1.4 @Controller
- 作用域:Controller控制层
- 作用:创建Controller对象,能够接收用户提交的参数,显示请求的处理结果;
- 原理:使用无参构造方法
- 属性:
- value:对象名称,等同于bean的id值;没有指定时默认类名首字母;
4.2.1.5 @Value
- 作用域:属性/set方法
- 作用:给属性赋值
- 原理:
- 在属性上,无需set方法,使用放射实现
- 在set方法上, 通过set方法注入,二者都存在时按照set方法实现
- 属性:
- value:赋值的值;
4.2.1.6 @Autowired
- 作用域:对象/属性/set方法
- 作用:实现引用类型的赋值
- 原理:通过自动注入原理实现,默认通过byType自动注入实现
- 属性:
- required:布尔类型,当值为true(默认为true)时表示引用类型赋值失败时,程序抛出异常并终止执行;当值为false时,程序正常执行,引用类型返回null
- 原理:当required为true时,spring会在程序时检查赋值行为的正确性,当发现异常时会提前抛出;减少程序执行后抛出的NP异常;
- required:布尔类型,当值为true(默认为true)时表示引用类型赋值失败时,程序抛出异常并终止执行;当值为false时,程序正常执行,引用类型返回null
- 联合注解:
- @Qualifier:当 @Autowired默认时,@Autowired使用byType自动注入实现,当加入该注解,并且配置value值时,使用byName方式,该注解的value属性为bean的id
4.2.1.7 @Resource
- 作用域:对象/属性/set方法
- 作用:实现引用类型的赋值
- 原理:通过自动注入原理实现,默认通过byName自动注入实现,当byName无法找到类时使用byType
- 属性:
- name:当配置该参数时只使用byName,值为bean的id
- type:当配置该参数时只使用byType,值为class
- 备注:该注解为JDK的注解,不是spring的注解;spring只是提供了对该注解的功能支持;
5 AOP 面向切面编程
5.1 动态代理
- 动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理对象只是由代理生成工具(不是真实定义的类)在程序运行时由JVM根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才成立;
- 动态代理的作用:
- 在目标类源代码不改变的情况下增加功能;
- 减少代码的重复;
- 专注业务逻辑代码;
- 解耦合;
5.1.1 JDK动态代理
- 动态代理的实现方式常用的有两种:使用JDK的Proxy,与通过CGLIB生成代理。jdk的动态要求目标对象必须实现接口,这是java设计上的要求;
- 从jdk1.3以来,java语言通过Java.lang.reflect包提供三个类支持代理模式Proxy,Method和InovationHandler;
5.1.2 CGLIB动态代理
- CGLIB(Code Generation Library) 是一个开源项目。是一个强大的、高性能的、高质量的Code生成类库,它可以在运行时扩展Java类与实现Java接口;
- CGLIB广泛的被许多AOP的框架使用,例如SpringAOP;
- 使用JDK的Proxy实现代理,要求目标类与代理类实现相同的接口;若目标不存在接口,则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用CGLIB来实现;
- CGLIB代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。
- 使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是final的类;
- CGLIB经常被应用在框架中,例如Spring,Hibernate等。CGLIB的代理效率高于jdk。项目中直接使用动态代理的地方不多,一般都使用框架提供的功能;
5.3 动态代理与AOP
- AOP(Aspect Orient ProGramming)又叫做面向切面编程,基于动态代理实现,可以使用jdk和CGLIB两种代理方式实现;
- AOP是动态代理的规范化,把动态代理的实现步骤和方式都定义好,然后提供给开发人员一个统一的方式来使用;
- 什么是切面(Aspect),一般是一些非业务的逻辑代码,很多业务逻辑中可能使用到,所以单独拿出来;
- 常见的切面功能:事务、日志、统计信息、参数检查、权限验证;
- 常用的术语:
- JoinPoint:连接点,连接业务方法和切面的位置,就是类中的业务方法
- PointOut:切入点,指多个连接点方法的集合,多个方法
- 目标对象:给哪个类的方法增加功能,这个类就是目标对象
- Advice:通知,标识切面功能执行的时间
5.4 AOP的实现
- spring:spring在内部实现了aop规范,但是spring主要在事务处理时使用aop,实际应用场景中因为spring的aop比较笨重而很少使用;
- aspectJ:一个开源的aop框架,是一个来自于Eclipse的开源项目;spring框架中集成了aspectJ框架,因为可以通过spring使用;
- aspectJ框架实现aop的方式有两种:
- 使用xml的配置文件:常用来配置全局事务
- 使用注解:最常用的aop使用方式
- aspectJ框架实现aop的方式有两种:
5.5 aspectJ框架的使用
- 切面的执行时间:在规范中它叫做Advice(通知/增强),在aspecJ中通过注解实现(也可以通过xml配置文件中的标签实现):
- @Aspect:是AspectJ框架中的注解
- 作用:表示当前类是切面类
- 切面类:用来给业务方法增加功能的类,在这个类中有切面的功能代码
- 作用域:类
- @Before:前置通知注解
- 属性:value,切入点表达式,表示切面的功能执行的位置
- 作用域:方法
- 特点:
- 在目标方法之前执行;
- 不会改变目标方法的执行结果;
- 不会影响目标方法的执行
- @AfterReturning:后置通知注解
- 属性:
- value:切入点表达式,表示切面的功能执行的位置
- returning:自定义变量,表示目标方法返回值,要求变量名和通知方法的形参相同
- 作用域:方法
- 特点:
- 在目标方法之后@After注解之前执行;
- 能够获取到目标的返回值,可以根据返回值做不同功能的处理;
- 可以修改返回值;
- 目标方法抛出异常不执行
- 属性:
- @Around:环绕通知注解
- 属性:
- value:切入点表达式,表示切面的功能执行的位置
- 作用域:方法
- 特点:
- 功能最强的通知
- 在目标方法的前和后都能增强功能
- 控制目标方法是否被调用执行
- 修改原来的目标方法的执行结果,影响最后的调用结果
- 功能等同于jdk的动态代理的InvocationHandle接口
- 属性:
- @AfterThrowing:异常通知注解
- 属性:
- value:切入点表达式,表示切面的功能执行的位置
- throwing:自定义的变量,表示目标方法抛出的异常对象,变量名要求和方法参数名一致
- 作用域:方法
- 特点:
- 在目标方法抛出异常时执行
- 可以做异常的监控程序,监控目标方法在执行时是否有异常,如果存在异常可单独处理(做异常通知啊什么的)
- 属性:
- @After:最终通知注解
- 属性:value,切入点表达式,表示切面的功能执行的位置
- 作用域:方法
- 特点:
- 在目标方法之后执行;
- 总会执行;
- 一般做资源清除;类似finally,当目标方法抛出异常时,@AfterReturning不执行,但是@After执行
- @Pointcut:切入点
- 作用:当较多的通知增强方法使用相同的execution切入点表达式时,编写、维护、均较为麻烦;AspectJ提供了@Pointcut注解,用于定义切入点表达式;
- 用法:
- 属性:
- value:切入点表达式,表示切面的功能执行的位置
- 作用域:方法
- 特点:
- 当一个方法使用该注解后,该方法的名称就是切入点表达式的别名,可直接替换其他注解的value值;
- 其他通知注解的value值为该注解的方法名时,默认切入点表达式为该value值中方法名对应方法的注解的value值;
- 属性:
- @Aspect:是AspectJ框架中的注解
- 切面执行的位置:使用的是切面表达式
- 解释:
- modifiers-pattern:访问权限类型
- ret-type-pattern:返回值类型
- declaring-type-pattern:包名类名
- name-pattern(param-pattern):方法名(参数类型和参数个数)
- throw-pattern:抛出异常类型
- ?:标识可选的部分
- 中文含义:execution(访问权限 方法返回值 方法声明(参数) 异常类型)
- 切入点表达式要匹配的对象就是目标方法的方法名,所以execution表达式中明显就是方法的签名;
- 使用pattern的部分可以使用通配符
*
:表示任意多个任意字符..
:用在方法参数中,表示任意多个参数;用在包名后,表示当前包以及子包路径+
:用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类
- 解释:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
- 通知方法的参数:
- JoinPoint:代表正在加入切面功能的业务方法
- 作用:可以在通知方法中获取目标业务方法执行时的信息,例如方法名、方法实参等
- 使用:该参数由框架赋予,必须是通知方法的第一个参数
- ProceedingJoinPoint:等同于动态代理参数中的Method
- 作用:作用为执行目标方法,同时因为继承了JoinPoint,所以JoinPoint的属性依旧有效
- 使用:在环绕通知@Around中使用
- JoinPoint:代表正在加入切面功能的业务方法
Q.E.D.