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

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

使用场景


说了这么多,为什么要有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.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}

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


image.pngimage.png


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

相关文章
|
9天前
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
23 5
|
2月前
|
XML 缓存 Java
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
49 10
|
2月前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
|
3月前
|
人工智能 自然语言处理 Java
Spring AI,Spring团队开发的新组件,Java工程师快来一起体验吧
文章介绍了Spring AI,这是Spring团队开发的新组件,旨在为Java开发者提供易于集成的人工智能API,包括机器学习、自然语言处理和图像识别等功能,并通过实际代码示例展示了如何快速集成和使用这些AI技术。
Spring AI,Spring团队开发的新组件,Java工程师快来一起体验吧
|
3月前
|
Java Spring
Spring的AOP组件详解
该文章主要介绍了Spring AOP(面向切面编程)组件的实现原理,包括Spring AOP的基础概念、动态代理模式、AOP组件的实现以及Spring选择JDK动态代理或CGLIB动态代理的依据。
Spring的AOP组件详解
|
3月前
|
Java 开发工具 Spring
【Azure 事件中心】azure-spring-cloud-stream-binder-eventhubs客户端组件问题, 实践消息非顺序可达
【Azure 事件中心】azure-spring-cloud-stream-binder-eventhubs客户端组件问题, 实践消息非顺序可达
|
3月前
|
安全 Java Spring
Spring Boot 关闭 Actuator ,满足安全工具扫描
Spring Boot 关闭 Actuator ,满足安全工具扫描
393 0
|
4月前
|
安全 前端开发 Java
Java技术栈中的核心组件:Spring框架
Java作为一门成熟的编程语言,其生态系统拥有众多强大的组件和框架,其中Spring框架无疑是Java技术栈中最闪耀的明星之一。Spring框架为Java开发者提供了一套全面的编程和配置模型,极大地简化了企业级应用的开发流程。
52 1
|
4月前
|
存储 安全 Java
实现基于Spring Cloud的分布式配置管理
实现基于Spring Cloud的分布式配置管理
|
4月前
|
安全 Java Spring
Spring Boot中的环境配置和管理
Spring Boot中的环境配置和管理