SPI、IOC、AOP、动态编译
一、dubbo自己的SPI实现
1、dubbo的SPI位置
举例:dubbo-3.x.x\META-INF\dubbo\internal
2、JDK的SPI位置
举例:spring-core-5.2.20\META-INF\services\
3、spi的设计目标
面向对象的设计里,模块之间是基于接口编程,模块之间不对实现类进行硬编码。
一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。
为了实现在模块装配的时候,不在模块里面写死代码,这就需要一种服务发现机制。
JDK SPI就是提供这样的一个机制。
为某个接口寻找服务实现类的机制。有点类似IOC的思想,就是将装配的控制权移到代码之外。(转移到了代码的配置文件里面)
4、SPI的具体约定
当服务的提供者(provider),提供了一个接口多种实现时,一般会在jar包的META-INF/services/目录下,创建该接口的同名文件。
该文件里面的内容就是该服务接口的具体实现类的名称。
而当外部加载这个模块的时候,就能通过jar包META-INF/services/里的配置文件得到具体的实现类名,并加载实例化,完成模块的装配。
5、JDK SPI例子
(1)接口Command.java
package jdkspi.api;
public interface Command {
public void execute();
}
(2)接口实现类1,StartCommand.java
package jdkspi.api;
public class StartCommand implements Command {
@Override
public void execute() {
System.out.println("start...");
}
}
(3)接口实现类2,ShutdownCommand.java
package jdkspi.api;
public class ShutdownCommand implements Command {
@Override
public void execute() {
System.out.println("shutdown...");
}
}
(4)Main.java
package jdkspi.api;
import java.util.ServiceLoader;
public class Main {
public static void main(String[] args) {
ServiceLoader<Command> serviceLoader = ServiceLoader.load(Command.class);
for (Command command : serviceLoader) {
command.execute();
}
}
}
(5)建立META-INF\services\jdkspi.api.Command
jdkspi.api.StartCommand
jdkspi.api.ShutdownCommand
(6)执行结果
start...
shutdown...
二、为什么dubbo不采用JDK的SPI
1、JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
2、增加了对扩展点IOC和AOP的支持,一个扩展点可以直接setter注入其他扩展点
三、dubbo SPI有哪些约定
1、SPI文件存储路径在,META-INF\dubbo\internal目录下,并且文件名为接口的全路径名,就是=接口的包名+接口名
2、每个SPI文件里面的格式定义为:扩展名=具体的类名,例如
org.apache.dubbo.rpc.Protocol文件内容
filter=org.apache.dubbo.rpc.cluster.filter.ProtocolFilterWrapper
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
registry=org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol
service-discovery-registry=org.apache.dubbo.registry.integration.RegistryProtocol
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
serializationwrapper=org.apache.dubbo.rpc.protocol.ProtocolSerializationWrapper
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
grpc=org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol
tri=org.apache.dubbo.rpc.protocol.tri.TripleProtocol
它是一个key=value的格式,那就可以不用实例化所有实现类,通过key来加载就ok了
四、dubbo SPI的目的和途径
1、目的:获取一个实现类的对象
2、途径:ExtensionLoader.java,在org.apache.dubbo.common.extension包下
ExtensionLoader.getExtension(String name)
3、实现路径
getExtensionLoader(Class<T> type),为该接口new一个ExtensionLoader,然后缓冲起来
getAdaptiveExtension(),获取一个扩展装饰类的对象,这个类有一个规则,如果它没有一个@Adaptive注解,就动态创建一个装饰类,例如Protocol$Adaptive对象。PS:如果注解在类上就是一个装饰类;如果注解在方法上就是一个动态代理类
getExtension(String name),获取一个实现类的对象
五、SPI机制的adpative原理
1、哪些类被@Adaptive注解了
注解在类上面,比如:AdaptiveExtensionInjector.java
@Adaptive
public class AdaptiveExtensionInjector implements ExtensionInjector, Lifecycle {...}
注解在方法上面,比如:Protocol.java
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
2、adaptive注解在类和方法上的区别
(1)注解在类上,代表人工实现编码,即实现了一个装饰类(设计模式中的装饰模式),例如:AdaptiveExtensionInjector
(2)注解在方法上,代表自动生成和编译一个动态的adaptive类,例如:Protocol
3、getAdaptiveExtension()分析
getAdaptiveExtension()//方法的目的是为cachedAdaptiveInstance赋值
-->createAdaptiveExtension()
-->getAdaptiveExtensionClass()
-->getExtensionClasses()//为cachedClasses赋值
-->loadExtensionClasses()
-->loadDirectory(extensionClasses, strategy, type.getName())
-->loadDirectoryInternal(extensionClasses, strategy, type)
-->createAdaptiveExtensionClass()//自动生成和编译一个动态的adaptive类,这个类是一个代理类
-->String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate()//动态生成adaptive类的代码
-->extensionDirector.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension()
-->compiler.compile(type, code, classLoader)
-->postProcessBeforeInitialization(instance, null)
-->injectExtension(instance)//作用:进入IOC的反转控制模式,实现了动态注入
-->postProcessAfterInitialization(instance, null)
-->initExtension(instance)
dubbo的所有对象都是通过ExtensionLoader来获取的
六、dubbo自己的IOC和AOP原理
1、getExtension(String name)分析
getExtension(String name)//get出来的对象是wrapper包装对象
-->getExtension(String name, boolean wrap)
-->getOrCreateHolder(cacheKey)//从缓存cachedInstances里提取对象,没有则创建
-->createExtension(name, wrap)
-->getExtensionClasses().get(name)//从SPI的配置文件里面,把所有的文件读出来,然后缓存起来
-->postProcessBeforeInitialization(instance, name)
-->injectExtension(instance)//dubbo的IOC反转控制,就是从spi和spring里面提取对象赋值
-->把这个instance对象里的所有方法提取出来
-->injector.getInstance(pt, property)
-->extensionAccessor.getExtensionLoader(type)
-->loader.getAdaptiveExtension()
-->postProcessAfterInitialization(instance, name)
-->injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))//获取wrapperClass类的构造函数,把instance赋值进去。先AOP在IOC
-->postProcessAfterInitialization(instance, name)
七、dubbo的动态编译
1、代码
org.apache.dubbo.common.compiler.Compiler compiler = extensionDirector.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
compiler.compile(type, code, classLoader)
获取一个ExtensionLoader,然后获取一个装饰类
2、什么是Javassist
Javassist是一款字节码编辑工具,同时也是一个动态类库,它可以直接检查、修改以及创建Java类
八、dubbo如何和spring融合
1、以provider为例
ProviderApplication.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.springboot.demo.provider;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
DemoServiceImpl.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.springboot.demo.provider;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.springboot.demo.DemoService;
@DubboService
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "Hello " + name;
}
}
application.yml
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
dubbo:
application:
name: dubbo-springboot-demo-provider
protocol:
name: dubbo
port: -1
registry:
address: zookeeper://${zookeeper.address:127.0.0.1}:2181
2、dubbo如何采用spring的schema
完成一个spring的自定义配置一般需要以下5个步骤:
(1)设计配置属性和JavaBean
(2)编写XSD文件,全称就是XML Schema,它就是校验XML,定义了一系列的语法来规范XML
(3)编写NamespaceHandler和BeanDefinitionParser完成解析工作
(4)编写spring.handlers和spring.schemas串联起所有部件
(5)在Bean文件中应用
参考资料:
Dubbo的spi机制分析和实战案例_田哥coder的博客-CSDN博客
06-Dubbo的SPI扩展机制之普通扩展对象的创建与Wrapper机制的源码解析 | Apache Dubbo