Spring 学习笔记

Spring5

中文文档: https://www.cntofu.com/book/95/index.html

框架概述

  1. 轻量框架
  2. AOP与IOC为核心部分
  3. 解决企业开发的复杂性
  4. 特点
    1. 方便解除耦合, 简化开发
    2. AOP编程支持
    3. 方便测试
    4. 方便和其他框架一起整合使用
    5. 方便进行事物操作
    6. 方便使用API
    7. 源码是Java开发经典示范

下载代码使用Jar包: https://repo.spring.io/

入门案例

SpringDemo1

XML文件中配置对象标签

1
<bean id="user" class="com.springdemo.spring5.User"></bean>

测试加载配置文件

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testAdd(){
//加载Spring配置文件

ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");

User user = context.getBean("user", User.class);
System.out.println(user);
user.add();

}

IOC容器

IOC底层原理

IOC: Inversion of control 控制反转

  1. 把对象创建和对象之间和调用过程都交给Spring管理
  2. 降低耦合
  3. 入门案例就是IOC实现

IOC底层原理

  1. XML解析, 工厂模式, 反射

    1. 原始模式

      image-20211017222504004
    2. 工厂模式 (耦合度依旧高 )

      image-20211017223004473
    3. IOC过程 (使用XML和反射进一步降低耦合)

      image-20211017223944133

IOC接口(BeanFactory)

IOC底层就是对象工厂

Spring提供IOC容器实现两种方式(两个接口)

  1. Bean Factory: IOC容器基本实现, 是Spring内部的是使用接口, 不提供开发人员使用

    加载配置文件的时候不会创建对象, 使用是时候才创建对象

  2. ApplicatonContext: BeanFactory的子接口, 更多强大的功能, 开发人员使用

    加载配置文件的时候就会创建对象

    1. ApplicationContext借口有实现类

IOC操作Bean管理(基于XML)

  1. 什么是Bean管理

    1. Spring创建对象
    2. Spring注入属性
  2. Bean管理有两种方式

    1. 基于XML方式创建

      1
      <bean id="user" class="com.springdemo.spring5.User"></bean>
      1. 在Spring中使用bean配置标签, 添加属性就可以创建对象
      2. bean标签的常用基本属性
        1. id: 唯一标识
        2. class: 类的全路径(包类路径)
        3. name: 跟id差不多, 但是可以加特殊字符
      3. 默认也是执行无参数构造(Constructor)
    2. 基于XML方式注入属性

      1. DI: 依赖注入, 就是注入属性

        1. 为IOC中一种具体实现, 需要在创建对象基础之上完成

          第一种注入方式: set方式注入

          1
          2
          3
          4
          5
          6
          <bean id="book" class="com.springdemo.spring5.Book">
          <!-- 使用property标签实现属性注入-->
          <property name="bname" value="易筋经"></property>
          <property name="bauthor" value="达摩"></property>

          </bean>

          第二种注入方式: 用有参数构造器注入

          ​ 如果不输入constructor-arg标签会报错, 因为bean默认调用无参构造器, 如果没有就会报错

          1
          2
          3
          4
          <bean id="orders" class="com.springdemo.spring5.Orders">
          <constructor-arg name="oname" value="电脑"></constructor-arg>
          <constructor-arg name="address" value="China"></constructor-arg>
          </bean>

          constructor-arg标签中还有 index标签

          可以用索引值找对象中的值, 比如 index=”0” 在这里代表”oname”

        2. 使用p名称空间注入

          1. 可以简化xml配置

            先在配置文件中添加一个xmlns:p的名称空间

            1
            2
            3
            4
            <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:p="http://www.springframework.org/schema/p"
            xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

            然后进行属性注入

            1
            2
            <bean id="book" class="com.springdemo.spring5.Book" p:bname="九阳神功" p:bauthor="无名氏">
            </bean>

IOC操作Bean管理(基于注解)

  1. 字面量

    1. null值

      使用null标签

      1
      2
      3
      <property name="baddress">
      <null/>
      </property>
    2. 属性值包含特殊符号

      1. <> <> 转义字符

      2. 把带特殊符号内容写到CDATA

        1
        2
        3
        4
        5
        <property name="baddress">
        <value>
        <![CDATA[<<南京>>]]>
        </value>
        </property>
  2. 注入属性-外部Bean

    1
    2
    3
    4
    <bean id="userService" class="com.springdemo.spring5.service.UserService">
    <property name="userDao" ref="userDaoImpl"></property>
    </bean>
    <bean id="userDaoImpl" class="com.springdemo.spring5.dao.UserDaoImpl"></bean>

    测试一下

    1
    2
    3
    4
    5
    6
    ApplicationContext context =
    new ClassPathXmlApplicationContext("bean2.xml");

    UserService userService = context.getBean("userService", UserService.class);
    // System.out.println(userService);
    userService.add();
  3. 注入属性-内部Bean和级联赋值

    1. 一对多关系: 部门和员工

      这就是内部Bean, 使用嵌套

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <bean id="emp" class="com.springdemo.spring5.bean.Emp">
      <property name="ename" value="lucy"></property>
      <property name="gender" value="girl"></property>
      <property name="dept">
      <bean id="dept" class="com.springdemo.spring5.bean.Dept">
      <property name="dname" value="安保部"></property>
      </bean>
      </property>
      </bean>
    2. 级联赋值

      第一种写法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <bean id="emp" class="com.springdemo.spring5.bean.Emp">
      <property name="ename" value="lucy"></property>
      <property name="gender" value="girl"></property>
      <!-- 级联赋值-->
      <property name="dept" ref="dept"></property>
      </bean>
      <bean id="dept" class="com.springdemo.spring5.bean.Dept">
      <property name="dname" value="财务部"></property>
      </bean>

      第二种写法

      需要先在Emp中生成dept的get方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
          <bean id="emp" class="com.springdemo.spring5.bean.Emp">
      <property name="ename" value="lucy"></property>
      <property name="gender" value="girl"></property>
      <!-- 级联赋值-->
      <property name="dept" ref="dept"></property>
      <property name="dept.dname" value="技术部"></property>
      </bean>
      <bean id="dept" class="com.springdemo.spring5.bean.Dept">
      <property name="dname" value="财务部"></property>
      </bean>

IOC操作Bean管理(xml注入集合属性)

  1. 注入数组类型属性
  2. 注入List集合类型属性
  3. 注入Map集合类型属性
  4. 注入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
<bean id="stu" class="com.springdemo.spring5.collectiontype.Stu">

<property name="course">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<property name="list">
<array>
<value>张三</value>
<value>小三</value>
</array>
</property>
<property name="map">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>

</bean>
  1. 在集合里面设置对象类型值

    1. 先创建多个对象

      1
      2
      3
      4
      5
      6
      <bean id="course1" class="com.springdemo.spring5.collectiontype.Course">
      <property name="cname" value="Spring5"></property>
      </bean>
      <bean id="course2" class="com.springdemo.spring5.collectiontype.Course">
      <property name="cname" value="MyBatis"></property>
      </bean>
    2. 然后引入到list里的值

      1
      2
      3
      4
      5
      6
      <property name="courseList">
      <list>
      <ref bean="course1"></ref>
      <ref bean="course2"></ref>
      </list>
      </property>
  2. 把集合注入部分提取出来

    先在xml配置文件配置一个名称空间

    1
    2
    3
    4
    5
    6
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    用util标签完成list集合注入提取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!--    提取-->
    <util:list id="bookList">
    <value>易筋经</value>
    <value>九阴真经</value>
    <value>九阳神功</value>
    </util:list>
    <!-- 注入使用-->
    <bean id="book" class="com.springdemo.spring5.collectiontype.Book">
    <property name="list" ref="bookList"></property>
    </bean>

IOC操作Bean管理(FactoryBean)

Spring里面有两种Bean

​ 普通Bean和FactoryBean

普通Bean–定义什么类型就返回什么类型

1
<bean id="book" class="com.springdemo.spring5.collectiontype.Book">

工厂Bean–定义类型可以不和返回类型一致

​ 创建一个类作为factoryBean, 实现接口FactoryBean

1
<bean id="myBean" class="com.springdemo.spring5.factorybean.MyBean"></bean>
1
2
3
4
5
6
7
public class MyBean implements FactoryBean<Course> {
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
1
2
3
4
5
6
7
8
9
@Test
public void test3(){
ApplicationContext context = new
ClassPathXmlApplicationContext("bean3.xml");

Course course = context.getBean("myBean",Course.class);
System.out.println(course);
// myBean.test();
}

定义类型(MyBean)和返回(Course)不一样

IOC操作Bean管理(作用域)

在Spring里面, 可以设置创建的bean实例是单实例还是多实例

默认情况下, 默认是但实例对象

1
2
3
4
5
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book1 = context.getBean("book",Book.class);
Book book2 = context.getBean("book",Book.class);
System.out.println(book1);
System.out.println(book2);

以上测试代码结果为

com.springdemo.spring5.collectiontype.Book@b2c9a9c
com.springdemo.spring5.collectiontype.Book@b2c9a9c

地址相同表示是但实例对象

设置多实例对象
1
2
3
<bean id="book" class="com.springdemo.spring5.collectiontype.Book" scope="prototype">
<property name="list" ref="bookList"></property>
</bean>

bean 标签的scope属性值

      1. singeton(默认值)
      2. prototype

输出为:

com.springdemo.spring5.collectiontype.Book@b2c9a9c
com.springdemo.spring5.collectiontype.Book@4c178a76

设置为singleton, 加载spring配置文件的时候, 就创建了实例对象

设置为prototype, 在调用getBean方法都时候才创建多实例对象

还有其他属性值

  1. request
  2. session

IOC操作Bean管理(生命周期)

生命周期: 从对象的创建到对象销毁的过程

Bean的生命周期
 1. 通过构造器创建bean实例(无参构造)
 2. 为bean的属性设置值和对其他bean的引用(调用set方法)
 3. 调用bean的初始化方法(需要进行配置)
 4. bean可以使用了(对象获取到了)
 5. 当容器关闭, 调用bean的销毁方法(需要配置)

初始化方法和销毁方法分别是bean标签中的两个属性init-method 和 destroy-method

1
2
3
<bean id="orders" class="com.springdemo.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>

一下为测试生命周期代码

1
2
3
4
5
6
7
8
9
10
@Test
public void testBean3(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");

Orders orders = context.getBean("myBean", Orders.class);
System.out.println(orders);
System.out.println("4--获取实例对象");
//手动销毁才会调用销毁方法
((ClassPathXmlApplicationContext) context).close();
}
Bean的后置处理器, 更完整的生命周期实际有7步
  1. 通过构造器创建bean实例(无参构造)

  2. 为bean的属性设置值和对其他bean的引用(调用set方法)

  3. 把bean实例传递到bean的后置处理器的方法

    1. ```java
      public Object postProcessBeforeInitialization(Object bean, String beanName)
      1
      2
      3
      4
      5
      6
      7

      4. 调用bean的初始化方法(需要进行配置)

      5. **把bean实例传递到bean的后置处理器的另外一个方法**

      1. ```java
      public Object postProcessAfterInitialization(Object bean, String beanName)
  4. bean可以使用了(对象获取到了)

  5. 当容器关闭, 调用bean的销毁方法(需要配置)

演示后置处理器

1
<bean id="myBeanPost" class="com.springdemo.spring5.bean.MyBeanPost"></bean>

配置之后控制台输出:

1–无参调用了
2–调用set
初始化之前
3–执行初始化
初始化之后
4–获取实例对象
com.springdemo.spring5.bean.Orders@765d7657
5–执行销毁方法

IOC操作Bean管理(XML自动装配)

根据指定装配规则, Spring会自动匹配属性值注入

autowire标签属性

​ 1. byName

1
2
3
4
5
    <bean id="emp" class="com.springdemo.spring5.autowire.Emp" autowire="byName">
<!-- 要注入的值 和 id属性值名一定要一样-->
<!-- 手动装配: <property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.springdemo.spring5.autowire.Dept"></bean>

​ 2. byType

1
2
3
4
<bean id="emp" class="com.springdemo.spring5.autowire.Emp" autowire="byType">

</bean>
<bean id="dept" class="com.springdemo.spring5.autowire.Dept"></bean>

实际开发用的较少, 一般使用注解

IOC操作Bean管理(引用外部属性文件)

  1. 直接配置数据库信息

    1. 配置德鲁伊(druid)连接池

    2. jar包引入

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <!-- 配置连接池 -->
      <!-- DruidDataSource dataSource = new DruidDataSource(); -->
      <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      <!-- dataSource.setDriverClassName("com.mysql.jdbc.Driver");
      set方法注入
      -->
      <!-- 获取properties文件内容,根据key获取,使用spring表达式获取 -->
      <property name="driverClassName" value="${jdbc.driverclass}"></property>
      <property name="url" value="${jdbc.url}"></property>
      <property name="username" value="${jdbc.username}"></property>
      <property name="password" value="${jdbc.password}"></property>
      </bean>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <!-- 配置连接池 -->
      <!-- DruidDataSource dataSource = new DruidDataSource(); -->
      <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      <!-- dataSource.setDriverClassName("com.mysql.jdbc.Driver");
      set方法注入
      -->
      <!-- 获取properties文件内容,根据key获取,使用spring表达式获取 -->
      <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
      <property name="url" value="jdbc:mysql://localhost:3306/"></property>
      <property name="username" value="root"></property>
      <property name="password" value="333"></property>
      </bean>
  2. 引入外部配置文件配置

1
2
3
4
prop.dirverClass = com.mysql.jdbc.Driver
prop.username = jdbc:mysql://localhost:3306/
prop.userName = root
prop.password = 333

引入Spring配置文件中

  1. 引入context名称空间

    1
    2
    3
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  2. 在配置文件中引入

    1
    2
    3
    4
    5
    6
    7
    8
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 配置连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${prop.driverClass}"></property>
    <property name="url" value="${prop.url}"></property>
    <property name="username" value="${prop.userName}"></property>
    <property name="password" value="${prop.password}"></property>
    </bean>

IOC操作Bean管理(基于注解方式)

注解就是特殊标记(annotation)

​ 格式: @注解名称(属性名=属性值, 属性名=属性值)

​ 目的: 简化xml配置

Spring 针对Bean管理(创建对象)提供注解
 1. @Component
 2. @Service (Service)
 3. @Controller (Web)
 4. @repository (DAO)

功能一样, 都可以创建bean实例, 只是建议用在不同地方, 也可以混用

基于注解的实例创建

需要引入AOP依赖的jar包

开启组件扫瞄

首先新建context名称空间

1
2
3
4
5
6
7
8
9
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context">

</beans>

第一种添加多个package扫描的方法为逗号隔开

1
<context:component-scan base-package="com.springdemo.spring5.dao, com.springdemo.spring5.service"></context:component-scan>

第二种方法是直接写共同点上层目录

1
<context:component-scan base-package="com.springdemo"></context:component-scan>

然后使用注解创建对象

1
2
3
4
5
6
7
8
9
import org.springframework.stereotype.Service;
//value 可以省略, 默认值为类名首字母自动小写
//@Component(value = "userService" )
@Service(value = "userService" )
public class UserService {
public void add(){
System.out.println("service add.......");
}
}
组件扫描的细节问题

可以配置大目录哪些扫描那些不扫描

use-default-filter =”false” 代表不是默认的全部扫描

context:include-filter 设置哪些扫描

expression=…..Service 代表只扫描带Service注解的方法

1
2
3
<context:component-scan base-package="com.springdemo" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

context:exclude-filter 设置哪些不扫描

1
2
3
4
<context:component-scan base-package="com.springdemo">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
基于注解方式实现属性注入
  1. @AutoWired 根据属性类型注入

    创建service和dao的对象, 然后添加创建对象的注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Service(value = "userService" )
    public class UserService {

    //不需要set方法
    @Autowired
    private UserDao userDao;
    public void add(){
    System.out.println("service add.......");
    }
    }
  2. @Qualifier 根据属性名称注入

    需要和@AutoWired 一起使用

    实现场景: 如果UserDao接口有多个实现类, AutoWired会不知道找那个进行注入, 加上@Qualifier可以让它找到对应名称的实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Service(value = "userService" )
    public class UserService {

    //不需要set方法
    @Autowired
    @Qualifier(value = "userDaoImpl") //为该类中@Repository的默认值, 可在@Repository的注解value中修改,需要对应相同名称
    private UserDao userDao;
    public void add(){
    System.out.println("service add.......");
    userDao.add();
    }
    }
  3. @Resource 可以根据类型注入 也可以根据名称注入

    这是javax里的注解, 所以Spring还是建议前两个

    1
    2
    3
    4
    5
    6
    7
    //    @Resource //根据类型注入
    @Resource(name = "userDaoImp;") //根据名称注入
    private UserDao userDao;
    public void add(){
    System.out.println("service add.......");
    userDao.add();
    }
  4. @Value 根据普通类型属性注入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Resource(name = "userDaoImpl") //根据名称注入
    private UserDao userDao;
    @Value(value = "abc")
    private String name;

    public void add(){
    System.out.println("service add......."+name);
    userDao.add();
    }

完全注解开发

创建配置类

1
2
3
4
@Configuration //作为配置类可以替代xml
@ComponentScan(basePackages = {"com.springdemo"})
public class SpringConfig {
}

编写测试类

1
2
3
4
5
6
7
8
@Test
public void testService2(){
ApplicationContext context
= new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}

以上代码调用了

1
new AnnotationConfigApplicationContext(SpringConfig.class)

来使用配置类, 与之前调用xml文件的方法不一样

实际开发中是基于SpringBoot

AOP面向切面(方面)

AOP概念

可以对业务逻辑的各个部分进行隔离, 从而降低耦合度

通俗描述, 不通过修改源代码的方式, 在主干内添加新功能

例子: 登录界面业务

image-20211019174924914

AOP底层原理

AOP底层使用动态代理

动态代理是JavaSE基础里的内容:

相关有用的链接: https://www.cnblogs.com/whirly/p/10154887.html

尚硅谷视频教程里也有(p659-686): https://www.bilibili.com/video/BV1Kb411W75N?p=659

  1. 有接口的情况, 使用JDK的动态代理

    • 创建接口实现类代理对象, 增强类的方法
    image-20211019212146814
  2. 没有接口的情况, 使用CGLIB动态代理

    • 创建子类代理对象, 增强类里的方法
    image-20211019212525417

JDK动态代理

使用Proxy类里面的方法创建代理对象

调用newProxyInstance, 有三个参数:

  1. ClassLoader
  2. 增强方法所在的类, 这个类的实现接口, 支持多个接口
  3. InvocationHandler 实现里面的接口, 创建代理对象的增强方法
*******代码实现(底层代码):
  1. 创建interface

  2. 创建实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class UserDaoImpl implements UserDao{
    @Override
    public int add(int a, int b) {
    System.out.println("added ");
    return a+b;
    }

    @Override
    public String update(String id) {
    System.out.println("updated");
    return id;
    }
    }
  3. 使用proxy类创建接口代理对象

    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
    public class JDKProxy {

    public static void main(String[] args) {
    //创建接口实现类的对象
    /* Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    return null;
    //匿名内部类
    }
    });*/
    Class[] interfaces = {UserDao.class};
    UserDaoImpl userDao = new UserDaoImpl();
    UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
    int result = dao.add(1, 2);
    System.out.println("Result::"+result);
    }
    }

    class UserDaoProxy implements InvocationHandler{

    //1 把创建的是谁的代理对象, 把谁传递过来
    //有参数构造传递
    private Object obj;
    public UserDaoProxy(Object obj){
    this.obj = obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("before method: "+ method.getName()+ " arguments passed: "+ Arrays.toString(args));
    Object res = method.invoke(obj, args);
    System.out.println("after method");
    return res;
    }
    }

    最终运行main方法的输出为

    1
    2
    3
    4
    before method: add arguments passed: [1, 2]
    added
    after method
    Result::3

AOP操作术语

  1. 连接点

    类里面哪些方法可以被增强, 这些方法称为连接点

  2. 切入点

    实际被真正增强的方法, 称为切入点

  3. 通知 (增强)

    实际增强的逻辑部分称为通知 (增强)

    • 前置通知
    • 后置通知
    • 环绕通知
    • 异常通知
    • 最终通知
  4. 切面

    是动作

    • 把通知应用到切入点的过程就叫切面

AOP操作(准备)

  1. Spring框架中一般基于AspectJ实现AOP操作

    • AspectJ不是Spring组成部分, 是独立AOP框架, 一般和Spring一起使用
  2. 基于AspectJ实现AOP

    • 基于XML配置文件实现
    • 基于注解方式实现(使用)
  3. 在工程项目里面引入AOP相关依赖

    image-20211020143517661

  4. 切入表达式

    1. 切入表达式作用: 知道哪个累里面的哪个方法进行增强

    2. 语法结构

      execution([权限修饰符][返回类型][类全路径][方法名]([参数列表]))

例子:

对com.atguigu.dao.BookDao类里的add进行增强

​ execution(* com.atguigu.dao.BookDao.add(..))

对com.atguigu.dao.BookDao类里的所有方法进行增强

​ execution(* com.atguigu.dao.BookDao.* (..))

对com.atguigu.dao里的所有类和类里的所有方法进行增强

​ execution(* com.atguigu.dao.*.*(..))

AOP操作(AspectJ注解)

  1. 创建类, 在里面定义方法

  2. 创建增强类(编写增强的逻辑)

    1. 在增强类中创建方法, 让不同方法代表不同通知类型

      1
      2
      3
      4
      5
      6
      7
      //增强类
      public class UserProxy {
      //前置通知
      public void before(){
      System.out.println("before.....");
      }
      }
  3. 进行通知的配置

    1. 在spring配置文件中开启注解扫描

      先添加命名空间context和aop

      然后使用xml配置

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

      <context:component-scan base-package="com.springdemo.spring5.aopanno"></context:component-scan>

      </beans>
    2. 使用注解创建User和UserProxy对象

      1
      2
      3
      4
      5
      6
      @Component
      public class User {
      public void add(){
      System.out.println("add....");
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      //增强类
      @Component
      public class UserProxy {
      //前置通知
      public void before(){
      System.out.println("before.....");
      }
      }
    3. 在增强类上面添加注解@Aspect

      1
      2
      3
      4
      5
      6
      7
      8
      9
      //增强类
      @Component
      @Aspect //表示生成代理对象
      public class UserProxy {
      //前置通知
      public void before(){
      System.out.println("before.....");
      }
      }
    4. 在spring配置文件中开启生成代理对象

      1
      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  4. 配置不同类型的通知

    1. 在增强类里面, 作为通知的方法上面, 添加通知类型注解, 使用切入点表达式配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      //增强类
      @Component
      @Aspect //表示生成代理对象
      public class UserProxy {

      //前置通知
      //Before 注解表示作为前置通知
      @Before(value = "execution(* com.springdemo.spring5.aopanno.User.add(..))")
      public void before(){
      System.out.println("before.....");
      }
      }

最终测试一下

1
2
3
4
5
6
@Test
public void testAopAnno(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
User user = applicationContext.getBean("user", User.class);
user.add();
}

测试结果

1
2
before.....
add....

所有类型的通知通过注解实现

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
//前置通知
//Before 注解表示作为前置通知
@Before(value = "execution(* com.springdemo.spring5.aopanno.User.add(..))")
public void before(){
System.out.println("before.....");
}
@After(value = "execution(* com.springdemo.spring5.aopanno.User.add(..))")
public void after(){
System.out.println("after.....");
}
@AfterReturning(value = "execution(* com.springdemo.spring5.aopanno.User.add(..))")
public void afterReturning(){
System.out.println("AfterReturning.....");
}
//异常通知,有异常才会执行
@AfterThrowing(value = "execution(* com.springdemo.spring5.aopanno.User.add(..))")
public void afterThrowing(){
System.out.println("AfterThrowing.....");
}
@Around(value = "execution(* com.springdemo.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("Before Around.....");
proceedingJoinPoint.proceed();
System.out.println("After Around.....");
}

最终执行顺序可以通过测试结果看出

1
2
3
4
5
6
7
Before Around.....
before.....
add....
After Around.....
after.....
AfterReturning.....

  1. 公共(相同的)切入点抽取

    1
    (value = "execution(* com.springdemo.spring5.aopanno.User.add(..))")

    方法如下, 使用@Pointcut注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //相同切入点抽取
    @Pointcut(value = "execution(* com.springdemo.spring5.aopanno.User.add(..))")
    public void pointdemo(){
    }
    //前置通知
    //Before 注解表示作为前置通知
    @Before(value = "pointdemo()")
    public void before(){
    System.out.println("before.....");
    }
    @After(value = "pointdemo()")
    public void after(){
    System.out.println("after.....");
    }
  2. 有多个增强类对同个方法增强, 设置增强优先级

    1. 在增强类上面添加注解@Order, 值越小优先级越高

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      @Component
      @Aspect
      @Order(1)
      public class PersonProxy {

      //相同切入点抽取
      @Pointcut(value = "execution(* com.springdemo.spring5.aopanno.User.add(..))")
      public void pointdemo(){
      }
      //前置通知
      //Before 注解表示作为前置通知
      @Before(value = "pointdemo()")
      public void before(){
      System.out.println("Person Before.....");
      }
      }

      与UserProxy 设置为@Order(2)后, 测试执行输出为

      1
      2
      3
      4
      5
      6
      7
      8
      Person Before.....
      Before Around.....
      before.....
      add....
      After Around.....
      after.....
      AfterReturning.....

AOP操作(AspectJ配置文件)

  1. 创建类, 增强类和被增强类

    1
    2
    3
    4
    5
    public class Book {
    public void buy(){
    System.out.println("buy....");
    }
    }
    1
    2
    3
    4
    5
    6
    public class BookProxy {
    // @Before()
    public void before(){
    System.out.println("before......");
    }
    }
  2. 在spring配置中创建两个类对象

    1
    2
    <bean id="book" class="com.springdemo.spring5.aopxml.Book"></bean>
    <bean id="bookProxy" class="com.springdemo.spring5.aopxml.BookProxy"></bean>
  3. 在spring配置文件中配置切入点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!--    配置aop增强-->
    <aop:config>
    <!-- 切入点-->
    <aop:pointcut id="p" expression="execution(* com.springdemo.spring5.aopxml.Book.buy(..))"/>

    <!-- 配置切面-->
    <aop:aspect ref="bookProxy">
    <aop:before method="before" pointcut-ref="p"/>
    </aop:aspect>

    </aop:config>

    输出:

    1
    2
    before......
    buy....
    如果想完全使用注解开发

    创建配置类

    使用三个注解

    @Configuration
    @ComponentScan(basePackages = {“com.springdemo”})
    @EnableAspectJAutoProxy(proxyTargetClass = true)

    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan(basePackages = {"com.springdemo"})
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class ConfigAop {
    }

JdbcTemplate

准备工作

配置spring的xml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
   <!-- 数据库连接池 -->
<bean id="dataSourcze" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="333" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- 创建jdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSourcze"></property>
</bean>

通过注解, 然后创建service类, 创建dao类, 在dao中注入jdbcTemplate对象

1
2
3
4
5
6
@Repository
public class BookDaoImpl implements BookDao{

@Autowired
private JdbcTemplate jdbcTemplate;
}
1
2
3
4
5
@Service
public class BookService {
@Autowired
private BookDao bookDao;
}

添加修改删除操作

创建一个Book实体类之后, 直接调用注入后的jdbcTemplate的update方法就可以了

1
2
3
4
5
6
7
@Autowired
private JdbcTemplate jdbcTemplate;

public void add(Book book){
String sql = "insert into t_book values(?,?,?)";
jdbcTemplate.update(sql,book.getId(),book.getName(),book.getStatus());
}

测试service里面的调用

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void add() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");

BookService bookService = context.getBean("bookService", BookService.class);

Book book = context.getBean("book", Book.class);
book.setId(1);
book.setUsername("java");
book.setBook_status("a");
bookService.addBook(book);
}

然后一样的逻辑使用update为DaoImpl加入update修改和delete删除操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void update(Book book) {
String sql = "update t_book set bookname=?, book_statud=? where id = ?";
int update = jdbcTemplate.update(sql,book.getUsername(),book.getBook_status(),book.getId());
System.out.println(update);

}

@Override
public void delete(int id) {
String sql = "delete from t_book where id = ?";
int update = jdbcTemplate.update(sql,id);
System.out.println(update);
}

查询操作

返回某个值

查询表里有多少条记录:

1
2
3
4
5
6
7
@Override
public int selectCount() {
String sql = "select count(*) from t_book";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;

}

使用queryForObject, 第二个参数是需要返回值得类型的参数

返回对象

查询图书详情

1
2
3
4
5
6
7
@Override
public Book findBook(int id) {
String sql = "select * from t_book where id=?";
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);

return book;
}

注意: 如果需要输出不为null, 数据库中的对应column名字需要和对象的属性名一致(原理是get方法名字一致)

返回集合

使用query方法

1
2
3
4
5
6
7
@Override
public List<Book> findAllBook() {
String sql = "select * from t_book";
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));

return bookList;
}

JdbcTemplate批量操作

批量添加

把每个数组集合中的值添加到数据库

1
2
3
4
5
6
7
8
9
10
11
String sql = "insert into t_book values(?,?,?)";

List<Object[]> batchArgs = new ArrayList<>();
Object[] o1= {"1","java","a"};
Object[] o2 = {"2", "c", "b"};
Object[] o3 = {"3", "python", "c"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
//批量添加
batchUpdate(sql, batchArgs);

返回结果是个数组 int[]

1
[1,1,1]

批量修改删除也和批量添加差不多, 只是sql参数不同

Spring事物操作

事务一般添加到JavaEE三层架构里的service层上

Spring中有两种事务管理操作

  1. 编程式事务管理(通过代码实现)
  2. 声明式事务管理(通过配置实现)

声明式事务管理

  • 基于注解
  • 基于xml

Spring中进行声明事务管理, 底层使用AOP原理

Spring事务管理API:

  • PlatformTransctionManager—针对不同的框架有不同的实现类
    • DataSourceTranscationManager
    • HibernateTransactionManager

基于注解

  1. 在Spring配置文件中配置事务管理器

    1
    2
    3
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSourcze"></property>
    </bean>
  2. 开启事务注解

    • 引入tx名称空间

    • ```xml
      <tx:annotation-driven transaction-manager=”dataSourceTransactionManager”/>

      1
      2
      3
      4
      5
      6
      7

      ```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"
      xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
      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 http://www.springframework.org/schema/tx/spring-tx.xsd">
  3. 在service类上面加上事务注解

    @Transctional — 在类上面可以, 也可以添加在方法上面

    1
    2
    3
    4
    5
    @Service
    @Transcational
    public class UserService(){

    }

@Transcational() 的参数配置

重要参数:

  • propagation – 事务传播行为
    • 有事务方法调用其他方法(有事务或无事务)
    • REQUIRED
    • REQUIRED_NEW
    • image-20211101203911372
  • isolation – 隔离级别
    • 三个问题: 赃读, 不可重复读, 幻读. 分别对应: 读未提交, 读已提交, 可重复读
  • timeout – 超时时间
    • 一定时间内不提交就会回滚
    • 默认值-1, 表示不超时. 值以秒为单位
  • readOnly – 是否只读
    • 默认值false — 表示可以增删改查
    • true — 只能查询
  • rollbackFor – 回滚
    • 出现哪些异常进行回滚
  • noRollBackFor – 不回滚
    • 出现哪些异常不回滚

基于xml

  1. 配置事务管理器
  2. 配置通知
  3. 配置切入点和切面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourcze"></property>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

<!-- 配置通知-->
<tx:advice id="txadvice">
<tx:attributes>
<!-- 指定在那种规则都方法上添加事务 -->
<tx:method name="accountMoney"/>
<!-- account 开头的方法 -->
<tx:method name="account*"/>
</tx:attributes>
</tx:advice>
<!-- 配置切入点和切面-->
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.atguigu.spring.service.UserService.*(..))"/>
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor>
</aop:config>

完全注解

  1. 创建配置类替代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
    25
    26
    27
    28
    29
    30
    31
    32

    @Configuration //配置类
    @ComponentScan //组件扫描
    @EnableTranscationManagement //开启事务
    public class TxConfig{
    //创建数据库连接池
    @Bean
    public DuridDataSource getDruidDataSource(){
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql:///user_db");
    dataSource.setUsername("root");
    dataSource.setPassword("333");
    return dataSource;
    }

    //创建jdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTeamplate(DataSource dataSource){
    JdbcTemplate = jdbcTemplate = new JdbcTemplate();
    //注入dataSource
    jdbcTemplalte.setDataSource(dataSource):
    return jdbcTemplate;
    }

    //创建事务管理器
    public DataSourceTranscationManager getDataSourceTranscationManager(DataSource dataSource){
    DataSourceTranscationManager dataSourceTranscationManager = new DataSourceTranscationManager();
    dataSourceTranscationManager.setDataSource(dataSource);
    return dataSourceTranscationManager;
    }
    }

Spring5新功能

  1. 整个Spring5框架基于Java8, 运行时兼容jdk9, 许多不建议使用的类在代码中删除

  2. 自带了通用的日志封装

    1. 可以整合其他的第三方日志

    2. Spring5 以及移除了Log4jConfigListener, 官方建议使用Log4j2

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <?xml version="1.0" encoding="UTF-8"?>
      <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
      <!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
      <configuration status="INFO">
      <!--先定义所有的appender-->
      <appenders>
      <!--输出日志信息到控制台-->
      <console name="Console" target="SYSTEM_OUT">
      <!--控制日志输出的格式-->
      <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
      </console>
      </appenders>
      <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
      <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
      <loggers>
      <root level="info">
      <appender-ref ref="Console"/>
      </root>
      </loggers>
      </configuration>
      1
      2
      3
      4
      5
      6
      7
      8
      public class UserLog{
      private static final Logger log = LoggerFactory.getLogger(UserLog.class);

      public static void main (String[] args){
      log.info("Hello log4j2");
      log.warn("");
      }
      }
  3. 核心容器

    1. @Nullable注解
      • 可以用在方法上, 属性上, 参数上, 表示对应的内容可以为空
        • 方法返回值可以为空
        • 属性值可以为空
        • 参数可以为空
    2. 支持lambda表达式

函数式风格创建对象

GenericApplicationContext 函数式风格创建对象, 交给Spring管理

lambda表达式

调用context的方法注册对象

1
2
3
4
5
6
7
GenericApplicationContext context = new GenericApplicationContext(); 
context.refresh();
context.registerBean("user1",User.class,() -> new User());
//根据名字获取对象
User user = (User) context.getBean("user1");
sout(user);

测试改进

整合JUnit4

  1. 引入依赖 spring-test

  2. 创建测试类

    • @RunWith(SpringJUnit4ClassRunner.class)

    • @ContextConfiguration(“classpath:bean1.xml”)

    • ```java
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration(“classpath:bean1.xml”)
      public class test{

      @AutoWired
      private UserService userService;
      
      @Test
      public void test1(){
          userService.accountMoney();
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12



      ### 整合JUnit5

      1. 引入JUnit5依赖

      2. 创建测试类, 使用注解

      - ```java
      @ExtendWith(SpringExtension.class)
      @ContextConfiguration("classpath:bean1.xml")
  3. 使用复合注解@SpringJUnitConfig(location=”classpath:bean1.xml”) 可以代替上面两个注解

WebFlux

SpringWebFlux介绍

用于web开发, 功能与SpringMVC类似, 使用当前一种比较流行的响应式编程而出现的框架

传统框架比如SpringMVC, 都是基于Servlet. 而Webflux是一种异步非阻塞的框架, 在Servlet3.1以上才支持

核心基于Reactor的相关API实现的

什么事异步非阻塞

异步同步

非阻塞阻塞

↑上面都是针对对象不一样

异步同步针对调用者, 调用者发送请求, 如果等着对方回应才去做其他事情就是同步, 不等就是异步

阻塞和非阻塞被调用者收到请求, 做完请求任务之后才反馈就是阻塞, 收到请求之后后马上反馈在做其他事就是非阻塞

WebFlux的优势

非阻塞有什么好处:

  • 可以在有限资源下, 提高系统吞吐量和伸缩性, 处理跟多请求, 以Reactor作为基础实现响应式编程

函数式编程: 基于java8, webflux使用java8中的函数式编程方式来实现路由请求

比较SpringMVC

都可以使用注解, 都可以运行在Tomcat等容器中

SpringMVC基于命令式编程, WebFlux使用异步响应式编程

响应式编程

电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似”=B1+C1”的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。

示例:

Java8及其之前版本

  • 提供的观察者模式, 两个类Observer和Observable
Author: klenq
Link: https://klenq.github.io/2021/11/08/Spring5学习笔记/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.