最新最全面的Spring详解(二)——classpath扫描和组件管理

news2024/11/18 5:32:16

前言

在这里插入图片描述

本文为 【Spring】classpath扫描和组件管理 相关知识,下边将对@Component 和及其派生出的其他注解自动检测类和注册beanDifination组件命名为自动检测组件提供scope使用过滤器自定义扫描在组件中定义Bean元数据基于Java的容器配置BeanFactory和FactoryBean环境抽象事件机制等进行详尽介绍~

📌博主主页:小新要变强 的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
👉Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)

↩️本文上接:最新最全面的Spring详解(一)——Spring概述与IOC容器


目录

【Spring】classpath扫描和组件管理

  • 前言
  • 目录
  • 1️⃣`@Component `和及其派生出的其他注解
  • 2️⃣自动检测类和注册beanDifination
  • 3️⃣组件命名
  • 4️⃣为自动检测组件提供scope
  • 5️⃣使用过滤器自定义扫描
  • 6️⃣在组件中定义Bean元数据
  • 7️⃣基于Java的容器配置
  • 8️⃣BeanFactory和FactoryBean
  • 9️⃣环境抽象
  • 🔟事件机制
  • 后记

在这里插入图片描述

本章中的大多数例子都使用【XML来指定配置元数据】,这些元数据在Spring容器启动时被扫描,每一个bean的元数据对应生成一个“BeanDefinition”。

​本节我们可以通过【扫描类路径】隐式检测候选组件。 【候选组件】指的是通过扫描筛选并在容器中注册了相应beanDifination的类。 这样就不需要使用XML来执行bean注册。 相反,您可以使用注解(例如,【@Component 】)。

​更多操作从Spring 3.0开始,Spring JavaConfig项目提供的许多特性都是核心Spring框架的一部分。 这允许您使用Java而不是使用传统的XML文件来定义bean。

1️⃣@Component 和及其派生出的其他注解

  • @Component 是任何spring管理组件的通用注解。
  • @Repository@Service@Controller是【@Component】用于更具体用例的注解(分别在持久性、服务和表示层中)。这些注解对于我们后期对特定bean进行批量处理时是有帮助的。

2️⃣自动检测类和注册beanDifination

Spring可以自动检测类的信息,并将相应的【BeanDefinition】实例注册到【ApplicationContext】中。 例如,以下两个类适合这样的自动检测:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

要自动检测这些类并注册相应的bean,您需要将【@ComponentScan】添加到您的【 @Configuration】类中,其中【basePackages】属性是这两个类的公共父包。说人话就是:指定一个包名,自动扫描会检测这个包及其子包下的所有类信息。

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}

为简单起见,前面的示例可能使用了注解的value属性 (即@ComponentScan ("org.example"))。

当然我们可以使用以下XML代替,他们是等效的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>
</beans>

注意: <context:component-scan> 的使用会隐式启用 <context:annotation-config>,当使用 <context:component-scan>时,通常不需要包含<context:annotation-config>元素。

3️⃣组件命名

当组件作为扫描过程的一部分被自动检测时,它的bean名是由该扫描器所知道的“BeanNameGenerator”策略生成的。

默认情况下,会使用【@Component】, 【@Repository】,【@Service】和【@Controller】注解的value值,因此将该名称会提供给相应的beanDefination。 如果你的注解不包含任何名称属性,会有默认bean名称生成器将返回【非首字母大写的非全限定类名】。 例如,如果检测到以下组件类,则名称为【myMovieLister】和【movieFinderImp】,这个和xml自动生成的标识符名称不同:

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

4️⃣为自动检测组件提供scope

与spring管理的组件一样,自动检测组件的默认和最常见的作用域是“单例”。 然而,有时您需要一个不同的范围,可以由' @Scope '注解指定。 您可以在注解中提供作用域的名称,如下面的示例所示:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

5️⃣使用过滤器自定义扫描

默认情况下,带有【@Component】、【@Repository】、【@Service】、【@Controller】、【@Configuration】注解的类是一定能被筛选器选中并进行注册的候选组件。 但是,您可以通过应用自定义过滤器来修改和扩展此行为,自由定制筛选哪些或不包含那些组件。 将它们作为@ComponentScan注解的includeFiltersexcludeFilters 属性添加(或者作为XML配置中’ <context:include-filter /> ‘或’ <context:exclude-filter /> ‘元素的子元素)。 每个筛选器元素都需要’ type ‘和’ expression '属性。 下表描述了过滤选项:

过滤方式示例表达式描述
annotation (默认)org.example.SomeAnnotation要在目标组件的类型级别上“存在”或“元注解存在”的注解。
assignableorg.example.SomeClass指定要排除的bean的类
aspectjorg.example…*Service+要被目标组件匹配的AspectJ类型表达式,后边会学习
regexorg.example.Default.*由目标组件的类名匹配的正则表达式
customorg.example.MyTypeFilter’ org.springframework.core.type的自定义实现,TypeFilter”接口。

下面的示例显示了忽略所有【@Repository】注解,而使用【stub】包下的类进行替换:

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    // ...
}

下面的例子显示了等效的XML:

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

【小知识】: 您还可以通过在注解上设置useDefaultFilters=false 或通过提供use-default-filters="false" 作为<component-scan/> 元素的属性来禁用默认过滤器。 这将有效地禁用使用【@Component】、【@Repository 】、【@Service】、【 @Controller】、【@Configuration】注解或元注解的类的自动检测。

6️⃣在组件中定义Bean元数据

Spring组件还可以向容器提供beanDifination元数据。 可以使用 @Bean 注解来实现这一点。

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
}

前面的类是一个Spring组件,它的【doWork()】方法中包含特定于应用程序的代码。 然而,它还提供了一个beanDifination,该beanDifination有一个引用方法【public Instance()】的工厂方法。 【@Bean注解】标识工厂方法,通过【@Qualifier】注解标识一个限定符值。 其他可以指定的方法级注解有【@Scope 】, 【@Lazy 】等。

下面的例子展示了如何做到这一点:

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance"1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

}

7️⃣基于Java的容器配置

🍀(1)@Bean和@Configuration

Spring新的java配置支持的中心组件是带注解的【@Configuration】类和带注解的【@Bean】方法。

@Bean注解用于指示一个方法,该方法负责【实例化、配置和初始化】一个由Spring IoC容器管理的新对象。 对于那些熟悉Spring <beans/>XML配置的人来说,@Bean注解扮演着与<bean/> 元素相同的角色。 你可以在任何Spring @Component中使用@Bean注解方法。 但是,它们最常与@Configuration一起使用。

@Configuration注解的一个类表明它的主要目的是作为beanDifination的源,我们通常称之为【配置类】。 此外,【@Configuration】类允许通过调用同一类中的其他【@Bean 】方法来【定义bean间的依赖关系】。 最简单的【@Configuration】类如下所示:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

前面的’ AppConfig '类等价于下面的Spring <beans/>XML:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

🍀(2)使用 AnnotationConfigApplicationContext实例化Spring容器

下面的章节记录了Spring 3.0中引入的【AnnotationConfigApplicationContext】。 这个通用的【ApplicationContext】实现不仅能够接受【@Configuration】类作为输入,还能够接受普通的【@Component】类和用JSR-330元数据注解的类。

当提供【@Configuration】类作为输入时,【@Configuration】类本身被注册为一个beanDifination,并且类中所有声明的【@Bean】方法也被注册为beanDifination。

​ 当提供【@Component 】和JSR-330相关的注解类时,它们被注册为beanDifination。

a、结构简洁

就像Spring XML文件在实例化【ClassPathXmlApplicationContext】时被用作输入一样,当实例化【AnnotationConfigApplicationContext】时,你可以使用【@Configuration】类作为输入。 这允许Spring容器完全不使用xml,如下例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

正如前面提到的,【AnnotationConfigApplicationContext】并不局限于只与【@Configuration】类一起工作。 任何【@Component】或JSR-330注解类都可以作为输入提供给构造函数,如下面的例子所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.classDependency1.classDependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

前面的例子假设【MyServiceImpl】、【Dependency1】和【Dependency2】使用Spring依赖注入注解,比如【@Autowired】。

b、通过使用’ register(Class<?>…)'以编程方式构建容器

你可以使用一个【没有参数的构造函数】来实例化一个【AnnotationConfigApplicationContext】,然后使用【register()】方法来配置它。 当以编程方式构建一个“AnnotationConfigApplicationContext”时,这种方法特别有用。 下面的例子展示了如何做到这一点:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.classOtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

c、使用 scan(String…)启用组件扫描

要启用组件扫描,你可以像下面这样注解你的 @Configuration 类:

@Configuration
@ComponentScan(basePackages = "com.acme") 
public class AppConfig  {
    // ...
}
<beans> 
	<context:component-scan base-package="com.ydlclass" / > 
</beans>

同时,AnnotationConfigApplicationContext也暴露了【 scan(String…)】方法来允许相同的组件扫描功能,如下例所示:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

请记住,【@Configuration】类是带有【@Component】元注解的一个注解,因此它们是组件扫描的候选对象。 在前面的例子中,假设【AppConfig】在"com.acme"中声明。 在’ refresh() ‘之后,它的所有’ @Bean '方法都被处理并注册为容器中的beanDifination。

🍀(3) @Bean注解

@Bean】是一个方法级注解,与XML<bean/> 元素具有相同的能力。 注解支持<bean/>提供的一些属性,例如:

  • init-method
  • destroy-method
  • autowiring
  • name

你可以在带有【@Configuration】注解的类或带有【@Component】注解的类中使用【@Bean】注解。

a、声明一个 Bean

使用【@Bean】对方法进行注解可以帮助我们申明一个bean。 您可以使用此方法在【ApplicationContext】中注册一个beanDifination,该bean的类型会被指定为【方法的返回值类型】,而具体的返回值则是交由spring管理的bean实例。 默认情况下,bean名与方法名相同。 下面的例子显示了一个【 @Bean 】方法声明:

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

上面的配置与下面的Spring XML完全相同:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

注意: 你也可以使用接口(或基类)作为返回类型来声明你的@Bean方法,如下面的例子所示:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

b、Bean的依赖关系

带注解的【@Bean】方法可以有任意数量的参数,这些参数描述构建该bean所需的依赖关系。 例如,如果我们的【TransferService】需要一个【AccountRepository】,我们可以用一个方法参数来实现这个依赖,如下例所示:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

c、接受生命周期回调

  • 任何用【@Bean】注解定义的类都支持常规的生命周期回调,并且可以使用JSR-250的’ @PostConstruct ‘和’ @PreDestroy '注解。
  • 也完全支持常规的Spring lifecycle回调。 如果一个bean实现了’ InitializingBean ‘、’ DisposableBean ‘或’ Lifecycle ',则容器会调用它们各自的方法。
  • 标准的【Aware 】接口也完全支持。

@Bean注解】支持指定任意的初始化和销毁回调方法,就像Spring XML在’ bean ‘元素上的’ init-method ‘和’ destroy-method '属性一样,如下面的示例所示:

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

小知识: 对于上面例子中的’ BeanOne ‘,在构造过程中直接调用’ init() '方法同样有效,如下例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}

当您直接在代码中进行配置时,您可以对您的对象做任何您想做的事情,而不总是需要依赖于容器生命周期。

d、指定Bean范围

Spring包含了【@Scope】注解,以便您可以指定bean的范围。
默认的作用域是’ singleton ‘,但是你可以用’ @Scope '注解来覆盖它,如下面的例子所示:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}

e、定制Bean命名

默认情况下,配置类使用【@Bean】方法的名称作为结果bean的名称。 但是,可以使用’ name '属性覆盖该功能,如下例所示:

@Configuration
public class AppConfig {

    @Bean("myThing")
    public Thing thing() {
        return new Thing();
    }
}

有时需要为单个bean提供多个名称,或者称为bean别名。【@Bean】注解的’ name '属性为此接受String数组。 下面的例子展示了如何为一个bean设置多个别名:

@Configuration
public class AppConfig {

    @Bean({"dataSource""subsystemA-dataSource""subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

f、Bean 描述

有时,提供bean的更详细的文本描述是很有帮助的。 当bean被公开(可能通过JMX)用于监视目的时,这可能特别有用。

要向【@Bean】添加描述,可以使用【@Description】注解,如下面的示例所示:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}

🍀(4)@Configuration

@Configuration】是一个类级注解,指示一个对象是beanDifination的源。【@Configuration】类通过【@Bean】带注解的方法声明bean。 【在“@Configuration”类上调用“@Bean”方法也可以用来定义bean间的依赖关系】。

注入bean之间的依赖

@Bean方法在没有标注@Configuration的类中声明时,它们被认为是在【lite】模式下处理的。 在【@Component】中声明的Bean方法甚至在一个普通的类中声明的Bean方法都被认为是【lite】。在这样的场景中,【@Bean】方法是一种通用工厂方法机制。

​ 与@Configuration 不同,【lite】模式下 【@Bean】方法不能【声明bean】间的【依赖关系】。 因此,这样的【@Bean】方法不应该调用其他【@Bean】下的方法。 每个这样的方法实际上只是特定bean引用的工厂方法,没有任何特殊的运行时语义。

​ 在一般情况下,@Bean方法要在【@Configuration】类中声明,这种功能情况下,会使用【full】模式,因此交叉方法引用会被重定向到容器的生命周期管理。 这可以防止通过常规Java调用意外调用相同的Bean,这有助于减少在【lite】模式下操作时难以跟踪的微妙错误。

@Bean@Configuration注解将在下面几节中深入讨论。 不过,我们首先介绍通过使用基于java的配置创建spring容器的各种方法。

当bean相互依赖时,表示这种依赖就像让一个bean方法调用另一个bean方法一样简单,如下面的示例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        // full模式可以直接调用方法,这个调用过程由容器管理,lite模式这就是普通方法调用,多次调用会产生多个实例。
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

在前面的例子中,【beanOne】通过构造函数注入接收对【beanTwo】的引用。

考虑下面的例子,它显示了一个带注解的@Bean方法被调用两次:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

clientDao() 在【clientService1()】和【clientService2()】中分别被调用一次。 由于该方法创建了一个新的【ClientDaoImpl】实例并返回它,所以通常期望有两个实例(每个服务一个)。 这肯定会有问题。在Spring中,实例化的bean默认有一个【单例】作用域,在调用父方法并创建新实例之前,首先检查容器中是否有缓存的(有作用域的)bean。

我们目前学习的描述候选组件的注解很多,但是仔细意思考,其实很简单:

我们自己的写代码通常使用以下注解来标识一个组件:

  • @Component 组件的通用注解
  • @Repository,持久层
  • @Service,业务层
  • @Controller,控制层
  • @Configuration + @Bean

配置类通常是我们不能修改源代码,但是需要注入别人写的类。例如向容器注入一个德鲁伊数据源的bean,我们是绝对不能给这个类加个【@Component 】注解的。

🍀(5) 使用 @Import 注解

就像在Spring XML文件中使用<import/> 元素来实现模块化配置一样,@Import注解允许从另一个配置类加载【@Bean】定义,如下面的示例所示:

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

现在,在实例化上下文时不需要同时指定ConfigA.class ConfigB.class,只需要显式地提供【ConfigB】,如下面的示例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

这种方法简化了容器实例化,因为只需要处理一个类,而不是要求您在构造过程中记住潜在的大量【@Configuration】类。

【小知识】: 我们一样可以给该注解传入一个实现了ImportSelector接口的类,返回的字符串数组的Bean都会被加载到容器当中:

public class ConfigSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.ydlclass.A""com.ydlclass.B"};
    }
}

🍀(6)结合Java和XML配置

Spring的【@Configuration】类支持的目标并不是100%完全替代Spring XML,有些场景xml仍然是配置容器的理想方式。

我们有如下选择:

  • (1)容器实例化在一个“以XML为中心”的方式使用,例如“ClassPathXmlApplicationContext”。
  • (2)"以java编程的方式为中心”的方式,实例化它通过使用【@ImportResource】注解导入XML。

以xml为中心使用“@Configuration”类

最好从XML引导Spring容器,并以一种特别的方式包含【@Configuration 】类。将【@Configuration 】类声明为普通的Spring <bean/> 元素。记住,【@Configuration】类最终是容器中的beanDifination。

下面的例子展示了Java中一个普通的配置类:

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}

下面的例子显示了一个’ system-test-config.xml '文件的一部分:

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

下面的示例显示了一个可能的’ jdbc '。 属性的文件:

user=root
password=root
url=jdbc:mysql://127.0.0.1:3306/ydlclass?characterEncoding=utf8&serverTimezone=Asia/Shanghai
driverName=com.mysql.cj.jdbc.Driver
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

因为【@Configuration】是用【@Component】注解的,所以被【@Configuration】注解的类会自动被组件扫描。 使用与前面示例中描述的相同的场景,我们可以重新定义system-test-config.xml来利用组件扫描。

下面的示例显示了修改后的system-test-config.xml文件:

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

使用@ImportResource以类为中心使用XML

在【@Configuration】类是配置容器的主要机制的应用程序中,可能仍然需要使用至少一些XML。 在这些场景中,您可以使用【@ImportResource】注解,并只定义所需的XML。 这样做可以实现一种“以java为中心”的方法来配置容器,并将XML最小化。

下面的例子说明了这一点:

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}

properties-config.xml:

<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

jdbc.properties::

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=

启动容器:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

8️⃣BeanFactory和FactoryBean

🍀(1)BeanFactory

BeanFactory是一个接口,它是Spring中工厂的顶层规范,是SpringIoc容器的核心接口,它定义了getBean()containsBean()等管理Bean的通用方法。Spring的容器都是它的具体实现如:

  • DefaultListableBeanFactory
  • XmlBeanFactory
  • ApplicationContext

这些实现类又从不同的维度分别有不同的扩展。

它的源码如下:

public interface BeanFactory {

	//对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象,
	//如果需要得到工厂本身,需要转义
	String FACTORY_BEAN_PREFIX = "&";

	//根据bean的名字,获取在IOC容器中得到bean实例
	Object getBean(String name) throws BeansException;

	//根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。
	<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;

	Object getBean(String name, Object... args) throws BeansException;

	<T> T getBean(Class<T> requiredType) throws BeansException;

	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

	//提供对bean的检索,看看是否在IOC容器有这个名字的bean
	boolean containsBean(String name);

	//根据bean名字得到bean实例,并同时判断这个bean是不是单例
	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

	//得到bean实例的Class类型
	@Nullable
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;

	//得到bean的别名,如果根据别名检索,那么其原名也会被检索出来
	String[] getAliases(String name);
}

使用场景:

  • 从Ioc容器中获取Bean(byName or byType)
  • 检索Ioc容器中是否包含指定的Bean
  • 判断Bean是否为单例

🍀(2)FactoryBean

首先它是一个Bean,但又不仅仅是一个Bean。它是一个能生产或修饰对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例。一般用于创建第三方或复杂对象。

源码如下:

public interface FactoryBean<T> {

	//从工厂中获取bean
	@Nullable
	T getObject() throws Exception;

	//获取Bean工厂创建的对象的类型
	@Nullable
	Class<?> getObjectType();

	//Bean工厂创建的对象是否是单例模式
	default boolean isSingleton() {
		return true;
	}
}

从它定义的接口可以看出,FactoryBean表现的是一个工厂的职责。 即一个Bean A如果实现了FactoryBean接口,那么A就变成了一个工厂,根据A的名称获取到的实际上是工厂调用getObject()返回的对象,而不是A本身,如果要获取工厂A自身的实例,那么需要在名称前面加上'&'符号。

  • getObject(‘name’):返回工厂中的实例
  • getObject(‘&name’):返回工厂本身的实例

通常情况下,bean 无须自己实现工厂模式,Spring 容器担任了工厂的 角色;但少数情况下,容器中的 bean 本身就是工厂,作用是产生其他 bean 实例。由工厂 bean 产生的其他 bean 实例,不再由 Spring 容器产生,因此与普通 bean 的配置不同,不再需要提供 class 元素。

下边的例子我们使用FactoryBean注入一个bean

@Component
public class MyBean implements FactoryBean {
    private String message;
    public MyBean() {
        this.message = "通过构造方法初始化实例";
    }
    @Override
    public Object getObject() throws Exception {
        // 这里并不一定要返回MyBean自身的实例,可以是其他任何对象的实例。
        //如return new Student()...
        return new MyBean("通过FactoryBean.getObject()创建实例");
    }
    @Override
    public Class<?> getObjectType() {
        return MyBean.class;
    }
    public String getMessage() {
        return message;
    }
}

MyBean实现了FactoryBean接口的两个方法,getObject()是可以返回任何对象的实例的,这里测试就返回MyBean自身实例,且返回前给message字段赋值。

同时在构造方法中也为message赋值。然后测试代码中先通过名称获取Bean实例,打印message的内容,再通过&+名称获取实例并打印message内容。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class FactoryBeanTest {
    @Autowired
    private ApplicationContext context;
    @Test
    public void test() {
        MyBean myBean1 = (MyBean) context.getBean("myBean");
        System.out.println("myBean1 = " + myBean1.getMessage());
        MyBean myBean2 = (MyBean) context.getBean("&myBean");
        System.out.println("myBean2 = " + myBean2.getMessage());
        System.out.println("myBean1.equals(myBean2) = " + myBean1.equals(myBean2));
    }
}

结果:

myBean1 = 通过FactoryBean.getObject()初始化实例 
myBean2 = 通过构造方法初始化实例 
myBean1.equals(myBean2) = false 

使用场景

说了这么多,为什么要有FactoryBean这个东西呢,有什么具体的作用吗? FactoryBean在Spring中最为典型的一个应用就是用来创建AOP的代理对象。

我们知道AOP实际上是Spring在运行时创建了一个代理对象,也就是说这个对象,是我们在运行时创建的,而不是一开始就定义好的,这很符合工厂方法模式。更形象地说,AOP代理对象通过Java的反射机制,在运行时创建了一个代理对象,在代理对象的目标方法中根据业务要求织入了相应的方法。这个对象在Spring中就是——ProxyFactoryBean

所以,FactoryBean为我们实例化Bean提供了一个更为灵活的方式,我们可以通过FactoryBean创建出更为复杂的Bean实例。

🍀(3)区别

  • FactoryBean本质上还是一个Bean,也归BeanFactory管理,他是用来构建bean,特别是复杂的bean的。
  • BeanFactory是Spring容器的顶层接口,FactoryBean是用来管理bean的。

9️⃣环境抽象

  • 接口是一个抽象,集成在容器中,它模拟了应用程序环境的两个关键方面:【profiles】 and 【properties】。
  • 一个profile是一个【给定名字】的,在【逻辑上分了组】的beanDifination配置,只有在给定的profile是激活的情况下才向容器注册。
  • properties在几乎所有的应用程序中都扮演着重要的角色,并且可能源自各种来源:属性文件、JVM系统属性、系统环境变量、JNDI、servlet上下文参数、特定的【Properties】对象、“Map”对象,等等。与属性相关的“Environment”对象的作用是为用户提供一个方便的服务接口,用于配置属性源并从那里解析属性。

🍀(1)Profiles

Profiles在核心容器中提供了一种机制,允许在不同环境中注册不同的Bean。 “环境”这个词对不同的用户有不同的含义,

  • 在开发中使用内存中的数据源,还是在生产中从JNDI中查找的数据源。
  • 为客户A和客户B部署注册定制的bean实现。

考虑一个实际应用程序中的第一个用例,它需要一个“数据源”。 在测试环境中,配置可能类似如下:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}

现在考虑如何将该应用程序部署到生产环境中,假设应用程序的数据源已注册到生产应用程序服务器的JNDI目录中。 我们的’ dataSource ’ bean现在看起来如下所示:

@Bean
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

重点:问题是如何根据当前环境在使用这两种数据源之间进行切换?

当然,我们可以使用 @Profile

@Profile】注解允许您指出,当一个或多个bean在哪一种Profile被激活时被注入。 使用前面的例子,我们可以将dataSource配置重写如下:

@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

@Profile也可以在方法级别声明,只包含一个配置类的一个特定bean(例如,对于一个特定bean的替代变体),如下面的示例所示:

@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development") 
    public DataSource standaloneDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

    @Bean("dataSource")
    @Profile("production") 
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

🍀(2)XML Bean 定义环境

XML对应的是<beans> 元素的’ profile '属性。 前面的示例配置可以在两个XML文件中重写,如下所示:

<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>

<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

也可以避免在同一个文件中分割和嵌套<beans/> 元素,如下例所示:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="development">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>

🍀(3)激活一个环境

现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件是活动的。 如果我们现在启动我们的样例应用程序,我们会看到抛出一个NoSuchBeanDefinitionException,因为容器无法找到名为dataSource的Spring bean。

激活配置文件有几种方式,但最直接的方式是通过【ApplicationContext】可用的【Environment】API以编程方式执行。 下面的例子展示了如何做到这一点:

@Test
public void testProfile(){
    // 创建容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    // 激活环境
    context.getEnvironment().setActiveProfiles("development");
    // 扫包
    context.scan("com.ydlclass.datasource");
    //  刷新
    context.refresh();
    // 使用
    DataSource bean = context.getBean(DataSource.class);
    logger.info("{}",bean);
}

此外,你还可以通过spring.profiles来声明性地激活环境【active】属性,它可以通过系统环境变量、JVM系统属性、servlet上下文参数在’ web.xml '中指定。

​请注意,配置文件不是一个“非此即彼”的命题。 您可以一次激活多个配置文件。 通过编程方式,您可以向’ setActiveProfiles() ‘方法提供多个配置文件名,该方法接受’ String…'可变参数。 下面的示例激活多个配置文件:

加入启动参数:

-Dspring.profiles.active="profile1,profile2"

编程的方式:

ctx.getEnvironment().setActiveProfiles("profile1""profile2");

🍀(4)porperties

Spring的【环境抽象】提供了对【属性】的搜索操作。 考虑以下例子:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

在前面的代码片段中,我们看到了查询Spring是否为当前环境定义了【my-property】属性的方法。 为了回答这个问题,“Environment”对象对一组【PropertySource】对象执行搜索。 “PropertySource”是对任何【键值对源】的一个简单抽象, spring的【StandardEnvironment】配置了两个PropertySource对象——一个代表JVM系统属性的集合(“System.getProperties()”)和一个代表系统环境变量的设置(System.getenv()”)。

Map<String,String> getenv = System.getenv();
Properties properties = System.getProperties();

具体地说,当你使用【StandardEnvironment】时,如果【my-property】系统属性或【my-property】环境变量在运行时存在,对env.containsProperty("my-property")的调用将返回true

​ 最重要的是,整个机制都是可配置的。 也许您有一个自定义的属性源,希望将其集成到此搜索中。 为此,我们可以实例化自己的【PropertySource】,并将它添加到当前’ Environment ‘的’ propertyssources '集合中。 下面的示例显示了如何这样做:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

使用@PropertySource

@PropertySource】注解提供了一种方便的声明性机制,用于向Spring的【Environment】中添加【 PropertySource】。

给定一个名为app的文件。 下面的【@Configuration】类使用了【@PropertySource】,从而调用“testBean.getName()”返回“myTestBean”:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

🔟事件机制

为了以更面向框架的风格增强【BeanFactory】功能,ApplicationContext还提供了以下功能:

  • 通过MessageSource接口访问i18n风格的消息,实现国际化。
  • 通过ResourceLoader接口访问资源,例如url和文件。
  • 事件发布,即通过使用’ ApplicationEventPublisher ‘接口发布实现’ ApplicationListener’接口的bean。
  • 通过“HierarchicalBeanFactory”接口,加载多个(分层的)上下文,让每个上下文都集中在一个特定的层上,比如应用程序的web层。

🍀(1)自定义事件

ApplicationContext中的事件处理是通过【ApplicationEvent】类和【ApplicationListener】接口提供的。 如果将实现“ApplicationListener”接口的bean部署到上下文中,那么每次将【ApplicationEvent】发布到【ApplicationContext】时,都会通知该bean。 本质上,这是标准的Observer设计模式。

​从spring4.2开始,事件基础设施得到了显著的改进,并提供了一个【基于注解的事件模型】以及发布任意事件的能力 。

您可以使用spring创建和发布自己的自定义事件。 下面的例子展示了一个简单的类,它扩展了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...
    }
}

将来容器只要发布这个事件,这个监听者就可以感知。

基于注解的事件监听器

您可以使用【@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.classContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}

🍀(2)Spring提供的标准事件

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

这些标准事件会在特定的时间发布,我们可以监听这些事件,并在事件发布时做我们想做的工作。


后记

在这里插入图片描述
↪️本文下接:XXXX
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~

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

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

相关文章

【Java开发】 Spring 03:云服务器 Docker 环境下安装 MongoDB 并连接 Spring 项目实现简单 CRUD:

接下来介绍一下 NoSQL &#xff0c;相比于 Mysql 等关系型的数据库&#xff0c;NoSQL &#xff08;文档型数据库&#xff09;由于存储的数据之间无关系&#xff0c;因此具备大数据量&#xff0c;高性能等特点&#xff0c;用于解决大规模数据集合多重数据种类带来的挑战&#xf…

Aspose.OMR for .NET 22.11.X Crack

Aspose.OMR for .NET 是一个可靠且通用的编程 API&#xff0c;用于设计和自动识别手填答题卡、调查、测试、选票、SAT 考试表格、保险索赔以及受访者通过随机抽取答案来回答问题的类似文件在圆形或正方形中标记。从成百上千个表单中手动读取和汇总结果的漫长且容易出错的过程归…

深入学习函数(2)

目录 一、函数的嵌套调用和链式访问 1、嵌套调用 2、链式访问 二、函数的声明和定义 1、函数的声明 2、函数的定义 声明和定义的拓展 拆成三个文件的好处 一、函数的嵌套调用和链式访问 当代码写的越来越多时&#xff0c;就会发现&#xff0c;其实一个程序都…

Day802.JVM热点问题 -Java 性能调优实战

JVM部分热点问题 Hi&#xff0c;我是阿昌&#xff0c;今天学习JVM部分热点问题的内容。 1、字符串常量不是在java8中已经被放入到堆中了吗&#xff0c;应该不在方法区中了&#xff0c;咋一些图中还在方法区中&#xff1f; JVM 的内存模型只是一个规范&#xff0c;方法区也是…

Fiddler基础使用

目录预备知识关于web的一些基础知识实验目的实验环境实验步骤一实验步骤二实验步骤三预备知识 关于web的一些基础知识 要分析Fiddler抓取的数据包&#xff0c;我们首先要熟悉HTTP协议。HTTP即超文本传输协议&#xff0c;是一个基于请求/响应模式的、无状态的、应用层的协议&a…

【Python开发】Flask项目的组织架构

Flask项目的组织架构在大型Flask项目中&#xff0c;主要有三种常见的项目组织架构&#xff1a;功能式架构&#xff08;也就是 Bluelog 程序使用的架构&#xff09;、分区式架构和混合式架构。我们将以一个示例程序 myapp 作为示例来介绍这三种架构的特点和区别&#xff0c;这个…

教你用HTML+CSS实现百叶窗动画效果

推荐学习专栏&#xff1a; 【JavaWeb】Web前端JavaWeb学习专栏 文章目录前言1、百叶窗效果2、原理讲解3、制作百叶窗4、资源下载5、完整代码总结前言 我们浏览网页的时候总能看见一些炫酷的特效&#xff0c;比如百叶窗效果&#xff0c;本文我们就用HTMLCSS制作一个百叶窗小项…

副业该怎么选择,适合新手的四个副业项目,零基础也可操作的兼职

副业有可能有时挣得并不多&#xff0c;但它是一个改变未来的好机会。假如玩的开了&#xff0c;盈利并不比你工资少。95%的人自主创业也是从第二职业做起&#xff0c;做着干着就全职的了。 四个全员第二职业&#xff0c;新手如何做到单月9000&#xff0c;深入分析看下文&#xf…

license授权服务器

项目介绍 为软件提供授权制的使用方式&#xff0c;license申请端可以为产品生成license授权文件&#xff0c;集成了flowable工作流&#xff0c;经审批后生成license文件。 然后导入到服务端。客户端与服务端netty通信。实时判断license是否合法&#xff0c;从而使软件得到安全…

辣椒辣素修饰卵清蛋白 Capsaicin-ova,苍耳亭偶联鸡卵白蛋白 Xanthatin-ovalbumin

产品名称&#xff1a;辣椒辣素修饰卵清蛋白 英文名称&#xff1a; Capsaicin-ova 用途&#xff1a;科研 状态&#xff1a;固体/粉末/溶液 产品规格&#xff1a;1g/5g/10g 保存&#xff1a;冷藏 储藏条件&#xff1a;-20℃ 储存时间&#xff1a;1年 辣椒碱又称辣椒辣素&#xf…

抓包工具总结对照【fiddler F12 Charles wireshark】

本文主要对比fiddler Charles wireshark&#xff0c;纯手敲制作&#xff0c;动动小手点赞 文章目录抓包fidderF12开发者工具wiresharkCharles下载安装使用web抓包APP 抓包IOSAndroidCharles过滤弱网测试篡改数据修改请求数据重复发送请求Compose编辑接口服务器压力测试本地映射…

前端:Node.js遇到的错误整理

node.js当前错误汇总&#xff1a;错误1npm WARN config global --global, --local are deprecated. Use --locationglobal instead.原因&#xff1a;初步判断是node.js版本问题解决方法&#xff1a;错误2npm WARN logfile could not create logs-dir: Error: EPERM: operation …

MySQL主从复制最全教程(CentOS7 yum)

一、MySQL主从复制介绍 &#xff08;1&#xff09;MySQL数据库默认是支持主从复制的&#xff0c;不需要借助于其他的技术&#xff0c;我们只需要在数据库中简单的配置即可。 &#xff08;2&#xff09;MySQL主从复制是一个异步的复制过程&#xff0c;底层是基于Mysql数据库自…

在Docker里安装FastDFS分布式文件系统详细步骤

安装需要的软件包 yum install -y yum-utils 设置yum源 yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.repo 安装docker yum install -y docker-ce 查看docker版本验证安装是否成功 docker -v 启动docker systemctl start d…

C++ 实用指南

C 发展得非常快&#xff01;例如&#xff0c;C 标准的页数从 C98/03 的 879 页增加到了 C20 的 1834 页&#xff0c;多了近 1000 页&#xff01;更重要的是&#xff0c;C 每次修订后&#xff0c;我们都会获得几十个新特性。你需要学习所有这些东西才能写出好代码吗&#xff1f;…

【计算机毕业设计】旅游网站ssm源码

下载链接:https://download.csdn.net/download/licongzhuo/87051535https://download.csdn.net/download/licongzhuo/87051535 一、系统截图&#xff08;需要演示视频可以私聊&#xff09; 摘 要 随着人民生活水平的提高,旅游业已经越来越大众化,而旅游业的核心是信息,不论是…

Redis数据结构之——跳表skiplist

写在前面 以下内容是基于Redis 6.2.6 版本整理总结 一、跳表&#xff08;skiplist&#xff09; 如何理解跳表&#xff1f;在了解跳表之前&#xff0c;我们先从普通链表开始&#xff0c;一点点揭开跳表的神秘面纱~ 首先&#xff0c;普通单链表来说&#xff0c;即使链表是有序…

第2-3-4章 上传附件的接口开发-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss

文章目录5.3 接口开发-上传附件5.3.1 接口文档5.3.2 代码实现5.3.3 接口测试5.3 接口开发-上传附件 第2-1-2章 传统方式安装FastDFS-附FastDFS常用命令 第2-1-3章 docker-compose安装FastDFS,实现文件存储服务 第2-1-5章 docker安装MinIO实现文件存储服务-springboot整合minio…

MindMaster思维导图及亿图图示会员 超值获取途径

MindMaster思维导图及亿图图示会员 超值获取途径 会员九折优惠方法分享给大家&#xff01;如果有需要&#xff0c;可以上~ 以下是食用方法&#xff1a; MindMaster 截图 亿图图示 截图 如果需要MindMaster思维导图或者亿图图示会员&#xff0c;可按照如下操作领取超值折扣优惠…

SaaS系统平台赋能大健康产业互联网变革,助力企业提升市场占有率

当前&#xff0c;数字化浪潮正在重塑大健康产业。随着全国多个重要省市的数字医疗、数字医保等措施正火热展开&#xff0c;我国大健康产业的数字化转型进程正在提速&#xff0c;这也为新一轮的行业洗牌带来新的发展机遇。 大健康产业数字化转型痛点&#xff1a;传统医疗信息化…