(二)实现Bean属性依赖注入功能【手撸Spring】

news2024/11/18 13:43:58

一、前言

在上一篇手撸Spring之实现一个简易版IoC容器,我们先把最简单的IoC架子搭了起来,即实现了一个Bean的注入,体会了依赖反转的过程。这里提到的依赖反转,大家肯定非常耳熟了,也就是将业务对Bean的依赖反转了,往常我们在使用一个Bean的时候,会在我们的业务代码中通过new创建一个Bean实例,而在IoC框架里,new创建的动作交给了框架来实现并注入到业务代码中去交给业务使用,如下图所示:

依赖反转

接下来,我们在前一篇的基础上,进一步丰富框架的功能:当一个类中有多个属性时,在xml配置上bean字段内容,框架实现对字段的注入。

<?xml version="1.0" encoding="utf-8" ?>
<beans>
    <!--setter 注入-->
    <bean id="a1" class="com.tiny.spring.test.bean.A">
        <property name="property1" value="property1"/>
        <property name="property2" value="property2"/>
        <property name="property3" value="property3"/>
        <property type="Integer" name="age" value="24"/>
    </bean>

    <!--构造器 注入-->
    <bean id="a2" class="com.tiny.spring.test.bean.A">
        <constructor-arg name="property1" value="property1"/>
        <constructor-arg name="property2" value="property2"/>
        <constructor-arg name="property3" value="property3"/>
        <property type="Integer" name="age" value="24"/>
    </bean>
</beans>

二、属性注入的实现原理

在Spring框架,实现属性注入的方式由以下两种:

  • 构造器注入
  • setter方法注入

下面我们介绍下两种注入方式的原理。

1、构造器注入

简单来讲,实现构造器注入的过程就是框架对xml中<constructor-arg>标签解析、将该标签下的元数据和ConstructorArgumentValues对象建立关系,将其设置到BeanDefinition中,并在createBean时,通过反射的方式调用对应的构造器函数创建实例。

2、setter方法注入

和构造器注入原理类似,框架解析<property>标签数据,将其和PropertyValues建立映射关系并设置到BeanDefinition中,在createBean时,通过反射的方式调用对应的setXxx方法实现参数注入。

3、理论正确性验证

我们进入到源码中也可以看到,事实确实是如此:(ps: spring框架实现的要远复杂很多,我们只关注核心的流程)

AbstractBeanDefinition

构造器创建实例

PropertyValues处理属性

3、Bean之间的依赖

我们前面提的属性都是普通属性,也就是基本类型+String,当涉及到其他引用类型需要注入时,Spring又是如何处理的?实际上,Spring设计的很巧妙,它在标签中增加了ref属性,这个属性就记了需要引用的另一个Bean,如下xml文件所示:

<?xml version="1.0" encoding="utf-8" ?>
<beans>
    <bean id="aService" class="com.tiny.spring.test.service.impl.AServiceImpl">
        <property type="com.tiny.spring.test.bean.A" name="a" ref="a1"/>
    </bean>
  
    <bean id="a1" class="com.tiny.spring.test.bean.A">
        <property name="property1" value="property1"/>
        <property name="property2" value="property2"/>
        <property name="property3" value="property3"/>
        <property type="Integer" name="age" value="24"/>
    </bean>
</beans>

Spring在加载xml文件时,会对ref属性做特殊处理,会对ref指定的bean做一次getBean(xx)操作,进而获取对应的Bean实例并注入到当前实例参数中。

spring对ref属性的特殊处理

解析字段值

循环依赖

如上所述,我们在处理Bean之间依赖的问题时,做这样的处理:

  • 获取实例A,发现容器中没有A的实例,则进入到实例A创建阶段
  • 创建A实例后,进行A实例属性填充时,发现其依赖B,则进入到获取B实例的阶段
  • 获取实例B,发现容器中没有B的实例,则进入到实例B创建阶段
  • 创建B实例后,进行B实例属性填充时,发现其又依赖A,则进入到获取A实例的阶段

这样一个操作,我们发现循环了,这可蛋疼了!这个问题也是我们经常听到的循环依赖。那么Spring是如何解决的呢?

这就引入了三级缓存的概念:

  • 一级缓存:存储最终的对象实例
  • 二级缓存:存储早期对象引用,就是创建完实例后填充实例前
  • 三级缓存:存储对象工厂(这个我们后面涉及到AOP时,再做说明)

其实在针对一般场景(即两个普通Bean对象间的循环依赖),二级缓存就够了。我们本节实现的代码也仅是实现了二级缓存,第三级缓存我们在AOP环节再做介绍和补充。也不能说AOP环节,在BeanPostProcessor实现时就会介入,而AOP功能的实现也依赖于BeanPostProcessor。

三、代码实现

1、流程图

针对本节要实现的功能,给出下面简图,让大家有个简单的了解:

Bean的生命周期

2、相关Bean

BeanDefinition

package com.tiny.spring.beans.factory.config;

import com.tiny.spring.beans.factory.ArgumentValues;
import com.tiny.spring.beans.factory.PropertyValues;

/**
 * @author: markus
 * @date: 2023/10/7 8:14 PM
 * @Description: Bean配置元信息
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class BeanDefinition {
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";
    /**
     * 是否是懒加载
     */
    private boolean lazyInit = false;
    /**
     *
     */
    private String[] dependsOn;
    private ArgumentValues constructorArgumentValues;
    private PropertyValues propertyValues;
    private String initMethodName;
    private volatile Object beanClass;
    private String id;
    private String className;
    private String scope = SCOPE_SINGLETON;

    // 构造器、setter、getter方法省略
}

PropertyValue

package com.tiny.spring.beans.factory;

/**
 * @author: markus
 * @date: 2023/10/8 11:04 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class PropertyValue {
    private final String type;
    private final String name;
    private final Object value;
    private final boolean isRef;
  
    // 构造器、setter、getter方法省略
}

PropertyValues

package com.tiny.spring.beans.factory;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: markus
 * @date: 2023/10/8 11:17 PM
 * @Description: Bean属性对象集合抽象
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class PropertyValues {
    private final List<PropertyValue> propertyValueList;

    public PropertyValues() {
        this.propertyValueList = new ArrayList<>(0);
    }

    public List<PropertyValue> getPropertyValueList() {
        return this.propertyValueList;
    }

    public int size() {
        return this.propertyValueList.size();
    }

    public void addPropertyValue(PropertyValue pv) {
        this.propertyValueList.add(pv);
    }

//    public void addPropertyValue(String propertyName, Object propertyValue) {
//        addPropertyValue(new PropertyValue(propertyName, propertyValue));
//    }

    public void removePropertyValue(PropertyValue pv) {
        this.propertyValueList.remove(pv);
    }

    public void removePropertyValue(String propertyName) {
        this.propertyValueList.remove(getPropertyValue(propertyName));
    }

    public PropertyValue[] getPropertyValues() {
        return this.propertyValueList.toArray(new PropertyValue[0]);
    }

    public PropertyValue getPropertyValue(String propertyName) {
        for (PropertyValue pv : this.propertyValueList) {
            if (pv.getName().equals(propertyName))
                return pv;
        }
        return null;
    }

    public Object get(String propertyName) {
        PropertyValue pv = getPropertyValue(propertyName);
        return pv != null ? pv.getValue() : null;
    }

    public boolean contains(String propertyName) {
        return getPropertyValue(propertyName) != null;
    }

    public boolean isEmpty() {
        return this.propertyValueList.isEmpty();
    }
}

ArgumentValue

package com.tiny.spring.beans.factory;

/**
 * @author: markus
 * @date: 2023/10/8 11:02 PM
 * @Description: 属性值抽象
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class ArgumentValue {
    private Object value;
    private String type;
    private String name;
}

ArgumentValues

package com.tiny.spring.beans.factory;

import com.sun.istack.internal.Nullable;

import java.util.*;

/**
 * @author: markus
 * @date: 2023/10/8 11:06 PM
 * @Description: 构造器参数集合抽象
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class ArgumentValues {
    private final List<ArgumentValue> argumentValueList = new ArrayList<>();

    public ArgumentValues() {
    }

    public void addArgumentValue(ArgumentValue argumentValue) {
        this.argumentValueList.add(argumentValue);
    }

    public ArgumentValue getIndexedArgumentValue(int index) {
        ArgumentValue argumentValue = this.argumentValueList.get(index);
        return argumentValue;
    }

    public int getArgumentCount() {
        return (this.argumentValueList.size());
    }

    public boolean isEmpty() {
        return (this.argumentValueList.isEmpty());
    }
}

XmlBeanDefinitionReader

package com.tiny.spring.beans.factory.xml;

import com.sun.media.sound.RIFFReader;
import com.tiny.spring.beans.factory.*;
import com.tiny.spring.beans.factory.config.BeanDefinition;
import com.tiny.spring.beans.factory.support.SimpleBeanFactory;
import com.tiny.spring.core.io.Resource;
import org.dom4j.Element;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: markus
 * @date: 2023/10/7 8:50 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class XmlBeanDefinitionReader {
    SimpleBeanFactory beanFactory;

    public XmlBeanDefinitionReader(SimpleBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public void loadBeanDefinitions(Resource resource) {
        while (resource.hasNext()) {
            Element element = (Element) resource.next();
            String beanId = element.attributeValue("id");
            String className = element.attributeValue("class");
            BeanDefinition beanDefinition = new BeanDefinition(beanId, className);
            // 处理属性
            List<Element> propertyElements = element.elements("property");
            PropertyValues pvs = new PropertyValues();
            // 存储属性中的引用对象 bean的id
            List<String> refs = new ArrayList<>();
            for (Element propertyElement : propertyElements) {
                String pType = propertyElement.attributeValue("type");
                String pName = propertyElement.attributeValue("name");
                String pValue = propertyElement.attributeValue("value");
                String pRef = propertyElement.attributeValue("ref");
                String pV = "";
                boolean isRef = false;
                if (pValue != null && !pValue.equals("")) {
                    pV = pValue;
                } else if (pRef != null && !pRef.equals("")) {
                    isRef = true;
                    pV = pRef;
                    refs.add(pRef);
                }
                pvs.addPropertyValue(new PropertyValue(pType, pName, pV, isRef));
            }
            beanDefinition.setPropertyValues(pvs);
            String[] refArray = refs.toArray(new String[0]);
            beanDefinition.setDependsOn(refArray);

            // 处理构造器参数
            List<Element> constructorArgumentElements = element.elements("constructor-arg");
            ArgumentValues avs = new ArgumentValues();
            for (Element constructorArgumentElement : constructorArgumentElements) {
                String aType = constructorArgumentElement.attributeValue("type");
                String aName = constructorArgumentElement.attributeValue("name");
                String aValue = constructorArgumentElement.attributeValue("value");
                avs.addArgumentValue(new ArgumentValue(aValue, aType, aName));
            }
            beanDefinition.setConstructorArgumentValues(avs);
            this.beanFactory.registerBeanDefinition(beanId, beanDefinition);
        }
    }
}

BeanDefinitionRegistry

package com.tiny.spring.beans.factory.support;

import com.tiny.spring.beans.factory.config.BeanDefinition;

/**
 * @author: markus
 * @date: 2023/10/7 11:57 PM
 * @Description: BeanDefinition注册器
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public interface BeanDefinitionRegistry {
    void registerBeanDefinition(String name, BeanDefinition bd);

    void removeBeanDefinition(String name);

    BeanDefinition getBeanDefinition(String name);

    boolean containsBeanDefinition(String name);
}

DefaultBeanDefinitionRegistry

package com.tiny.spring.beans.factory.support;

import com.tiny.spring.beans.factory.config.SingletonBeanRegistry;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: markus
 * @date: 2023/10/7 11:43 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {

    protected List<String> beanNames = new ArrayList<>();
    /*并发安全的单例Bean操作,保证容器中的Bean唯一*/
    protected Map<String, Object> singletons = new ConcurrentHashMap<>(256);
    /*早期Bean实例引用(实例被创建但还未被初始化),提前暴露,解决简单的循环依赖问题*/
    protected final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

    @Override
    public void registerSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletons) {
            this.singletons.put(beanName, singletonObject);
            this.beanNames.add(beanName);
        }
    }

    @Override
    public Object getSingleton(String beanName) {
        return this.singletons.get(beanName);
    }

    @Override
    public boolean containsSingleton(String beanName) {
        return this.singletons.containsKey(beanName);
    }

    @Override
    public String[] getSingletonNames() {
        return this.beanNames.toArray(new String[0]);
    }

    protected void removeSingleton(String beanName) {
        synchronized (this.singletons) {
            this.beanNames.remove(beanName);
            this.singletons.remove(beanName);
        }
    }
}

SingletonBeanRegistry

package com.tiny.spring.beans.factory.config;

/**
 * @author: markus
 * @date: 2023/10/7 11:40 PM
 * @Description: 单例Bean注册器
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public interface SingletonBeanRegistry {
    /**
     * 单例Bean注册
     * @param beanName
     * @param singletonObject
     */
    void registerSingleton(String beanName, Object singletonObject);

    /**
     * 获取单例对象
     * @param beanName
     * @return
     */
    Object getSingleton(String beanName);

    /**
     * 是否包含某个单例Bean
     * @param beanName
     * @return
     */
    boolean containsSingleton(String beanName);

    /**
     * 获取单例BeanName集合
     * @return
     */
    String[] getSingletonNames();
}

SimpleBeanFactory

package com.tiny.spring.beans.factory.support;

import com.tiny.spring.beans.BeansException;
import com.tiny.spring.beans.factory.*;
import com.tiny.spring.beans.factory.config.BeanDefinition;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: markus
 * @date: 2023/10/7 8:52 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class SimpleBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory, BeanDefinitionRegistry {

    /* bean definition map*/
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    private List<String> beanDefinitionNames = new ArrayList<>();

    public SimpleBeanFactory() {
    }

    @Override
    public Object getBean(String beanName) throws BeansException {
        // 先尝试直接拿bean实例
        Object singleton = getSingleton(beanName);
        if (singleton == null) {
            // 如果没有实例,则尝试从早期Bean引用缓存中去获取一下 todo 先在这里获取下,后续会像spring源码一样 抽象到DefaultSingletonBeanRegistry中去。
            singleton = this.earlySingletonObjects.get(beanName);
            if (singleton == null) {
                // 如果早期Bean引用缓存中还是没有,那就老老实实创建实例吧
                BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
                if (beanDefinition == null) {
                    throw new BeansException("The bean name does not exit. bean name [" + beanName + "]");
                } else {
                    // 获取Bean的定义
                    try {
                        singleton = createBean(beanDefinition);
                    } catch (BeansException e) {
                        throw e;
                    }
                    // 注册最终成形的Bean实例
                    this.registerSingleton(beanDefinition.getId(), singleton);
                }
            }
        }
        return singleton;
    }

    @Override
    public boolean containsBean(String beanName) {
        return containsSingleton(beanName);
    }

    @Override
    public boolean isSingleton(String beanName) {
        // todo 这里可能会抛空指针
        return getBeanDefinition(beanName).isSingleton();
    }

    @Override
    public boolean isPrototype(String beanName) {
        // todo 这里可能会抛空指针
        return getBeanDefinition(beanName).isPrototype();
    }

    @Override
    public Class<?> getType(String beanName) {
        // todo 这里可能会抛空指针
        return getBeanDefinition(beanName).getClass();
    }

    @Override
    public void registerBeanDefinition(String name, BeanDefinition bd) {
        this.beanDefinitionMap.put(name, bd);
        this.beanDefinitionNames.add(name);
        if (!bd.isLazyInit()) {
            try {
                getBean(name);
            } catch (BeansException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void removeBeanDefinition(String name) {
        this.beanDefinitionMap.remove(name);
        this.beanDefinitionNames.remove(name);
        this.removeSingleton(name);
    }

    @Override
    public BeanDefinition getBeanDefinition(String name) {
        return this.beanDefinitionMap.get(name);
    }

    @Override
    public boolean containsBeanDefinition(String name) {
        return this.beanDefinitionMap.containsKey(name);
    }

    private Object createBean(BeanDefinition beanDefinition) throws BeansException {
        Class<?> clazz = null;
        // 创建实例
        Object obj = doCreateBean(beanDefinition);
        // 将创建好的实例 存储到早期Bean引用缓存中
        this.earlySingletonObjects.put(beanDefinition.getId(), obj);
        try {
            clazz = Class.forName(beanDefinition.getClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // 处理属性
        handleProperties(beanDefinition, clazz, obj);
        return obj;
    }

    /**
     * 仅仅是创建实例对象,并没有赋值
     *
     * @param bd
     * @return
     * @throws BeansException
     */
    private Object doCreateBean(BeanDefinition bd) throws BeansException {
        Class<?> clazz = null;
        Object obj = null;
        Constructor<?> constructor = null;

        try {
            clazz = Class.forName(bd.getClassName());
            // 处理构造器参数
            ArgumentValues argumentValues = bd.getConstructorArgumentValues();
            // 如果有参数
            if (!argumentValues.isEmpty()) {
                Class<?>[] paramTypes = new Class<?>[argumentValues.getArgumentCount()];
                Object[] paramValues = new Object[argumentValues.getArgumentCount()];
                // 对每一个参数,分数据类型分别处理
                for (int i = 0; i < argumentValues.getArgumentCount(); i++) {
                    ArgumentValue argumentValue = argumentValues.getIndexedArgumentValue(i);
                    if ("String".equals(argumentValue.getType()) || "java.lang.String".equals(argumentValue.getType())) {
                        paramTypes[i] = String.class;
                        paramValues[i] = argumentValue.getValue();
                    } else if ("Integer".equals(argumentValue.getType()) || "java.lang.Integer".equals(argumentValue.getType())) {
                        paramTypes[i] = Integer.class;
                        paramValues[i] = Integer.valueOf((String) argumentValue.getValue());
                    } else if ("int".equals(argumentValue.getType())) {
                        paramTypes[i] = int.class;
                        paramValues[i] = Integer.valueOf((String) argumentValue.getValue());
                    } else { //默认为string
                        paramTypes[i] = String.class;
                        paramValues[i] = argumentValue.getValue();
                    }
                }
                // 按照特定构造器创建实例
                constructor = clazz.getConstructor(paramTypes);
                obj = constructor.newInstance(paramValues);
            } else {
                // 如果没有构造器参数,则直接使用默认构造器创建实例即可
                obj = clazz.newInstance();
            }
        } catch (Exception e) {
            throw new BeansException("bean constructor invoke error,bean id is" + bd.getId());
        }
        return obj;
    }

    private void handleProperties(BeanDefinition bd, Class<?> clazz, Object obj) throws BeansException {
        // 处理属性
        System.out.println("handle properties for bean : " + bd.getId());
        // 如果有属性值的话,进行属性赋值
        PropertyValues pvs = bd.getPropertyValues();
        if (!pvs.isEmpty()) {
            for (int i = 0; i < pvs.getPropertyValues().length; i++) {
                // 对每一个属性,分数据类型分别处理
                PropertyValue propertyValue = pvs.getPropertyValues()[i];
                String pType = propertyValue.getType();
                String pName = propertyValue.getName();
                Object pValue = propertyValue.getValue();
                boolean isRef = propertyValue.isRef();

                Class[] paramTypes = new Class[1];
                Object[] paramValues = new Object[1];
                if (!isRef) {
                    // 如果不是 ref,只是普通属性,则对该属性分数据类型处理
                    if ("String".equals(pType) || "java.lang.String".equals(pType)) {
                        paramTypes[0] = String.class;
                        paramValues[0] = pValue;
                    } else if ("Integer".equals(pType) || "java.lang.Integer".equals(pType)) {
                        paramTypes[0] = Integer.class;
                        paramValues[0] = Integer.valueOf((String) pValue);
                    } else if ("int".equals(pType)) {
                        paramTypes[0] = int.class;
                        paramValues[0] = Integer.valueOf((String) pValue);
                    } else { // 默认为string
                        paramTypes[0] = String.class;
                        paramValues[0] = pValue;
                    }
                } else {
                    // 是 ref,则通过 getBean()方式获取引用Bean实例
                    try {
                        paramTypes[0] = Class.forName(pType);
                        paramValues[0] = getBean((String) pValue);
                    } catch (ClassNotFoundException e) {
                        throw new BeansException("ref bean " + pValue + " not found!");
                    }
                }


                // 按照setXxx规范查找setter方法,调用setter方法设置属性
                String methodName = "set" + pName.substring(0, 1).toUpperCase() + pName.substring(1);
                Method method = null;
                try {
                    method = clazz.getMethod(methodName, paramTypes);
                    method.invoke(obj, paramValues);
                } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                    throw new BeansException("bean setter method invoke error,bean id is " + bd.getId() + ",method is " + methodName);
                }

            }
        }
    }
}

四、本文总结

好了,本文介绍了构造器注入、setter方法注入的实现原理、Bean依赖的问题以及循环依赖问题Spring是如何做的,基于原理我们实现了一个非常简单的属性注入功能,目前类结构还不复杂,大家可以在我的github项目tiny-spring中直接查看,有不明白的地方也可以评论我。

目录结构

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

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

相关文章

【网络】网络编程——带你手搓简易TCP服务端(echo服务器)+客户端(四种版本)

这里写自定义目录标题 前言正式开始用生活中的例子来讲解TCP服务端和客户端代码讲解服务端基本框架创建套接字 bindlisten监听accept接收连接通信单线程版多进程①版多进程②版多线程版线程池版 客户端 收尾 前言 本篇主要讲解套接字编程&#xff0c;以TCP服务端和客户端为主…

MySQL为什么用b+树

索引是一种数据结构&#xff0c;用于帮助我们在大量数据中快速定位到我们想要查找的数据。 索引最形象的比喻就是图书的目录了。注意这里的大量&#xff0c;数据量大了索引才显得有意义&#xff0c;如果我想要在[1,2,3,4]中找到4这个数据&#xff0c;直接对全数据检索也很快&am…

SP605官方开发板不能扫到链的问题

很早之前的板子&#xff0c;近些天需要重新搞FPGA&#xff0c;所以又拿出来&#xff0c;应该以前都是在win7下开发&#xff0c;现在都win10了&#xff0c;vivado都不支持sp6&#xff0c;所以先得下载一个14.7版本&#xff0c;但是出现了新的问题&#xff0c;就是不能扫到链。 …

windows认证机制_NTLM

文章目录 概念本地认证 NTLMLMNTML原理网络认证 Net NTLM实际测试利用hash传递浏览上传⽂件使用 mimikatz hash传递获取域控RDPmimikatz工具使用案例 概念 原文参考&#xff1a;xiu --》http://www.xiusafe.com/ 域渗透就是基于 windows 域环境的渗透&#xff0c;而域渗透涉及…

第二证券:中概股,昨夜狂飙!

当地时间10月10日&#xff0c;美股三大股指集体收高&#xff0c;其间道指涨0.40%&#xff0c;标普指数涨0.52%&#xff0c;纳斯达克指数涨0.58%。多位美联储官员鸽派讲话支撑不再加息&#xff0c;投资者等待美联储9月会议纪要、美国CPI通胀数据及企业三季报等要素支撑美股持续走…

基于Docker来部署Nacos的注册中心

基于Docker来部署Nacos的注册中心 准备MySQL数据库表nacos.sql&#xff0c;用来存储Nacos的数据。 最终表结构如下&#xff1a; 在本地nacos/custom.env文件中&#xff0c;有一个MYSQL_SERVICE_HOST也就是mysql地址&#xff0c;需要修改为你自己的虚拟机IP地址&#xff1a; …

铝型材公司【Brilliance Group】申请1080万美元纳斯达克IPO上市

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于新加坡的铝型材公司【Brilliance Group】近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯达克IPO上市&#xff0c;股票代码为(KHIW),Brilliance…

python爬虫练手项目之获取某地企业名录

因为很多网站都增加了登录验证&#xff0c;所以需要添加一段利用cookies跳过登陆验证码的操作 import pandas as pd import requests from lxml import etree # 通过Chrome浏览器F12来获取cookies&#xff0c;agent&#xff0c;headers cookies {ssxmod_itna2:eqfx0DgQGQ0QGDC…

python每日一练(5)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

网页报错 Uncaught TypeError: a(...).tooltip is not a function

网页报错 Uncaught TypeError: a(…).tooltip is not a function 网页上f12看了一下控制台有一个报错 &#xff1a;Uncaught TypeError: a(…).tooltip is not a function 排查查找了一下原因&#xff0c;是网页嵌套&#xff0c;报错原因是引入了两次jquery&#xff0c;注释掉…

数学建模、统计建模、计量建模整体框架的理解以及建模的步骤

数学建模、统计建模、计量建模整体框架的理解以及建模的步骤 引言正文模型的设定模型的估计建模中可能遇到的四种数据类型 模型的检验模型的应用 最后 引言 这篇博客主要写给统计或者数学专业的小白&#xff0c;以供快速上手建模比赛&#xff1b;本人将在这里整合参加建模比赛…

基于nodemailer实现邮件发送

概述 node中可用nodemailer实现邮件的发送。本文使用QQ邮箱实现邮件的发送。 实现效果 实现 1. QQ邮箱配置 首先需要开启POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务&#xff0c;如下图所示。 生成授权码 2. 发送邮件 发送邮件的代码比较简单&#xff0c;如下&#xf…

197、管理 RabbitMQ 的虚拟主机

开启Rabbitmq的一些命令&#xff1a; 小黑窗输入&#xff1a; rabbitmq-plugins enable rabbitmq_management 启动控制台插件&#xff0c; 就是启动登录rabbitmq控制台的页面&#xff0c;rabbitmq_management 代表了RabbitMQ的管理界面。 rabbitmq-server 启动rabbitMQ服务器…

Jetson Orin NX 开发指南(5): 安装 OpenCV 4.6.0 并配置 CUDA 以支持 GPU 加速

一、前言 Jetson 系列的开发板 CPU 性能不是很强&#xff0c;往往需要采用 GPU 加速的方式处理图像数据&#xff0c;因此本文主要介绍如何安装带有 GPU 加速的 OpenCV&#xff0c;其中 GPU 加速通过 CUDA 来实现。 参考博客 Ubuntu 20.04 配置 VINS-Fusion-gpu OpenCV 4.6.…

分布式锁3:基于redis的插件redission实现分布式锁

一 redision简介 1.1 redission介绍 Redisson分布式锁是一种基于redis实现的分布式锁&#xff0c;它利用redis的setnx命令实现分布式锁的互斥访问。同时还支持锁的自动续期功能&#xff0c;可以避免因为某个进程崩溃或者网络故障导致锁无法释放的情况。 只要线程一加锁成功…

Java8实战-总结41

Java8实战-总结41 用Optional取代nullOptional 类入门应用 Optional 的几种模式创建 Optional 对象使用 map 从 Optional 对象中提取和转换值 用Optional取代null Optional 类入门 Java 8中引入了一个新的类java.util.Optional<T>。这是一个封装Optional值的类。举例来…

数据库基础(一)

数据库面试基础 注&#xff0c;本文章内容主要来自于JAVAGUIDE&#xff0c;只是结合网上资料和自己的知识缺陷进行一点补充&#xff0c;需要准备面试的请访问官方网址。 一、范式 参考链接 函数依赖&#xff1a;一张表中&#xff0c;确定X则必定能确定Y&#xff0c;则X->…

Factory-Method

Factory-Method 动机 在软件系统中&#xff0c;经常面临着创建对象的工作&#xff1b;由于需求的变化&#xff0c;需要创建的对象的具体类型经常变化。如何应对这种变化&#xff1f;如何绕过常规的对象创建方法(new)&#xff0c;提供一种“封装机制”来避免客户程序和这种“具…

【JQuery插件】手把手教你如何白瓢一个网站的全部付费资源!前端狂喜

视频讲解地址&#xff1a;https://www.bilibili.com/video/BV1bm4y157GF/ 网站地址&#xff1a;https://www.jq22.com/ 大家好&#xff0c;这一集给大家分享一个好用的JQuery插件网站&#xff0c;当然&#xff0c;更主要的是教大家如何白瓢这个网站上的资源&#xff0c;这个网…

Harmony ArkTS语言

ArkTS语言 前言正文一、声明式UI二、数据列表① 创建ArkTS文件② 添加资源③ 样式④ 组件⑤ 标题组件⑥ 列表头组件⑦ 列表Item组件⑧ 组件生命周期⑨ 渲染列表数据⑩ 单选 三、源码 随着华为宣布鸿蒙后续的版本不再兼容Android应用之后&#xff0c;对于现在的开发环境来说有一…