Spring核心——Stereotype组件与Bean扫描

注解自动装载中介绍了经过注解(Annotation)自动向Bean中注入其余Bean的方法,本篇将介绍经过注解(Annotation)向容器添加Bean的方法。html

 Spring的核心容器提供了@Component和@Bean注解来标记如何向IoC容器添加Bean。在核心包中@Component又派生了@Service、@Controller和@Repository这三个注解(在其余的Spring工程或包中还有更多的派生),本文主要介绍@Component及其派生注解的使用。前端

一个简单的使用例子

要想使用@Component等注解来向容器添加Bean,须要向IoC容器指明什么类有这个注解,因此Spring提供了一个扫描机制让使用者指定要检查的路径。配置很是简单,只要使用上下文的component-scan标签便可。咱们经过下面的例子来简单说明如何配置。java

例子中的代码仅用于说明问题,并不能运行。源码请到https://gitee.com/chkui-com/spring-core-sample自行clone,例子在chkui.springcore.example.hybrid.component包中。git

有一个接口和一个实现类做为要添加到IoC容器的Bean:web

package chkui.springcore.example.hybrid.component.bean;

public interface NameService {
	String getName();
}
package chkui.springcore.example.hybrid.component.bean;

@Component
public class NameServiceImpl implements NameService{

	@Override
	public String getName() {
		return "This is My Component";
	}
}

在实现类NameServiceImpl上使用了@Component注解。spring

而后XML(/spring-core-sample/src/main/resources/hybrid/component)配置为:express

<?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="chkui.springcore.example.hybrid.component.bean"/>
</beans>

XML配置文件中没有任何<bean>的声明,仅仅是经过component-scan启用了路径扫描功能,base-package指定了扫描的包路径。json

而后咱们加载这个XML运行Spring IoC容器:设计模式

package chkui.springcore.example.hybrid.component;

public class SimpleScanApp {

	public static void main(String[] args) {
		print(new ClassPathXmlApplicationContext("hybrid/component/scanConfig.xml"));
	}
	
	private static void print(ApplicationContext context) {
    	NameService service = context.getBean(NameService.class);
    	System.out.println(service.getName());
	}
}

运行以后NameServiceImpl就会做为一个Bean添加到IoC容器中。api

 IOC功能扩展点 一文中已经介绍经过XML、@Component、@Bean任何一种方式去声明一个Bean都会转化为一个 BeanDefinition 的实现类交给BeanFactory来建立实例,因此实际上经过@Component注解和在XML文件中编写一个<bean>标签在结果上并无什么区别——都是向容器添加了一个Bean实例。可是Spring恰恰提供了@Bean和@Component(以及他的派生注解)2个注解来声名Bean,这当中确定是有一些差别的。

@Bean在后续的文章会介绍,它就等价与在XML编写一个<bean>标签。而@Component以及他的派生注解除了是一个IoC容器中的Bean还有许多附加的含义。

Stereotype与功能分层

观察@Bean和@Component两个注解的包,前者是在 org.springframework.context.annotation ,然后者是在 org.springframework.stereotype 。不只仅是@Component,他的派生注解@Service、@Controller和@Repository都在这个包中,实际上它就是在告诉使用者这些注解提供stereotype的特性(或者称为功能、做用)。

那什么是stereotype特性呢?这很难经过Stereotype这个词的字面意思(这个词能翻译的意思不少,这里最接近的翻译应该是“旧规矩”或者“使固定”)来理解。

Stereotype特性最先出如今J2EE6中(忘记是哪一个JSR提出的了),能够理解为围绕着“元数据”功能而发展出来的一种设计模式,虽然我很难说清楚他属于23个设计模式中的哪个,可是这确实已是一种约定俗成的作法,只要看到Stereotype就应该像看到“Factory——工厂模式”、“Adapter——适配器模式”、“Facade——外观模式”同样,一眼就知道他的做用。

Stereotype特性的目标就是为“组合模式的分层系统”按层标记一个类的功能。所谓的“组合模式的分层系统”实际上就是咱们经常使用的Controller-Service-Dao这种分层模式,只不过有些系统可能会多几层(好比Controller和Service之间加个RPC框架什么的)。根据Stereotype特性的Java官网原文介绍,它是一个用来标记注解的注解(annotating annotation)。一个注解若是被@Stereotype标记证实他提供Stereotype模式的功能,例以下面这样:

@Stereotype 
@Target(TYPE) 
@Retention(RUNTIME) 
@interface controller {}

@Stereotype 
@Target(TYPE) 
@Retention(RUNTIME) 
@interface service {}

而后咱们在使用时能够为不一样层的类打上这些标记,表示他们属于不一样的分层:

interface UserService{}

@Service
class UserServiceImpl implements UserService{
	
}

@Controller
class UseController{
	@Autowired
	UserService userService;
	
}

一个类的实例可能会被用于0到多个分层中(好比Spring的一个Bean既能够是Controller也能够是Service,只要标记对应的注解便可),可是一般状况下一个类最多只会用在一个分层中使用。简单的说Stereotype特性就是用注解来告诉框架某个类是属于系统功能中的哪一层。

Java的文档上要求提供Stereotype特性的注解须要用@Stereotype来标记。可是Spring的开发大神并无理会这个事,@Component并无使用@Stereotype来标记,可是他确实提供了Stereotype的模式。

在Stereotype模式下,Spring核心工程为Controller-Service-Dao的分层模型分别提供了@Controller、@Service、@Repository注解。咱们按照Stereotype的模式为对应的类标记3个注解,而后在引入MVC、ORM、JPA相关的框架以后这些注解会告诉框架对应的类扮演着什么样的功能角色,框架就能很清晰的根据注解提供相关的功能服务。

例如引入Spring-webmvc以后,一个类若是用@Controller注解标记了以后框架就知道他们都是处理前端请求的,MVC框架就会为他提供RequestMapping之类的功能。随后咱们须要将框架调整为WebFlux,基本上直接更换依赖的Jar包就能够了,由于你们都是按照一个模式来开发的。

因此,若是咱们的某个类是用于指定的分层功能,那么最好使用org.springframework.stereotype包中的注解来标记他所属的分层。若是类没有明确的功能(例如用于存储配置数据的类,或者Helper类),使用@Bean等其余方式添加到容器中更合适(@Bean会在后续的文章中介绍)。

使用Stereotype特性来标记分层,还有一个好处是即便工程的结构再复杂多样,均可以很轻松的使用注解(Annotation)来实现拦截器或者AOP功能。由于咱们可以很清晰的知道每一个分层的做用,开发AOP的功能就很是便利。

扫描配置

本文开篇使用了一个简单的例子说明使用<context:component-scan>扫描功能来自动添加被注解标记的Bean。除了使用base-package属性还有其余的标签来控制扫描的路径。

<context:include-filter>和<context:exclude-filter>标签用来指定包含和排除的过滤规则。他们提供2个参数——type和expression,用来指定过滤类型和过滤参数,例如:

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

此外还可使用use-default-filters属性来指定是否扫描默认注解(@Component@Repository@Service@Controller、@Configuration),默认值为ture。若是设定成false,须要咱们在include-filter中增长对应的annotation。

除了使用XML配置,还可使用@ComponentScan注解来指定扫描的路径,他提供和XML配置同样的功能。在后续的文章会介绍纯Java配置的功能。

关于扫描的详细说明见官网的过滤规则说明

组件命名

和普通的Bean同样,咱们也能够在@Component上添加注解来指定Bean在IoC容器的名称:

package chkui.springcore.example.hybrid.component.bean;

@Service("implementNameService")
public class NameServiceImpl implements NameService{
	@Override
	public String getName() {
		return "This is My Component";
	}
}

这样在容器中这个Bean的名称被命名为"implementNameService"。除了直接在注解上添加内容,咱们还能够实现 BeanNameGenerator 接口来实现全局的命名方法。看下面这个例子。(源码请到https://gitee.com/chkui-com/spring-core-sample自行clone,例子在chkui.springcore.example.hybrid.component包中。)

首先在XML中使用 "name-generator" 指定名称的生成器:

<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="chkui.springcore.example.hybrid.component.bean"
		name-generator="chkui.springcore.example.hybrid.component.bean.NameGenerator" />
</beans>

而后编写咱们的命名生成规则:

package chkui.springcore.example.hybrid.component.bean;
public class NameGenerator implements BeanNameGenerator {
	@Override
	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		AnnotatedBeanDefinition annotdef = AnnotatedBeanDefinition.class.cast(definition);
		AnnotationMetadata meta = annotdef.getMetadata();
		//生成规则:若是已经命名不作任何调整,若是未命名则在类名车后面增长”_NoDefinedName“字符串
		return Optional.of(meta).map(met -> met.getAnnotationTypes()).map(set -> set.toArray(new String[] {}))
				.map(array -> array[0]).map(name -> meta.getAnnotationAttributes(name)).map(entry -> entry.get("value"))
				.map(obj -> "".equals(obj) ? null : obj).orElse(definition.getBeanClassName() + "_NoDefinedName")
				.toString();
	}
}

使用索引提高启动速度

一般状况下,即便是对整个classpath进行扫描并不会占用太多的时间,可是某些应用对启动时间有极高的要求,对此Spring提供了索引功能。索引功能并不复杂,就是第一次扫描以后生成一个静态文件记录全部的组件,而后下一次扫描就直接读取文件中的内容,而不去执行扫描过程。

首先引入spring-context-indexer包:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.0.7.RELEASE</version>
        <optional>true</optional>
    </dependency>
</dependencies>
dependencies {
    compileOnly("org.springframework:spring-context-indexer:5.0.7.RELEASE")
}

而后在运行后会生成一个 META-INF/spring.components 的文件,以后只要运行工程发现这个文件都会直接使用他。能够经过环境变量或工程根目录的spring.properties中设置spring.index.ignore=ture来禁用这个功能。

这个功能若是没有什么明确的需求,慎重使用,会提升工程的管理成本。

相关文章
相关标签/搜索