【Spring面试全家桶】Spring之@Configuration底层原理详解


@Configuration注解是Spring框架中常用的注解之一,它的作用是定义一个配置类,并且在其中定义一些Bean对象和Bean之间的关系。在运行时,Spring框架会根据@Configuration注解生成相应的BeanDefinition对象,并将其注册到IoC容器中。

@Configuration注解底层原理主要涉及两个方面:BeanDefinition解析和代理对象生成。

1. BeanDefinition解析

@Configuration注解在底层是通过@ConfigurationClassParser类来解析的。当Spring框架扫描到@Configuration注解时,就会调用@ConfigurationClassParser类的parse()方法对其进行解析。

@ConfigurationClassParser类会首先解析@Configuration注解所标记的类,然后通过反射扫描类中的方法,找到其中所有与@Bean注解标记的方法,并解析这些方法生成对应的BeanDefinition对象。同时,它还会解析类之间的依赖关系,确定哪些Bean对象需要在IoC容器中生成代理对象,哪些不需要。

@Configuration注解被认为是定义配置类的主要方式之一。在Spring的IoC容器中,配置类是一种特殊的类,其目的是定义应用程序中的Bean定义、配置元数据和业务逻辑等信息。一个配置类可以使用@Bean注解来定义一个或多个Bean对象,这些对象将被IoC容器管理,以便在需要时进行注入或使用。

@ConfigurationClassParser类是Spring框架内部的一个类,它是用来解析@Configuration注解的类。它的主要功能是扫描@Configuration注解所标记的类,并解析其中所有的@Bea注解标记的方法,为这些方法生成对应的BeanDefinition对象,并将这些对象注册到IoC容器中。

在解析@Configuration注解时,@ConfigurationClassParser类会使用Java的反射机制扫描类中的所有方法,并对其中被@Bean注解标记的方法进行解析。解析时,它会查找方法的返回类型、参数类型,以及方法上的一些元数据,如@Scope、@Lazy、@DependsOn等。通过这些元数据,它可以生成对应的BeanDefinition对象,并将其注册到IoC容器中。

同时,@ConfigurationClassParser类还会解析类之间的依赖关系,确定哪些Bean对象需要在IoC容器中生成代理对象,哪些不需要。如果有必要,它还可以根据条件判断是否需要为某些Bean对象生成代理对象。

@Configuration注解用于标记一个类作为应用程序的配置类,以便在运行时为IoC容器提供一些有关Bean定义和配置元数据的信息。它通常与@Bean注解一起使用,用于定义一个或多个Bean对象。

@Bean注解用于标记一个方法,使其返回一个对象作为IoC容器中的Bean对象。通常情况下,该方法将在容器启动时被执行,并且它返回的对象将被容器管理和注入到其他的Bean中。

@ConfigurationClassParser类是Spring框架内部的一个类,它用于解析@Configuration注解,扫描其中所有的@Bea注解标记的方法,并为这些方法生成对应的BeanDefinition对象,然后将这些对象注册到IoC容器中。同时,它还可以解析类之间的依赖关系,并为需要进行代理的Bean对象生成代理对象。

下面是一个简单的使用@Configuration注解和@Bean注解的例子:

@Configuration
public class AppConfig {
    
    @Bean
    public UserDao userDao() {
        return new UserDaoImpl();
    }
    
    @Bean
    public UserService userService(UserDao userDao) {
        return new UserServiceImpl(userDao);
    }
}

在这个例子中,AppConfig类被标记为一个@Configuration类,它包含了两个@Bean方法:userDao()和userService()。userDao()方法返回一个UserDaoImpl对象,userService()方法接受一个UserDao参数,并返回一个UserServiceImpl对象。这两个Bean对象将被IoC容器管理,并可以在其他的Bean中使用。

下面是一个使用@ConfigurationClassParser类的例子:

@Configuration
public class AppConfig {
    
    @Bean
    public UserDao userDao() {
        return new UserDaoImpl();
    }
    
    @Bean
    public UserService userService(UserDao userDao) {
        return new UserServiceImpl(userDao);
    }
    
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        
        UserService userService = context.getBean(UserService.class);
        userService.addUser(new User("John", "Doe"));
    }
}

在这个例子中,AppConfig类被标记为一个@Configuration类,并包含两个@Bean方法。在main()方法中,我们使用AnnotationConfigApplicationContext类来创建一个IoC容器,并将AppConfig类作为参数传递给它。然后,我们从容器中获取一个UserService对象,并调用它的addUser()方法来添加一个新用户。这个过程中,@ConfigurationClassParser类被用来解析@Configuration注解,并生成对应的BeanDefinition对象,然后将这些对象注册到IoC容器中。

总之,@Configuration注解、@Bean注解和@ConfigurationClassParser类是Spring框架中非常重要的组成部分,它们共同构成了Spring的IoC容器的核心机制,可以帮助我们方便地管理Bean对象,并实现依赖注入和AOP等高级功能。

2. 代理对象生成

在@Configuration注解中,如果一个Bean对象被@Scope注解标记,则在IoC容器中生成的对象不是该类的实例,而是一个代理对象。这个代理对象是由CGLIB或者JDK动态代理生成的。

当@Bean注解所标记的方法返回一个代理对象时,Spring框架会首先判断是使用CGLIB还是JDK动态代理。如果被@Bean注解标记的方法所返回的类型是接口,则Spring框架会使用JDK动态代理生成代理对象;如果返回的类型是类,则Spring框架会使用CGLIB生成代理对象。生成代理对象后,Spring框架会将该对象注册到IoC容器中。

@Configuration注解表示该类是一个配置类,其中的@Bean注解用于将返回的对象装配到Spring的IoC容器中。当被@Bean注解标记的方法返回的对象需要在不同的作用域中重用时,可以使用@Scope注解。

@Scope注解用于指定一个Bean对象的作用域范围,可以取值为singleton、prototype、request、session、global session等等。其中,singleton表示该Bean对象在IoC容器中是单例的,每次请求都会获取同一个实例。prototype表示该Bean对象在IoC容器中不是单例,每次请求都会创建一个新的实例。

当一个Bean对象被@Scope注解标记时,如果该类实现了接口,则Spring框架会使用JDK动态代理来创建代理对象;如果该类没有实现接口,则Spring框架会使用CGLIB来创建代理对象。代理对象的生成是由Spring框架来完成的,生成的代理对象是原始Bean对象的一个替身。

使用代理对象的好处是可以在原始Bean对象的基础上增加一些额外的功能,比如事务管理、缓存管理等等。在使用代理对象时,Spring框架会自动将代理对象注入到调用方中,调用方无需关心代理对象是如何创建的。

代码示例:

@Configuration
public class MyConfig {

    @Bean
    @Scope(value = "singleton")
    public MySingletonBean mySingletonBean() {
        return new MySingletonBean();
    }

    @Bean
    @Scope(value = "prototype")
    public MyPrototypeBean myPrototypeBean() {
        return new MyPrototypeBean();
    }

}

public class MySingletonBean {
    // 实现单例Bean的逻辑
}

public class MyPrototypeBean {
    // 实现非单例Bean的逻辑
}

在上面的代码示例中,MyConfig 类被 @Configuration 注解标记,表示它是一个配置类。在这个类中,定义了两个方法 mySingletonBean()myPrototypeBean(),它们分别返回一个单例 Bean 对象和一个非单例 Bean 对象。

@Scope(value = "singleton") 表示 MySingletonBean 是一个单例 Bean,在 IoC 容器中只会被创建一次,每次请求都会返回同一个实例。而 @Scope(value = "prototype") 则表示 MyPrototypeBean 是一个非单例 Bean,在 IoC 容器中每次请求都会创建一个新的实例。

需要注意的是,当一个 Bean 类实现了接口时,Spring 框架会使用 JDK 动态代理来创建原始 Bean 对象的代理对象;如果一个 Bean 类没有实现接口,则 Spring 框架会使用 CGLIB 来创建代理对象。代理对象的生成是由 Spring 框架自动完成的。

使用 @Scope 注解指定 Bean 的作用域范围,可以有效地控制 Bean 对象的生命周期和创建次数,提高程序的性能和可维护性。同时,Spring 框架还可以利用代理对象来增加一些额外的功能,比如事务管理、缓存管理等等,从而提高程序的可扩展性。

总体来说,@Configuration注解底层原理涉及到BeanDefinition解析和代理对象生成两方面。在运行时,Spring框架会根据@Configuration注解生成相应的BeanDefinition对象,并将其注册到IoC容器中。同时,如果一个Bean对象被@Scope注解标记,则在IoC容器中生成的对象不是该类的实例,而是一个代理对象。

3.实战中Spring之@Configuration的问题与解决方案

问题:

  1. @Configuration注解下@Bean方法没有被调用 - 可能原因是@Configuration类没有被正确加载或者没有被作为Spring的组件扫描到。

  2. @Autowired自动注入失效 - 可能原因是@Autowired注解的类没有被纳入到Spring容器的管理之中,也可能是@Autowired注解的类的构造函数或setter方法没有正确地被调用。

  3. @Value获取属性值失败 - 可能原因是属性值没有被正确地注入到@Configuration类中,或者是@Configuration类没有被正确地加载。

解决方案:

  1. 确认@Configuration类被正确地加载并且被纳入到Spring容器的管理之中。可以通过在Spring Boot启动类上添加@ComponentScan注解或者在@Configuration类上添加@Component注解来解决这个问题。

  2. 确认@Autowired注解的类被正确地纳入到Spring容器的管理之中,并且构造函数或setter方法被正确地调用。可以通过在@Autowired注解的类上添加@Component注解或者在@Configuration类中通过@Bean方法注入该类来解决这个问题。

  3. 确认属性值被正确地注入到@Configuration类中,并且@Configuration类被正确地加载。可以通过在@Configuration类中添加@PropertySource注解或者在Spring Boot配置文件中添加属性来解决这个问题。同时,也可以使用@Value注解的默认值来解决该问题。

4.Java代码示例

  1. 解决@Configuration注解下@Bean方法没有被调用的问题:
@Configuration
public class MyConfig {
    
    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

确保MyConfig类被正确地加载并且被纳入到Spring容器的管理中。

  1. 解决@Autowired自动注入失效的问题:
@Component
public class MyComponent {
    
    private MyService myService;

    @Autowired
    public MyComponent(MyService myService) {
        this.myService = myService;
    }

    //...
}

@Configuration
public class MyConfig {

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

确保MyService类被正确地纳入到Spring容器的管理中,并且构造函数被正确地调用。

  1. 解决@Value获取属性值失败的问题:
@Configuration
@PropertySource("classpath:myconfig.properties")
public class MyConfig {

    @Value("${my.property}")
    private String myProperty;

    //...
}

确保属性值被正确地注入到MyConfig类中,并且MyConfig类被正确地加载。同时,在myconfig.properties文件中添加my.property属性即可。如果属性值获取失败,则可以使用@Value注解的默认值来解决该问题:

@Value("${my.property:default}")
private String myProperty;

如果my.property属性未被正确地注入到MyConfig类中,则使用默认值"default"。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员廖志伟

赏我包辣条呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值