【spring源码分析】IOC容器初始化(十)
前言:前文【spring源码分析】IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean对象的流程,这里接着分析createBeanInstance方法中的剩余流程。
直接看createBeanInstance中剩余的流程:
- // AbstractAutowireCapableBeanFactory
- boolean resolved = false;
- boolean autowireNecessary = false;
- if (args == null) {
- // 做同步
- synchronized (mbd.constructorArgumentLock) {
- // 如果已缓存的解析构造函数或者工厂方法不为null,则可以利用构造函数解析
- // 因为需要根据参数确认到底使用哪个构造函数,该过程比较消耗性能,所以采用缓存机制
- if (mbd.resolvedConstructorOrFactoryMethod != null) {
- resolved = true;
- autowireNecessary = mbd.constructorArgumentsResolved;
- }
- }
- }
- // 已经解析好了,直接注入即可
- if (resolved) {
- // autowire自动注入,调用构造函数自动注入
- if (autowireNecessary) {
- return autowireConstructor(beanName, mbd, null, null);
- } else {
- // 使用默认构造函数构造
- return instantiateBean(beanName, mbd);
- }
- }
- // Need to determine the constructor...
- // 确定解析的构造函数
- // 主要是检查已经注册的SmartInstantiationAwareBeanPostProcessor
- Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
- // 确定构造方法进行bean创建
- if (ctors != null ||
- mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
- mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
- return autowireConstructor(beanName, mbd, ctors, args);
- }
- // 有参数,又没有获取到构造方法,则只能调用无参构造方法来创建实例
- // 这是一个兜底的方法
- // No special handling: simply use no-arg constructor.
- return instantiateBean(beanName, mbd);
分析:
剩余流程还剩下两大分支:构造函数自动注入初始化bean和默认构造函数初始化bean。
#1 AbstractAutowireCapableBeanFactory#autowireConstructor
- protected BeanWrapper autowireConstructor(
- String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {
- return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
- }
可以看到这里还是委托给ConstructorResolver来执行的:
- public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
- @Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
- // 封装BeanWrapperImpl并完成初始化
- BeanWrapperImpl bw = new BeanWrapperImpl();
- this.beanFactory.initBeanWrapper(bw);
- // 获取constructorToUse、argsHolderToUse、argsToUse属性
- Constructor<?> constructorToUse = null; // 构造函数
- ArgumentsHolder argsHolderToUse = null; // 构造参数
- Object[] argsToUse = null; // 构造参数
- // 确定构造参数
- // 如果getBean()已传递,则直接使用
- if (explicitArgs != null) {
- argsToUse = explicitArgs;
- } else {
- // 尝试从缓存中获取
- Object[] argsToResolve = null;
- // 同步
- synchronized (mbd.constructorArgumentLock) {
- // 缓存中的构造函数或工厂方法
- constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
- if (constructorToUse != null && mbd.constructorArgumentsResolved) {
- // Found a cached constructor...
- // 缓存中的构造参数
- argsToUse = mbd.resolvedConstructorArguments;
- if (argsToUse == null) {
- // 获取缓存中的构造函数参数 包括可见字段
- argsToResolve = mbd.preparedConstructorArguments;
- }
- }
- }
- // 缓存中存在,则解析存储在BeanDefinition中的参数
- // 如给定方法的构造函数 f(int,int),则通过此方法后就会把配置文件中的("1","1")转换为 (1,1)
- // 缓存中的值可能是原始值也有可能是最终值
- if (argsToResolve != null) {
- argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
- }
- }
- // 没有缓存,则尝试从配置文件中获取参数
- if (constructorToUse == null || argsToUse == null) {
- // Take specified constructors, if any.
- Constructor<?>[] candidates = chosenCtors;
- // 如果chosenCtors未传入,则获取构造方法
- if (candidates == null) {
- Class<?> beanClass = mbd.getBeanClass();
- try {
- candidates = (mbd.isNonPublicAccessAllowed() ?
- beanClass.getDeclaredConstructors() : beanClass.getConstructors());
- } catch (Throwable ex) {
- throw new BeanCreationException(mbd.getResourceDescription(), beanName,
- "Resolution of declared constructors on bean Class [" + beanClass.getName() +
- "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
- }
- }
- // 创建bean
- if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
- Constructor<?> uniqueCandidate = candidates[0];
- if (uniqueCandidate.getParameterCount() == 0) {
- synchronized (mbd.constructorArgumentLock) {
- mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
- mbd.constructorArgumentsResolved = true;
- mbd.resolvedConstructorArguments = EMPTY_ARGS;
- }
- bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS));
- return bw;
- }
- }
- // 是否需要解析构造器
- // Need to resolve the constructor.
- boolean autowiring = (chosenCtors != null ||
- mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
- // 用于承载解析后的构造函数参数值
- ConstructorArgumentValues resolvedValues = null;
- int minNrOfArgs;
- if (explicitArgs != null) {
- minNrOfArgs = explicitArgs.length;
- } else {
- // 从BeanDefinition中获取构造参数,也就是从配置文件中提取构造参数
- ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
- resolvedValues = new ConstructorArgumentValues();
- // 解析构造函数的参数
- // 将该bean的构造函数参数解析为resolvedValues对象,其中会涉及到其他bean
- minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
- }
- // 对构造函数进行排序处理
- // public构造函数优先,参数数量降序,非public构造函数参数数量降序
- AutowireUtils.sortConstructors(candidates);
- // 最小参数类型权重
- int minTypeDiffWeight = Integer.MAX_VALUE;
- Set<Constructor<?>> ambiguousConstructors = null;
- LinkedList<UnsatisfiedDependencyException> causes = null;
- // 遍历所有构造函数
- for (Constructor<?> candidate : candidates) {
- // 获取该构造函数的参数类型
- Class<?>[] paramTypes = candidate.getParameterTypes();
- // 如果已经找到选用的构造函数且需要的参数个数大于当前构造函数参数个数,则break
- if (constructorToUse != null && argsToUse != null && argsToUse.length > paramTypes.length) {
- // Already found greedy constructor that can be satisfied ->
- // do not look any further, there are only less greedy constructors left.
- break;
- }
- // 参数个数不等,继续循环
- if (paramTypes.length < minNrOfArgs) {
- continue;
- }
- // 参数持有者ArgumentsHolder对象
- ArgumentsHolder argsHolder;
- if (resolvedValues != null) {
- try {
- // 注释上获取参数名称
- String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length);
- if (paramNames == null) {
- // 获取构造函数、方法参数的探测器
- ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
- if (pnd != null) {
- // 通过探测器获取构造函数的参数名称
- paramNames = pnd.getParameterNames(candidate);
- }
- }
- // 根据构造函数和构造参数,创建参数持有者ArgumentsHolder对象
- argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
- getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
- } catch (UnsatisfiedDependencyException ex) {
- if (logger.isTraceEnabled()) {
- logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
- }
- // Swallow and try next constructor.
- if (causes == null) {
- causes = new LinkedList<>();
- }
- // 若产生UnsatisfiedDependencyException异常,则添加到causes中
- causes.add(ex);
- continue;
- }
- } else {
- // Explicit arguments given -> arguments length must match exactly.
- // 构造函数中没有参数,则continue
- if (paramTypes.length != explicitArgs.length) {
- continue;
- }
- // 根据explicitArgs创建ArgumentsHolder对象
- argsHolder = new ArgumentsHolder(explicitArgs);
- }
- // isLenientConstructorResolution 判断解析构造函数的时候是否以宽松模式还是严格模式
- // 严格模式:解析构造函数时,必须所有的都需要匹配,否则抛出异常
- // 宽松模式:使用具有"最接近的模式"进行匹配
- // typeDiffWeight:类型差异权重
- int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
- argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
- // Choose this constructor if it represents the closest match.
- // 如果它代表着当前最接近的匹配则选择其作为构造函数
- if (typeDiffWeight < minTypeDiffWeight) {
- constructorToUse = candidate;
- argsHolderToUse = argsHolder;
- argsToUse = argsHolder.arguments;
- minTypeDiffWeight = typeDiffWeight;
- ambiguousConstructors = null;
- } else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
- if (ambiguousConstructors == null) {
- ambiguousConstructors = new LinkedHashSet<>();
- ambiguousConstructors.add(constructorToUse);
- }
- ambiguousConstructors.add(candidate);
- }
- }
- // 没有可执行的工厂方法,则抛出异常
- if (constructorToUse == null) {
- if (causes != null) {
- UnsatisfiedDependencyException ex = causes.removeLast();
- for (Exception cause : causes) {
- this.beanFactory.onSuppressedException(cause);
- }
- throw ex;
- }
- throw new BeanCreationException(mbd.getResourceDescription(), beanName,
- "Could not resolve matching constructor " +
- "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
- } else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
- throw new BeanCreationException(mbd.getResourceDescription(), beanName,
- "Ambiguous constructor matches found in bean '" + beanName + "' " +
- "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
- ambiguousConstructors);
- }
- if (explicitArgs == null && argsHolderToUse != null) {
- // 将解析的构造函数加入缓存
- argsHolderToUse.storeCache(mbd, constructorToUse);
- }
- }
- // 创建bean对象,并设置到BeanWrapperImpl中
- Assert.state(argsToUse != null, "Unresolved constructor arguments");
- bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
- return bw;
- }
分析:
该函数可简单理解为是带有参数的构造方法来进行bean的初始化。可以看到该函数与【spring源码分析】IOC容器初始化(九)中分析的instantiateUsingFactoryMethod函数一样方法体都非常的大,如果理解了instantiateUsingFactoryMethod中的初始化过程,其实对autowireConstructor函数的理解就不是很难了,该函数主要是确定构造函数参数、构造函数然后调用相应的初始化策略对bean进行初始化,其大部分逻辑都与instantiateUsingFactoryMethod函数一致,如有不明之处,请移步到上篇文章,但需但需注意其中的instantiate是不一样的,这里我们来看下该函数。
instantiate
- private Object instantiate(
- String beanName, RootBeanDefinition mbd, Constructor<?> constructorToUse, Object[] argsToUse) {
- try {
- InstantiationStrategy strategy = this.beanFactory.getInstantiationStrategy();
- if (System.getSecurityManager() != null) {
- return AccessController.doPrivileged((PrivilegedAction<Object>) () ->
- strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse),
- this.beanFactory.getAccessControlContext());
- } else {
- return strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
- }
- } catch (Throwable ex) {
- throw new BeanCreationException(mbd.getResourceDescription(), beanName,
- "Bean instantiation via constructor failed", ex);
- }
- }
分析:
这里对bean进行实例化,会委托SimpleInstantiationStrategy#instantiate方法进行实现:
- public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
- final Constructor<?> ctor, Object... args) {
- // 没有覆盖,则直接使用反射实例化即可
- if (!bd.hasMethodOverrides()) {
- if (System.getSecurityManager() != null) {
- // use own privileged to change accessibility (when security is on)
- // 设置构造方法,可访问
- AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
- ReflectionUtils.makeAccessible(ctor);
- return null;
- });
- }
- // 通过BeanUtils直接使用构造器对象实例化bean对象
- return BeanUtils.instantiateClass(ctor, args);
- } else {
- // CGLIB创建bean对象
- return instantiateWithMethodInjection(bd, beanName, owner, ctor, args);
- }
- }
分析:
- 如果该bean没有配置lookup-method、replace-method或者@LookUp注解,则直接通过反射的方式实例化bean对象即可。
- 如果存在覆盖,则需要使用CGLIB进行动态代理,因为在创建代理的同时可将动态方法织入类中。
#1 通过反射实例化bean对象
- // BeanUtils
- public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
- Assert.notNull(ctor, "Constructor must not be null");
- try {
- // 设置构造方法,可访问
- ReflectionUtils.makeAccessible(ctor);
- // 使用构造方法创建对象
- if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
- return KotlinDelegate.instantiateClass(ctor, args);
- } else {
- Class<?>[] parameterTypes = ctor.getParameterTypes();
- Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
- Object[] argsWithDefaultValues = new Object[args.length];
- for (int i = 0; i < args.length; i++) {
- if (args[i] == null) {
- Class<?> parameterType = parameterTypes[i];
- argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
- } else {
- argsWithDefaultValues[i] = args[i];
- }
- }
- return ctor.newInstance(argsWithDefaultValues);
- }
- }
- // 各种异常,最终统一抛出BeanInstantiationException异常
- catch (InstantiationException ex) {
- throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
- } catch (IllegalAccessException ex) {
- throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
- } catch (IllegalArgumentException ex) {
- throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
- } catch (InvocationTargetException ex) {
- throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
- }
- }
#2 通过CGLIB创建Bean对象,这里会通过其子类实现
- // CglibSubclassingInstantiationStrategy
- protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
- @Nullable Constructor<?> ctor, Object... args) {
- // Must generate CGLIB subclass...
- // 通过CGLIB生成一个子类对象
- return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
- }
分析:
首先创建一个CglibSubclassCreator对象,然后调用其instantiate进行初始化bean对象。
CglibSubclassingInstantiationStrategy#instantiate
- public Object instantiate(@Nullable Constructor<?> ctor, Object... args) {
- // 通过cglib创建一个代理类
- Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
- Object instance;
- // 没有构造器,通过BeanUtils使用默认构造器创建一个bean实例
- if (ctor == null) {
- instance = BeanUtils.instantiateClass(subclass);
- } else {
- try {
- // 获取代理类对应的构造器对象,并实例化bean
- Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
- instance = enhancedSubclassConstructor.newInstance(args);
- } catch (Exception ex) {
- throw new BeanInstantiationException(this.beanDefinition.getBeanClass(),
- "Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex);
- }
- }
- // SPR-10785: set callbacks directly on the instance instead of in the
- // enhanced class (via the Enhancer) in order to avoid memory leaks.
- // 为了避免memory leaks异常,直接在bean实例上设置回调对象
- Factory factory = (Factory) instance;
- factory.setCallbacks(new Callback[]{NoOp.INSTANCE,
- new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
- new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
- return instance;
- }
分析:
这里是通过CGLIB来创建bean对象,这部分内容后续分析AOP的时候再详细分析。
至此通过构造函数自动注入初始化bean对象分析完毕,下面来看使用默认构造函数初始化bean。
#2 AbstractAutowireCapableBeanFactory#instantiateBean
- protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
- try {
- Object beanInstance;
- final BeanFactory parent = this;
- // 安全模式
- if (System.getSecurityManager() != null) {
- // 获得InstantiationStrategy对象,并使用它创建bean对象
- beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () -> getInstantiationStrategy().instantiate(mbd, beanName, parent),
- getAccessControlContext());
- } else {
- // 获得InstantiationStrategy对象,并使用它创建bean对象
- beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
- }
- // 封装BeanWrapperImpl,并完成初始化
- BeanWrapper bw = new BeanWrapperImpl(beanInstance);
- initBeanWrapper(bw);
- return bw;
- } catch (Throwable ex) {
- throw new BeanCreationException(
- mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
- }
- }
分析:
在经历过前面大方法体的分析后,再看该方法就简单得多了,该方法不需要构造参数,所以不需要进行复杂的确定构造参数、构造器等步骤,主要通过instantiate实例化对象后,注入BeanWrapper中,然后初始化BeanWrapper。
SimpleInstantiationStrategy#instantiate
- public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
- // Don't override the class with CGLIB if no overrides.
- // 没有覆盖,直接使用反射实例化即可
- if (!bd.hasMethodOverrides()) {
- Constructor<?> constructorToUse;
- synchronized (bd.constructorArgumentLock) {
- // 获得构造方法 constructorToUse
- constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
- if (constructorToUse == null) {
- final Class<?> clazz = bd.getBeanClass();
- // 如果是接口,则抛出异常
- if (clazz.isInterface()) {
- throw new BeanInstantiationException(clazz, "Specified class is an interface");
- }
- try {
- // 从clazz中,获得构造方法
- if (System.getSecurityManager() != null) {
- constructorToUse = AccessController.doPrivileged(
- (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
- } else {
- constructorToUse = clazz.getDeclaredConstructor();
- }
- // 标记resolvedConstructorOrFactoryMethod属性
- bd.resolvedConstructorOrFactoryMethod = constructorToUse;
- } catch (Throwable ex) {
- throw new BeanInstantiationException(clazz, "No default constructor found", ex);
- }
- }
- }
- // 通过BeanUtils直接使用构造器实例化bean对象
- return BeanUtils.instantiateClass(constructorToUse);
- } else {
- // 通过CGLIB创建子类对象
- // Must generate CGLIB subclass.
- return instantiateWithMethodInjection(bd, beanName, owner);
- }
- }
分析:
该方法也比较简单,通过判断BeanDefinition是否有覆盖方法进行分支:通过反射或CGLIB来创建bean实例。
最后,如果以上分支还不满足,则会通过instantiateBean方法兜底来完成bean的实例化。
总结
终于把AbstractAutowireCapableBeanFactory#createBeanInstance方法大致分析完了,该方法就是选择合适的实例化策略来为bean创建实例对象,具体策略有:
- Supplier回调方式。
- 工厂方法初始化。
- 构造函数自动注入初始化。
- 默认构造函数初始化。
其中工厂方法初始化与构造函数自动注入初始化方式非常复杂,需要大量的经历来确定构造函数和构造参数。
至此createBeanInstance过程已经分析完毕,下面将介绍doCreateBean的其他处理流程。
by Shawn Chen,2019.04.25,下午。
【spring源码分析】IOC容器初始化(十)的更多相关文章
- SPRING源码分析:IOC容器
在Spring中,最基本的IOC容器接口是BeanFactory - 这个接口为具体的IOC容器的实现作了最基本的功能规定 - 不管怎么着,作为IOC容器,这些接口你必须要满足应用程序的最基本要求: ...
- Spring源码解析-ioc容器的设计
Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...
- spring源码分析---IOC(1)
我们都知道spring有2个最重要的概念,IOC(控制反转)和AOP(依赖注入).今天我就分享一下spring源码的IOC. IOC的定义:直观的来说,就是由spring来负责控制对象的生命周期和对象 ...
- spring 源码之 ioc 容器的初始化和注入简图
IoC最核心就是两个过程:IoC容器初始化和IoC依赖注入,下面通过简单的图示来表述其中的关键过程:
- Spring源码阅读-IoC容器解析
目录 Spring IoC容器 ApplicationContext设计解析 BeanFactory ListableBeanFactory HierarchicalBeanFactory Messa ...
- Spring 源码剖析IOC容器(一)概览
目录 一.容器概述 二.核心类源码解读 三.模拟容器获取Bean ======================= 一.容器概述 spring IOC控制反转,又称为DI依赖注入:大体是先初始化bean ...
- Spring源码解析-IOC容器的实现
1.IOC容器是什么? IOC(Inversion of Control)控制反转:本来是由应用程序管理的对象之间的依赖关系,现在交给了容器管理,这就叫控制反转,即交给了IOC容器,Spring的IO ...
- Spring源码解析-IOC容器的实现-ApplicationContext
上面我们已经知道了IOC的建立的基本步骤了,我们就可以用编码的方式和IOC容器进行建立过程了.其实Spring已经为我们提供了很多实现,想必上面的简单扩展,如XMLBeanFacroty等.我们一般是 ...
- Spring源码之IOC容器创建、BeanDefinition加载和注册和IOC容器依赖注入
总结 在SpringApplication#createApplicationContext()执行时创建IOC容器,默认DefaultListableBeanFactory 在AbstractApp ...
随机推荐
- 处理安卓和ios当页面原生键盘弹出,输入框不显示
$('input').on('click', function () { var target = this; // 使用定时器是为了让输入框上滑时更加自然 setTimeout(function() ...
- C#类型(一)
1.System.Object C#的所有类型都是派生自System.Object 也就是说下面的两个类型定义完全一致 // 隐式派生自Object public class Person{ } { ...
- markdown实战问题备忘
问题一:怎么把文档标题放在中间呢? 下面这个能解决问题. 居中: <center>诶嘿</center> 左对齐: <p align="left"&g ...
- 常用Latex公式
注意: 1 在博客中书写Latex公式时,需在公式两侧用$包括 2 大括号{ }在Latex有本身的含义,如果要输出为普通字符需要使用\{...\} 符号 公式 说明 $ \in $ \in 包含 $ ...
- 总结http get和post的区别
这个问题几乎面试的时候都会问到,是一个老生常谈的话题,然而随着不断的学习,对于以前的认识有很多误区,所以还是需要不断地总结的,学而时习之,不亦说乎. 什么是http? get.post常见的区别 ge ...
- 新建项目到Jenkins中
在以Jenkins为镜像创建Docker容器时,我们在jenkins的dockerfile文件中写明了要安装Docker Compose,目的也是在Jenkins容器中借助Docker Compose ...
- Java——容器类库框架浅析
前言 通常,我们总是在程序运行过程中才获得一些条件去创建对象,这些动态创建的对象就需要使用一些方式去保存.我们可以使用数组去存储,但是需要注意数组的尺寸一旦定义便不可修改,而我们并不知道程序在运行过程 ...
- Spring Boot 2.x基础教程:工程结构推荐
Spring Boot框架本身并没有对工程结构有特别的要求,但是按照最佳实践的工程结构可以帮助我们减少可能会遇见的坑,尤其是Spring包扫描机制的存在,如果您使用最佳实践的工程结构,可以免去不少特殊 ...
- jQuery里面的常用的事件和基础动画的实现
一:了解jquery里面常用的事件 二:了解基础动画的实现 1:加载DOM 在JavaScript中使用window.onload事件作为窗体加载事件(它在页面所有数据加载完成之后才会执行) 在jQu ...
- C# 绘制PDF嵌套表格
嵌套表格,即在一张表格中的特定单元格中再插入一个或者多个表格,使用嵌套表格的优点在于能够让内容的布局更加合理,同时也方便程序套用.下面的示例中,将介绍如何通过C#编程来演示如何插入嵌套表格到PDF文档 ...