手写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中,直接执行就能与数据库关联

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

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

相关文章

API 开放平台项目(已整理,已废弃)

项目大纲 前端 React 18Ant Design Pro 5.x 脚手架Ant Design & Procomponents 组件库Umi 4 前端框架OpenAPI 前端代码生成 后端 Java Spring BootMySQL 数据库MyBatis-Plus 及 MyBatis X 自动生成API 签名认证&#xff08;Http 调用&#xff09;Spring Boot Starter&#…

探寻数据压缩——第一代小波构造的统一框架

小波分析作为一种多尺度分析方法&#xff0c;已经在信号处理、图像处理、数据压缩和模式识别等领域中展现出了巨大的应用潜力。在小波分析的发展历程中&#xff0c;第一代小波构造算法被广泛应用&#xff0c;为了更好地理解和应用第一代小波构造算法&#xff0c;构建一个统一的…

数据库——创建存储过程、函数和触发器安装phpmyadmin

1.实验内容及原理 1. 在 Windows 系统中安装 VMWare 虚拟机&#xff0c;在 VMWare 中安装 Ubuntu 系统,并在 Ubuntu 中搭建 LAMP 实验环境。 2. 使用 MySQL 进行一些基本操作&#xff1a; &#xff08;1&#xff09;登录 MySQL&#xff0c;在 MySQL 中创建用户&#xff0c;…

事实验证文章分类 Papers Category For Fact Checking

事实验证文章分类 Papers Category For Fact Checking By 2023.11 个人根据自己的观点&#xff0c;花了很多时间整理的一些关于事实验证领域证据召回&#xff0c;验证推理过程的文献综合整理分类&#xff08;不是很严谨&#xff09;。 引用请注明出处 欢迎从事事实验证Fact…

TypeScript源码中的一个很有意思的简写

在读TypeScript源码时&#xff0c;发现一个很有意思的简写 &#xff1a; return scriptInfo ? scriptInfo.getDefaultProject() : (this.logErrorForScriptInfoNotFound(isString(fileNameOrScriptInfo) ? fileNameOrScriptInfo : fileNameOrScript…

挑战Python100题(6)

100 Python challenging programming exercises 6 Question 51 Define a class named American and its subclass NewYorker. Hints: Use class Subclass(ParentClass) to define a subclass. 定义一个名为American的类及其子类NewYorker。 提示&#xff1a;使用class Subcla…

【史上最细教程】1台服务器部署2台MongoDB实例

文章目录 【史上最全教程】1台服务器部署2台MongoDB实例1.下载解压安装包2.配置系统环境变量3.创建实例27017扩展内容(可跳过) 4.配置安全组、防火墙5.可视化工具连接问题1&#xff1a;not authorized on admin to execute command 【史上最全教程】1台服务器部署2台MongoDB实例…

Flink(十一)【状态管理】

Flink 状态管理 我们一直称 Flink 为运行在数据流上的有状态计算框架和处理引擎。在之前的章节中也已经多次提到了“状态”&#xff08;state&#xff09;&#xff0c;不论是简单聚合、窗口聚合&#xff0c;还是处理函数的应用&#xff0c;都会有状态的身影出现。状态就如同事务…

Java日期工具类LocalDate

Java日期工具类LocalDate 嘚吧嘚java.util.DateJava8新增日期类时区 LocalDate - API创建日期获取年月日修改年月日日期比较 嘚吧嘚 java.util.Date 在Java8之前通常会使用Date结合SimpleDateFormat、Calender来处理时间和日期的相关需求。 1、可读性差、易用性差、使用起来冗…

C#的checked关键字判断是否溢出

目录 一、定义 二、示例&#xff1a; 三、生成&#xff1a; 一、定义 使用checked关键字处理溢出。 在进行数学运算时&#xff0c;由于变量类型不同&#xff0c;数值的值域也有所不同。如果变量中的数值超出了变量的值域&#xff0c;则会出现溢出情况&#xff0c;出现溢出…

12.21自动售货机,单物品,多物品

自动售货机 if朴素方法 一种思路是用寄存器cnt记录已有的最小单位货币量&#xff0c;这里就是0.5 当d1时&#xff0c;cnt1;d2时&#xff0c;cnt2;d3时&#xff0c;cnt4; timescale 1ns/1ns module seller1(input wire clk ,input wire rst ,input wire d1 ,input wire d2 …

vue3 组件之间传值

vue3 组件之间传值 非常好&#xff0c;为啥突然开这样一篇博文&#xff0c;首先是因为 vue3 是未来发展的趋势。其次&#xff0c;vue 官方已经确认&#xff0c;将于2023年最后一天停止对 vue2 项目的维护&#xff0c;这个是官方发出的通知&#xff0c;并且呢&#xff0c;尤雨溪…

面试算法78:合并排序链表

题目 输入k个排序的链表&#xff0c;请将它们合并成一个排序的链表。 分析&#xff1a;利用最小堆选取值最小的节点 用k个指针分别指向这k个链表的头节点&#xff0c;每次从这k个节点中选取值最小的节点。然后将指向值最小的节点的指针向后移动一步&#xff0c;再比较k个指…

统信UOS及麒麟KYLINOS操作系统上设置GRUB密码

原文链接&#xff1a;给单用户模式上一层保险&#xff01;&#xff01;&#xff01; hello&#xff0c;大家好啊&#xff01;今天我要给大家介绍的是在统信UOS及麒麟KYLINOS操作系统上设置GRUB密码的方法。GRUB&#xff08;GRand Unified Bootloader&#xff09;是Linux系统中的…

利用F12和Fiddler抓包

网络基础 http 而http协议又分为下面的部分,点击具体条目后可以查看详细信息 http请求消息:请求行(请求方法),请求路径,请求头,请求体(载荷) http响应消息:响应行(响应状态码),响应头&#xff0c;响应体 请求行 即请求方法 get post put patch 响应行 即响应码,常见响应状态…

祖先是否安宁,直接关系到个人以及家运哦!

一直以来&#xff0c;中国古代流传下来的思想就认为&#xff0c;祖先安葬在好的风水福地&#xff0c;一定能给子孙后代带来吉祥如意。相反的&#xff0c;假如祖坟风水不好&#xff0c;则会影响到后人的运气&#xff0c;轻者诸事不顺、重者家庭破裂、噩运连连&#xff0c;所以&a…

【C++杂货铺】C++11新特性——lambda

文章目录 一、C98中的排序二、先来看看 lambda 表达式长什么样三、lambda表达式语法3.1 捕捉列表的使用细节 四、lambda 的底层原理五、结语 一、C98中的排序 在 C98 中&#xff0c;如果要对一个数据集合中的元素进行排序&#xff0c;可以使用 std::sort 方法&#xff0c;下面…

二叉树顺序结构与堆的概念及性质(c语言实现堆)

上次介绍了树&#xff0c;二叉树的基本概念结构及性质&#xff1a;二叉树数据结构&#xff1a;深入了解二叉树的概念、特性与结构 今天带来的是&#xff1a;二叉树顺序结构与堆的概念及性质&#xff0c;还会用c语言来实现堆 文章目录 1. 二叉树的顺序结构2.堆的概念和结构3.堆…

推荐几个开源HTTP服务接口快速生成工具

在现在流行微服务、前后端分离软件开发架构下&#xff0c;基于标准RESTful/JSON的HTTP接口已经成为主流。在实际业务中有很多需要快速开发调用数据服务接口的需求&#xff0c;但团队中缺乏专业的后端开发人员&#xff0c;比如&#xff1a; &#xff08;1&#xff09;数据库表已…

PHP开发日志 ━━ 基于PHP和JS的AES相互加密解密方法详解(CryptoJS) 适合CryptoJS4.0和PHP8.0

最近客户在做安全等保&#xff0c;需要后台登录密码采用加密方式&#xff0c;原来用个base64变形一下就算了&#xff0c;现在不行&#xff0c;一定要加密加key加盐~~ 前端使用Cypto-JS加密&#xff0c;传输给后端使用PHP解密&#xff0c;当然&#xff0c;前端虽然有key有盐&…