天天看點

SpringBoot 基于注解實作接口的代理Bean注入

SpringBoot 基于注解實作接口的代理Bean注入

在springboot加載時需自己手動将接口的代理bean注入到spring容器中,這樣在service層注入該接口類型即可,

1.在SpringBoot啟動類上添加EnableProxyBeanScan注解

  

SpringBoot 基于注解實作接口的代理Bean注入

EnableProxyBeanScan為自定義注解,通過Import注解掃描被ProxyBean注解的類或者被ProxyBean修飾的注解注解的類("注解繼承")

ProxyBeanDefinitionRegistrar實作ImportBeanDefinitionRegistrar 通過ProxyInterfaceBeanBeanDefinitionScanner 來進行bean的加載

ProxyFactoryBean為bean的工廠類,提供代理bean

ProxyHandler為代理業務邏輯接口,提供三個參數: Class(被代理的類),Method(被代理的方法),Object[] 入參參數

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(EnableProxyBeanScan.ProxyBeanDefinitionRegistrar.class)
public @interface EnableProxyBeanScan {

    String[] basePackages() default {};

    class ProxyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            ProxyInterfaceBeanBeanDefinitionScanner scanner = new ProxyInterfaceBeanBeanDefinitionScanner(registry);
            scanner.scan(getBasePackages(importingClassMetadata));
        }

        private String[] getBasePackages(AnnotationMetadata importingClassMetadata){
            Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableProxyBeanScan.class.getCanonicalName());
            Set<String> basePackages = new HashSet();
            String[] basePackagesArr = (String[])((String[])attributes.get("basePackages"));
            for(String item: basePackagesArr){
                if(StringUtils.hasText(item))
                    basePackages.add(item);
            }

            if (basePackages.isEmpty()) {
                basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
            }

            return basePackages.toArray(new String[basePackages.size()]);
        }
    }
}      
public class ProxyInterfaceBeanBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

    public ProxyInterfaceBeanBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        //registry是Spring的Bean注冊中心
        // false表示不使用ClassPathBeanDefinitionScanner預設的TypeFilter
        // 預設的TypeFilter隻會掃描帶有@Service,@Controller,@Repository,@Component注解的類
        super(registry,false);
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        addIncludeFilter(new AnnotationTypeFilter(ProxyBean.class));
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        if (beanDefinitionHolders.isEmpty()){
            System.err.println("No Interface Found!");
        }else{
            //建立代理對象
            createBeanDefinition(beanDefinitionHolders);
        }
        return beanDefinitionHolders;
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return metadata.isInterface() || metadata.isAbstract();
    }



    /**
     * 為掃描到的接口建立代理對象
     * @param beanDefinitionHolders
     */
    private void createBeanDefinition(Set<BeanDefinitionHolder> beanDefinitionHolders) {
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            GenericBeanDefinition beanDefinition = ((GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition());
            //将bean的真實類型改變為FactoryBean
            beanDefinition.getConstructorArgumentValues().
                    addGenericArgumentValue(beanDefinition.getBeanClassName());
            beanDefinition.setBeanClass(ProxyFactoryBean.class);
            beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        }
    }
}      
@Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ProxyBean {

    Class<? extends ProxyHandler> value();
}      
public interface ProxyHandler{

    Object execute(Class<?> proxyType, Method proxyMethod, Object[] args);
}      
public class ProxyFactoryBean<T> implements FactoryBean {

    private static final Map<Class<? extends ProxyHandler>,ProxyHandler> ProxyHandlers = new ConcurrentHashMap<>();

    private Class<T> interfaceClass;
    private Class<? extends ProxyHandler> proxyHandlerType;

    public ProxyFactoryBean(Class<T> interfaceClass) {
        this.interfaceClass = interfaceClass;
        this.proxyHandlerType = AnnotationUtils.findAnnotation(interfaceClass, ProxyBean.class).value();
        if(!ProxyFactoryBean.ProxyHandlers.containsKey(proxyHandlerType)) {
            ProxyHandler proxyHandler = ClassUtils.newInstance(proxyHandlerType);
            SpringBean.inject(proxyHandler);
            ProxyFactoryBean.ProxyHandlers.put(proxyHandlerType, proxyHandler);
        }
    }

    @Override
    public T getObject() throws Exception {
        final ProxyHandler proxyHandler = ProxyFactoryBean.ProxyHandlers.get(proxyHandlerType);
        return (T) Proxy.newProxyInstance(
                interfaceClass.getClassLoader(),
                new Class[]{interfaceClass},
                (proxy,method,args) ->   proxyHandler.execute(interfaceClass,method,args)
        );
    }

    @Override
    public Class<T> getObjectType() {
        return interfaceClass;
    }
}      

簡單的例子:

  類似spring-feign的接口發送Http請求

1.先定義一個注解HttpClient,和HttpClientProxyHandler

@Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ProxyBean(HttpClient.HttpClientProxyHandler.class)
public @interface HttpClient {

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface Request{
        String url();
        RequestMethod method() default RequestMethod.POST;
    }
    /**簡單定義下進行測試,實際實作肯定要比這個複雜*/
    class HttpClientProxyHandler implements ProxyHandler {
        /**這個類雖然沒有被Spring管理,不過通過這個注解可以實作SpringBean的注入和使用,
         * 見ProxyFactoryBean構造方法的代碼  
         *  SpringBean.inject(proxyHandler);
         */
        @Autowired      
        private RestTemplate template;
        @Override
        public Object execute(Class<?> proxyType, Method proxyMethod, Object[] args) {
           return template.postForObject(
                   proxyMethod.getAnnotation(Request.class).url()
                   ,args[0]
                   ,proxyMethod.getReturnType()
           );
        }
    }
}      

2.被代理的接口

@HttpClient
public interface LoginService {

    @HttpClient.Request(url="ddd")
    String getUserAge(ExamineReqDto username);
}      

3.測試,

SpringBoot 基于注解實作接口的代理Bean注入

 測試這裡沒有細緻的測,RestTemplate這裡是成功拿到了,不影響後續的使用

最後,附Bean注入的代碼:

@Component
public class SpringBean implements ApplicationContextAware {

    private static final Logger log = LoggerFactory.getLogger(SpringBean.class);


    private  static ApplicationContext applicationContext;

    private SpringBean(){}

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringBean.applicationContext = applicationContext;
    }

    public static <T> T getSpringBean(Class<T> clazz){
        return SpringBean.applicationContext.getBean(clazz);
    }
    @SuppressWarnings("unchecked")
    public static <T> T getSpringBean(String beanName){
        return (T) SpringBean.applicationContext.getBean(beanName);
    }

    public static void inject(Object object){
        if(object == null)
            return;
        Class clazz = object.getClass();
        while (clazz != Object.class) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                Autowired annotation = field.getAnnotation(Autowired.class);
                if (annotation != null) {
                    Reflector.setFieldValue(object,field,SpringBean.getSpringBean(field.getType()));
                }

                Resource resource = field.getAnnotation(Resource.class);
                if (resource != null) {
                    Reflector.setFieldValue(object,field,SpringBean.getSpringBean(field.getName()));
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

}