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://spring.io/

  • 官方下载地址: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中创建对象的方式

  1. 构造方法:new
  2. 反射创建
  3. 序列化
  4. 克隆
  5. IOC:容器创建对象
  6. 动态代理

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项目的实现

  1. 创建maven项目
  2. 加入maven依赖
    • spring依赖
    • 单元测试依赖
  3. 创建类
    • 接口和实现类
  4. 创建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的实现方式有两种:
  1. 在spring的配置文件中,使用标签和属性完成,叫做基于XML的DI实现;
  2. 使用spring中的注解,完成属性赋值,叫做基于注解的DI实现;
    DI的语法分类:
  3. set注入:spring调用类的set方法,在set方法中实现属性的赋值;
  4. 构造注入:spring调用类的有参构造方法,创建对象;在构造方法中完成赋值;
4.1.1.1 set注入

set注入:spring调用类的setter方法,给属性赋值

  1. 简单类型的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>
  1. 引用类型的set注入:
		<bean id="xx" class="xx.xxx.xx">  
			 <property name="属性名" ref="bean's id" />  
		</bean>
4.1.1.2 构造注入
  1. 构造注入:在构造调用者实例的同时,完成被调用者的实例化;即使用构造器设置依赖关系;
    • 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的值是接口和实现类的
	<!-- 当存在多个同源关系的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方法
  • 作用:给属性赋值
  • 原理:
    1. 在属性上,无需set方法,使用放射实现
    2. 在set方法上, 通过set方法注入,二者都存在时按照set方法实现
  • 属性:
    • value:赋值的值;
4.2.1.6 @Autowired
  • 作用域:对象/属性/set方法
  • 作用:实现引用类型的赋值
  • 原理:通过自动注入原理实现,默认通过byType自动注入实现
  • 属性:
    • required:布尔类型,当值为true(默认为true)时表示引用类型赋值失败时,程序抛出异常并终止执行;当值为false时,程序正常执行,引用类型返回null
      • 原理:当required为true时,spring会在程序时检查赋值行为的正确性,当发现异常时会提前抛出;减少程序执行后抛出的NP异常;
  • 联合注解:
    • @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根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才成立;
  • 动态代理的作用:
    1. 在目标类源代码不改变的情况下增加功能;
    2. 减少代码的重复;
    3. 专注业务逻辑代码;
    4. 解耦合;

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),一般是一些非业务的逻辑代码,很多业务逻辑中可能使用到,所以单独拿出来;
  • 常见的切面功能:事务、日志、统计信息、参数检查、权限验证;
  • 常用的术语:
    1. JoinPoint:连接点,连接业务方法和切面的位置,就是类中的业务方法
    2. PointOut:切入点,指多个连接点方法的集合,多个方法
    3. 目标对象:给哪个类的方法增加功能,这个类就是目标对象
    4. Advice:通知,标识切面功能执行的时间

5.4 AOP的实现

  1. spring:spring在内部实现了aop规范,但是spring主要在事务处理时使用aop,实际应用场景中因为spring的aop比较笨重而很少使用;
  2. aspectJ:一个开源的aop框架,是一个来自于Eclipse的开源项目;spring框架中集成了aspectJ框架,因为可以通过spring使用;
    • aspectJ框架实现aop的方式有两种:
      1. 使用xml的配置文件:常用来配置全局事务
      2. 使用注解:最常用的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值;
  • 切面执行的位置:使用的是切面表达式
    • 解释:
      • 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中使用

Q.E.D.


If you don't come, I will snow.