当前位置: 首页 > news >正文

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的BeanDefinitionif (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的BeanDefinitionif (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,然后我们根据此来构建singleBeanHashMapfor (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的BeanDefinitionif (beanDefinitionHashMap.containsKey(beanName)) {BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);if (beanDefinition.getScope().equals("single")) {//单例模式创建beanreturn singleBeanHashMap.get(beanName);}else {//原型模式创建beanreturn 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 nameif (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 nameif (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 {@Nullabledefault Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Nullabledefault Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}
}
  • postProcessBeforeInitialization:初始化前追加功能
  • postProcessAfterInitialization:初始化后追加功能
    在这里插入图片描述

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

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {@Nullabledefault Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {return null;}default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {return true;}@Nullabledefault PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {return null;}/** @deprecated */@Deprecated@Nullabledefault 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 {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println("初始化之前");if (beanName.equals("userService")) {System.out.println("为userService初始化之前追加的功能");}return bean;}@Overridepublic 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 {@Autowiredprivate  OrderService orderService;private String beanName;public String getBeanName() {return beanName;}@Overridepublic void afterPropertiesSet() {System.out.println("自定义初始化");}@Overridepublic 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 {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println("初始化之前");if (beanName.equals("userService")) {System.out.println("为userService初始化之前追加的功能");}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("初始化之后");if (beanName.equals("userService")) {return Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic 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!
在这里插入图片描述

相关文章:

Spring框架核心功能手写实现

文章目录概要Spring启动以及扫描流程实现基础环境搭建扫描逻辑实现bean创建的简单实现依赖注入实现BeanNameAware回调实现初始化机制模拟实现BeanPostProcessor模拟实现AOP模拟实现概要 手写Spring启动以及扫描流程手写getBean流程手写Bean生命周期流程手写依赖注入流程手写Be…...

k8s-镜像构建Flink集群Native session

一.Flink安装包下载 wget https://dlcdn.apache.org/flink/flink-1.14.6/flink-1.14.6-bin-scala_2.12.tgz 二.构建基础镜像推送私服 docker pull apache/flink:1.14.6-scala_2.12 docker tag apache/flink:1.14.6-scala_2.12 172.25.152.2:30002/dmp/flink:...

在 k8S 中搭建 SonarQube 7.4.9 版本(使用 PostgreSQL 数据库)

本文搭建的 SonarQube 版本是 7.4.9-community&#xff0c;由于在官方文档中声明 7.9 版本之后就不再支持使用 MySQL 数据库。所以此次搭建使用的数据库是 PostgreSQL 11.4 版本。 一、部署 PostgreSQL 服务 1. 创建命名空间 将 PostgreSQL 和 SonarQube 放在同一个命名空间…...

从getBean()分析BeanFactory和ApplicationContext

本文说了哪些问题: BeanFactory 是啥ApplicationContext 是啥什么时候去实例化一个 bean, BeanFactory 和 ApplicationContext 实例化 bean 都是在什么时候 一个 Bean 什么时候被初始化 任何一个 Bean, 都是在 getBean () 的时候被初始化的.BeanFactory 需要字节手动调用 getb…...

详解Redis的主从同步原理

前言 Redis为了保证服务高可用&#xff0c;其中一种实现就是主从模式&#xff0c;即一个Redis服务端作为主节点&#xff0c;若干个Redis服务端作为主节点的从节点&#xff0c;从而实现即使某个服务端不可用时&#xff0c;也不会影响Redis服务的正常使用。本篇文章将对主从模式…...

前端项目上线后,浏览器缓存未刷新问题

文章目录问题背景一、解决办法二、实现原理关于缓存强缓存协商缓存刷新页面对浏览器的影响总结问题背景 前端页面开发测试完&#xff0c;要进行上线&#xff0c;某些页面上传更新到服务器之后&#xff0c;浏览器并没有更新&#xff0c;渲染的还是老页面。这是因为浏览器读了缓存…...

Vulnhub系列:Raven 1

该篇为Vulnhub系列靶机渗透&#xff0c;本次靶机存在4个flag。下面开始我们今天的渗透之旅。Raven靶机有很多种思路&#xff0c;我将对其进行一一整理。首先进行信息收集&#xff0c;利用arp-scan和nmap&#xff0c;进行靶机的ip及端口扫描发现了22、80、111端口。下面访问80端…...

MybatisPlus------多数据源环境(十一)

MybatisPlus------多数据源环境&#xff08;十一&#xff09; 生产环境中常常会存在多个数据源。 比如读写分离、一主多从、混合模式等等。 首先再pom文件中需要引入依赖&#xff1a; 多数据源所需要使用到的依赖 <!-- 多数据源所需要使用到的依赖--><depend…...

Tomcat+IDEA+Servlet能显示页面但提交form表单出现404问题

问题&#xff1a; 当我们使用tomcat启动&#xff0c;然后输入对应的url路径时候&#xff0c;能出现该html的页面&#xff0c;但提交表单后&#xff0c;却出现了404的问题&#xff0c;这时候我就很疑惑了....然后开始慢慢分析。 思路&#xff1a; 首先我们得知道404状态码是什…...

【蓝桥杯集训16】多源汇求最短路——Floyd算法(2 / 2)

目录 Floyd求最短路模板 4074. 铁路与公路 - floyd 脑筋急转弯 Floyd求最短路模板 活动 - AcWing 题目&#xff1a; 给定一个 n 个点 m 条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c;边权可能为负数。 再给定 k 个询问&#xff0c;每个询问包含两个整数 x 和…...

simulink stateflow 状态机

系列文章目录 文章目录系列文章目录前言一、基操二、stateflow 数据三、chart动作四、chart的执行五、flow chart / junction六、状态机中的函数 Stateflow Functions七、chart层次结构八、案例——吸尘器机器人的驱动模式前言 一、基操 在tooltrip中选择DEBUG&#xff0c;通过…...

水库大坝安全监测的主要坝体类型介绍

水电站和水库大坝安全的分类中有重力坝、土石坝等不同的大坝形式。就在这里详细水库大坝安全监测按照建造形式&#xff0c;基本上可以分为三类&#xff1a;重力坝、土石坝和拱坝。 &#xff08;1&#xff09;重力坝 重力坝&#xff0c;顾名思义就是利用自身重力来维持坝体稳定…...

物理层概述(二)重点

目录前言编码与调制&#xff08;1&#xff09;基带信号与宽带信号编码与调制编码与调制&#xff08;2&#xff09;数字数据编码为数字信号非归零编码【NRZ】曼斯特编码差分曼彻斯特编码数字数据调制为模拟信号模拟数据如何编码为数字信号模拟数据调制为模拟信号物理层传输介质导…...

成都待慕电商:抖音极速版商品卡免佣扶持政策规则

新规&#xff0c;抖音极速版推出商品卡免佣扶持政策规则&#xff0c;本次抖音规则如何规定&#xff1f;具体往下看&#xff1a;一、政策简介1.1政策介绍为了更好地满足用户消费需求&#xff0c;丰富商家经营模式&#xff0c;降低商家经营成本&#xff0c;现平台针对商品卡场景推…...

青岛双软认定标准

软件企业的认定是有一定的标准的&#xff0c;需要满足以下这些条件&#xff1a;1、在我国境内依法设立了企业法人的企业&#xff1b;2、以计算机软件开发生产、系统集成、应用服务和其他相应技术服务为经营业务和主要经营收入&#xff1b;3、具有一种以上由本企业开发或由本企业…...

【00后卷王秘籍】python自动化测试—Python自动化框架及工具

1 、概述 手续的关于测试的方法论&#xff0c;都是建立在之前的文章里面提到的观点&#xff1a; 功能测试不建议做自动化 接口测试性价比最高 接口测试可以做自动化 后面所谈到的 测试自动化 也将围绕着 接口自动化 来介绍。 本系列选择的测试语言是 python 脚本语言。由于其…...

MySQL数据库基本操作

DDL 1、DDL解释 DDL(Data Definition Language)&#xff0c;数据定义语言&#xff0c;该语言部分包括以下内容&#xff1a; 对数据库的常用操作 对表结构的常用操作 修改表结构1、对数据库的常用操作 2、对表结构的常用操作-创建表 创建表格式 3、对表结构的常用操作-创建表…...

2023年最新的站内SEO指南:如何通过关键词优化提高网站排名

SEO或搜索引擎优化是指通过改善网站的内部和外部元素&#xff0c;以获得更好的自然搜索引擎排名和更多的网站流量。 链接建设和外链是SEO的重要组成部分&#xff0c;因为它们可以提高网站的权威性和可信度&#xff0c;从而使其在搜索引擎中排名更高。 在此指南中&#xff0c;…...

【Java】Java环开发环境安装

Java环开发环境安装 简介&#xff1a; 如果要从事Java编程&#xff0c;则需要安装JDK&#xff0c;如果仅仅是运行一款Java程序则JRE就满足要求。 Java的安装包分为两类 一类是JRE其就是一个独立的Java运行环境&#xff1b; 一类是JDK其是Java的开发环境&#xff0c;不过在JDK…...

[蓝桥杯] 枚举、模拟和排列问题

文章目录 一、连号区间数 1、1 题目描述 1、2 题解关键思路与解答 二、递增三元组 2、1 题目描述 2、2 题解关键思路与解答 三、错误票据 3、1 题目描述 3、2 题解关键思路与解答 四、回文日期 4、1 题目描述 4、2 题解关键思路与解答 五、归并排序 标题&#xff1a;蓝桥杯——…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

Java - Mysql数据类型对应

Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...