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

手写Spring与基本原理--简易版

文章目录

  • 手写Spring与基本原理解析
    • 简介
    • 写一个简单的Bean加载容器
      • 定义一个抽象所有类的BeanDefinition
      • 定义一个工厂存储所有的类
      • 测试
    • 实现Bean的注册定义和获取
    • 基于Cglib实现含构造函数的类实例化策略
    • Bean对象注入属性和依赖Bean的功能
    • Spring.xml解析和注册Bean对象
    • 实现应用上下文
    • 向虚拟机注册钩子,实现Bean对象的初始化和销毁方法
    • 感知容器对象
    • Bean对象作用域以及FactoryBean的实现和使用
    • 基于观察者实现,容器事件和事件监听器
    • AOP切面
    • AOP动态代理,融入到Bean的生命周期
    • 通过注解配置和包自动扫描的方式完成Bean对象的注册
    • 通过注解给属性注入配置和Bean对象
    • 循环依赖
    • 事务功能设计
    • 实际使用--JDBCTemplate
    • 整合ORM框架

手写Spring与基本原理解析

详细请跳转 https://bugstack.cn/md/spring/develop-spring
这里是学习上述小博哥的《手写Spring》做的简易记录,夹杂着很多个人私活,建议直接看链接

简介

从SpringBean的加载流程博文中可以知道Spring的简易加载流程
Spring bean的生命周期包括实例化、属性赋值、初始化前回调、自定义初始化、初始化后回调、初始化完成、销毁前回调、自定义销毁这些阶段和回调方法。
接下来讲根据上述流程逐步深入

写一个简单的Bean加载容器

定义一个抽象所有类的BeanDefinition

首先进行简单的容器设计,我们需要将一个个的类进行统一的定义,这个定义名称为BeanDefinition,他包含众多的属性,包括是否是单例,类的名称等等。为了减少代码的复杂性,这里不再展开
简单定义这个类

public class BeanDefinition {private Object object;public BeanDefinition(Object object) {this.object = object;}public Object getBean() {return object;}
}

在这个类中,BeanDefinition单纯代表这个类的统一接口,真正的类是object,后面我们可以通过getBean的方式直接获取这个object。

定义一个工厂存储所有的类

实体类有了统一的接口后,就可以建立一个工厂,这个工厂叫做“Bean”工厂,建立的所有的类都以Map的方式放到这个工程中,后续可以直接调用
Map中主键就是我们定义的名称,也就是平常xml中的bean的id

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class BeanFactory {private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();public Object getBean(String name) {return beanDefinitionMap.get(name).getBean();}public void registerBeanDefinition(String name, BeanDefinition beanDefinition){beanDefinitionMap.put(name, beanDefinition);}
}

这样,一个简易的容器实际上已经建好了,我们只需要往这个bean里面塞class就可以

测试

定义一个实体类UserService

public class UserService {public void query(){System.out.println("用户名称查询");}
}

测试

public class ApiTest {public static void main(String[] args) {//定义好bean工厂BeanFactory beanFactory = new BeanFactory();//注册bean对象BeanDefinition beanDefinition = new BeanDefinition(new UserService());beanFactory.registerBeanDefinition("userService", beanDefinition);//获取类对象UserService userService = (UserService) beanFactory.getBean("userService");userService.query();}
}

这样一个简单的Spring Bean容器实际就建立好了

实现Bean的注册定义和获取

定义 BeanFactory 这样一个 Bean 工厂,提供 Bean 的获取方法 getBean(String name),之后这个 Bean 工厂接口由抽象类 AbstractBeanFactory 实现。
BeanFactory 的定义由 AbstractBeanFactory 抽象类实现接口的 getBean 方法
而 AbstractBeanFactory 又继承了实现了 SingletonBeanRegistry 的DefaultSingletonBeanRegistry 类。这样 AbstractBeanFactory 抽象类就具备了单例 Bean 的注册功能。
AbstractBeanFactory 中又定义了两个抽象方法:getBeanDefinition(String beanName)、createBean(String beanName, BeanDefinition beanDefinition) ,而这两个抽象方法分别由 DefaultListableBeanFactory、AbstractAutowireCapableBeanFactory 实现。
最终 DefaultListableBeanFactory 还会继承抽象类 AbstractAutowireCapableBeanFactory 也就可以调用抽象类中的 createBean 方法了。

基于Cglib实现含构造函数的类实例化策略

通过策略模式拆分单例构造与有参数构造,判断条件为是否有参数,构造时使用Cglib

1、BeanFactory 中我们重载了一个含有入参信息 args 的 getBean 方法,这样就可以方便的传递入参给构造函数实例化了。
2、在实例化接口 instantiate 方法中添加必要的入参信息,包括:beanDefinition、 beanName、ctor、args
其中 Constructor 你可能会有一点陌生,它是 java.lang.reflect 包下的 Constructor 类,里面包含了一些必要的类信息,有这个参数的 目的就是为了拿到符合入参信息相对应的构造函数。
而 args 就是一个具体的入参信息了,最终实例化时候会用到。
3、在 AbstractAutowireCapableBeanFactory 抽象类中定义了一个创建对象的实例化策略属性类 InstantiationStrategy instantiationStrategy,这里我们选择了 Cglib 的实现类。
4、抽取 createBeanInstance 方法,在这个方法中需要注意 Constructor 代表了你有多少个构造函数,通过 beanClass.getDeclaredConstructors() 方式可以获取到你所有的构造函数,是一个集合。
5、循环比对出构造函数集合与入参信息 args 的匹配情况

Bean对象注入属性和依赖Bean的功能

属性填充是在 Bean 使用 newInstance 或者 Cglib 创建后,开始补全属性信息,那么就可以在类 AbstractAutowireCapableBeanFactory 的 createBean 方法中添加补全属性方法

在BeanDefinition中增加一个 PropertyValues,PropertyValues中引用一个List,其中所有的属性会放到这个list中,PropertyValue是一种类似Map的name-valu结构,name是属性名称,value是具体引用,这个value如果是引用的其他类,那么就要用到BeanReference。

    public PropertyValue(String name, Object value) {this.name = name;this.value = value;}

具体填充是在AbstractAutowireCapableBeanFactory中的 applyPropertyValues 方法进行具体的填充操作,如果遇到的是 BeanReference,那么就需要递归获取 Bean 实例,调用 getBean 方法。

Spring.xml解析和注册Bean对象

把 Bean 的定义、注册和初始化交给 Spring.xml 配置化处理,那么就需要实现两大块内容,分别是:资源加载器、xml资源处理类,实现过程主要以对接口 Resource、ResourceLoader 的实现,而另外 BeanDefinitionReader 接口则是对资源的具体使用,将配置信息注册到 Spring 容器中去。

Resource 的资源加载器的实现中包括了,ClassPath、系统文件、云配置文件,这三部分与 Spring 源码中的设计和实现保持一致,最终在 DefaultResourceLoader 中做具体的调用。
接口:BeanDefinitionReader、抽象类:AbstractBeanDefinitionReader、实现类:XmlBeanDefinitionReader,这三部分内容主要是合理清晰的处理了资源读取后的注册 Bean 容器操作。
关键代码:
解析标签填入beanfinition

// 解析标签:property
Element property = (Element) bean.getChildNodes().item(j);
String attrName = property.getAttribute("name");
String attrValue = property.getAttribute("value");
String attrRef = property.getAttribute("ref");
// 获取属性值:引入对象、值对象
Object value = StrUtil.isNotEmpty(attrRef) ? new BeanReference(attrRef) : attrValue;
// 创建属性信息
PropertyValue propertyValue = new PropertyValue(attrName, value);
beanDefinition.getPropertyValues().addPropertyValue(propertyValue);

注册 BeanDefinition

getRegistry().registerBeanDefinition(beanName, beanDefinition);

实现应用上下文

引入应用上下文,进行资源扫描与加载,为Bean对象实例化过程添加扩展机制,为bean对象执行修改、记录和替换等动作。
过程:
加载–》注册–》修改–》实例化–》扩展
满足于对 Bean 对象扩展的两个接口,其实也是 Spring 框架中非常具有重量级的两个接口:BeanFactoryPostProcessor与BeanPostProcessor
BeanFactoryPostProcessor,是由 Spring 框架组建提供的容器扩展机制,允许在 Bean 对象注册后但未实例化之前,对 Bean 的定义信息 BeanDefinition 执行修改操作。定义如下

public interface BeanFactoryPostProcessor {/*** 在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前,提供修改 BeanDefinition 属性的机制** @param beanFactory* @throws BeansException*/void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;}

refresh() 定义实现过程,包括:
1、创建 BeanFactory,并加载 BeanDefinition
2、获取 BeanFactory
3、在 Bean 实例化之前,执行 BeanFactoryPostProcessor (Invoke factory processors registered as beans in the context.)
4、BeanPostProcessor 需要提前于其他 Bean 对象实例化之前执行注册操作
5、提前实例化单例Bean对象

BeanPostProcessor,也是 Spring 提供的扩展机制,不过 BeanPostProcessor 是在 Bean 对象实例化之后修改 Bean 对象,也可以替换 Bean 对象。这部分与后面要实现的 AOP 有着密切的关系。
定义如下:

public interface BeanPostProcessor {/*** 在 Bean 对象执行初始化方法之前,执行此方法** @param bean* @param beanName* @return* @throws BeansException*/Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;/*** 在 Bean 对象执行初始化方法之后,执行此方法** @param bean* @param beanName* @return* @throws BeansException*/Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}

在Bean创建时完成前置和后置处理:AbstractAutowireCapableBeanFactory
initializeBean方法:
// 1. 执行 BeanPostProcessor Before 处理
Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
// 待完成内容:invokeInitMethods(beanName, wrappedBean, beanDefinition);
invokeInitMethods(beanName, wrappedBean, beanDefinition);
// 2. 执行 BeanPostProcessor After 处理
wrappedBean = applyBeanPostProcessorsAfterInitialization(bean, beanName);

向虚拟机注册钩子,实现Bean对象的初始化和销毁方法

spring.xml 配置中添加 init-method、destroy-method 两个注解
在配置文件加载的过程中,把注解配置一并定义到 BeanDefinition 的属性当中。这样在 initializeBean 初始化操作的工程中,就可以通过反射的方式来调用配置在 Bean 定义属性当中的方法信息了
1、定义初始化和销毁方法的接口:InitializingBean和DisposableBean
在一些需要结合 Spring 实现的组件中,经常会使用这两个方法来做一些参数的初始化和销毁操作。比如接口暴漏、数据库数据读取、配置文件加载等等。
2、BeanDefinition 新增加了两个属性:initMethodName、destroyMethodName,这两个属性是为了在 spring.xml 配置的 Bean 对象中,可以配置 init-method=“initDataMethod” destroy-method=“destroyDataMethod” 操作

最终实现接口的效果是一样的。只不过1是接口方法的直接调用,2是在配置文件中读取到方法反射调用

感知容器对象

感知容器定义一个标签Aware,继承了这个标签的接口会在初始化和BeanPostProcessor Before之前进行处理
核心代码:

       // invokeAwareMethodsif (bean instanceof Aware) {if (bean instanceof BeanFactoryAware) {((BeanFactoryAware) bean).setBeanFactory(this);}if (bean instanceof BeanClassLoaderAware){((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());}if (bean instanceof BeanNameAware) {((BeanNameAware) bean).setBeanName(beanName);}}// 1. 执行 BeanPostProcessor Before 处理Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);// 执行 Bean 对象的初始化方法try {invokeInitMethods(beanName, wrappedBean, beanDefinition);} catch (Exception e) {throw new BeansException("Invocation of init method of bean[" + beanName + "] failed", e);}// 2. 执行 BeanPostProcessor After 处理wrappedBean = applyBeanPostProcessorsAfterInitialization(bean, beanName);return wrappedBean;

有几个常用的Aware接口
BeanNameAware, BeanClassLoaderAware, ApplicationContextAware, BeanFactoryAware
具体功能望文生义即可

也可以自定义Aware接口:
1、接口继承Aware
2、bean实现接口
3、实现接口方法
4、bean中具体执行

Bean对象作用域以及FactoryBean的实现和使用

注意BeanFactory与FactoryBean的区别:
BeanFactory是bean的工厂,FactoryBean是一个工厂Bean,他也可以通过BeanFactory获得,算是一个特殊的bean,用于定义创建和配置复杂对象。通过实现 FactoryBean 接口,你可以自定义对象的创建逻辑,并将其纳入 Spring 容器的管理。

BeanDefinition中
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
这两个属性决定着单例还是多例

基于观察者实现,容器事件和事件监听器

以围绕实现 event 事件定义、发布、监听功能实现和把事件的相关内容使用 AbstractApplicationContext#refresh 进行注册和处理操作。

实际上是定义三个角色
ApplicationEventPublisher,事件发布者,并在实现类中提供事件监听功能,是整个一个事件的发布接口,所有的事件都需要从这个接口发布出去。
ApplicationEventMulticaster 接口是注册监听器和发布事件的广播器,提供添加、移除和发布事件方法,在事件广播器中定义了添加监听和删除监听的方法以及一个广播事件的方法 multicastEvent 最终推送时间消息也会经过这个接口方法来处理谁该接收事件。
ApplicationEvent:实现此接口定义具体事件

在抽象应用上下文 AbstractApplicationContext#refresh 中,主要新增了 初始化事件发布者、注册事件监听器、发布容器刷新完成事件,三个方法用于处理事件操作。
初始化事件发布者(initApplicationEventMulticaster),主要用于实例化一个 SimpleApplicationEventMulticaster,这是一个事件广播器。
注册事件监听器(registerListeners),通过 getBeansOfType 方法获取到所有从 spring.xml 中加载到的事件配置 Bean 对象。
发布容器刷新完成事件(finishRefresh),发布了第一个服务器启动完成后的事件,这个事件通过 publishEvent 发布出去,其实也就是调用了 applicationEventMulticaster.multicastEvent(event); 方法。
最后是一个 close 方法中,新增加了发布一个容器关闭事件。publishEvent(new ContextClosedEvent(this));

AOP切面

核心代码

        // 目标对象(可以替换成任何的目标对象)Object targetObj = new UserService();// AOP 代理IUserService proxy = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), targetObj.getClass().getInterfaces(), new InvocationHandler() {// 方法匹配器MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))");@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (methodMatcher.matches(method, targetObj.getClass())) {// 方法拦截器MethodInterceptor methodInterceptor = invocation -> {long start = System.currentTimeMillis();try {return invocation.proceed();} finally {System.out.println("监控 - Begin By AOP");System.out.println("方法名称:" + invocation.getMethod().getName());System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");System.out.println("监控 - End\r\n");}};// 反射调用return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args));}return method.invoke(targetObj, args);}});String result = proxy.queryUserInfo();System.out.println("测试结果:" + result);}
}

在这里插入图片描述

Pointcut:切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类,这两个接口获取都是切点表达式提供的内容。
ClassFilter:定义类匹配类,用于切点找到给定的接口和目标类。
MethodMatcher:方法匹配,找到表达式范围内匹配下的目标类和方法

AspectJExpressionPointcut:实现了 Pointcut、ClassFilter、MethodMatcher,三个接口定义方法,同时这个类主要是对 aspectj 包提供的表达式校验方法使用。
用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。

public class AdvisedSupport {// 被代理的目标对象private TargetSource targetSource;// 方法拦截器private MethodInterceptor methodInterceptor;// 方法匹配器(检查目标方法是否符合通知条件)private MethodMatcher methodMatcher;// ...get/set
}

最后进行代理抽象实现(JDK&Cglib)

AOP动态代理,融入到Bean的生命周期

BeanPostProcessor 接口实现继承的 InstantiationAwareBeanPostProcessor 接口后,做了一个自动代理创建的类 DefaultAdvisorAutoProxyCreator,这个类的就是用于处理整个 AOP 代理融入到 Bean 生命周期中的核心类。
DefaultAdvisorAutoProxyCreator 会依赖于拦截器、代理工厂和Pointcut与Advisor的包装服务 AspectJExpressionPointcutAdvisor,由它提供切面、拦截方法和表达式。

融入Bean生命周期的自动代理创建者,如下

public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {private DefaultListableBeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = (DefaultListableBeanFactory) beanFactory;}@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {if (isInfrastructureClass(beanClass)) return null;//获取aspc表达式Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();//获取通知信息for (AspectJExpressionPointcutAdvisor advisor : advisors) {ClassFilter classFilter = advisor.getPointcut().getClassFilter();if (!classFilter.matches(beanClass)) continue;//构造包括代理类,要代理的信息的类AdvisedSupport advisedSupport = new AdvisedSupport();TargetSource targetSource = null;try {targetSource = new TargetSource(beanClass.getDeclaredConstructor().newInstance());} catch (Exception e) {e.printStackTrace();}advisedSupport.setTargetSource(targetSource);advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());advisedSupport.setProxyTargetClass(false);//生成代理类return new ProxyFactory(advisedSupport).getProxy();}return null;}
}

创建bean

    @Overrideprotected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {Object bean = null;try {// 判断是否返回代理 Bean 对象bean = resolveBeforeInstantiation(beanName, beanDefinition);if (null != bean) {return bean;}bean = createBeanInstance(beanDefinition, beanName, args);// 给 bean 填充属性applyPropertyValues(beanName, bean, beanDefinition);// 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法bean = initializeBean(beanName, bean, beanDefinition);} catch (Exception e) {throw new BeansException("Instantiation of bean failed.", e);}// 注册实现了 DisposableBean 接口的 Bean 对象registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);// 判断 SCOPE_SINGLETON,SCOPE_PROTOTYPEif (beanDefinition.isSingleton()) {registerSingleton(beanName, bean);}return bean;}

通过注解配置和包自动扫描的方式完成Bean对象的注册

在XmlBeanDefinitionReader中解析<context:component-scan />标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。
主要包括的就是 xml 解析类 XmlBeanDefinitionReader 对 ClassPathBeanDefinitionScanner#doScan 的使用。

依赖于 BeanFactoryPostProcessor 在 Bean 生命周期的属性,可以在 Bean 对象实例化之前,改变属性信息。所以这里通过实现 BeanFactoryPostProcessor 接口,完成对配置文件的加载以及摘取占位符中的在属性文件里的配置。
这样就可以把提取到的配置信息放置到属性配置中了,buffer.replace(startIdx, stopIdx + 1, propVal); propertyValues.addPropertyValue

Component注解扫描原理:
定义

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

1、解析xml,在得到有component-scan是开启注解扫描功能,XmlBeanDefinitionReader

    protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException, DocumentException {SAXReader reader = new SAXReader();Document document = reader.read(inputStream);Element root = document.getRootElement();// 解析 context:component-scan 标签,扫描包中的类并提取相关信息,用于组装 BeanDefinitionElement componentScan = root.element("component-scan");if (null != componentScan) {String scanPath = componentScan.attributeValue("base-package");if (StrUtil.isEmpty(scanPath)) {throw new BeansException("The value of base-package attribute can not be empty or null");}scanPackage(scanPath);}// ... 省略其他// 注册 BeanDefinitiongetRegistry().registerBeanDefinition(beanName, beanDefinition);}private void scanPackage(String scanPath) {String[] basePackages = StrUtil.splitToArray(scanPath, ',');ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry());scanner.doScan(basePackages);}

2、扫描所有的要扫描的package,并注册,ClassPathBeanDefinitionScanner

  public void doScan(String... basePackages) {for (String basePackage : basePackages) {//发现注解,下面详细看Set<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition beanDefinition : candidates) {// 解析 Bean 的作用域 singleton、prototypeString beanScope = resolveBeanScope(beanDefinition);if (StrUtil.isNotEmpty(beanScope)) {beanDefinition.setScope(beanScope);}registry.registerBeanDefinition(determineBeanName(beanDefinition), beanDefinition);}}}

3、上述处理注解对象的装配,ClassPathScanningCandidateComponentProvider

    public Set<BeanDefinition> findCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();//扫描所有的包含Component的类,并加入candidates,最后返回Set<Class<?>> classes = ClassUtil.scanPackageByAnnotation(basePackage, Component.class);for (Class<?> clazz : classes) {candidates.add(new BeanDefinition(clazz));}return candidates;}

通过注解给属性注入配置和Bean对象

围绕实现接口 InstantiationAwareBeanPostProcessor 的类 AutowiredAnnotationBeanPostProcessor 作为入口点,被 AbstractAutowireCapableBeanFactory创建 Bean 对象过程中调用扫描整个类的属性配置中含有自定义注解 Value、Autowired、Qualifier,的属性值。

AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues

 @Overridepublic PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException {// 1. 处理注解 @ValueClass<?> clazz = bean.getClass();clazz = ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz;Field[] declaredFields = clazz.getDeclaredFields();for (Field field : declaredFields) {Value valueAnnotation = field.getAnnotation(Value.class);if (null != valueAnnotation) {String value = valueAnnotation.value();value = beanFactory.resolveEmbeddedValue(value);BeanUtil.setFieldValue(bean, field.getName(), value);}}// 2. 处理注解 @Autowiredfor (Field field : declaredFields) {Autowired autowiredAnnotation = field.getAnnotation(Autowired.class);if (null != autowiredAnnotation) {Class<?> fieldType = field.getType();String dependentBeanName = null;Qualifier qualifierAnnotation = field.getAnnotation(Qualifier.class);Object dependentBean = null;if (null != qualifierAnnotation) {dependentBeanName = qualifierAnnotation.value();dependentBean = beanFactory.getBean(dependentBeanName, fieldType);} else {dependentBean = beanFactory.getBean(fieldType);}BeanUtil.setFieldValue(bean, field.getName(), dependentBean);}}return pvs;}

循环依赖

循环依赖主要分为这三种,自身依赖于自身、互相循环依赖、多组循环依赖。
循环依赖需要用到三个缓存,这三个缓存分别存放了成品对象、半成品对象(未填充属性值)、代理对象,分阶段存放对象内容,来解决循环依赖问题。
用于解决循环依赖需要用到三个缓存,这三个缓存分别存放了成品对象、半成品对象(未填充属性值)、代理对象,分阶段存放对象内容,来解决循环依赖问题。
关于循环依赖在我们目前的 Spring 框架中扩展起来也并不会太复杂,主要就是对于创建对象的提前暴露,如果是工厂对象则会使用 getEarlyBeanReference 逻辑提前将工厂🏭对象存放到三级缓存中。等到后续获取对象的时候实际拿到的是工厂对象中 getObject,这个才是最终的实际对象。

事务功能设计

基本原理:通过AOP的方式来设置关闭数据库的自动提交事务,如:connection.setAutoCommit(false),然后在程序中在合适的位置进行手动提交事务和回滚事务

实际使用–JDBCTemplate

JdbcTemplate可以作为一个普通的bean来管理,里面定义了对数据库的操作,实际上是依赖的各种数据库的驱动。
想要完成连接数据库也需要引入其他的bean,例如DriverManagerDataSource连接池技术。

整合ORM框架

实际上是将ORM框架的连接信息和执行sql的信息交给Spring来管理
例如整合mybatis框架时实现了一个SqlsessionFactoryBuild的工厂类对sqlSession进行管理,同时扫描到众多的执行sql的bean,在ORM完成sql到bean的方法的映射后,注入到spring中,直接执行就能与数据库关联

相关文章:

手写Spring与基本原理--简易版

文章目录 手写Spring与基本原理解析简介写一个简单的Bean加载容器定义一个抽象所有类的BeanDefinition定义一个工厂存储所有的类测试 实现Bean的注册定义和获取基于Cglib实现含构造函数的类实例化策略Bean对象注入属性和依赖Bean的功能Spring.xml解析和注册Bean对象实现应用上下…...

EasyNTS端口穿透服务新版本发布 0.8.7 增加隧道流量总数记录,可以知晓设备哪个端口耗费流量了

EasyNTS上云平台可通过远程访问内网应用&#xff0c;包含网络桥接、云端运维、视频直播等功能&#xff0c;极大地解决了现场无固定IP、端口不开放、系统权限不开放等问题。平台可提供一站式上云服务&#xff0c;提供直播上云、设备上云、业务上云、运维上云服务&#xff0c;承上…...

python自动合计各部周销

下载依赖 pip install openpyxl -i https://pypi.doubanio.com/simplepip install pandas -i https://pypi.doubanio.com/simple引入依赖 from openpyxl import load_workbook from openpyxl import styles from openpyxl.styles import * import pandas as pd import string…...

Java内存区域与内存溢出异常

Java与C++之间有一堵由内存分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。 2.1 概述 对于从事C、C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的“皇帝”,又是从事最基础工作的劳动人民——即拥有每一个对象的“所有权”,又…...

远程网络唤醒家庭主机(openwrt设置)

远程网络唤醒家庭主机&#xff08;openwrt设置&#xff09; 前提&#xff1a; 1.配置好主板bios的网络唤醒功能(网络教程自己百度一下找) 2.电脑开启网络唤醒功能(网络教程自己百度一下找) 3.路由器通过ddns实现域名和动态IP绑定内网穿透方法汇总_不修改光猫进行内网穿透-C…...

Spring知识02

1、这边是做单元测试的 2、项目部署上线的时候需要把Test那里注解掉 3、pom.xml的坐标系&#xff0c;用来导出包给别人用 4、项目名称&#xff0c;artifactId&#xff0c;name属性名保持一致 5、maven中央仓库那里可以看到导包之后会随着附加的内容 6、class.getSingleName获取…...

Linux服务器搭建笔记-006:拓展/home目录容量

一、问题说明 Ubuntu服务器在使用过程中创建的新用户&#xff0c;每位用户会在/home目录下生成一个属于其个人的主文件夹。如果不限制各个用户的使用空间&#xff0c;所有的用户都会共用/home所挂载的硬盘。在这种多用户情况下&#xff0c;会很快的填满/home目录&#xff0c;导…...

元宇宙与VR虚拟现实的未来如何?

从科幻小说到商业现实 自从 Facebook年更名为 Meta 以来&#xff0c;关于元宇宙的热议不断&#xff0c;人们对虚拟世界的兴趣也重新燃起&#xff0c;因为尽管虚拟现实 (VR) 的概念由来已久&#xff0c;但该技术现在才开始真正得以应用。 定义元宇宙和虚拟现实 首先是 The Met…...

微服务事务处理:CAP 定理和最终一致性的关系

CAP 定理和最终一致性 CAP 定理和最终一致性是两个密切相关的概念&#xff0c;但它们在范围和细节上有所不同。以下是比较&#xff1a; CAP 定理 **正式陈述&#xff1a;**在分布式系统中&#xff0c;最多只能同时满足以下三个保证中的两个&#xff1a;一致性、可用性和分区…...

【Linux操作系统】探秘Linux奥秘:操作系统的入门与实战

&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《操作系统实验室》&#x1f516;诗赋清音&#xff1a;柳垂轻絮拂人衣&#xff0c;心随风舞梦飞。 山川湖海皆可涉&#xff0c;勇者征途逐星辉。 目录 &#x1fa90;1 初识Linux OS …...

Copilot概述:AI助手引领编程新纪元

前言&#xff1a; 随着人工智能&#xff08;AI&#xff09;技术的不断进步&#xff0c;编程领域也在逐渐迎来一场革命。GitHub Copilot&#xff0c;作为一款由 OpenAI 和 GitHub 合作开发的编程助手&#xff0c;引发了广泛的关注和讨论。本篇博客将全面概述 Copilot 的背景、功…...

最小覆盖子串(LeetCode 76)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路参考文献 1.问题描述 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 “” 。 注意&#xff1a; 对于 t 中重复字符&#xff…...

Windows Sockets 2 笔记

文章目录 一、Winsock简介二、Windows中Winsock对网络协议支持的情况三、使用Winsock3.1 关于服务器和客户端3.2 创建基本Winsock应用程序3.3 初始化Winscok3.3.1 初始化步骤3.3.2 初始化的核心代码3.3.3 WSAStartup函数的协调3.3.4 WSACleanup函数3.3.5 初始化的完整代码 3.4 …...

13章总结

一.泛型 1.定义泛型类 泛型机制语法&#xff1a; 类名<T> 其中&#xff0c;T是泛型的名称&#xff0c;代表某一种类型。 【例13.6】创建带泛型的图书类 代码&#xff1a; 结果&#xff1a; 2.泛型的常规用法 (1)定义泛型类时声明多个变量 class MyClass<T1,T2>…...

(2023,3D NeRF,无图像变分分数蒸馏,单步扩散)SwiftBrush:具有变分分数蒸馏的一步文本到图像扩散模型

SwiftBrush : One-Step Text-to-Image Diffusion Model with Variational Score Distillation 公众&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 方法 1.1 基础 1.2 SwiftBrus…...

【WPF.NET开发】将路由事件标记为已处理和类处理

本文内容 先决条件何时将路由事件标记为已处理预览和浮升路由事件对实例和类路由事件处理程序复合控件中的输入事件禁止 尽管对于何时将路由事件标记为已处理没有绝对规则&#xff0c;但如果代码以重要方式响应事件&#xff0c;请考虑将事件标记为已处理。 标记为已处理的路由…...

2023年03月18日_微软office365 copilot相关介绍

文章目录 Copilot In WordCopilot In PowerpointCopilot In ExcelCopilot In OutlookCopilot In TeamsBusiness Chat1 - copilot in word2 - copilot in excel3 - copilot in powerpoint4 - copilot in outlook5 - copilot in teams6 - business chat word 1、起草草稿 2、自动…...

GBASE南大通用携手宇信科技打造“一表通”全链路解决方案

什么是“一表通”&#xff1f; “一表通”是国家金融监督管理总局为发挥统计监督效能、完善银行保险监管统计制度、推进监管数据标准化建设、打破数据壁垒&#xff0c;而制定的新型监管数据统计规范。相较于以往的报送接口&#xff0c;“一表通”提高了对报送时效性、校验准确…...

Python 内置高阶函数练习(Leetcode500.键盘行)

Python 内置高阶函数练习&#xff08;Leetcode500.键盘行&#xff09; 【一】试题 &#xff08;1&#xff09;地址&#xff1a; 500. 键盘行 - 力扣&#xff08;LeetCode&#xff09; &#xff08;2&#xff09;题目 给你一个字符串数组 words &#xff0c;只返回可以使用在…...

【JavaWeb】day01-HTMLCSS

day01-HTML&CSS HTML 图片标签&#xff1a;<img> src&#xff1a;指定图像URL&#xff08;绝对路径/相对路径&#xff09;width&#xff1a;图像宽度&#xff08;像素/相对于父元素的百分比&#xff09;height&#xff1a;图像高度&#xff08;像素/相对于父元素的百…...

【工具】windeployqt 在windows + vscode环境下打包

目录 0.背景简介 1.windeployqt简介 2.打包具体过程 1&#xff09;用vscode编译&#xff0c;生成Release文件夹&#xff08;也有Debug文件夹&#xff0c;但是发布版本一般都是用Release&#xff09; 2&#xff09;此时可以看下Release文件夹内&#xff0c;一般是.exe可执行…...

跟着LearnOpenGL学习12--光照贴图

文章目录 一、前言二、漫反射贴图三、镜面光贴图3.1、采样镜面光贴图 一、前言 在跟着LearnOpenGL学习11–材质中&#xff0c;我们讨论了让每个物体都拥有自己独特的材质从而对光照做出不同的反应的方法。这样子能够很容易在一个光照的场景中给每个物体一个独特的外观&#xf…...

DotNet 命令行开发

DotNet 命令行开发 下载安装下载 SDK安装 SDK绿色版下载绿化脚本 常用命令创建 dotnet new运行 dotnet run发布应用 dotnet publish更多命令 VSCode 调试所需插件调试 CS 配置项目.csproj排除依赖关系 launch.jsontasks.json 参考资料 下载安装 下载 SDK 我们就下最新的好&am…...

hyperf console 执行

一、原理描述 hyperf中&#xff0c;不难发现比如自定义控制器中获取参数&#xff0c;hyperf.php中容器获取&#xff0c;传入的都是接口&#xff0c;而不是实体类。 这是因为框架中的配置文件有设置对应抽象类的子类&#xff0c;框架加载的时候将其作为数组&#xff0c;使用的…...

第一篇 设计模式引论 - 探索软件设计的智慧结晶

1. 设计模式的定义和起源 设计模式&#xff0c;这个术语最初在建筑领域被广泛使用&#xff0c;用来描述在建筑设计中反复出现的问题及其解决方案。在软件工程中&#xff0c;设计模式同样指的是在软件设计过程中反复出现的、经过验证的最佳实践和解决方案。 1994年&#xff0c…...

HBase基础知识(六):HBase 对接 Hive

1. HBase 与 Hive 的对比 1&#xff0e;Hive (1) 数据仓库 Hive 的本质其实就相当于将 HDFS 中已经存储的文件在 Mysql 中做了一个双射关系&#xff0c;以 方便使用 HQL 去管理查询。 (2) 用于数据分析、清洗 Hive 适用于离线的数据分析和清洗&#xff0c;延迟较高。 (3) 基于…...

Java连接Mysql报错:javax.net.ssl.SSLException: Received fatal alert: internal_error

大致报错日志如下&#xff1a; The last packet successfully received from the server was 11 milliseconds ago. The last packet sent successfully to the server was 10 milliseconds ago.at sun.reflect.GeneratedConstructorAccessor275.newInstance(Unknown Source)…...

Mixtral 8*7B + Excel + Python 超强组合玩转数据分析

Mixtral 8*7B Excel Python 超强组合玩转数据分析 0. 背景1. 使用 Mixtral 8*7B pandas 实现数据导入和导出1.1 使用 Mixtral 8*7B pandas 导入 Excel 文件中的数据1.2 使用 Mixtral 8*7B pandas 导出 Excel 文件中的数据 2. 使用 Mixtral 8*7B pandas 实现单个文件数据的…...

深入浅出理解Web认证:Session、Cookie与Token

在Web开发的世界中&#xff0c;理解Session、Session ID、Cookie和Token之间的区别至关重要。实际上&#xff0c;这些概念并不复杂&#xff0c;只需几句话就能澄清它们的核心区别。 首先&#xff0c;我们需要区分Session和Session ID。Session实际上是存储在服务器端的数据&am…...

智慧零售技术探秘:关键技术与开源资源,助力智能化零售革新

智慧零售是一种基于先进技术的零售业态&#xff0c;通过整合物联网、大数据分析、人工智能等技术&#xff0c;实现零售过程的智能化管理并提升消费者体验。 实现智慧零售的关键技术包括商品的自动识别与分类、商品的自动结算等等。 为了实现商品的自动识别与分类&#xff0c;…...

永年做网站多少钱/百度搜索引擎

题目 长度为n(n<1e5)的仅由abc三种字母组成的串&#xff0c; q(q<1e5)次操作&#xff0c;每次给出两个参数pos x&#xff08;x属于a、b、c三种字母中的一种&#xff09;&#xff0c; 表示把pos位置的字母改成x&#xff0c; 每次改完之后&#xff0c;都需要输出把整个…...

网站开发上线ftp怎么用/怎么做网址

editTable.js 提供编辑表格当前行、添加一行、删除当前行的操作&#xff0c;其中可以设置参数&#xff0c;如&#xff1a; operatePos 用于设置放置操作的列&#xff0c;从0开始&#xff0c;-1表示以最后一列作为放置操作的列&#xff1b;&#xff08;这里的操作包括 编辑当前行…...

长宁区网站建设网页制/个人怎么注册自己的网站

一&#xff1a;编写目的 本文档的编写旨在探寻规范的软件开发流程、加快软件开发速度、提高软件开发质量、降低项目综合成本。 IT界有一句格言&#xff1a;"You can do it right; you can do it fast; you can do it cheap. Pick two." 而我们要做的就是&#xff1a;…...

质量好网站建设公司/关键词优化排名的步骤

locale 关于locale的设定 locale 是国际化与本土化过程中的一个非常重要的概念&#xff0c;个人认为&#xff0c;对于中文用户来说&#xff0c;通常会涉及到的国际化或者本土化&#xff0c;大致包含三个方面&#xff1a;看中文&#xff0c;写中文&#xff0c;与 window中文系统…...

web旅游网站开发/网络策划是做什么的

【芝麻代理】请添加链接描述做新媒体运营经常遇到的问题是&#xff0c;我的网站每天都发布新文章&#xff0c;被百度收录的文章却很少&#xff0c;不知道是什么原因&#xff0c;如何让我发布的文章被百度收录&#xff1f;就此问题&#xff0c;个人总结了如下十一条经验&#xf…...

网站首页样式/杭州网站关键词排名优化

配置办法https://blog.csdn.net/qq342643414/article/details/78364601 可能会遇到的问题https://www.cnblogs.com/chuijingjing/p/9880095.html转载于:https://www.cnblogs.com/helf/p/10709686.html...