Spring注解扫描:ComponentScan使用及原理详解

2023-10-29

1.概述

当下Spring Boot之所以能成为主流首选开发框架,得益于其核心思想:约定大于配置Spring提供的基于注解配置式开发,解决了繁琐的XML文件配置问题,大大提高了开发效率。基于Spring MVC三层架构框架开发的项目中大量用到@Controller, @Service...等注解,即使这些类在不同包路径下,都能被注入到Spring容器中,然后可以相互之间进行依赖注入、使用。这时候就有一个问题了:Spring是如何将声明了@Component注解的Bean注入到Spring容器当中的呢?怎么做到bean的类定义可以随意写在不同包路径下?答案就是今天的主角@ComponentScan,该注解告诉Spring扫描那些包路径下的类,然后判断如果类使用了@Component,@Controller, @Service...等注解,就注入到Spring容器中。

之前我们讲过一个注解@Component,它就是声明当前类是一个bean组件,那@ComponentScan注解顾名思义就是扫描声明了@Component注解的类,然后注入到Spring容器中的。这时候你可能会问@Controller, @Service...等注解为什么也会被扫描、注入到Spring容器中。

接下来我们就看看这些注解@Controller, @Service, @Repository@Component的关系,从这些注解的定义上来看都声明了@Component,所以都是@Component衍生注解,其作用及属性和@Component是一样,只不过是提供了更加明确的语义化,是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰

  • @Controller:一般用于表现层的注解。
  • @Service:一般用于业务层的注解。
  • @Repository:一般用于持久层的注解。
  • @RestController:是@Controller的衍生注解,主要用于前后端分离,接口返回JSON格式数据的表现层注解

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent
微信公众号:Shepherd进阶笔记

接下来我们就来讲讲@ComponentScan的使用和底层实现。

2.@ComponentScan的使用

在讲述@ComponentScan使用之前先来看看定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)//可重复注解
public @interface ComponentScan {

   @AliasFor("basePackages")
   String[] value() default {};//基础包名,等同于basePackages

   @AliasFor("value")
   String[] basePackages() default {};//基础包名,value

   Class<?>[] basePackageClasses() default {};//扫描的类,会扫描该类所在包及其子包的组件。

   Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;//注册为BeanName生成策略 默认BeanNameGenerator,用于给扫描到的Bean生成BeanName

   Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;//用于解析bean的scope的属性的解析器,默认是AnnotationScopeMetadataResolver

   ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;//scoped-proxy 用来配置代理方式 // no(默认值):如果有接口就使用JDK代理,如果没有接口就使用CGLib代理 interfaces: 接口代理(JDK代理) targetClass:类代理(CGLib代理)

   String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;//配置要扫描的资源的正则表达式的,默认是"**/*.class",即配置类包下的所有class文件。
  
   boolean useDefaultFilters() default true;//useDefaultFilters默认是true,扫描带有@Component ro @Repository ro @Service ro @Controller 的组件

   Filter[] includeFilters() default {};//包含过滤器

   Filter[] excludeFilters() default {};//排除过滤器

   boolean lazyInit() default false;//是否是懒加载

   @Retention(RetentionPolicy.RUNTIME)
   @Target({})
   @interface Filter {//过滤器注解

      FilterType type() default FilterType.ANNOTATION;//过滤判断类型

      @AliasFor("classes")
      Class<?>[] value() default {};//要过滤的类,等同于classes

      @AliasFor("value")
      Class<?>[] classes() default {};//要过滤的类,等同于value

      String[] pattern() default {};// 正则化匹配过滤

   }

}

从定义来看,比起之前讲的@Import注解相对有点复杂,但是不用过于担心,其大部分属性使用默认即可,我们一般只需要配置一下basePackages属性指定包扫描路径即可。

下面我们来看看如何使用,我在包路径下com.shepherd.common.bean下定义如下类:

@Component
public class Coo {
}

@Repository
public class Doo {
}

@Service
public class Eoo {
}

@RestController
public class Foo {
}

然后在另一个包路径com.shepherd.common.bean1下再定义一个类:

@Component
public class Goo {
}

最后声明一个类,使用ComponentScan注解进行包扫描:

@ComponentScan("com.shepherd.common.bean")
public class MyConfig {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        // 遍历Spring容器中的beanName
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }

}

执行结果如下:

myConfig
coo
doo
eoo
foo

我们发现Goo没有注入到Spring容器中,因为我们扫描的包路径是com.shepherd.common.bean,但是它在com.shepherd.common.bean1

下,所以没有被扫描到,要想被扫描到只需要指定扫描包添加路径com.shepherd.common.bean1即可

@ComponentScan(basePackages = {"com.shepherd.common.bean", "com.shepherd.common.bean1"})
public class MyConfig {
  
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        // 遍历Spring容器中的beanName
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

执行结果如下:

myConfig
coo
doo
eoo
foo
goo

可以看到Goo被成功扫描、注入到Spring容器中了。

从上面@ComponentScan定义看到声明了@Repeatable(ComponentScans.class),意味着该注解可以在同一个类中多次使用,这时候我想着使用两次分别指定不同的包扫描路径,解决前面Goo没有被扫描到的问题,下面的@ComponentScan多次使用等价于 @ComponentScans({@ComponentScan("com.shepherd.common.bean"), @ComponentScan("com.shepherd.common.bean1")}),代码如下:

@ComponentScan("com.shepherd.common.bean")
@ComponentScan("com.shepherd.common.bean1")
public class MyConfig {

  public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        // 遍历Spring容器中的beanName
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

执行结果如下:

myConfig

这时候惊奇发现指定的包路径下类都没有被扫描注入,很是纳闷不知道问题出在哪里,只能debug调试了,你会发现又来到配置后置处理器ConfigurationClassPostProcessor的#processConfigBeanDefinitions()方法,这个方法会先判断有没有配置类,没有的话不再做后续的注解解析。

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
      // 判断当前bean(这里就是上面的定义的MyConfig类)是不是配置类,是的话加入配置类候选集合
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// Return immediately if no @Configuration classes were found
    // 配置类集合为空,直接返回,不在做后续的相关注解解析
		if (configCandidates.isEmpty()) {
			return;
		}
		
		......
		
	}

进入到ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)方法,核心逻辑如下:

Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
		if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
		}
		else if (config != null || isConfigurationCandidate(metadata)) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
		}
		else {
			return false;
		}

这里判断当前是不是配置类,是配置类还分是FULL模式或LITE模式,两种模式的区别之前我们总结过,请查看 @Configuration 和 @Component区别于实现原理,上面定义的MyConfig没有用@Configuration注解,所以config是null,所以接下来会进入到方法isConfigurationCandidate(metadata)

发现配置类LITE模式匹配规则里面并没有包含@ComponentScans注解,所以判断当前类不是配置类,自然不会再进行后面的相关注解解析了,这也就是上面多次使用@ComponentScan扫描注入不成功的问题。

上面的案例都是只简单配置@ComponentScanbasePackages()属性,当然我们也可以基于@Filter进行过滤啥的,如下面Spring Boot的启动类注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {


}

3.@ComponentScan的实现原理

@ComponentScan的底层实现流程和之前我们分析 @Import实现原理基本一致的,都是依靠配置类后置处理器ConfigurationClassPostProcessor进行处理、解析的,核心流程图如下所示:

所以我们这里直接看配置类解析器ConfigurationClassParser的解析方法doProcessConfigurationClass()

	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {

    // 对@Component的解析处理,对@ComponentScan注解解析在下面,意味着会先跳过这里对@ComponentScan解析进行包扫描拿到生了@Component的beanDefinition,然后递归调用会再次来到这里解析@Component
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass, filter);
		}

		// Process any @PropertySource annotations
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// Process any @ComponentScan annotations  解析@ComponentScan核心所在
    // 这里是调用AnnotationConfigUtils的静态方法attributesForRepeatable,获取@ComponentScan注解的属性
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
     // for循环,遍历componentScans,此时仅有一个componentScan,使用componentScanParser解析器来解析componentScan这个对象
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
            // componentScanParser解析器进行解析
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				// for循环扫描到的beanDefinition信息
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
          // 这里递归调用前面的配置类解析器的解析方法,也就是会再次来到doProcessConfigurationClass()这个方法,会匹配到方法一开始的对@Component解析逻辑
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// Process any @Import annotations
    // 处理注解@import的入口方法
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

		// Process any @ImportResource annotations
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// Process individual @Bean methods
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

ComponentScanAnnotationParserparse()

	public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

		Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
		boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
		scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
				BeanUtils.instantiateClass(generatorClass));

		ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
		if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
			scanner.setScopedProxyMode(scopedProxyMode);
		}
		else {
			Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
			scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
		}

		scanner.setResourcePattern(componentScan.getString("resourcePattern"));

		for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addIncludeFilter(typeFilter);
			}
		}
		for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addExcludeFilter(typeFilter);
			}
		}

		boolean lazyInit = componentScan.getBoolean("lazyInit");
		if (lazyInit) {
			scanner.getBeanDefinitionDefaults().setLazyInit(true);
		}

		Set<String> basePackages = new LinkedHashSet<>();
		String[] basePackagesArray = componentScan.getStringArray("basePackages");
		for (String pkg : basePackagesArray) {
			String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			Collections.addAll(basePackages, tokenized);
		}
		for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(declaringClass));
		}

		scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
			@Override
			protected boolean matchClassName(String className) {
				return declaringClass.equals(className);
			}
		});
		return scanner.doScan(StringUtils.toStringArray(basePackages));
	}

初始化ClassPathBeanDefinitionScanner扫描器,根据·@ComponentScan的属性,设置扫描器的属性,最后调用扫描器的doScan()方法执行真正的扫描工作。遍历扫描包,调用findCandidateComponents()方法根据基础包路径来找到候选的Bean。

之后就是遍历扫描到的候选Bean,给他们设置作用域,生成BeanName等一系列的操作。然后检查BeanName是否冲突,添加到beanDefinitions集合当中,调用registerBeanDefinition注册Bean,将Bean的定义beanDefinition注册到Spring容器当中,方便后续注入bean。

4.总结

以上全部就是对@ComponentScan注解实现流程的解析,也是对使用了@Component的组件怎么注入到Spring容器的梳理,Spring Boot项目会默认扫描启动类包下面的所有组件,其自动配置原理实现中使用到了@ComponentScan注解,所以我们需要关注该注解啦。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring注解扫描:ComponentScan使用及原理详解 的相关文章

  • 使用 Checkstyle Plugin 时从插件调用代码时出现问题:“org.eclipse.jface”

    我正在尝试在 Rational Software Architect 7 0 0 4 上使用 eclipse cs 插件 我最近卸载了旧的 beta2 版本并安装了 beta3 插件本身按照之前的配置工作 但是每当我尝试通过 Windows
  • 使用 Spring 时实例化对象,用于测试与生产

    使用 Spring 时 应该使用 Spring 配置 xml 来实例化生产对象 并在测试时直接实例化对象 这样的理解是否正确 Eg MyMain java package org world hello import org springf
  • Java 变量的作用域

    我不明白为什么这段代码的输出是10 package uno public class A int x 10 A int x 12 new B public static void main String args int x 11 new
  • 所有junit测试后的清理

    在我的项目中 我必须在所有测试之前进行一些存储库设置 这是使用一些棘手的静态规则来完成的 然而 在所有测试之后我不知道如何进行清理 我不想保留一些神奇的静态数字来引用所有测试方法的数量 我应该一直维护它 最受赞赏的方法是添加一些侦听器 该侦
  • 为什么我在 Mac 上看到“java.lang.reflect.InaccessibleObjectException: Unable to make private java.nio.DirectByteBuffer(long,int)accessibl

    我已经在工作中愉快地构建代码好几天了 但突然我的一个项目 不是全部 失败并出现此错误消息 看看下面的答案吧 我是如何修复它的 起初我用谷歌搜索 看到很多有这个问题的人正在使用 Java 16 但我认为 错误 我正在使用 Java 11 因为
  • 如何在 Spring MongoDB 聚合上投影 DBRef?

    我在 MongoDB shell 中完成了以下聚合 以获取每个用户每种类型的警报数量 db getCollection alerts aggregate unwind son group id son son level level cou
  • Java:VM 如何在 32 位处理器上处理 64 位“long”

    JVM 如何在 32 位处理器上处理 64 位的原始 long 在多核 32 位机器上可以并行利用多个核心吗 64 位操作在 32 位机器上慢了多少 它可能使用多个核心来运行不同的线程 但不会并行使用它们进行 64 位计算 64 位长基本上
  • 参数动态时如何构建 JPQL 查询?

    我想知道是否有一个好的解决方案来构建基于过滤器的 JPQL 查询 我的查询太 富有表现力 我无法使用 Criteria 就像是 query Select from Ent if parameter null query WHERE fiel
  • 打印包含 JBIG2 图像的 PDF

    请推荐一些库 帮助我打印包含 JBIG2 编码图像的 PDF 文件 PDFRenderer PDFBox别帮我 这些库可以打印简单的 PDF 但不能打印包含 JBIG2 图像的 PDF PDFRenderer尝试修复它 根据 PDFRedn
  • 从 html 页面和 javascript 调用 java webservice

    我正在尝试从 javascript 调用 java 实现的 Web 服务 使用 NetBeans IDE 我读过很多关于 jQuery 和 AJAX 的内容 但我似乎无法掌握它 假设我的 Web 服务 WSDL 位于 http localh
  • 让JScrollPane控制多个组件

    对于我的应用程序 我正在设计一个脚本编辑器 目前我有一个JPanel其中包含另一个JPanel保存行号 位于左侧 以及JTextArea用于允许用户输入代码 位于右侧 目前 我已经实施了JScrollPane on the JTextAre
  • OpenJDK 版本控制

    上下文 我想确保我们系统上安装的 Java 不受 CVE 2022 21449 的影响 java version 给出 openjdk version 11 0 7 2020 04 14 LTS OpenJDK Runtime Enviro
  • 从 Stax XMLStreamReader 读取以解组部分

    我正在使用 Stax 游标 API 从大型 xml 文件中提取数据 当前 我转到特殊标签的开头并使用 JAXB 解组该标签 这对于格式良好的 xml 文件效果很好 但不久前我有一个文档 其中数十万个标签中有一个未关闭 JAXB 使用 XML
  • 在 Selenium WebDriver 上如何从 Span 标签获取文本

    在 Selenium Webdriver 上 如何从 span 标记检索文本并打印 我需要提取文本UPS Overnight Free HTML代码如下 div id customSelect 3 class select wrapper
  • ExceptionHandler 不适用于 Throwable

    我们的应用程序是基于 Spring MVC 的 REST 应用程序 我正在尝试使用 ExceptionHandler 注释来处理所有错误和异常 I have ExceptionHandler Throwable class public R
  • 阻止 OSX 变音符号为所有用户禁用 Java 中的 KeyBindings?

    注 我知道这个问题 https stackoverflow com questions 40335285 java keybinds stop working after holding down a key用户必须输入终端命令才能解决此问
  • 我想要一个 Java 阿拉伯语词干分析器

    我正在寻找阿拉伯语的 Java 词干分析器 我找到了一个名为 AraMorph 的库 但它的输出是无法控制的 并且它会形成不需要的单词 还有其他阿拉伯语词干分析器吗 这是新的阿拉伯语词干分析器 Assem 的阿拉伯语轻词干分析器 http
  • 从java中的字符串数组中删除空值

    java中如何从字符串数组中删除空值 String firstArray test1 test2 test4 我需要像这样没有 null 空 值的 firstArray String firstArray test1 test2 test4
  • Spock模拟inputStream导致无限循环

    我有一个代码 gridFSFile inputStream bytes 当我尝试这样测试时 given def inputStream Mock InputStream def gridFSDBFile Mock GridFSDBFile
  • Path2D 上的鼠标指针检测

    我构建了一个Path2D http docs oracle com javase 7 docs api java awt geom Path2D html表示由直线组成的未闭合形状 我希望能够检测何时单击鼠标并且鼠标指针靠近路径 在几个像素

随机推荐

  • k8s之Informer Store、Indexer、ThreadStore详解

    Store 在 Informer 中 Store 提供了将存储对象的能力 而 Indexer 在 Store 的基础上又提供了可以自定义索引来查询对象的功能 Store 接口提供的功能比较直观 主要是用于对对象的增删改查 包括Indexer
  • Docker 之MySQL 重启,提示Error response from daemon: driver failed programming external connectivity on **

    今天刚刚修改基于Docker服务的MySQL配置参数 准备重新加载MySQL 容器 提示如下截图的错误信息 root localhost docker restart 7d98a59a8012 Error response from dae
  • 【Vue】【01】vue环境搭建

    1 环境安装 https nodejs org zh cn 选择长期维护版 下载完成下一步下一步安装 建议选择版本低的行安装 2 cmd命令 node v npm v 3 淘宝镜像cnpm npm install g cnpm regist
  • 如何使用GPT2中文闲聊对话系统,机器人对话,自动对话!

    目录 目录结构 运行环境 首次运行 如何训练自己的模型 数据预处理 训练模型 如何确定这个 val num数值 编辑 编辑最后说明 打算做一个微博自动评论的一个模型 假象可以模拟回复粉丝 或者评论其他微博 首先下载GPT2模型代码 http
  • 运维实践

    欢迎关注 WeiyiGeek 点击 下方卡片 即可关注我哟 设为 星标 每天带你 基础入门 到 全栈实践 再到 放弃学习 涉及 网络安全运维 应用开发 物联网IOT 学习路径 个人感悟 等知识 花开堪折直须折 莫待无花空折枝 作者主页 ht
  • (已解决) org.apache.ibatis.ognl.OgnlException: source is null for getProperty(null, “staffId“)

    问题现象 今天在项目中遇到一个mybatis的报错 org apache ibatis ognl OgnlException source is null for getProperty null staffId 这是我之前从未遇到过的问题
  • 如何结合FT2232HL/CMSIS-DAP+Eclipse+OpenOCD软硬件工具使用SWD调试接口在Windows10操作系统在线调试S32K3XX(Cortex M7)

    由于我们公司自己需要开发烧录工具 本人通过google搜相关文档和看ARM公司的技术文档 终于实现了这个功能 因为涉及的内容知识点比较多 对于玩嵌入式MCU的小白来说要普及的知识 逐个介绍篇幅会比较长 本文中只介绍相应的关键知识点 篇幅有相
  • 大家好,这里是第一篇博客文章!

    大家好 我的第一篇CSDN博客与大家见面了 去年曾经在班级微信公众平台写过一篇关于MySql的安装及配置的推送 周围同学的反响还都不错 想着为今后找到一个技术分享交流的平台 也能够快速地增删改查已发布的文章 并且进行有效的沟通交流 便开通了
  • 面向对象的设计原则

    开放封闭原则 软件实体 类 模块 函数等 应该可以扩展 但是不可修改 大白话 就是面对新需求时 现有的类最好不做修改 以增加新的类的方式来完成新需求 这样方便维护和扩展 依赖倒转原则 抽象不应该依赖细节 细节应该依赖于抽象 1 高层模块不应
  • 慕课:软件测试各章节答案

    说明 1 该课程为西北工业大学的软件测试的慕课 2 绿色选项为正确答案 题目后括号中的答案为本人的作答 对的用绿色标示 错的用红色标示 与错题集类似 第一章 软件测试基础 1 软件测试目的是什么 ABC A 修正软件错误和缺陷提高软件质量
  • NUC980开源项目29-W25N01挂载YAFFS2文件系统

    上面是我的微信和QQ群 欢迎新朋友的加入 配置内核 取消ramdisk 设置Boot option set bootargs noinitrd root dev mtdblock2 rootfstype yaffs2 rootflags i
  • 互连线的阻抗不连续

    前言 保持互连通道的阻抗连续性 是信号完整性设计的一项重要内容 互连通道中如果存在阻抗不连续点 不仅会引起待传输信号本身的反射 而且可能还会放大其他方面因素的影响 例如串扰噪声耦合到邻近线 在临近线上传播 如果邻近线上存在多处不连续点 串扰
  • 亲密关系-【有效表达】-如何完善自己的表达思路?

    有效沟通三步 1 正面假设 2 区分感受和结论 3 区分请求和命令 案例 最近工作压力大 跟老公说我需要他安慰 他却觉得我作 还说最近是不是闲的 你需要安慰 老公理解的是 老公说你作 你听到的是 攻击 不想再表达 你听到这件事 想说些什么
  • 关于数据治理ChatGPT是如何回答的?

    这两天你的朋友圈是不是被火爆全网的ChatGPT霸屏了 你是不是已经迫不及待感受过ChatGPT带来的惊喜 那你知道ChatGPT是什么吗 面对掀起的一波话题热潮 好奇使然 小编去特别关注了一下最近火热的ChatGPT 看看这是何方神圣 竟
  • openvas漏洞扫描器

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 一 openvas简介 二 作用 三 安装及使用 openvas架构 在这里插入图片描述 https img blog csdnimg cn f46eba30a8a
  • jvm垃圾回收机制原理读书笔记

    可达性分析 主要是用来解决哪些对象是不可用的 可以被回收 以一系列称为GC Roots的对象为起点 若对象到GC Roots之间没有任何引用关系 则认为该对象是不可用的 可以被回收 具体可作为GC Roots的对象如下 不是全部 所有Jav
  • 基于随机森林特征工程语音用户满意度分析(数据预处理+随机森林)

    1 背景 数据分析而言 数据是显而易见的核心 但是并不是所有的数据都是有用的 存在不完整的 含噪声的和不一致的数据是现实世界大型的数据库或数据仓库的共同特点 一些比较成熟的算法对其处理的数据集合一般有一定的要求 如数据完整性好 数据的冗余性
  • 小程序生成长图(预览,不限高度)+小程序码+小程序码中间logo换成自定义logo

    上条博客已经介绍了如何云调用生成小程序码 链接地址 https blog csdn net mingjuna2010 article details 92796711 这条博客我们来介绍小程序中怎么生成长图 如何预览 并且在长图中绘制小程序
  • Ubuntu虚拟环境的创建与退出

    第一 安装Anaconda 并将其移入到系统环境变量中 查看安装结果 conda info或conda V 第二 创建虚拟环境 conda create name py38 python 3 8 第三 每次使用虚拟环境时需要激活环境 激活方
  • Spring注解扫描:ComponentScan使用及原理详解

    1 概述 当下Spring Boot之所以能成为主流首选开发框架 得益于其核心思想 约定大于配置和Spring提供的基于注解配置式开发 解决了繁琐的XML文件配置问题 大大提高了开发效率 基于Spring MVC三层架构框架开发的项目中大量