bootdefault和configuration_springboot常用注解、包引入和自动配置功能解读

springboot使用起来确实很方便,做到开箱即用,减少了许多繁琐的配置。不过在使用过程中时常会想,为啥会这样方便,springboot为我们做哪些工作。或者是我们在使用的过程中,会遇到springboot不满足的情况,我们要去了解内部实现机制,然后才能改进。

过去我们对这种方式确很少去了解,或者是了解了一些但是没有彻底搞清楚。今天我们就学习一下springboot相关的几个问题,希望能够揭开一些疑问。相信对这些看似比较基础的知识地理解,会给我们设计程序带来好的思路。

注解

spring最开始大量使用xml进行配置,当然也支持注解进行配置,springboot做了很多自动化的工作,进行默认配置,在此过程中将注解发挥到极致。所以在此之前先回顾一下注解的基本知识。

注解为代码添加信息提供了一种形式化的方法,使得我们可以在后面某个时刻非常方便地使用这些数据。java5引入注解的,有需要多好处,完整地描述程序需要的信息,相比于增加其他非java语言的文件对程序描述,这样使得代码可读性变差,并且不容易检查。可以生成新的描述符文件设置是新的类定义,可以减少许多重复的模板代码。

注解定义和常见的注解

java中内置了几个常见的注解,这也是我们经常在代码中见到的。

@Override 用来表示覆盖父类中的方法,如果方法签名写错,编译器将会报错,该注解是可选的。

@Deprecated 表示废弃的方法或者字段,如果程序中使用了会报出警告。

@SuppressWarnings 关闭不当的编译器警告信息

注解的定义需要使用到元注解,顾名思义,元注解就是用来定义注解的注解,例如:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Test{}

这样就定义了一个注解,注解的定义和接口定义非常类似,编译后也会生成一个class文件。如果像上面的注解一样,不包含任何元素,叫做标记注解。可以包含元素,就类似于接口的方法定义。但是和方法定义又有一些区别,访问权修饰符为默认或者public,类型为八种基本类型和String,Enum,Class,annotations类型,以及它们的数组。

值得说明的是如果为annotation类型,说明这个注解是嵌套注解。成员名字自定义,另外有一点不同的是,相比接口方法的定义,这里可以设置默认值:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Test{

public String value() default "some value";

public int test();//没有默认值

}

如果成员里面有一个名字是value,并且是唯一一个被赋值的成员,那么不需要写出成员名字直接赋值,例如:

@Test("a")

public void hello(){

}

四种元注解:

@Target 表示注解作用的对象,ElementType参数包括:

CONSTRUCTOR,构造器声明

FIELD,域声明

LOCAL_VARIABLE,局部变量声明

METHOD,方法声明

PACKAGE,包声明

PARAMETER,参数声明

TYPE,类,接口包括注解类型或enum声明,对于注解类型特别要提到的是,这样可以自定义注解的注解。

@Retention Retention是保留的意思,这个注解说的是被注解的注解被保留的级别,RetentionPolicy可选有:

SOURCE,保留在源码中,也就是说编译成class文件不会有该注解,被编译器丢弃了

CLASS,保留在class文件中,但是在虚拟机运行的时候会丢弃该注解

RUNTIME,保留在运行期,一般都是通过反射机制读取注解的信息

@Documented 表示注解包含在javadoc中

@Inherited 表示子类可以继承父类的注解

上面说到RUNTIME的注解,类都实现了AnnotatedElement接口,具有getAnnotation()的实现方法,可以获取到某个类型的注解:

Test test = method.getAnnocation(Test.class);

@Enable*注解

既然我们搞清楚了注解的基本使用方法,那么让我们还看一看springboot是怎样使用注解的。对于很多功能的启用的开关是加上了@Enable*的注解,例如开启eureka:

@EnableDiscoveryClient

以此为例,搞清楚这类注解的使用原理,首先看这个注解的定义:

@Target(ElementType.TYPE) //作用在类上面

@Retention(RetentionPolicy.RUNTIME) //jvm使用该注解

@Documented //javadoc包含该注解

@Inherited //子类可以继承该注解

@Import(EnableDiscoveryClientImportSelector.class) //自定义元注解

public @interface EnableDiscoveryClient {

/**

* If true, the ServiceRegistry will automatically register the local server.

*/

boolean autoRegister() default true; //将该应用注册到eureka server

}

元素就一个boolean类型的,直接说明的是这里使用了一个自定义的元注解,也就是该注解的@Target是ElementType.TYPE,其中的TYPE指的是一个注解定义的类:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface Import {

/**

* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}

* or regular component classes to import.

*/

Class>[] value();

}

这个@Import注解非常重要,可以看到有一个唯一的默认的元素,这个元素名字叫value,后面为元素赋值的时候,可以不写value,类型是一个class数组。我们知道这个@Import注解是为了引入一个类,下面来看一下注解处理器做了什么操作:

package org.springframework.context.annotation;

/**

* Recursively collect all declared {@code @Import} values. Unlike most

* meta-annotations it is valid to have several {@code @Import}s declared with

* different values; the usual process of returning values from the first

* meta-annotation on a class is not sufficient.

*

For example, it is common for a {@code @Configuration} class to declare direct

* {@code @Import}s in addition to meta-imports originating from an {@code @Enable}

* annotation.

* @param sourceClass the class to search

* @param imports the imports collected so far

* @param visited used to track visited classes to prevent infinite recursion

* @throws IOException if there is any problem reading metadata from the named class

*/

private void collectImports(SourceClass sourceClass, Set imports, Set visited)

throws IOException {

if (visited.add(sourceClass)) {

for (SourceClass annotation : sourceClass.getAnnotations()) {

String annName = annotation.getMetadata().getClassName();

if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {

collectImports(annotation, imports, visited);

}

}

imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));

}

}

上面的代码片段是从spring中摘录出来,将@Import注解中value的类加到集合中,后续生成bean进行加载。也就是说会把EnableDiscoveryClientImportSelector这个类生成bean加载到上下文中。

@Order(Ordered.LOWEST_PRECEDENCE - 100)

public class EnableDiscoveryClientImportSelector

extends SpringFactoryImportSelector {

@Override

public String[] selectImports(AnnotationMetadata metadata) {

String[] imports = super.selectImports(metadata);

AnnotationAttributes attributes = AnnotationAttributes.fromMap(

metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));

boolean autoRegister = attributes.getBoolean("autoRegister");

if (autoRegister) {

List importsList = new ArrayList<>(Arrays.asList(imports));

importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");

imports = importsList.toArray(new String[0]);

}

return imports;

}

@Override

protected boolean isEnabled() {

return new RelaxedPropertyResolver(getEnvironment()).getProperty(

"spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);

}

@Override

protected boolean hasDefaultFactory() {

return true;

}

}

这个类由于继承了SpringFactoryImportSelector,spring会进行import操作,在这个类里面,引入了org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration,这个类,所以使得该应用开启了eureka客户端的功能。

自动配置

通过源码可以发现@SpringBootApplication注解定义中有,有元注解@EnableAutoConfiguration,让我们看一下这个定义:

package org.springframework.boot.autoconfigure;

@SuppressWarnings("deprecation")

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import(EnableAutoConfigurationImportSelector.class)

public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

/**

* Exclude specific auto-configuration classes such that they will never be applied.

* @return the classes to exclude

*/

Class>[] exclude() default {};

/**

* Exclude specific auto-configuration class names such that they will never be

* applied.

* @return the class names to exclude

* @since 1.3.0

*/

String[] excludeName() default {};

}

所有自动配置功能都由@AutoConfigurationPackage和@Import(EnableAutoConfigurationImportSelector.class)这两个注解来决定。

@AutoConfigurationPackage

这个注解的源码如下:

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Inherited;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

import org.springframework.context.annotation.Import;

/**

* Indicates that the package containing the annotated class should be registered with

* {@link AutoConfigurationPackages}.

*

* @author Phillip Webb

* @since 1.3.0

* @see AutoConfigurationPackages

*/

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@Import(AutoConfigurationPackages.Registrar.class)

public @interface AutoConfigurationPackage {

}

通过注释可以知道,这个注解的作用是将被该注解进行注解的类所在的包进行扫描。EnableAutoConfiguration被注解了,而该类在org.springframework.boot.autoconfigure包下面,所以会扫描该包下的类,声明成bean的,将会生成bean。

/**

* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing

* configuration.

*/

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

@Override

public void registerBeanDefinitions(AnnotationMetadata metadata,

BeanDefinitionRegistry registry) {

register(registry, new PackageImport(metadata).getPackageName());

}

@Override

public Set determineImports(AnnotationMetadata metadata) {

return Collections.singleton(new PackageImport(metadata));

}

}

可以看到将BasePackages注册在了注册中心,BasePackages中含有包名:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {

if (registry.containsBeanDefinition(BEAN)) {

BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);

ConstructorArgumentValues constructorArguments = beanDefinition

.getConstructorArgumentValues();

constructorArguments.addIndexedArgumentValue(0,

addBasePackages(constructorArguments, packageNames));

}

else {

GenericBeanDefinition beanDefinition = new GenericBeanDefinition();

beanDefinition.setBeanClass(BasePackages.class);

beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,

packageNames);

beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

registry.registerBeanDefinition(BEAN, beanDefinition);

}

}

这个bean的名字叫做AutoConfigurationPackages,需要说明的是,此时并没有扫描包。

正如该类的注释所说,是为了存储需要自动配置的包,以便于后面的扫描器进行扫描。

* Class for storing auto-configuration packages for reference later (e.g. by JPA entity

* scanner).

也许此时会有个疑问,为啥不用@ComponentScan进行包扫描?

实际上,在org.springframework.boot.autoconfigure包下面,并不是所有的组件都需要包扫描,只有几个组件的集成才需要,例如cassandra、jpa等,一般都是这样的代码:

@Bean

@ConditionalOnMissingBean

public CassandraMappingContext cassandraMapping(

CassandraCustomConversions conversions) throws ClassNotFoundException {

CassandraMappingContext context = new CassandraMappingContext();

List packages = EntityScanPackages.get(this.beanFactory)

.getPackageNames();

if (packages.isEmpty() && AutoConfigurationPackages.has(this.beanFactory)) {

packages = AutoConfigurationPackages.get(this.beanFactory);

}

if (!packages.isEmpty()) {

context.setInitialEntitySet(CassandraEntityClassScanner.scan(packages));

}

if (StringUtils.hasText(this.properties.getKeyspaceName())) {

context.setUserTypeResolver(new SimpleUserTypeResolver(this.cluster,

this.properties.getKeyspaceName()));

}

context.setCustomConversions(conversions);

return context;

}

上述代码说明,如果存在@EntityScan注解的包,则只需要scan该包即可,如果不存在则将autoconfigure都scan了。

需要说明的是该注解,不是每个组件的自动配置都会用到,只有一部分用到,比较重要的是下面的自动配置功能。

EnableAutoConfigurationImportSelector的作用

下面是最关键的源码:

@Override

public String[] selectImports(AnnotationMetadata annotationMetadata) {

if (!isEnabled(annotationMetadata)) {

return NO_IMPORTS;

}

try {

AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader

.loadMetadata(this.beanClassLoader);

AnnotationAttributes attributes = getAttributes(annotationMetadata);

List configurations = getCandidateConfigurations(annotationMetadata,

attributes);

configurations = removeDuplicates(configurations);

configurations = sort(configurations, autoConfigurationMetadata);

Set exclusions = getExclusions(annotationMetadata, attributes);

checkExcludedClasses(configurations, exclusions);

configurations.removeAll(exclusions);

configurations = filter(configurations, autoConfigurationMetadata);

fireAutoConfigurationImportEvents(configurations, exclusions);

return configurations.toArray(new String[configurations.size()]);

}

catch (IOException ex) {

throw new IllegalStateException(ex);

}

}

protected boolean isEnabled(AnnotationMetadata metadata) {

if (getClass().equals(AutoConfigurationImportSelector.class)) {

return getEnvironment().getProperty(

EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,

true);

}

return true;

}

这个类会选择哪些类会被引入并且生成一个Bean,如果属性spring.boot.enableautoconfiguration为false,将会关闭自动配置。如果开启的话,会将META-INFO目录下的配置文件的配置读进来,选择需要引入的Bean。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\

...

以RabbitMq进行举例说明,需要引入RabbitAutoConfiguration这个Bean。

RabbitAutoConfiguration的作用

下面是该类的定义的一部分:

@Configuration

@ConditionalOnClass({ RabbitTemplate.class, Channel.class })

@EnableConfigurationProperties(RabbitProperties.class)

@Import(RabbitAnnotationDrivenConfiguration.class)

public class RabbitAutoConfiguration {

@Configuration

@ConditionalOnMissingBean(ConnectionFactory.class)

@Configuration注解说明是一个配置Bean。

@EnableConfigurationProperties(RabbitProperties.class),该注解的源码如下:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Import(EnableConfigurationPropertiesImportSelector.class)

public @interface EnableConfigurationProperties {

/**

* Convenient way to quickly register {@link ConfigurationProperties} annotated beans

* with Spring. Standard Spring Beans will also be scanned regardless of this value.

* @return {@link ConfigurationProperties} annotated beans to register

*/

Class>[] value() default {};

}

其中EnableConfigurationPropertiesImportSelector在生成Bean的时候,如果发现没有RabbitProperties这个bean,会生成这个bean,然后我们又发现这个bean有@ConfigurationProperties,凡事有这个注解的Bean都会将配置文件的属性映射到这个Bean的字段上。

@ConfigurationProperties(prefix = "spring.rabbitmq")

public class RabbitProperties {

所以此时会有RabbitProperties这个Bean存在。基于此会生成许多其他配置Bean,例如CachingConnectionFactory,这个Bean就含有文件的配置信息,供后面RabbitMq连接通信使用。这样就完成了自动配置。

@Conditional条件注解

上面的注解中用到了条件注解,是值得关注的地方。上面通过自动配置,得到了RabbitMq的连接配置Bean也就是CachingConnectionFactory,这个类的定义是在org.springframework.amqp.rabbit.connection中,实际上使用rabbit的核心类可能是在另外一个jar包中,也就是说了配置类,但是没有rabbitmq的操作类也没有作用,怎样确保有核心类,然后再进行加载配置Bean呢?还有一个问题需要思考,如果用户自定义了一个连接配置Bean,而不是使用自动配置Bean?这些问题该怎么解决呢?

实际上springboot定义了非常灵活的条件注解:

@ConditionalOnMissingBean(ConnectionFactory.class)

再去看看这个注解的定义:

@Target({ ElementType.TYPE, ElementType.METHOD })

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Conditional(OnBeanCondition.class)

public @interface ConditionalOnMissingBean {

/**

* The class type of bean that should be checked. The condition matches when each

* class specified is missing in the {@link ApplicationContext}.

* @return the class types of beans to check

*/

Class>[] value() default {};

/**

* The class type names of bean that should be checked. The condition matches when

* each class specified is missing in the {@link ApplicationContext}.

* @return the class type names of beans to check

*/

String[] type() default {};

/**

@Conditional这个注解是spring中定义的,它的成员是一些条件类,比如现在是OnBeanCondition.class。这里面核心的方法是判断ConditionalOnMissingBean中的value这个Bean是否存在:

if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {

BeanSearchSpec spec = new BeanSearchSpec(context, metadata,

ConditionalOnMissingBean.class);

MatchResult matchResult = getMatchingBeans(context, spec);

if (matchResult.isAnyMatched()) {

String reason = createOnMissingBeanNoMatchReason(matchResult);

return ConditionOutcome.noMatch(ConditionMessage

.forCondition(ConditionalOnMissingBean.class, spec)

.because(reason));

}

matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)

.didNotFind("any beans").atAll();

}

除了这些条件以外,还有其他条件,列举如下:

@ConditionalOnJava 系统的java版本是否符合要求

@ConditionalOnBean 容器中存在指定Bean;

@ConditionalOnMissingBean 容器中不存在指定Bean;

@ConditionalOnExpression 满足SpEL表达式指定

@ConditionalOnClass 系统中有指定的类

@ConditionalOnMissingClass 系统中没有指定的类

@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean

@ConditionalOnProperty 系统中指定的属性是否有指定的值

@ConditionalOnResource 类路径下是否存在指定资源文件

@ConditionalOnWebApplication 当前是web环境

@ConditionalOnNotWebApplication 当前不是web环境

@ConditionalOnJndi JNDI存在指定项

这样我们就把整个自动配置大致流程搞清楚了。

4+

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

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

相关文章

radare2 常用操作总结

radare2 简介 radare2是一个用C语言编写的跨平台的二进制分析工具&#xff0c;支持ARM、MIPS、X86等平台&#xff0c;支持分析多种格式的二进制文件如ELF、Mach-O、Fatmach-O, PE、PE、 MZ、 COFF等&#xff0c;同时也支持多种操作系统如Windows (since XP)、GNU/Linux、GNU/D…

Could NOT find SDL_image (missing:SDL_IMAGE_LIBRARIES SDL_IMAGE_INCLUDE_DIRS)

sudo apt-get install libsdl-image1.2-dev转载于:https://www.cnblogs.com/sea-stream/p/9792496.html

Erlang TCP Socket的接收进程的2种方案

转自&#xff1a;http://blog.csdn.net/summerhust/article/details/8740973 一旦打开了一个使用&#xff34;&#xff23;&#xff30;连接的套接字&#xff0c;它就始终保持打开状态&#xff0c;直至任何一方关闭它或因为一个错误而终止。在建立一个连接时&#xff0c;一般为…

序列化与反序列化(1)Serializable —— Java原生态方法

摘自&#xff1a;序列化与反序列化&#xff08;1&#xff09;Serializable —— Java原生态方法 作者&#xff1a;丶PURSUING 发布时间&#xff1a; 2021-05-08 19:20:21 网址&#xff1a;https://blog.csdn.net/weixin_44742824/article/details/116503261 本文为学习笔记&…

极路由4刷机

准备工作 https://www.right.com.cn/forum/thread-161906-1-1.html https://breed.hackpascal.net/ 在第二个链接里下载路由器对应的breed固件&#xff0c;固件的说明见第一个链接&#xff0c;下文以极路由4为例演示 由于极路由官方挂了&#xff0c;无法进行root&#xff0…

背景颜色及背景图片相关的属性

1.background-color   设置背景颜色&#xff0c;取颜色值   注意&#xff1a;     1.所有元素默认的背景颜色都是透明色&#xff0c;新建窗口的白色不是body的背景色&#xff0c;是浏览器渲染的     2. 背景颜色从边框位置开始绘制     3. 如果元素添加内边距&…

评论安装_评论送|机电安装监理质量控制要点130页

来源&#xff1a;网络整理&#xff0c; 如有侵权请联系删除因本文篇幅有限&#xff0c;只能展示60页&#xff0c;全部130页需要您对本文进行精彩评论或者转发分享&#xff0c;加微信dahe0608送给您本文的ppt原件因本文篇幅有限&#xff0c;只能展示60页&#xff0c;全部130页需…

积跬步-java任职要求

2019独角兽企业重金招聘Python工程师标准>>> 1.JAVA基础扎实&#xff0c;熟悉io、多线程、集合等基础框架&#xff0c;熟悉分布式、缓存、消息、搜索等机制&#xff1b; 2.四年以上使用java进行web开发的经验&#xff0c;熟练使用spring 、MVC等框架&#xff0c;熟悉…

MIPS内联汇编

0X0 近几天使用内联汇编来完成部分功能&#xff0c;下面总结下基础知识点&#xff0c;以便以后查询 语法基本结构 __asm__ __volatile__("汇编语句\n\t""汇编语句\n\t":输出寄存器:输入寄存器:寄存器破坏表);‘:’把内联汇编分割为了4个部分 第一部分 如…

安卓APP_ 控件(11)webView —— 简单应用:显示网页

摘自&#xff1a;安卓APP_ 控件&#xff08;11&#xff09;webView —— 简单应用&#xff1a;显示网页 作者&#xff1a;丶PURSUING 发布时间&#xff1a; 2021-05-11 11:50:52 网址&#xff1a;https://blog.csdn.net/weixin_44742824/article/details/116602469 目录 简单了…

Python 抓取数据存储到Mysql中

# -*- coding: utf-8 -*- import os,sys import requests import bs4 import pymysql#import MySQLdb#连接MYSQL数据库 db pymysql.connect(host127.0.0.1,userroot,passwordmysql,dbtest,port3306,charsetutf8) #db MySQLdb.connect(127.0.0.1,root,mysql,test,coon.set_cha…

ios 去掉底部状态栏_iOS 隐藏状态栏

1.整个项目隐藏状态栏在Targets->General->勾选中Hide status bar2.单个界面隐藏状态栏,例如登录注册页面1.首先在info.plist里面View controller-based status bar appearance 设置为 NO.2.在需要调控状态栏的ViewController类中添加以下代码://进入时隐藏-(void)viewWi…

QT应用开发基础

目录前言Windows上搭建开发环境C基础什么是C什么是面向对象&#xff0c;什么又是面向过程c的灵魂&#xff1a;c的类对象&#xff1a;类的实例化怎么访问类的成员类的函数成员类的访问修饰符函数的重载构造函数和析构函数类的继承虚函数和纯虚函数制作一个简单的QT界面创建工程U…

meson构建系统

简介 Meson是一个构建系统&#xff0c;它被设计成在不牺牲性能的前提下尽可能的友好。这方面的主要工具是用户用来描述构建结构的自定义语言。这种语言的主要设计目标是简单、清晰和简洁。很多灵感都来自于Python编程语言&#xff0c;它被认为是非常易读的&#xff0c;即使对以…

射灯安装方法图解_江苏天筑不锈钢雕塑厂家格栅射灯安装方法,格栅射灯安装注意事项...

格栅射灯安装方法&#xff0c;您是否已经学会了呢&#xff1f;不管您会不会&#xff0c;今天江苏天筑不锈钢雕塑厂家就结合这个问题&#xff0c;一起来为大家详细介绍这部分内容的一些相关的注意事项&#xff0c;南方的天气比较潮湿&#xff0c;所以我们尽量不要把格栅射灯安装…

在centos8 stream启用 Extra Packages

前言 最近把一台机器装了centos8 stream&#xff0c;centos8 的维护周期将会在2021-12-31结束 而centos8 stream和centos8有什么区别呢&#xff1f;我参考官方的描述简单总结下 由下游变更到上游 centos8是RHEL的rebuild版本&#xff0c;所以是处在下游的&#xff0c;意思是收…

ARM体系结构简介 —— 迅为

目录单片机和ARM处理器内存管理单元&#xff08;MMU&#xff09;高速缓冲存储器&#xff08;CACHE&#xff09;指令集ARM的指令系统ARM处理器工作模式ARM处理器的内部寄存器ARM处理器的异常ARM中断向量ARM架构的发展单片机和ARM处理器 内存管理单元&#xff08;MMU&#xff09;…

getElementById() getElementsByName() getElementsByTagName()

http://www.cnblogs.com/winner/archive/2007/03/28/593028.html1、getElementById()getElementById()可以访问DOCUMENT中的某一特定元素&#xff0c;顾名思义&#xff0c;就是通过ID来取得元素&#xff0c;所以只能访问设置了ID的元素。比如说有一个DIV的ID为docid&#xff1a…

网口扫盲二:Mac与Phy组成原理的简单分析(转)

1. general 下图是网口结构简图.网口由CPU、MAC和PHY三部分组成.DMA控制器通常属于CPU的一部分,用虚线放在这里是为了表示DMA控制器可能会参与到网口数据传输中. 对于上述的三部分,并不一定都是独立的芯片,根据组合形式,可分为下列三种方案: CPU集成MAC与PHY;CPU集成MAC,PHY采用…

python拼接两个数组_在Python中连接两个数组

根据你的描述&#xff0c;你的案子看起来像&#xff1a;In [344]: a[np.arange(5),np.arange(5)]In [345]: b[np.arange(5),np.arange(3)]In [346]: aOut[346]: [array([0, 1, 2, 3, 4]), array([0, 1, 2, 3, 4])]In [347]: bOut[347]: [array([0, 1, 2, 3, 4]), array([0, 1, …