侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 285 篇文章
  • 累计创建 125 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

SpringBoot配置类的实现 - @Configuration、@Bean注解的使用详解

孔子说JAVA
2022-08-02 / 0 评论 / 0 点赞 / 76 阅读 / 11,283 字 / 正在检测是否收录...

Spring Boot 推荐使用 java 配置完全代替 XML 配置,java 配置是通过 @Configration 和 @Bean 注解实现的。其中的 @Configration 注解作用于类上,声明当前类是一个配置类,相当于 Spring 中的一个 XML 文件,而 @Bean 注解则作用在方法上,声明当前方法的返回值是一个 Bean。在普通的Spring 项目中使用 @Configuration 和 @Bean,需要注意加上扫描包的配置 <context:component-scan base-package="com.xxx.xxx" /> 或者使用注解 @ComponentScan,而Springboot项目则不需要,因为它会自动扫描包。

1、注解介绍

在springboot中定义bean不是通过xml配置文件,而是通过java配置来实现的,@Configuration 和 @Bean 注解是实现java配置的2个注解。其中@Configuration注解相当于spring的xml配置文件中beans标签,@Bean可理解为用spring的时候xml里面的bean标签。

1.1 @Configuration注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

可以看到 @Configuration 注解底层是含有 @Component,所以@Configuration 具有和 @Component 的作用。被 @Configuration 修饰的类被定义为一个Spring容器(应用上下文),@Configuration 就相当于Spring配置文件中的 <beans /> 标签,里面可以配置bean。

  • @Component 注解的范围最广,所有自己写的类都可以注解,而 @Configuration 注解一般注解在配置类上,起配置作用。

用法:作用在类上面 作用:告诉SpringBoot这是一个配置类,相当于Spring中的xml配置文件。

@Configuration //告诉SpringBoot这是一个配置类 == 配置文件
public class Config {
}

1.2 @Bean注解

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    /** @deprecated */
    @Deprecated
    Autowire autowire() default Autowire.NO;

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

@Bean 注解作用于方法上,相当于spring的xml配置文件中的bean标签,告诉容器注入一个bean。

  • @Bean注解的方法上如果没通过bean指定实例名,默认实例名与方法名相同。也可以指定实例名,初始化和销毁方法。如 @Bean(name="testNean",initMethod="start",destroyMethod="cleanUp")
  • @Bean注解默认为单例模式(singleton作用域),可以通过@Scope(“prototype”)设置为多例,如 @Scope("prototype")
  • @Bean 注解可以接受一个 String 数组设置多个别名。

配置类里面使用 @Bean 注解在方法上给IoC容器注册组件,默认也是单实例的。

  • 作用:给容器中添加组件,相当于Spring中xml配置文件中的 <bean> 标签。
  • 理解:以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例。
@Configuration //告诉SpringBoot这是一个配置类 == 配置文件
public class Config {
    @Bean   //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public Person person1(){
        return new Person("Mr.Yu",21,"male");
    }
    @Bean("customize")  //id值也可以指定
    public Person person2(){
        return new Person("小明",20,"male");
    }
    // @Bean 注解可以接受一个 String 数组设置多个别名。比如下面除了主名称 person3 外,还有别名 customize1、customize2。
    @Bean("customize1, customize2")
    public Person person3(){
        return new Person("小z",22,"male");
    }
    
    // 配合 @Scope 注解将其改成 prototype 原型模式(每次获取 Bean 的时候会有一个新的实例)
    @Bean
    @Scope("prototype")
    public Person person4(){
        return new Person("小a",23,"male");
    }
}

当需要强制指定实例化bean的顺序,可以通过 @Order 或 @DependsOn 注解来实现

2、示例讲解

2.1 @Configuration注解

在springboot项目中增加一个类,在该类上增加 @Configuration 注解,类中定义默认的构造方法。

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TestConfig {
	public TestConfig() {
		System.out.println("springboot已经启动。。。");
	} 
}

启动springboot可以看到该构造方法已经执行,这表明springboot启动的同时已经加载了这个配置:

  • 注:TestConfig 这个类要与Springboot启动类放在同一级目录或子目录下,否则无法加载。

image-1659341364060

2.2 @Bean注解

创建一个javaBean

public class TestBean {
	private String username="张三";
	private String url="www.kongzid.com";
	private String password="12345";
	public void sayHello(){
		System.out.println("TestBean sayHello...");
	}
	public String toString(){
		return "username:"+this.username+",url:"+this.url+",password:"+this.password;
	}
	public void start(){
		System.out.println("TestBean 初始化。。。");
	}
	public void cleanUp(){
		System.out.println("TestBean 销毁。。。");
	}
}

修改上例的TestConfig,添加Bean

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import com.example.test1.TestBean;

@Configuration
public class TestConfiguration {
	public TestConfiguration(){
		System.out.println("spring容器启动初始化。。。");
	}

	//@Bean注解注册bean,同时可以指定初始化和销毁方法
	//@Bean(name="testNean",initMethod="start",destroyMethod="cleanUp")
	@Bean
	@Scope("prototype")
	//默认是单例模式,即scope="singleton"。另外scope还有prototype、request、session、global session作用域。scope="prototype"多例 
	//每次获取Bean的时候会有一个新的实例
	public TestBean testBean() {
		return new TestBean();
	}
}

Springboot启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.example.test1.TestBean;

@SpringBootApplication
public class SpringajaxApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext run = SpringApplication.run(SpringajaxApplication.class, args);
		TestBean bean =(TestBean) run.getBean("testBean");
		bean.sayHello();
	}
}

启动springboot后的输出结果如下,可以看到bean已经注册,能够正常调用bean所开放的方法:

2022119144513123

2.3 单实例

public static void main(String[] args) {
	// 1.返回我们IOC容器
	ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
	// 2.查看容器里面的组件
	String[] names = run.getBeanDefinitionNames();
	for (String name : names) {
		System.out.println(name);
	}
	//单实例
	TestBean bean1 = run.getBean("testBean",TestBean.class);
	TestBean bean2 = run.getBean("testBean",TestBean.class);
	System.out.println("bean1 == bean2:"+ (bean1 == bean2));
}

输出结果:“bean1 == bean2: true”,无论获取多少次bean,都是同一个实例。

2.4 配置类也是容器的组件

@SpringBootApplication
public class SpringajaxApplication {

	public static void main(String[] args) {
		// 1. 返回IOC容器
		ConfigurableApplicationContext run = SpringApplication.run(SpringajaxApplication.class, args);
		// 2. 查看容器里面的组件
		String[] names = run.getBeanDefinitionNames();
		for (String name : names) {
				System.out.println(name);
		}
		// 配置类本身也是组件
		TestConfiguration bean = run.getBean(TestConfiguration.class);
		// com.kz.boot.config.TestConfiguration$$EnhancerBySpringCGLIB$$4aa44992@381d7219
		System.out.println(bean);
	}
}

2.5 直接调用配置类里面的testBean()方法

如果我们直接调用配置类里面的testBean()方法会发生什么情况,它是从IoC容器中拿还是直接new一个对象呢?

  • 在new一个配置类出来的情况下,调用testBean方法,它返回的是new出来的对象
  • 但是如果我们从容器中取得的配置类,无论再去掉用多少次testBean方法,它始终返回的都是同一个单实例对象,也就是从IoC容器中拿的对象。
public static void main(String[] args) {
	// 1.返回我们IOC容器
	ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
	// 2.查看容器里面的组件
	String[] names = run.getBeanDefinitionNames();
	for (String name : names) {
		System.out.println(name);
	}
	// 单实例
	TestBean bean1 = run.getBean("testBean",TestBean.class);
	TestBean bean2 = run.getBean("testBean",TestBean.class);
	System.out.println("bean1 == bean2:"+ (bean1 == bean2));
    
	// 如果我们直接调用testBean方法,它是从IoC容器中拿还是直接new一个对象呢,
	// 在new一个配置类出来的情况下,调用testBean方法,它返回的是new出来的对象
	TestConfiguration config = new TestConfiguration();
	TestBean bean3 = config.testBean();
	TestBean bean4 = config.testBean();
	System.out.println("bean3 == bean4 :"+ (bean3 == bean4));
	// 但是如果我们从容器中取得的配置类,无论再去掉用多少次testBean方法,它始终返回的都是同一个单实例对象,也就是从IoC容器中拿的对象。
	// 如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有;
	// 保持组件单实例
	TestBean bean5 = bean.testBean();
	TestBean bean6 = bean.testBean();
	System.out.println("bean5 == bean6 :"+ (bean5 == bean6));
}

输出结果:

bean3 == bean4 : false
bean5 == bean6 : true

2.6 proxyBeanMethods——代理bean的方法

从容器中获取到的配置类对象输出结果:com.kz.boot.config.TestConfiguration$$EnhancerBySpringCGLIB$$4aa44992@381d7219,从输出结果中我们可以看到从容器中获取到的配置类对象本身就是一个被 SpringCGLIB 增强了的代理对象。

  • @Configuration()默认设置的是proxyBeanMethods = true,是Full模式(重量级模式)
  • 如果@Configuration(proxyBeanMethods = true),就是代理对象调用方法。SpringBoot总会检查这个组件是否在容器中已有,调用配置类中的方法时会返回容器中已有的组件(即IoC容器中已存在的对象),确保单例。
  • 如果@Configuration(proxyBeanMethods = false),则是Lite模式(轻量级模式)。此模式下,就不是代理对象调用方法,配置类是作为普通类去调用方法,SpringBoot不会检查这个组件是否在容器中已有,调用配置类中的方法时会返回新对象,不会形成组件依赖。

例子:把上述代码中的@Configuration()改为@Configuration(proxyBeanMethods = false),这时候再执行2.5中的示例,得出结果:

bean3 == bean4 : false
bean5 == bean6 : false

总结:

  • Full模式(proxyBeanMethods = true):保证每个@Bean方法被调用多少次返回的组件都是单实例的
  • Lite模式(proxyBeanMethods = false):每个@Bean方法被调用多少次返回的组件都是新创建的
  • 组件依赖必须使用Full模式默认。其他默认是否Lite模式
  • 配置类组件之间无依赖关系用Lite模式加速容器启动过程,调用配置类中的方法时,SpringBoot每次都不会判断对象在容器中是否已经存在,减少了判断过程
  • 配置类组件之间有依赖关系,调用配置类中的方法时,SpringBoot每次都会判断对象在容器中是否已经存在,方法会被调用得到之前单实例组件,用Full模式

2.7 bean的依赖

@Configration 注解类中可以声明多个 @Bean 方法,并且 bean 与 bean 之间是可以有依赖关系的。如果一个 bean 的定义依赖其他 bean,则直接调用对应的 JavaConfig 类中依赖 bean 的创建方法就可以了。

  • 本例中 country 这个 Bean 和 userInfo中直接调用的 country() 方法返回的是同一个实例。
@Configuration
public class MyBeanConfig {
  
    @Bean
    public Country country(){
        return new Country();
    }
  
    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }
}

2.8 SpringBootApplication注解

下述代码中 @SpringBootApplication(scanBasePackages={“com.kz.boot”}) 等同于三个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan(com.kz.boot)。如果不指定scanBasePackages,默认组件扫描基础包是主程序类 SpringajaxApplication 所在包及其子包。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.example.test1.TestBean;

@SpringBootApplication(scanBasePackages={"com.kz.boot"})
public class SpringajaxApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext run = SpringApplication.run(SpringajaxApplication.class, args);
		TestBean bean =(TestBean) run.getBean("testBean");
		bean.sayHello();
	}
}

3、使用示例

编写User Model对象

public class User {
    private String username;
    private String password;
    private Integer age;
 
    // getter、setter方法
}

编写UserDAO

用于模拟与数据库的交互(注意此DAO没有加注解)

import java.util.ArrayList;
import java.util.List;
 
public class UserDAO {
 
    public List<User> queryUserList() {
        // 模拟数据库的查询
        List<User> result = new ArrayList<User>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setUsername("username_" + i);
            user.setPassword("password_" + i);
            user.setAge(i + 1);
            result.add(user);
        }
 
        return result;
    }
 
}

编写UserService

用于实现User数据操作业务逻辑(声明service注解,并自动注入dao对象)。

import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
@Service
public class UserService {
    // 注入Spring容器中的bean对象
    @Autowired
    private UserDAO userDAO;
 
    public List<User> queryUserList() {
        // 调用userDAO中的方法进行查询
        return this.userDAO.queryUserList();
    }
 
}

编写SpringConfig

用于实例化Spring容器。加上 @Configuration 注解,同时加上 @ComponentScan 配置扫描的包。

  • @Bean用于向容器中注入对象,如果在UserDao类前面打上@Repository注解就不用@Bean方式。
package com.kz.javaconfig;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
 
//通过@Configuration注解来表明该类是一个Spring的配置,相当于一个xml文件
@Configuration
@ComponentScan(basePackages = "com.kz.javaconfig")
public class SpringConfig {
 
    // 通过@Bean注解来表明是一个Bean对象,相当于xml中的<bean>
    @Bean
    public UserDAO getUserDAO() {
        return new UserDAO(); // 直接new对象做演示
    }
 
}

编写测试方法,启动Spring容器

package com.kz.javaconfig;
 
import java.util.List;
 
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class Application {
 
    public static void main(String[] args) {
        // 通过Java配置来实例化Spring容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
 
        // 在Spring容器中获取Bean对象
        UserService userService = context.getBean(UserService.class);
 
        // 调用对象中的方法
        List<User> list = userService.queryUserList();
        for (User user : list) {
            System.out.println(user.getUsername() + ", " + user.getPassword() + ", " + user.getPassword());
        }
 
        // 销毁该容器
        context.destroy();
    }
 
}

执行结果

username_0, password_0, password_0
username_1, password_1, password_1
username_2, password_2, password_2
username_3, password_3, password_3
username_4, password_4, password_4
username_5, password_5, password_5
username_6, password_6, password_6
username_7, password_7, password_7
username_8, password_8, password_8
username_9, password_9, password_9

总结:从本示例中可以看出,使用Java代码就完美的替代了xml配置文件,并且结构更加的清晰。

0

评论区