Spring框架核心功能手写实现

文章目录

  • 概要
  • Spring启动以及扫描流程实现
    • 基础环境搭建
    • 扫描逻辑实现
    • bean创建的简单实现
  • 依赖注入实现
  • BeanNameAware回调实现
  • 初始化机制模拟实现
  • BeanPostProcessor模拟实现
  • AOP模拟实现

概要

  • 手写Spring启动以及扫描流程
  • 手写getBean流程
  • 手写Bean生命周期流程
  • 手写依赖注入流程
  • 手写BeanPostProcessor机制
  • 手写Aop机制

Spring启动以及扫描流程实现

在这里插入图片描述

我们平时都是使用这两种方法获得spring容器,上面的是通过加载类路径上的配置文件来获得容器。下面的方式和上面的原理相同只不过是通过注解的形式去实现,我们传入的也是一个配置类的class文件,我们可以把这个文件类比成第一种方法中的xml文件,然后这个xml文件里的一个个标签都变成了注解。

基础环境搭建

首先搭建好基础环境:
在这里插入图片描述

我们的测试类:

public class MySpringTest {
    public static void main(String[] args) {
        MyApplicationContext applicationContext = new MyApplicationContext(AppConfig.class);

        Object bean = applicationContext.getBean("");

        System.out.println(bean);
    }
}

我们的容器类MyApplicationContext :

public class MyApplicationContext {
    private Class configClass;

    public MyApplicationContext(Class configClass) {
        this.configClass = configClass;
    }

    public Object getBean(String beanName){
        return null;
    }
}

我们原来在编写Spring的配置文件的时候会使用一个注解@ComponentScan,来定义一个扫描路径。所以我们这里也定义了一个。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ComponentScan {

    String value();
}
  • Retention指定该注解的生命周期,设置成RUNTIME便于反射获取

  • Target指定注解的使用位置,设置为TYPE表示能在类上使用

我们的AppConfig;

@ComponentScan("com.zyb.service")
public class AppConfig {
}

根据扫描包我们再创建一个业务层类UseService,这个UseService我们一般会使用@Component注解进行标记,这里我们也是如此:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String value();
}
@Component("userService")
public class UserService {

}

扫描逻辑实现

我们的思路就是;

  • 解析配置类
  • 拿到ComponentScan注解
  • 得到扫描路径
  • 进行扫描

首先我们可以通过如下代码拿到AppConfig中ComponentScan的内容:

public MyApplicationContext(Class configClass) {
        this.configClass = configClass;
        //解析配置类
        //ComponentScan注解 --》 扫描路径 --》 扫描

        ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScanAnnotation.value();
        System.out.println(path);

}

我们测试一下:
在这里插入图片描述
拿到路径之后我们就可以开始扫描了。而扫描的目的并不是包下的所有类,而是那些带有@Component注解的类,而Spring会将他们的对象当作Spring中的一个bean。

这里我们扫描的思路如下:

  • 通过类加载器,得到类路径上的class文件
  • 对文件进行筛选
    • 是否以class结尾(判断是否为class文件)
    • 对class文件名进行处理
      • 替换\.
      • 截取全限定名
  • 然后将类加载进jvm虚拟机
  • 判断运行时类是否有@Component注解,如果有则进行相关的创建bean对象操作

代码如下:

    public MyApplicationContext(Class configClass) {
        this.configClass = configClass;
        //解析配置类
        //ComponentScan注解 --》 扫描路径 --》 扫描
        ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScanAnnotation.value();
        String searchPath = path.replace(".", "/");
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(searchPath);
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            //首先筛选末尾是.class文件的
            File[] files = file.listFiles();
            for (File classFile: files) {
                String absolutePath = classFile.getAbsolutePath();
                if (absolutePath.endsWith(".class")) {
                    //拼接全限定名
                    String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    fullyQualifiedClassName = fullyQualifiedClassName.replace("\\", ".");
                    //使用app类加载器加载这个类
                    Class<?> aClass = null;
                    try {
                        aClass = classLoader.loadClass(fullyQualifiedClassName);
                        if (aClass.isAnnotationPresent(Component.class)) {
                            //然后加载bean

                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    
                }
            }
        }
    }

注意;

  • classLoader.loadClass接收的是类的全限定名

当我们确认当前类有@Component注解的时候并不是急着去给其创建bean,我们在使用spring的时候是可以决定该bean是否为单例的。我们在这里还是创建一个同名注解@Scope:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    String value() default "single";
}

默认是single单例的,如果是原型bean则传入prototype。

而我们实现单例bean是通过一个Map单例池,不考虑懒加载。

现在我们的大致思路就是在通过@Scope注解确定bean是单例模式还是原型模式之后,在进行相应的bean的创建,但是这里我们考虑到一个问题,我们在getBean的时候如果每次都去解析这个类获得@Scope是很麻烦的。所以我们在这里引入一个新的概念BeanDefinition。

BeanDefinition中包含了当前bean的所有定义,例如bean的类型、作用域、是否懒加载等等。注意bean的name不在BeanDefinition中:

public class BeanDefinition {
    private Class beanClass;
    private String scope;

    public Class getBeanClass() {
        return beanClass;
    }

    public void setBeanClass(Class beanClass) {
        this.beanClass = beanClass;
    }

    public String getScope() {
        return scope;
    }

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

在Spring中每扫描到一个bean都会对其进行解析然后生成各自的BeanDefinition实例,随后将这个BeanDefinition对象放在map中,key为bean的name,BeanDefinition对象作为value。以后当我们要获取一个bean时先会去map中查看,如果查找不到bean的BeanDefinition才会去解析类,以此来减少解析类的次数提高效率。

接下来我们完善一下代码,思路如下:

  • 判断运行时类是否有Component注解
  • 如果有,再获取该类的@Scope
    • 如果没有注解则为默认的单例模式
    • 如果有此注解则为原型模式
  • 然后再把对应的模式添加到BeanDefinition
  • 将此BeanDefinition和对应的bean name添加到beanDefinitionMap中

getBean方法思路:

  • 判断要获取的bean的BeanDefinition是否存在于beanDefinitionMap
    • 如果不存在,说明容器中不存在该bean则直接报错
    • 如果存在则在BeanDefinition中拿到该bean的scope,根据scope再去具体的创建bean

代码如下:

public class MyApplicationContext {
    private Class configClass;
    private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
    private HashMap<String,BeanDefinition> beanDefinitionHashMap  = new HashMap<>();

    public MyApplicationContext(Class configClass) {
        this.configClass = configClass;
        //解析配置类
        //ComponentScan注解 --》 扫描路径 --》 扫描
        ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScanAnnotation.value();
        String searchPath = path.replace(".", "/");
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(searchPath);
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            //首先筛选末尾是.class文件的
            File[] files = file.listFiles();
            for (File classFile: files) {
                String absolutePath = classFile.getAbsolutePath();
                if (absolutePath.endsWith(".class")) {
                    //拼接全限定名
                    System.out.println(classFile);
                    String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    fullyQualifiedClassName = fullyQualifiedClassName.replace("\\", ".");
                    //使用app类加载器加载这个类
                    Class<?> aClass = null;
                    try {
                        aClass = classLoader.loadClass(fullyQualifiedClassName);
                        if (aClass.isAnnotationPresent(Component.class)) {
                            BeanDefinition beanDefinition = new BeanDefinition();
                            if (aClass.isAnnotationPresent(Scope.class)) {
                                Scope scopeAnnotation = aClass.getAnnotation(Scope.class);
                                String scope = scopeAnnotation.value();
                                beanDefinition.setScope(scope);
                            }else{
                                beanDefinition.setScope("single");
                            }

                            Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            beanDefinitionHashMap.put(beanName,beanDefinition);

                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }

    public Object getBean(String beanName){
        //首先判断是否存在该bean的BeanDefinition
        if (beanDefinitionHashMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
            if (beanDefinition.getScope().equals("single")) {
                //单例模式创建bean
            }else {
                //原型模式创建bean
            }
        }else {
            throw new NullPointerException("不存在该bean");
        }
    }
}

最后我们可以把扫描这部分逻辑提取出来重新建立一个scan方法:

public class MyApplicationContext {
    private Class configClass;
    private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
    private HashMap<String,BeanDefinition> beanDefinitionHashMap  = new HashMap<>();

    public MyApplicationContext(Class configClass) {
        this.configClass = configClass;
        scan(configClass);
    }

    private void scan(Class configClass) {
        //解析配置类
        //ComponentScan注解 --》 扫描路径 --》 扫描
        ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScanAnnotation.value();
        String searchPath = path.replace(".", "/");
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(searchPath);
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            //首先筛选末尾是.class文件的
            File[] files = file.listFiles();
            for (File classFile: files) {
                String absolutePath = classFile.getAbsolutePath();
                if (absolutePath.endsWith(".class")) {
                    //拼接全限定名
                    System.out.println(classFile);
                    String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    fullyQualifiedClassName = fullyQualifiedClassName.replace("\\", ".");
                    //使用app类加载器加载这个类
                    Class<?> aClass = null;
                    try {
                        aClass = classLoader.loadClass(fullyQualifiedClassName);
                        if (aClass.isAnnotationPresent(Component.class)) {
                            BeanDefinition beanDefinition = new BeanDefinition();
                            if (aClass.isAnnotationPresent(Scope.class)) {
                                Scope scopeAnnotation = aClass.getAnnotation(Scope.class);
                                String scope = scopeAnnotation.value();
                                beanDefinition.setScope(scope);
                            }else{
                                beanDefinition.setScope("single");
                            }

                            Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            beanDefinitionHashMap.put(beanName,beanDefinition);

                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }

    public Object getBean(String beanName){
        //首先判断是否存在该bean的BeanDefinition
        if (beanDefinitionHashMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
            if (beanDefinition.getScope().equals("single")) {
                //单例模式创建bean
            }else {
                //原型模式创建bean
            }
        }else {
            throw new NullPointerException("不存在该bean");
        }
    }
}

bean创建的简单实现

spring容器启动之后,先进行扫描步骤,而这个扫描的主要作用就是得到BeanDefinition,这样我们就有了BeanDefinitionMap,然后我们就可以根据BeanDefinitionMap去创建单例池singleBeanHashMap为我们的getBean方法提供支持。

在getBean方法中:

  • 如果是单例模式的bean就直接在singleBeanHashMap中去拿
  • 如果是原型模式的bean就直接使用creatBean方法创建bean

我们这里的creatBean方法就是用来创建bean的方法,我们这里暂时只对此方法进行一个简单的实现。

整体代码如下:

public class MyApplicationContext {
    private Class configClass;
    private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
    private HashMap<String,BeanDefinition> beanDefinitionHashMap  = new HashMap<>();

    public MyApplicationContext(Class configClass) {
        this.configClass = configClass;
        scan(configClass);
        //scan之后就得到了beanDefinitionHashMap,然后我们根据此来构建singleBeanHashMap
        for (String beanName:beanDefinitionHashMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
            if (beanDefinition.getScope().equals("single")) {
                singleBeanHashMap.put(beanName,createBean(beanName));
            }
        }
    }

    public Object createBean(String name){
        BeanDefinition beanDefinition = beanDefinitionHashMap.get(name);
        Class beanClass = beanDefinition.getBeanClass();
        Object instance = null;
        try {
            instance = beanClass.getConstructor().newInstance();
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return instance;
    }

    private void scan(Class configClass) {
        //解析配置类
        //ComponentScan注解 --》 扫描路径 --》 扫描
        ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScanAnnotation.value();
        String searchPath = path.replace(".", "/");
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(searchPath);
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            //首先筛选末尾是.class文件的
            File[] files = file.listFiles();
            for (File classFile: files) {
                String absolutePath = classFile.getAbsolutePath();
                if (absolutePath.endsWith(".class")) {
                    //拼接全限定名
                    System.out.println(classFile);
                    String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    fullyQualifiedClassName = fullyQualifiedClassName.replace("\\", ".");
                    //使用app类加载器加载这个类
                    Class<?> aClass = null;
                    try {
                        aClass = classLoader.loadClass(fullyQualifiedClassName);
                        if (aClass.isAnnotationPresent(Component.class)) {
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setBeanClass(aClass);
                            if (aClass.isAnnotationPresent(Scope.class)) {
                                Scope scopeAnnotation = aClass.getAnnotation(Scope.class);
                                String scope = scopeAnnotation.value();
                                beanDefinition.setScope(scope);
                            }else{
                                beanDefinition.setScope("single");
                            }

                            Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            beanDefinitionHashMap.put(beanName,beanDefinition);

                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }

    public Object getBean(String beanName){
        //首先判断是否存在该bean的BeanDefinition
        if (beanDefinitionHashMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
            if (beanDefinition.getScope().equals("single")) {
                //单例模式创建bean
                return singleBeanHashMap.get(beanName);
            }else {
                //原型模式创建bean
                return createBean(beanName);
            }
        }else {
            throw new NullPointerException("不存在该bean");
        }
    }
}

我们测试一下效果:

在这里插入图片描述

  • 当UserService类上的Scope Vablue值为prototype
    在这里插入图片描述

  • 当UserService类上的Scope Vablue值为single或者去掉Scope注解
    在这里插入图片描述

依赖注入实现

前面我们对createBean方法进行了一个简单地实现,这里依赖注入的实现就是将createBean写完整。我们知道不管是单例bean还是原型bean他们的作用范围可能不同,但是他们在创建某一个对象的时候步骤是一样的,这也就是我们为什么要单独抽取一个方法出来专门用来创建bean。

这里我们只考虑使用@Autowired注解进行注入,createBean方法思路:

  • 首先通过beanName在BeanDefinition中拿到Class对象
  • 通过空参构造器创建instance对象
  • 遍历当前Class所有成员变量,检查是否有@Autowired注解
    • 如果没有则直接调用空参构造器返回bean
    • 如果有,那么我们此时只能根据当前属性的属性名以及类型进行依赖注入,这里我们选择简单一点的按照名称注入,思路就是将属性名传入到getBean方法中尝试去获取bean,在getBean方法我们会对当前依赖进行一个判断
      • 如果是单例模式,则将依赖直接从单例池中拿出来对依赖进行赋值
      • 如果不是单例模式,则递归调用createBean方法,创建依赖,再对依赖进行赋值

代码如下:

    public Object createBean(String name){
        BeanDefinition beanDefinition = beanDefinitionHashMap.get(name);
        Class beanClass = beanDefinition.getBeanClass();

        Object instance = null;
        try {
            instance = beanClass.getConstructor().newInstance();
            for (Field field:beanClass.getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    //拿到属性名
                    String fieldName = field.getName();
                    field.set(instance,getBean(fieldName));
                }
            }
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return instance;
    }

我们测试一下:

在这里插入图片描述

在这里插入图片描述
结果:
在这里插入图片描述

BeanNameAware回调实现

我们思考一个场景:
在这里插入图片描述
如果我们想获得当前的beanName,这里是不能使用Autowired的。Spring为我们提供了一种接口BeanNameAware,将当前的beanName返回给我们。

我们将这个功能实现一下:

首先写一个BeanNameAware接口:

public interface BeanNameAware {
    void setBeanNameAware(String beanName);
}

然后我们在createBean方法中调用接口方法,传入bean的name:

    public Object createBean(String name){
        ···
       
            //进行依赖注入
            ···

            //BeanNameAware返回bean name
            if (instance instanceof BeanNameAware) {
                BeanNameAware beanNameAware = (BeanNameAware) instance;
                beanNameAware.setBeanNameAware(name);
            }

		···
    }

初始化机制模拟实现

在bean的属性默认初始化设置完之后,我们程序员可以自定义进行业务方面的初始化,例如bean中的属性是否为空或者说是否符合业务方面的要求,或者你想对某一个属性赋值。那么这个功能如何完成呢?

在Spring中我们可以去实现InitializingBean接口来完成这一个功能,Spring在检测到当前类实现了InitializingBean接口之后,就会帮我们调用接口方法完成我们自定义的初始化。

InitializingBean接口定义如下:

public interface InitializingBean {
    void afterPropertiesSet();
}

然后我们在createBean方法中调用接口方法:

    public Object createBean(String name){
        ···
       
            //进行依赖注入
            ···

            //BeanNameAware返回bean name
            if (instance instanceof BeanNameAware) {
                BeanNameAware beanNameAware = (BeanNameAware) instance;
                beanNameAware.setBeanNameAware(name);
            }
            //自定义初始化
            if (instance instanceof InitializingBean) {
                InitializingBean initializingBean = (InitializingBean) instance;
                initializingBean.afterPropertiesSet();
            }

		···
    }

BeanPostProcessor模拟实现

BeanPostProcessor:bean的后置处理器

我们在前文中提到的初始化机制、Aware回调机制都是Spring提供给我们的拓展功能,我们只需要在需求类上实现相应接口,Spring即可帮我们完成需求。

那么Spring能否给我们提供帮助:在实例化之前或之后给我们提供额外的功能追加,或者说在初始化之前或初始化之后给我们提供额外的功能追加。

为了实现这一需求Spring为我们提供了BeanPostProcessor。

我们来看一下Spring的源码:

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
  • postProcessBeforeInitialization:初始化前追加功能
  • postProcessAfterInitialization:初始化后追加功能
    在这里插入图片描述

BeanPostProcessor有很多子接口,其中InstantiationAwareBeanPostProcessor的源码如下:

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        return null;
    }

    default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return true;
    }

    @Nullable
    default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        return null;
    }

    /** @deprecated */
    @Deprecated
    @Nullable
    default PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        return pvs;
    }
}
  • postProcessBeforeInstantiation:实例化之前追加功能
  • postProcessAfterInstantiation:实例化之后追加功能
    在这里插入图片描述

我们在Spring框架中要想使用这一个功能,一般是新创建一个类然后去实现相应的接口,而不会去在业务层的类中去实现。因为初始化、实例化都是相对于每一个bean来说的,我们理应将他们单独抽离出来,而且这样不会污染业务代码。

接下来我们开始BeanPostProcessor的代码实现:

实现思路如下:

  • 扫描类的时候(也就是scan方法中) 看是否有类实现了BeanPostProcessor接口
  • 如果实现了该接口,则创建该类的实例
  • 将实例放入beanPostProcessorList中
  • 在createBean方法的相应的位置将列表中的实例一个个取出来,然后调用他们的方法

代码实现:

public interface BeanPostProcessor {

    Object postProcessBeforeInitialization(Object bean, String beanName);


    Object postProcessAfterInitialization(Object bean, String beanName);
}

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("初始化之前");
        if (beanName.equals("userService")) {
            System.out.println("为userService初始化之前追加的功能");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("初始化之后");
        return bean;
    }
}

在MyApplicationContext中新建一个成员属性beanPostProcessorList:
在这里插入图片描述

scan方法:
在这里插入图片描述

注意:

  • 这里的isAssignableFrom方法和instanceof要区别开
    • instanceof是判断某一个对象与父类或者接口的关系
    • isAssignableFrom是判断某一个类与父类或者接口的关系
  • 同时isAssignableFrom方法与instanceof的用法也不一样,例如
    • 判断a对象是否实现b接口:a instanceof b
    • 判断a类是否实现b接口:b.isAssignableFrom(a)
  • 这里的代码逻辑与Spring源码并不相同,在Spring源码中这里创建BeanPostProcessor的实例对象时使用的getBean方法,这样走的是Spring内部的逻辑可以处理当BeanPostProcessor的实现类里面存在使用@Autowired进行依赖注入的情况。但是我们这里并没有考虑这样的情况,所以我们直接得到类的构造器去创建BeanPostProcessor的实例对象

createBean方法:
在这里插入图片描述

我们测试一下:

public class MySpringTest {
    public static void main(String[] args) {
        MyApplicationContext applicationContext = new MyApplicationContext(AppConfig.class);

//        UserService userService = (UserService) applicationContext.getBean("userService");
//        System.out.println(userService.getOrderService());
//        System.out.println(userService.getBeanName());

    }
}

结果:
在这里插入图片描述
我们的扫描范围时service包下,而我们的service包下有三个类,与结果相吻合。

AOP模拟实现

在spring框架中,使用代理模式比较典型的场景就是AOP的实现了,代理逻辑核心要点如下:

  • 默认使用 JDK 动态代理,这样可以代理所有的接口类型;
  • 如果目标对象没有实现任何接口,则默认采用CGLIB代理;
  • 可强制使用CGLIB,指定proxy-target-class = “true” 或者 基于注解@EnableAspectJAutoProxy(proxyTargetClass = true)

AOP的工作流程可以直接看我的另一篇文章:
[Spring Framework]AOP工作流程

除了动态代理技术之外,AOP在Spring中的实现还需要借助BeanPostProcessor。我们知道BeanPostProcessor是bean的后置处理器,其可以在bean初始化的前后对bean造成额外的影响。
在这里插入图片描述

这里我们可以借助BeanPostProcessor接口中的postProcessAfterInitialization方法,返回需要增强类的代理对象。

接下来我们进行代码实现:

这里我们实现JDK动态代理的情况,所以先要为委托类编写接口方法,方便根据接口实现代理对象:

public interface ProxyInterface {
    void show();
}
@Component("userService")
@Scope("single")
public class UserService implements BeanNameAware, InitializingBean,ProxyInterface {

    @Autowired
    private  OrderService orderService;

    private String beanName;

    public String getBeanName() {
        return beanName;
    }
    @Override
    public void afterPropertiesSet() {
        System.out.println("自定义初始化");
    }


    @Override
    public void setBeanNameAware(String beanName) {
        this.beanName = beanName;
    }

    public OrderService getOrderService() {
        return orderService;
    }

    public void show(){
        System.out.println("这里是UserService的实例");
    }
    
}

然后我们重写一下postProcessAfterInitialization方法:

@Component("myBeanPostProcessor")
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("初始化之前");
        if (beanName.equals("userService")) {
            System.out.println("为userService初始化之前追加的功能");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("初始化之后");
        if (beanName.equals("userService")) {
            return Proxy.newProxyInstance(
                    MyBeanPostProcessor.class.getClassLoader(),
                    bean.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if (method.getName().equals("show")) {
                                System.out.println("这里是对show方法的前置增强");
                            }
                            return method.invoke(bean, args);
                        }
                    }
            );
        }
        return bean;
    }
}

我们这里省略了是否进行AOP的判断以及找切点的逻辑

测试:

public class MySpringTest {
    public static void main(String[] args) {
        MyApplicationContext applicationContext = new MyApplicationContext(AppConfig.class);
        ProxyInterface proxyInterface = (ProxyInterface) applicationContext.getBean("userService");
        proxyInterface.show();
    }
}

结果:
在这里插入图片描述

我们在Spring中使用AOP时会在配置类上使用一个注解@EnableAspectJAutoProxy。这个注解会向Spring的容器中注册一个bean:
在这里插入图片描述

而这个bean你往上一直寻找会发现,他其实就是一个BeanPostProcessor!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

乐鑫 ESP-IoT-Bridge 方案支持设备灵活入网

观看视频了解 ESP-IoT-Bridge 联网方案乐鑫科技推出 ESP-IoT-Bridge 联网方案&#xff0c;能够为物联网应用场景下的 Wi-Fi、蓝牙、Thread、以太网、MCU 等设备&#xff0c;提供便捷的网络服务。 ESP-IoT-Bridge 以乐鑫 SoC 为载体&#xff0c;通过实现各类网络接口&#xff08…

Java文件IO

目录 一. 文件路径 1.1 绝对路径 1.2 相对路径 二 . 文件操作 2.1 File类 2.2 字符流 Reader/Writer 2.3 字节流 InputStream/OutputStream 三. 实现一个文件的搜索功能 一. 文件路径 1.1 绝对路径 从盘符开始&#xff0c;一层一层往下找&#xff0c;得到的路径是绝对路…

nvm管理node版本粗及

步骤一&#xff1a;清理本地node cmd ——> where node ——> 删除对应文件夹下所有node.exe的父文件夹控制面板 ——> 卸载node步骤二&#xff1a;安装nvm Tags coreybutler/nvm-windows GitHub 下载解压后运行安装exe文件&#xff0c;安装完成后重新cmd打开命令…

Hive3.1.3安装及部署

目录 1 下载地址 2 安装部署 2.1 安装Hive 2.2 启动并使用Hive 2.3 MySQL安装 2.3.1 安装MySQL 2.3.2 配置MySQL 2.3.3 卸载MySQL说明 2.4 配置Hive元数据存储到MySQL 2.4.1 配置元数据到MySQL 2.4.2 验证元数据是否配置成功 2.4.3 查看MySQL中的元数据 2.5 Hive服…

中金支付经历了4个月完成主要出资人前置审批

2023年4月6日&#xff0c;中国人民银行公示了关于中金支付有限公司的《中国人民银行准予行政许可决定书》&#xff08;银许准予决字〔2023〕第41号&#xff09;&#xff0c;同意中金支付有限公司主要出资人由中金金融认证中心有限公司变更为广州广电运通金融电子股份有限公司&a…

Nacos安全性探究

Nacos怎么做安全校验的&#xff1f; 以下使用nacos2.x 如上图所示&#xff0c; 可以直接访问Nacos的接口来获取用户列表。这说明Nacos的接口被爆露&#xff0c;任何情况下都可以访问&#xff0c;因此安全性得不到保障。 Nacos 使用 spring security 作为安全框架。spring sec…

【Mybatis】1—前言日志框架

⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star~&#x1f60a; 如果文章对你有所帮助&#xff0c;可以点赞&#x1f44d;…

RBF-UKF径向基神经网络结合无迹卡尔曼滤波估计锂离子电池SOC(附MATLAB代码)

目录 RBFNN训练结果 UKF估计SOC 文章的结尾红色部分有彩蛋 RBFNN训练结果 这篇文章主要介绍如何使用RBF神经网络训练出的参数并结合UKF算法完成锂离子电池SOC的估计&#xff0c;有关RBF参数训练过程的代码分析放在2天后的下一篇文章&#xff0c;这里只给出训练完成后的结果…

关于async/await、promise和setTimeout执行顺序

关于async/await、promise和setTimeout执行顺序 async function async1() {console.log(async1 start);await async2();console.log(asnyc1 end); } async function async2() {console.log(async2); } console.log(script start); setTimeout(() > {console.log(setTimeOut…

springboot(01)项目搭建与启动

01&#xff0c;项目搭建与启动 一&#xff0c;项目搭建 有多种方式可以搭建Spring Boot项目&#xff0c;包括&#xff1a; 使用Spring Boot CLI命令行工具使用Spring Initializr网站或IDE插件生成项目模板使用Maven或Gradle手动配置项目 每种方式都有其优缺点&#xff0c;具…

Android IPC Binder机制学习(一)

一、多进程系统设计及意义Android系统分为5层&#xff0c;不过Android一般岗位只关注上三层就够用了即&#xff1a;应用层、framework层、native层。Android中的应用层和系统服务层不在同一个进程&#xff0c;系统服务在单独的进程中。Android中不同的应用属于不同的进程中Andr…

ChatGPT遭禁用、抵制后又停止Plus付费发生了?

ChatGPT相关信息 2023年2月27日消息&#xff0c;Snapchat 正在推出一个基于 OpenAI 的 ChatGPT 最新版本的聊天机器人。 这款名为“My AI”的机器人将被固定在应用界面的聊天选项卡上&#xff0c;虽然最初仅适用于每月3.99美元的SnapchatPlus付费订阅用户&#xff0c;但最终目…

图像分类综述

一、图像分类介绍 什么是图像分类&#xff0c;核心是从给定的分类集合中给图像分配一个标签的任务。实际上&#xff0c;这意味着我们的任务是分析一个输入图像并返回一个将图像分类的标签。标签来自预定义的可能类别集。 示例&#xff1a;我们假定一个可能的类别集categories …

Vue3+vite2 博客前端开发

Vue3vite2 博客前端开发 文章目录Vue3vite2 博客前端开发前言页面展示代码设计卡片设计背景&#xff08;Particles.js粒子效果&#xff09;右侧个人信息与公告内容页友链总结前言 大家是否也想拥有一个属于自己的博客&#xff1f;但是如何去开发博客&#xff0c;怎样去开发一个…

毫升 | 主成分分析(PCA)

这种方法是由Karl Pearson 介绍的。它的工作条件是&#xff0c;当高维空间中的数据映射到低维空间中的数据时&#xff0c;低维空间中数据的方差应最大。 主成分分析 (PCA) 是一种用于降低大型数据集维数的统计技术。它是机器学习、数据科学和其他处理大型数据集的领域中常用的…

如何通过C++ 将数据写入 Excel 工作表

直观的界面、出色的计算功能和图表工具&#xff0c;使Excel成为了最流行的个人计算机数据处理软件。在独立的数据包含的信息量太少&#xff0c;而过多的数据又难以理清头绪时&#xff0c;制作成表格是数据管理的最有效手段之一。这样不仅可以方便整理数据&#xff0c;还可以方便…

aspnet030高校学生团体管理系统sqlserver

net030高校学生团体管理系统 . 1.用户基本信息管理模块&#xff1a;录入、修改、删除、查询、统计、打印等功能 2.学生成绩管理模块&#xff1a;录入、修改、删除、查询、统计、打印等功能 3.学生团体信息管理模块&#xff1a;录入、修改、删除、查询、统计、打印等功能 4.教…

Excel技能之查找筛选排序,同事竖起大拇指

每天面对大量的数据&#xff0c;眼睛都看花了。头疼、脱发、颈椎病、胸闷、腰间盘突出&#xff0c;一系列并发症严重影响打工人的心情。同事看在眼里&#xff0c;痛在心里。 救救打工人吧&#xff01;打工人的福音来了&#xff0c;自从学会了查找筛选排序&#xff0c;手脚利索…

动态规划(一) part1

T1:一个数组 中的最长 升序 子序列 的长度 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组…

C 非线性结构——树 万字详解(通俗易懂)

目录 一、树的介绍 1.定义 : 2.相关概念 : 3.简单分类 : 4.相关应用 : 二、树的存储 1.二叉树的存储 : 1 二叉树连续存储 2 二叉树链式存储&#xff08;常用&#xff09; 2.普通树和森林的存储 : 1 普通树的存储 2 森林的存储 三、树的遍历 1.二叉树先序遍历 : 2.二叉…