扩展Spring的两种方式

 

 

Spring的扩展方式

  1. 自定义xml的方式,现实中的案例 dubbo框架,apollo框架
  2. 通过实现注解,实现ImportBeanDefinitionRegistrar接口实现. 现实中的案例 JPA,Feign,dubbo

下面我会通过两个小demo的是形式实现上述两种方案.

通过自定义Xml的方式扩展Spring

需要用到的知识点:

  1. xsd  xml schema definition 是xml文件的定义文件.个人觉得可以理解为xml的meta,里面定义了xml的规范,spring在解析xml的时候也会通过这个文件验证xml文件是否有效(可以模仿 dubbo,applo他们的xsd文件)
  2. spring.handlers,spring.schemas. spring在启动的时候回扫描meta-info目录下面的这个文件,通过handlers文件可以获取解析自定xml的handler 类
  3. 继承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

需要了解的知识点

  1. 了解ImportBeanDefinitionResgistrar接口,改接口需配合@Configuration,@ImportSelector,@Import三个注解使用.能够动态的注册bean
  2. ClassPathScanningCandidateComponentProvider 类,扫描我们需要用注册到spring容器的beanDefinition.该类可设置一些条件对扫描类进行过滤
  3. jdk 反射 Proxy.newInstance()方法
  4. 注解的一些常识

源码:

启动类:

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 "";

}

到此结束,其实流程可以总结为一下几步

  1. 创建启动注解 在这里可以认为是@EnableHttpCall注解,改注解定义需要扫描的类
  2. 根据业务逻辑创建实现ImportBeanDefinitionRegistrar接口类
  3. 在启动注解上import 2步骤中实现类
  4. 定义元数据注解,在上面的可认为是HttpCall注解,改注解附带了运行时需要的数据如请求方法,请求url
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值