Spring的扩展方式
- 自定义xml的方式,现实中的案例 dubbo框架,apollo框架
- 通过实现注解,实现ImportBeanDefinitionRegistrar接口实现. 现实中的案例 JPA,Feign,dubbo
下面我会通过两个小demo的是形式实现上述两种方案.
通过自定义Xml的方式扩展Spring
需要用到的知识点:
- xsd xml schema definition 是xml文件的定义文件.个人觉得可以理解为xml的meta,里面定义了xml的规范,spring在解析xml的时候也会通过这个文件验证xml文件是否有效(可以模仿 dubbo,applo他们的xsd文件)
- spring.handlers,spring.schemas. spring在启动的时候回扫描meta-info目录下面的这个文件,通过handlers文件可以获取解析自定xml的handler 类
- 继承NamesHandlerSupport类
源码:
自定义xml文件,xsd 文件, spring.handlers,spring.schemas文件
xml:
<?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:user="http://www.ivan.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.ivan.com/schema/user http://www.ivan.com/schema/user.xsd">
<user:config username="ivan" password="123456"/>
</beans>
xsd:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns="http://www.ivan.com/schema/user"
targetNamespace="http://www.ivan.com/schema/user">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
<xsd:annotation>
<xsd:documentation>
<![CDATA[ Namespace support for the User ]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="config">
<xsd:annotation>
<xsd:documentation>
<![CDATA[ Apollo configuration section to integrate with Spring.]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="username" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The username is user's name.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="password" type="xsd:int" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The password is user's password.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
spring.handlers
http\://www.ivan.com/schema/user=com.example.learn.CustomizationXmlHandler
spring.schemas
http\://www.ivan.com/schema/user.xsd=META-INF/user.xsd
启动类:
package com.example.learn;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"customization.xml"});
User u = context.getBean(User.class);
System.out.println(u.getUsername());
System.out.println(u.getPassword());
}
}
继承NamesHandlerSupport类:
package com.example.learn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class CustomizationXmlHandler extends NamespaceHandlerSupport {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomizationXmlHandler.class);
@Override
public void init() {
registerBeanDefinitionParser("config", new ConfigParser());
}
}
Paser类:
package com.example.learn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
public class ConfigParser implements BeanDefinitionParser {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigParser.class);
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName(User.class.getName());
String username = element.getAttribute("username");
int password = Integer.valueOf(element.getAttribute("password"));
beanDefinition.getPropertyValues().add("username", username);
beanDefinition.getPropertyValues().add("password", password);
parserContext.getRegistry().registerBeanDefinition(User.class.getName(), beanDefinition);
return beanDefinition;
}
}
User:
package com.example.learn;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Setter
@Getter
public class User {
private static final Logger LOGGER = LoggerFactory.getLogger(User.class);
private String username;
private int password;
}
上述是通过xml扩展spring,原理应该是这样当如果要做到像dubbo,apollo这样的还是框架在解析与注册definition还是要复杂许多的.
通过注解和实现ImportBeanDefinitionRegistrar来扩展spring
需要了解的知识点
- 了解ImportBeanDefinitionResgistrar接口,改接口需配合@Configuration,@ImportSelector,@Import三个注解使用.能够动态的注册bean
- ClassPathScanningCandidateComponentProvider 类,扫描我们需要用注册到spring容器的beanDefinition.该类可设置一些条件对扫描类进行过滤
- jdk 反射 Proxy.newInstance()方法
- 注解的一些常识
源码:
启动类:
package com.example.demo;
import com.example.demo.remote.UserService;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@SpringBootApplication
@EnableHttpRemoteCall(scanBasePackage = "com.example.demo.remote")
public class SpringExtentionApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext =
new AnnotationConfigApplicationContext(SpringExtentionApplication.class);
annotationConfigApplicationContext.start();
UserService userService = annotationConfigApplicationContext.getBean(UserService.class);
userService.findUserById("r123");
}
}
userService接口
package com.example.demo.remote;
import com.example.demo.HttpCall;
import com.example.demo.User;
@HttpCall
public interface UserService {
@HttpCall(httpMethod = "GET", url = "lcoalhost:8080/users")
User findUserById(String userId);
}
User 类:
package com.example.demo;
public class User {
}
Registrar类,这个类是关键
package com.example.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Set;
public class HttpRemoteCallRegsitrar implements ImportBeanDefinitionRegistrar, EnvironmentAware,
BeanFactoryAware, BeanClassLoaderAware {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpRemoteCallRegsitrar.class);
private Environment environment;
private BeanFactory beanFactory;
private ClassLoader classLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableHttpRemoteCall.class.getName());
String pacakge = (String) map.get("scanBasePackage");
ClassPathScanningCandidateComponentProvider classScanner =
new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
if (beanDefinition.getMetadata().isInterface()) {
try {
Class<?> target = ClassUtils.forName(
beanDefinition.getMetadata().getClassName(),
classLoader);
return !target.isAnnotation();
} catch (Exception ex) {
LOGGER.error("load class exception:", ex);
}
}
return false;
}
};
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(HttpCall.class);
classScanner.addIncludeFilter(annotationTypeFilter);
Set<BeanDefinition> beanDefinitions = classScanner.findCandidateComponents(pacakge);
for (BeanDefinition b : beanDefinitions) {
try {
Class<?> target = Class.forName(b.getBeanClassName());
Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{target},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Annotation annotation = method.getAnnotation(HttpCall.class);
String httpMethod = ((HttpCall) annotation).httpMethod();
String url = ((HttpCall) annotation).url();
LOGGER.info("Http call start: httpMethod:[{}], url:[{}]", httpMethod, url);
return null;
}
});
((DefaultListableBeanFactory) beanFactory).registerSingleton(b.getBeanClassName(), proxy);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
System.out.println(map.get("scanBasePackage"));
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}
启动注解类
package com.example.demo;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 启用HttpRemoteCall, 需配合configuration注解一起用
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(HttpRemoteCallRegsitrar.class)
public @interface EnableHttpRemoteCall {
String scanBasePackage() default "";
}
注解类:
package com.example.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface HttpCall {
String httpMethod() default "GET";
String url() default "";
}
到此结束,其实流程可以总结为一下几步
- 创建启动注解 在这里可以认为是@EnableHttpCall注解,改注解定义需要扫描的类
- 根据业务逻辑创建实现ImportBeanDefinitionRegistrar接口类
- 在启动注解上import 2步骤中实现类
- 定义元数据注解,在上面的可认为是HttpCall注解,改注解附带了运行时需要的数据如请求方法,请求url