当前位置: 首页 > news >正文

15、IOC 之ApplicationContext 的附加功能

15、IOC 之 ApplicationContext 的附加功能

正如引言中所讨论的,org.springframework.beans.factory 包提供了管理和操作Bean的基本功能,包括以编程的方式。org.springframework.context 包添加了ApplicationContext 接口,该接口扩展了 BeanFactory 接口,此外还扩展了其他接口,以更面向应用程序框架的风格提供额外的功能。许多人以完全声明式的方式使用 ApplicationContext,甚至不是通过编程方式创建它,而是依赖于像 ContextLoader 这样的支持类来自动实例化一个 ApplicationContext ,作为Java EE web应用程序正常启动过程的一部分。

为了以更面向框架的方式增强 BeanFactory 的功能,context包还提供了以下功能:

  • 通过 MessageSource 接口访问 i18n 风格的消息。
  • 通过 ResourceLoader 接口访问资源,如 URL 和文件。
  • 事件发布,即通过使用 ApplicationListener 接口向实现 ApplicationEventPublisher 接口的 Bean发布。
  • 加载多个(分层)上下文,让每个上下文都通过 HierarchicalBeanFactory 接口聚焦在一个特定层上,例如应用程序的 Web 层。

15.1、国际化使用 MessageSource

ApplicationContext 接口扩展了一个名为 MessageSource 的接口,因此提供了国际化(“i18n”)功能。Spring还提供了 HierarchicalMessageSource 接口,它可以分层地解析消息。这些接口共同提供了Spring实现消息解析的基础。这些接口上定义的方法包括:

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从 MessageSource 中检索消息的基本方法。如果找不到指定区域设置的消息,则使用默认消息。使用标准库提供的 MessageFormat 功能,传入的任何参数都将成为替换值。
  • String getMessage(String code, Object[] args, Locale loc):本质上与前面的方法相同,但有一个区别:不能指定默认消息。如果找不到该消息,则抛出 NoSuchMessageException
  • String getMessage(MessageSourceResolvable resolvable, Locale locale):上述方法中使用的所有属性也包装在名为 MessageSourceResolvable 的类中,你可以将该类与此方法一起使用。

ApplicationContext 被加载时,它会自动搜索在上下文中定义的MessageSource Bean。Bean的名称必须为 messageSource。如果找到了这样的Bean,则将对上述方法的所有调用委托给消息源。如果没有找到消息源,ApplicationContext 将尝试寻找包含具有相同名称的bean的父类。如果是,则使用该Bean作为 MessageSource。如果 ApplicationContext 不能找到任何消息源,则实例化一个空的 DelegatingMessageSource,以便能够接受对上面定义的方法的调用。

Spring 提供了三个 MessageSource 实现,ResourceBundleMessageSourceReloadableResourceBundleMessageSourceStaticMessageSource。它们都实现了 HierarchicalMessageSource 以执行嵌套的消息传递。很少使用 StaticMessageSource,但它提供了将消息添加到源的编程方法。下面的例子显示了 ResourceBundleMessageSource:

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

该示例假设在类路径中定义了三个名为 formatexceptionswindows 的资源包。任何解析消息的请求都以通过 ResourceBundle 对象解析消息的jdk标准方式处理。在本例中,假设上述两个资源包文件的内容如下:

    # in format.properties
    message=Alligators rock!
    # in exceptions.properties
    argument.required=The {0} argument is required.

下一个示例展示了运行 MessageSource 功能的程序。请记住,所有的 ApplicationContext 实现也是 MessageSource 实现,因此可以转换为 MessageSource 接口。

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message);
}

上述程序的结果输出如下:

Alligators rock!

总之,MessageSource 定义在一个名为 beans.xml 的文件中,该文件存在于类路径的根目录中。messageSource Bean定义通过其 basenames 属性引用许多资源包。在列表中传递给 basenames 属性的三个文件作为文件存在于类路径的根目录中,它们被称为 format.propertiesexceptions.propertieswindows.properties

下一个示例显示传递给消息查找的参数。这些参数被转换为 String 对象,并插入到查找消息中的占位符中。

<beans>

    <!-- 此 MessagesSource 正在web应用程序中使用 -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- 让我们将上述消息源注入到这个POJO中 -->
    <bean id="example" class="com.something.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", Locale.ENGLISH);
        System.out.println(message);
    }
}

调用 execute() 方法的结果输出如下:

The userDao argument is required.

关于国际化(“i18n”),Spring的各种 MessageSource 实现遵循与标准JDK ResourceBundle 相同的地区解析和回退规则。简而言之,继续前面定义的 messageSource 示例,如果你希望针对英国(en-GB)语言环境解析消息,你将创建名为 format_en_GB.propertiesexceptions_en_GB.propertieswindows_en_GB.properties 文件。

通常,区域设置解析是由应用程序的周围环境管理的。在下面的例子中,手动指定了(英国)消息解析的语言环境:

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}

运行上述程序的结果输出如下:

Ebagum lad, the 'userDao' argument is required, I say, required.

你还可以使用 MessageSourceAware 接口来获取对已定义的任何 MessageSource 的引用。当创建和配置Bean时,在实现 MessageSourceAware 接口的 ApplicationContext 中定义的任何Bean都将被注入应用程序上下文的 MessageSource

因为 Spring的 MessageSource基于 Java 的 ResourceBundle,所以它不会合并具有相同基本名称的包,而只会使用找到的第一个包。具有相同基名称的后续消息包将被忽略。

作为 ResourceBundleMessageSource的替代方案,Spring提供了一个 ReloadableResourceBundleMessageSource 类。该变体支持相同的包文件格式,但比基于标准 JDK的 ResourceBundleMessageSource 实现更灵活。特别是,它允许从任何 Spring资源位置读取文件 (不仅仅是从类路径 ),并支持 bundle属性文件的热加载 ( 同时高效地缓存它们 )。详见 ReloadableResourceBundleMessageSource Java 文档。

15.2、标准和自定义事件

ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口提供的。如果将实现 ApplicationListener 接口的Bean部署到上下文中,那么每当 ApplicationEvent 发布到 ApplicationContext 时,该Bean就会收到通知。本质上,这是标准的观察者设计模式。

到 Spring 4.2为止,事件基础结构已经得到了显著的改进,并提供了一个基于注释的模型,以及发布任意事件的能力 ( 也就是说,一个对象不一定是从 ApplicationEvent 扩展而来 )。当这样的对象被发布时,我们将它包装在一个事件中。

下表描述了 Spring 提供的标准事件:

EventExplanation
ContextRefreshedEventApplicationContext 被初始化或刷新时发布 ( 例如,通过使用 ConfigurableApplicationContext 接口上的 refresh() 方法 )。在这里,“初始化”意味着加载了所有Bean,检测并激活了后处理器Bean,预实例化了单例,ApplicationContext 对象已经准备好可以使用。只要上下文没有关闭,就可以多次触发刷新,前提是所选的 ApplicationContext 实际上支持这种“热”刷新。例如, XmlWebApplicationContext 支持热刷新,但 GenericApplicationContext 不支持。
ContextStartedEvent通过使用 ConfigurableApplicationContext 接口上的 start() 方法,在 ApplicationContext 启动时发布。在这里,“started”意味着所有LifecycleBean都收到一个显式的开始信号。通常,此信号用于在显式停止后重新启动Bean,但也可以用于启动未配置为自动启动的组件(例如,在初始化时尚未启动的组件)。
ContextStoppedEvent通过使用 ConfigurableApplicationContext 接口上的 stop() 方法,在 ApplicationContext 停止时发布。在这里,“stopped”意味着所有Lifecycle Bean接收一个显式的停止信号。停止的上下文可以通过 start() 调用重新启动。
ContextClosedEvent通过使用ConfigurableApplicationContext接口上的close()方法或通过JVM关闭挂钩,在ApplicationContext关闭时发布。在这里,“关闭”意味着所有的单例Bean将被销毁。一旦上下文被关闭,它将到达生命的尽头,并且不能被刷新或重新启动。
RequestHandledEvent一个特定于web的事件,告诉所有Bean HTTP请求已得到服务。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的web应用程序。
ServletRequestHandledEventRequestHandledEvent 的子类,它添加了servlet特定的上下文信息。

你还可以创建和发布你自己的自定义事件。下面的例子展示了一个简单的类,它扩展了Spring的ApplicationEvent基类:

public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}

要发布一个自定义的ApplicationEvent,在ApplicationEventPublisher上调用publishEvent()方法。通常,这是通过创建一个实现ApplicationEventPublisherAware的类并将其注册为Spring Bean来完成的。下面的例子展示了这样一个类:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blockedList;
    private ApplicationEventPublisher publisher;

    public void setBlockedList(List<String> blockedList) {
        this.blockedList = blockedList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // send email...
    }
}

在配置时,Spring容器检测到EmailService实现了ApplicationEventPublisherAware并自动调用setApplicationEventPublisher()。实际上,传入的参数是Spring容器本身。你正在通过它的ApplicationEventPublisher接口与应用程序上下文交互。

要接收定制的ApplicationEvent,你可以创建一个实现ApplicationListener的类,并将其注册为Spring Bean。下面的例子展示了这样一个类:

public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

请注意,ApplicationListener通常是用自定义事件的类型参数化的 ( 在前面的例子中是BlockedListEvent )。这意味着onApplicationEvent()方法可以保持类型安全,避免任何向下转换的需要。你可以注册任意数量的事件监听器,但是请注意,在默认情况下,事件监听器是同步接收事件的。这意味着publishEvent()方法会阻塞,直到所有侦听器都完成了对事件的处理。这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它将在发布者的事务上下文内操作。如果需要另一种事件发布策略,请参阅javadoc获取Spring的 ApplicationEventMulticaster 接口和SimpleApplicationEventMulticaster实现的配置选项。

下面的示例显示了用于注册和配置上面每个类的Bean定义:

<bean id="emailService" class="example.EmailService">
    <property name="blockedList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blockedListNotifier" class="example.BlockedListNotifier">
    <property name="notificationAddress" value="blockedlist@example.org"/>
</bean>

综合起来,当调用emailService Bean的sendEmail()方法时,如果有任何应该被阻止的电子邮件消息,就会发布一个BlockedListEvent类型的自定义事件。blockedListNotifier Bean被注册为ApplicationListener并接收BlockedListEvent,此时它可以通知适当的方。

基于注释的事件侦听器

你可以使用@EventListener注释在托管Bean的任何方法上注册事件监听器。BlockedListNotifier可以重写如下:

public class BlockedListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlockedListEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

方法签名再次声明它侦听的事件类型,但这次使用灵活的名称,而不实现特定的侦听器接口。还可以通过泛型缩小事件类型的范围,只要实际的事件类型在其实现层次结构中解析泛型参数即可。

如果你的方法应侦听多个事件,或者要定义它时根本不使用参数,则还可以在注释本身上指定事件类型。下面的示例演示如何执行此操作:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}

还可以通过使用定义SpEL表达式的注释的 condition 属性来添加额外的运行时筛选,该注释应该与针对特定事件的方法的实际调用相匹配。

下面的例子展示了如何重写我们的通知符,使其仅在事件的content属性等于my-event时才被调用:

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

每个SpEL表达式在专用上下文中计算。下表列出了上下文可用的项目,以便你可以将它们用于条件事件处理:

NameLocationDescriptionExample
Eventroot object实际的 ApplicationEvent.#root.eventevent
Arguments arrayroot object用于调用方法的参数(作为对象数组)#root.argsargs; args[0]来访问第一个参数,等等。
Argument nameevaluation context任何方法参数的名称。如果由于某种原因,名称不可用 ( 例如,因为在编译的字节码中没有调试信息 ),单个参数也可以使用#a<#arg> 语法,其中 <#arg> 表示参数索引(从0开始)#blEvent#a0 (你也可以使用 #p0#p<#arg> 参数表示法作为别名)

请注意 #root.event使你能够访问底层事件,即使你的方法签名实际上引用了已发布的任意对象。

如果你需要发布一个事件作为处理另一个事件的结果,你可以改变方法签名来返回应该发布的事件,如下所示:

@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

异步监听器不支持此特性。

handleBlockedListEvent()方法为它处理的每个BlockedListEvent发布一个新的ListUpdateEvent。如果需要发布多个事件,可以返回一个Collection或事件数组。

异步侦听器

如果希望特定侦听器异步处理事件,则可以重用常规@Async支持。下面的示例演示如何执行此操作:

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}

使用异步事件时,请注意以下限制:

  • 如果异步事件侦听器抛出Exception ,它不会传播给调用方。有关更多详细信息 AsyncUncaughtExceptionHandler
  • 异步事件侦听器方法无法通过返回值来发布后续事件。如果需要发布另一个事件作为处理的结果,请注入ApplicationEventPublisher以手动发布事件。

对侦听器进行排序

如果需要先调用一个侦听器,然后再调用另一个侦听器,则可以将@Order注释添加到方法声明中,如下面的示例所示:

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}

普通事件

你还可以使用泛型来进一步定义事件的结构。考虑使用EntityCreatedEvent<T>,其中T是被创建的实际实体的类型。例如,你可以创建以下侦听器定义来仅接收PersonEntityCreatedEvent:

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}

由于类型擦除,只有当触发的事件解析事件监听器筛选的泛型参数(即类似于class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }时),该方法才有效。

在某些情况下,如果所有事件都遵循相同的结构(就像前面例子中的事件一样),这可能会变得相当乏味。在这种情况下,你可以实现ResolvableTypeProvider来指导框架超出运行时环境提供的范围。下面的事件显示了如何这样做:

public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}

这不仅适用于ApplicationEvent,而且适用于任何作为事件发送的对象。

15.3、方便地访问低级资源

为了最优地使用和理解应用程序上下文,你应该熟悉Spring的Resource抽象,如参考资料中所述。
应用程序上下文是一个ResourceLoader,可用于加载Resource对象。Resource本质上是JDK java.net.URL类的功能更丰富的版本。事实上,Resource的实现在适当的地方封装了java.net.URL的实例。Resource可以以一种透明的方式从几乎任何位置获得底层资源,包括从类路径、文件系统位置、任何可以用标准URL描述的位置,以及一些其他变体。如果资源位置字符串是一个没有任何特殊前缀的简单路径,那么这些资源来自于特定的、适合于实际应用程序上下文类型的地方。

你可以配置部署到应用程序上下文中的Bean,以实现特殊的回调接口ResourceLoaderAware,在初始化时自动回调,同时应用程序上下文本身作为ResourceLoader传入。还可以公开Resource类型的属性,以用于访问静态资源。它们像其他属性一样被注入其中。你可以将这些Resource属性指定为简单的String路径,并在部署Bean时依赖于从这些文本字符串到实际Resource对象的自动转换。

提供给ApplicationContext构造函数的位置路径实际上是资源字符串,在简单的形式中,根据特定的上下文实现进行适当的处理。例如,ClassPathXmlApplicationContext将一个简单的位置路径作为类路径位置处理。还可以使用带有特殊前缀的位置路径(资源字符串)强制从类路径或URL加载定义,而不管实际的上下文类型是什么。

15.4、应用程序启动跟踪

ApplicationContext管理Spring应用程序的生命周期,并围绕组件提供丰富的编程模型。因此,复杂的应用程序可以具有同样复杂的组件图和启动阶段。

使用特定指标跟踪应用程序启动步骤有助于了解启动阶段所花费的时间,但也可用于更好地了解整个上下文生命周期。

AbstractApplicationContext(及其子类)是用ApplicationStartup来检测的,它收集关于不同启动阶段的StartupStep数据:

  • 应用程序上下文生命周期(基本包扫描、配置类管理)
  • Bean生命周期(实例化、智能初始化、后处理)
  • 应用程序事件处理

下面是AnnotationConfigApplicationContext中的检测示例:

// 创建启动步骤并开始录制
StartupStep scanPackages = 
    this.getApplicationStartup().start("spring.context.base-packages.scan");
// 向当前步骤添加标记信息
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// 执行我们正在检测的实际阶段
this.scanner.scan(basePackages);
// 结束当前步骤
scanPackages.end();

应用程序上下文已经通过多个步骤进行了插装。一旦记录下来,就可以用特定的工具收集、显示和分析这些启动步骤。要获得现有启动步骤的完整列表,请查看专用附录部分。

为了最小化开销,ApplicationStartup的默认实现是一个无操作变体。这意味着在默认情况下,应用程序启动期间不会收集任何指标。Spring框架附带了一个Java飞行记录器跟踪启动步骤的实现:FlightRecorderApplicationStartup。要使用此变体,你必须在ApplicationContext创建后立即将其实例配置到ApplicationContext

如果开发人员提供了自己的AbstractApplicationContext子类,或者希望收集更精确的数据,他们也可以使用ApplicationStartup基础结构。

ApplicationStartup只用于应用程序启动期间和核心容器;这绝不是 Java分析器或像 Micrometer这样的指标库的替代品。

要开始收集自定义StartupStep,组件可以直接从应用程序上下文获取ApplicationStartup实例,让它们的组件实现ApplicationStartupAware,或者在任何注入点请求ApplicationStartup类型。

开发人员在创建自定义启动步骤时不应使用 "spring.*"命名空间。此命名空间保留给内部Spring使用,可能会更改。

15.5、方便的应用程序上下文实例化Web应用程序

你可以通过使用,例如,ContextLoader来声明性地创建ApplicationContext实例。当然,你也可以通过使用ApplicationContext实现之一以编程方式创建ApplicationContext实例。

你可以使用ContextLoaderListener注册一个ApplicationContext,如下所示:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

监听器检查contextConfigLocation参数。如果该参数不存在,监听器将默认使用/WEB-INF/applicationContext.xml。当参数存在时,侦听器使用预定义的分隔符(逗号、分号和空格)分隔String,并使用这些值作为搜索应用程序上下文的位置。也支持反样式的路径模式。例如/WEB-INF/*Context.xml(用于所有名称以Context.xml结尾且位于WEB-INF目录下的文件)和/WEB-INF/**/*Context.xml(用于WEB-INF任意子目录下的所有此类文件)。

15.6、将Spring ApplicationContext部署为 Java EE RAR文件

可以将Spring ApplicationContext部署为RAR文件,将上下文及其所需的所有bean类和库jar封装在Java EE RAR部署单元中。这相当于启动一个独立的ApplicationContext(仅托管在Java EE环境中),使其能够访问Java EE服务器设施。RAR部署是部署无头WAR文件的一种更自然的替代方案——实际上,没有任何HTTP入口点的WAR文件仅用于在Java EE环境中引导Spring ApplicationContext

RAR部署对于不需要HTTP入口点,而只包含消息端点和计划作业的应用程序上下文非常理想。这种上下文中的Bean可以使用应用服务器资源,比如JTA事务管理器、JNDI绑定的 JDBC DataSource实例和 JMS ConnectionFactory实例,还可以注册到平台的JMX服务器——所有这些都是通过Spring的标准事务管理以及JNDI和JMX支持工具实现的。应用程序组件还可以通过Spring的TaskExecutor抽象与应用服务器的JCA WorkManager交互。

有关RAR部署中涉及的配置细节,请参阅 SpringContextResourceAdapter 类的 Java文档。

对于一个简单的Spring ApplicationContext作为Java EE RAR文件的部署:

  1. 将所有应用程序类打包到一个RAR文件中 ( 这是一个具有不同文件扩展名的标准JAR文件 )。
  2. 将所有必需的库 jar 添加到RAR存档的根目录中。
  3. 添加一个META-INF/ra.xml部署描述符(如SpringContextResourceAdapter的 Java文档所示)和相应的Spring XML Bean定义文件 ( 通常是META-INF/applicationContext.xml )。
  4. 将生成的RAR文件放入应用程序服务器的部署目录中。

这种 RAR部署单元通常是自给自足的。它们不向外界公开组件,甚至不向同一应用程序的其他模块公开组件。与基于 RAR 的 ApplicationContext的交互通常通过与其他模块共享的 JMS目的地发生。例如,基于 RAR 的 ApplicationContext 还可以调度一些作业或响应文件系统中的新文件(或类似的)。如果它需要允许来自外部的同步访问,它可以(例如)导出 RMI端点,这可能由同一机器上的其他应用程序模块使用。

相关文章:

  • Hive sql 行列转换(行转列,列转行)
  • 【MATLAB教程案例10】使用MATLAB自带的LDPC工具箱实现LDPC编译码误码率仿真
  • 小学数学学习:神奇的走马灯数 142857
  • 【OFDM系列6】MIMO-OFDM系统模型、迫零(ZF)均衡检测和最小均方误差(MMSE)均衡检测原理和公式推导
  • 点云处理简介
  • 跨域问题以及经过网关二次转发重复跨域
  • 自动控制原理9.2---线性系统的可控性与可观测性(上)
  • DOM事件流+阻止冒泡事件+dom包含
  • ZYNQ之GPIO机制
  • 第二章:Qt下载与安装 之 2.2 Qt安装
  • 一种基于堆的链式优先队列实现(使用golang)
  • 【笔记】文献阅读[YOLOV2]-YOLO9000: Better, Faster, Stronger
  • 【JVM基础】方法区
  • Delphi的函数指针传递和调用
  • Java实现简单图书操作系统思路讲解
  • 时间复杂度分析经典问题——最大子序列和
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • java8-模拟hadoop
  • JDK9: 集成 Jshell 和 Maven 项目.
  • Service Worker
  • swift基础之_对象 实例方法 对象方法。
  • windows-nginx-https-本地配置
  • 大快搜索数据爬虫技术实例安装教学篇
  • 前端临床手札——文件上传
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • #pragma once
  • #Z2294. 打印树的直径
  • (ros//EnvironmentVariables)ros环境变量
  • (超详细)语音信号处理之特征提取
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (排序详解之 堆排序)
  • (一)Java算法:二分查找
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .net专家(张羿专栏)
  • :not(:first-child)和:not(:last-child)的用法
  • @SuppressWarnings(unchecked)代码的作用
  • [<死锁专题>]
  • [120_移动开发Android]008_android开发之Pull操作xml文件
  • [20161101]rman备份与数据文件变化7.txt
  • [2019.3.20]BZOJ4573 [Zjoi2016]大森林
  • [AMQP Connection 127.0.0.1:5672] An unexpected connection driver error occured
  • [Ariticle] 厚黑之道 一 小狐狸听故事
  • [C# 网络编程系列]专题六:UDP编程
  • [cocos2d-x]关于CC_CALLBACK
  • [Java][算法 双指针]Day 02---LeetCode 热题 100---04~07
  • [JavaWeb玩耍日记]Maven的安装与使用
  • [Java基础]—JDBC
  • [Manacher]【学习笔记】
  • [MongoDB]------windos下的安装部署与基础使用
  • [one_demo_18]js定时器的示例
  • [openGL]在ubuntu20.06上搭建openGL环境