Annotations——@Import注解

本文深入解析Spring框架中@Import注解的功能与用途,包括声明Bean、导入配置类、选择器及动态注册BeanDefinition,展示如何利用@Import增强Spring应用的配置灵活性。

@Import注解的作用

Spring 3.0之前,我们的Bean可以通过xml配置文件与扫描特定包下面的类来将类注入到Spring IOC容器内。Spring 3.0之后提供了JavaConfig的方式,也就是将IOC容器里Bean的元信息以java代码的方式进行描述。我们可以通过@Configuration与@Bean这两个注解配合使用来将原来配置在xml文件里的bean通过java代码的方式进行描述。@Import注解提供了@Bean注解的功能,同时还有xml配置文件里标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的@Configuration。
其源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
	 * or regular component classes to import.
	 */
	Class<?>[] value();
}

通过分析注释我们可以看出,@Import注解有四个作用:
分析类注释得出结论:

  • 声明一个bean
  • 导入@Configuration注解的配置类
  • 导入ImportSelector的实现类
  • 导入ImportBeanDefinitionRegistrar的实现类

声明Bean和导入@Configuration

导入普通的Java类并将其声明为一个Bean注入到Spring容器中的功能在Sprin4.2之后才可以使用,在 spring 4.2 以前,该注解,只能导入配置类,其功能与 标签类似。

@Configuration
@Import(value = FirstBean.class)
public class MyConfig {
    
}
class FirstBean {

}
public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        String[] beanNames = context.getBeanDefinitionNames();
        for(int i=0;i<beanNames.length;i++){
            System.out.println("bean名称为:"+beanNames[i]);
        }
    }
class FirstBean {}

我们分别尝试用@Import和不用@Import引入FirstBean,控制台得到输出:
bean名称为:org.springframework.context.annotation.internalConfigurationAnnotationProcessor
bean名称为:org.springframework.context.annotation.internalAutowiredAnnotationProcessor
bean名称为:org.springframework.context.annotation.internalCommonAnnotationProcessor
bean名称为:org.springframework.context.event.internalEventListenerProcessor
bean名称为:org.springframework.context.event.internalEventListenerFactory
bean名称为:myConfig
bean名称为:com.example.demo.importTest.FirstBean
就是多出了FirstBean这个Bean,也证明了@Import这个注解确实可以注入Bean。

导入ImportSelector的实现类

public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);
}

ImportSelector接口注释说了几点内容:

  1. 这个接口是用来搜集需要导入配置类的
  2. 如果该接口的实现类同时实现EnvironmentAware, BeanFactoryAware ,BeanClassLoaderAware或者ResourceLoaderAware,那么在调用其selectImports方法之前先调用上述接口中对应的方法
  3. 如果需要在所有的@Configuration处理完在导入时可以实现DeferredImportSelector接口

为了验证我们上面分析的,我们看一下SpringBoot中对@Import的使用:@EnableAutoConfiguration这个注解是开启自动配置的注解,该注解上使用了@Import(AutoConfigurationImportSelector.class),我们简单看一下AutoConfigurationImportSelector这个类:

public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered {
		}

AutoConfigurationImportSelector正是实现了上述的几个接口。

导入ImportBeanDefinitionRegistrar的实现类

ImportBeanDefinitionRegistrar是spring对外提供动态注册beanDefinition的接口,spring内部大部分套路也是用该接口进行动态注册beanDefinition的。关于其的用法,SpringBoot的@AutoConfigurationPackage注解就是一个典型例子:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}
/ **
* 我们可以看到Registrar正是实现了ImportBeanDefinitionRegistrar接口
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

   	@Override
   	public void registerBeanDefinitions(AnnotationMetadata metadata,
   			BeanDefinitionRegistry registry) {
   		register(registry, new PackageImport(metadata).getPackageName());
   	}

   	@Override
   	public Set<Object> determineImports(AnnotationMetadata metadata) {
   		return Collections.singleton(new PackageImport(metadata));
   	}
   }

我们再看register方法的实现:

// 我在这个方法内打了断点,发现packageNames正是被@SpringBootApplication注解修饰的启动类所在的包
// registry是Spring的Bean管理器
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		// 判断是否有org.springframework.boot.autoconfigure.AutoConfigurationPackages这个Bean来决定是新增一个bean还是对现有bean进行更新
		if (registry.containsBeanDefinition(BEAN)) {
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition
					.getConstructorArgumentValues();
			constructorArguments.addIndexedArgumentValue(0,
					addBasePackages(constructorArguments, packageNames));
		}
		else {
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
					packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			// 注册一个新的bean
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

通过上面的例子,给我们提供了一个向Spring容器中注入Bean的思路。

### 使用 `@LogInfo` 注解 在 Java 中,`@LogInfo` 并不是一个标准库自带的注解;然而,基于提供的背景信息以及常见的日志记录实践,可以推断这是一个自定义的日志记录注解。为了创建并使用这样的注解来简化日志记录过程,通常会涉及以下几个方面: #### 定义 `@LogInfo` 注解 首先,需要定义一个新的注解类 `LogInfo` 来标记目标方法或字段,并指定其元数据属性。 ```java import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义日志记录注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LogInfo { String value() default ""; } ``` 此段代码展示了如何声明一个名为 `LogInfo` 的注解,它可以在方法上应用,并且会在运行时被保留以便反射机制能够访问到这些注解的信息[^1]。 #### 实现 AOP 或者代理模式自动注入日志逻辑 为了让带有 `@LogInfo` 注解的方法能够在执行前后打印相应的日志信息,可以通过面向切面编程 (AOP) 技术或者动态代理的方式,在不修改原有业务代码的情况下增强功能。这里以 Spring AOP 为例说明如何实现这一点。 ##### 添加依赖项至 Gradle 文件 确保项目的构建文件中包含了必要的依赖关系,比如 AspectJ 和 Lombok(如果打算继续使用的话),这样可以帮助减少样板代码的数量。 ```groovy dependencies { implementation 'org.springframework.boot:spring-boot-starter-aop' compileOnly 'org.projectlombok:lombok' // 如果仍然希望保持Lombok的支持 annotationProcessor 'org.projectlombok:lombok' } ``` ##### 编写切入点表达式匹配带 `@LogInfo` 的方法 接下来,通过编写一个简单的切片类来拦截所有标注有 `@LogInfo` 的方法调用,并在其周围添加额外的行为——即记录进入和离开该方法的时间戳以及其他上下文信息。 ```java package com.example.logging.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.AfterReturning; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); @Before("@annotation(com.example.logging.LogInfo)") public void beforeMethodExecution(JoinPoint joinPoint){ var methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); StringBuilder argString = new StringBuilder("["); for(Object obj : args){ argString.append(obj).append(", "); } if(args.length > 0){ argString.setLength(argString.length()-2); } argString.append("]"); logger.info("Entering method {} with arguments {}", methodName, argString.toString()); } @AfterReturning(pointcut="@annotation(com.example.logging.LogInfo)", returning="result") public void afterMethodExecutedSuccessfully(JoinPoint joinPoint, Object result){ var methodName = joinPoint.getSignature().getName(); logger.info("Exiting method {}. Result was: {}", methodName, result != null ? result.toString(): "null"); } } ``` 这段代码实现了两个主要的功能:一是在每次调用带有 `@LogInfo` 注解的方法之前记录输入参数;二是在成功完成后记录返回的结果。注意这里的包名应当根据实际项目结构调整。 #### 应用 `@LogInfo` 到具体的服务层方法 最后一步就是在适当的地方运用新创建好的 `@LogInfo` 注解了。下面是一个服务类的例子,其中某些方法已经加上了这个注解来进行更详细的日志跟踪。 ```java @Service public class MyService { @LogInfo(value = "Processing user request.") public User processUserRequest(User userInput){ // 处理用户的请求... return userInput; } // Other service methods without logging annotations. } ``` 以上就是关于如何在一个典型的 Spring Boot 应用程序里引入并利用 `@LogInfo` 进行细粒度控制的日志记录的一个完整流程介绍。当然也可以根据自己需求调整具体的实现细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值