spring 自定义属性解析器spring web 5.28

自定义属性解析器

org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory

beanFactory.setBeanClassLoader(getClassLoader());
//设置EL表达式解析器(${})
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
//设置默认的属性解析器
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

PropertyEditorSupport属性编辑器

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
               <entry key="cn.com.luofu.selfEditor.Address" value="cn.com.luofu.selfEditor.AddressPropertyEditor"></entry>
        </map>
    </property>
</bean>
//属性编辑器注入
org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors->invokeBeanFactoryPostProcessors->invokeBeanFactoryPostProcessors->postProcessBeanFactory{
    //beanfactory中customEditors的map中添加对应属性解析器
    ...this.customEditors.forEach(beanFactory::registerCustomEditor{
        ...this.customEditors.put(requiredType, propertyEditorClass)...
    })
}
//实例化bean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean{
    ...instanceWrapper = createBeanInstance{
        ...instantiateBean{
            //BeanWrapperImpl extends AbstractNestablePropertyAccessor extends AbstractPropertyAccessor extends TypeConverterSupport extends PropertyEditorRegistrySupport 看出 BeanWrapperImpl 继承 PropertyEditorRegistrySupport
            ...BeanWrapper bw = new BeanWrapperImpl
                initBeanWrapper(bw){
                registerCustomEditors(bw){
                    //这里registry为传进来的bw,this为beanfactory(上面自定义解析器已经添加到了工厂)
                    ...this.customEditors.forEach((requiredType, editorClass) ->
					registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass))){
                        //PropertyEditorRegistrySupport属性customEditors加了自定义属性解析器
                       ...this.customEditors.put(requiredType, propertyEditor)...
                    }
                }
            }...
        }...
    }...
}
//属性编辑器获取
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean{
    ...applyPropertyValues{
        //处理value为依赖bean包括循环依赖
        ...valueResolver.resolveValueIfNecessary...
        ...convertForProperty->convertForProperty->convertForProperty->convertIfNecessary->convertIfNecessary{
            ...PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor{
                //从之前放进去的PropertyEditorRegistrySupport#findCustomEditor
                ...getCustomEditor{
                    //取出对应的edit如AddressPropertyEditor
                    ...this.customEditors.get(requiredType)...
                }...
            }...
            //对应的edit属性解析器解析
            ...doConvertValue->doConvertTextValue->editor.setAsText
        }...
    }...
}

ConversionService属性编辑器

conversionService(ConversionServiceFactoryBean,含属性GenericConversionService)创建过程 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean->invokeInitMethods->afterPropertiesSet{ //添加默认的属性转换器,大部分是转为string this.conversionService = createConversionService(){ ...return new DefaultConversionService()... } //注册自定义converter(this.converters通过xml注入的) ConversionServiceFactory.registerConverters(this.converters, this.conversionService){ ...else if (converter instanceof Converter) { //ConverterRegistry中添加属性解释器 registry.addConverter{ //GenericConversionService属性converters中添加解析器 ...addConverter... }... } }

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#convertForProperty->convertForProperty...->convertIfNecessary..->canConvert->conversionService.convert //从GenericConversionService属性converters中获取解析器,自定义convert需要实现Converter接口。如:StudentConverter implements Converter<String,Student>

Spel 表达式

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues{
    //convertForProperty 上面两个解析器 在el处理之后在处理
    ...resolveValueIfNecessary{
        ...else if (value instanceof TypedStringValue) {
            ...evaluate{
                ...doEvaluate->evaluateBeanDefinitionString->evaluate{
                    //el 解析器解析
                    ...this.expressionParser.parseExpression{
                        ...parseTemplate{
                            ...parseExpressions{
                                ... //#{和} 解析#{person.hobit}
                                    String prefix = context.getExpressionPrefix();
		                            String suffix = context.getExpressionSuffix()...
                            }...
                        }...
                    }...
                    return expr.getValue(sec){
                        ...getValue->getValueInternalgetValueRef->getValueInternal->getValueInternal->readProperty->read->getObject->getBean(key) @1
                    }
                }...
            }...
        }
    }...
}
// @1 将person.hobit拆分person和hobit分别解析
org.springframework.beans.factory.config.BeanExpressionContext#getObject{
    //key: person,然后通过反射获取属性值org.springframework.expression.spel.support.ReflectivePropertyAccessor.OptimalPropertyAccessor#read->method.invoke(target)
    ...this.beanFactory.getBean(key)...
}

循环依赖

<bean name="person" class="cn.com.luofu.dto.Person">
    <property name="hobit" value="zzz"></property>
    <property name="film"  ref="film"></property>
</bean>

<bean name="film" class="cn.com.luofu.el.Film">
    <property name="name" value="lisi"></property>
    <property name="person"  ref="person"></property>
</bean>
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean{
... // Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName){
			...getSingleton(beanName, true){
				//依次从一二三级缓存获取singletonObjects、earlySingletonObjects、singletonFactories。此时bean还未标记singletonsCurrentlyInCreation(方法后面的getSingleton(beanName, () ->这个方法会标记)返回的为null.如果标记了接下来的代码会从二级缓存拿值放到二级缓存
			}...
				...getSingleton(beanName,()...){
					...beforeSingletonCreation{
						...this.singletonsCurrentlyInCreation.add(beanName)...
					}...
					...getObject{
						//lumbdar 
						...doCreateBean{
							//判读支持循环引用后(含标记singletonsCurrentlyInCreation为true)
							...addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)){
								//实例化后放到三级缓存
								...if (!this.singletonObjects.containsKey(beanName)) {
								this.singletonFactories.put(beanName, singletonFactory);
									this.earlySingletonObjects.remove(beanName);
									this.registeredSingletons.add(beanName);
								}...
							}...
						}...
					}...
					...afterSingletonCreation{
						...this.singletonsCurrentlyInCreation.remove(beanName)...
					}...
					...addSingleton{
						//实例化完成后放到一级缓存,从二级、三级缓存移除
						...this.singletonObjects.put(beanName, singletonObject);
							this.singletonFactories.remove(beanName);
							this.earlySingletonObjects.remove(beanName);
							this.registeredSingletons.add(beanName);...
					}...
				}...
		}...
}

getbean("person")->

<mvc:resources mapping="/static/**" location="/script/"></mvc:resources>

对应解析文件配置:D:\repo\org\springframework\spring-webmvc\5.2.8.RELEASE\spring-webmvc-5.2.8.RELEASE.jar!\META-INF\spring.handlers
内容:
http://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler

registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
org.springframework.web.servlet.config.ResourcesBeanDefinitionParser#parse{
...registerResourceHandler{
//添加了ResourceHttpRequestHandler处理器并且交给spring管理
...new RootBeanDefinition(ResourceHttpRequestHandler.class)...
...context.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef)...
}...
//添加了SimpleUrlHandlerMapping处理器并且交给spring管理
...handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class)...
//urlMap内容来自标签属性mapping,后续实例化bean:SimpleUrlHandlerMapping时populate会放到映射map(处理请求)中去。urlMap.put(resourceRequestPath(/static/**), resourceHandlerName(ResourceHttpRequestHandler));
...handlerMappingDef.getPropertyValues().add("urlMap", urlMap)...
}

org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0
initializeBean->postProcessBeforeInitialization->invokeAwareInterfaces->setApplicationContext->initApplicationContext->..->registerHandlers->

SimpleUrlHandlerMapping 处理常规的对象然后返回source(org.springframework.web.servlet.resource.ResourceHttpRequestHandler#handleRequest->getResource),判断返回的source 流是否为空判断是否找到资源
requestmap.... 返回modelandview

org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle->handleInternal->invokeHandlerMethod->invokeAndHandle->handleReturnValue->selectHandler->supportsReturnType其中对于ViewNameMethodReturnValueHandler#supportsReturnType{
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}支持return void或者String.
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler#handleReturnValue{
//mavContainer为ModelAndViewContainer 返回的为mavContainer.getView()
...mavContainer.setViewName(viewName)...
}
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod{
...invokeAndHandle...
...return getModelAndView(mavContainer, modelFactory, webRequest){
...ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus())...
...return mav...
}...
}
Java中的值传递与引用传递
在Java中,参数传递机制可以分为两种类型:值传递(Value Passing)和引用传递(Reference Passing)。理解这两种机制对于正确地使用Java非常重要,尤其是在处理基本类型和对象时。

  1. 值传递(Value Passing)
    当方法参数是基本类型(如int、char、boolean等)时,Java使用值传递。这意味着当一个方法被调用时,实际参数的值被复制并传递给方法的形参。在方法内部对形参的任何修改都不会影响到实际参数的值。
    示例:
public class ValuePassingExample {
    public static void main(String[] args) {
        int x = 10;
        modifyValue(x);
        System.out.println(x); // 输出10,因为x的值没有改变
    }

    public static void modifyValue(int y) {
        y = 20; // 这里修改的是y的值,不会影响到x
    }
}
  1. 引用传递(Reference Passing)
    当方法参数是对象(如String、List、Map等)时,Java使用引用传递。这意味着当一个方法被调用时,实际参数的引用(即对象在内存中的地址)被传递给方法的形参。在方法内部对形参的任何修改都会影响到实际参数,因为它们引用的是同一个对象。
    示例:
public class ReferencePassingExample {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        modifyList(list);
        System.out.println(list); // 输出[1, 2],因为list的引用被传递,修改了同一个对象
    }

    public static void modifyList(List<Integer> list) {
        list.add(2); // 这里修改的是同一个list对象
    }
}
  1. 对象的引用传递与对象的复制
  2. 需要注意的是,虽然Java在处理对象时使用引用传递,但这并不意味着方法内部可以改变实际参数引用的对象本身。如果在方法内部创建了一个新的对象并将其赋值给形参,这实际上是在创建一个新的对象,不会影响到实际参数引用的对象。
public class ObjectPassingExample {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        modifyObject(obj);
        System.out.println(obj.getValue()); // 输出10,因为obj引用的对象没有改变
    }

    public static void modifyObject(MyClass obj) {
        obj = new MyClass(); // 这里创建了一个新的对象,不会影响到实际参数obj
        obj.setValue(20);
    }
}

class MyClass {
    private int value;

    public MyClass() {
        this.value = 10;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}
  1. 总结
    基本类型:在Java中,基本类型的参数传递是值传递,方法内部对形参的修改不会影响实际参数。
    对象:在Java中,对象的参数传递是引用传递,方法内部对形参的修改会影响实际参数,因为它们引用的是同一个对象。但是,如果在方法内部创建了新的对象并将其赋值给形参,这不会影响实际参数引用的对象。

理解引用传递
在Java中,对象存储在堆内存中,而引用则存储在栈内存中。当一个对象被创建时,它在堆内存中分配空间,并返回一个引用,这个引用指向堆内存中的对象。当我们将对象作为参数传递给方法时,实际上传递的是这个引用的副本,而不是对象本身。

实际参数与形参的关系
同一对象:实际参数和形参引用的是堆内存中同一个对象,因此对形参的任何修改都会影响到实际参数引用的对象。
引用的副本:虽然实际参数和形参引用的是同一个对象,但它们是不同的引用。这意味着在方法内部,如果创建了一个新的对象并将其赋值给形参,这不会影响实际参数引用的对象,因为实际参数和形参仍然是独立的引用。

this关键字在Java中用于引用当前对象的实例。它可以在类的方法中使用,以访问该对象的属性和方法。当一个方法被调用时,this关键字指向调用该方法的对象。当一个方法内部调用另一个方法时(同一个类中方法),即使被调用的方法内部也使用了this关键字,它仍然引用的是最初调用链中方法的那个对象。这是因为this关键字的含义是在方法调用的上下文中确定的,而不是在方法的嵌套调用中。当methodA和methodB位于不同的类中时,this关键字在每个方法中引用的是调用该方法的对象。这意味着在methodB中,this关键字引用的是调用methodB的那个对象,而不是调用methodA的对象。

public class ClassA {
    private int valueA;

    public ClassA(int valueA) {
        this.valueA = valueA;
    }

    public void methodA() {
        ClassB objB = new ClassB(20);
        objB.methodB();
    }
}

public class ClassB {
    private int valueB;

    public ClassB(int valueB) {
        this.valueB = valueB;
    }

    public void methodB() {
        System.out.println("methodB: " + this.valueB);
    }
}

public class Main {
    public static void main(String[] args) {
        ClassA objA = new ClassA(10);
        objA.methodA(); // 输出: methodB: 20
    }
}

在上面的示例中,methodA创建了一个ClassB的实例,并调用了methodB。在methodB中,this关键字引用的是
ClassB的实例,即objB。因此,methodB中的this.valueB输出的是20,这是ClassB实例的valueB属性的值,而不是ClassA实例的任何属性。

代码解释:1L << 2
这条代码表示将数字1左移2位,其中1L表示一个长整型(long)数字1,<<是左移运算符。让我们详细解释这条代码:
1L:在Java中,L或l(小写L)是长整型(long)字面量的后缀,用于表示这个数字是一个长整型数字。在本例中,1L表示一个值为1的长整型数字。
<<:这是左移运算符,它将操作数左侧的二进制位向左移动指定的位数。在本例中,1L << 2表示将1L的二进制位向左移动2位。
二进制表示
在二进制中,数字1表示为00000000000000000000000000000001(对于long类型,它有64位,这里只显示了最后的几位)。当我们将这个数字左移2位时,它会变成00000000000000000000000000000100。在十进制中,这个结果等于4。

总结
1L << 2这条代码的结果是将数字1左移2位,得到的结果是4。在计算机科学和编程中,位移运算符(左移<<和右移>>)经常用于位操作和位级优化,例如在位图数据结构中设置或清除特定的位,或者在低级别编程中用于快速乘除2的幂次方。

在标准的ASCII编码中,每个字符使用7位进行编码,这意味着在8位(一个字节)的二进制表示中,最高位(即最左边的一位)通常是0。这是因为标准ASCII编码只使用了从0到127的数值范围,这在7位二进制中就足够表示了。

在计算机中,有符号整数通常使用补码(Two's Complement)表示法来表示。补码表示法可以简化加法和减法运算,同时也能有效地表示负数。

ASCII码的二进制表示与补码表示不同。补码主要用于表示有符号整数,特别是负数,在计算机的算术运算中使用。而ASCII码表示的是字符,没有正负之分,因此不需要使用补码表示。

两个字节2^16 = 65536可以表示65536个不同的编码。这意味着,使用两个字节,可以表示从0到65535的65536个不同的数值或状态。

Unicode编码标准使用多个字节来表示字符,但是,使用两个字节(16位)表示的字符范围并不是从-65536到32765。Unicode编码标准中,两个字节(16位)的表示范围是0到65535,即从U+0000到U+FFFF。

在Unicode编码中,字符编码值没有负数。Unicode编码值(也称为码点)都是非负整数,用于唯一标识一个字符。码点的范围从U+0000(零)到U+10FFFF(最后一个定义的字符)。

在ASCII编码、GBK编码、Unicode编码等不同的字符编码标准中,编码值的二进制表示方式和是否使用最高位作为符号位有所不同。ASCII编码和Unicode编码的最高位不是符号位,而GBK编码的最高位用于区分单字节和双字节字符,也不是符号位。字符编码标准通常不使用最高位作为符号位,因为字符编码值通常都是非负数,符号位主要用于有符号整数的表示。

表达式2-1在计算机中首先被计算为1,然后以二进制形式存储。整数1的二进制表示为0001,字符串 '2-1' 在计算机中存储时,会被视为一系列字符的序列,每个字符根据所使用的字符编码标准(如ASCII、UTF-8等)转换为对应的二进制表示。以下以ASCII编码为例,说明字符串 '2-1' 的二进制存储方式。

  1. 字符串 '2-1' 的ASCII编码
    字符 '2':在ASCII编码中,字符 '2' 的十进制值为50,其二进制表示为 00110010。
    字符 '-':在ASCII编码中,字符 '-' 的十进制值为45,其二进制表示为 00101101。
    字符 '1':在ASCII编码中,字符 '1' 的十进制值为49,其二进制表示为 00110001。
  2. 将每个字符的二进制表示连接起来,得到字符串 '2-1' 的二进制表示。注意,每个字符的二进制表示通常占据一个字节(8位)。
    字符串 '2-1' 的二进制表示:00110010 00101101 00110001

GB2312是一种用于表示中文字符的编码标准,它主要在中国大陆使用。GB2312编码使用1到2个字节来表示字符,其中单字节字符(通常为ASCII字符)和双字节字符的最高位的使用方式有所不同。

在GB2312编码中,单字节字符的最高位为0,而双字节字符的最高位为1。最高位为1的设计用于区分单字节字符和双字节字符,确保了GB2312编码与ASCII编码的兼容性。对于双字节字符,最高位不能为0,因为这将导致无法区分单字节和双字节字符,从而破坏了编码的正确性和兼容性。

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是一种用于表示文本的字符编码标准,最初设计用于英文字符集。ASCII编码表包含128个不同的字符,这些字符包括:
控制字符:ASCII编码的前32个字符(从0到31,以及127)是控制字符,用于控制数据流和设备操作,如回车、换行、退格等。
可打印字符:ASCII编码的第32个字符到第127个字符(不包括127)是可打印的字符,包括数字、大写和小写字母、标点符号以及一些特殊字符。
ASCII编码的可打印字符
数字:0-9(ASCII值48-57)。
大写字母:A-Z(ASCII值65-90)。
小写字母:a-z(ASCII值97-122)。
标点符号和特殊字符:包括空格、标点符号、美元符号、百分号等。

跨域请求被阻止的机制
跨域请求被阻止主要是由浏览器的安全策略——同源策略(Same-Origin Policy)和CORS(Cross-Origin Resource Sharing)机制共同作用的结果。以下是详细的解释:
同源策略(Same-Origin Policy)
同源策略是浏览器的一个安全策略,它限制了从一个源加载的脚本如何与另一个源交互。一个“源”由协议、域名和端口号三部分组成。例如,http://example.com:80https://example.com:443 被视为不同的源。
当一个源的脚本尝试访问另一个源的资源时,同源策略会阻止这个请求,除非满足特定的条件,如CORS。
CORS(Cross-Origin Resource Sharing)
CORS机制允许服务器明确指定哪些源可以访问其资源,从而放宽了同源策略的限制。CORS通过响应头来控制跨域访问,主要涉及以下响应头:
Access-Control-Allow-Origin:指定哪些源可以访问资源。
Access-Control-Allow-Methods:指定允许的HTTP方法。
Access-Control-Allow-Headers:指定允许的请求头。
Access-Control-Allow-Credentials:指定是否允许携带凭据(如Cookie)的跨域请求。
如果服务器没有正确设置这些CORS响应头,或者设置的值与请求不匹配,浏览器将阻止跨域请求。

预检请求(Preflight Request)
对于某些HTTP方法(如PUT、DELETE)或包含自定义请求头的跨域请求,浏览器会先发送一个预检请求(通常是OPTIONS方法)来检查服务器是否允许该跨域请求。预检请求中,浏览器会检查服务器是否返回了正确的CORS响应头。
请求被阻止的场景
如果服务器没有设置CORS响应头,或者设置的值与请求不匹配(如源、方法或请求头不匹配),浏览器会阻止跨域请求,并在控制台中显示错误信息,如“XMLHttpRequest cannot load URL due to Access-Control-Allow-Origin header”。

跨域请求被阻止主要是由浏览器的同源策略和CORS机制共同作用的结果。浏览器会检查跨域请求是否满足CORS响应头中指定的条件,如果不符合,请求将被阻止,以防止潜在的安全风险,如跨站脚本(XSS)攻击。为了允许跨域访问,服务器需要正确设置CORS响应头,以满足浏览器的安全策略。

CorsFilter
com.example.CorsFilter

CorsFilter /*

public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Initialization logic
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) {
res.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
// Cleanup logic
}
}
浏览器发送预检请求的条件
浏览器并不会对所有请求都发送预检请求(Preflight Request)。预检请求是CORS(Cross-Origin Resource Sharing)机制的一部分,主要用于处理特定类型的跨域请求。以下是一些关键条件,当满足这些条件时,浏览器会发送预检请求:
跨域请求
预检请求仅在跨域请求中发生,即请求的源(由协议、域名和端口号组成)与请求的目标源不同。
请求方法
如果请求方法是 GET、HEAD 或 POST,并且 POST 请求不包含自定义请求头,那么浏览器不会发送预检请求。对于 POST 请求,如果包含自定义请求头,浏览器会发送预检请求。
对于 PUT、DELETE、CONNECT、OPTIONS 和 TRACE 等HTTP方法,浏览器会发送预检请求。
自定义请求头
如果请求包含了除简单请求头(如 Accept、Accept-Language、Content-Language 和 Last-Event-ID)之外的自定义请求头,浏览器会发送预检请求。

请求体类型
对于 POST 请求,如果请求体是简单类型(如 application/x-www-form-urlencoded、multipart/form-data 或 text/plain),浏览器不会发送预检请求。如果请求体类型是其他类型,如 application/json 或 text/xml,浏览器会发送预检请求。
预检请求的细节
请求方法:预检请求使用 OPTIONS 方法。
请求头:预检请求会包含 Access-Control-Request-Method 和 Access-Control-Request-Headers 头,分别表示实际请求将要使用的方法和自定义请求头。
响应头:服务器需要在预检请求的响应中包含 Access-Control-Allow-Origin、Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 等CORS响应头,以表明允许的源、方法和请求头。
总结
浏览器仅在特定类型的跨域请求中发送预检请求,主要是为了处理非简单请求(如 PUT、DELETE 方法或包含自定义请求头的请求)。预检请求的目的是确保服务器允许该跨域请求,并且请求的类型和请求头是被服务器接受的。通过预检请求,浏览器可以避免不必要的跨域请求失败,提高跨域资源共享的安全性和效率。

自定义请求头是在HTTP请求中除了标准定义的请求头之外,由开发者自行定义的请求头。这些头允许开发者在请求中传递额外的元数据或控制信息,以满足特定的应用需求或与服务器进行更复杂的交互。自定义请求头可以用于各种目的,包括但不限于:
传递应用特定的标识信息
应用程序可能需要在请求中传递一些标识信息,如应用的版本号、用户ID或会话ID,以便服务器能够识别请求的来源或进行更精细的权限控制。
控制服务器的行为
自定义请求头可以用于控制服务器如何处理请求,例如,指示服务器使用特定的缓存策略、数据格式或安全设置。

优化性能
通过自定义请求头,客户端可以向服务器传递有关其能力或偏好信息,如支持的压缩算法、语言偏好或设备类型,以优化数据传输和用户体验。
调试和监控
自定义请求头可以用于调试和监控目的,例如,添加追踪ID或性能指标,以帮助开发者分析请求的性能或追踪请求的路径。
安全性
自定义请求头可以用于增强安全性,例如,通过传递签名或加密的令牌来验证请求的来源或完整性。
自定义请求头的命名规则
自定义请求头的名称通常遵循一定的命名规则,以避免与标准的HTTP请求头冲突。根据HTTP/1.1规范(RFC 2616),自定义请求头的名称应该以X-前缀开始,例如X-My-Header。然而,在HTTP/2和HTTP/3中,X-前缀不再是强制要求,但仍然被广泛使用,以保持与现有系统的兼容性。

示例
以下是一个使用自定义请求头的示例:

// JavaScript (Fetch API)
fetch('https://api.example.com/data', {
  method: 'GET',
  headers: {
    'X-My-Custom-Header': 'my-value',
    'X-Another-Custom-Header': 'another-value'
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));

在这个示例中,X-My-Custom-Header 和 X-Another-Custom-Header 是自定义请求头,它们被添加到Fetch API的请求中,用于传递额外的元数据或控制信息。

线程池的配置
核心线程数(corePoolSize):这是线程池中始终会保持的线程数量,即使没有任务执行,这些线程也不会被释放,除非线程池被关闭。
最大线程数(maximumPoolSize):这是线程池中可以创建的最大线程数量,当任务数量超过核心线程数时,线程池会创建额外的线程来处理任务,直到达到最大线程数。当没有任务执行时,这些额外的线程可能会被释放,具体取决于线程的空闲时间(keepAliveTime)和线程池的工作策略。
线程的空闲时间(keepAliveTime)
这是线程在没有任务执行时可以保持空闲的最大时间,如果线程的空闲时间超过了这个时间,那么这个线程可能会被释放。但是,如果线程的数量没有超过核心线程数,那么这些线程不会被释放

读锁、写锁和行锁、表锁的关系
读锁和写锁是根据事务对数据的访问类型来划分的,读锁用于读取数据,写锁用于修改数据。
行锁和表锁是根据锁的范围来划分的,行锁用于锁定特定行,表锁用于锁定整个表。
在实际使用中,读锁和写锁可以与行锁或表锁结合使用。例如,读锁可以与行锁结合使用,形成读行锁(Read Row Lock),用于读取特定行的数据;写锁可以与行锁结合使用,形成写行锁(Write Row Lock),用于修改特定行的数据;读锁可以与表锁结合使用,形成读表锁(Read Table Lock),用于读取整个表的数据;写锁可以与表锁结合使用,形成写表锁(Write Table Lock),用于修改整个表的数据。
总的来说,读锁、写锁和行锁、表锁是MySQL中用于控制并发事务对数据访问的两种不同类型的锁,它们可以结合使用,以满足不同的并发控制需求。

行锁:Indo才有 ,尽量不用读锁、写锁这两种方式去加锁,因为InnoDB的优点就是行锁,所以尽量使用行锁,性能更高。

在数据库事务的隔离级别中,REPEATABLE READ(可重复读)级别可以解决PHANTOM READ(幻读)问题,但这取决于具体的数据库系统和存储引擎。
在REPEATABLE READ隔离级别下,对于同一个事务,多次读取同一范围的数据将返回相同的记录集,即使在两次读取之间有其他事务插入了新的记录。这是因为REPEATABLE READ级别使用了Next-Key Locking(下一个键锁定)机制,它不仅锁定被读取的行,还锁定了这些行之间的间隙,防止其他事务在这些间隙中插入新的行,从而避免了幻读。
但是,需要注意的是,REPEATABLE READ隔离级别并不能完全避免幻读,它只能避免在同一个事务中的幻读。如果在两个不同的事务中,第一个事务读取了一范围的数据,第二个事务在第一个事务读取后插入了新的行,然后第一个事务再次读取同一范围的数据,那么第一个事务将看到第二个事务插入的新行,这仍然是一种幻读。
因此,如果要完全避免幻读,可能需要使用更高级别的隔离级别,如SERIALIZABLE(串行化),或者在应用程序中使用其他的技术,如乐观锁或悲观锁。
总的来说,REPEATABLE READ隔离级别可以解决在同一个事务中的幻读问题,但不能解决在不同事务中的幻读问题。
在数据库中,"这些行之间的间隙"通常指的是在已存在的行之间的空间,或者说是两个已存在的行之间的值域。在数据库的存储引擎中,数据通常是以某种形式的索引树(如B树或B+树)来存储的,每个节点包含多个键值对,每个键值对对应一个行。
在B+树中,键值对是按照键的顺序来排序的,因此,两个键值对之间的值域可以看作是这两个键值对之间的"间隙"。例如,假设我们有一个按照数值顺序排序的B+树,其中包含键值对(1,a),(3,b),(5,c),那么,(1,a)和(3,b)之间的"间隙"就是值域(1,3),(3,b)和(5,c)之间的"间隙"就是值域(3,5)。
在REPEATABLE READ隔离级别下,当一个事务读取了一行数据,那么这个事务不仅会锁定这一行,还会锁定这一行和下一行之间的"间隙",防止其他事务在这些"间隙"中插入新的行,从而避免了幻读。例如,如果一个事务读取了键值对(3,b),那么这个事务不仅会锁定键值对(3,b),还会锁定键值对(3,b)和(5,c)之间的"间隙",即值域(3,5),防止其他事务在值域(3,5)中插入新的行。
总的来说,"这些行之间的间隙"指的是在已存在的行之间的空间,或者说是两个已存在的行之间的值域,REPEATABLE READ隔离级别下的Next-Key Locking机制会锁定这些"间隙",防止其他事务在这些"间隙"中插入新的行,从而避免了幻读。

Java集合可以分为Collection和Map两种体系

其中Collection是单列数据,定义了存储一组对象的方法的集合
Collection又可以分为List和Set:

而Map是双列数据,保存具有映射关系key-value对的集合。

Iterator对象称为迭代器,主要用于遍历Collection集合中的元素.

Map集合可以通过Iterator迭代。但是,由于Map是一个键值对的集合,因此,你不能直接使用Iterator迭代Map,而是需要先获取Map的keySet()、values()或entrySet(),然后使用Iterator迭代这些集合。

HashMap是Map接口的一个实现类,它使用哈希表(Hash Table)作为其数据结构。哈希表是一种基于数组的数据结构,它使用哈希函数将键映射到数组的索引,然后在该索引处存储键值对。HashMap在添加键值对时,会计算键的哈希值,然后将键值对存储在哈希值对应的数组位置(非顺序的),如果多个键的哈希值相同,那么这些键值对将形成一个链表。HashMap在查找键值对时,会再次计算键的哈希值,然后在哈希值对应的数组位置查找键值对,如果存在链表,那么将在链表中查找键值对

在MySQL中,binlog(二进制日志)、redolog(重做日志)和undolog(undo日志)是三种用于保证数据一致性和持久性的日志,它们各自有不同的作用和区别:
binlog(二进制日志 事务提交后记录)
binlog是MySQL的二进制日志,它记录了所有更改数据库数据的SQL语句,包括数据的插入、更新和删除操作。binlog主要用于数据恢复和主从复制,当数据库发生故障时,可以通过binlog恢复数据;当需要将数据从主数据库复制到从数据库时,也可以通过binlog实现。
redolog(重做日志)
redolog是MySQL的重做日志,它记录了所有更改数据库数据的物理操作,包括数据的插入、更新和删除操作。redolog主要用于数据恢复,当数据库发生故障时,可以通过redolog恢复数据。redolog是物理日志,它记录的是数据页的物理变化,因此,即使在数据页没有被提交的情况下,也可以通过redolog恢复数据。
undolog(undo日志)
undolog是MySQL的undo日志,它记录了所有更改数据库数据的逆向操作,即如果一个操作更改了数据,那么undolog会记录如何将数据恢复到更改前的状态。undolog主要用于事务的回滚和可重复读隔离级别的实现,当一个事务需要回滚时,可以通过undolog恢复数据;当需要实现可重复读隔离级别时,也可以通过undolog实现。
总的来说,binlog、redolog和undolog是三种用于保证数据一致性和持久性的日志,它们各自有不同的作用和区别。binlog主要用于数据恢复和主从复制,redolog主要用于数据恢复,undolog主要用于事务的回滚和可重复读隔离级别的实现。

binlog:存储在磁盘上,是顺序写入的,因此,写入速度较快,但是,由于binlog是顺序写入的,因此,如果binlog文件过大,可能会导致binlog的读取速度较慢。
**redolog:存储在磁盘上,是循环写入的,因此,写入速度较慢,但是,由于redolog是循环写入的,因此,redolog`的大小是固定的,不会因为日志的写入而无限增大。
总的来说,binlog和redolog是MySQL中两种不同的日志,它们在作用、内容和存储方式上都有所区别。binlog主要用于数据恢复和主从复制,redolog主要用于数据恢复;binlog记录的是SQL语句,redolog记录的是数据页的物理变化;binlog是顺序写入的,redolog是循环写入的。

filesort仅仅表示没有使用索引的排序

在XML中,命名空间(Namespace)是一种用于区分具有相同名称但来自不同源的元素和属性的方法。在大型系统或多个组织共享数据的情况下,命名空间可以防止命名冲突。命名空间通过xmlns属性来声明。在XML中,xmlns:ns1这样的声明是用来定义一个命名空间前缀(namespace prefix)的。这里的ns1就是一个命名空间前缀。
命名空间前缀提供了一种在XML文档中引用命名空间URI的方式,而不需要每次都写出完整的URI。xmlns是一个保留的前缀,它总是用于声明命名空间,而不能用于其他目的。但是,除了xmlns之外,你可以使用任何字符串作为命名空间前缀。xmlns:xsi是一个特殊的命名空间声明,它用于引用XML Schema instance (XSI)命名空间。XSI命名空间定义了一组用于在XML文档中声明和使用模式的属性,这些属性可以帮助验证XML文档的结构和内容。
xmlns:xsi声明通常会和xsi:schemaLocation或xsi:noNamespaceSchemaLocation属性一起使用,用于指定验证XML文档的模式文件的位置。xsi是一个命名空间前缀,它用于在XML文档中引用XSI命名空间。xmlns:xsi声明定义了这个前缀,然后你就可以在XML文档中使用xsi:schemaLocation或xsi:noNamespaceSchemaLocation这样的属性了。xsi:schemaLocation是XML Schema实例(XSI)命名空间中的一个属性,用于指定一个或多个模式文档的位置,这些模式文档定义了XML文档的结构和数据类型。
xsi:schemaLocation属性的值是一个空格分隔的列表,列表中的每一对值分别是一个目标命名空间和一个模式文档的URL

Tomcat服务器的默认最大连接数(也称为最大线程数)是由maxThreads参数决定的,这个参数可以在Tomcat的server.xml配置文件的Connector元素中设置。在Tomcat 8和更高版本中,maxThreads参数的默认值是200,这意味着Tomcat服务器默认可以同时处理200个请求。但是,这个值可能会根据你的操作系统和硬件配置进行调整。需要注意的是,maxThreads参数的值并不是越大越好,如果设置的值过大,可能会导致服务器的资源(如内存和CPU)耗尽,从而影响服务器的性能和稳定性。因此,你应该根据你的应用程序的需求和你的服务器的资源来合理设置maxThreads参数的值。在Tomcat中,每个连接并不一定需要一个独立的线程来处理。Tomcat使用了一个线程池来处理HTTP请求,这个线程池的大小由maxThreads参数决定。当一个新的请求到达时,Tomcat会从线程池中分配一个线程来处理这个请求。如果线程池中的线程都被分配出去了,那么新的请求将被放入一个队列中等待,直到线程池中有空闲的线程。Tomcat还提供了一个acceptCount参数,用于设置等待队列的大小。如果线程池中的线程都被分配出去了,那么新的请求将被放入等待队列中,直到线程池中有空闲的线程。如果等待队列也满了,那么新的请求将被拒绝。因此,acceptCount参数的值也会影响最大连接数。Tomcat的线程池模型与Java的Executor框架的线程池模型有所不同,它并不明确区分核心线程和非核心线程。
在Java的Executor框架中,线程池通常有一个核心线程数和一个最大线程数。核心线程会一直保持活跃,即使没有任务执行,它们也不会被销毁。当线程池中的线程数超过核心线程数时,多余的线程被称为非核心线程,它们在空闲一段时间后会被销毁。
但是,在Tomcat中,线程池的模型更简单。Tomcat的线程池只有一个最大线程数,由maxThreads参数决定。当一个新的请求到达时,Tomcat会从线程池中分配一个线程来处理这个请求。如果线程池中的线程都被分配出去了,那么新的请求将被放入一个队列中等待,直到线程池中有空闲的线程。
并发(Concurrency)
并发是指在一段时间内,多个任务或进程看起来像是同时进行的。在计算机系统中,由于CPU的执行速度非常快,它可以在多个任务之间快速切换,使得这些任务看起来像是同时进行的。但是,实际上,CPU在任何时刻只能执行一个任务,它只是在多个任务之间快速切换,使得这些任务看起来像是同时进行的。这种技术被称为时间分片(Time Slicing)。
并发通常用于描述操作系统或程序设计中的任务调度策略,例如,多线程编程就是一种实现并发的方式。
并行(Parallelism)
并行是指在真正的同一时刻,多个任务或进程同时进行。在计算机系统中,只有在有多个处理器或多个核心的情况下,才能实现真正的并行。在这种情况下,每个处理器或核心可以同时执行一个任务,从而实现真正的并行。
并行通常用于描述计算机硬件的架构,例如,多核处理器就是一种实现并行的方式。
总的来说,并发和并行的主要区别在于,并发是指在一段时间内,多个任务或进程看起来像是同时进行的,而并行是指在真正的同一时刻,多个任务或进程同时进行。并发通常用于描述操作系统或程序设计中的任务调度策略,而并行通常用于描述计算机硬件的架构。

JSON数据通常由键值对组成,这些键值对被组织成对象,对象可以嵌套在其他对象中,形成复杂的数据结构。键值对中的键必须是字符串,而值可以是字符串、数字、布尔值、null、数组或另一个对象。在JSON数据中,值为列表(list)类型的对象可以表示为一个数组。数组在JSON中使用方括号[]表示,数组中的元素用逗号,分隔。每个元素可以是任何JSON数据类型,包括字符串、数字、布尔值、null、对象或另一个数组。数组在JSON中使用方括号[]表示,用于表示一个有序的元素集合。数组中的元素可以是任何JSON数据类型,包括字符串、数字、布尔值、null、对象或另一个数组。数组中的元素是无名的,它们的位置(即索引)决定了它们的顺序。数组在JSON中通常用于表示列表、集合或序列等数据结构。
对象在JSON中使用大括号{}表示,用于表示一个无序的键值对集合。对象中的键必须是字符串,而值可以是任何JSON数据类型,包括字符串、数字、布尔值、null、数组或另一个对象。对象在JSON中通常用于表示复杂的数据结构,例如,具有多个属性的实体或具有多个字段的记录。
因此,当你需要表示一个列表或集合时,你应该使用数组,而不是对象。

Optional.ofNullable(order).map(order1 -> order1.name).orElse(null) 这段代码不会抛出空指针异常。Optional对象可以包含一个值,也可以不包含值。Optional.ofNullable方法可以接收一个可能为null的值,并返回一个Optional对象。如果传入的值为null,那么返回的Optional对象也不包含值。
在你的代码中,Optional.ofNullable(order)会返回一个Optional对象,如果order为null,那么返回的Optional对象也不包含值。map方法可以接收一个函数,并将函数应用到Optional对象包含的值上。如果Optional对象不包含值,那么map方法也不会执行函数,而是返回原来的Optional对象。filter(Objects::nonNull)方法会过滤掉Stream中的null元素,从而避免在调用getName()方法时,抛出NullPointerException如:users.stream()
.filter(Objects::nonNull) .map(s -> s.getName()) .filter(Objects::nonNull).collect(Collectors.toList());

java8方法引用方法的参数列表必须与函数式接口的抽象方法的参数列表保持一致,返回值不作要求

实例对象::实例方法名

​ System.out::println;

​ System.out代表的就是PrintStream类型的一个实例,println是这个实例的一个方法。

类名::静态方法名

​ Math::abs

​ Math是一个类而abs为该类的静态方法。Function中的唯一抽象方法apply方法参数列表与abs方法的参数列表 相同,都是接收一个Long类型参数

类名::实例方法名

String::equals;

若Lambda表达式的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时, 就可以使用这种方法

引用构造

​ 在引用构造器的时候,构造器参数列表要与接口中抽象方法的参数列表一致,格式为 类名::new 如: StringBuffer::new

ApplicationContextAwareProcessor implements BeanPostProcessor 会在bean初始化时回调一系列的aware的回调方法如:ApplicationContextAware、EnvironmentAware、EmbeddedValueResolverAware等

org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces

https://blog.csdn.net/qq_26000415/article/details/78957026 spring零配置

服务启动在http://localhost:8080有get请求list,post请求delete.

http://localhost:8085/检查里面没有响应码等信息,get请求delete响应码405(Method Not Allowed)。get请求delete2响应码404(Not Found)其他业务返回的异常code显示在对应的业务code,http状态码200

HTTP状态码是由服务器返回给客户端的响应状态的数字代码,它表示了HTTP请求的处理结果。HTTP状态码由三位数字组成,第一个数字定义了响应的类别,后两位数字没有分类作用。HTTP状态码分为五类,分别是:
1xx:信息响应类 - 这类状态码表示请求已被服务器接收,继续处理。例如100(Continue)表示客户端应该继续发送请求的其余部分。
2xx:成功响应类 - 这类状态码表示请求已成功被服务器接收、理解,并接受。例如200(OK)表示请求已成功,请求所希望的响应头或数据体将随此响应返回。
3xx:重定向响应类 - 这类状态码表示需要客户端采取进一步的操作才能完成请求。例如301(Moved Permanently)表示请求的资源已被永久移动到新的URI,新的URI将随响应返回。
4xx:客户端错误响应类 - 这类状态码表示请求包含语法错误或无法完成请求。例如400(Bad Request)表示服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求;404(Not Found)表示请求的资源在服务器上没有找到。
5xx:服务器错误响应类 - 这类状态码表示服务器在处理请求的过程中发生了错误。例如500(Internal Server Error)表示服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。
例如,以下是一些常见的HTTP状态码:
200 OK:请求已成功,请求所希望的响应头或数据体将随此响应返回。
201 Created:请求成功并且服务器创建了新的资源。
204 No Content:服务器成功处理了请求,但不需要返回任何内容。
301 Moved Permanently:请求的资源已被永久移动到新的URI,新的URI将随响应返回。
302 Found:请求的资源现在临时从不同的URI响应请求。
400 Bad Request:服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。

401 Unauthorized:请求要求用户的身份认证。
403 Forbidden:服务器理解请求客户端的请求,但是拒绝执行此请求。
404 Not Found:请求的资源在服务器上没有找到。
500 Internal Server Error:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。
503 Service Unavailable:服务器当前无法处理请求。
如果服务器未启动或不可达,客户端发送的HTTP请求将无法到达服务器,因此服务器无法返回任何响应。在这种情况下,客户端通常会收到一个超时错误,而不是一个HTTP状态码。
但是,具体的行为取决于客户端和网络环境。在某些情况下,客户端的网络库或代理服务器可能会返回一个错误响应,最常见的错误响应是:
404 Not Found:这通常表示服务器无法找到请求的资源,但在服务器未启动的情况下,这可能表示服务器本身无法被找到。
502 Bad Gateway:这通常表示服务器作为网关或代理工作时,从上游服务器收到无效的响应。在服务器未启动的情况下,这可能表示服务器无法响应请求。
503 Service Unavailable:这通常表示服务器目前无法处理请求,但在服务器未启动的情况下,这可能表示服务器无法响应请求。
504 Gateway Timeout:这通常表示作为网关或代理工作的服务器没有及时从上游服务器收到请求的响应。在服务器未启动的情况下,这可能表示服务器无法响应请求。
但是,这些错误响应并不是由目标服务器返回的,而是由客户端的网络库或代理服务器返回的。在服务器未启动的情况下,客户端通常会收到一个超时错误,而不是一个HTTP状态码。

Spring MVC的初始化和加载过程可以分为两个主要的部分:Spring环境的加载和Web环境的加载。
Spring环境的加载:Spring环境的加载主要由Spring框架完成,它负责创建和管理Spring应用上下文(ApplicationContext),并初始化Spring Bean。在Spring MVC应用中,Spring环境的加载通常由AnnotationConfigApplicationContext或XmlWebApplicationContext等类完成,它们会读取Spring配置类或Spring配置文件,创建和管理Spring Bean。
Web环境的加载:Web环境的加载主要由Servlet容器完成,它负责创建和管理Servlet应用上下文(ServletConfig和ServletContext),并初始化Servlet、Filter和Listener等Web组件。在Spring MVC应用中,Web环境的加载通常由DispatcherServlet完成,它是一个Servlet,用于处理HTTP请求,将请求分发到相应的Controller,然后将Controller的处理结果返回给客户端。
在Spring MVC应用中,Spring环境的加载和Web环境的加载是相互独立的,但是它们之间存在紧密的联系。Spring环境的加载会创建和管理Spring Bean,包括Controller、Service、Repository等,而Web环境的加载会创建和管理Servlet、Filter和Listener等Web组件,包括DispatcherServlet。DispatcherServlet会从Spring应用上下文中获取Controller、Service、Repository等Spring Bean,并将它们用于处理HTTP请求。
因此,Spring MVC的初始化和加载过程可以分为两个主要的部分:Spring环境的加载和Web环境的加载。Spring环境的加载主要由Spring框架完成,而Web环境的加载主要由Servlet容器完成。这两个部分是相互独立的,但是它们之间存在紧密的联系。

在Java中,private,protected,public和默认(即没有访问修饰符)修饰的方法或者变量的子类继承规则如下:
private:private修饰的方法或者变量不能被子类继承。这是因为private修饰的成员只能在定义它们的类中访问,子类不能访问父类的private成员。但是,子类可以通过父类的公共方法间接访问private成员。
default(没有访问修饰符):默认(即没有访问修饰符)修饰的方法或者变量可以被子类继承,但是继承的条件是子类和父类在同一个包中。如果子类和父类不在同一个包中,那么子类不能继承父类的默认(即没有访问修饰符)成员。
protected:protected修饰的方法或者变量可以被子类继承,无论子类是否在同一个包中。这是因为protected修饰的成员可以被同一包中的所有类,以及不同包中的子类访问。
public:public修饰的方法或者变量可以被子类继承,无论子类是否在同一个包中。这是因为public修饰的成员可以被任何类访问。

protected:protected修饰的变量或者方法可以被同一包中的所有类,以及不同包中的子类访问。因此,即使在其他非子类中new一个Child对象,你仍然可以使用这个new出来的Child对象访问Parent中的protected变量或者方法,但是访问的条件是这个非子类和Child类在同一个包中,或者这个非子类是Child类的子类。

在Spring框架中,bean的实例化和初始化的顺序是先实例化,后初始化。
实例化:当Spring容器初始化时,它会根据配置文件或注解创建bean的实例。这个过程被称为bean的实例化。实例化是通过调用bean的默认构造函数,或者通过使用@Autowired注解的构造函数来完成的。实例化完成后,bean的属性会被注入,这个过程被称为依赖注入。
初始化:在bean实例化并完成依赖注入后,Spring会调用bean的初始化方法。初始化方法可以通过以下两种方式定义:
在XML配置文件中,使用init-method属性指定初始化方法。
在Java配置或注解中,使用@PostConstruct注解标记初始化方法。
初始化方法通常用于执行一些bean初始化后的设置,如打开资源、初始化数据等

在Spring MVC框架中,HandlerAdapter和Handler(通常指的是Controller中的处理方法,或者更具体地,HandlerMethod)是处理HTTP请求的两个关键概念。

Handler(HandlerMethod):
定义:在Spring MVC中,Handler通常指的是控制器(Controller)中的处理方法,它是一个具体的Java方法,用于处理特定的HTTP请求。当一个HTTP请求到达时,Spring MVC框架会根据请求的URL、HTTP方法等信息,找到对应的Handler来处理这个请求。

HandlerMethod是Spring MVC框架中的一个核心类,它用于封装控制器(Controller)中的一个方法,这个方法将被用作处理特定的HTTP请求。HandlerMethod类的主要作用如下:封装Controller方法:HandlerMethod对象包含了控制器方法的所有信息,包括方法的Method对象、方法所属的Bean对象(即Controller对象)、方法的参数信息、返回类型信息等。这些信息对于处理请求和执行方法是至关重要的。
解析请求参数:HandlerMethod能够解析请求中的参数,并将它们绑定到控制器方法的参数上。例如,如果控制器方法的参数上标注了@RequestParam、@PathVariable、@ModelAttribute等注解,HandlerMethod能够根据这些注解来解析请求中的参数,并将它们转换为方法参数的类型。
执行控制器方法:当一个请求到达Spring MVC框架时,DispatcherServlet会调用HandlerAdapter来执行HandlerMethod。HandlerAdapter会调用HandlerMethod的invoke方法来执行控制器方法,并将请求参数传递给方法。
返回处理结果:HandlerMethod能够处理控制器方法的返回值,将它转换为适当的响应。例如,如果控制器方法的返回类型是ModelAndView,HandlerMethod会将它转换为视图和模型数据;如果返回类型是String,HandlerMethod会将它解释为视图名称;如果返回类型是void,HandlerMethod会处理无返回值的情况。
处理异常:如果在执行控制器方法时抛出了异常,HandlerMethod会将异常传递给DispatcherServlet,由DispatcherServlet调用异常处理器(ExceptionResolver)来处理异常。
总的来说,HandlerMethod是Spring MVC框架中用于封装和执行控制器方法的核心类,它通过封装方法信息、解析请求参数、执行方法、处理返回值和异常,实现了Spring MVC框架的请求处理机制。

HandlerExecutionChain是Spring MVC框架中的一个核心类,它用于封装一个请求处理过程中的所有组件,包括处理器(Handler)和拦截器(Interceptors)
HandlerAdapter:
定义:HandlerAdapter是Spring MVC框架中的一个接口,用于适配具体的Handler。Spring MVC框架在处理请求时,会先找到对应的Handler,然后使用HandlerAdapter来执行这个Handler。
实现:Spring MVC框架提供了多种HandlerAdapter的实现,如RequestMappingHandlerAdapter、SimpleControllerHandlerAdapter等,它们分别适配了不同类型的Handler。
作用:HandlerAdapter的作用是适配和执行Handler。它能够解析Handler的类型,调用Handler的执行方法,处理Handler的返回值,处理Handler抛出的异常等。
总的来说,Handler是处理请求的具体实现,而HandlerAdapter是适配和执行Handler的适配器。在Spring MVC框架中,Handler和HandlerAdapter是处理HTTP请求的两个关键概念,它们共同实现了Spring MVC框架的请求处理机制。

HandlerAdapter是Spring MVC框架中的一个接口,用于适配具体的Handler。Spring MVC框架在处理请求时,会先找到对应的Handler,然后使用HandlerAdapter来执行这个Handler。

WebMvcConfigurer是Spring Framework 5.0引入的一个接口,用于配置Spring MVC的功能。这个接口提供了一系列的方法,可以用来定制Spring MVC的行为,包括视图解析、拦截器、静态资源处理、消息转换、数据格式化等

在调试过程中,IDE会显示对象的toString()方法的返回值。ApplicationContextFacade的toString()方法默认会返回类名加上对象的哈希码。哈希码是对象在内存中的唯一标识符,通常由System.identityHashCode()方法生成。哈希码的值是随机的,每次运行程序时可能会不同

HttpMessageConverter 是 Spring MVC 框架中用于处理 HTTP 消息转换的核心接口。它负责将请求体中的数据转换为 Java 对象(即序列化),以及将 Java 对象转换为响应体中的数据(即反序列化)。HttpMessageConverter 的主要作用是处理 HTTP 请求和响应的数据格式,如 JSON、XML、HTML 等。

GenericHttpMessageConverter是Spring MVC框架中用于处理HTTP消息转换的一个类,它实现了HttpMessageConverter接口,用于将HTTP请求体中的数据转换为Java对象,以及将Java对象转换为HTTP响应体中的数据。GenericHttpMessageConverter是一个通用的转换器,可以处理多种数据类型,但需要一个ObjectMapper来具体实现数据的序列化和反序列化

通常,你不会直接使用HttpMessageConverter接口,而是使用它的实现类,如MappingJackson2HttpMessageConverter(用于处理JSON数据)、StringHttpMessageConverter(用于处理字符串数据)等。
GenericHttpMessageConverter: 当你需要一个通用的转换器来处理多种数据类型时,或者需要自定义序列化和反序列化行为时,GenericHttpMessageConverter是一个很好的选择

POST请求中的所有数据,包括表单数据,都包含在HttpServletRequest的InputStream中。当你发送一个POST请求,无论是表单数据还是JSON、XML等其他格式的数据,这些数据都会作为请求体被发送,并且可以通过HttpServletRequest的getInputStream()方法获取。

application/x-www-form-urlencoded: 这种格式通常用于HTML表单提交,数据以key=value的形式,多个键值对之间用&连接。例如:username=John&password=123456。这种格式的数据通常用于简单的键值对数据传输,易于阅读和理解。
application/json: 这种格式使用JSON(JavaScript Object Notation)数据格式,可以表示更复杂的数据结构,如对象和数组。例如:{"username":"John","password":"123456"}。JSON数据格式更灵活,可以表示嵌套的结构,适用于传输更复杂的数据。

application/x-www-form-urlencoded: 在Servlet或Spring MVC中,这种格式的数据可以通过HttpServletRequest的getParameter方法直接获取。Servlet容器会自动解析这种格式的数据,并将其转换为键值对。
application/json: 对于JSON格式的数据,通常需要使用@RequestBody注解(在Spring MVC中)来绑定到一个Java对象,或者手动从InputStream中读取并使用JSON库(如Jackson、Gson等)进行解析

application/x-www-form-urlencoded格式的参数确实可以从InputStream中读取,但这通常不是开发者直接操作的。在Servlet或Spring MVC等框架中,这个过程是由框架底层自动完成的。当一个application/x-www-form-urlencoded格式的POST请求到达时,Servlet容器会读取请求体中的InputStream,解析其中的key=value对,并将它们存储在HttpServletRequest对象中,以便开发者通过getParameter方法访问。
然而,如果你需要手动处理application/x-www-form-urlencoded格式的数据,你可以直接从HttpServletRequest的getInputStream()方法获取InputStream,然后读取和解析数据。
总结:对于bean属性,controller入参,@Value处理都是先处理$,#再convertIfNecessary处理CustomEditor,和conversionService
spring 属性赋值 applyPropertyValues(属性赋值、参数解析)由下面可以知道执行CustomEditor、conversionService
1、如果需要解析$需要注入bean:PropertyPlaceholderConfigurer继承BeanFactoryPostProcessor{
...postProcessBeanFactory->processProperties->doProcessProperties->visitBeanDefinition->visitPropertyValues->resolveValue->resolveStringValue->resolveStringValue->replacePlaceholders->parseStringValue(递归处理$)->resolvePlaceholder->resolvePlaceholder->resolveSystemProperty{
//解析后设置到PropertyValue中
...System.getProperty(key)...
}
}
2、applyPropertyValues{
//解析pv类型包括为bean时的依赖
...valueResolver.resolveValueIfNecessary...
...convertForProperty..{
...convertIfNecessary..{
//自定义属性编辑器,需要注入bean类型CustomEditorConfigurer
...findCustomEditor...
//自定义属性编辑器convert,使用时需要注册bean类型ConversionServiceFactoryBean(Springmvc 不需要手动注入,support中adapter自动注入了)
...conversionService.convert...
}...
}...
}

//RequestMappingHandlerAdapter的构造或者@Bean、afterPropertiesSet等会初始化一些messageConverters和MethodArgumentResolver、returnValueHandlers同时和解析器对应的MessageConverters等。org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet->getDefaultArgumentResolvers{
...resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver())...
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice))...
}
//http参数解析如下:
org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues{
...if (!this.resolvers.supportsParameter(parameter)) ...
...args[i] = this.resolvers.resolveArgument...
}
对于如下:
@RequestMapping(value = "/queryId")
public void queryId(String channelId ,String channelCode)

@RequestMapping(value = "/queryId")
public void queryId(@RequestParam("${channelIdpre}")String id)

//默认第二个RequestParamMethodArgumentResolver处理,处理逻辑是request.getParameterValues(如果非表单数据即key=value模式或者非get请求则解析不了):
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument{
//处理$和#
...resolveStringValue{
...resolveEmbeddedValue...
...getBeanExpressionResolver...
}...
//从系统属性中取值
...resolveName{
...if (arg == null) {
//获取指定name的值取第一个,如果name不对则取不到值
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}...
}...
//创建WebDataBinder,绑定属性
...WebDataBinder binder = binderFactory.createBinder...
...binder.convertIfNecessary..{
...findCustomEditor...
...conversionService.convert...
}...
}

//RequestResponseBodyMethodProcessor处理解析(判断contentType是否支持)类似json等和java的转换
public ChannelInfo queryByChannelId(@RequestBody ChannelInfo info)
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument{
...readWithMessageConverters{
...readWithMessageConverters{
//messageConverters
...for (HttpMessageConverter<?> converter : this.messageConverters) {
...body = (genericConverter != null ? genericConverter.read...
}...
}...
}...
}
//WebMvcConfigurationSupport中注册默认的conversionService bean类型为FormattingConversionService不仅能够处理类型之间的转换,还能在转换过程中应用格式化规则,这在处理日期、时间、数字等需要格式化或解析的类型时非常有用。(初始化org.springframework.core.convert.support.GenericConversionService#converters转换器)org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#mvcConversionService

//ServletModelAttributeMethodProcessor通过request.getParameterNames()返回mpvs结合ServletRequestDataBinder封装属性 也意味着对于json格式请求数据无效
public ChannelInfo queryByChannelId(ChannelInfo info)

org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest->getMethodArgumentValues->resolveArgument->resolveArgument{
...WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name)...
...bindRequestParameters->bind{
//从request.getParameterValues放置到map中返回mpvs用于后面复制
...MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request)...
...doBind->doBind->applyPropertyValues->setPropertyValues->setPropertyValue..->processLocalProperty->convertForProperty->convertIfNecessary..{
...// Custom editor for this type 自定义属性解析器
this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName)...
}->canConvert->getConverter{
//组合sourceType和targetType的本类及父类看converters中是否存在对应的转换器。如String就有String,java.io.Serializable,java.lang.Comparable,java.lang.CharSequence,Object然后Date就有Date,java.io.Serializable,java.lang.Cloneable,java.lang.Comparable,Object等
...this.converters.find(sourceType, targetType){
...getRegisteredConverter{
//此处可以知道CustomEditor(CustomEditorConfigurer)先于converter(ConversionServiceFactoryBean)转换属性

                //对于String转Date通过上面组合后converters中存在key为java.lang.String->java.util.Date的DateTimeFormatAnnotationFormatterFactory转换器,这个转换器后面转换的时候会校验是否有org.springframework.format.annotation.DateTimeFormat这个注解,没有就抛异常org.springframework.format.support.FormattingConversionService.AnnotationParserConverter#convert
                ...this.converters.get(convertiblePair)...
            }...
        }...
    }...
}...

}

//对于自定义的属性解析器使用






org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance->instantiateBean->initBeanWrapper->registerCustomEditors(bw){
//此处的this为DefaultListableBeanFactory,在xml配置文件中 registry为BeanWrapper这样就把beanfactory中的customEditors注册到beanwrapper中了
...this.customEditors.forEach((requiredType, editorClass) ->
registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)))...
}
接着属性复制populateBean->applyPropertyValues(会先判断属性是否bean如果是则resolveValueIfNecessary->resolveReference->getBean 循环依赖也在这里处理的)->convertForProperty..->convertIfNecessary..{
// Custom editor for this type 自定义的解析器作用处
...PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName)...
}
//对于复杂的$和#使用,Edit Config..配置 VMoptions添加-DchannelIdpre="#{dataSource.url}" (如果是多个参数分别使用-Dkey=value配置项间空格分隔)
@RequestMapping(value = "/queryId")
public void queryId(@RequestParam("${channelIdpre}")String id)
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument->resolveStringValue{
//$占位符解析,解析器配置是在finishBeanFactoryInitialization方法中beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal))可以看出来是createPlaceholderHelper后从环境中取值
...resolveEmbeddedValue(value)->resolveStringValue;
...exprResolver = this.configurableBeanFactory.getBeanExpressionResolver()...
//#{} spl解析,使用StandardBeanExpressionResolver(准备工厂时默认的org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory)
...exprResolver.evaluate(placeholdersResolved, this.expressionContext){
...expr.getValue{

    }...
}...

}
//SpringMvc参数解析器binding设置conversionService
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerAdapter{
//adapter给binding设置了conversionService
...adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator){
...ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(mvcConversionService)...
})...
}

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod-{
...WebDataBinderFactory binderFactory = getDataBinderFactory{
...createDataBinderFactory{
//getWebBindingInitializer返回的ConfigurableWebBindingInitializer
...new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer())...
}...
}...
//binderFactory为ServletRequestDataBinderFactory含有ConfigurableWebBindingInitializer属性
...invocableMethod.setDataBinderFactory(binderFactory)...
}

org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument{
...binderFactory.createBinder(webRequest, attribute, name){
...WebDataBinder dataBinder = createBinderInstance...
...this.initializer.initBinder(dataBinder, webRequest)->initBinder{
//this为ConfigurableWebBindingInitializer
...binder.setConversionService(this.conversionService)...
}...
}...
}
//对于@Value,@Autowired注解标记
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata{
...buildAutowiringMetadata{
//遍历类对象的filed是否存在@Value,@Autowired注解
...ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field)...
}...
//放入injectionMetadataCache后面放入InjectionMetadata
...this.injectionMetadataCache.put(cacheKey, metadata)...
}
//对于@Value,@Autowired注解解析值
populateBean->org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties{
...findAutowiringMetadata...
...metadata.inject(bean, beanName, pvs){
...element.inject{
...beanFactory.resolveDependency{
...doResolveDependency{
//解析$
...resolveEmbeddedValue...
//解析#
...evaluateBeanDefinitionString...
//解析findCustomEditor,conversionService
...convertIfNecessary...
}...
}...
}...
}...
}
在AOP(Aspect Oriented Programming,面向切面编程)中,切面、切点和通知是三个核心概念,它们的含义如下:
切面(Aspect)
切面是AOP中的一个核心概念,它封装了横切关注点,如日志记录、性能监控、安全控制等。横切关注点是指那些在多个类中重复出现,但又与业务逻辑无关的代码,如日志记录、性能监控、安全控制等。通过切面,可以将这些横切关注点从业务逻辑中分离出来,实现关注点的模块化。
切点(Pointcut)
切点是AOP中的另一个核心概念,它定义了切面所关注的代码范围,即哪些代码需要被切面所影响。切点通常是一个匹配规则,用于匹配那些需要被切面所影响的方法或类。例如,你可以定义一个切点,用于匹配所有在com.example.service包下的类的所有方法。
通知(Advice)
通知是AOP中的另一个核心概念,它定义了切面在切点处所执行的代码,即当切点处的代码被调用时,切面所执行的代码。通知通常是一个方法,它在切点处的代码被调用时被调用。通知有多种类型,如前置通知(Before Advice)、后置通知(After Advice)、环绕通知(Around Advice)等。
总的来说,AOP是一种编程范式,它通过切面、切点和通知等概念,将横切关注点从业务逻辑中分离出来,实现关注点的模块化。切面封装了横切关注点,切点定义了切面所关注的代码范围,通知定义了切面在切点处所执行的代码。通过AOP,可以实现代码的解耦,提高代码的可维护性和可扩展性。

在Spring AOP(Aspect Oriented Programming,面向切面编程)中,切面(Aspect)和通知(Advice)是两个核心概念,它们之间存在层级关系。
切面(Aspect):切面是封装了横切关注点的模块,它由一个或多个通知和一个或多个切入点组成。切面是AOP的核心,它定义了哪些代码将被增强,以及如何增强。在Spring AOP中,切面通常由一个类实现,这个类包含了通知和切入点的定义。
通知(Advice):通知是在切面定义的切入点上执行的代码,它是在程序执行的某个特定点插入的代码,如方法调用前、方法调用后、方法抛出异常后等。在Spring AOP中,通知通常由一个方法实现,这个方法将在匹配的切入点上执行。
切入点(Pointcut):切入点是通知将被应用到的代码的某个位置,它定义了通知将被应用到哪些连接点(Joinpoint)上。在Spring AOP中,切入点通常由一个表达式定义,这个表达式匹配了将被增强的方法或类。
因此,切面、通知和切入点之间的层级关系如下:
切面包含了通知和切入点,一个切面可以包含一个或多个通知,一个切面可以包含一个或多个切入点。
通知是在切面定义的切入点上执行的代码,一个通知只能在一个切面中定义,但是一个切面可以包含一个或多个通知。
切点定义了通知将被应用到的代码的某个位置,一个切点只能在一个切面中定义,但是一个切面可以包含一个或多个切点。
在Redis中,你可以同时使用RDB(Redis Database Backup)和AOF(Append Only File)两种持久化方式。当Redis启动时,如果同时存在RDB和AOF文件,那么Redis会优先使用AOF文件来恢复数据,因为AOF文件的数据更完整。当Redis运行时,RDB和AOF两种持久化方式会同时工作,RDB会定期创建数据集的时间点快照,AOF会记录服务器执行的所有写入操作命令。当Redis需要进行数据恢复时,它会优先使用AOF文件,如果AOF文件不存在或者无法使用,那么它会使用RDB文件。

MySQL提供了多种类型的日志文件,每种日志文件都有其特定的用途。以下是一些主要的日志文件:
错误日志(Error Log):错误日志记录了MySQL服务器启动、关闭以及运行时发生的错误信息。错误日志对于诊断和解决问题非常有帮助。错误日志的文件名默认为hostname.err。
二进制日志(Binary Log):二进制日志记录了所有更改数据的SQL语句,但不包括SELECT或SHOW语句。二进制日志主要用于数据恢复和主从复制。二进制日志的文件名默认为hostname-bin.index和hostname-bin.000001等。
慢查询日志(Slow Query Log):慢查询日志记录了执行时间超过指定阈值的SQL语句。慢查询日志对于性能调优非常有帮助。慢查询日志的文件名默认为hostname-slow.log。
一般查询日志(General Query Log):一般查询日志记录了所有发送到MySQL服务器的SQL语句,包括SELECT和SHOW语句。一般查询日志的文件名默认为hostname.log。
审计日志(Audit Log):审计日志记录了所有对MySQL服务器的访问和操作,包括登录、登出、查询、修改等。审计日志对于安全审计非常有帮助。
InnoDB日志(InnoDB Log):InnoDB日志包括重做日志(Redo Log)和回滚日志(Undo Log)。重做日志记录了所有更改数据的操作,用于数据恢复。回滚日志记录了所有未提交的事务,用于事务回滚。
慢启动日志(Slow Start Log):慢启动日志记录了启动时间超过指定阈值的线程。慢启动日志对于性能调优非常有帮助。
以上日志文件的配置和管理可以通过MySQL的配置文件my.cnf或my.ini,以及SHOW VARIABLES和SET等SQL语句进行。

MySQL的主从复制是MySQL提供的一种数据复制机制,可以将一个MySQL服务器(主服务器)的数据复制到一个或多个MySQL服务器(从服务器)。主从复制可以用于数据备份、读写分离、负载均衡等场景。以下是MySQL主从复制的实现步骤:
开启二进制日志:在主服务器上,需要开启二进制日志。二进制日志记录了所有更改数据的SQL语句,但不包括SELECT或SHOW语句。在my.cnf或my.ini配置文件中,设置log_bin参数,例如log_bin = /var/log/mysql/mysql-bin.log。
设置服务器ID:在主服务器和从服务器上,需要设置唯一的服务器ID。在my.cnf或my.ini配置文件中,设置server-id参数,例如server-id = 1。
创建复制用户:在主服务器上,需要创建一个用于复制的用户。例如,执行以下SQL语句:CREATE USER 'repl'@'%' IDENTIFIED BY 'password';,GRANT REPLICATION SLAVE ON . TO 'repl'@'%';。
获取二进制日志的位置:在主服务器上,需要获取二进制日志的文件名和位置。例如,执行以下SQL语句:SHOW MASTER STATUS;。
配置从服务器:在从服务器上,需要配置复制源和复制位置。在my.cnf或my.ini配置文件中,设置server-id参数,例如server-id = 2。然后,执行以下SQL语句:CHANGE MASTER TO MASTER_HOST='主服务器IP', MASTER_USER='repl', MASTER_PASSWORD='password', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=107;,其中MASTER_LOG_FILE和MASTER_LOG_POS是主服务器上获取的二进制日志的文件名和位置。
启动复制:在从服务器上,需要启动复制。执行以下SQL语句:START SLAVE;。
检查复制状态:在从服务器上,可以检查复制的状态。执行以下SQL语句:SHOW SLAVE STATUS\G;。
以上是MySQL主从复制的基本实现步骤,但是实际的配置和管理可能需要根据具体的需求和环境进行调整。例如,可以设置复制过滤规则,只复制部分数据库或表;可以设置复制延迟,使从服务器的数据落后于主服务器;可以设置复制模式,只复制部分类型的SQL语句;可以设置复制故障自动恢复,使从服务器在故障后自动恢复复制等。

MASTER_LOG_POS=107中的107指的是二进制日志(Binary Log)的位置,具体来说,是二进制日志文件中的一个字节位置。第107个字节是上一次复制的记录的结束位置,那么当你在从服务器上执行CHANGE MASTER TO MASTER_LOG_POS=107;时,从服务器就会从mysql-bin.000001文件的第107个字节开始读取二进制日志,复制之后的数据 ,MASTER_LOG_POS参数的值需要在主服务器上获取,通常是在执行SHOW MASTER STATUS;语句后,从返回的结果中找到File和Position字段

InnoDB Log(InnoDB日志):
InnoDB是MySQL的一种存储引擎,它提供了事务、行级锁和外键等特性。InnoDB日志包括重做日志(Redo Log)和回滚日志(Undo Log)。
重做日志(Redo Log):当InnoDB存储引擎在事务提交时,会将事务的更改信息写入重做日志。如果服务器崩溃,InnoDB会使用重做日志来恢复未写入磁盘的数据,保证事务的持久性。重做日志的使用场景通常是在服务器重启或崩溃后,用于恢复未提交的事务。
回滚日志(Undo Log):当InnoDB存储引擎在事务开始时,会将事务的原始数据写入回滚日志。如果事务回滚,InnoDB会使用回滚日志来恢复数据,保证事务的原子性。回滚日志的使用场景通常是在事务回滚时,用于恢复数据。
Binary Log(二进制日志):
二进制日志是MySQL的一种日志,它记录了所有更改数据的SQL语句,但不包括SELECT或SHOW语句。二进制日志的使用场景通常是在主从复制和数据恢复中。
主从复制:在MySQL的主从复制中,从服务器会读取主服务器的二进制日志,执行其中的SQL语句,将数据复制到从服务器。二进制日志的使用场景通常是在主从复制中,用于复制数据。
数据恢复:在MySQL的数据恢复中,可以使用二进制日志来恢复数据。例如,如果在某个时间点,数据被意外删除或修改,可以使用二进制日志来恢复数据。二进制日志的使用场景通常是在数据恢复中,用于恢复数据。
总的来说,InnoDB日志和二进制日志的使用场景不同,InnoDB日志主要用于保证事务的原子性和持久性,而二进制日志主要用于主从复制和数据恢复。但是,InnoDB日志和二进制日志也可以结合使用,例如,在主从复制中,可以使用InnoDB日志来保证事务的原子性和持久性,同时使用二进制日志来复制数据。
spring容器在创建bean的过程中,会判断bean是否为ApplicationListener类型,进而会将其作为监听器注册到AbstractApplicationContext#applicationEventMulticaster中,这块的源码在下面这个方法中:org.springframework.context.support.ApplicationListenerDetector#postProcessAfterInitialization

处理@EventListener注解源码位于:org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated

//getServletContext()方法返回javax.servlet.ServletContext的属性ApplicationContext(此处作为event的source),属性ApplicationContext中的standardContext(含servletmappings、mimemappings、innitiallize如:JasperInitializer、SpringServletContainerInitializer等)
org.apache.catalina.core.StandardContext#listenerStart{
...ServletContextEvent event = new ServletContextEvent(getServletContext())...
...listener.contextInitialized(event)...
}->(传入了ApplicationContext参数由tomcat进入spring-web)org.springframework.web.context.ContextLoaderListener#contextInitialized(ServletContextEvent event)->org.springframework.web.context.ContextLoader#initWebApplicationContext{
//org.springframework.web.context.ContextLoader#createWebApplicationContext
//由ContextLoader获取默认的WebApplicationContext。ContextLoader.properties文件定义了WebApplicationContext默认为XmlWebApplicationContext
...this.context = createWebApplicationContext(servletContext)...
...cwac = (ConfigurableWebApplicationContext) this.context...
//servletContext即方法参数传进来的tomcat中的ApplicationContext
...configureAndRefreshWebApplicationContext(cwac, servletContext){
//spring上下文中设置servletContext
...wac.setServletContext(sc);
//spring配置文件从tomcat的ApplicationContext中获取属性contextConfigLocation的值classpath:applicationContext.xml
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}...
//运行initializerClasses如web.xml中定义的globalInitializerClasses、contextInitializerClasses等
...customizeContext(sc, wac);
//刷新spring
wac.refresh(){
...finishRefresh{
//此时不会触发org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener的监听,因为此时ContextRefreshListener还没有被bean管理也就不会被ApplicationListenerDetector识别,它是在org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext方法中wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()))加进去的,同理不会触发org.springframework.web.servlet.resource.ResourceUrlProvider implements ApplicationListener 它也是在递归循环识别@import注解时(org.springframework.context.annotation.ConfigurationClassParser#processImports->getImports->collectImports->collectImports)通过识别类或注解的元注解是否含有import并注入对应的bean如识别@EnableWebMvc导入DelegatingWebMvcConfiguration(extends WebMvcConfigurationSupport)类注册.WebMvcConfigurationSupport通过@Bean注入了类ResourceUrlProvider(映射静态资源放入handlerMap对应于simpleUrlhandlermapping适配)。所以这两者都是容器加载时候添加进去的
...publishEvent(new ContextRefreshedEvent(this))...
}...
}...
}...
...servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context)...//servletContext设置属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE值为创建的spring context(XmlWebApplicationContext)。后续dispatchservlet初始化mvc环境设置它为父parent
}...
}->
//FrameworkServlet extends HttpServletBean(左边是spring-webmvc包中的类,右边是servlet包) extends HttpServlet extends GenericServlet extends Servlet
org.apache.catalina.core.StandardWrapper#load->loadServlet->initServlet->servlet.init->javax.servlet.GenericServlet#init->org.springframework.web.servlet.HttpServletBean#initServletBean->org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext{
//基于继承关系,此处的getServletContext方法继承父类获取servlet的ApplicationContextFacade。getWebApplicationContext方法获取到的为监听器初始化spring后的XmlWebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(){
//获取的监听器初始化spring环境时将spring的XmlWebApplicationContext放到ServletContext中的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性值
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE){
...sc.getAttribute(attrName)...
}
})...
...createWebApplicationContext(rootContext){
//parent即入参rootContext
return createWebApplicationContext((ApplicationContext) parent){
//实例化新的XmlWebApplicationContext contextClass 为org.springframework.web.servlet.FrameworkServlet#DEFAULT_CONTEXT_CLASS= XmlWebApplicationContext.class
...wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass)...
//容器上下文XmlWebApplicationContext设置Spring的XmlWebApplicationContext为parent(对应后面getbean找不到去父里面找)
...wac.setParent(parent)
//获取
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
//调用refresh()->finishRefresh->发布ContextRefreshedEvent事件触发org.springframework.web.servlet.FrameworkServlet#onApplicationEvent->onRefresh->initStrategies(initHandlerMappings、initHandlerAdapters、initViewResolvers等)
configureAndRefreshWebApplicationContext(wac)...
}
}...
}->

ContextNamespaceHandler(spring.handlers)->component-scan(ComponentScanBeanDefinitionParser仅会解析一层componet,其中的parse->registerComponents->registerAnnotationConfigProcessors会注入ConfigurationClassPostProcessor类解析import等)

org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory->processConfigBeanDefinitions->(筛选spring中的配置bean(Configuration、Component、ComponentScan、Import、ImportResource))parser.parse(candidates)->parse->processConfigurationClass->doProcessConfigurationClass{
//循环处理类,如果类中有子类是配置类-》processConfigurationClass
...processMemberClasses...
//陆续处理@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean、default methods on interfaces等
...this.componentScanParser.parse->scanner.doScan{
...findCandidateComponents...
...beanDefinitions.add(definitionHolder)... @1
...registerBeanDefinition...
}...
//检查@1的beanDefinitions是否配置类(Configuration、Component、ComponentScan、Import、ImportResource),如果是就递归解析
...if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { //org.springframework.context.annotation.ConfigurationClassParser#parse(java.lang.String, java.lang.String)->processConfigurationClass递归扫描解析
parse(bdCand.getBeanClassName(), holder.getBeanName());
}...
}->
//参数没有对应解析器异常捕获
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)->throw new HttpMessageNotReadableException 被 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#doResolveException捕获统一处理
参数解析org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument{
//binder含属性BeanPropertyBindingResult存放检验错误信息
...WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name)...
...validateIfApplicable(binder, parameter){
//检验,校验不通过抛出异常被上层捕获。接着org.springframework.web.servlet.DispatcherServlet#processDispatchResult判断入参dispatchException,如果不为空则processHandlerException->processHandlerException->resolveException->resolveException->org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#doResolveException抛出(org.springframework.web.servlet.DispatcherServlet#initHandlerExceptionResolvers初始化加载的org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#handlerExceptionResolver)
...binder.validate...
}...
}
如果不使用二级缓存(earlySingletonObjects),在解决循环依赖问题时,Spring只能通过singletonFactories创建早期Bean引用。这可能会带来以下限制和问题:
性能问题:每次需要获取早期Bean引用时,都需要调用singletonFactory来创建Bean,可能会增加不必要的Bean创建和销毁操作,降低性能。

在接口设计中,当用户使用用户名和密码登录后,返回一个ticket(或称为令牌、token等),并在后续的交互中使用这个ticket而不是用户名和密码,这种设计有几个重要的原因:
安全性:
防止泄露:用户名和密码是用户的敏感信息,如果每次请求都发送这些信息,有可能会被截获或泄露。使用ticket可以避免直接传输敏感信息。
限制访问:ticket通常具有过期时间,即使被截获,其危害也是有限的。同时,系统可以更容易地撤销或更改ticket,而无需用户更改密码。
性能:
减少认证开销:验证用户名和密码是一个相对昂贵的操作,因为它通常需要访问数据库或进行复杂的认证逻辑。使用ticket可以减少每次请求的认证开销,因为验证ticket通常更简单、更快。
可扩展性:使用ticket可以更容易地扩展系统。例如,可以将ticket验证逻辑分布到多个服务器上,而不需要每个请求都访问中心认证服务器。
用户体验:
无感知认证:用户只需要登录一次,就可以在多个请求之间自由切换,而不需要每次操作都重新输入用户名和密码。这提高了用户的便利性和体验。
单点登录(SSO):ticket机制使得实现单点登录变得可能,用户在一个系统中登录后,可以访问多个关联系统,而不需要重复输入用户名和密码。
审计和追踪:
细粒度控制:ticket可以包含更多的上下文信息,如用户权限、过期时间等,这使得系统可以对访问进行更细粒度的控制和审计。
综上所述,使用ticket机制不仅提高了系统的安全性、性能和用户体验,还使得系统管理更为方便和灵活。因此,在现代接口设计中,这是一种广泛采用的认证和授权方式。
"你好"的UTF-8编码字节数组表示为:E4BDA0 E5A5BD(16进制),System.out.print(getBytes(StandardCharsets.UTF_8))打印的“-28-67-96-27-91-67”(实际上是字节数组中每个字节的十进制表示。这是因为计算机内部以二进制形式存储数据,而字节是由8位二进制组成的数据单位。当字节的值超过127时(即最高位为1),在Java等语言中会将其视为负数进行处理和显示),getBytes(StandardCharsets.UTF_8)这个方法调用的确是在进行字符串到字节数组的转换,但它不是先将字符串hello用UTF-8编码成另一个字符串,而是直接将字符串根据UTF-8编码规则转换成相应的字节序列。E4 对应的二进制是 11100100。在计算机中,字节(8位二进制数)可以表示的十进制数值范围是0到255。然而,在Java等编程语言中,字节类型(byte)是有符号的,意味着它能够表示的范围是-128到127。当一个字节的二进制表示以1开头时,它被视为负数。具体到E4,其二进制表示为11100100,最高位是1,因此在Java的有符号字节类型中,它被解释为负数。为了得到这个负数值,可以使用一种称为“二进制补码”的表示法。简单来说,对于负数,其补码等于对该数的绝对值取反加1。所以,要得到E4作为负数的十进制值:首先,找到E4的反码(即每位取反):00011011然后,将反码加1得到补码:00011100,这表示28。因此,E4在有符号字节中表示为-28。

在Redis中,将key转换为字节数组(byte array)而不是直接使用字符串作为key,主要是出于以下几个原因:
节省内存:虽然在大多数情况下,直接使用字符串作为key已经非常高效,但对于大规模数据存储或特定场景下,将字符串序列化为字节数组可以减少内存的使用。这是因为字符串在Java等语言中存储时,除了字符内容外,还包括额外的信息(如长度、字符编码等),而字节数组则更加紧凑。
统一数据处理:在分布式系统或需要跨语言操作Redis的场景中,直接使用字节数组作为key可以避免字符编码转换的问题,确保不同系统间的一致性。字节数组是更底层、更通用的数据表示形式,可以被任何编程语言直接处理。
提高性能:在某些情况下,直接操作字节数组可能会带来性能上的优势,尤其是在需要频繁进行序列化和反序列化操作时。避免了字符串与字节数组之间的转换,可以减少CPU的计算负担。
特殊字符处理:直接使用字符串作为key时,如果字符串中包含特殊字符(如空格、控制字符等),可能需要进行额外的转义处理,以防止这些字符在操作Redis时引起问题。使用字节数组则可以避免这类问题,因为Redis直接操作的是原始的字节流。
自定义序列化策略:将key转换为字节数组后,开发者可以根据需要自定义序列化策略,比如使用更高效的序列化库(如Protobuf、Kryo等),这可以进一步提升性能和减少空间占用。
在 SQL 语句中,如果包含函数,函数的执行顺序通常与其他子句的执行顺序一致。以下是 SELECT 语句的执行顺序,包括函数的处理:
1、FROM:确定查询的表。
包括 JOIN 操作,如 LEFT JOIN、INNER JOIN 等。
例如:FROM table1 LEFT JOIN table2 ON table1.id = table2.id
2、WHERE:对表中的记录进行筛选。
例如:WHERE column1 = value1
3、GROUP BY:对记录进行分组。
例如:GROUP BY column1
4、SELECT:选择需要返回的列。
包括函数的计算,如 SUM、AVG、COUNT 等。
例如:SELECT column1, SUM(column2)
5、HAVING:对分组后的结果进行筛选。
例如:HAVING COUNT(column1) > 10
6、ORDER BY:对结果进行排序。
例如:ORDER BY column1 ASC
7、LIMIT:限制返回的记录数。
例如:LIMIT 10

SQL 语句中的 LEFT JOIN 操作是按照从左到右的顺序依次执行的。这意味着首先会执行 A LEFT JOIN B 得到一个结果集,然后再将这个结果集与 C 表进行 LEFT JOIN 操作。

当 B 表中有多条记录对应 A 表中的同一条记录时,INNER JOIN 会返回所有匹配的记录。
结果集中会包含多行相同的 A 表记录,每行对应 B 表中的一条匹配记录。

当查询中有 GROUP BY 时,COUNT 函数会为每个分组计算行数。当查询中没有 GROUP BY 时,COUNT 函数会计算整个结果集的行数。

// 用户登录成功后生成会话并存储在 cookie 中
HttpSession session = request.getSession(true);
session.setAttribute("userId", userId);
session.setAttribute("username", username);

// 创建 cookie 并设置 session ID
Cookie sessionCookie = new Cookie("JSESSIONID", session.getId());
sessionCookie.setPath("/");
response.addCookie(sessionCookie);

// 在服务器端会话表中记录该会话ID和用户ID
SessionTable sessionTable = new SessionTable();
sessionTable.setSessionId(session.getId());
sessionTable.setUserId(userId);
sessionTable.setActive(true);
sessionTable.save();

// 每次请求验证会话
HttpSession currentSession = request.getSession(false);
if (currentSession != null) {
String sessionId = currentSession.getId();
SessionTable existingSession = SessionTable.find(sessionId);
if (existingSession != null && existingSession.isActive()) {
// 会话有效
} else {
// 会话无效或已过期
response.sendRedirect("/login");
}
} else {
// 没有会话
response.sendRedirect("/login");
}

posted @ 2024-09-01 15:35  gsluofu  阅读(51)  评论(0)    收藏  举报