• 万字详解Spring相关组件配置原理


    1.前言

    相信小伙伴们对于 spring 都不陌生,甚至第一次接触的后端框架就是 spring ,比如:IOC、AOP、DI、Bean的生命周期,用了哪些设计模式等等。大家都知道这些或多或少也了解。虽然说用的很熟悉,也能解决一些项目中的业务问题,但在用的时候有没有想过,我为什么要这么用,这么用的意义是什么,其背后的意义和原理 又是什么呢?接下来这篇文章就来剖析下,spring 中常用的一些结构,它的背后的实现原理,既能巩固自身基础也能应对面试。双赢。

    2. 为啥要用 Spring

    张三是一个编程小白,他每次在 service 层写代码都要自己 new 一堆 Dao 接口的实现类。

    public class ProjectServiceImpl implements ProjectService {
        UserDao userDao = new UserDaoImpl();
        ProjectSectionDao projectSessionDao = new ProjectSessionDaoImpl();
        ProjectDao projectDao = new ProjectDaoImpl();
        SupplyDao supplyDao = new SupplyDaoImpl();
        ....... 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    有一天正 new 着对象,张三心想:“我这一个 service 都需要 new 好多 Dao ,那如果有一堆 service ,那我不得花费好长时间?”

    有没有一个工具类或者什么框架能帮我管理这些对象?我只需要配置一下,需要的时候它就能自动帮我 new 个对象出来?”

    张三陷入了深深的沉思之中。

    张三的室友李四也是一个编程小白。

    李四呢想给自己的小项目增加一个功能:记录方法执行的时间。结果他脑子一热竟然给所有的方法都增加了一堆打印方法:

    System.out.println("项目开始执行");
    // 开始时间
    long start = System.currentTimeMillis();
    
    // 业务代码
    
    // 结束时间
    long end = System.currentTimeMillis();
    // 计算执行时间
    System.out.printf("执行时间:%d 毫秒.", (end - start));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    过了半个小时,李四终于给项目中所有的方法都复制粘贴上了打印语句。他长舒一口气:“我真是个大聪明!”

    图片
    张三看了一眼李四的代码,连连鼓掌:“妙啊!咱们宿舍的技术大神!”

    旁边的王五实在忍不住了,对张三说:“妙个屁!最近的 Spring 框架课你俩是不是都没去?光顾着打游戏了?我都替你俩答了三次到了!”

    李四问王五:“这个Spring 框架学了有用吗?”

    王五:“不仅能解决张三说的管理对象的问题,还能帮你解决记录日志的问题。配置完 Spring ,你只需要定义一个切面类,根本不需要在一堆类上面复制粘贴一堆代码。”

    张三摸摸后脑勺笑着说:“原来 Spring 框架那么好用,我以后再也不逃课了。我这就去翻课本学习 Spring 框架去。”

    2. Spring 简介

    Spring 是一个轻量级的 Java 开发框架。Spring 的核心是控制反转(IOC)面向切面编程(AOP)

    Spring 主要有如下优点:

    • 解耦
    • 支持面向切面编程
    • 便于集成其他框架

    3. Spring搭建

    3.1.创建 Maven 项目

    步骤:File -> New -> Project -> Maven

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    3.2. 引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.16.RELEASE</version>
        </dependency>
    </dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.3.创建接口和实现类

    UserService

    public interface UserService {
        void print();
    }
    
    • 1
    • 2
    • 3

    UserServiceImpl

    public class UserServiceImpl implements  UserService{
        @Override
        public void print() {
            System.out.println("hello world");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.4.创建配置文件

    applicationContext.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="userService" class="com.example.service.impl.UserServiceImpl"/>
    </beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.5.测试

    @Test
    public void testSpring(){
        // 1、获取工厂
        ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
        // 2、通过工厂类获得对象
        UserService userService = (UserService)act.getBean("userService");
        // 3.调用方法
        userService.print();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    测试结果:

    hello world
    
    • 1

    4.IOC

    4.1 IOC 简介

    IOC,全称 Inversion of Control,意思是控制反转。它是 Spring 框架中的一种思想。

    控制反转就是将对象的控制权从程序中的代码转移到了 Spring 的工厂,通过 Spring 的工厂完成对象的创建以及赋值。

    也就是说之前是我们自己 new 对象、给对象中的成员变量赋值。 现在是让 Spring 来帮助我们创建对象、给成员变量赋值。

    4.2 Spring 核心内容描述

    4.2.1.配置文件

    Spring 的配置文件可以放到项目中的任意一个地方,也可以随意命名,但是建议使用:applicationContext.xml

    你可以将这个配置文件看成一个装有一堆 bean 标签的容器

    4.2.2.bean 标签

    Spring 工厂创建的对象,叫做 bean,所以一个 bean 标签代表一个对象

    <bean id="userService" class="com.example.service.impl.UserServiceImpl"/>
    
    • 1

    bean 标签中必须要有 class 属性,它的值是一个类的全限定名(包名+类名)。

    除了 class 属性,bean 标签还可以设置 id 、name 、scope属性。

    id:

    1. id 必须以字母开头,相当于这个 bean 的身份证号,是唯一的。
    2. 如果这个 bean 只使用一次,id 可以省略不写。
    3. 如果这个 bean 需要被其他 bean 引用,或者这个 bean 要使用很多次,则必须要有 id 属性。
    4. 如果只配置 class 属性,Spring 框架会给每一个 bean 配置一个默认的 id:“全限定名#1”。

    name:

    • name 相当于这个 bean 的别名,它可以配置多个,例如:
    <bean id="user" name="aa,bb,cc" class="com.example.model.User"/>
    
    • 1

    scope:

    scope 属性可以控制简单对象的创建次数,它有两个值:

    1. singleton:每次只会创建唯一⼀个简单对象,默认值。
    2. prototype:每⼀次都会创建新的对象。

    4.2.3.ApplicationContext

    ApplicationContext 是 Spring 的工厂,主要用来创建对象

    Spring 通过读取配置文件创建工厂。

    因为 Spring 的工厂会占用大量内存,所以一个程序一般只会创建一个工厂对象。

    4.2.4.工厂常用方法

    (1)根据 id 获取对象

    UserService userService = (UserService)act.getBean("userService");
    
    • 1

    (2)根据 id 和类名获取对象

    UserService userService = (UserService)act.getBean("userService",UserService.class);
    
    • 1

    (3)只根据类名获取对象

    UserService userService = (UserService)act.getBean(UserService.class);
    
    • 1

    (4)获取配置文件中所有 bean 标签的 id 值

    String[] beanDefinitionNames = act.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
    
    • 1
    • 2
    • 3
    • 4

    (5)判断是否存在指定 id 或者 name 的 bean

    act.containsBean("userService")
    
    • 1

    (6)判断是否存在指定 id 的 bean,只能用来判断 id

    act.containsBeanDefinition("userService")
    
    • 1

    4.3 IOC 优点

    解耦。

    说起解耦之前先说下耦合:耦合是指代码之间的关联性太强,我如果改了这一段代码,可能会影响到一堆代码。

    那创建对象哪里有耦合了?其实就是new关键字带来的耦合。

    如果你发现一个接口的实现类需要修改,你需要手动改动程序中的代码,比如修改 new 关键字后面的实现类,这样可能会影响到其他的代码。

    但是使用了 Spring 之后,我们只需要修改配置文件中 bean 标签的 class 属性对应的类的全限定名,不用修改程序中的代码,这样就做到了解耦。

    解耦就是解除不同代码之间的关联性、依赖性。

    5.创建对象

    Spring 是如何创建对象的呢?

    5.1.工厂和反射。

    首先说下反射,我们可以通过一个类的全限定名获取 Class 对象,然后再通过 Class 实例化一个对象:

    Class serviceClass = Class.forName("com.example.service.impl.UserServiceImpl");
    UserService userService = (UserService)serviceClass.newInstance();
    
    • 1
    • 2

    Spring 配置文件中 bean 标签的 id 和类的全限定名一一对应,所以 Spring 工厂的 getBean 方法其实就是先根据
    bean 的 id 获取该类的全限定名,然后再利用反射根据类的全限定名创建对象并返回。

    6.DI

    DI 全称 Dependency Injection,意思是依赖注入,它是 IOC 的具体实现

    依赖就是说我需要你,比如 Service 层依赖 Dao 层,注入就是赋值。

    依赖注入:使用 Spring 的工厂和配置文件为一个类的成员变量赋值

    没有使用 Spring 的依赖注入我们是这样赋值的:

    User user = new User();
    user.setName("张三");
    
    • 1
    • 2

    如果设置有误,就需要手动修改代码,代码耦合度较高,而依赖注入的出现就是为了解耦。

    6.1.DI 注入方式

    Spring 的依赖注入包含两种方式:

    6.1.1 set 注入

    set 注入:Spring 调用 Set 方法通过配置文件为成员变量赋值。

    1.创建对象,为属性添加 set/get 方法

    public class User {
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2.修改配置文件

    <bean id="user" class="com.xxl.model.User">
      <property name="name" value="小曲同学" />
      <property name="age" value="18" />
    </bean>
    
    • 1
    • 2
    • 3
    • 4

    3.测试

    // 1、获取工厂
    ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
    // 2、通过工厂类获得对象
    User user = (User)act.getBean("user");
    System.out.println("姓名:"+user.getName());
    System.out.println("性别:"+user.getAge());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    从上面的例子可以看出 Set 注入就是在 property 标签中为属性赋值。spring 可以为 JDK 内置的数据类型进行赋值,也可以为用户自定义的数据类型进行赋值。

    6.1.2 JDK 内置数据类型

    1.基本类型

    <property name="name" value="小曲同学" />
    <property name="age" value="18" />
    
    • 1
    • 2

    2.List 集合

    <property name="phones">
      <list>
        <value>15799999918</value>
        <value>15788888819</value>
        <value>15766666620</value>
      </list>
    </property>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.Set 集合

    <property name="phones">
      <set>
        <value>15799999918</value>
        <value>15788888819</value>
        <value>15766666620</value>
      </set>
    </property>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.Map 集合

    <property name="mapInfo">
        <map>
            <entry>
                <key><value>name</value></key>
                <value>小曲同学</value>
            </entry>
            <entry>
                <key><value>age</value></key>
                <value>23</value>
            </entry>
        </map>
    </property>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    5.数组

    <property name="phones">
      <set>
        <value>15799999918</value>
        <value>15788888819</value>
        <value>15766666620</value>
      </set>
    </property>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6.Properites

    <property name="prop">
        <props>
            <prop key="key1">value1</prop>
            <prop key="key2">value2</prop>
        </props>
    </property>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    6.1.3 用户自定义数据类型

    1.为成员变量添加 set/get 方法

    public class UserServiceImpl implements UserService {
        
        private UserDao userDao;
    
        public UserDao getUserDao() {
            return userDao;
        }
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
        @Override
        public void print() {
            userDao.print();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.bean 标签使用 ref 属性

    <bean id="userDao" class="com.xxl.dao.impl.UserDaoImpl" />
    <bean id="userService" class="com.xxl.service.impl.UserServiceImpl">
       <property name="userDao" ref="userDao"/>
    </bean>
    
    • 1
    • 2
    • 3
    • 4

    上面的例子中,因为 userDao 是 userService 的一个成员变量,所以在配置文件中需要使用 property 标签,ref
    指向了 userDao 这个对象,然后调用 userDao 的 set 方法为 userDao 赋值。

    6.1.3 自动注入

    我们还可以使用 bean 标签的 autowire 属性为自定义变量自动赋值。当类中引用类型的属性名和 bean 标签的 id 值相同时,我们可以使用 byName。例如:

    <bean id="userDao" class="com.example.dao.impl.UserDaoImpl" />
    
    <bean id="userService" autowire="byName" class="com.example.service.impl.UserServiceImpl" />
    
    • 1
    • 2
    • 3

    当类中引用类型的全限定名和 bean 标签的 class 属性的值相同,或者是子类、实现类,我们可以使用 byType。例如:

    <bean id="userDao" class="com.example.dao.impl.UserDaoImpl" />
    
    <bean id="userService" autowire="byType" class="com.example.service.impl.UserServiceImpl" />
    
    • 1
    • 2
    • 3

    6.1.4 构造注入

    构造注入:Spring 调用构造方法通过配置文件为成员变量赋值。

    1.为类添加构造方法

    public class User {
        private String name;
        private int age;
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    
    • 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

    2.修改配置文件
    在 bean 标签中使用 constructor-arg 标签。

    <bean class="com.xxl.model.User">
        <constructor-arg value="张三"/>
        <constructor-arg value="18"/>
    </bean>
    
    • 1
    • 2
    • 3
    • 4

    6.1.5 注入总结

    注入就是通过 Spring 的配置文件为类的成员变量赋值。在实际开发中,我们一般采用 Set 方式为成员变量赋值。

    7.Bean 的生命周期

    Bean 生命周期指的就是由 Spring 管理的对象从创建到销毁的过程,和人生老病死的过程一样。

    它主要分为三个阶段:创建 --> 初始化 --> 销毁

    7.1 创建阶段

    Spring 工厂创建对象的方式分两类:

    7.1.1 singleton 模式

    当 scope 属性为 singleton ,创建 Spring 工厂的同时创建所有单例对象

    spring 配置文件注册 bean :

    <bean id="user" class="com.example.model.User">
        <property name="name" value="小曲同学"/>
        <property name="age" value="23"/>
    </bean>
    
    • 1
    • 2
    • 3
    • 4

    我们发现当创建 Spring 工厂的同时就会调用对象的构造方法。因为 spring 中 bean 默认的 scope 就是 singleton ,所以创建工厂的同时默认就会创建多个单例对象
    如果想修改创建单例对象的方式为获取的时候才创建,只需要在 bean 标签上面添加如下属性:

    lazy-init="true"
    
    • 1

    7.1.2 prototype 模式

    只有获取对象的时候才会创建对象。
    修改 bean 标签的 scope 属性:

    <bean id="user" class="com.example.model.User" scope="prototype">
        <property name="name" value="知否君"/>
        <property name="age" value="23"/>
    </bean>
    
    • 1
    • 2
    • 3
    • 4

    7.2 初始化阶段

    spring 中 bean 的初始化操作指的是在创建对象的时候完成一些附加的功能。bean 的初始化操作有两种实现方式:

    7.2.1 实现 InitializingBean 接口

    public class 类名 implements InitializingBean {
        public void afterPropertiesSet(){
           // 初始化方法操作
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    7.2.2 通过创建普通方法完成初始化

    在 User 类中创建一个方法

    // 初始化方法
      public void initMethod() {
          this.name = "张无忌";
      }
    
    • 1
    • 2
    • 3
    • 4

    在配置文件中配置 init-method 属性

    <bean id="user" class="com.example.model.User" init-method="initMethod" >
        <property name="name" value="小曲同学"/>
        <property name="age" value="23"/>
    </bean>
    
    • 1
    • 2
    • 3
    • 4

    初始化方法修改了注入的值,所以初始化方法一定在注入之后执行。

    6.3 销毁阶段

    Spring 销毁对象前,会调用对象的销毁方法,完成销毁操作。
    Spring 什么时候销毁所创建的对象?当 Spring 工厂关闭时,Spring 工厂会调用我们自定义的销毁方法。

    销毁方法的定义有两种方式:

    6.3.1 实现DisposableBean接口

    public class 类名 implements DisposableBean {
        // 销毁操作
        @Override
        public void destroy(){
            // 销毁操作业务
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6.3.2 创建普通方法

    // 销毁方法
      public void destroyMethod() {
         // 销毁操作业务
      }
    
    • 1
    • 2
    • 3
    • 4

    在配置文件中配置 destroy-method 属性

    <bean id="user" class="com.xxl.model.User" destroy-method="destroyMethod">
          <property name="name" value="知否君"/>
          <property name="age" value="23"/>
      </bean>
    
    • 1
    • 2
    • 3
    • 4

    8. 代理设计模式

    代理设计模式:通过代理类为目标类做一些额外(非业务)的功能。

    专业名词解释:

    1. 目标类(原始类):指的是完成业务的核心类,一般指的是 service 层的各种实现类。
    2. 目标方法(原始方法):目标类中的方法是目标方法(原始方法)。
    3. 额外功能(附加功能):打印日志等非业务功能。

    8.1 代理设计模式开发步骤:

    1. 代理类和目标类实现相同的接口
    2. 代理类中除了要调用目标类的方法实现业务功能,还要实现额外功能。
    // 接口
    public interface CalculateService {
      业务方法
    }
    
    // 目标类
    public CalculateServiceImpl implements CalculateService {
      业务方法
    }
    
    // 代理类:要实现目标类相同的接口
    public CalculateServiceProxy implements CalculateService {
     // 业务方法
     // 额外功能
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    8.2 静态代理

    静态代理:给每一个目标类手动开发一个代理类。

    public interface CalculateService {
       // 加法
      int add(int a,int b);
       // 减法
      int sub(int a,int b);
    }
    // 目标类
    public CalculateServiceImpl implements CalculateService {
       @Override
        public int add(int a, int b) {
            int result = a + b;
            return result;
        }
        @Override
        public int sub(int a, int b) {
            int result = a - b;
            return result;
        }
    }
    // 代理类:要实现目标类相同的接口
    public CalculateServiceProxy implements CalculateService {
      private CalculateService calculateService = new CalculateServiceImpl();
      @Override
        public int add(int a, int b) {
            System.out.println("方法执行前打印");
            int result = calculateService.add(a,b);
            System.out.println("方法执行后打印");
            return result;
        }
        @Override
        public int sub(int a, int b) {
            System.out.println("方法执行前打印");
            int result = calculateService.sub(a,b);
            System.out.println("方法执行后打印");
            return result;
        }
    }
    
    • 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

    通过上面的例子我们发现静态代理也存在很多问题:

    1. 如果存在很多目标类,我们就要手动创建一堆代理类,太繁琐。
    2. 代理类中混杂着目标类方法和额外功能,代码耦合度高。

    那有没有这样一种代理模式?

    1. 目标类和代理类互不干扰
    2. 代码耦合度低,便于维护

    有的,动态代理闪亮登场!

    8.3 动态代理

    动态代理:也是通过代理类为目标类做一些额外的功能,但是不用手动写一堆代理类,而是动态地为目标类创建代理类。

    开发流程:

    1、引入依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.16.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.9.5</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.5</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这里我们主要是引入了 aspectj 这个技术,aspectj 是 spring 社区中非常流行的基于动态代理技术的框架。

    2、创建目标类和目标方法

    接口:

    public interface CalculateService {
        // 加法
        int add(int a,int b);
        // 减法
        int sub(int a,int b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    实现类(目标类):

    public class CalculateServiceImpl implements CalculateService {
         @Override
        public int add(int a, int b) {
            int result = a + b;
            System.out.println("加法操作。。。");
            return result;
        }
    
        @Override
        public int sub(int a, int b) {
            int result = a - b;
            System.out.println("减法操作。。。");
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.在 spring 配置文件中注册 bean

    <bean id="calculateService"  class="com.example.service.impl.CalculateServiceImpl" />
    
    • 1

    4.实现额外功能

    这里我们需要创建一个类实现 MethodInterceptor 接口:

    /**
     * @Desc: 动态代理完成非业务功能
     * @Author: 
     * @date: 下午8:49 2022/9/20
     */
    public class PrintLog implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            System.out.println("在目标方法执行之前打印。。。。。");
            // 执行目标方法
            Object object = methodInvocation.proceed();
            System.out.println("在目标方法执行之后打印。。。。。");
            return object;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.注册完成额外功能的 bean

    <bean id="printLog" class="com.example.aop.PrintLog" />
    
    • 1

    6、定义切入点

    <!--切入点:给哪些方法加入额外功能-->
    <aop:config>
      <aop:pointcut id="pc" expression="execution(* * (..))"/>
    </aop:config>
    
    • 1
    • 2
    • 3
    • 4

    7、组装切入点和额外功能

    <!--切入点:给哪些方法加入额外功能-->
    <aop:config>
      <aop:pointcut id="pc" expression="execution(* * (..))"/>
      <aop:advisor advice-ref="printLog" pointcut-ref="pc"/>
    </aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    9. AOP

    9.1 AOP 概念

    AOP: 全称 Producer Oriented Programing,即面向切面编程。

    那啥是面向切面编程?其实说白了还是 Spring 的动态代理,通过代理类为原始类增加一些 额外功能(例如打印等)。

    那啥是切面?

    切面 = 切入点 + 额外功能。

    切入点:额外功能作用的位置,在哪些类哪些方法上。

    9.2 AOP 底层实现原理

    AOP 的底层还是使用 Spring 的动态代理技术创建代理类对象。

    动态代理的方式分为两种:

    • 基于接口实现动态代理:JDK 动态代理
    • 基于继承实现动态代理:Cglib 动态代理

    9.2.1 JDK 动态代理

    创建代理对象的三个元素:

    • 1.原始对象
    • 2.额外功能
    • 3.原始对象实现的接口
    Proxy.newPorxyInstance(classloader,interfaces,invocationHandler)
    
    • 1
    • classloader:叫做类加载器,它可以用来创建代理对象。
    • interfaces:原始对象实现的接口
    • invocationHandler:额外功能

    完整代码:

    @Test
    public void testJDKProxy() {
        // 1. 原始对象
        CalculateService calculateService = new CalculateServiceImpl();
    
        // 2. JDK 动态代理:包含额外功能
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("---- 方法执行前打印 ----");
                // 执行原始方法
                Object result = method.invoke(calculateService, args);
                System.out.println("---- 方法执行后打印 ----");
                return result;
            }
        };
        // 3. 代理类
        CalculateService calService = (CalculateService) Proxy.
                newProxyInstance(CalculateService.class.getClassLoader(),
                        calculateService.getClass().getInterfaces(),
                        handler);
        // 4. 执行方法
        int result = calService.add(1, 2);
        System.out.println("result:" + result);
    }
    
    • 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

    9.2.2 Cglib 动态代理

    CGlib 创建动态代理的原理:原始类作为父类,代理类作为子类,通过继承关系创建代理类。

    代码格式:

    Enhancer enhancer = new Enhancer();
    enhancer.setClassLoader(classLoader);
    enhancer.setSuperclass(calculateService);
    enhancer.setCallback(interceptor);
    
    • 1
    • 2
    • 3
    • 4

    (1)classLoader:类加载器(了解即可)
    (2)Superclass:父类,就是原始类
    (3)interceptor:额外功能

    完整代码:

    @Test
        public void testCglibProxy() {
            // 1. 原始对象
            CalculateService calculateService = new CalculateServiceImpl();
    
            // 2. Cglib 动态代理:包含额外功能
            MethodInterceptor interceptor = new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    System.out.println("---- 方法执行前打印 ----");
                    // 执行原始方法
                    Object result = method.invoke(calculateService, args);
                    System.out.println("---- 方法执行后打印 ----");
                    return result;
                }
            };
    
            Enhancer enhancer = new Enhancer();
            enhancer.setClassLoader(CalculateService.class.getClassLoader());
            enhancer.setSuperclass(calculateService.getClass());
            enhancer.setCallback(interceptor);
    
            // 3. 创建代理类
            CalculateService calService = (CalculateService)enhancer.create();
            // 4. 执行方法
            int result = calService.add(3, 4);
            System.out.println("result:" + result);
        }
    
    • 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
  • 相关阅读:
    论NFV的六大用例
    1.4 w字,25 张图让你彻底掌握分布式事务原理
    Python | 排列与组合
    手把手教会将 Windows 窗体桌面应用从.NET Framework迁移到 .NET SDK/.NET 6 格式
    【Linux】基本操作指令汇总(不完全)
    记录一次较为完整的Jenkins发布流程
    1、JVM:什么是JVM?
    vue中常用的两种路由模式
    大厂秋招真题【贪心】美团20230826秋招T2-小美的数组重排
    Nginx Linux ubuntu离线安装部署教程
  • 原文地址:https://blog.csdn.net/weixin_44427181/article/details/126949202