【开源项目】AOP框架Nepxion Matrix原理拆解

news2024/12/28 19:38:38

项目地址

项目地址:https://toscode.gitee.com/nepxion/Matrix

原理分析

Spring AutoProxy机制

它统一封装接口(Spring)代理和类代理(CGLIB),注解无论在接口和类的头部或者方法上,都可以让业务端执行有效切面,可以轻松快速实现对接口或者类的复杂代理业务

DefaultAutoScanProxy,继承DefaultAutoScanProxy,定义包扫描路径,代理模式和扫描模式;指定拦截器和注解。

@Component("myAutoScanProxyForClass")
public class MyAutoScanProxyForClass extends DefaultAutoScanProxy {
    private static final long serialVersionUID = -5968030133395182024L;

    // 多个包路径,用“;”分隔
    private static final String SCAN_PACKAGES = "com.charles";

    @SuppressWarnings("rawtypes")
    private Class[] commonInterceptorClasses;

    @SuppressWarnings("rawtypes")
    private Class[] classAnnotations;

    public MyAutoScanProxyForClass() {
        super(SCAN_PACKAGES, ProxyMode.BY_CLASS_ANNOTATION_ONLY, ScanMode.FOR_CLASS_ANNOTATION_ONLY);
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Class<? extends MethodInterceptor>[] getCommonInterceptors() {
        if (commonInterceptorClasses == null) {
            commonInterceptorClasses = new Class[]{MyInterceptor1.class};
        }
        return commonInterceptorClasses;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Class<? extends Annotation>[] getClassAnnotations() {
        if (classAnnotations == null) {
            classAnnotations = new Class[]{Annotation1.class};
        }
        return classAnnotations;
    }

    @Override
    protected void classAnnotationScanned(Class<?> targetClass, Class<? extends Annotation> classAnnotation) {
        System.out.println("Class annotation scanned, targetClass=" + targetClass + ", classAnnotation=" + classAnnotation);
    }
}

AbstractAutoProxyCreator#postProcessAfterInitialization,入口类在AbstractAutoProxyCreator。判断类是否被拦截,如果被拦截,获取被拦截后的代理类。

	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

Aspect获取拦截器是用的AbstractAdvisorAutoProxyCreator,而Nepxion Matrix实现的子类是是AbstractAutoScanProxy,那么分析一下AbstractAutoScanProxy#getAdvicesAndAdvisorsForBean是怎么获取拦截器类的。

  • 判断当前类是否在扫描包的目录下
  • 获取接口或者实现类的拦截器
    @Override
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
        boolean scanPackagesEnabled = scanPackagesEnabled();
        // scanPackagesEnabled=false,表示“只扫描指定目录”的方式未开启,则不会对扫描到的bean进行代理预先判断
        if (scanPackagesEnabled) {
            boolean scanPackagesContained = scanPackagesContained(beanClass);
            // 如果beanClass的类路径,未包含在扫描目录中,返回DO_NOT_PROXY
            if (!scanPackagesContained) {
                return DO_NOT_PROXY;
            }
        }

        // 根据Bean名称获取Bean对象
        Object bean = beanMap.get(beanName);

        // 获取最终目标类
        Class<?> targetClass = null;
        if (bean != null /* && AopUtils.isCglibProxy(bean) */) {
            targetClass = AopProxyUtils.ultimateTargetClass(bean);
        } else {
            targetClass = beanClass;
        }

        // Spring容器扫描实现类
        if (!targetClass.isInterface()) {
            // 扫描接口(从实现类找到它的所有接口)
            if (targetClass.getInterfaces() != null) {
                for (Class<?> targetInterface : targetClass.getInterfaces()) {
                    Object[] proxyInterceptors = scanAndProxyForTarget(targetInterface, beanName, false);
                    if (proxyInterceptors != DO_NOT_PROXY) {
                        return proxyInterceptors;
                    }
                }
            }

            // 扫描实现类(如果接口上没找到注解, 就找实现类的注解)
            Object[] proxyInterceptors = scanAndProxyForTarget(targetClass, beanName, true);
            if (proxyInterceptors != DO_NOT_PROXY) {
                return proxyInterceptors;
            }
        }

        return DO_NOT_PROXY;
    }

AbstractAutoScanProxy#scanAndProxyForTarget,根据代理模式,获取类上的或者方法上的注解

    protected Object[] scanAndProxyForTarget(Class<?> targetClass, String beanName, boolean proxyTargetClass) {
        String targetClassName = targetClass.getCanonicalName();
        Object[] interceptors = getInterceptors(targetClass);
        // 排除java开头的接口,例如java.io.Serializable,java.io.Closeable等,执行不被代理
        if (StringUtils.isNotEmpty(targetClassName) && !targetClassName.startsWith("java.")) {
            // 避免对同一个接口或者类扫描多次
            Boolean proxied = proxyMap.get(targetClassName);
            if (proxied != null) {
                if (proxied) {
                    return interceptors;
                }
            } else {
                Object[] proxyInterceptors = null;
                switch (proxyMode) {
                    // 只通过扫描到接口名或者类名上的注解后,来确定是否要代理
                    case BY_CLASS_ANNOTATION_ONLY:
                        proxyInterceptors = scanAndProxyForClass(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
                        break;
                    // 只通过扫描到接口或者类方法上的注解后,来确定是否要代理
                    case BY_METHOD_ANNOTATION_ONLY:
                        proxyInterceptors = scanAndProxyForMethod(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
                        break;
                    // 上述两者都可以
                    case BY_CLASS_OR_METHOD_ANNOTATION:
                        Object[] classProxyInterceptors = scanAndProxyForClass(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
                        // 没有接口或者类名上扫描到目标注解,那么扫描接口或者类的方法上的目标注解
                        Object[] methodProxyInterceptors = scanAndProxyForMethod(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
                        if (classProxyInterceptors != DO_NOT_PROXY || methodProxyInterceptors != DO_NOT_PROXY) {
                            proxyInterceptors = interceptors;
                        } else {
                            proxyInterceptors = DO_NOT_PROXY;
                        }
                        break;
                }

                // 是否需要代理
                proxyMap.put(targetClassName, Boolean.valueOf(proxyInterceptors != DO_NOT_PROXY));

                if (proxyInterceptors != DO_NOT_PROXY) {
                    // 是接口代理还是类代理
                    proxyTargetClassMap.put(beanName, proxyTargetClass);

                    /*LOG.info("------------ Matrix Proxy Information -----------");
                    Class<? extends MethodInterceptor>[] commonInterceptorClasses = getCommonInterceptors();
                    if (ArrayUtils.isNotEmpty(commonInterceptorClasses)) {
                        LOG.info("Class [{}] is proxied by common interceptor classes [{}], proxyTargetClass={}", targetClassName, ProxyUtil.toString(commonInterceptorClasses), proxyTargetClass);
                    }

                    String[] commonInterceptorNames = getCommonInterceptorNames();
                    if (ArrayUtils.isNotEmpty(commonInterceptorNames)) {
                        LOG.info("Class [{}] is proxied by common interceptor beans [{}], proxyTargetClass={}", targetClassName, ProxyUtil.toString(commonInterceptorNames), proxyTargetClass);
                    }

                    if (proxyInterceptors != PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS && ArrayUtils.isNotEmpty(proxyInterceptors)) {
                        LOG.info("Class [{}] is proxied by additional interceptors [{}], proxyTargetClass={}", targetClassName, proxyInterceptors, proxyTargetClass);
                    }
                    LOG.info("-------------------------------------------------");*/
                }

                return proxyInterceptors;
            }
        }

        return DO_NOT_PROXY;
    }

AbstractAutoScanProxy#getInterceptors,获取拦截器。优先执行getAdditionalInterceptors获取拦截器,获取不到再执行getCommonInterceptors

    protected Object[] getInterceptors(Class<?> targetClass) {
        Object[] interceptors = getAdditionalInterceptors(targetClass);
        if (ArrayUtils.isNotEmpty(interceptors)) {
            return interceptors;
        }

        Class<? extends MethodInterceptor>[] commonInterceptorClasses = getCommonInterceptors();
        String[] commonInterceptorNames = getCommonInterceptorNames();
        if (ArrayUtils.isNotEmpty(commonInterceptorClasses) || ArrayUtils.isNotEmpty(commonInterceptorNames)) {
            return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS;
        }

        return DO_NOT_PROXY;
    }

AbstractAutoScanProxy#scanAndProxyForClass,扫描类上的注解。

AbstractAutoScanProxy#scanAndProxyForMethod,扫描方法上的注解。

    protected Object[] scanAndProxyForClass(Class<?> targetClass, String targetClassName, String beanName, Object[] interceptors, boolean proxyTargetClass) {
        // 判断目标注解是否标注在接口名或者类名上
        boolean proxied = false;
        Class<? extends Annotation>[] classAnnotations = getClassAnnotations();
        if (ArrayUtils.isNotEmpty(classAnnotations)) {
            for (Class<? extends Annotation> classAnnotation : classAnnotations) {
                if (targetClass.isAnnotationPresent(classAnnotation)) {
                    // 是否执行“注解扫描后处理”
                    if (scanMode == ScanMode.FOR_CLASS_ANNOTATION_ONLY || scanMode == ScanMode.FOR_CLASS_OR_METHOD_ANNOTATION) {
                        classAnnotationScanned(targetClass, classAnnotation);
                    } else {
                        // 如果“注解扫描后处理”不开启,没必要再往下执行循环,直接返回
                        return interceptors;
                    }

                    // 目标注解被扫描到,proxied赋值为true,即认为该接口或者类被代理
                    if (!proxied) {
                        proxied = true;
                    }
                }
            }
        }

        return proxied ? interceptors : DO_NOT_PROXY;
    }

    protected Object[] scanAndProxyForMethod(Class<?> targetClass, String targetClassName, String beanName, Object[] interceptors, boolean proxyTargetClass) {
        // 判断目标注解是否标注在方法上
        boolean proxied = false;
        Class<? extends Annotation>[] methodAnnotations = getMethodAnnotations();
        if (ArrayUtils.isNotEmpty(methodAnnotations)) {
            for (Method method : targetClass.getDeclaredMethods()) {
                for (Class<? extends Annotation> methodAnnotation : methodAnnotations) {
                    if (method.isAnnotationPresent(methodAnnotation)) {
                        // 是否执行“注解扫描后处理”
                        if (scanMode == ScanMode.FOR_METHOD_ANNOTATION_ONLY || scanMode == ScanMode.FOR_CLASS_OR_METHOD_ANNOTATION) {
                            methodAnnotationScanned(targetClass, method, methodAnnotation);
                        } else {
                            // 如果“注解扫描后处理”不开启,没必要再往下执行循环,直接返回
                            return interceptors;
                        }

                        // 目标注解被扫描到,proxied赋值为true,即认为该接口或者类被代理
                        if (!proxied) {
                            proxied = true;
                        }
                    }
                }
            }
        }

        return proxied ? interceptors : DO_NOT_PROXY;
    }

AbstractInterceptor,抽象拦截器定义了一堆封装好的方法,获取方法名称,代理类名,类上的注解和方法上的注解。

@Component("myInterceptor3")
public class MyInterceptor3 extends AbstractInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        String proxyClassName = getProxyClassName(invocation);
        Object[] arguments = getArguments(invocation);
        String proxiedClassName = getProxiedClassName(invocation);
        Class<?>[] proxiedInterfaces = getProxiedInterfaces(invocation);
        Annotation[] classAnnotations = getProxiedClassAnnotations(invocation);
        String methodName = getMethodName(invocation);
        Annotation[] methodAnnotations = getMethodAnnotations(invocation);
        String[] parameterNames = getMethodParameterNames(invocation);
    }
}

代理模式:根据类上或者方法上的注解来生成代理

扫描模式:根据类上或者方法上的注解来执行methodAnnotationScanned或者classAnnotationScanned

Spring Registrar机制

实现象@FeignClient注解那样,只有接口没有实现类,就能实现注入和动态代理。

EnableMyAnnotation注入MyRegistrar

MyRegistrar继承了AbstractRegistrarAbstractRegistrar实现了ImportBeanDefinitionRegistrar

  • 根据MyRegistrar配置的getAnnotationClass注解类
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registerAnnotations(metadata, registry);
    }

    public void registerAnnotations(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);

        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(getAnnotationClass());
        scanner.addIncludeFilter(annotationTypeFilter);
        Set<String> basePackages = getBasePackages(metadata);

        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    // verify annotated class is an interface
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();

                    Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(getAnnotationClass().getCanonicalName());
                    registerAnnotation(registry, annotationMetadata, attributes);
                }
            }
        }
    }

AbstractRegistrar#getBasePackages,根据getEnableAnnotationClass获取扫描包的路径。获取使用EnableMyAnnotation注解的包名。

    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(getEnableAnnotationClass().getCanonicalName());

        Set<String> basePackages = new HashSet<>();
        for (String pkg : (String[]) attributes.get("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }

        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }

        return basePackages;
    }

AbstractRegistrar#registerAnnotation,根据配置的getBeanClassgetInterceptor(beanDefinition.getPropertyValues()),生成MyRegistrarFactoryBean实例,配置拦截器和接口的属性,customize将注解的属性数据配置在实体类,以及类名。

    private void registerAnnotation(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();

        LOG.info("Found annotation [{}] in {} ", getAnnotationClass().getSimpleName(), className);

        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(getBeanClass());
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

        customize(registry, annotationMetadata, attributes, definition);

        try {
            definition.addPropertyValue("interfaze", Class.forName(className));
        } catch (ClassNotFoundException e) {
            LOG.error("Get interface for name error", e);
        }
        definition.addPropertyValue("interceptor", getInterceptor(beanDefinition.getPropertyValues()));

        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

        String alias = className;

        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

RegistrarFactoryBean实现了FactoryBean,Spring容器获取到的就是proxy;实现了InitializingBean,调用代理类proxyFactory.getProxy(classLoader)获取到代理类。

public class RegistrarFactoryBean implements ApplicationContextAware, FactoryBean<Object>, InitializingBean, BeanClassLoaderAware {
    private ApplicationContext applicationContext;
    private Class<?> interfaze;
    private MethodInterceptor interceptor;
    private Object proxy;
    private ClassLoader classLoader;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public Object getObject() throws Exception {
        return proxy;
    }

    @Override
    public Class<?> getObjectType() {
        return interfaze;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addInterface(interfaze);
        proxyFactory.addAdvice(interceptor);
        proxyFactory.setOptimize(false);

        proxy = proxyFactory.getProxy(classLoader);
    }
}

总结一下:拦截注解,生成对应接口的代理类。所以如果标注的接口有实现,需要标注@Primary

Spring Import Selector机制

实现象@EnableCircuitBreaker注解那样,入口加上@EnableMyAnnotation,自动初始化对应的Configuration。

EnableMyAnnotation注入了EnableMyAnnotationImportSelector

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableMyAnnotationImportSelector.class)
public @interface EnableMyAnnotation {
    boolean extension() default true;
}

EnableMyAnnotationImportSelector,调用父类的selectImports。判断注解上extension属性值,如果为true,加载MyConfigurationExtension,否则环境变更存入extension.enabled=false

@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableMyAnnotationImportSelector extends AbstractImportSelector<EnableMyAnnotation> {
    // 如下方法适合EnableXXX注解上带有参数的情形,一般用不到
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        // 获取父类的Configuration列表
        String[] imports = super.selectImports(metadata);

        // 从注解上获取参数
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
        boolean extension = attributes.getBoolean("extension");

        if (extension) {
            // 如果EnableMyAnnotation注解上的extension为true,那么去装载MyConfigurationExtension,即初始化里面的MyBeanExtension
            List<String> importsList = new ArrayList<>(Arrays.asList(imports));
            importsList.add(MyConfigurationExtension.class.getCanonicalName());
            imports = importsList.toArray(new String[0]);
        } else {
            // 如果EnableMyAnnotation注解上的extension为false,那么你可以把该参数动态放到属性列表里
            Environment environment = getEnvironment();
            if (ConfigurableEnvironment.class.isInstance(environment)) {
                ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment;
                LinkedHashMap<String, Object> map = new LinkedHashMap<>();
                map.put("extension.enabled", false);
                MapPropertySource propertySource = new MapPropertySource("nepxion", map);
                configurableEnvironment.getPropertySources().addLast(propertySource);
            }
        }

        return imports;
    }
}

AbstractImportSelector#selectImports,用Spring SPI加载指定的注解类。而注解类的获取是获取子类上面的泛型。

    protected AbstractImportSelector() {
        this.annotationClass = (Class<T>) GenericTypeResolver.resolveTypeArgument(this.getClass(), AbstractImportSelector.class);
    }

	@Override
    public String[] selectImports(AnnotationMetadata metadata) {
        if (!isEnabled()) {
            return new String[0];
        }

        AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(this.annotationClass.getName(), true));

        Assert.notNull(attributes, "No " + getSimpleName() + " attributes found. Is " + metadata.getClassName() + " annotated with @" + getSimpleName() + "?");

        // Find all possible auto configuration classes, filtering duplicates
        List<String> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(this.annotationClass, this.beanClassLoader)));

        if (factories.isEmpty() && !hasDefaultFactory()) {
            throw new IllegalStateException("Annotation @" + getSimpleName() + " found, but there are no implementations. Did you forget to include a starter?");
        }

        if (factories.size() > 1) {
            // there should only ever be one DiscoveryClient, but there might be more than one factory
            LOG.warn("More than one implementation " + "of @" + getSimpleName() + " (now relying on @Conditionals to pick one): " + factories);
        }

        return factories.toArray(new String[factories.size()]);
    }

在这里插入图片描述

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

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

相关文章

c++ 11标准模板(STL) std::set(七)

定义于头文件 <set> template< class Key, class Compare std::less<Key>, class Allocator std::allocator<Key> > class set;(1)namespace pmr { template <class Key, class Compare std::less<Key>> using se…

CMS搭建篇:内容模型配置-用户管理模型

微信小程序云开发实战-答题积分赛小程序 CMS搭建篇:内容模型配置-用户管理模型 内容模型 内容模型是对数据库中存储的数据结构的描述,包含了内容的属性定义。通过内容模型,内容管理可以自动生成内容管理界面。 这里,我们需要建立一个内容模型,描述用户所具有的属性,如:微…

debounce(防抖)和throttle(节流)小结

前端工程师们都听过看起来很高级的词&#xff0c;节流和防抖&#xff0c;其实节流就是throttle&#xff0c;防抖就是debounce&#xff0c;其实这个也属于前端性能优化的一部分。 节流 像阀门一样控制水流&#xff0c;避免单位时间内流量过大防抖 防止抖动&#xff0c;比节流的…

2023-5-17-CPU架构学习(amd、ard等)

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…

Redis高可用性详解

目录 ​编辑 高可用性&#xff1a; 主从复制&#xff08;Master-Slave Replication&#xff09;&#xff1a; 主从复制的一般工作流程&#xff1a; 哨兵模式&#xff08;Sentinel Mode&#xff09;&#xff1a; 哨兵模式的一般工作流程&#xff1a; 集群模式&#xff08…

【大数据学习篇7】 热门品类Top10分析

在HBase命令行工具中执行“list”命令&#xff0c;查看HBase数据库中的所有数据表。学习目标/Target 掌握热门品类Top10分析实现思路 掌握如何创建Spark连接并读取数据集 掌握利用Spark获取业务数据 掌握利用Spark统计品类的行为类型 掌握利用Spark过滤品类的行为类型 掌握利用…

梯度消失和爆炸问题

一、为什么会产生梯度消失和梯度爆炸&#xff1f; 目前优化神经网络的方法都是基于BP&#xff0c;即根据损失函数计算的误差通过梯度反向传播的方式&#xff0c;指导深度网络权值的更新优化。其中将误差从末层往前传递的过程需要链式法则&#xff08;Chain Rule&#xff09;的…

自定义mysql函数之字符串逗号分割查询(find_in_set)

增强 find_in_set() 在mysql中&#xff0c;我们有时候设计数据库某个字段需要通过逗号进行分割&#xff0c;然后根据传入的字符串查询是否存在的方法进行判断&#xff0c;mysql默认的 find_in_set() 可以对比某个逗号分割的字符串中是否存在指定字符串&#xff0c;例如下面的例…

小程序技术,打开跨端管理的思路,提高客户满意度和忠诚度

小程序容器作为跨端管理的有效工具&#xff0c;已经成为越来越多企业的选择。通过小程序容器&#xff0c;企业可以实现跨平台部署&#xff0c;提供一致的用户体验&#xff0c;整合多种渠道实现全渠道协同&#xff0c;进行个性化营销&#xff0c;以及通过数据分析和监控等手段优…

手把手教你,用Auto-GPT自动写个网站(保姆级)

目录 一、什么是 Auto-GPT 二、用Auto-GPT自动给我实现了一个网站 1、运行过程 2、执行任务 3、运行结果 三、如何安装使用&#xff1f;怎么玩 1、注册OpenAI的账号&#xff0c;并获取key 2、下载Git和Python3&#xff08;无脑安装&#xff09; 3、克隆仓库到本地 4、…

【SVN】SVN常用操作

1、svn客户端安装 下载地址&#xff1a;https://tortoisesvn.net/downloads.zh.html 下SVN客户端及汉化包 &#xff08;根据提示安装即可&#xff09; 2、检查是否安装成功 在空白处&#xff0c;右键&#xff0c;可见TortoiseSVN&#xff0c;如下图所示 3、检出&#xff…

React Antd Typescript开发碰到的问题 DatePicker Radio should update 后端数据回显

需求&#xff1a; DatePicker控件 离职人员默认显示后端传过来的离职时间 有两种类型页面&#xff0c;编辑时可操作&#xff0c;详情时不可操作 进入编辑页面时&#xff0c;状态切换成在职时&#xff0c;清空离职时间框且离职时间框不可用&#xff0c;字段设置为 undefined 状态…

国际top5功能完善在线投资平台app软件最新排名(综合评测)

如今&#xff0c;随着科技的发展&#xff0c;越来越多的人选择使用在线投资app软件来进行投资。但是面对众多的选择&#xff0c;选择一款靠谱的在线投资app软件是非常重要的。首先&#xff0c;我们需要考虑该软件的安全性。投资是一项涉及资金的活动&#xff0c;因此&#xff0…

高精度示波器keysight是德DSOS054、MSOS054销售回收

安捷伦Keysight DSOS054A MSOS054 500MHZ高清晰度示波器 特征&#xff1a; 带宽&#xff1a;500 MHz&#xff0c;具有平坦的频率响应&#xff0c;可实现高信号保真度 频道&#xff1a;4 最大存储深度&#xff1a;800 Mpts&#xff08;2 通道&#xff09;&#xff0c;400 Mpt…

新工匠精神是啥

给工匠精神加入新的“工程师”文化 工程化是现代制造的内核 工程师是工程文化的承载人 趣讲大白话&#xff1a;新工匠精神&#xff0c;新在哪里&#xff1f; 【趣讲信息科技170期】 **************************** 工程师文化起源于硅谷 传承工匠精神的&#xff0c;也要加入工程…

本科生高薪专业top10,全被计算机承包了

在每年被唱衰的行业里&#xff0c;即使如高薪神话的IT行业&#xff0c;也难逃此“劫”——IT不行了&#xff01;疲软了&#xff01;现在再入行IT和计算机就是坑&#xff01; 然而事实上&#xff0c;根据最新数据报告显示&#xff0c;2022届本科毕业生毕业半年后月收入排前10位的…

Linux|minio对象存储服务的部署和初步使用总结

前言&#xff1a; minio是一个非常轻量化的对象存储服务&#xff0c;是可以算到云原生领域的。 该服务是使用go语言编写的&#xff0c;因此&#xff0c;主文件就一个文件&#xff0c;它的下载&#xff0c;部署什么的都是非常简单的&#xff0c;一般两三步就可以搭建好了&…

Word控件Spire.Doc 【文本框】教程(6):如何在文本框中设置文本方向

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

腾讯的这道公益「微光」,竟已燎原了

台上开发者们的陆续发言&#xff0c;瞬间把我带到了公益的海洋中。谁都不愿放弃追随那道光&#xff0c;那道热爱公益的光&#xff0c;那道为了解决弱势群体难题与时俱进的光&#xff0c;那道为了文化传承的创新之光。这就是第三届Light•技术公益创造营项目路演现场带给我的直观…

若依框架语言国际化操作流程?

国际化是指将产品、服务、企业或组织的活动适应不同国家、不同文化背景和不同语言环境的过程。它包括将产品或服务适应不同的市场需求、文化习惯和法律法规等方面的工作。国际化的目的是拓展企业或组织的市场,提高竞争力和盈利能力。 目录 一、前端国际化流程 1、html使用…