注解之光:揭秘Spring注解发展的演进历程

简介: 注解之光:揭秘Spring注解发展的演进历程


通过 Spring 注解编程的发展过程的演变的了解,我们能够更加清楚 SpringBoot 的由来。

Spring 1.x

2004年3月24日,Spring1.0 正式发布,提供了 IoC,AOP 及 XML 配置的方式。

在 Spring 1.x 版本中提供的是纯 XML 配置的方式,也就是在该版本中我们必须要提供 XML 的配置文件,在该文件中我们通过 标签来配置需要被 IoC容器管理的 Bean。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="com.bobo.demo01.UserService" />
</beans>
public static void main(String[] args) {
    ApplicationContext ac = new FileSystemXmlApplicationContext("classpath:applicationContext01.xml");
    System.out.println("ac.getBean(UserService.class) = " + ac.getBean(UserService.class));
}

在 Spring1.2 版本的时候提供了 @Transaction(org.springframework.transaction.annotation)注解。简化了事务的操作。

Spring 2.x

在 2006年10月3日,Spring2.0 问世了,在 2.x 版本中,比较重要的特点是增加了很多注解。

在 2.5 版本之前新增的有 @Required @Repository @Aspect,同时也扩展了 XML 的配置能力,提供了第三方的扩展标签,比如

@Required

如果你在某个类的某个 Set 方法上使用了该注释,那么该 Set 方法对应的属性在 XML 配置文件中必须被设置,否则就会报错!

public class UserService {
    private String userName;
    public String getUserName() {
        return userName;
    }
    @Required
    public void setUserName(String userName) {
        this.userName = userName;
    }
}

如果在 XML 文件中我们不设置对应的属性就会给出错误的提示。

设置好属性后就没有了错误提示了。

源码中可以看到 @Required 从 2.0 开始提供。

@Repository

@Repository 对应数据访问层 Bean。这个注解在 Spring2.0 版本就提供的有哦,大家可能没有想到。

@Aspect

@Aspect 是 AOP 相关的一个注解,用来标识切面类。

在 2007年11月19日,Spring更新到了2.5版本,新增了很多常用注解,大大的简化配置操作。

注解 说明
@Autowired 依赖注入
@Qualifier 配置 @Autowired 注解使用
@Component 声明组件
@Service 声明业务层组件
@Controller 声明控制层组件
@RequestMapping 声明请求对应的处理方法

在这些注解的作用下,我们可以不用在 XML 文件中去注册没有 Bean,这时我们只需要指定扫码路径,然后在对应的 Bean 头部添加相关的注解即可,这大大的简化了我们的配置及维护工作。案例如下:

我们在配置文件中只需要配置扫码路径即可:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.bobo" />
</beans>

持久层代码:

@Repository
public class UserDao {
    public void query(){
        System.out.println("dao query ..." );
    }
}

业务逻辑层代码:

@Service
public class UserService {
    @Autowired
    private UserDao dao;
    public void query(){
        dao.query();
    }
}

控制层代码:

@Controller
public class UserController {
    @Autowired
    private UserService service;
    public void query(){
        service.query();
    }
}

测试代码:

public class Demo02Main {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext02.xml");
        UserController acBean = ac.getBean(UserController.class);
        acBean.query();
    }
}

虽然在 Spring 的 2.5 版本提供了很多的注解,也大大的简化了我们的开发,但是仍然没有摆脱 XML 配置驱动。

Spring 3.x

在 2009年12月16日 发布了 Spring3.0 版本,这是一个注解编程发展的里程碑版本,在该版本中全面拥抱 Java5。提供了 @Configuration注解,目的就是去 XML 化。同时通过 @ImportResource 来实现 Java 配置类和 XML 配置的混合使用来实现平稳过渡。

/**
 * @Configuration 标注的Java类 相当于 application.xml 配置文件
 */
@Configuration
public class JavaConfig {
    /**
     * @Bean 注解 标注的方法就相当于 <bean></bean> 标签
              也是 Spring3.0 提供的注解
     * @return
     */
    @Bean
    public UserService userService(){
        return new UserService();
    }
}

在 Spring3.1 版之前配置扫描路径我们还只能在 XML 配置文件中通过 component-scan 标签来实现,在 3.1 之前还不能够完全实现去 XML 配置,在3.1 版本到来的时候,提供了一个 @ComponentScan 注解,该注解的作用是替换掉 component-scan 标签,是注解编程很大的进步,也是 Spring 实现无配置话的坚实基础。

@ComponentScan

@ComponentScan 的作用是指定扫码路径,用来替代在XML中的 标签,默认的扫码路径是当前注解标注的类所在的包及其子包。

定义 UserService:

@Service
public class UserService {
}

创建对于的 Java 配置类:

@Configuration
@ComponentScan
public class JavaConfig {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        System.out.println("ac.getBean(UserService.class) = " + ac.getBean(UserService.class));
    }
}

输出的结果:

当然也可以指定特定的扫描路径:

@Configuration
// 指定特定的扫描路径
@ComponentScan(value = {"com.bobo.demo04"})
public class JavaConfig {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        System.out.println("ac.getBean(UserService.class) = " + ac.getBean(UserService.class));
    }
}

@Import

@Import 注解只能用在类上,作用是快速的将实例导入到 Spring 的 IoC 容器中,将实例导入到 IoC 容器中的方式有很多种,比如 @Bean 注解,@Import 注解可以用于导入第三方包。具体的使用方式有三种。

第一种方式:静态导入

静态导入的方式是直接将我们需要导入到 IoC 容器中的对象类型直接添加进去即可。

这种方式的好处是简单,直接,但是缺点是如果要导入的比较多,则不太方便,而且也不灵活。

第二种方式:ImportSelector

@Import 注解中我们也可以添加一个实现了 ImportSelector 接口的类型,这时不会将该类型导入 IoC 容器中,而是会调用 ImportSelector 接口中定义的 selectImports 方法,将该方法的返回的字符串数组的类型添加到容器中。

定义两个业务类:

public class Cache {
}
public class Logger {
}

定义 ImportSelector 接口的实现,方法返回的是需要添加到 IoC 容器中的对象对应的类型的全类路径的字符串数组,我们可以根据不同的业务需求而导入不同的类型,会更加的灵活些。

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Logger.class.getName(),Cache.class.getName()};
    }
}

导入测试案例:

@Configuration
@Import(MyImportSelector.class)
public class JavaConfig {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}

输出结果:

第三种方式:ImportBeanDefinitionRegistrar

除了上面所介绍的 ImportSelector 方式灵活导入以外还提供了 ImportBeanDefinitionRegistrar 接口,也可以实现,相比 ImportSelector 接口的方式,ImportBeanDefinitionRegistrar 的方式是直接在定义的方法中提供了 BeanDefinitionRegistry,自己在方法中实现注册。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 将需要注册的对象封装为 RootBeanDefinition 对象
        RootBeanDefinition cache = new RootBeanDefinition(Cache.class);
        registry.registerBeanDefinition("cache",cache);
        RootBeanDefinition logger = new RootBeanDefinition(Logger.class);
        registry.registerBeanDefinition("logger",logger);
    }
}

输出结果:

@EnableXXX

@Enable 模块驱动,其实是在系统中我们先开发好各个功能独立的模块,比如 Web MVC 模块, AspectJ 代理模块,Caching 模块等。

案例说明,先定义好功能模块:

/**
 * 定义一个Java配置类
 */
@Configuration
public class HelloWorldConfiguration {
    @Bean
    public String helloWorld(){
        return "Hello World";
    }
}

然后定义 @Enable 注解:

/**
 * 定义 @Enable 注解
 * 在该注解中通过 @Import 注解导入我们自定义的模块,使之生效。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}

测试代码:

@Configuration
// 加载 自定义 模块
@EnableHelloWorld
public class JavaMian {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaMian.class);
        String helloWorld = ac.getBean("helloWorld", String.class);
        System.out.println("helloWorld = " + helloWorld);
    }
}

Spring 4.x

2013年11月1日 更新的 Spring 4.0,完全支持 Java8。这是一个注解完善的时代,提供的核心注解是 @Conditional 条件注解。@Conditional 注解的作用是按照一定的条件进行判断,满足条件就给容器注册 Bean 实例。

@Conditional 的定义为:

// 该注解可以在类和方法中使用
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    /**
     * 注解中添加的类型必须是 实现了 Condition 接口的类型
     */
    Class<? extends Condition>[] value();
}

Condition 是个接口,需要实现 matches 方法,返回 true 则注入 bean,false 则不注入。

案例讲解:

/**
 * 定义一个 Condition 接口的是实现
 */
public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false; // 默认返回false
    }
}

创建 Java 配置类:

@Configuration
public class JavaConfig {
    @Bean
    // 条件注解,添加的类型必须是 实现了 Condition 接口的类型
    // MyCondition的 matches 方法返回true 则注入,返回false 则不注入
    @Conditional(MyCondition.class)
    public StudentService studentService(){
        return new StudentService();
    }
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}

输出结果:

但是将 matchs 方法的返回结果设置为 true 则效果不同。

所以 @Conditional 的作用就是给我们提供了对象导入 IoC 容器的条件机制,这也是 SpringBoot 中的自动装配的核心关键。

当然在 4.x 还提供一些其他的注解支持,比如 @EventListener,作为 ApplicationListener 接口编程的第二选择,@AliasFor 解除注解派生的时候冲突限制。@CrossOrigin 作为浏览器跨域资源的解决方案。

Spring 5.x

2017年9月28日,Spring 来到了 5.0 版本。5.0 同时也是 SpringBoot2.0 的底层。注解驱动的性能提升方面不是很明显。在 Spring Boot 应用场景中,大量使用 @ComponentScan 扫描,导致 Spring 模式的注解解析时间耗时增大,因此 5.0 时代引入 @Indexed,为 Spring 模式注解添加索引。

当我们在项目中使用了 @Indexed 之后,编译打包的时候会在项目中自动生成 META-INT/spring.components 文件。当 Spring 应用上下文执行 ComponentScan 扫描时,META-INT/spring.components 将会被 CandidateComponentsIndexLoader 读取并加载,转换为 CandidateComponentsIndex 对象,这样的话 @ComponentScan 不在扫描指定的 package,而是读取 CandidateComponentsIndex 对象,从而达到提升性能的目的。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-indexer</artifactId>
</dependency>

使用 @Indexed 注解

原文链接:全面解析 - Spring注解发展史-CSDN博客


相关文章
|
3月前
|
缓存 监控 Java
SpringBoot @Scheduled 注解详解
使用`@Scheduled`注解实现方法周期性执行,支持固定间隔、延迟或Cron表达式触发,基于Spring Task,适用于日志清理、数据同步等定时任务场景。需启用`@EnableScheduling`,注意线程阻塞与分布式重复问题,推荐结合`@Async`异步处理,提升任务调度效率。
663 128
|
3月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
491 0
|
2月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
462 2
|
3月前
|
Java 测试技术 数据库
使用Spring的@Retryable注解进行自动重试
在现代软件开发中,容错性和弹性至关重要。Spring框架提供的`@Retryable`注解为处理瞬时故障提供了一种声明式、可配置的重试机制,使开发者能够以简洁的方式增强应用的自我恢复能力。本文深入解析了`@Retryable`的使用方法及其参数配置,并结合`@Recover`实现失败回退策略,帮助构建更健壮、可靠的应用程序。
515 1
使用Spring的@Retryable注解进行自动重试
|
3月前
|
XML Java 数据格式
常用SpringBoot注解汇总与用法说明
这些注解的使用和组合是Spring Boot快速开发和微服务实现的基础,通过它们,可以有效地指导Spring容器进行类发现、自动装配、配置、代理和管理等核心功能。开发者应当根据项目实际需求,运用这些注解来优化代码结构和服务逻辑。
343 12
|
3月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
216 0
探索Spring Boot的@Conditional注解的上下文配置
|
3月前
|
智能设计 Java 测试技术
Spring中最大化@Lazy注解,实现资源高效利用
本文深入探讨了 Spring 框架中的 `@Lazy` 注解,介绍了其在资源管理和性能优化中的作用。通过延迟初始化 Bean,`@Lazy` 可显著提升应用启动速度,合理利用系统资源,并增强对 Bean 生命周期的控制。文章还分析了 `@Lazy` 的工作机制、使用场景、最佳实践以及常见陷阱与解决方案,帮助开发者更高效地构建可扩展、高性能的 Spring 应用程序。
171 0
Spring中最大化@Lazy注解,实现资源高效利用
|
3月前
|
安全 IDE Java
Spring 的@FieldDefaults和@Data:Lombok 注解以实现更简洁的代码
本文介绍了如何在 Spring 应用程序中使用 Project Lombok 的 `@Data` 和 `@FieldDefaults` 注解来减少样板代码,提升代码可读性和可维护性,并探讨了其适用场景与限制。
169 0
Spring 的@FieldDefaults和@Data:Lombok 注解以实现更简洁的代码
|
3月前
|
Java 测试技术 编译器
@GrpcService使用注解在 Spring Boot 中开始使用 gRPC
本文介绍了如何在Spring Boot应用中集成gRPC框架,使用`@GrpcService`注解实现高效、可扩展的服务间通信。内容涵盖gRPC与Protocol Buffers的原理、环境配置、服务定义与实现、测试方法等,帮助开发者快速构建高性能的微服务系统。
792 0
|
3月前
|
XML Java 测试技术
使用 Spring 的 @Import 和 @ImportResource 注解构建模块化应用程序
本文介绍了Spring框架中的两个重要注解`@Import`和`@ImportResource`,它们在模块化开发中起着关键作用。文章详细分析了这两个注解的功能、使用场景及最佳实践,帮助开发者构建更清晰、可维护和可扩展的Java应用程序。
253 0

热门文章

最新文章