前言
接着上一节继续讲解注解的发展
1.4Spring4.x
在2013年更新了Spring4.0,完全支持Java8的特性,这是一个注解完善的时代,提供核心的注解@Conditional条件注解,@Conditional注解的作用是按照一定的条件进行判断,满足条件就会给容器注入Bean实例
@Conditional的定义为:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
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;
}
}
创建Java配置类
@Configuration
public class JavaConfig {
@Bean
// MyCondition的matche方法返回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);
}
}
}
测试结果:
MyCondition的matche方法返回true注入,返回false不注入
所以@Conditional的作用就是给我们提供了对象导入IoC容器的条件机制,这也是SpringBoot中的自动装配的核心关键,当然在4.x中还提供其他的注解的支持,比如@EventListener,作为ApplicationListener接口编程的第二选择,@AliasFor接触注解派生的时候冲突限制,@CrossOrigin作为浏览器跨域资源的解决方案
1.5Spring 5.x
2017年Spring来到5.0版本,Spring5.0同时也是SpringBoot2.0的底层,注解驱动的性能提升方面不是很明显,在SpringBoot应用场景中,大量使用@ComponentScan扫描,导致Spring模式的注解解析时间耗时增大,因此5.0版本引入了@Indexed注解,为Spring模式注解添加索引
当我们在项目中使用了@Indexed之后,编译打包时会在项目中自动生成META-INF/spring.components文件,当Spring应用上下文执行ComponentScan扫描时,META-INF/spring.components将会被CandidateComponentIndexLoader读取并加载,转换为CandidateComponentsIndex对象,这样的话@ComponentScan不在扫描指定的package,而是读取CandidateComponentsIndex对象,从而达到提升启动性能的目的
在pom.xml引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
</dependency>
使用@Indexed注解
@Indexed
public class StudentService {
}
编译之后查看目录
什么是SPI
最后为什么药讲SPI呢?因为在SpringBoot自动装配中其实有使用到SPI机制,所以掌握这部分知识是对学习SpringBoot很有帮助的
SPI全称:Service Provice Interface,即是一种服务发现机制,它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo,JDBC中都使用到了SPI机制,我们先通过一个例子看下怎么用?
案例介绍
1.在项目里新建一个Module,然后定义一个接口
public interface Connection {
void getUrl();
}
然后install,其他项目就可以引用了
2.然后再新建一个Module,引入上面的依赖
<dependencies>
<dependency>
<artifactId>MyDataBase</artifactId>
<groupId>com.maocai</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
创建接口的实现类
public class MySqlConnection implements Connection {
@Override
public void getUrl() {
System.out.println("mysql...");
}
}
然后在resources目录下创建META-INF/services目录,在该目录下创建一个文件,文件名称必须为定义接口的全限定名,然后在文件中写上接口实现类的全限定名
3.同样的实现Module再创建一个
4.最后新建测试类Module,测试类如下
public class StartApp {
// SPI
public static void main(String[] args) {
ServiceLoader<Connection> loader = ServiceLoader.load(Connection.class);
Iterator<Connection> iterator = loader.iterator();
while (iterator.hasNext()) {
Connection con = iterator.next();
con.getUrl();
}
}
}
引入不同的依赖,比如引用如下的依赖
<dependencies>
<dependency>
<artifactId>MySqlDataBase</artifactId>
<groupId>com.maocai</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
运行查看结果
源码分析
首先来看下ServiceLoader类结构
// 配置文件的路径
private static final String PREFIX = "META-INF/services/";
// 加载的服务:类或者接口
private final Class<S> service;
// 类加载器
private final ClassLoader loader;
// 访问权限的上下文对象
private final AccessControlContext acc;
// 保存已经加载的服务类
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 内部类,真正加载服务类
private LazyIterator lookupIterator;
load()方法创建了一些属性,重要的是实例化内部类,LazyInterator
private ServiceLoader(Class<S> svc, ClassLoader cl) {
// 要加载的接口
service = Objects.requireNonNull(svc, "Service interface cannot be null");
// 类加载器
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// 访问控制器
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
// 先清空
providers.clear();
// 实例化内部类
lookupIterator = new LazyIterator(service, loader);
}
查找实现类的创建实现类的过程,都在LazyIterator完成。当我们调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private boolean hasNextService() {
// 第二次调用的时候,已经解析完成了,直接返回
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// META-INF/services/加上接口的全限定类名,就是文件服务类的文件
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 将文件路径转成URL对象
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 解析URL文件对象,读取内容,最后返回
pending = parse(service, configs.nextElement());
}
// 拿到第一个实现类的类名
nextName = pending.next();
return true;
}
}
创建实例对象,当调用next方法的时候,实际调用到的是lookupInerator.nextService,它通过反射的方式,创建实现类的实例并返回
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
// 类的全限定名
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 创建类的对象
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service, "Provider " + cn + " not a subtype");
}
try {
// 通过newInstance实例化
S p = service.cast(c.newInstance());
// 放入集合,返回实例
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
看到这里,应该已经很清楚了,获取到类的实例,我们自然对它就可以为所欲为了~