最新最全面的Spring详解(二)——classpath扫描和组件管理(中)

简介: 最新最全面的Spring详解(二)——classpath扫描和组件管理(中)

d、指定Bean范围


Spring包含了【@Scope】注解,以便您可以指定bean的范围。

默认的作用域是’ singleton ‘,但是你可以用’ @Scope '注解来覆盖它,如下面的例子所示:

@Configuration
public class MyConfiguration {
    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}

e、定制Bean命名

默认情况下,配置类使用【@Bean】方法的名称作为结果bean的名称。 但是,可以使用’ name '属性覆盖该功能,如下例所示:

@Configuration
public class AppConfig {
    @Bean("myThing")
    public Thing thing() {
        return new Thing();
    }
}

有时需要为单个bean提供多个名称,或者称为bean别名。【@Bean】注解的’ name '属性为此接受String数组。 下面的例子展示了如何为一个bean设置多个别名:

@Configuration
public class AppConfig {
    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

f、Bean 描述

有时,提供bean的更详细的文本描述是很有帮助的。 当bean被公开(可能通过JMX)用于监视目的时,这可能特别有用。

要向【@Bean】添加描述,可以使用【@Description】注解,如下面的示例所示:

@Configuration
public class AppConfig {
    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}

🍀(4)@Configuration


【@Configuration】是一个类级注解,指示一个对象是beanDifination的源。【@Configuration】类通过【@Bean】带注解的方法声明bean。 【在“@Configuration”类上调用“@Bean”方法也可以用来定义bean间的依赖关系】。


注入bean之间的依赖


当@Bean方法在没有标注@Configuration的类中声明时,它们被认为是在【lite】模式下处理的。 在【@Component】中声明的Bean方法甚至在一个普通的类中声明的Bean方法都被认为是【lite】。在这样的场景中,【@Bean】方法是一种通用工厂方法机制。


与@Configuration 不同,【lite】模式下 【@Bean】方法不能【声明bean】间的【依赖关系】。 因此,这样的【@Bean】方法不应该调用其他【@Bean】下的方法。 每个这样的方法实际上只是特定bean引用的工厂方法,没有任何特殊的运行时语义。


在一般情况下,@Bean方法要在【@Configuration】类中声明,这种功能情况下,会使用【full】模式,因此交叉方法引用会被重定向到容器的生命周期管理。 这可以防止通过常规Java调用意外调用相同的Bean,这有助于减少在【lite】模式下操作时难以跟踪的微妙错误。


@Bean 和@Configuration注解将在下面几节中深入讨论。 不过,我们首先介绍通过使用基于java的配置创建spring容器的各种方法。


当bean相互依赖时,表示这种依赖就像让一个bean方法调用另一个bean方法一样简单,如下面的示例所示:

@Configuration
public class AppConfig {
    @Bean
    public BeanOne beanOne() {
        // full模式可以直接调用方法,这个调用过程由容器管理,lite模式这就是普通方法调用,多次调用会产生多个实例。
        return new BeanOne(beanTwo());
    }
    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

在前面的例子中,【beanOne】通过构造函数注入接收对【beanTwo】的引用。

考虑下面的例子,它显示了一个带注解的@Bean方法被调用两次:

@Configuration
public class AppConfig {
    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }
    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }
    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

clientDao() 在【clientService1()】和【clientService2()】中分别被调用一次。 由于该方法创建了一个新的【ClientDaoImpl】实例并返回它,所以通常期望有两个实例(每个服务一个)。 这肯定会有问题。在Spring中,实例化的bean默认有一个【单例】作用域,在调用父方法并创建新实例之前,首先检查容器中是否有缓存的(有作用域的)bean。


我们目前学习的描述候选组件的注解很多,但是仔细意思考,其实很简单:


我们自己的写代码通常使用以下注解来标识一个组件:


@Component 组件的通用注解

@Repository,持久层

@Service,业务层

@Controller,控制层

@Configuration + @Bean

配置类通常是我们不能修改源代码,但是需要注入别人写的类。例如向容器注入一个德鲁伊数据源的bean,我们是绝对不能给这个类加个【@Component 】注解的。


🍀(5) 使用 @Import 注解


就像在Spring XML文件中使用<import/> 元素来实现模块化配置一样,@Import注解允许从另一个配置类加载【@Bean】定义,如下面的示例所示:

@Configuration
public class ConfigA {
    @Bean
    public A a() {
        return new A();
    }
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
    @Bean
    public B b() {
        return new B();
    }
}

现在,在实例化上下文时不需要同时指定ConfigA.class 和ConfigB.class,只需要显式地提供【ConfigB】,如下面的示例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

这种方法简化了容器实例化,因为只需要处理一个类,而不是要求您在构造过程中记住潜在的大量【@Configuration】类。


【小知识】: 我们一样可以给该注解传入一个实现了ImportSelector接口的类,返回的字符串数组的Bean都会被加载到容器当中:


public class ConfigSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.ydlclass.A","com.ydlclass.B"};
    }
}

🍀(6)结合Java和XML配置


Spring的【@Configuration】类支持的目标并不是100%完全替代Spring XML,有些场景xml仍然是配置容器的理想方式。


我们有如下选择:


(1)容器实例化在一个“以XML为中心”的方式使用,例如“ClassPathXmlApplicationContext”。

(2)"以java编程的方式为中心”的方式,实例化它通过使用【@ImportResource】注解导入XML。

以xml为中心使用“@Configuration”类


最好从XML引导Spring容器,并以一种特别的方式包含【@Configuration 】类。将【@Configuration 】类声明为普通的Spring <bean/> 元素。记住,【@Configuration】类最终是容器中的beanDifination。


下面的例子展示了Java中一个普通的配置类:

@Configuration
public class AppConfig {
    @Autowired
    private DataSource dataSource;
    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}

下面的例子显示了一个’ system-test-config.xml '文件的一部分:

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
    <bean class="com.acme.AppConfig"/>
    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

下面的示例显示了一个可能的’ jdbc '。 属性的文件:

user=root
password=root
url=jdbc:mysql://127.0.0.1:3306/ydlclass?characterEncoding=utf8&serverTimezone=Asia/Shanghai
driverName=com.mysql.cj.jdbc.Driver
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

因为【@Configuration】是用【@Component】注解的,所以被【@Configuration】注解的类会自动被组件扫描。 使用与前面示例中描述的相同的场景,我们可以重新定义system-test-config.xml来利用组件扫描。


下面的示例显示了修改后的system-test-config.xml文件:

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

使用@ImportResource以类为中心使用XML


在【@Configuration】类是配置容器的主要机制的应用程序中,可能仍然需要使用至少一些XML。 在这些场景中,您可以使用【@ImportResource】注解,并只定义所需的XML。 这样做可以实现一种“以java为中心”的方法来配置容器,并将XML最小化。


下面的例子说明了这一点:

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}

properties-config.xml:


<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>


jdbc.properties::


jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=


启动容器:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}


8️⃣BeanFactory和FactoryBean


🍀(1)BeanFactory


BeanFactory是一个接口,它是Spring中工厂的顶层规范,是SpringIoc容器的核心接口,它定义了getBean()、containsBean()等管理Bean的通用方法。Spring的容器都是它的具体实现如:


DefaultListableBeanFactory

XmlBeanFactory

ApplicationContext

这些实现类又从不同的维度分别有不同的扩展。


它的源码如下:

public interface BeanFactory {
  //对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象,
  //如果需要得到工厂本身,需要转义
  String FACTORY_BEAN_PREFIX = "&";
  //根据bean的名字,获取在IOC容器中得到bean实例
  Object getBean(String name) throws BeansException;
  //根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。
  <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
  Object getBean(String name, Object... args) throws BeansException;
  <T> T getBean(Class<T> requiredType) throws BeansException;
  <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
  //提供对bean的检索,看看是否在IOC容器有这个名字的bean
  boolean containsBean(String name);
  //根据bean名字得到bean实例,并同时判断这个bean是不是单例
  boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
  boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
  boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
  boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
  //得到bean实例的Class类型
  @Nullable
  Class<?> getType(String name) throws NoSuchBeanDefinitionException;
  //得到bean的别名,如果根据别名检索,那么其原名也会被检索出来
  String[] getAliases(String name);
}

使用场景:


从Ioc容器中获取Bean(byName or byType)

检索Ioc容器中是否包含指定的Bean

判断Bean是否为单例

🍀(2)FactoryBean


首先它是一个Bean,但又不仅仅是一个Bean。它是一个能生产或修饰对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例。一般用于创建第三方或复杂对象。


源码如下:

public interface FactoryBean<T> {
  //从工厂中获取bean
  @Nullable
  T getObject() throws Exception;
  //获取Bean工厂创建的对象的类型
  @Nullable
  Class<?> getObjectType();
  //Bean工厂创建的对象是否是单例模式
  default boolean isSingleton() {
    return true;
  }
}


从它定义的接口可以看出,FactoryBean表现的是一个工厂的职责。 即一个Bean A如果实现了FactoryBean接口,那么A就变成了一个工厂,根据A的名称获取到的实际上是工厂调用getObject()返回的对象,而不是A本身,如果要获取工厂A自身的实例,那么需要在名称前面加上'&'符号。


getObject(‘name’):返回工厂中的实例

getObject(‘&name’):返回工厂本身的实例

通常情况下,bean 无须自己实现工厂模式,Spring 容器担任了工厂的 角色;但少数情况下,容器中的 bean 本身就是工厂,作用是产生其他 bean 实例。由工厂 bean 产生的其他 bean 实例,不再由 Spring 容器产生,因此与普通 bean 的配置不同,不再需要提供 class 元素。


下边的例子我们使用FactoryBean注入一个bean

@Component
public class MyBean implements FactoryBean {
    private String message;
    public MyBean() {
        this.message = "通过构造方法初始化实例";
    }
    @Override
    public Object getObject() throws Exception {
        // 这里并不一定要返回MyBean自身的实例,可以是其他任何对象的实例。
        //如return new Student()...
        return new MyBean("通过FactoryBean.getObject()创建实例");
    }
    @Override
    public Class<?> getObjectType() {
        return MyBean.class;
    }
    public String getMessage() {
        return message;
    }
}

MyBean实现了FactoryBean接口的两个方法,getObject()是可以返回任何对象的实例的,这里测试就返回MyBean自身实例,且返回前给message字段赋值。


同时在构造方法中也为message赋值。然后测试代码中先通过名称获取Bean实例,打印message的内容,再通过&+名称获取实例并打印message内容。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class FactoryBeanTest {
    @Autowired
    private ApplicationContext context;
    @Test
    public void test() {
        MyBean myBean1 = (MyBean) context.getBean("myBean");
        System.out.println("myBean1 = " + myBean1.getMessage());
        MyBean myBean2 = (MyBean) context.getBean("&myBean");
        System.out.println("myBean2 = " + myBean2.getMessage());
        System.out.println("myBean1.equals(myBean2) = " + myBean1.equals(myBean2));
    }
}

结果:


myBean1 = 通过FactoryBean.getObject()初始化实例 
myBean2 = 通过构造方法初始化实例 
myBean1.equals(myBean2) = false


相关文章
|
1月前
|
人工智能 自然语言处理 Java
Spring AI,Spring团队开发的新组件,Java工程师快来一起体验吧
文章介绍了Spring AI,这是Spring团队开发的新组件,旨在为Java开发者提供易于集成的人工智能API,包括机器学习、自然语言处理和图像识别等功能,并通过实际代码示例展示了如何快速集成和使用这些AI技术。
Spring AI,Spring团队开发的新组件,Java工程师快来一起体验吧
|
1月前
|
Java Spring
Spring的AOP组件详解
该文章主要介绍了Spring AOP(面向切面编程)组件的实现原理,包括Spring AOP的基础概念、动态代理模式、AOP组件的实现以及Spring选择JDK动态代理或CGLIB动态代理的依据。
Spring的AOP组件详解
|
27天前
|
Java 开发工具 Spring
【Azure 事件中心】azure-spring-cloud-stream-binder-eventhubs客户端组件问题, 实践消息非顺序可达
【Azure 事件中心】azure-spring-cloud-stream-binder-eventhubs客户端组件问题, 实践消息非顺序可达
|
1月前
|
安全 Java Spring
Spring Boot 关闭 Actuator ,满足安全工具扫描
Spring Boot 关闭 Actuator ,满足安全工具扫描
46 0
|
2月前
|
安全 前端开发 Java
Java技术栈中的核心组件:Spring框架
Java作为一门成熟的编程语言,其生态系统拥有众多强大的组件和框架,其中Spring框架无疑是Java技术栈中最闪耀的明星之一。Spring框架为Java开发者提供了一套全面的编程和配置模型,极大地简化了企业级应用的开发流程。
35 1
|
2月前
|
存储 安全 Java
实现基于Spring Cloud的分布式配置管理
实现基于Spring Cloud的分布式配置管理
|
2月前
|
安全 Java Spring
Spring Boot中的环境配置和管理
Spring Boot中的环境配置和管理
|
2月前
|
Java 开发工具 git
Spring Cloud中的分布式配置管理
Spring Cloud中的分布式配置管理
|
2月前
|
缓存 监控 Java
Spring Boot中使用Ehcache进行缓存管理
Spring Boot中使用Ehcache进行缓存管理
|
2月前
|
Java 开发工具 数据安全/隐私保护
Spring Cloud中的分布式配置管理最佳实践
Spring Cloud中的分布式配置管理最佳实践