当前位置: 首页 > 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;蓝桥杯——…...

N_m3u8DL-RE:现代流媒体下载的终极解决方案

N_m3u8DL-RE&#xff1a;现代流媒体下载的终极解决方案 【免费下载链接】N_m3u8DL-RE 跨平台、现代且功能强大的流媒体下载器&#xff0c;支持MPD/M3U8/ISM格式。支持英语、简体中文和繁体中文。 项目地址: https://gitcode.com/GitHub_Trending/nm3/N_m3u8DL-RE 在当今…...

消费级GPU福音:OpenClaw+百川2-13B量化版显存占用实测

消费级GPU福音&#xff1a;OpenClaw百川2-13B量化版显存占用实测 1. 为什么关注显存占用&#xff1f; 去年折腾大模型本地部署时&#xff0c;最头疼的就是显存问题。我的RTX3060显卡只有12GB显存&#xff0c;跑Llama2-13B原版模型时&#xff0c;加载阶段就直接爆显存。直到发…...

童年回忆杀!仿《燃烧的蔬菜》游戏完整源码 免费!!!

谁的童年没玩过《燃烧的蔬菜》&#xff01;这款经典的塔防休闲游戏&#xff0c;用蔬菜当炮弹击退怪物&#xff0c;治愈又解压。今天用PythonPygame复刻核心玩法&#xff0c;包含蔬菜发射、怪物生成、碰撞检测、计分系统&#xff0c;完整源码直接运行&#xff0c;带你重温童年&a…...

Optimizing ImageNet Classification with Advanced Deep Convolutional Neural Networks

1. 深度卷积神经网络在ImageNet分类中的核心挑战 ImageNet分类任务一直是计算机视觉领域的标杆性挑战&#xff0c;这个包含1400万张手工标注图像的数据集&#xff0c;要求模型能够准确识别22000个不同类别的物体。当我第一次尝试用传统卷积神经网络处理这个任务时&#xff0c;遇…...

保姆级教程:造相Z-Image文生图模型v2快速上手,一键生成768高清图

保姆级教程&#xff1a;造相Z-Image文生图模型v2快速上手&#xff0c;一键生成768高清图 1. 为什么选择造相Z-Image模型&#xff1f; 造相Z-Image是阿里通义万相团队开源的高性能文生图扩散模型&#xff0c;拥有20亿级参数规模。相比市面上常见的512512分辨率模型&#xff0c…...

想在职场走得远,必须戒掉弱者心态

想在职场走得远&#xff0c;必须戒掉弱者心态前言抱怨者心态&#xff1a;错永远在外部依赖者心态&#xff1a;永远在被动等待逃避者心态&#xff1a;用无视应对问题如何建立强者心态许多人在职场受挫&#xff0c;习惯性地指责环境、指责他人&#xff0c;唯独不愿审视自身。他们…...

突破模态壁垒:Audio Flamingo 3如何重塑音频AI开发范式

突破模态壁垒&#xff1a;Audio Flamingo 3如何重塑音频AI开发范式 【免费下载链接】audio-flamingo-3 项目地址: https://ai.gitcode.com/hf_mirrors/nvidia/audio-flamingo-3 问题象限&#xff1a;音频智能的三重技术困境 当前音频AI领域正面临着制约行业发展的三大…...

工业相机图像采集处理:从 RAW 数据到 AI 可读图像,附basler相机 C#实战代码

工业相机图像采集处理&#xff1a;从 RAW 数据到 AI 可读图像&#xff0c;附basler相机 C#实战代码前言&#xff1a; 做工业视觉的兄弟们都遇到过这种场景&#xff1a; 用 Basler Pylon SDK 自带的 Converter 转图&#xff0c;代码是简洁了&#xff0c;但一上高帧率&#xff08…...

OpenClaw+nanobot自动化处理客服常见问题

OpenClawnanobot自动化处理客服常见问题 1. 为什么选择OpenClawnanobot做客服自动化 去年夏天&#xff0c;我的个人项目突然迎来一波用户增长&#xff0c;随之而来的是每天上百条的客服咨询。当我连续三天凌晨两点还在回复"如何重置密码"这类问题时&#xff0c;终于…...

开源项目依赖管理:从冲突解决到高效协作的实践指南

开源项目依赖管理&#xff1a;从冲突解决到高效协作的实践指南 【免费下载链接】IPED IPED Digital Forensic Tool. It is an open source software that can be used to process and analyze digital evidence, often seized at crime scenes by law enforcement or in a corp…...