前言
傻逼guide一笔带过,我自己学得自己总结
这里讲的主要是通过注解注册/消费服务
然后有的springboot的知识呢,学八股的时候再学习
自定义注解
使用注解开发,而不是xml配置文件,好处是便捷性,操作性。
比如@Builder主要作用是用来生成对象,并能够进行链式赋值。
所以我们这里定义了两个注解
在annotation包里
@RpcService放在服务实现类上。
@RpcReference放在服务引用上。
服务端发送的数据类型是:RpcMessage<RpcReponse
@RpcService
注册服务
/**
* RPC service annotation, marked on the service implementation class
*
* @author shuang.kou
* @createTime 2020年07月21日 13:11:00
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface RpcService {
/**
* Service version, default value is empty string
*/
String version() default "";
/**
* Service group, default value is empty string
*/
String group() default "";
}
@Documented:该注解表示带有该类型的注解应该由javadoc和类似工具记录。换句话说,这个注解应该包含在注释元素的文档中。
@Retention(RetentionPolicy.RUNTIME):这表示该注解应在运行时保留。如果要使用反射在运行时访问注解,则这是必需的。
@Target({ElementType.TYPE}):此注解仅允许在类声明上使用。
@Inherited:这表示该注解类型可以从超类继承。
@RpcReference
消费服务
- @RpcReference(version = “version1”, group = “test1”) private HelloService helloService; 该注解标记到成员变量上,然后对整个类使用@Component来注入到Spring中
- 实现BeanPostProcessor接口,实现里面的postProcessAfterInitialization方法,在Bean被初始化之后处理服务引用,生成代理对象
- 遍历Bean的成员变量,如果有@RpcReference则通过注解值获取RpcServiceConfig,然后传入RpcClient和RpcServiceConfig初始化RpcClientProxy。
- 通过代理类获取接口的代理对象,使用反射将Controller里的对象替换为我们的代理对象,再调用方法时,会调用到我们的invoke函数里,来实现发送request收到response的目的。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Inherited
public @interface RpcReference {
/**
* Service version, default value is empty string
*/
String version() default "";
/**
* Service group, default value is empty string
*/
String group() default "";
}
@RpcScan
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Import(CustomScannerRegistrar.class)
@Documented
public @interface RpcScan {
String[] basePackage();//指定要扫描的包路径,对应CustomScannerRegistrar里的BASE_PACKAGE_ATTRIBUTE_NAME
}
通过@Import注解导入了CustomScannerRegistrar.class,这可能是一个配置类,用于处理自定义的注解扫描逻辑。
一些基本概念
我他妈发现我一些基本概念都不懂啊
注册Bean
“注册bean”通常是指将一个Java对象(被称为bean)注册到Spring容器中,以便由Spring进行管理。Spring容器是一个负责创建、配置和管理bean的容器。通过注册bean,我们告诉Spring框架如何实例化、配置和管理这些对象。
注册了之后能干什么?
- 实例化和管理: Spring负责实例化bean,并在需要时管理其生命周期。它会根据配置或注解创建bean的实例,并在需要时销毁它们。
- 依赖注入: Spring负责将bean之间的依赖关系注入到它们中。通过配置或注解,可以将其他bean引用注入到一个bean中,实现了松散耦合。
- AOP(面向切面编程): Spring提供了强大的AOP支持,允许在应用程序中方便地使用切面来处理横切关注点,例如事务管理、日志记录等。
- 声明性事务管理: 通过配置或注解,可以声明性地管理事务,而无需在代码中编写显式的事务管理代码。
- Spring Boot自动配置: 在Spring Boot应用程序中,注册的bean可以利用自动配置机制,使得开发者能够更轻松地构建和部署应用程序。
- 组件扫描和自动装配: Spring容器可以扫描指定的包,自动发现标有特定注解的组件,并将它们自动装配到应用程序中。
- 事件和监听器: Spring框架提供了事件和监听器机制,通过它们,bean可以发布和监听事件,实现模块之间的松散耦合通信。
- 面向切面编程(AOP): Spring允许通过AOP在应用程序中定义横切关注点,例如日志、事务管理等。
Bean的实例化
简单来说就是创建一个对象
复杂来说就是
- Bean 的定义: 在 Spring 配置文件(如 XML 文件)、Java 类上的注解或者 Java 代码中定义 Bean。这个定义包含了 Bean 的类型、属性、依赖关系等信息。
- Spring 容器加载配置: Spring 容器读取配置文件或者扫描注解,解析配置信息,将 Bean 的定义信息加载到容器中。
- Bean 的实例化: 根据 Bean 的定义信息,Spring 容器使用适当的方式(通常是反射)实例化 Bean 对象。
- 属性注入: 如果 Bean 的定义中包含属性信息,Spring 容器会将配置的属性值注入到 Bean 的对应属性中。
- 初始化方法: 如果 Bean 的定义中有指定初始化方法,Spring 容器在实例化完成后调用该方法进行初始化。
- Bean 的使用: 完成初始化后,Bean 就可以被其他对象引用和使用了。
- 销毁方法(可选): 如果 Bean 的定义中有指定销毁方法,当容器关闭时,会调用该方法进行资源释放和清理。
Spring启动时注解执行顺序
- 加载配置类: 在启动时,Spring 会加载配置类,这包括通过
@Configuration标记的配置类、通过@ComponentScan扫描的组件、通过@Import导入的其他配置类等。- 解析注解: 在加载配置类的过程中,Spring 会解析各种注解,包括
@Bean、@Component、@Autowired等。在这个过程中,如果遇到了@Import注解,就会触发被导入配置类的解析和注册。- 初始化 ApplicationContext: 随后,Spring 会初始化 ApplicationContext(应用上下文)。在这一阶段,它会实例化 bean,完成依赖注入,执行生命周期方法,以及执行其他与 bean 相关的初始化工作。
- 执行自定义逻辑: 在初始化 ApplicationContext 过程中,如果遇到实现了
ImportBeanDefinitionRegistrar接口的类,Spring 会调用这些类的registerBeanDefinitions方法,执行自定义的逻辑,比如注册额外的 bean。
元数据
在这里,元数据指的是有关代码元素(如类、方法、字段等)的信息。Java提供了AnnotationMetadata接口,该接口用于获取与注解相关的元数据信息,就是用来获取注解里面的内容的
CustomScanner
一个自定义扫描器,用于扫描指定包下的类,并根据给定的注解类型进行过滤。
public class CustomScanner extends ClassPathBeanDefinitionScanner {
public CustomScanner(BeanDefinitionRegistry registry, Class<? extends Annotation> annoType) {
super(registry);
super.addIncludeFilter(new AnnotationTypeFilter(annoType));
}
@Override
public int scan(String... basePackages) {
return super.scan(basePackages);
}
}
构造函数
BeanDefinitionRegistry是Spring框架中用于注册bean定义的接口。Class<? extends Annotation>是指定的注解类型。
在构造函数中,调用了super(registry)来调用父类的构造函数,并使用super.addIncludeFilter(new AnnotationTypeFilter(annoType))添加了一个包含过滤器,该过滤器根据给定的注解类型过滤扫描结果。也就是说保留含有特定注解的类
scan方法
它调用了父类的scan方法,实际上触发了扫描过程。传入的basePackages参数是要扫描的包路径。
所以这个类的主要作用就是,扫描带有特定注解的类,并且将他们注册为bean
CustomScannerRegistrar
真正用来扫描包并进行注册的类
@Slf4j
public class CustomScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private static final String SPRING_BEAN_BASE_PACKAGE = "github.javaguide";
private static final String BASE_PACKAGE_ATTRIBUTE_NAME = "basePackage";
private ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
//get the attributes and values of RpcScan annotation
AnnotationAttributes rpcScanAnnotationAttributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(RpcScan.class.getName()));
String[] rpcScanBasePackages = new String[0];
if (rpcScanAnnotationAttributes != null) {
// get the value of the basePackage property
rpcScanBasePackages = rpcScanAnnotationAttributes.getStringArray(BASE_PACKAGE_ATTRIBUTE_NAME);
}
if (rpcScanBasePackages.length == 0) {
rpcScanBasePackages = new String[]{((StandardAnnotationMetadata) annotationMetadata).getIntrospectedClass().getPackage().getName()};
}
// Scan the RpcService annotation
CustomScanner rpcServiceScanner = new CustomScanner(beanDefinitionRegistry, RpcService.class);
// Scan the Component annotation
CustomScanner springBeanScanner = new CustomScanner(beanDefinitionRegistry, Component.class);
if (resourceLoader != null) {
rpcServiceScanner.setResourceLoader(resourceLoader);
springBeanScanner.setResourceLoader(resourceLoader);
}
int springBeanAmount = springBeanScanner.scan(SPRING_BEAN_BASE_PACKAGE);
log.info("springBeanScanner扫描的数量 [{}]", springBeanAmount);
int rpcServiceCount = rpcServiceScanner.scan(rpcScanBasePackages);
log.info("rpcServiceScanner扫描的数量 [{}]", rpcServiceCount);
}
}
字段和属性
private static final String SPRING_BEAN_BASE_PACKAGE = "github.javaguide";// Spring Bean 默认扫描的基础包路径。
private static final String BASE_PACKAGE_ATTRIBUTE_NAME = "basePackage";//定义了 @RpcScan 注解中指定扫描包路径的属性名称。
private ResourceLoader resourceLoader;//加载资源的接口
registerBeanDefinitions
在 Spring 启动过程中,当解析到配置类中的 @Import(CustomScannerRegistrar.class) 注解时,Spring 会触发 CustomScannerRegistrar 类的加载和初始化。在初始化过程中,会调用 registerBeanDefinitions 方法,执行自定义的注册逻辑。这个过程是 Spring 在启动时自动完成的,不需要手动调用。
参数
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry)
AnnotationMetadata annotationMetadata: 通过这个参数获取自定义注解 @RpcScan 的元数据,以便后续获取注解属性值。
获取 @RpcScan 注解的属性和值。
AnnotationAttributes rpcScanAnnotationAttributes =AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(RpcScan.class.getName()));
RpcScan.class.getName()获取了@RpcScan注解的全限定类名。(就是包名加上类名)annotationMetadata.getAnnotationAttributes(...)返回一个Map<String, Object>,其中包含了注解属性和对应的值。在这个情况下,map里就是String[] basePackage();和对应的值通过
AnnotationAttributes.fromMap(...)将Map<String, Object>转换为AnnotationAttributes对象,类似一个map,使得可以更方便地访问和操作注解的属性。
获取扫描包路径数组
String[] rpcScanBasePackages = new String[0];
if (rpcScanAnnotationAttributes != null) {
// get the value of the basePackage property
rpcScanBasePackages = rpcScanAnnotationAttributes.getStringArray(BASE_PACKAGE_ATTRIBUTE_NAME);
}
//如果没有指定路径
if (rpcScanBasePackages.length == 0) {
rpcScanBasePackages = new String[]{((StandardAnnotationMetadata) annotationMetadata).getIntrospectedClass().getPackage().getName()};
}
解释一下第二个if:
如果没有指定路径,那么就通过反射,讲扫描包路径设置为当前被扫描的类所在的包
getIntrospectedClass() 是 StandardAnnotationMetadata 类提供的方法之一,用于获取被 introspected(内省、审查)的类的 Class 对象。
设置扫描器
// Scan the RpcService annotation
CustomScanner rpcServiceScanner = new CustomScanner(beanDefinitionRegistry, RpcService.class);
// Scan the Component annotation
CustomScanner springBeanScanner = new CustomScanner(beanDefinitionRegistry, Component.class);
为什么不扫描RpcReference?
RpcReference里面是@Target({ElementType.FIELD})
RpcReference是标记在字段上的,本身的类被@Componet标记,所以也含在里面了
因为你的
资源加载器设置到 CustomScanner 实例中
if (resourceLoader != null) {
rpcServiceScanner.setResourceLoader(resourceLoader);
springBeanScanner.setResourceLoader(resourceLoader);
}
扫描并注册
int springBeanAmount = springBeanScanner.scan(SPRING_BEAN_BASE_PACKAGE);
log.info("springBeanScanner扫描的数量 [{}]", springBeanAmount);
int rpcServiceCount = rpcServiceScanner.scan(rpcScanBasePackages);
log.info("rpcServiceScanner扫描的数量 [{}]", rpcServiceCount);
- 使用
springBeanScanner扫描 Spring 组件(带有@Component注解的类)的数量,并记录日志。 - 使用
rpcServiceScanner扫描 RPC 服务(带有@RpcService注解的类)的数量,并记录日志。
小结
CustomScanner 和 CustomScannerRegistrar 的组合起到了扫描指定注解的作用,并将扫描到的类注册为 Spring Bean。
CustomScanner :主要功能就是调用父类进行扫描的实际操作
CustomScannerRegistrar:主要功能就是 实现了 ImportBeanDefinitionRegistrar 接口,重写了registerBeanDefinitions函数,来定义要扫描哪些包,扫描有哪些注解的类
所以逻辑是这样的
- 当开始时,因为我们@Import了
@Import(CustomScannerRegistrar.class),所以此时就会触发CustomScannerRegistrar的加载和初始化 - 这个类初始化的过程中
registerBeanDefinitions又是被自动调用 registerBeanDefinitions方法里面,扫描@Componet注解和@RpcService注解的类,并且注册为bean- 那么注册前后就会有
postProcessBeforeInitialization()方法和postProcessAfterInitialization()的执行了
SpringBeanPostProcessor
傻逼guide说的原理:
我们实现需要 BeanPostProcessor 接口并重写 postProcessBeforeInitialization()方法和 postProcessAfterInitialization() 方法。
Spring bean 在实例化之前会调用 postProcessBeforeInitialization()方法,在 Spring bean 实例化之后会调用 postProcessAfterInitialization() 方法。
被我们使用 RpcService和RpcReference 注解的类都算是 Spring Bean。
●我们可以在postProcessBeforeInitialization()方法中去判断类上是否有RpcService 注解。如果有的话,就取出 group 和 version 的值。然后,再调用 ServiceProvider 的 publishService() 方法发布服务即可!
●我们可以在 postProcessAfterInitialization() 方法中遍历类的属性上是否有 RpcReference 注解。如果有的话,我们就通过反射将这个属性赋值即可!
静态变量和构造函数
private final ServiceProvider serviceProvider;
private final RpcRequestTransport rpcClient;
public SpringBeanPostProcessor() {
this.serviceProvider = SingletonFactory.getInstance(ZkServiceProviderImpl.class);
this.rpcClient = ExtensionLoader.getExtensionLoader(RpcRequestTransport.class).getExtension(RpcRequestTransportEnum.NETTY.getName());
}
serviceProvider在provider包内,见对应文章
serviceProvider: 这是一个接口,用于提供服务注册的功能。通过 SingletonFactory.getInstance(ZkServiceProviderImpl.class) 来获取 ZkServiceProviderImpl 的单例实例,这是服务注册的一种具体实现。
rpcClient: 这是一个接口,用于处理远程过程调用(RPC)。通过 ExtensionLoader.getExtensionLoader(RpcRequestTransport.class).getExtension(RpcRequestTransportEnum.NETTY.getName()) 来获取 RpcRequestTransport 接口的具体实现,其中 RpcRequestTransportEnum.NETTY.getName() 表示获取 NETTY 实现,这是 RPC 请求的传输方式。
postProcessBeforeInitialization
在 Bean 初始化之前检查是否有 @RpcService 注解,如果有,则进行服务注册操作。
@SneakyThrows
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean.getClass().isAnnotationPresent(RpcService.class)) {
log.info("[{}] is annotated with [{}]", bean.getClass().getName(), RpcService.class.getCanonicalName());
// get RpcService annotation
RpcService rpcService = bean.getClass().getAnnotation(RpcService.class);
// build RpcServiceProperties
RpcServiceConfig rpcServiceConfig = RpcServiceConfig.builder()
.group(rpcService.group())
.version(rpcService.version())
.service(bean).build();
serviceProvider.publishService(rpcServiceConfig);
}
return bean;
}
@SneakyThrows: Lombok 注解,用于在方法中抛出受检异常时不需要显式捕获。
@Override: 表示该方法是对父类或接口中同名方法的重写。
postProcessBeforeInitialization: 这是 BeanPostProcessor 接口定义的方法之一,在 Bean 初始化之前被调用。Object bean: 表示正在被初始化的 Bean 实例。
String beanName: 表示正在被初始化的 Bean 的名称。
bean.getClass().isAnnotationPresent(RpcService.class): 检查当前 Bean 类是否被 @RpcService 注解标记。
如果被注解标记:
- 通过
bean.getClass().getAnnotation(RpcService.class)获取@RpcService注解的实例。
- 使用获取到的注解信息构建
RpcServiceConfig对象。 - 调用
serviceProvider.publishService(rpcServiceConfig)注册服务。这个里面最终是用Curator实现
postProcessAfterInitialization
在 Bean 初始化之后检查是否有 @RpcReference 注解,如果有,则创建相应的代理对象,并将其注入到对应的字段中。这是实现 RPC 客户端代理的关键逻辑。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> targetClass = bean.getClass();
Field[] declaredFields = targetClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
RpcReference rpcReference = declaredField.getAnnotation(RpcReference.class);
if (rpcReference != null) {
RpcServiceConfig rpcServiceConfig = RpcServiceConfig.builder()
.group(rpcReference.group())
.version(rpcReference.version()).build();
RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcClient, rpcServiceConfig);
Object clientProxy = rpcClientProxy.getProxy(declaredField.getType());
declaredField.setAccessible(true);
try {
declaredField.set(bean, clientProxy);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return bean;
}
Class<?> targetClass = bean.getClass(): 获取当前 Bean 的类对象。
Field[] declaredFields = targetClass.getDeclaredFields(): 获取当前
for (Field declaredField : declaredFields): 遍历所有声明字段。
RpcReference rpcReference = declaredField.getAnnotation(RpcReference.class): 获取字段上的 @RpcReference 注解。
if (rpcReference != null): 如果字段上有 @RpcReference 注解,执行以下操作:
构建 RpcServiceConfig 对象,用于配置 RPC 服务的相关信息。
创建 RpcClientProxy 实例,用于生成动态代理对象。
通过 rpcClientProxy.getProxy(declaredField.getType()) 获取代理对象。代理的部分见动态代理博客
declaredField.setAccessible(true);将字段设置为可访问
为什么?
绕过Java的访问修饰符,以便在运行时通过反射访问和修改字段的值。
declaredField.set(bean, clientProxy);
将创建的代理对象 clientProxy 设置为字段 declaredField 所在的对象 bean 的值,也就是把bean里面的代理类设置成我们新建的这个proxy类
如果类型不匹配就抛出异常
小结
SpringBeanPostProcessor 类上使用了 @Component 注解,这意味着它会被 Spring 扫描并注册为一个 Spring Bean。由于它实现了 BeanPostProcessor 接口,Spring 在初始化每个 Bean 的过程中都会调用 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法。
当一个类被标记为 @Component 时,Spring 会将其纳入到 IoC 容器的管理中,自动进行实例化并进行依赖注入。在这个过程中,如果该类同时实现了 BeanPostProcessor 接口,Spring 就会注意到它,并在适当的时机调用其 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法。
因此,@Component 注解是触发 Spring 自动执行 BeanPostProcessor 方法的一种方式。