spring

后端 / 笔记 / 2021-10-21

Spring

1. Spring概述

1.1 什么是Spring?

spring是一个java框架,使用java语言开发的轻量级,开源框架。可以在j2se,j2ee项目中使用。

Spring核心技术:IOC,AOP

Spring作用:实现解耦,解决对象之间的耦合,模块之间的耦合。

1.2 Spring官网

https://spring.io/

1.3 Spring优点

(1) 轻量

​ Spring框架实用的jar都比较小,一般在1M以。Spring核心功能所需jar总共在3M左右。

(2) 针对接口编程,解耦

​ Spring提供了IOC控制反转,由容器管理对象,对象的依赖关系。

对象之间依赖解耦合

(3) AOP编程支持

​ 通过SpringAOP功能,方便进行面向切面编程,许多传统OOP实现功能,都可以通过AOP轻松解决,提高了开发效率。

(4) 方便集成各种框架

​ Spring不排斥各种优秀的开源框架,相反Spring还兼容各种优秀的框架。

2. IOC控制反转

2.1 概念

​ IOC,Inversion of Control:控制反转,是一个理论,一种指导思想。指导开发人员如何使用对象,管理对象.把对象的创建,属性赋值,对象生的命周期交给代码之外的容器管理。

  1. IOC 分为控制反转

​ 控制:对象创建,属性赋值,对象生命周期管理

​ 反转: 把开发人员管理对象的权限,转移给代码之外的容器实现。由容器完成对象的管理,通过容器使用容器中的对象。

  1. DIDependency Injection依赖注入

    是IOC的一种技术实现。程序猿只需要提供要使用的对象的名称就可以了,对象如何创建,如何从容器中查找,获取都由容器内部自己实现。

  1. 依赖

    如果ClassA使用了ClassB的属性或方法,叫做ClassA依赖ClassB。

  2. Spring通过DI实现IOC

    通过Spring框架,只需要提供对象名称就可以了。从容器中获取名称对应的对象。

    Spring底层是通过,反射机制,通过反射创建对象。

2.2 第一个Spring程序

2.2.1 创建maven项目

image20211014173626582.png

image20211014173745040.png

image20211014173815616.png

更改项目SDK版本号

更改模块SDK版本号
image20211014174157824.png

image20211014174233427.png

image20211014174257269.png

修改pom.xml中java版本

image20211014174347261.png

2.2.2 添加Spring依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>spring-01</artifactId>
  <version>1.0-SNAPSHOT</version>



  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
  <!--单元测试-->
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  <!--Spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.9</version>
    </dependency>

  </dependencies>
</project>

image20211014175523673.png

2.2.3 Spring配置文件

<?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">


</beans>
  • 根标签是 beans
  • beans 后面是约束文件说明
  • beans 里面是bean声明
  • 什么是bean?
    • bean就是java对象,Spring容器管理java对象

2.3 Spring容器创建对象特点

  • 容器对象ApplicationContext:接口
    • 通过ApplicationContext对象,获取要实用的其他java对象,执行getBean(id)
  • Spring默认调用类的无参构造方法,创建对象
  • Spring读取配置文件,一次创建好所有java对象,放到map中

2.4 DI:给属性赋值

Spring调用类的无参构造方法,创建对象,对象创建后给属性赋值.

给属性赋值有两种方式

  • 使用xml标签和属性赋值
  • 使用注解

DI分类

  • set注入设值注入
  • 构造注入

2.4.1 基于XML的DI

在xml配置文件中使用标签和属性,完成对象的创建,属性赋值。

set注入

概念: Spring调用类中的set方法,在set方法中可以完成属性赋值。推荐使用

简单类型的set注入 使用 value

<bean id="myStudent" class="com.lu.pack01.Student">
  <property name="name" value="luckyFang"/>  <!-- setName() -->
  <property name="age" value="22"/> <!-- setAge() -->
</bean>

引用类型的set注入 使用 ref

<!--    Spring bean 声明顺序无关,但是区分大小写-->

<!-- 首先创建school bean-->
<bean id="mySchool" class="com.lu.pack02.School">
  <property name="name" value="山西晋中理工学院"/>
  <property name="address" value="晋中市榆次区张庆乡工业园区"/>
</bean>

<!--    其次使用 ref 引用 school bean-->
<bean id="myStudent" class="com.lu.pack02.Student">
  <property name="name" value="luckyFang"/>
  <property name="age" value="22"/>
  <property name="school" ref="mySchool"/> <!-- setSchool() -->
</bean>

构造注入

构造注入: Spring调用类中的有参数构造方法,在对象创建时给属性赋值

<bean id="mySchool" class="com.lu.pack03.School">
  <constructor-arg index="0" value="山西晋中理工学院"/>
  <constructor-arg index="1" value="山西省晋中市榆次区"/>
</bean>

<!--   可以省略index 属性 默认顺序 0 1 2 3 -->
<bean id="mySchool02" class="com.lu.pack03.School">
  <constructor-arg value="山西晋中理工学院"/>
  <constructor-arg value="山西省晋中市榆次区"/>
</bean>


<bean id="myStudent" class="com.lu.pack03.Student">
  <constructor-arg name="name" value="luckyFang"/>
  <constructor-arg name="age" value="22"/>
  <constructor-arg name="school" ref="mySchool02"/>
</bean>


<bean id="myFile" class="java.io.File">
  <constructor-arg name="parent" value="/Users/luckyfang/Desktop"/>
  <constructor-arg name="child" value="第一章梳理逻辑.pdf"/>
</bean>
自动注入

概念: Spring可以根据某些规则给引用类型完成赋值。只对引用类型有效,规则byNamebyType

  • byName

    • 按名称注入: java类中引用类型属性名称和spring容器中bean的id名称一样的,并且数据类型也一致,这些bean能够赋值给引用类型。
    <!--byName注入
        1. 按名称注入  java类中引用类型的属性名称和 spring容器中的 bean id一样,数据类型一致
        语法:
            <bean id  = "xxx" class ="xxx" autowire  = "byName">
                简单属性赋值
            </bean>
    
    -->
    
    
    <!--这里的id 要和 属性中的 名称一致-->
    <bean id="school" class="com.lu.pack04.School">
      <property name="name" value="山西晋中理工学院"/>
      <property name="address" value="山西省晋中市榆次区"/>
    </bean>
    
    <!--所有属性规则 按照 byName 赋值-->
    <bean id="myStudent" class="com.lu.pack04.Student" autowire="byName">
      <property name="name" value="张三"/>
      <property name="age" value="22"/>
    </bean>
    
  • byType

    • 按类型注入: java类中引用类型输入类型和Spring容器中bean的class值是同源关系,这样的bean就会赋值给引用类型。
    <!-- byType注入
        按类型注入,java中引用类型数据类型和ban的class 是同源的
        这些bean能够赋值给引用类型
        同源:
            1. java引用数据类型和bean的calss值是一样的
            2. java引用数据类型和bean的class值是父子关系
            3. java引用数据类型和bean的class值是接口实现关系
    
        注意:
            在整个xml文件中,符合条件的对象只能有一个 不然会引发二义性问题
    -->
    
    <!--    <bean id="mySchool" class="com.lu.pack05.School">-->
    <!--        <property name="name" value="山西晋中理工学院"/>-->
    <!--        <property name="address" value="山西省晋中市榆次区"/>-->
    <!--    </bean>-->
    
    <!--所有属性规则 按照 byType 赋值-->
    <bean id="myStudent" class="com.lu.pack05.Student" autowire="byType">
      <property name="name" value="张三"/>
      <property name="age" value="22"/>
    </bean>
    
    
    <!--    声明子类 子类被注入-->
    <bean id="primerySchool" class="com.lu.pack05.PrimarySchool">
      <property name="name" value="山西应用科技学院"/>
      <property name="address" value="山西省太原市小店区"/>
    </bean>
    
    

    在整个xml文件中,符合条件的对象只能有一个 不然会引发二义性问题

多文件配置

目的:减少单文件大小,提高代码可读性

按功能模块划分

  • 一个模块一个配置文件

按类的功能划分

  • 配置文件 事务 等一个文件

包含管理配置文件

  • ApplicationContext.xml
    • aaa.xml
    • bbb.xml
    • ccc.xml
<import resource ="filepath1"/>
<import resource ="filepath2"/>

classpath: 表示类路径,也就是类文件所在的目录。
什么时候使用classpath? 在一个文件中使用其他文件,就需要使用classpath
<!--
        总配置文件,包含多个配置文件,一般不声明bean
        语法:
            <import resource = "classpath:"/>
    -->
<import resource="classpath:pack06/spring-student.xml"/>-->
<import resource="spring-school.xml"/>-->

当然也可以使用通配符*

<!--包含关系配置文件可用通配符 * 表示任意字符
        注意: 总文件不能包含在通配符范围内
    -->
<import resource="classpath:pack06/spring-*.xml"/>

注意: 主文件不能包含在通配符范围内死循环

2.4.2 基于注解的DI

使用Spring提供的注解来完成配置。

  • @Component
  • @Value
  • @AutoWired
  • @Qualifier
  • @Resource
开启注解支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 开启注解扫描-->
    <context:component-scan base-package="com.lu.pack01"/>
</beans>
  • base-package: 注解所在的包,会扫描包及其子包

扫描多个包

  • 多次使用扫描器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 开启注解扫描-->
    <!--    多次使用扫描器-->
    <context:component-scan base-package="com.lu.pack01"/>
    <context:component-scan base-package="com.lu.pack02"/>
    <context:component-scan base-package="com.lu.pack03"/>
    
</beans>
  • 使用分隔符, or ;
<!--分隔符扫描-->
<context:component-scan base-package="com.lu.pack01;com.lu.pack02;com.lu.pack03"/>
  • 指定父包
<!--指定父包-->
<context:component-scan base-package="com.lu"/>
@Componet

需要在类上使用注解@Component该注解的value属性用于指定该bean的id

package com.lu.pack01;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


/**
 * @Component 表示创建对象放到容器中
 * 相当于 <bean></bean>
 * 属性: value
 * 相当于 bean id
 * 位置: 在类的上面,表示此类的对象
 * <bean id "myStudent" class = "com.lu.pack01.Student"/>
 */

// @Component(value = "myStudent")
// value 也可以省略
// 如果不提供 value 则采用默认值,对象名(小写)
@Component("myStudent") 
public class Student {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

当然除了@Component注解用来创建对象,还有

  • @Repository: 放在dao接口实现类上,表示创建一个doa对象 (持久层对象)
  • @Service: 放在业务层接口实现类上,表示业务层对象 业务层有事务功能
  • @Controller: 放在控制器类上面的对象,用于表示层

以上4个注解都能创建对象,表示对象是分层的。

@Value

值注入

Spring中值注入有两种

  • 属性注入
  • set注入
package com.lu.pack02;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


@Component(value = "myStudent")
public class Student {
    /**
     * 简单类型属性赋值
     *
     * @value 简单类型属性赋值
     * 属性: value
     * 位置:
     * 1. 属性定义上面,无需set方法
     * 2. 在set方法上面
     */
  	@Value("jack")
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

		@Value("22")
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

这样注入值没意义,如果我们要动态修改值呢?

读取外部配置文件注入值

<!-- 读取配置文件到spring-->
<context:property-placeholder location="classpath:/config.properties"/>
myName=Fangfang
myAge=22

然后使用 @Value("${")来实现注入

package com.lu.pack02;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


@Component(value = "myStudent")
public class Student {
    /**
     * 简单类型属性赋值
     *
     * @value 简单类型属性赋值
     * 属性: value
     * 位置:
     * 1. 属性定义上面,无需set方法
     * 2. 在set方法上面
     */
    @Value("${myName}")
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    @Value("${myAge}")
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

@Autowired

Spring框架提供的,给引用类型赋值,自动注入注解

  • required(boolean)
    • true: 在spring启动时,创建容器对象,会检查引用类型是否赋值成功,如果赋值失败程序终止,然后报错
    • false: 在spring启动时,创建容器对象,会检查引用类型是否赋值成功,如果赋值失败 程序正常执行,不报错,引用类型值为null

支持

  • byName
  • byType默认

使用位置

  • 属性定义上面
  • 在set方法上面
package com.lu.pack03;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("mySchool")
public class School {
    private String name;
    private String addr;

    @Value("山西晋中理工学院")
    public void setName(String name) {
        this.name = name;
    }

    @Value("山西省晋中市榆次区张庆乡工业园区")
    public void setAddr(String addr) {
        this.addr = addr;
    }
    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", addr='" + addr + '\'' +
                '}';
    }
}

package com.lu.pack03;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


@Component(value = "myStudent")
public class Student {
    /**
     * 简单类型属性赋值
     *
     * @value 简单类型属性赋值
     * 属性: value
     * 位置:
     * 1. 属性定义上面,无需set方法
     * 2. 在set方法上面
     */
    @Value("${myName}")
    private String name;
    private int age;
    private School school;

  	// 自动注入
    @Autowired
    public void setSchool(School school) {
        this.school = school;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Value("${myAge}")
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

Student{name='Fangfang', age=22, school=School{name='山西晋中理工学院', addr='山西省晋中市榆次区张庆乡工业园区'}}

byType注入

package com.lu.pack03;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


@Component(value = "myStudent")
public class Student {
    /**
     * 简单类型属性赋值
     *
     * @value 简单类型属性赋值
     * 属性: value
     * 位置:
     * 1. 属性定义上面,无需set方法
     * 2. 在set方法上面
     */
//    @Value("jack")
    @Value("${myName}")
    private String name;
    private int age;
    private School school;

    // byType注入
    @Autowired
    public void setSchool(School school) {
        this.school = school;
    }

    public void setName(String name) {
        this.name = name;
    }

    //    @Value("22")
    @Value("${myAge}")
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

@Qualifier

byName注入时指定要注入对象名称 结合 @AutoWired使用

byName注入

package com.lu.pack03;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


@Component(value = "myStudent")
public class Student {
    /**
     * 简单类型属性赋值
     *
     * @value 简单类型属性赋值
     * 属性: value
     * 位置:
     * 1. 属性定义上面,无需set方法
     * 2. 在set方法上面
     */
//    @Value("jack")
    @Value("${myName}")
    private String name;
    private int age;
    private School school;


    // byName注入
    @Autowired
    @Qualifier("mySchool")
    public void setSchool(School school) {
        this.school = school;
    }

    public void setName(String name) {
        this.name = name;
    }

    //    @Value("22")
    @Value("${myAge}")
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

@Resource

是jdk自带的注解 默认按名称注入

作用

  • 属性上
  • set方法上

先使用 byName赋值,如果找不到则使用byType赋值

如果指定 name参数 则 强制采用 byName 注入

package com.lu.pack04;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;


@Component(value = "myStudent")
public class Student {
    /**
     * 简单类型属性赋值
     *
     * @value 简单类型属性赋值
     * 属性: value
     * 位置:
     * 1. 属性定义上面,无需set方法
     * 2. 在set方法上面
     */
//    @Value("jack")
    @Value("${myName}")
    private String name;
    private int age;
    private School school;


    // 这里是 byType注入成功
    @Resource
    public void setSchool(School school) {
        this.school = school;
    }

    public void setName(String name) {
        this.name = name;
    }

    //    @Value("22")
    @Value("${myAge}")
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

2.5 IOC总结

IOC: 管理对象的,把对象放到容器中,创建赋值,管理依赖关系

IOC: 通过管理对象,实现解耦,IOC解决业务处理逻辑中对象之间的耦合关系。

Spring作为容器适合管理什么对象?

  • service 对象 dao 对象
  • 工具类对象

不适合什么对象?

  • 实体类
  • servlet listener filter 等 web对象 因为他们是 tomcat管理的

3. AOP面向切面编程

在源码中,不改变原有方法增加功能。

3.1 传统开发面临问题

  • 源码改动可能比较多
  • 重复代码比较多
  • 代码难以维护

3.2 AOP概念

什么是AOP?

​ AOP(Aspect Orient Programing):面向切面编程

​ Aspect: 表示切面:给业务方法增加的功能,叫做切面一般都是非业务功能,并且切面是可以复用的,比如日志,事务,权限检查,参数检查,统计信息等。

Orient: 面向切面

Programming: 编程

3.3 AOP作用

  • 提高切面复用率
  • 让开发人员专注于业务逻辑,提高开发效率
  • 实现业务功能和其他非业务功能解耦合
  • 给存在的业务方法增加功能,不用修改原来代码

3.4 AOP中术语

  • Aspect: 切面,给业务方法增加的功能。
  • JoinPoint: 连接点,连接切面的业务方法。在这个业务方法执行时,会同时执行切面的功能
  • PointCut: 切入点,是一个或多个连接点集合。表示这些方法执行时,都能增加切面的功能。
  • target: 目标对象,给哪个对象添加切面功能,那么这个对象就是目标
  • **Advice:**通知(增强),表示切面的执行时间。在目标方法执行前,还是目标方法执行后执行切面。

在 Advice时间,在PointCut位置,执行Aspect

AOP本质是动态代理

3.5 什么时候用AOP

要给某些方法增加相同的功能。源代码不方便修改。

给业务方法增加非业务功能也可以使用AOP

3.6 AOP

下面我们来用AOP实现日志功能。

导入依赖

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.7</version>
</dependency>

标注一个切面

package com.lu.log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class Log {
    @Before("execution(* com.lu.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("before ...");
    }
    @Before("execution(* com.lu.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("after ...");
    }
    @Around("execution(* com.lu.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint joinPoint){
        System.out.println("around before...");
        try {
            joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("around after");

    }
}

package com.lu.service;

public interface UserService {
    void add();
    void delete();
    void modify();
    void query();
}

package com.lu.service;

import org.springframework.stereotype.Component;

@Component("userService")
public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("add method");
    }

    @Override
    public void delete() {
        System.out.println("delete method");
    }

    @Override
    public void modify() {
        System.out.println("modify method");
    }

    @Override
    public void query() {
        System.out.println("query method");
    }
}

  • @Before: 前置通知 执行方法前
  • @After: 后置通知 执行方法后
  • @Around: 环绕通知执行方法时

关于execution表达式

  • 返回值 *
  • 要拦截的类 com.xxx
  • 下所有方法 .*
  • 任意参数(..)