Spring

学习来源于B站狂神说

介绍

Spring框架是一个开放源代码J2EE应用程序框架,由[Rod Johnson](https://baike.baidu.com/item/Rod Johnson/1423612)发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。 -百度百科

相关文档:

  1. Spring Framework
  2. Spring Framework Overview
  3. Overview (Spring Framework 5.3.22 API)
  4. GitHub - spring-projects/spring-framework: Spring Framework
  5. Maven Repository: org.springframework » spring-webmvc (mvnrepository.com)

SSM:

  1. Spring
  2. Spring MVC
  3. Mybatis

优点

  1. 开源免费的容器(框架)
  2. 轻量级、非入侵式框架
  3. 控制反转(IOC)、面向切面编程(AOP)
  4. 支持事务处理,对框架整合的支持
  5. 总结:Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)编程的框架

扩展

Spring Boot

  • 一个快速开发的脚手架
  • 基于SpringBoot可以快速开发单个微服务
  • 约定大于配置

Spring Cloud

  • SpringCloud基于SpringBoot实现

IOC

简单入门:-> Spring01

UserDao接口

UserDaoImpl实现类

UserService业务接口

UserServiceImpl业务实现

IOC本质

-> Spring02

Pojo和Dao以及Service的联系与区别

Hello 对象是谁创建的?

  • hello对象是由Spring创建的

Hello对象的属性是怎么设置的?

  • hello对象的属性是由Spring容器设置的,这个过程就叫控制反转:
  • 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的.
  • 反转:程序本身不创建对象,而变成被动的接收对象.
  • 依赖注入:就是利用set方法来进行注入的.

IOC是一种编程思想,由主动的编程变成被动的接收,可以通过newClassPathXmlApplicationContext去浏览一下底层源码 。OK ,到了现在,我们彻底不用再程序中去改动了,要实现不同的操作,只需要在xm|配置文件中进行

构造函数

-> Spring03

在配置文件加载的时候,容器中管理的对象就已经初始化了

spring默认使用无参构造函数创建bean

  1. index索引

    1
    2
    3
    4
    <bean id="user" class="org.example.pojo.User">
    <constructor-arg index="0" value="hello"></constructor-arg>
    <constructor-arg index="1" value="18"></constructor-arg>
    </bean>
  2. 属性名

    1
    2
    3
    4
    <bean id="user" class="org.example.pojo.User">
    <constructor-arg name="name" value="hello"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    </bean>
  3. 属性类型(不推荐,同个类型的多个变量你咋整)

    1
    2
    3
    4
    <bean id="user" class="org.example.pojo.User">
    <constructor-arg type="java.lang.String" value="hello"></constructor-arg>
    <constructor-arg type="int" value="18"></constructor-arg>
    </bean>

    Spring配置

-> Spring03

别名-alias

1
2
<!--todo alias 别名: name对应id所属bean, alias可以起别名-->
<alias name="user" alias="user2"/>

bean配置

1
2
3
4
5
6
7
8
<!--todo bean配置
1. name:可以起多个别名,用空格、逗号、分号进行分割
2. id bean唯一标识符,相当于对象名
3. class:bean对象对应的全限定名:包名+类型-->
<bean id="user" class="org.example.pojo.User" name="u u1,u2;u3">
<constructor-arg name="name" value="hello"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
</bean>

import

一般用于团队开发使用:可以将多个配置文件,导入合并为一个

1
2
3
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>

DI依赖注入

-> Spring04

构造器注入

同构造函数(spring03)

Set方式注入

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
<!--todo set注入-->
<bean id="student" class="org.example.dao.Student">
<!--todo 普通注入-->
<property name="name" value="小明"/>
<!--todo bean注入 ref-->
<property name="address" ref="address"></property>
<!--数组-->
<property name="books">
<array>
<value>三国演义</value>
<value>水浒传</value>
<value>西游记</value>
</array>
</property>
<!--List-->
<property name="hobbies">
<list>
<value>游泳</value>
<value>睡觉</value>
<value>打游戏</value>
</list>
</property>
<!--map-->
<property name="card">
<map>
<entry key="银行卡" value="1234567890"/>
<entry key="身份证" value="987654321"/>
</map>
</property>
<!--set-->
<property name="games">
<set>
<value>lol</value>
<value>bob</value>
<value>hhh</value>
</set>
</property>
<!--null-->
<property name="wife">
<null></null>
</property>
<!--properties-->
<property name="info">
<props>
<prop key="性别"></prop>
<prop key="学号">123123123</prop>
</props>
</property>
</bean>

拓展方式注入

1
2
3
4
<!--todo p命名空间注入 直接注入属性的值 property-->
<bean id="address" class="org.example.dao.Address" p:address="江西"/>
<!--todo c命名空间注入 构造方法注入-->
<bean id="address2" class="org.example.dao.Address" c:address="广东"/>

Bean作用域(scopes)

Bean Scopes

1
2
3
4
5
<!--todo 单例模式 - 默认机制 - 保持所有创建的对象都为单例-->
<bean id="address3" class="org.example.dao.Address" p:address="江西" scope="singleton"/>
<!--todo 原型模式,每次从容器中获取都会产生一个新对象-->
<bean id="address4" class="org.example.dao.Address" p:address="广东" scope="prototype"/>
<!--其余的request、session、application只能在web开发中使用-->

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* todo scope机制 单例模式、原型模式
*/
@Test
public void test3() {
ApplicationContext context = new ClassPathXmlApplicationContext("pBeans.xml");
/*todo 单例模式*/
Address address3 = context.getBean("address3", Address.class);
Address address4 = context.getBean("address3", Address.class);
System.out.println(address3 == address4); //true
/*todo 原型模式*/
Address address5 = context.getBean("address4", Address.class);
Address address6 = context.getBean("address4", Address.class);
System.out.println(address5 == address6); //false

}

自动装配beans(重点)

  • 自动装配是Spring满足bean依赖一种方式
  • Spring会在 上下文中自动寻找,并自动给bean装配属性!

在Spring中有三种装配的方式

  1. 在xml中显式的配置

    -> Spring05

    首先导入需要装配的bean

    1
    2
    <bean id="dog" class="org.example.dao.Dog"></bean>
    <bean id="cat" class="org.example.dao.Cat"></bean>
    • byname的时候,需要保证所有bean的id唯一, 并且这个bean需要和自动注入的属性的set方法的值一致!

      1
      2
      3
      4
      <!--todo 按名称byname 需要保证所有bean的id唯一, 并且这个bean需要和自动注入的属性的set方法的值一致-->
      <bean id="human" class="org.example.dao.Human" autowire="byName">
      <property name="name" value="小明"/>
      </bean>
    • bytype的时候,需要保证所有bean的class唯一, 并且这个bean需要和自动注入的属性的类型一致!

      1
      2
      3
      4
      <!--todo 按类型bytype 需要保证所有bean的class唯一-->
      <bean id="human" class="org.example.dao.Human" autowire="byType">
      <property name="name" value="小明"/>
      </bean>
  2. 在java中显示配置 - 注解实现自动装配

    -> Spring07

    1. 指定要扫描的包

      1
      2
      <!--todo 指定要扫描的包-->
      <context:component-scan base-package="org.example.pojo"/>
    2. 开启注解支持

      1
      2
      <!--todo 开启注解支持-->
      <context:annotation-config></context:annotation-config>
    3. 注解实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      /*todo Component组件:等价于<bean id="human" class="org.example.pojo.Human"/>*/
      /*@Scope("prototype") 作用域设置*/
      @Component
      public class Human {
      /*todo Value : 相当于<property name="name" value="小明"/>*/
      @Value("小明")
      private String name;
      private Dog dog;
      private Cat cat;
      //......
      }

      @Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!

    • dao [@Repository]

    • service [@Service]

    • controller [ @Controller]

      这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean

  3. 隐式的自动装配bean [重要]

    -> Spring06

    Core Technologies (spring.io)

    1. 开启注解支持

      1
      2
      <!--todo 开启注解支持-->
      <context:annotation-config></context:annotation-config>
    2. 导入bean

      1
      2
      3
      4
      <bean id="human" class="org.example.dao.Human"></bean>
      <bean id="cat1" class="org.example.dao.Cat"/>
      <bean id="cat2" class="org.example.dao.Cat"/>
      <bean id="dog1" class="org.example.dao.Dog"/>

    @Resource和@ Autowired的区别:都是用来自动装配的,都可以放在属性字段上

    • @Autowired通过byname的方式实现,而且必须要求这个对象存在!

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      /* todo 使用注解就可以不用编写set方法了
      * 可以在属性上使用也可以在set方法上使用
      * Autowired:先找类型再找名称
      * Qualifier可以指定bean唯一的注入对象
      * 如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解[@Autowired] 完成的时候、我们可以
      * 使用@Qualifier(value=" xxx' )去配置@Autowired的使用,指定一个唯-的bean对象注入!
      * */
      @Qualifier(value = "cat1")
      @Autowired
      //todo Nullable说明该字段可以为空
      //@Nullable
      private Cat cat;
    • @Resource默认通过byname的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错

      1
      2
      3
      /*todo Resource:先名字后类型*/
      @Resource(name = "dog1")
      private Dog dog;

      xml与注解:

  • xml更加万能,适用于任何场合!维护简单方便
  • 注解不是自己类使用不了,维护相对复杂!
  • xml与注解最佳实践:
    • xml用来管理bean;
    • 注解只负责完成属性的注入;
    • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持

使用Java方式装配Spring

-> Spring08

Core Technologies (spring.io)

直接使用java类的方式去实现配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example.config;

import org.example.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Configuration
//todo 扫描该包下的所有类
@ComponentScan("org.example.pojo")
public class UserConfig {
//todo 注册一个bean
// 该方法的名字相当于bean标签的id属性
// 该方法的返回值相当于bean标签的class属性
@Bean
public User getUser() {
return new User();
}
}

使用方法有所改变:如下:

1
2
3
4
5
6
7
8
@Test
public void test() {
//ClassPathXmlApplicationContext
// todo 使用AnnotationConfigApplicationContext去获取配置文件
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser.getName());
}

AOP

image-20220822094841860

使用Spring实现AOP

-> Spring09

横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…

  • 切面(ASPECT) :横切关注点被模块化的特殊对象。即,它是一个类。
  • 通知(Advice) :切面必须要完成的工作。即,它是类中的一一个方法。
  • 目标(Target) :被通知对象。
  • 代理(Proxy) :向目标对象应用通知之后创建的对象。
  • 切入点(PointCut) :切面通知执行的“地点”的定义。
  • 连接点JointPoint) :与切入点匹配的执行点。

image-20220822094830210

方式一:使用spring API接口实现aop

1
2
3
4
5
6
7
8
9
<!--todo 方式一:spring api接口配置-->
<!--需要导入aop的模块-->
<aop:config>
<!--todo 切入点:excution(要执行的位置)-->
<aop:pointcut id="pointcut" expression="execution(* org.example.service.UserServiceImpl.*(..))"/>
<!--todo 执行环境: 环绕增加-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>

方式二:自定义实现aop(主要是切面定义)

首先定义方法执行前后要执行的函数

1
2
3
4
5
6
7
8
9
10
11
package org.example.diy;

public class DiyPointCut {
public void before() {
System.out.println("方法执行前");
}

public void after() {
System.out.println("方法执行后");
}
}

其次,在xml文件中进行AOP切入

1
2
3
4
5
6
7
8
9
10
11
<!--todo 方式二:使用自定义实现AOP(主要是切面定义)-->
<bean id="diyPointCut" class="org.example.diy.DiyPointCut"/>
<aop:config>
<!--todo 自定义切面,ref要引用的类-->
<aop:aspect ref="diyPointCut">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* org.example.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>

方式三:使用注解方式

首先,创建注解类去实现AOP,注意一下切入的执行顺序

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
package org.example.diy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/*todo 使用注解方式实现AOP*/
@Aspect
public class AnnotationPointCut {
@Before("execution(* org.example.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("方法执行前");
}

@After("execution(* org.example.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("方法执行后");
}

@Around("execution(* org.example.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
//执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}

其次在beans.xml文件中去导入bean和开启注解支持

1
2
3
4
<!--todo 方式三:使用注解方式-->
<bean id="annotationPointCut" class="org.example.diy.AnnotationPointCut"/>
<!--todo 开启注解支持-->
<aop:aspectj-autoproxy/>

结合Mybatis

步骤:

  1. 导入相关jar包

    • junit
    • mybatis
    • mysq|数据库
    • spring相关的
    • aop注入
    • mybatis-spring [new]
    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
    <dependencies>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
    </dependency>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.22</version>
    </dependency>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.22</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9.1</version>
    </dependency>
    </dependencies>
  2. 编写配置文件

  3. 测试

整合Spring

  1. 编写数据源配置

    将大部分mybatis操作写至spring-dao.xml文件中去

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--todo 使用Spring提供的JDBC-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url"
    value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai"/>
    <property name="username" value="root"/>
    <property name="password" value="333333"/>
    </bean>
    </beans>

    mybatis-config只需简单配置包别名即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    <!--todo 包别名-->
    <!-- 引用的包的类默认小写-驼峰命名-->
    <typeAliases>
    <package name="org.example.pojo"/>
    </typeAliases>
    </configuration>
  2. sqlSessionFactory

    在spring-dao.xml配置sqlSessionFactory并引入mybatis-config配置文件

    1
    2
    3
    4
    5
    6
    7
    <!--sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <!--绑定Mybatis配置文件-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:org/example/dao/*.xml"/>
    </bean>
  3. sqISessionTemplate

    在spring-dao.xml配置sqISessionTemplate

    1
    2
    3
    4
    <!--SqlSessionTemplate 就是之前的sqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
  4. 需要给接口加实现类【】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package org.example.dao;

    import org.example.pojo.Dept;
    import org.mybatis.spring.SqlSessionTemplate;

    import java.util.List;

    public class DeptMapperImpl implements DeptMapper1 {
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
    this.sqlSession = sqlSession;
    }

    @Override
    public List<Dept> getAllDept() {
    DeptMapper1 mapper = sqlSession.getMapper(DeptMapper1.class);
    return mapper.getAllDept();
    }
    }
  5. 将自己写的实现类,注入到Spring中,

    1
    2
    3
    <bean id="deptMapper" class="org.example.dao.DeptMapperImpl">
    <property name="sqlSession" ref="sqlSession"></property>
    </bean>
  6. 测试使用即可!

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void test() throws IOException {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    DeptMapper1 deptMapper = context.getBean("deptMapper", DeptMapper1.class);
    for (Dept dept : deptMapper.getAllDept()) {
    System.out.println(dept);
    }
    }

    另有方式二,看代码->DeptMapperImpl2

声明式事务

-> Spring11

事务

  • 把一组业务当成- -个业务来做;要么都成功,要么都失败!
  • 事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能马虎!
  • 确保完整性和一致性

事务的作用:如果不配置事务,可能存在数据提交不一致的情况

事务ACID原则:

  • 原子性

  • 一致性

  • 隔离性

    多个业务可能操作同-个资源,防止数据损坏

  • 持久性

    事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中!

spring中的事务管理

  1. 声明式事务:AOP

    全程在spring-dao.xml文件进行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <!--todo 1. 配置声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="dataSource"/>
    </bean>

    <!--todo 结合AOP实现事务植入-->
    <!--todo 2. 配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--todo 3.给予那些方法配置事务-->
    <!--todo 4.配置事务的传播特性:new propagation-->
    <tx:attributes>
    <tx:method name="add" propagation="REQUIRED"/>
    <tx:method name="delete" propagation="REQUIRED"/>
    <tx:method name="update" propagation="REQUIRED"/>
    <tx:method name="query" read-only="true"/>
    <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
    </tx:advice>

    <!--todo 5.配置事务切入-->
    <aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* org.example.mapper.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
  2. 编程式事务:代码中进行事务管理