Spring 手动实现Spring底层机制

news2024/11/19 18:34:23

目录

一、前言

二、Spring底层整体架构

        1.准备工作 : 

        2.架构分析 : (重要)

        3.环境搭建 :

三、手动实现Spring容器结构

        1.自定义注解 : 

            1.1 @Component注解 

            1.2 @Scope注解

        2.自定义组件 : 

        3.自定义用于封装Bean信息的BeanDefinition类:

        4.自定义IOC容器 : 

        5.运行测试 : 

四、手动实现Spring依赖注入

        1.准备工作 : 

        2.代码实现 : 

        3.运行测试 : 

五、手动实现Spring后置处理器

        1.实现思路 : 

        2.代码实现 : 

        3.运行测试 : 

六、手动实现SpringAOP机制

        1.准备工作 : 

        2.代码实现 : 

        3.运行测试 : 

七、总结


一、前言

  • 第六节内容,我们一起来手动实现一下Spring的底层机制,这有助于加深我们对Spring的理解;包括手动实现Spring容器结构,手动实现IOC依赖注入,手动实现Bean的后置处理器机制,以及手动实现AOP机制等(PS : AOP 底层是基于 BeanPostProcessor 机制的)
  • 注意事项——①代码中的注释也很重要;不要眼高手低,自己跟着敲一遍才真正有收获;点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
  • PS : up会博文中会展示出所有的源代码,不过考虑到有朋友们想要完整源代码包的需求,up会将代码上传到CSDN 和 Github,没会员可以去Github下载。
  • 良工不示人以朴,up所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。感谢阅读!

二、Spring底层整体架构

        1.准备工作 : 

                在IDEA中搭建Maven项目,原型Archetype选择“org.apache.maven.archetypes:maven-archetype-webapp”,建立Maven项目后,在pom.xml配置文件中引入如下依赖 : 

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.8</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.8</version>
    </dependency>
    <dependency>
      <groupId>org.dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>2.1.4</version>
    </dependency>
  </dependencies>

                建立java, resources, test包,并右键通过 “Mark Directory As” 进行标记,如下图所示:

                接着,up在java包下,建了OrderDAO, OrderService, OrderServlet这几个类,如下图所示 : 

                OrderDAO类代码如下 : 

package com.cyan.spring.component;

import org.springframework.stereotype.Component;

@Component
public class OrderDAO {
    public void saveOrder() {
        System.out.println("保存订单~");
    }
}

                OrderService类代码如下 : 

package com.cyan.spring.component;

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

@Component
public class OrderService {
    @Autowired
    private OrderDAO orderDAO;

    public void saveOrder() {
        orderDAO.saveOrder();
    }
}

                OrderServlet类代码如下 : 

package com.cyan.spring.component;

import org.springframework.stereotype.Component;

@Component
public class OrderServlet {

}

                然后,up在resources目录下建立一个beans.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"
       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.cyan.spring.component"/>
</beans>

                PS : 在IDEA中,如果是Java项目,beans.xml文件要放在src目录下;而在Maven项目中,需要放在resources目录下

                接着,我们新建一个spring.test包,并在该包下新建一个类AppMain,如下图所示 : 

                AppMain类代码如下 : 

package com.cyan.spring.test;

import com.cyan.spring.component.OrderDAO;
import com.cyan.spring.component.OrderService;
import com.cyan.spring.component.OrderServlet;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class AppMain {
    public static void main(String[] args) {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");

        OrderServlet orderServlet = ioc.getBean("orderServlet", OrderServlet.class);
        OrderServlet orderServlet2 = ioc.getBean("orderServlet", OrderServlet.class);
        OrderService orderService = ioc.getBean("orderService", OrderService.class);
        OrderDAO orderDAO = ioc.getBean("orderDAO", OrderDAO.class);

        System.out.println("orderServlet = " + orderServlet);
        System.out.println("orderServlet2 = " + orderServlet2);
        System.out.println("orderService = " + orderService);
        System.out.println("orderDAO = " + orderDAO);
    }
}

                运行结果 : 

                可以看到,在默认情况下,我们配置的@Component, @Controller, @Service, @Repository组件都是单例的
                成功运行后,我们可以看到实际的工作目录target/classes下,可以找到beans.xml文件,如下图所示 :

        2.架构分析 : (重要

                Spring整体底层架构如下图所示 : (从上至下,从左到右)

        3.环境搭建 :

                在上文“准备工作”创建的Maven项目的基础上,新建一个并行的模块Module(也可以不这么做, whatever),选择同样的Maven原型,只不过Parent为None,然后引入同样的maven依赖,并创建beans.xml配置文件。如下图所示 :

                创建这个新的模块的目的是为了有一个干净的环境,不会被之前创建的包干扰(当然,你就在原来那个模块下写也是可以的)。 
                接着,我们右键选择“Open Module Settings”,把两个模块的Language level都修改为17(注意:修改pom.xml配置文件中的依赖,会引起此处Language level的变化,需要再手动修改回来,如下图所示 : 

                另外,在"Settings-->Build,Execution,Deployment-->Compiler-->Java Compiler"中,将右侧的version也改成17,如下图所示 : 


三、手动实现Spring容器结构

        1.自定义注解 : 

            1.1 @Component注解 

                在annotation包下自定义一个Component注解,用于模拟Spring原生的"@Component"注解,该注解可以标注一个类为Bean组件。代码如下 : 

package com.cyan.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
    自定义的Component注解,用于模拟Spring原生的"@Component"注解,
    该自定义注解也是用于标注组件。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}

            1.2 @Scope注解

                在annotation包下自定义一个Scope注解,用于模拟Spring原生的"@Scope"注解,该注解可以标注一个Bean为单例还是多例(singleton OR prototype)。代码如下 : 

package com.cyan.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
    自定义的Scope注解,用于模拟Spring原生的"@Scope"注解,
    该自定义注解也是用于标注Bean为单例还是多例。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
    String value() default "";
}

        2.自定义组件 : 

                在component包下创建OrderService和OrderDAO两个类,表示待注入的组件,并用我们自定义的@Component注解和自定义的@Scope注解进行标注。(假设OrderService为单例singleton,而OrderDAO为多例prototype)
                OrderService类代码如下 : 

package com.cyan.spring.component;

import com.cyan.spring.annotation.Component;
import com.cyan.spring.annotation.Scope;

/**
    OrderService用了自定义的@Component标注,表示这是一个组件,会被注入到IOC容器中。
    OrderService用了自定义的@Scope标注,表明了OrderService为单例singleton。
 */
@Component(value = "orderService")
@Scope(value = "singleton")
public class OrderService {
}

                OrderDAO类代码如下 : 

package com.cyan.spring.component;

import com.cyan.spring.annotation.Component;
import com.cyan.spring.annotation.Scope;

/**
    OrderDAO也用了自定义的@Component标注,表示这是一个组件,会被注入到IOC容器中。
    OrderDAO用了自定义的@Scope标注,表明了OrderDAO为多例prototype。
 */
@Component(value = "orderDAO")
@Scope(value = "prototype")
public class OrderDAO {
}

                假设com.cyan.spring.component包就是我们要扫描的包,beans.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"
       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.cyan.spring.component"/>
</beans>

        3.自定义用于封装Bean信息的BeanDefinition类:

                通过上文“Spring底层架构分析图”我们可以得知——通过Dom4J解析beans.xml后,我们不能直接将获取到的Bean信息封装到Map容器中,而是应该先将需要的Bean的信息封装在一个BeanDefinition对象中,再将该BeanDefinition对象保存到IOC容器维护的Map容器中。BeanDefinition类代码如下 : 

package com.cyan.spring.ioc;

/**
    该类对象用于封装Bean的相关信息,且对象最终会存放在自定义的IOC容器中。
 */
public class BeanDefinition {
    private String scope;       //Bean的单例多例情况
    private Class<?> clazz;     //Bean的字节码文件对象

    public BeanDefinition() {
    }
    public BeanDefinition(String scope, Class<?> clazz) {
        this.scope = scope;
        this.clazz = clazz;
    }

    public String getScope() {
        return scope;
    }
    public void setScope(String scope) {
        this.scope = scope;
    }

    public Class<?> getClazz() {
        return clazz;
    }
    public void setClazz(Class<?> clazz) {
        this.clazz = clazz;
    }

    @Override
    public String toString() {
        return "BeanDefinition{" +
                "scope='" + scope + '\'' +
                ", clazz=" + clazz +
                '}';
    }
}

        4.自定义IOC容器 : 

                在自定义的IOC容器中,首先我们肯定得获取到被扫描的包的信息,这里up使用Dom4j来解析target/classes目录下的beans.xml配置文件,通过DOM操作来获取到被扫描的包,这一点和我们在“Spring IOC—基于注解配置和管理Bean”一文中手动实现Spring注解配置机制有所不同,但我们在“手写Tomcat底层机制”中已经有过“使用DOM4j解析XML配置文件”的经验了,不熟悉的朋友可以先去快速看一下up之前写的源码。

                得到被扫描的包后,后续步骤同我们之前写的手动实现Springt注解配置机制类似,即遍历该包下的.class文件,然后得到每个资源的Class对象。我们需要对每个Class对象进行判断,如果它是一个Bean类型,就要将Bean的配置信息封装到BeanDefinition对象中,并将BeanDefinition对象保存在IOC容器维护的Map容器中。这是初始化IOC容器的第一步,beanDefinitionMap初始化完毕后,还需要初始化单例池singletonObjects。

                记住,beanDefinitionMap的初始化是单例池初始化的前提,因为单例池需要根据beanDefinitionMap中的信息来创建Bean实例。当然,基于OOP的思想,“初始化beanDefinitionMap”,“初始化singletonObjects”,以及“反射创建Bean实例”的代码我们都可以分别封装在方法中,然后在自定义的IOC容器的构造器中调用需要的方法即可。

                当初始化IOC容器的任务完成后,我们就需要定义自己的getBean(String id)方法,正如我们在上文“Spring整体底层架构分析图”中描绘的那样,我们在getBean(..)方法中需要先判断传入的beanName是否是beanDefinitionMap中的一个key。若不存在,可以考虑直接throw一个NullPointerException异常对象;若存在,继续判断它是单例还是多例,如果是singleton单例,就直接从单例池中获取实例,并返回,如果是prototype多例,就调用已经封装好的createBean(BeanDefinition beanDefinition)方法来反射创建新的对象,并返回。

                CyanApplicationContext类代码如下 : 

package com.cyan.spring.ioc;

import com.cyan.spring.annotation.Autowired;
import com.cyan.spring.annotation.Component;
import com.cyan.spring.annotation.Scope;
import com.cyan.spring.component.OrderDAO;
import com.cyan.spring.component.OrderService;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.util.StringUtils;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class CyanApplicationContext {
    //scanPath属性用于暂时保存要扫描的包的路径(由Dom4j解析得到)
    private String scanPath;

    //beanDefinitionMap属性用于存放Bean的配置信息(以对象的形式存放)
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    //singletonObjects属性用于存放创建的Bean对象/实例(单例池)
    private final ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();

    public CyanApplicationContext() {
        //初始化BeanDefinitionMap
        this.initBeanDefinitionMap();
        //初始化单例池
        this.initSingletonObjects(this.beanDefinitionMap);

        System.out.println("beanDefinitionMap = " + beanDefinitionMap);
        System.out.println("singletonObjects = " + singletonObjects);
    }

    //初始化BeanDefinitionMap的方法
    private void initBeanDefinitionMap() {
        //1.通过DOM4j来解析target/classes目录下的beans.xml配置文件
            //1.1 获取beans.xml的实际工作路径path
        String path = CyanApplicationContext.class.getResource("/").getPath();
        //path = /D:/JAVA/IDEA/IntelliJ_IDEA/javaProject/SSM/cyanSpring_selfDef/target/classes/

        SAXReader saxReader = new SAXReader();
        try {
            //1.2 获取Document文档对象(注意文件名)
            Document document = saxReader.read(new File(path + "beans.xml"));
            System.out.println("document = " + document);

            //1.3 获取beans.xml配置文件的根元素beans
            Element rootElement = document.getRootElement();

            //1.4 获取根元素下的所有子元素(context:component-scan元素)
            List<Element> elements = rootElement.elements();

        //2.遍历并判断获得的元素,得到扫描包的包名
            for (Element element : elements) {
                if ("component-scan".equals(element.getName())) {
                    scanPath = element.attributeValue("base-package");
                    //scanPath = com.cyan.spring.component
                }
            }
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }

        //3.将包名转换为路径格式
        scanPath = scanPath.replace(".", "/");
        System.out.println("scanPath = " + scanPath);

        //4.获取App类加载器,以得到真实的工作路径(资源目录)————target/classes下的.class文件
        ClassLoader classLoader = CyanApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(scanPath);
        System.out.println("resource = " + resource);

        //5.遍历目标包下的所有资源(.class文件)
            //5.1 首先获取到该包对应的File
        File scanPackage = new File(resource.getFile());

        /*
            PS : 注意路径中不要有特殊字符,比如空格,否则会判断不是目录,
            报错NullPointerException : Cannot read the array length because "<local8>" is null.
         */
            //5.2 判断该File是不是一个目录
        if (scanPackage.isDirectory()) {
            //5.3 获取被扫描的包下的所有文件/资源(.class)
            File[] files = scanPackage.listFiles();
            //5.4 使用增强for对所有.class文件进行遍历
            for (File file : files) {
                System.out.println("===========================================");
                //D:\JAVA\IDEA\IntelliJ_IDEA\javaProject\SSM\cyanSpring_selfDef\target\classes\com\cyan\spring\component\OrderDAO.class
                System.out.println(file.getAbsolutePath());

            //5.5 对资源的绝对路径做接收
                String fileAbsolutePath = file.getAbsolutePath();

            //5.6 判断是否为.class文件
                if (fileAbsolutePath.endsWith(".class")) {
            //5.7 得到用于反射的全类名
                    //先获取到包名(反转)
                    scanPath = scanPath.replace("/", ".");
                    //再获取到类名(此处两个右斜杠表示转义)
                    String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    //最后得到反射所需的全类名(全类名 = 包名 + 类名; 用于反射)
                    String fullClassName = scanPath + "." + className;

            //5.8 反射获取Class对象
                    try {
                        Class<?> clazz = classLoader.loadClass(fullClassName);

                        //判断是否该类是否需要被实例化(是否被特定注解标识)
                        if (clazz.isAnnotationPresent(Component.class)) {
                            //若被自定义的@Component注解修饰,说明它是一个组件,是Bean类型,需要被注入

        //6.将Bean信息封装到BeanDefinition对象中
            //6.1 定义一个BeanDefinition对象
                            BeanDefinition beanDefinition = new BeanDefinition();
            //6.2 获得Bean的beanName
                            /*
                                "Declared"表示不包括从父类继承来的注解,
                                该方法只有在Java8中才可以使用。
                             */
                            Component component = clazz.getDeclaredAnnotation(Component.class);
                            String beanName = component.value();
                            //若没有指定bean的id值,默认以类名首字母小写作为id
                            if ("".equals(beanName)) {
                                beanName = StringUtils.uncapitalize(className);
                            /*
                                若不想使用SpringFramework的工具类,可以如下写:
                                className.toLowerCase().substring(0,1) + className.substring(1);
                             */
                            }

            //6.3 获得Bean的Scope值
                            if (clazz.isAnnotationPresent(Scope.class)) {
                                Scope scope = clazz.getAnnotation(Scope.class);
                                String scopeValue = scope.value();
                                beanDefinition.setScope(scopeValue);
                            } else {
                                //若没有使用@Scope注解标识,默认为单例
                                beanDefinition.setScope("singleton");
                            }
            //6.4 将Bean的beanName, 以及Scope值和Class对象进行封装
                            beanDefinition.setClazz(clazz);

        //7. 将封装好的BeanDefinition对象保存到Map容器中
                            beanDefinitionMap.put(beanName, beanDefinition);
                        } else {
                            System.out.println("Sorry, this isn't a bean!");
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
    //初始化单例池的方法(注意形参列表中泛型的使用)
    private void initSingletonObjects(ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap) {
        //遍历枚举类型
        //PS : asIterator()方法只有在Java9之后才能使用。
        //keys是所有的beanName
        Enumeration<String> keys = beanDefinitionMap.keys();
        Iterator<String> iterator = keys.asIterator();

        String beanName = null;
        BeanDefinition beanDefinition = null;

        //通过迭代器进行遍历
        while (iterator.hasNext()) {
            if ((beanName = iterator.next()) != null) {
                //若BeanDefinitionMap中存在该beanName,就获取到对应的BeanDefinition对象。
                beanDefinition = beanDefinitionMap.get(beanName);

                /*
                    此处实际不需要Objects的静态方法,
                    因为ConcurrentHashMap是线程安全的,不支持null键或null值,
                    此处使用仅为了回顾。
                 */
                if (Objects.requireNonNull(beanDefinition) != null) {
                    String scope = beanDefinition.getScope();
                    if ("singleton".equalsIgnoreCase(scope)) {
                        //调用已经定义好的实例化Bean的方法
                        Object instance = this.createBean(beanDefinition);
                        singletonObjects.put(beanName, instance);
                    }
                }
            }
        }
    }

    /*
        自定义createBean(...)方法,用于根据传入的BeanDefinition对象,创建Bean对象.
        不管是实例化单例池,还是多例Bean的懒加载,底层都是通过反射来创建Bean对象。
     */
    private Object createBean(BeanDefinition beanDefinition) {
        Class<?> clazz = beanDefinition.getClazz();
        //若反射过程中出现异常,默认返回null
        Object instance = null;

        try {
            Constructor<?> constructor = clazz.getConstructor();
            instance = constructor.newInstance();

            /**依赖注入*/
            //1.首先通过Class对象获取到所有Field字段。
            Field[] fields = clazz.getDeclaredFields();

            //2.遍历所有Filed字段
            for (Field field : fields) {
            //3.判断哪些字段用了自定义的@Autowired注解修饰
                if (field.isAnnotationPresent(Autowired.class)) {
                    //required属性默认为true.
                    if (field.getAnnotation(Autowired.class).required() == true) {
                        String fieldName = field.getName();
                        System.out.println("fieldName = " + fieldName);

            //4.根据字段名,获取Bean对象
                        Object bean = this.getBean(fieldName);

            //5.通过set(Object obj, Object value)方法为instance实例注入依赖
                        //开启暴力反射
                        field.setAccessible(true);
                        field.set(instance, bean);
                    }
                }
            }

        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        return instance;
    }

    //自定义getBean(...)方法,通过id返回Bean对象
    public Object getBean(String id) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(id);

        //判断beanDefinitionMap中是否存在该key
        if (beanDefinitionMap.containsKey(id)) {
            String scope = beanDefinition.getScope();
            if ("singleton".equalsIgnoreCase(scope)) {
                return singletonObjects.get(id);
            } else {
                //每次反射创建对象,都是一个新的对象,符合“多例”的特点。
                Object bean = createBean(beanDefinition);
                return bean;
            }
        } else {
            throw new NullPointerException("No such bean exists in the beanDefinitionMap!");
        }
    }
}

        5.运行测试 : 

                在test包下新建一个AppMain类用于测试,如下图所示 :

                在AppMain类中我们通过getBean方法分别获取3次OrderService实例和3次OrderDAO实例,由于我们配置的@Scope中,OrderService为单例,OrderDAO为多例,所以预期结果应该是——3次获取到的OrderService实例是同一个Bean对象,而3次获取到的OrderDAO实例是三个不同的Bean对象。
                AppMain类代码如下 : 

import com.cyan.spring.component.OrderDAO;
import com.cyan.spring.component.OrderService;
import com.cyan.spring.ioc.CyanApplicationContext;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class AppMain {
    public static void main(String[] args) {
        CyanApplicationContext cyanApplicationContext = new CyanApplicationContext();
        OrderService orderService1 = (OrderService) cyanApplicationContext.getBean("orderService");
        OrderService orderService2 = (OrderService) cyanApplicationContext.getBean("orderService");
        OrderService orderService3 = (OrderService) cyanApplicationContext.getBean("orderService");

        OrderDAO orderDAO1 = (OrderDAO) cyanApplicationContext.getBean("orderDAO");
        OrderDAO orderDAO2 = (OrderDAO) cyanApplicationContext.getBean("orderDAO");
        OrderDAO orderDAO3 = (OrderDAO) cyanApplicationContext.getBean("orderDAO");

        System.out.println("orderService1 = " + orderService1);
        System.out.println("orderService2 = " + orderService2);
        System.out.println("orderService3 = " + orderService3);

        System.out.println("orderDAO1 = " + orderDAO1);
        System.out.println("orderDAO2 = " + orderDAO2);
        System.out.println("orderDAO3 = " + orderDAO3);
    }
}

                运行结果 : 


四、手动实现Spring依赖注入

        1.准备工作 : 

                首先,回顾一下,我们曾在“Spring IOC—基于注解配置和管理Bean”中简单了解了“泛型依赖注入”,其实就是某一个组件的属性是另一个组件的类型,从而产生了依赖关系。同样地,我们仍要通过@Autowired注解来实现,只不过这里的@Autowired注解不再是Spring原生的了,而是我们自定义的“@Autowired”注解。
                自定义的@Autowired注解类代码如下 : 

package com.cyan.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
    自定义的@Autowired注解,用于实现自动装配。
 */
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    boolean required() default true;
}

                为了验证我们自定义的@Autowired注解成功实现自动装配,我们在OrderDAO类中定义一个方法OrderDAO类代码如下 : 

package com.cyan.spring.component;

import com.cyan.spring.annotation.Component;
import com.cyan.spring.annotation.Scope;

/**
    OrderDAO也用了自定义的@Component标注,表示这是一个组件,会被注入到IOC容器中。
    OrderDAO用了自定义的@Scope标注,表明了OrderDAO为多例prototype。
 */
@Component(value = "orderDAO")
@Scope(value = "prototype")
public class OrderDAO {
    public void save() {
        System.out.println("OrderDAO's save() is invoked~");
    }
}

                同样地,OrderService类中要维护有一个OrderDAO类型的属性,该属性使用自定义的@Autowired注解修饰;在OrderService类中也定义一个save()方法,通过orderDAO属性调用。代码如下 : 

package com.cyan.spring.component;

import com.cyan.spring.annotation.Autowired;
import com.cyan.spring.annotation.Component;
import com.cyan.spring.annotation.Scope;

/**
    OrderService用了自定义的@Component标注,表示这是一个组件,会被注入到IOC容器中。
    OrderService用了自定义的@Scope标注,表明了OrderService为单例singleton。
 */
@Component(value = "orderService")
@Scope(value = "singleton")
public class OrderService {
    //使用自定义的Autowired注解
    @Autowired(required = true)
    private OrderDAO orderDAO;

    public void save() {
        orderDAO.save();
    }
}

        2.代码实现 : 

                其实我们在上文“自定义IOC容器”中,已经实现了依赖注入,就在我们封装好的createBean(BeanDefinition beanDefinition)方法中,需要注意一点,由于OrderService类维护的OrderDAO属性是被private关键字修饰的,即是私有的,所以我们在装配前,一定要先调用setAccessible(true)方法实现暴力反射。如下图所示 : 

        3.运行测试 : 

                在测试类AppMain中,我们获取OrderService实例,并调用save()方法,检测OrderDAO实例的save()方法是否被调用,若成功调用,则说明“通过自动装配实现依赖注入”实现成功。AppMain类代码如下 : 

package com.cyan.spring.test;

import com.cyan.spring.component.OrderService;
import com.cyan.spring.ioc.CyanApplicationContext;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class AppMain {
    public static void main(String[] args) {
        CyanApplicationContext cyanApplicationContext = new CyanApplicationContext();
        OrderService orderService1 = (OrderService) cyanApplicationContext.getBean("orderService");

        System.out.println("orderService1 = " + orderService1);

        orderService1.save();
    }
}

                运行结果 : 


五、手动实现Spring后置处理器

        1.实现思路 : 

                我们先来简单回顾一下原生Spring的后置处理器,那么要回顾后置处理器,就必须先回顾一下Bean的生命周期,如下图所示 : 

                我们知道,在原生Spring框架中,后置处理器的本质是一个实现了BeanPostProcessor接口的Java类,而这个实现类中重写了BeanPostProcessor接口中的两个方法,分别为postProcessBeforeInitialization(...) 和 postProcessAfterInitialization(...)。这两个方法允许该后置处理器在Bean的初始化方法(即配置Bean时init-method属性所指定的初始化方法)调用前 和 调用后进行相应的业务处理。

                那么,既然我们想自己模拟实现Spring的后置处理器机制,首先就要解决一个问题——初始化Bean的方法从哪儿来?

                因为在原生Spring框架中,我们是可以直接在beans.xml配置文件中为Bean指定初始化方法的,但是现在我们的beans.xml配置文件仅仅起到“指明扫描包”的作用,因为我们是手动模拟呀,当然要自己想办法弄一个Bean的初始化方法。诶,我们发现在原生Spring框架中,有这么一个接口,叫InitializingBean,如下图所示 : 

                可以看到,接口中有个“afterPropertiesSet()”方法,嗯?这个方法不就是“在属性注入之后”的意思吗?噢~,想想我们的Bean的生命周期,属性注入之后,那不就是Bean的初始化方法嘛。所以,可以想到,如果我们能自己模拟一个InitializingBean接口,岂不是可以利用afterPropertiesSet()方法来充当Bean的初始化方法?
                噢,这么一来,我们只需要让JavaBean去实现模拟的InitializingBean接口,并重写afterPropertiesSet()方法,Bean实例便有了自己的初始化方法。

                那儿,有了初始化方法后,我们是不是得考虑,上哪儿去调用这个初始化方法呢?嗯?那当然是我们之前定义的IOC容器类CyanApplicationContext了。在我们自定义的容器类中,我们不是封装了一个createBean(BeanDefinition beanDefinition)方法吗,在这个方法中我们反射创建了Bean实例,并且实现了依赖注入。那我们可以在创建好Bean实例后,判断是否需要调用初始化Bean的方法(PS : 在容器中,我们常常根据一个类是否实现了某个接口,来判断是否要执行相应的某个业务逻辑,这其实就是Java接口编程的实际运用。实际开发中,我们有时会让类去实现一个什么方法都没有定义的接口,称为“标记接口”,其实就是利用接口编程实现业务。)

                这还没完,要知道,原生的Spring的后置处理器是要在Bean的初始化方法调用前和调用后,分别调用postProcessBeforeInitialization(...) 和 postProcessAfterInitialization(...)方法。那么到现在为止,我们仅仅是解决了Bean的初始化方法的问题,剩下的才是重头戏。我们还需要自定义一个BeanPostProcessor接口,并在其中定义postProcessBeforeInitialization(...) 和 postProcessAfterInitialization(...)方法,从而让接口的实现类(自定义的后置处理器)去重写这两个方法。显然,接口的实现类需要用@Component注解修饰,表明这是一个组件,即Bean的后置处理器;它会和其他单例Bean一样,在容器启动时被实例化并放入到单例池中保存。So,我们只需要设法从单例池中取出所有的后置处理器对象,并在createBean方法的合适位置调用接口中的方法即可。

        2.代码实现 : 

                首先,up新建一个processor包,并在改包下定义一个自己的InitializingBean接口,如下图所示 :

                InitializingBean接口代码如下 : 

package com.cyan.spring.processor;

/**
    自定义的InitializingBean接口,
    afterPropertiesSet()方法用于充当Bean的初始化方法。
 */
public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

                接着,我们让OrderService 去实现这个自定义接口,并重写afterPropertiesSet()方法。(此处仅让OrderService去实现接口,是为了在IOC容器中进行判断测试,即判断Bean是否定义了初始化方法。
                OrderService类代码如下 : 

package com.cyan.spring.component;

import com.cyan.spring.annotation.Autowired;
import com.cyan.spring.annotation.Component;
import com.cyan.spring.annotation.Scope;
import com.cyan.spring.processor.InitializingBean;

/**
    OrderService用了自定义的@Component标注,表示这是一个组件,会被注入到IOC容器中。
    OrderService用了自定义的@Scope标注,表明了OrderService为单例singleton。
 */
@Component(value = "orderService")
@Scope(value = "singleton")
public class OrderService implements InitializingBean {
    //使用自定义的Autowired注解
    @Autowired(required = true)
    private OrderDAO orderDAO;

    public void save() {
        orderDAO.save();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("OrderService's init-method is invoked~");
    }
}

                在IOC容器类中,我们要在之前封装好的createBean(..)方法中进行判断,若当前Bean定义了初始化方法,就进行调用,如下图所示 : 

                继续,仍是在processor包下,自定义BeanPostProcessor接口,代码如下 : 

package com.cyan.spring.processor;

/**
    自定义的BeanPostProcessor接口
 */
public interface BeanPostProcessor {
    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    default Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}

                接着,在component包下,定义自己的后置处理器实现类,CyanBeanPostProcessor类代码如下 : (要注意后置处理器也是一个组件)

package com.cyan.spring.component;

import com.cyan.spring.annotation.Component;
import com.cyan.spring.processor.BeanPostProcessor;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
@Component
public class CyanBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("postProcessBeforeInitialization方法被调用~ " + ", bean=" + bean.getClass() + ", beanName=" + beanName);
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("postProcessorAfterInitialization方法被调用~ " + ", bean=" + bean.getClass() + ", beanName=" + beanName);
        return null;
    }
}

                继续,我们在IOC容器类中定义一个方法,用于取出单例池中所有的后置处理器对象,并返回,代码如下 : 

    //定义一个方法,用于从单例池中取出Bean后置处理器对象
    private List<BeanPostProcessor> getBeanPostProcessors() {
        List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

        for (Object singletonKey : this.singletonObjects.keySet()) {
            Object o = this.singletonObjects.get(singletonKey);
            //instanceof关键字回顾————实际参与判断的是引用变量指向的——堆空间中真正的对象
            if (o instanceof BeanPostProcessor) {
                beanPostProcessorList.add((BeanPostProcessor) o);
            }
        }

        return beanPostProcessorList;
    }

                有了这个方法,我们便可以在createBean(..)方法中去调用后置处理器对象,createBean(..)方法代码如下 : (注意,相比之前,形参增加了String beanName)

    /*
        自定义createBean(...)方法,用于根据传入的BeanDefinition对象,创建Bean对象.
        不管是实例化单例池,还是多例Bean的懒加载,底层都是通过反射来创建Bean对象。
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class<?> clazz = beanDefinition.getClazz();
        //若反射过程中出现异常,默认返回null
        Object instance = null;

        try {
            Constructor<?> constructor = clazz.getConstructor();
            instance = constructor.newInstance();

            /**依赖注入*/
            //1.首先通过Class对象获取到所有Field字段。
            Field[] fields = clazz.getDeclaredFields();

            //2.遍历所有Filed字段
            for (Field field : fields) {
                //3.判断哪些字段用了自定义的@Autowired注解修饰
                if (field.isAnnotationPresent(Autowired.class)) {
                    //required属性默认为true.
                    if (field.getAnnotation(Autowired.class).required() == true) {
                        String fieldName = field.getName();

                        //4.根据字段名,获取Bean对象
                        Object bean = this.getBean(fieldName);

                        //5.通过set(Object obj, Object value)方法为instance实例注入依赖
                        //开启暴力反射
                        field.setAccessible(true);
                        field.set(instance, bean);
                    }
                }
            }

        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        /** 调用Bean后置处理器的postProcessBeforeInitialization(..)方法 */
        //回顾———此处的Map.Entry实际为Map.Node类型。
        for (BeanPostProcessor beanPostProcessor : this.getBeanPostProcessors()) {
            Object current = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
            if (null != current) {
                instance = current;
            }
        }

        /** 判断是否调用Bean的初始化方法
         * 实际参与判断的是引用变量指向的——堆空间中真正的对象 */
        if (instance instanceof InitializingBean) { //多态
            try {
                ((InitializingBean) instance).afterPropertiesSet();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        /** 调用Bean后置处理器的postProcessAfterInitialization(..)方法 */
        //回顾———此处的Map.Entry实际为Map.Node类型。
        for (BeanPostProcessor beanPostProcessor : this.getBeanPostProcessors()) {
            Object current = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            if (null != current) {
                instance = current;
            }
        }

        return instance;
    }

        3.运行测试 : 

                我们在测试类中AppMain中只保留启动容器的代码,如下图所示 : 

                运行结果 :

                可以看到,在OrderService的初始化方法调用前和调用后,分别调用了Bean的后置处理器的postProcessBeforeInitialization方法和postProcessAfterInitialization方法。需要注意的是,我们之前配置的OrderDAO是"prototype"多例,按道理讲,它在容器初始化的时候,根本就不会进入单例池中,而且我们也没有在测试类中调用getBean方法,那为什么运行结果会显示后置处理器也作用于OrderDAO呢?

                其实,这是因为我们上文已经实现的“依赖注入”,即下图所示 : 

                由于OrderService的属性使用了@Autowired注解修饰,所以此处会进行Bean的自动装配,从而间接调用了getBean方法,而又因为OrderDAO是多例prototype,所以在getBean方法中又调用了createBean方法,从而触发了Bean的后置处理器


六、手动实现SpringAOP机制

        1.准备工作 : 

                首先,在annotation包下,我们当然需要定义用于标记AOP切面类的@Aspect注解,代码如下 : 

package com.cyan.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义的@Aspect注解,用于标识切面类。
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Aspect {
    String value() default "";
}

                其次,还需要定义用于实现各种通知的注解,up这里只定义了@Before注解 和 @AfterReturning注解,用于实现前置通知 和 返回通知@Before注解代码如下 : 

package com.cyan.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义的@Before注解,用于实现前置通知。
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Before {
    String value();

    String argNames() default "";
}

                @AfterReturning注解代码如下 : 

package com.cyan.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
    自定义的@AfterReturning注解,用于实现返回通知。
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AfterReturning {
    String value() default "";

    String pointcut() default "";

    String returning() default "";

    String argNames() default "";
}

                在component包下,新建Calculator接口,以及一个它的实现类Calculator_Demo1。
                Calculator接口代码如下 : 

package com.cyan.spring.component;

public interface Calculator {
    public abstract double add(double n1, double n2);
    public abstract double subtract(double n1, double n2);
    public abstract double multiply(double n1, double n2);
    public abstract double divide(double n1, double n2);
}

                Calculator_Demo1类代码如下 : (注意,实现类是一个组件,因此需要用@Component注解修饰)

package com.cyan.spring.component;

import com.cyan.spring.annotation.Component;

@Component
public class Calculator_Demo1 implements Calculator {
    @Override
    public double add(double n1, double n2) {
        double result = n1 + n2;

        System.out.println("result = " + result);

        return result;
    }

    @Override
    public double subtract(double n1, double n2) {
        double result = n1 - n2;

        System.out.println("result = " + result);

        return result;
    }

    @Override
    public double multiply(double n1, double n2) {
        double result = n1 * n2;

        System.out.println("result = " + result);

        return result;
    }

    @Override
    public double divide(double n1, double n2) {
        double result = -1;

        if (n2 != 0) {  //分母不允许为0
            result = n1 / n2;
        }

        System.out.println("result = " + result);

        return result;
    }
}

        2.代码实现 : 

                定义切面类CalculatorAspect,用我们自定义的@Aspect注解和@Component注解修饰,并在其中定义用于前置通知和返回通知的方法,代码如下 : 

package com.cyan.spring.component;

import com.cyan.spring.annotation.AfterReturning;
import com.cyan.spring.annotation.Aspect;
import com.cyan.spring.annotation.Before;
import com.cyan.spring.annotation.Component;

/**
    切面类CalculatorAspect,用于实现AOP切入表达式
 */
@Aspect
@Component
public class CalculatorAspect {

    //1.前置通知
    @Before(value = "execution(public double com.cyan.spring.component.Calculator_Demo1.add(double, double))")
    public void beforeAdvice() {
        System.out.println("--------------------前置通知~~");
    }

    //2.返回通知
    @AfterReturning(value = "execution(public double com.cyan.spring.component.Calculator_Demo1.add(double, double))")
    public void afterReturningAdvice() {
        System.out.println("--------------------返回通知~~");
    }
}

                之前我们说,"Bean的后置处理器"机制是"AOP"机制的底层支撑,现在我们就将深刻体会这一点。我们需要在CyanBeanPostProcessor的postProcessAfterInitialization(..)方法中做点手脚。
                首先,如果判断当前Bean是一个切面类,我们就必须获取到该切面类中定义的各种通知方法以及需要被切入的方法(即配置的切入点表达式),并且我们还得把这种映射关系保存到一个Map集合中保存到Map集合中有什么用呢?诶,别急。
                如果判断当前Bean是一个需要被代理的对象,就利用JDK的Proxy动态代理,返回一个代理对象,注意,在匿名内部重写的invoke方法中,我们便可以利用之前已经保存的Map集合,来反射调用前置通知的方法和返回通知的方法。

                CyanBeanPostProcessor类代码如下 : 

package com.cyan.spring.component;

import com.cyan.spring.annotation.AfterReturning;
import com.cyan.spring.annotation.Aspect;
import com.cyan.spring.annotation.Before;
import com.cyan.spring.annotation.Component;
import com.cyan.spring.processor.BeanPostProcessor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
@Component
public class CyanBeanPostProcessor implements BeanPostProcessor {
    //定义一个Map属性用于保存切入点表达式
        //key——String : 接口实现类需要被切入的方法的name + 空格 + 对应的通知类型
        //value——Method : 切面类定义的切入方法
    private ConcurrentHashMap<String, Method> pointcutMap = new ConcurrentHashMap<>();

    //定义一个CalculatorAspect类对象,用于invoke方法执行时作为参数传入。
    private CalculatorAspect calculatorAspect = new CalculatorAspect();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("postProcessBeforeInitialization方法被调用~ " + ", bean=" + bean.getClass() + ", beanName=" + beanName);
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws NoSuchMethodException {
        System.out.println("postProcessorAfterInitialization方法被调用~ " + ", bean=" + bean.getClass() + ", beanName=" + beanName);

        /**如果当前Bean是一个切面类,就设法获取到切入点表达式。*/
        if (bean.getClass().isAnnotationPresent(Aspect.class)) {
            //1.获取到切面类的Class对象
            Class<CalculatorAspect> calculatorAspectClass = CalculatorAspect.class;

            //2.遍历切面类的所有方法
            Method[] declaredMethods = calculatorAspectClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                //3.遍历过程中,拿到切入点表达式的value值
                if (method.isAnnotationPresent(Before.class)) {
                    Before before = method.getAnnotation(Before.class);
                    String value = before.value();

                    String pointcutMethodName = value.substring(value.lastIndexOf(".") + 1, value.lastIndexOf("("));

                    //Δ 你要是不知道通知类型,怎么执行各种通知?
                    //所以必须一并保存
                    String key = pointcutMethodName + " Before";
                    Method declaredMethod = calculatorAspectClass.getDeclaredMethod(method.getName());

                    //4.将被切入的方法名(以及对应的通知类型) 和 用于执行前置通知的方法的映射保存到Map中。
                    pointcutMap.put(key, declaredMethod);
                    //CalculatorAspect calculatorAspect = calculatorAspectClass.getConstructor().newInstance();
                    //method.invoke(calculatorAspect);
                } else if (method.isAnnotationPresent(AfterReturning.class)) {
                    AfterReturning afterReturning = method.getAnnotation(AfterReturning.class);
                    String value = afterReturning.value();

                    String pointcutMethodName = value.substring(value.lastIndexOf(".") + 1, value.lastIndexOf("("));

                    String key = pointcutMethodName + " AfterReturning";
                    Method declaredMethod = calculatorAspectClass.getDeclaredMethod(method.getName());

                    pointcutMap.put(key, declaredMethod);
                    //CalculatorAspect calculatorAspect = calculatorAspectClass.getConstructor().newInstance();
                    //method.invoke(calculatorAspect);
                }
            }
        }

        /** AOP——使用JDK的Proxy动态代理,返回一个代理对象(针对于接口的实现类) */
        //判断当前bean是否需要被代理
        if (bean instanceof Calculator) {
            //1.获取newProxyInstance方法的第一个参数———类加载器
            ClassLoader classLoader = CyanBeanPostProcessor.class.getClassLoader();

            //2.获取newProxyInstance方法的第二个参数———接口信息
            Class<?>[] interfaces = bean.getClass().getInterfaces();

            //3.获取newProxyInstance方法的第三个参数———处理器对象(通过匿名内部类来实现)
            InvocationHandler invocationHandler = new InvocationHandler() {

                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object result = null;

                    if ("add".equalsIgnoreCase(method.getName())) {
                        /** 前置通知*/
                        //PS : 匿名内部类中不能使用this关键字。
                        for (String key : CyanBeanPostProcessor.this.pointcutMap.keySet()) {
                            String[] names = key.split(" ");
                            if (names[1].equals("Before")) {
                                Method before = CyanBeanPostProcessor.this.pointcutMap.get(key);
                                before.invoke(calculatorAspect);
                            }
                        }

                        //反射调用接口实现类中的方法
                        result = method.invoke(bean, args);

                        /** 返回通知*/
                        for (String key : CyanBeanPostProcessor.this.pointcutMap.keySet()) {
                            String[] names = key.split(" ");
                            if (names[1].equals("AfterReturning")) {
                                Method before = CyanBeanPostProcessor.this.pointcutMap.get(key);
                                before.invoke(calculatorAspect);
                            }
                        }
                    } else {
                        //如果不是目标对象,直接执行方法
                        result = method.invoke(bean, args);
                    }
                    return result;
                }
            };

            //若当前Bean符合AOP机制,就返回代理对象
            Object instance = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

            return instance;
        }

        //若不走AOP机制, 则直接返回原生Bean
        return null;
    }
}

        3.运行测试 : 

                在测试类AppMain中,我们获取Calculator_Demo1对象,打印出它的运行类型,查看是否成功获得代理对象;接着,通过获取的代理对象调用 add方法,查看切面类配置的前置通知和返回通知是否切入成功。AppMain类代码如下 : 

package com.cyan.spring.test;

import com.cyan.spring.component.Calculator;
import com.cyan.spring.ioc.CyanApplicationContext;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class AppMain {
    public static void main(String[] args) {
        CyanApplicationContext cyanApplicationContext = new CyanApplicationContext();

        Calculator calculator = (Calculator) cyanApplicationContext.getBean("calculator_Demo1");
        System.out.println("calculator = " + calculator);
        System.out.println("calculator's Class = " + calculator.getClass());

        double result = calculator.add(11.2, 5.55);
        System.out.println("我在测试类AppMain中,result = " + result);
    }
}

                运行结果 : 

                可以看到,动态代理 + AOP 成功实现。


七、总结

  • 🆗,以上就是Spring系列博文第六小节的全部内容了。
  • 总结一下,最重要的就是“Spring 底层架构”的那张图,理解了那张图之后,实现Spring底层容器结构便不是难事;实现依赖注入也就是在IOC容器类上做做手脚。当然,Bean后置处理器机制的实现也很重要,因为它直接作为AOP机制的底层支撑。总体来说,代码量较大,结合注释来看会好一点,但需要亲手去敲,亲手反复地去敲。
  • 下一节内容——Spring jdbcTemplate,我们不见不散😆。感谢阅读!

        System.out.println("END-----------------------------------------------------"); 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1466485.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32 SPI(基础概念)

文章目录 前言一、SPI通信协议概述二、SPI硬件框图和软件层次三、SPI通信时序四、SPI控制器总结 前言 本篇文章来给大家讲解一个非常重要的通信协议SPI&#xff0c;SPI在MCU和外设之间的通信用的是非常多的&#xff0c;这篇文章将带大家先来学习SPI的一些概念。 一、SPI通信协…

alist修改密码(docker版)

rootarmbian:~# docker exec -it [docker名称] ./alist admin set abcd123456 INFO[2024-02-20 11:06:29] reading config file: data/config.json INFO[2024-02-20 11:06:29] load config from env with prefix: ALIST_ INFO[2024-02-20 11:06:29] init logrus..…

《TCP/IP详解 卷一》第3章 链路层

目录 3.1 引言 3.2 以太网 3.3 全双工 省点 自动协商 流量控制 3.4 网桥和交换机 3.5 WiFi 3.6 PPP协议 3.6.1 PPP协议流程 3.7 环回 3.8 MTU和路径MTU 3.9 隧道基础 3.9.1 GRE 3.9.2 PPTP 3.9.3 L2TP 3.10 与链路层相关的攻击 3.11 总结 3.1 引言 城域网&…

2024年1月京东洗衣机行业数据分析:TOP10品牌销量销额排行榜

鲸参谋监测的京东平台1月份洗衣机市场销售数据已出炉&#xff01; 根据鲸参谋电商数据分析平台显示&#xff0c;今年1月份&#xff0c;京东平台上洗衣机的销量约160万件&#xff0c;环比上个月增长约42%&#xff0c;同比去年下滑7%&#xff1b;销售额约28亿元&#xff0c;环比…

Java零基础 - 三元运算符

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

Golin 弱口令/漏洞/扫描/等保/基线核查的快速安全检查小工具

下载地址&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/db6afba6de1f 主要功能 主机存活探测、漏洞扫描、子域名扫描、端口扫描、各类服务数据库爆破、poc扫描、xss扫描、webtitle探测、web指纹识别、web敏感信息泄露、web目录浏览、web文件下载、等保安全风险问题风险…

强大的文本绘图——PlantUML

PlantUML是一款开源工具&#xff0c;它允许用户通过简单的文本描述来创建UML图&#xff08;统一建模语言图&#xff09;。这种方法可以快速地绘制类图、用例图、序列图、状态图、活动图、组件图和部署图等UML图表。PlantUML使用一种领域特定语言&#xff08;DSL&#xff09;&am…

通过platform总线驱动框架编写LED灯的驱动,编写应用程序测试,发布到CSDN

效果图 设备树代码 myplatform{compatible "hqyj,myplatform";led1-gpio<&gpioe 10 0>;led2-gpio<&gpiof 10 0>;led3-gpio<&gpioe 8 0>; interrupt-parent <&gpiof>;interrupts<9 0>;reg<0X12345678 …

idea在工具栏中显示快速创建包和类的图标

一、效果图 点击需要创建包或者类的位置&#xff0c;在点击对用的图标就可以快速创建类或者包了。 二、设置 步骤一 View-->Appearance-->Toolbar 步骤二 File-->Settings-->Appearance & Behavior-->Menus and Toolbars-->Main Toolbar-->----…

Vue3 (unplugin-auto-import自动导入的使用)

安装 参考链接 npm i -D unplugin-auto-importvite.config.ts里面配置 import AutoImport from unplugin-auto-import/viteAutoImport({imports:[ vue,vue-router]})重新运行项目会生成一个auto-imports.d.ts的文件 /* eslint-disable */ /* prettier-ignore */ // ts-nochec…

在Ubuntu系统下搭建TDengine集群

目录 一、Ubuntu虚拟机创建 二、系统相关配置 1、设置系统hostname 2、网络配置及IP规划 3、配置FQDN&#xff08;etc/hosts&#xff09; 4、服务端口设置 三、TDengine server安装 1、服务安装 2、修改配置 3、启动taosd 4、服务卸载 四、客户端安装 1、client安…

密评技术要求实施详解:每一步都关键

密评简介 密评定义&#xff1a;全称商用密码应用安全性评估, 是对采用商用密码技术、产品和服务集成建设的网络和信息系统密码应用的合规性、正确性、有效性进行评估的活动。 评测依据&#xff1a;GB/T 39786-2021《信息安全技术 信息系统密码应用基本要求》。 密评对象&…

(done) 什么是特征值和特征向量?如何求特征值的特征向量 ?如何判断一个矩阵能否相似对角化?

什么是齐次方程&#xff1f; https://blog.csdn.net/shimly123456/article/details/136198159 行列式和是否有解的关系&#xff1f; https://blog.csdn.net/shimly123456/article/details/136198215 特征值和特征向量 参考视频&#xff1a;https://www.bilibili.com/video/BV…

【MATLAB】CEEMD_ MFE_SVM_LSTM 神经网络时序预测算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 CEEMD_MFE_SVM_LSTM神经网络时序预测算法是一种结合了多种先进技术的复杂预测方法&#xff0c;旨在提高时序预测的准确性和稳定性。下面是对该算法的详细介绍&#xff1a; CEEMD&#xff…

基于Java+SpringBoot+Vue前后端分离婚纱影楼管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行交流合作✌ 主要内容&#xff1a;SpringBoot、Vue、SSM、HLM…

【监督学习之决策树和随机森林】

曾梦想执剑走天涯&#xff0c;我是程序猿【AK】 目录 简述概要知识图谱决策树&#xff08;Decision Tree&#xff09;随机森林&#xff08;Random Forest&#xff09; 简述概要 了解决策树和随机森林 知识图谱 决策树和随机森林都是机器学习中常用的算法&#xff0c;它们在处…

.net core wbeapi 关于swagger的配置

当创建好一个webapi之后&#xff0c;在Program.cs中注释掉原本的AddSwaggerGen&#xff0c;修改为如下配置 Program.cs //builder.Services.AddSwaggerGen();builder.Services.AddSwaggerGen(options >{options.SwaggerDoc("v1", new OpenApiInfo{Version "…

利用docker一键部署LLaMa到自己的Linux服务器,有无GPU都行、可以指定GPU数量、支持界面对话和API调用,离线本地化部署包含模型权重合并

利用docker一键部署LLaMa到自己的Linux服务器,有无GPU都行、可以指定GPU数量、支持界面对话和API调用,离线本地化部署包含模型权重合并。两种方式实现支持界面对话和API调用,一是通过搭建text-generation-webui。二是通过llamma.cpp转换模型为转换为 GGUF 格式,使用 quanti…

介绍 CI / CD

目录 一、介绍 CI / CD 1、为什么要 CI / CD 方法简介 1、持续集成 2、持续交付 3、持续部署 2、GitLab CI / CD简介 3、GitLab CI / CD 的工作原理 4、基本CI / CD工作流程 5、首次设置 GitLab CI / CD 6、GitLab CI / CD功能集 一、介绍 CI / CD 在本文档中&#x…

.NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2

前言 很多同学都不愿给电脑设动态壁纸&#xff0c;其中有个重要原因就是嫌它占资源过多。今天大姚分享一个.NET开源、免费&#xff08;MIT license&#xff09;的一个小而快并且功能强大的 Windows 动态桌面软件&#xff0c;支持视频和网页动画播放&#xff1a;DreamScene2。 …