spring中使用到的设计模式

我们把上面的例子搬到互联网,就是 OAuth 的设计了。

首先,居民小区就是储存用户数据的网络服务。比如,微信储存了我的好友信息,获取这些信息,就必须经过微信的"门禁系统"。

其次,快递员(或者说快递公司)就是第三方应用,想要穿过门禁系统,进入小区。

最后,我就是用户本人,同意授权第三方应用进入小区,获取我的数据。

简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。

交换机(Switch)是一种网络设备,用于将多个计算机或网络设备连接在一起,形成一个局域网(LAN)。交换机通过在数据链路层(第2层)工作,基于MAC地址来转发数据包,从而提高网络效率和性能。理解交换机需要掌握以下几个关键概念:

  1. 交换机的基本功能
    数据包转发:交换机接收网络设备发送的数据包,通过查找其内部的MAC地址表,决定将数据包转发到哪个端口。
    MAC地址表:交换机维护一张MAC地址表,记录网络中每个设备的MAC地址和对应的端口。这张表会动态更新,随着网络中的设备发送数据包,交换机会学习这些设备的MAC地址。
    全双工通信:交换机的每个端口都支持全双工通信,即同时进行发送和接收数据,从而提高网络带宽和减少冲突。
  2. 交换机的工作原理
    学习(Learning):交换机接收到一个数据包后,会记录下发送这个数据包的设备的MAC地址和相应的端口号,更新到MAC地址表中。
    转发/过滤(Forwarding/Filtering):交换机根据数据包的目的MAC地址查找MAC地址表,决定将数据包转发到哪个端口。如果目的MAC地址在表中没有对应项,交换机会将数据包广播到所有端口。
    老化(Aging):为了保持MAC地址表的有效性,交换机会定期移除一定时间内没有活动的MAC地址条目。
  3. 交换机类型
    非管理型交换机:即插即用,没有配置功能,适用于小型网络或家庭网络。
    管理型交换机:可以通过命令行接口(CLI)、图形用户界面(GUI)或SNMP协议进行配置和管理,适用于大型网络或企业网络。
  4. 交换机的优点
    提高网络性能:通过将数据包只发送到目的设备,减少网络拥塞和冲突。
    安全性:可以配置VLAN(虚拟局域网),将网络划分为多个逻辑子网,增强安全性和管理性。
    扩展性:可以轻松添加新的设备,扩大网络规模。
  5. 相关概念
    VLAN(虚拟局域网):通过交换机在逻辑上将一个物理网络划分为多个独立的子网,提高网络的灵活性和安全性。
    链路聚合(Link Aggregation):将多个物理链路捆绑在一起,形成一个逻辑链路,提高带宽和冗余性。
    生成树协议(STP):防止网络环路,确保网络拓扑结构的无环性。
    总之,交换机是现代网络中不可或缺的设备,它通过高效的MAC地址转发机制和丰富的管理功能,为网络的稳定性、安全性和扩展性提供了有力支持。

日志记录与性能监控:插件可以拦截执行器(Executor)、语句处理器(StatementHandler)等组件的方法,记录 SQL 语句、执行时间、参数信息等,用于调试、性能分析或审计目的。

动态 SQL 优化:插件可以分析和优化动态生成的 SQL 语句,比如去除无效的条件、合并重复的子查询等,提高查询效率。

自动分页:通过插件来实现分页查询的功能。

数据脱敏:对敏感数据(如身份证号、手机号登)进行脱敏处理,确保在查询结果中返回的事加密、脱敏后的数据

数据校验:在执行 INSERT、UPDATE 操作前,插件可以对即将写入数据库的参数进行合法性校验,如格式检查、唯一性验证、业务规则检查等,保证数据完整性。

下面就来实现一个记录 SQL 执行时间的插件,以实现对慢SQL的监控。



@Slf4j
@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class, Integer.class }) })
public class PageInterceptor implements Interceptor {
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
		BoundSql boundSql = statementHandler.getBoundSql();
		PageRequest pageRequest = getPageRequest(boundSql);
		if (pageRequest == null) { // 如果不是分页请求,则往下继续执行
			return invocation.proceed();
		}
		setCount2PageRequest(invocation, boundSql.getSql(), pageRequest); // 查询count,将count写入到入参pageRequest中
		return executePageQuery(statementHandler, invocation, pageRequest); // 改写originalSql -> pageSql
	}

	private Object executePageQuery(StatementHandler statementHandler, Invocation invocation, PageRequest pageRequest) throws InvocationTargetException, IllegalAccessException {
		String pageSql = buildPageSql(statementHandler.getBoundSql().getSql(), pageRequest);

		MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
		metaObject.setValue("boundSql.sql", pageSql);
		return invocation.proceed();
	}

	private String buildPageSql(String originalSql, PageRequest pageRequest) {
		return new StringBuilder().append(originalSql).append(" limit ")
		.append(pageRequest.getPageSize() * (pageRequest.getPageNo() - 1))
		.append(",").append(pageRequest.getPageSize())
		.toString();
	}

	private void setCount2PageRequest(Invocation invocation, String originalSql, PageRequest pageRequest) throws SQLException {
		int count = getCount(invocation, originalSql);
		pageRequest.setTotal(count);
		pageRequest.setTotalPage(count / pageRequest.getPageSize() + 1);
	}

	private int getCount(Invocation invocation, String originSql) throws SQLException {
		Connection connection = (Connection) invocation.getArgs()[0];
		PreparedStatement ps = null;
		ResultSet resultSet = null;
		try {
			StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
			MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
			ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("parameterHandler");
			ps = connection.prepareStatement(buildCountSql(originSql));
			parameterHandler.setParameters(ps);
			resultSet = ps.executeQuery();
			if (resultSet == null) {
				return 0;
			}
			if (resultSet.next()) {
				return resultSet.getInt(1);
			}
		} catch (Exception e) {

		} finally {
			if (ps != null) {
				ps.close();
			}
			if (resultSet != null) {
				resultSet.close();
			}
		}
		return 0;
	}

	private String buildCountSql(String originalSql) {
        return "select count(*) from (" + originalSql + " ) as temp";
    }
 
    private PageRequest getPageRequest(BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        if (parameterObject instanceof Map) {
            Map paramMap = (Map) parameterObject;
            for (Object value : paramMap.values()) {
                if (value instanceof PageRequest) {
                    return (PageRequest) value;
                }
            }
        } else if (parameterObject instanceof PageRequest) {
            return (PageRequest) parameterObject;
        }
        return null;
    }
}

2.3. 内置的 Resource 实现

Spring包括几个内置的 Resource 实现。

关于Spring中可用的 Resource 实现的完整列表,请查阅 Resource avadoc中的 "所有已知的实现类" 部分。

2.3.1. UrlResource

UrlResource 包装了一个 java.net.URL,可以用来访问任何通常可以用URL访问的对象,如文件、HTTPS目标、FTP目标等。所有的URL都有一个标准化的 String 表示,这样,适当的标准化前缀被用来表示一个URL类型与另一个URL类型。这包括 file: 用于访问文件系统路径,https: 用于通过HTTPS协议访问资源,ftp: 用于通过FTP访问资源,以及其他。

UrlResource 是由Java代码通过明确使用 UrlResource 构造函数来创建的,但当你调用一个API方法时,往往是隐式创建的,该方法需要一个代表路径的 String 参数。对于后一种情况,JavaBeans 的 PropertyEditor 最终决定创建哪种类型的 Resource。如果路径字符串包含一个众所周知的(对属性编辑器来说)前缀(如 classpath:),它将为该前缀创建一个适当的专用 Resource。然而,如果它不认识这个前缀,它就假定这个字符串是一个标准的URL字符串,并创建一个 UrlResource。

2.3.2. ClassPathResource

该类表示应从 classpath 获得的资源。它使用线程上下文的类加载器、一个给定的类加载器或一个给定的类来加载资源。

如果 class path 资源驻留在文件系统中,该 Resource 实现支持作为 java.io.File 的解析,但对于驻留在 jar 中且未被扩展(由 servlet 引擎或任何环境)到文件系统中的 class path 资源,则不支持。为了解决这个问题,各种 Resource 实现总是支持解析为 java.net.URL。

Java代码通过明确使用 ClassPathResource 构造函数来创建 ClassPathResource,但当你调用一个API方法时,往往会隐含地创建,该方法需要一个代表路径的 String 参数。对于后一种情况,JavaBeans PropertyEditor 会识别字符串路径上的特殊前缀 classpath:,并在这种情况下创建一个 ClassPathResource。

2.3.3. FileSystemResource

这是一个用于 java.io.File 句柄 的 Resource 实现。它也支持 java.nio.file.Path 句柄,应用Spring标准的基于String的路径转换,但通过 java.nio.file.Files API执行所有操作。对于纯粹的基于 java.nio.path.Path 的支持,请使用 PathResource 代替。FileSystemResource 支持以`File` 和 URL 的形式解析。

2.3.4. PathResource

这是一个用于 java.nio.file.Path 处理的 Resource 实现,通过 Path API执行所有操作和转换。它支持作为 File 和 URL 的解析,也实现了扩展的 WritableResource 接口。PathResource 实际上是一个纯粹的基于 java.nio.path.Path 的、具有不同 createRelative 行为的 FileSystemResource 替代品。

2.3.5. ServletContextResource

这是一个用于ServletContext资源的 Resource 实现,它解释了相对于Web应用根目录中的相对路径。

它总是支持流访问和URL访问,但只有当Web应用程序归档文件被扩展并且资源在文件系统上时才允许 java.io.File 访问。无论它是否被扩展并在文件系统上,还是直接从JAR或其他地方(如数据库)访问(这是可以想象的),实际上都取决于Servlet容器。

2.3.6. InputStreamResource

InputStreamResource 是给定的 InputStream 的一个 Resource 实现。只有在没有特定的 Resource 实现的情况下才应该使用它。特别是在可能的情况下,最好选择 ByteArrayResource 或任何基于文件的 Resource 实现。

与其他 Resource 实现不同,这是一个已经打开的资源的描述符。因此,它从 isOpen() 返回 true。如果你需要把资源描述符保存在某个地方,或者需要多次读取一个流,请不要使用它。

2.3.7. ByteArrayResource

这是一个给定字节数组的 Resource 实现。它为给定的字节数组创建一个 ByteArrayInputStream。

它对于从任何给定的字节数组中加载内容是很有用的,而不必求助于单一用途的 InputStreamResource。

Environment 接口是一个集成在容器中的抽象,它对 application environment 的两个关键方面进行建模:配置文件(profiles)属性(properties)

profile是一个命名的、逻辑上的bean定义组,只有在给定的profile处于活动状态时才会在容器中注册。无论是用 XML 定义的还是用注解定义的,Bean 都可以被分配给一个profile。Environment 对象在profile方面的作用是确定哪些profile(如果有的话)是当前活动(active)的,以及哪些profile(如果有的话)应该是默认活动的。

属性(Properties)在几乎所有的应用程序中都扮演着重要的角色,它可能来自各种来源:properties 文件、JVM系统属性、系统环境变量、JNDI、Servlet上下文参数、特设的 Properties 对象、Map 对象等等。与属性有关的 Environment 对象的作用是为用户提供一个方便的服务接口,用于配置属性源并从它们那里解析属性。

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

它能干啥

@Import注解在Spring框架中主要用于解决模块化和配置管理方面的技术问题,它可以帮助开发者实现以下几个目标:

  1. 模块化配置:在大型项目中,通常需要将配置信息分散到多个配置类中,以便更好地组织和管理,@Import注解允许开发者在一个主配置类中导入其他配置类,从而实现配置的模块化。
  2. 第三方库或组件的集成:当项目中需要集成第三方库或组件时,这些库或组件可能会提供自己的配置类,通过@Import注解,开发者可以轻松地将这些第三方配置类集成到项目的总体配置中。
  3. 条件化配置:@Import还可以与条件注解(如@Conditional)结合使用,以实现基于特定条件的配置加载,因此在不同的环境或情境下,可以加载不同的配置类,从而实现更加灵活和动态的配置管理。
  4. 扩展Spring功能:通过导入实现了特定接口的类,开发者可以扩展Spring框架的功能,比如,可以导入自定义的BeanFactoryPostProcessor或BeanDefinitionRegistrar来修改或添加bean定义。
  5. 解决循环依赖问题:在某些情况下,使用@Import注解可以解决因循环依赖而导致的配置问题,通过将相互依赖的配置类分解并使用@Import进行导入,可以打破循环依赖的链条。

它有哪些特性

在Spring框架中,@Import注解可以用来引入一个或多个组件,这些组件通常是通过@Bean注解定义的,当使用@Import注解时,实际上是在告诉Spring:“除了当前配置类中的bean定义外,还想包含另一个配置类(或多个配置类)中定义的bean。”

@Import注解可以用来引入:

  1. 带有@Bean方法的配置类:这是最常见的情况,可以在一个配置类中定义bean,并使用@Import将其引入到其他配置类中。
  2. ImportSelector实现:这是一个更高级的特性,允许根据某些条件或运行时环境动态地选择要导入的配置类。
  3. ImportBeanDefinitionRegistrar实现:这是一个更底层的机制,允许在运行时手动注册bean定义。
  4. 使用@Import来组合多个配置类,从而构建复杂的配置层次结构。

使用@Import注解引入一个类

和ImportBeanDefinitionRegistrar接口一起使用

ImportBeanDefinitionRegistrar是一个Spring接口,它允许在运行时以编程方式注册额外的bean定义,当需要在Spring容器刷新过程中动态添加bean定义时,可以实现这个接口,ImportBeanDefinitionRegistrar通常与@Import注解结合使用,以便在Spring容器启动时执行自定义的bean注册逻辑。

下面是一个简单的案例,演示了如何使用ImportBeanDefinitionRegistrar来动态注册bean定义。

首先,创建一个简单的服务类MyDynamicService:

public class MyDynamicService {  
    public void performTask() {  
        System.out.println("MyDynamicService is performing a task.");  
    }  
}

然后,创建一个实现了 ImportBeanDefinitionRegistrar 接口的类 MyBeanDefinitionRegistrar:

import org.springframework.beans.factory.support.BeanDefinitionRegistry;  
import org.springframework.beans.factory.support.GenericBeanDefinition;  
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;  
import org.springframework.core.type.AnnotationMetadata;  
  
public class MyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {  
  
    @Override  
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {  
        // 创建 GenericBeanDefinition 实例  
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();  
        // 设置 bean 类  
        beanDefinition.setBeanClassName(MyDynamicService.class.getName());  
        // 注册 bean 定义到容器中,指定 bean 的名称  
        registry.registerBeanDefinition("myDynamicService", beanDefinition);  
    }  
}

记着,需要一个配置类来触发 MyBeanDefinitionRegistrar 的注册:

import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.Import;  
  
@Configuration  
@Import(MyBeanDefinitionRegistrar.class)  
public class MyAppConfig {  
    // 其他配置...  
}

最后,在应用程序中使用 AnnotationConfigApplicationContext 来加载 MyAppConfig 并获取 MyDynamicService 的实例:

import org.springframework.context.ApplicationContext;  
import org.springframework.context.annotation.AnnotationConfigApplicationContext;  
  
public class Application {  
    public static void main(String[] args) {  
        ApplicationContext context = new AnnotationConfigApplicationContext(MyAppConfig.class);  
        MyDynamicService myDynamicService = context.getBean(MyDynamicService.class);  
        myDynamicService.performTask(); // 输出:"MyDynamicService is performing a task."  
    }  
}

在这个例子中,当Spring容器启动时,它会处理@Import注解并调用MyBeanDefinitionRegistrar的registerBeanDefinitions方法。

这个方法会在容器中动态地注册MyDynamicService的bean定义,随后,可以像获取其他Springbean一样获取并使用MyDynamicService的实例。

和ImportSelector接口一起使用

ImportSelector是Spring框架提供的一个接口,它允许开发者在运行时根据某些条件或逻辑选择要导入的配置类,ImportSelector接口定义了一个方法selectImports,该方法返回一个字符串数组,表示要导入的配置类的全限定名。

以下是一个简单的示例,展示了如何使用ImportSelector来动态选择要导入的配置类:

首先,定义两个简单的配置类ConfigA和ConfigB,每个配置类都有一个Bean定义:

// ConfigA.java  
@Configuration  
public class ConfigA {  
    @Bean  
    public String configABean() {  
        return "Bean from ConfigA";  
    }  
}  

// ConfigB.java  
@Configuration  
public class ConfigB {  
    @Bean  
    public String configBBean() {  
        return "Bean from ConfigB";  
    }  
}

接下来,创建一个实现 ImportSelector 接口的类 MyImportSelector,它根据某个条件(例如系统属性)来决定导入哪个配置类:

// MyImportSelector.java  
import org.springframework.context.annotation.ImportSelector;  
import org.springframework.core.type.AnnotationMetadata;  

import java.util.Arrays;  

public class MyImportSelector implements ImportSelector {  

    @Override  
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {  
        // 根据某个条件决定导入哪个配置类  
        if (System.getProperty("config.selector") != null && "configA".equals(System.getProperty("config.selector"))) {  
            return new String[]{ConfigA.class.getName()};  
        } else {  
            return new String[]{ConfigB.class.getName()};  
        }  
    }  
}

在上面的 MyImportSelector 类中,selectImports 方法检查系统属性 config.selector 的值,并根据该值返回相应的配置类全限定名。

最后,创建一个主配置类 MainConfig,并使用 @Import 注解引入 MyImportSelector:

// MainConfig.java  
import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.Import;  

@Configuration  
@Import(MyImportSelector.class)  
public class MainConfig {  
    // 其他配置...  
}

现在,在应用程序中,可以根据系统属性 config.selector 的值来动态选择要加载的配置类:

// Application.java  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.annotation.AnnotationConfigApplicationContext;  

public class Application {  
    public static void main(String[] args) {  
        // 设置系统属性以决定要导入的配置类  
        System.setProperty("config.selector", "configA"); // 设置为 "configA" 或 "configB"  

        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);  

        // 根据系统属性的设置,以下将打印出不同的结果  
        String bean = context.getBean(String.class);  
        System.out.println(bean);  
    }  
}

运行上述 Application 类的 main 方法,并根据需要设置系统属性 config.selector 的值,将看到根据该值动态加载了不同的配置类中的 Bean。如果设置为 "configA",则输出将是 "Bean from ConfigA";如果设置为其他值或未设置,则输出将是 "Bean from ConfigB"。

使用@Import注解引入多个类

Spring揭秘:ImportBeanDefinitionRegistrar应用场景及实现原理

内容概念

ImportBeanDefinitionRegistrar接口提供了强大的动态注册Bean的能力,它允许开发者在Spring容器初始化时,灵活地根据特定条件或需求来添加或修改Bean定义,从而实现更为精细的控制和扩展性。这是构建可扩展框架、插件系统或处理复杂配置场景的利器。

核心概念

ImportBeanDefinitionRegistrar是Spring框架中一个非常强大的接口,它允许在运行时动态地向Spring容器中注册Bean定义,这特性在一些需要动态扩展、插件化或者编程式配置Spring应用的场景中特别有用。

模拟一个业务案例,假如,有一个电商平台,平台支持多种支付方式,比如支付宝、微信支付、银联支付等,每种支付方式都有自己的配置参数和实现逻辑,而且这些支付方式可能会随着业务的发展不断增加或变更。

传统的做法可能是为每种支付方式编写一个配置类,然后在主配置类中使用@Import注解将这些配置类静态地导入到Spring容器中,但这种方式不够灵活,每次增加新的支付方式时都需要修改主配置类,并且需要重启应用才能生效。

类似这样的场景,就非常适合用ImportBeanDefinitionRegistrar来解决,可以创建一个实现了ImportBeanDefinitionRegistrar接口的类,比如叫做PaymentRegistrar,在这个类中,可以编写逻辑来动态地扫描和识别所有可用的支付方式,并为每种支付方式创建一个对应的Bean定义,然后注册到Spring容器中。

可以在PaymentRegistrar的registerBeanDefinitions方法中编写逻辑,实现思路大概如下:

  1. 扫描指定路径下的支付方式实现类。
  2. 对于每个找到的支付方式实现类,创建一个对应的Bean定义,并设置必要的属性,比如支付URL、密钥等。
  3. 将这些Bean定义注册到传入的BeanDefinitionRegistry中。

最后,在主配置类中使用@Import注解将PaymentRegistrar导入到Spring容器中,这样,当应用启动时,Spring会自动调用PaymentRegistrar的registerBeanDefinitions方法,从而动态地加载和注册所有可用的支付方式。

这种方式,可以在不修改主配置类和重启应用的情况下,灵活地添加、删除或修改支付方式。这对于快速响应业务需求变化和降低维护成本非常有帮助。

核心案例

下面是一个简单的Java例子,演示了如何使用ImportBeanDefinitionRegistrar来动态注册Bean定义,在例子中,创建一个简单的服务接口GreetingService,并提供两个实现类EnglishGreetingService和SpanishGreetingService,然后使用ImportBeanDefinitionRegistrar来动态地注册这些服务,并通过客户端代码来调用它们,如下代码:

首先,定义服务接口和实现类:

// GreetingService.java  
public interface GreetingService {  
    String sayGreeting();  
}  
  
// EnglishGreetingService.java  
public class EnglishGreetingService implements GreetingService {  
    @Override  
    public String sayGreeting() {  
        return "Hello!";  
    }  
}  
  
// SpanishGreetingService.java  
public class SpanishGreetingService implements GreetingService {  
    @Override  
    public String sayGreeting() {  
        return "¡Hola!";  
    }  
}

接下来,创建实现了ImportBeanDefinitionRegistrar接口的类,用于动态注册Bean定义:

// GreetingServiceRegistrar.java  
import org.springframework.beans.factory.support.BeanDefinitionRegistry;  
import org.springframework.beans.factory.support.GenericBeanDefinition;  
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;  
import org.springframework.core.type.AnnotationMetadata;  

public class GreetingServiceRegistrar implements ImportBeanDefinitionRegistrar {  

    @Override  
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {  
        // 动态注册 EnglishGreetingService  
        GenericBeanDefinition englishGreetingServiceDefinition = new GenericBeanDefinition();  
        englishGreetingServiceDefinition.setBeanClassName(EnglishGreetingService.class.getName());  
        registry.registerBeanDefinition("englishGreetingService", englishGreetingServiceDefinition);  

        // 动态注册 SpanishGreetingService  
        GenericBeanDefinition spanishGreetingServiceDefinition = new GenericBeanDefinition();  
        spanishGreetingServiceDefinition.setBeanClassName(SpanishGreetingService.class.getName());  
        registry.registerBeanDefinition("spanishGreetingService", spanishGreetingServiceDefinition);  
    }  
}

现在,需要在Spring配置中使用@Import注解来导入GreetingServiceRegistrar:

// AppConfig.java  
import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.Import;  

@Configuration  
@Import(GreetingServiceRegistrar.class)  
public class AppConfig {  
    // 其他配置...  
}

最后,编写客户端代码来调用动态注册的Bean:

// Application.java  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.annotation.AnnotationConfigApplicationContext;  

public class Application {  

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

        // 获取并调用 EnglishGreetingService  
        GreetingService englishService = context.getBean("englishGreetingService", GreetingService.class);  
        System.out.println(englishService.sayGreeting()); // 输出: Hello!  

        // 获取并调用 SpanishGreetingService  
        GreetingService spanishService = context.getBean("spanishGreetingService", GreetingService.class);  
        System.out.println(spanishService.sayGreeting()); // 输出: ¡Hola!  
    }  
}

在上面的代码中,使用了AnnotationConfigApplicationContext来创建一个Spring应用上下文,并指定了配置类AppConfig,然后,使用context.getBean()方法来获取动态注册的Bean,并调用它们的方法来输出问候语,输出结果应该是分别打印出"Hello!"和"¡Hola!"。

核心API

ImportBeanDefinitionRegistrar 接口允许开发者在运行时动态地向 Spring 应用程序上下文中注册 Bean 定义,这个接口通常与 @Import 注解结合使用,当 Spring 容器扫描到带有 @Import 注解的类时,会调用实现了 ImportBeanDefinitionRegistrar 接口的类的相关方法,ImportBeanDefinitionRegistrar 接口中只有一个方法,它的核心方法以及含义如下:registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry),参数:

1、importingClassMetadata:提供有关正在导入的类的元数据信息,如类名、注解等。

2、registry:用于注册 Bean 定义的 BeanDefinitionRegistry,可以使用这个注册中心来添加、移除或修改 Bean 定义。

该方法的用途:

  1. 该方法是 ImportBeanDefinitionRegistrar 接口的核心,它允许开发者在 Spring 容器初始化过程中动态地添加或修改 Bean 定义。
  2. 通过 BeanDefinitionRegistry,可以创建新的 BeanDefinition 对象,并使用 registerBeanDefinition 方法将其注册到容器中。
  3. 还可以使用其他 BeanDefinitionRegistry 的方法来修改已存在的 Bean 定义或查询容器中的 Bean 定义。

技术原理

ImportBeanDefinitionRegistrar接口的实现类会在Spring容器解析到带有@Import注解的配置类时被调用,从而允许开发者在容器初始化过程中动态地添加、修改或删除Bean定义。

实现原理

  1. @Import注解的解析:当Spring容器解析到带有@Import注解的类时,它会查看该注解所引用的类,如果这些类实现了ImportBeanDefinitionRegistrar接口,Spring容器就会创建这些类的实例,并调用它们的registerBeanDefinitions方法。
  2. registerBeanDefinitions方法的调用:registerBeanDefinitions方法接收两个参数:一个是AnnotationMetadata,它包含了关于正在被处理的注解类的元数据(如类名、方法、其他注解等);另一个是BeanDefinitionRegistry,它是一个允许操作容器中Bean定义的注册表。
  3. 动态注册Bean定义:在registerBeanDefinitions方法内部,开发者可以编写自定义逻辑来创建BeanDefinition对象(这些对象描述了如何创建Bean实例),并使用BeanDefinitionRegistry将它们注册到Spring容器中,注册过程可以基于传入的AnnotationMetadata来做出决策。

工作流程

  1. 扫描和解析注解:Spring容器在启动时会扫描指定的包路径,查找并解析带有特定注解(如@Component, @Service, @Repository, @Controller, @Configuration等)的类,当遇到@Import注解时,它会特别处理。
  2. 处理@Import注解:对于每个@Import注解,Spring会查看其值(即要导入的类),并检查这些类是否实现了ImportBeanDefinitionRegistrar接口,如果实现了,就会实例化这些类,并准备调用它们的registerBeanDefinitions方法。
  3. 执行自定义注册逻辑:对于每个实现了ImportBeanDefinitionRegistrar的类,Spring会调用其registerBeanDefinitions方法,在这个方法中,开发者可以编写任意逻辑来创建和注册Bean定义,这通常涉及到创建BeanDefinition对象(如GenericBeanDefinition),设置其属性(如bean类名、作用域、依赖等),然后使用BeanDefinitionRegistry的registerBeanDefinition方法将其注册到容器中。
  4. 完成容器初始化:在调用了所有ImportBeanDefinitionRegistrar实现类的registerBeanDefinitions方法后,Spring容器会继续其初始化过程,包括创建和初始化所有已注册的Bean实例。

核心总结

优点在于灵活性高,允许开发者在Spring容器初始化时,根据特定条件或逻辑动态地添加、修改Bean定义,实现更细粒度的控制,对于编写框架代码或需要动态扩展功能的应用来说非常有用。

但是,由于是在运行时动态注册Bean,可能会增加容器的启动时间和复杂性,推荐,在确实需要动态注册Bean的场景下使用,如插件系统、动态数据源等。

Spring揭秘:Environment接口应用场景及实现原理!

内容概要

Environment接口提供了强大且灵活的环境属性管理能力,通过它,开发者能轻松地访问和配置应用程序运行时的各种属性,如系统属性、环境变量等。

同时,Environment接口还支持属性源的定制和扩展,使得开发者能根据实际需求灵活地定制属性的加载和解析方式。

核心概念

它能解决了什么问题?

Environment接口在Spring框架主要负责管理和提供应用程序运行时的环境信息,这些信息包括但不限于系统属性、环境变量、命令行参数、配置文件属性等。

以下是Environment接口主要用来解决的一些技术问题:

  1. 属性管理:Environment提供了访问应用程序属性的集中机制,可以使用它来读取、设置和管理这些属性,这些属性可以来自不同的源,如系统属性、环境变量、配置文件(如properties或yml文件)等。
  2. 配置灵活性:通过使用Environment,开发者可以在运行时动态地改变或覆盖配置,这增加了配置的灵活性和可定制性,对于需要根据不同环境(如开发、测试、生产等)进行不同配置的应用程序来说尤为重要。
  3. 简化配置访问:在Spring应用程序中,可能需要访问各种配置值,Environment简化了这一过程,通过提供统一且易于使用的API,可以轻松地从代码中访问这些配置值。
  4. 配置源整合:Spring的Environment能够将来自不同源的配置信息整合到一起,提供了一个统一的视图来访问这些配置,这意味着,无论配置信息是存储在系统属性、环境变量还是配置文件中,都可以使用相同的方式来访问它们。
  5. 属性占位符解析:Environment还支持属性占位符的解析,这允许在配置中使用占位符来引用其他属性的值,这对于创建基于其他属性值的配置特别有用。
  6. 支持Spring表达式语言:在一些高级用例中,可能需要使用Spring表达式语言(SpEL)来访问或操作配置数据。Environment与SpEL的集成使得这种操作变得更加简单和直接。

如上图所示,Environment接口有多个实现类,这些实现类根据用途和配置源的不同而有所区分,以下是Environment接口的一些主要实现类:

Enviroment接口使用示例:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.Environment;

public class EnvironmentExample {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.refresh();

        Environment env = context.getEnvironment();

        // 获取属性值
        String myProp = env.getProperty("my.property");
        String defaultProp = env.getProperty("non.existent.property", "default");

        System.out.println("my.property: " + myProp);
        System.out.println("non.existent.property: " + defaultProp);

        // 获取激活的配置文件
        String[] activeProfiles = env.getActiveProfiles();
        System.out.println("Active profiles: " + String.join(", ", activeProfiles));

        context.close();
    }
}

  1. StandardEnvironment:这是Environment接口的最基本实现,用于非Web应用程序,它主要管理JVM级别的系统属性和环境变量,通常,在创建独立的Java应用程序或命令行工具时会使用StandardEnvironment。

chatgpt扩展:StandardEnvironment 是 Spring 框架中一个具体实现类,继承自 AbstractEnvironment 并实现了 ConfigurableEnvironment 接口。它主要用于表示标准的应用程序运行环境,包括 JVM 系统属性和操作系统环境变量。

1. 属性源管理

StandardEnvironment 默认包含两个重要的属性源:

  • 系统环境变量(System Environment Variables):环境变量通过 System.getenv() 方法获取。
  • JVM 系统属性(JVM System Properties):系统属性通过 System.getProperties() 方法获取。
  • 系统环境变量(System Environment Variables):环境变量通过 System.getenv() 方法获取。

JVM 系统属性(JVM System Properties):系统属性通过 System.getProperties() 方法获取。

这些属性源按照一定的顺序进行配置,确保在解析属性值时能够按照优先级进行查找。

2. 配置文件支持

StandardEnvironment 支持配置文件(profiles)的概念,可以根据当前激活的配置文件来决定哪些 bean 定义会被注册到 Spring 容器中。通过 getActiveProfiles() 和 setActiveProfiles() 方法,可以分别获取和设置当前激活的配置文件。

3. 属性解析

StandardEnvironment 实现了 PropertyResolver 接口,提供了多种方法来解析属性值,包括获取属性值、解析占位符和设置默认值等。例如:

getProperty(String key)

getProperty(String key, String defaultValue)

getProperty(String key, Class<T> targetType)

resolvePlaceholders(String text)

import org.springframework.core.env.StandardEnvironment;

public class StandardEnvironmentExample {

    public static void main(String[] args) {
        StandardEnvironment environment = new StandardEnvironment();

        // 获取系统属性
        String javaVersion = environment.getProperty("java.version");
        System.out.println("Java Version: " + javaVersion);

        // 获取环境变量
        String path = environment.getProperty("PATH");
        System.out.println("PATH: " + path);

        // 设置和获取激活的配置文件
        environment.setActiveProfiles("development");
        String[] activeProfiles = environment.getActiveProfiles();
        System.out.println("Active Profiles: " + String.join(", ", activeProfiles));

        // 解析占位符
        String resolvedText = environment.resolvePlaceholders("Java version: ${java.version}");
        System.out.println(resolvedText);
    }
}
  1. StandardServletEnvironment:这是用于基于Servlet的Web应用程序的Environment实现,除了系统属性和环境变量外,它还考虑了Servlet规范定义的配置参数,如Servlet上下文初始化参数和JNDI属性,在Spring MVC应用程序中,通常会使用StandardServletEnvironment。
  2. ConfigurableEnvironment:这是一个可以配置和修改的Environment实现,它通常用作其他更具体环境实现的基类,如StandardEnvironment和AbstractEnvironment。它允许添加、删除或修改属性源(PropertySource)。
  3. ConfigurableWebEnvironment:提供了一个专门用于Web应用程序的环境接口,它扩展了ConfigurableEnvironment接口,这个接口的主要作用是为Web应用程序提供配置环境的能力,同时添加了一些与Web环境相关的特性。

个人思考

ConfigurableEnvironment和ConfigurableWebEnvironment有什么区别?

他们都是Spring框架中用于配置和管理应用程序运行环境的接口,然而,它们之间存在一些关键的区别,主要在于它们的应用场景和提供的功能:

1、应用场景

  • ConfigurableEnvironment:这是Spring框架中用于配置和管理通用应用程序环境的接口,它可以用于任何类型的应用程序,无论是Web应用程序还是非Web应用程序。
  • ConfigurableWebEnvironment:这个接口是专门为Web应用程序设计的,用于配置和管理Web应用程序的运行环境,它扩展了ConfigurableEnvironment接口,添加了一些与Web环境相关的特性。然而,需要注意的是,在较新版本的Spring框架中(如Spring 5及以后),ConfigurableWebEnvironment接口实际上已经被废弃,而推荐使用ConfigurableEnvironment来处理Web和非Web环境的配置,但在旧版本的Spring中,这个接口仍然存在并用于区分Web和非Web环境。

2、提供的功能

  • ConfigurableEnvironment:这个接口提供了一组方法来配置和管理应用程序的环境属性,可以通过它添加、删除或修改属性源,并定制属性的检索顺序,此外,它还提供了访问和修改系统属性、环境变量等功能。
  • ConfigurableWebEnvironment:除了继承自ConfigurableEnvironment的所有功能外,这个接口还提供了对Servlet上下文初始化参数的访问,这是Web应用程序特有的功能,允许在Web应用程序的运行环境中访问和修改Servlet上下文的初始化参数,由于该接口在较新版本的Spring中已被废弃,因此在实际开发中可能不会直接使用它

代码案例

下面代码演示了几个使用频率较高的Environment代码案例。

StandardEnvironment案例

下面是一个简单的Java代码示例,演示了如何使用org.springframework.core.env.StandardEnvironment类。

这个示例创建了一个StandardEnvironment实例,并通过它访问了系统属性和环境变量。

import org.springframework.core.env.StandardEnvironment;  

public class StandardEnvironmentExample {  

    public static void main(String[] args) {  
        // 创建一个StandardEnvironment实例  
        StandardEnvironment environment = new StandardEnvironment();  

        // 获取系统属性"java.version"  
        String javaVersion = environment.getSystemProperties().getProperty("java.version");  
        System.out.println("Java Version: " + javaVersion); // 输出Java版本信息  

        // 获取环境变量"PATH"(注意:环境变量的名称可能因操作系统而异)  
        String pathVariable = environment.getSystemEnvironment().get("PATH");  
        // 在Windows上,PATH环境变量的分隔符是";",在Unix/Linux上是":"  
        String pathSeparator = System.getProperty("path.separator");  
        if (pathVariable != null) {  
            System.out.println("PATH Environment Variable (first entry): " + pathVariable.split(pathSeparator)[0]);  
            // 输出PATH环境变量的第一个条目(根据路径分隔符分割后)  
        } else {  
            System.out.println("PATH Environment Variable is not set.");  
        }  

        // 尝试获取一个不存在的系统属性或环境变量将返回null  
        String nonExistentProperty = environment.getSystemProperties().getProperty("non.existent.property");  
        System.out.println("Non-Existent Property: " + nonExistentProperty); // 输出null或未定义属性的信息  
    }  
}

在这段代码中,创建了一个StandardEnvironment的实例,然后,使用getSystemProperties()方法获取系统属性,并通过getProperty()方法获取特定的属性(在这个例子中是"java.version")。

类似地,使用getSystemEnvironment()方法获取环境变量,并通过get()方法获取特定的环境变量(在这个例子中是"PATH")。请注意,环境变量的名称和可用性可能因操作系统而异。

注意,如果是非Web应用程序则推荐使用StandardEnvironment,如果是Web应用程序,则推荐使用StandardServletEnvironment

ConfigurableEnvironment案例

下面是一个使用org.springframework.core.env.ConfigurableEnvironment类的Java代码示例。

在这个示例中,创建一个ConfigurableEnvironment实例,并向其添加自定义的属性源。=,然后,将演示如何从环境中检索这些属性。

import org.springframework.core.env.ConfigurableEnvironment;  
import org.springframework.core.env.MapPropertySource;  
import org.springframework.core.env.Environment;  

import java.util.HashMap;  
import java.util.Map;  

public class ConfigurableEnvironmentExample {  

    public static void main(String[] args) {  
        // 创建一个ConfigurableEnvironment实例  
        ConfigurableEnvironment environment = new ConfigurableEnvironment();  

        // 创建一个包含自定义属性的Map  
        Map<String, Object> customProperties = new HashMap<>();  
        customProperties.put("custom.property.one", "Value One");  
        customProperties.put("custom.property.two", "Value Two");  

        // 创建一个属性源,并命名为"customProperties"  
        MapPropertySource customPropertySource = new MapPropertySource("customProperties", customProperties);  

        // 将自定义属性源添加到环境中  
        environment.getPropertySources().addLast(customPropertySource);  

        // 从环境中检索自定义属性并打印它们  
        String propertyOne = environment.getProperty("custom.property.one");  
        String propertyTwo = environment.getProperty("custom.property.two");  

        System.out.println("Custom Property One: " + propertyOne); // 输出: Custom Property One: Value One  
        System.out.println("Custom Property Two: " + propertyTwo); // 输出: Custom Property Two: Value Two  

        // 尝试获取一个不存在的属性将返回null  
        String nonExistentProperty = environment.getProperty("non.existent.property");  
        System.out.println("Non-Existent Property: " + nonExistentProperty); // 输出: Non-Existent Property: null  
    }  
}

在这段代码中,创建了一个ConfigurableEnvironment实例,然后,创建了一个包含自定义属性的Map,并使用这个Map创建了一个MapPropertySource实例,将这个自定义属性源命名为"customProperties",并将其添加到环境的属性源列表的末尾。

接着,使用environment.getProperty()方法从环境中检索自定义属性,并将它们打印到控制台。最后,尝试获取一个不存在的属性,以演示当属性不存在时返回null的情况。

Spring揭秘:AnnotationMetadata接口应用场景及实现原理!

内容概要

AnnotationMetadata接口可以轻松获取类、方法或字段上的注解信息,简化注解处理,提供一致且灵活的访问方式,支持运行时处理,让开发者能更专注于业务逻辑而非底层细节,从而加速开发进程。

核心应用场景

AnnotationMetadata接口提供对Java类注解元数据的访问,例如类、方法或字段上的注解及其属性值。

AnnotationMetadata接口主要用来解决以下几个场景技术问题:

  1. 注解的读取与解析:通过AnnotationMetadata接口,Spring能够读取并解析Java类上的注解,从而了解类的结构、行为及其依赖关系,这对于实现诸如依赖注入、组件扫描、自动配置等Spring核心功能至关重要。
  2. 类层次的注解继承:在某些情况下,一个类可能继承了其父类或其他接口的注解,AnnotationMetadata接口能够处理这种类层次的注解继承,使得子类能够访问并继承父类或接口的注解信息。
  3. 注解的合并与覆盖:当多个注解应用于同一个Java元素(如类、方法或字段)时,可能需要合并或覆盖这些注解的属性值,AnnotationMetadata接口及其实现类能够处理这种合并与覆盖逻辑,确保注解信息的正确性。
  4. 支持多种类型的注解元数据:Spring框架支持多种类型的注解元数据,包括标准Java注解、Spring自定义注解等。AnnotationMetadata接口作为一个通用的注解元数据访问接口,能够屏蔽这些底层差异,为上层应用提供一个统一的注解访问方式。
  5. 与其他Spring组件的集成:AnnotationMetadata接口与其他Spring组件(如BeanDefinition、AutowiredAnnotationBeanPostProcessor等)紧密集成,共同实现了Spring的依赖注入、自动装配、AOP等核心功能。

注意:AnnotationMetadata接口本身并不直接处理注解的解析和应用逻辑,通常由Spring框架内部的其他组件(如注解解析器、代理生成器等)来完成。AnnotationMetadata接口主要提供了一个访问注解元数据的通用接口,使得这些组件能够更方便地获取和处理注解信息。

核心子类实现

AnnotationMetadata接口有多个实现类,这些实现类根据所处理的注解元数据的来源和类型而有所不同。

以下是AnnotationMetadata接口的一些主要实现类:

  1. StandardAnnotationMetadata:这是AnnotationMetadata接口的一个标准实现,它通常用于表示直接从Java类文件中读取的注解元数据,这个实现类提供了对Java类、方法、字段等上的标准Java注解的访问。
  2. ScannedGenericBeanDefinition虽然ScannedGenericBeanDefinition本身不是AnnotationMetadata的直接实现类,但它内部持有一个AnnotationMetadata实例(通常是StandardAnnotationMetadata),用于表示通过组件扫描找到的带有注解的bean定义。在Spring的上下文初始化过程中,当扫描到带有如@Component、@Service、@Repository或@Controller等注解的类时,会使用这个实现来创建bean定义。
  3. AnnotationMetadataReadingVisitor:这不是AnnotationMetadata接口的直接实现,而是一个用于读取注解元数据的访问者(Visitor)类,它通常与ASM(一个Java字节码操作库)结合使用,以在不加载类的情况下读取类的注解信息。
  4. MergedAnnotationMetadata:用于表示合并后的注解元数据,当处理重复注解或元注解时,这个实现类特别有用,它允许合并来自多个源的注解属性,例如类上的注解和方法上的注解。
  5. SyntheticMergedAnnotationMetadata:这是MergedAnnotationMetadata的一个特殊实现,用于表示合成的注解元数据,合成的注解元数据通常是在运行时根据一定的逻辑动态创建的,而不是直接从源代码或字节码中读取的。

代码案例

下面是一个示例代码,演示了如何创建一个 StandardAnnotationMetadata 实例,并如何使用它来获取注解的属性值。

如上面所说AnnotationMetadata的子类实现有多个,这里只用StandardAnnotationMetadata作为演示,如下代码:

先定义一个简单的注解 MyCustomAnnotation:

import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public @interface MyCustomAnnotation {  
    String value() default "";  
    int number() default 0;  
}

然后,创建一个带有这个注解的类 MyClass:

@MyCustomAnnotation(value = "Hello, World!", number = 42)  
public class MyClass {  
    // 类的实现...  
}

接下来,编写一个客户端程序,使用 StandardAnnotationMetadata 来获取 MyClass 上的注解信息:接下来,编写一个客户端程序,使用 StandardAnnotationMetadata 来获取 MyClass 上的注解信息:

import org.springframework.core.type.AnnotationMetadata;  
import org.springframework.core.type.StandardAnnotationMetadata;  

public class AnnotationMetadataExample {  

    public static void main(String[] args) {  
        // 创建 MyClass 的 Class 对象  
        Class<?> myClass = MyClass.class;  

        // 创建 StandardAnnotationMetadata 的实例  
        // 这里使用 MyClass.class 来构造 StandardAnnotationMetadata  
        AnnotationMetadata metadata = new StandardAnnotationMetadata(myClass);  

        // 判断 MyClass 是否有 MyCustomAnnotation 注解  
        if (metadata.hasAnnotation(MyCustomAnnotation.class.getName())) {  
            // 获取 MyCustomAnnotation 注解的属性  
            String value = metadata.getAnnotationAttributes(MyCustomAnnotation.class.getName()).get("value").toString();  
            int number = (int) metadata.getAnnotationAttributes(MyCustomAnnotation.class.getName()).get("number");  

            // 输出注解的属性值  
            System.out.println("Annotation value: " + value);  
            System.out.println("Annotation number: " + number);  
        } else {  
            System.out.println("MyClass does not have MyCustomAnnotation.");  
        }  
    }  
}

在这个例子中,先创建了 MyClass 的 Class 对象,然后,用这个对象构造了一个 StandardAnnotationMetadata 的实例,接着,检查 MyClass 是否带有 MyCustomAnnotation 注解,如果有,就获取注解的属性值并打印出来,运行代码会有如下类似输出:

Annotation value: Hello, World!  
Annotation number: 42

技术原理

实现原理

当Spring应用程序启动时,它会扫描指定的包路径以查找带有特定注解的类(例如@Component、@Service、@Repository、@Controller等)。

这个扫描过程是由ClassPathBeanDefinitionScanner类完成的,它会读取类路径下的类文件,并使用MetadataReader接口的实现(如SimpleMetadataReader)来获取类的元数据。

chatgpt补充:

MetadataReader 使用示例:

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.AnnotationMetadata;

public class AnnotationMetadataExample {

    public static void main(String[] args) throws Exception {
        MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
        MetadataReader reader = readerFactory.getMetadataReader(new ClassPathResource("com/example/MyClass.class"));

        AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();

        // 检查是否存在某个注解
        boolean hasMyAnnotation = annotationMetadata.hasAnnotation("com.example.MyAnnotation");
        System.out.println("Has @MyAnnotation: " + hasMyAnnotation);

        // 获取注解属性
        if (hasMyAnnotation) {
            Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes("com.example.MyAnnotation");
            attributes.forEach((key, value) -> System.out.println(key + ": " + value));
        }
    }
}

MetadataReader接口提供了对类的元数据的访问,包括类名、父类名、接口名以及注解信息。AnnotationMetadata接口的实现类(如StandardAnnotationMetadata)通常作为MetadataReader的一部分提供。

public interface MetadataReader {

	/**
	 * Return the resource reference for the class file.
	 */
	Resource getResource();

	/**
	 * Read basic class metadata for the underlying class.
	 */
	ClassMetadata getClassMetadata();

	/**
	 * Read full annotation metadata for the underlying class,
	 * including metadata for annotated methods.
	 */
	AnnotationMetadata getAnnotationMetadata();

}

StandardAnnotationMetadata类封装了关于注解的详细信息,包括注解的属性值。它使用ASM库(一个Java字节码框架)来读取类文件中的注解信息,而不需要加载类本身,这种方式使Spring能够在不实例化类的情况下获取注解信息,从而提高启动速度。

AnnotationMetadata的运行机制主要涉及以下几个步骤:

  1. 扫描类路径:Spring使用ClassPathBeanDefinitionScanner扫描指定的包路径以查找类文件。
  2. 读取类文件:使用ASM库或其他类似机制读取类文件的字节码。
  3. 解析注解:从类文件的字节码中解析出注解信息。这包括注解的类型、属性及其值。
  4. 创建元数据对象:根据解析出的注解信息创建AnnotationMetadata对象(如StandardAnnotationMetadata),这些对象存储在内存中,供后续处理使用。
  5. 处理元数据:Spring使用这些注解元数据来决定如何创建和配置bean实例,例如,它可能会根据注解的属性值自动装配依赖项或应用特定的配置。

核心API

AnnotationMetadata接口表示注解的元数据,它提供了访问类、方法或字段上注解的信息,它包含多个方法,用于检索注解的属性、判断是否存在特定的注解等。

以下是AnnotationMetadata接口中一些主要方法的含义:

  1. getClassName(): 返回被注解的类的全限定名。
  2. isInterface(): 判断被注解的类是否是一个接口。
  3. isAnnotation(): 判断被注解的类是否是一个注解类型。
  4. hasAnnotation(String annotationType): 判断被注解的类、方法或字段上是否存在指定类型的注解,参数annotationType是注解的全限定名。
  5. getAnnotationAttributes(String annotationType): 获取指定类型注解的属性值,如果注解存在,则返回一个包含属性名称和属性值的映射;如果注解不存在,则返回null。参数annotationType是注解的全限定名。
  6. getAnnotationAttributes(String annotationType, boolean classValuesAsString): 与getAnnotationAttributes(String annotationType)类似,但提供了一个额外的参数classValuesAsString,用于控制当注解属性的值是Class类型时,是否将其转换为字符串表示形式。如果classValuesAsString为true,则Class类型的属性值将被转换为字符串;如果为false,则保持为Class对象。
  7. getMetaAnnotationTypes(String annotationType): 获取指定类型注解上的元注解类型,元注解是注解其他注解的注解,参数annotationType是注解的全限定名,返回一个包含元注解类型的集合。
  8. getAllAnnotationAttributes(String annotationType): 获取指定类型注解及其所有元注解的属性值,返回一个多层嵌套的映射结构,外层映射的键是注解类型,内层映射的键是属性名称,值是属性值,参数annotationType是注解的全限定名。
  9. getMemberClassNames(): 返回被注解的类中的成员类的名称列表,这些成员类可以是内部类、嵌套类等。

AnnotationMetadata接口为开发者提供了一种标准化、简洁的方式来访问Java注解信息。

其优点主要体现在:一是灵活性高,可以轻松获取类、方法或字段等不同粒度的注解数据;二是简化了注解处理流程,使得开发者能够更专注于业务逻辑的实现;三是与Spring框架紧密集成,为构建强大的企业级应用提供了有力支持。

Spring揭秘:ClassPathScanningProvider接口应用场景及实现原理

技术应用场景

ClassPathScanningCandidateComponentProvider是Spring框架中一个非常核心的类,它主要用于在类路径下扫描并发现带有特定注解的组件,支持诸如@ComponentScan、@Component、@Service、@Repository和@Controller等注解的自动扫描和注册。

ClassPathScanningCandidateComponentProvider 解决了以下几个技术问题:

  1. 组件自动发现:在Spring应用程序中,会有大量的组件(如服务、控制器、存储库等),这些组件通常使用Spring的注解进行标记,手动配置这些组件可能会非常繁琐且容易出错,使用ClassPathScanningCandidateComponentProvider,Spring可以自动扫描类路径,发现并注册这些组件,从而大大简化了配置过程。
  2. 可扩展性:这个类提供了高度的可扩展性,可以通过覆盖其方法或提供自定义的过滤器来定制扫描过程,例如,可以指定只扫描特定包下的组件,或者只扫描带有特定注解的组件。
  3. 与Spring容器集成:ClassPathScanningCandidateComponentProvider与Spring的ApplicationContext容器紧密集成,使用它发现的组件可以直接注册到容器中,使得这些组件能够在应用程序的其他部分中被自动装配和使用。
  4. 支持多种注解类型:这个类不仅支持Spring自带的注解(如@Component、@Service等),还支持自定义注解,因此可以创建自己的注解,并使用ClassPathScanningCandidateComponentProvider在类路径中扫描带有这些注解的组件。

伪代码案例

下面是一个简单的示例,演示了如何使用 ClassPathScanningCandidateComponentProvider 类来扫描指定包路径下带有特定注解的类。

在这个例子中,使用带有 @Component 注解的类进行测试,如下代码。

首先,创建一些带有 @Component 注解的组件类作为扫描的目标。

// MyComponent1.java  
package com.example.components;  
  
import org.springframework.stereotype.Component;  
  
@Component  
public class MyComponent1 {  
    // ...  
}  
  
// MyComponent2.java  
package com.example.components;  
  
import org.springframework.stereotype.Component;  
  
@Component  
public class MyComponent2 {  
    // ...  
}

然后,编写一个客户端类,该类使用 ClassPathScanningCandidateComponentProvider 来扫描这些组件。

// ComponentScannerClient.java  
package com.example;  

import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;  
import org.springframework.core.type.filter.AnnotationTypeFilter;  
import org.springframework.stereotype.Component;  

import java.io.IOException;  
import java.util.Set;  

public class ComponentScannerClient {  

    public static void main(String[] args) {  
        // 创建一个 ClassPathScanningCandidateComponentProvider 实例  
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);  

        // 添加一个过滤器,只包含带有 @Component 注解的类  
        scanner.addIncludeFilter(new AnnotationTypeFilter(Component.class));  

        // 指定要扫描的包路径  
        String basePackage = "com.example.components";  

        // 执行扫描并获取候选组件  
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);  

        // 输出扫描结果  
        for (BeanDefinition beanDefinition : candidateComponents) {  
            System.out.println("Found component: " + beanDefinition.getBeanClassName());  
        }  
    }  
}

运行这个示例,控制台会输出类似下面的内容:

Found component: com.example.components.MyComponent1  
Found component: com.example.components.MyComponent2

核心API

ClassPathScanningCandidateComponentProvider 类提供了一系列的方法,用于配置扫描过程、执行扫描以及处理扫描结果。以下是该类中一些主要方法的含义:

  1. ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters)
  • 构造函数,用于创建一个新的 ClassPathScanningCandidateComponentProvider 实例。
  • 参数 useDefaultFilters 指定是否应用默认的过滤器。如果为 true,则会自动包含对 @Component、@Repository、@Service 和 @Controller 的支持。
  • addIncludeFilter(TypeFilter includeFilter)
  • 添加一个包含过滤器,用于指定哪些类型的组件应该被包含在扫描结果中。
  • TypeFilter 是一个接口,可以通过实现该接口来定义自定义的过滤逻辑。
  • addExcludeFilter(TypeFilter excludeFilter)
  • 添加一个排除过滤器,用于指定哪些类型的组件应该被排除在扫描结果之外。
  • 同样,TypeFilter 可以用于定义自定义的排除逻辑。
  • findCandidateComponents(String basePackage)
  • 执行扫描操作,查找指定基础包及其子包下的候选组件。
  • 返回的是一个 Set<BeanDefinition>。
  • isCandidateComponent(MetadataReader metadataReader)
  • 判断给定的 MetadataReader 是否是一个候选组件。
  • 这个方法通常用于内部逻辑,但也可以被覆盖以实现自定义的候选组件判断逻辑。
  • resetFilters(boolean includeDefaultFilters)
  • 重置之前添加的所有过滤器,并可以选择是否包含默认过滤器。
  • 这允许重用同一个 ClassPathScanningCandidateComponentProvider 实例进行多次不同的扫描操作。
  • setEnvironment(Environment environment)
  • 设置用于解析属性占位符的 Environment 实例。
  • 这允许在扫描过程中使用 Spring 的环境抽象来解析例如占位符配置的值。
  • setResourceLoader(ResourceLoader resourceLoader)
  • 设置用于加载资源的 ResourceLoader 实例。
  • 这允许在扫描过程中访问和加载类路径资源。
  • setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory)
  • 设置用于创建 MetadataReader 实例的工厂。
  • 这允许自定义如何读取类的元数据。
  • registerDefaultFilters()
  • 注册默认的过滤器。这个方法通常在构造函数中被调用,但也可以被覆盖以实现自定义的默认过滤器注册逻辑。

注意:这里列出的是一些核心方法,可能在不同的Spring版本中方法的数量会不一样,但是总体上差距不会非常大。

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值