参考MongoRepository,为接口生成bean实现注入

Posted badboyf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了参考MongoRepository,为接口生成bean实现注入相关的知识,希望对你有一定的参考价值。

首先弄个注解,给代码个入口,这个就是mongo的@EnableMongoRepositories了。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(ProxyBeanDefinitionRegistrar.class)
public @interface DefaultProxy {
    String[] packages() default {};
}

还有一个注解,类似mongo的NoRepositoryBean

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface NoProxyBean {

}

上面的ProxyBeanDefinitionRegistrar,就是入口了,在这里注册bean

public class ProxyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    private ResourceLoader resourceLoader;
    private Environment environment;

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
        if (annotationMetadata.getAnnotationAttributes(DefaultProxy.class.getName()) == null) {
            return;
        }

        ClasspathScannerProvider scanner = new ClasspathScannerProvider(new ArrayList<>(), registry);
        scanner.setEnvironment(environment);
        scanner.setResourceLoader(resourceLoader);

        List<BeanDefinition> beanComponentDefinitions = new ArrayList<BeanDefinition>();
        for (String basePackage : getBasePackages(annotationMetadata)) {
            Set<BeanDefinition> candidate = scanner.findCandidateComponents(basePackage);
            beanComponentDefinitions.addAll(new ArrayList<>(candidate));
        }

        for (BeanDefinition beanDefinition : beanComponentDefinitions) {
            RootBeanDefinition bean = new RootBeanDefinition();
            bean.setBeanClassName(ProxyServiceFactoryBean.class.getName());
            bean.setFactoryMethodName(null);
            bean.getConstructorArgumentValues().addIndexedArgumentValue(0, beanDefinition.getBeanClassName());
            registry.registerBeanDefinition(beanDefinition.getBeanClassName(), bean);
        }
    }

    Set<String> getBasePackages(AnnotationMetadata metadata) {
        Set<String> packages = new HashSet<>();
        Map<String, Object> attr = metadata.getAnnotationAttributes(DefaultProxy.class.getName());
        String[] pac = (String[]) attr.get("packages");
        for (String tmp : pac) {
            packages.add(tmp);
        }
        
        return packages;
    }
}

实现代理,是实现一个接口,在继承需要代理的类,spring-data-mongo中,这个类是SimpleMongoRepository,实现的那个接口就是自定义的实现了MongoRepository的接口(如:UserRepository)
我们也得做一个接口,所有需要代理的接口都实现它IProxyService

@NoProxyBean
public interface IProxyService {

    void test();
}

做一个需要被代理的类,不然没法实现代理SimpleService,就是一个最简单的类

public class SimpleService {

}

spring-data-mongo做代理的代码是MongoRepositoryFactoryBean,我们的类似的为

public class ProxyServiceFactoryBean<T extends IProxyService> implements InitializingBean, FactoryBean<T>, BeanClassLoaderAware {
    private ClassLoader classLoader;
    private T repository;
    Class<? extends T> serviceInterface;
    
    public ProxyServiceFactoryBean(Class<? extends T> repositoryInterface) {
        this.serviceInterface = repositoryInterface;
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        initAndReturn();
    }


    @SuppressWarnings("unchecked")
    private T initAndReturn() {
        SimpleService target = new SimpleService();
        ProxyFactory result = new ProxyFactory();
        result.setTarget(target);
        result.setInterfaces(new Class[] { serviceInterface });

        result.addAdvice(new ProxyInterpreter(target, serviceInterface));

        this.repository = (T) result.getProxy(classLoader); 
        return this.repository;
    }
    
    static class ProxyInterpreter implements MethodInterceptor {
        private final Object target;
        private final Class<?> serviceInterface;

        public ProxyInterpreter(Object target, Class<?> serviceInterface) {
            this.target = target;
            this.serviceInterface = serviceInterface;
        }

        public Object invoke(MethodInvocation invocation) throws Throwable {

            Object result = doInvoke(invocation);

            return result;
        }

        private Object doInvoke(MethodInvocation invocation) throws Throwable {
            Method method = invocation.getMethod();
            Object[] arguments = invocation.getArguments();

            System.out.println("invoke " + method.getName() + "(), args=" + arguments + ", target=" + target + ", interface=" + serviceInterface);
            return null;
        }
    }
    
    public T getObject() {
        return (T) initAndReturn();
    }
    public Class<? extends T> getObjectType() {
        return serviceInterface;
    }
    public boolean isSingleton() {
        return true;
    }
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }
}

InitializingBean, FactoryBean实现这两个接口在关键点,InitializingBean是注册bean后做代理,FactoryBean是在spring处理依赖注入时,判断是不是要注入的是一个FactoryBean,如果是FactoryBean会调用getObject()来生成真正需要注入的类型。如果不实现FactoryBean,启动会报错

Description:

Field proxyService in com.fzk.proxy.test.service.ProxyController required a bean of type ‘com.fzk.proxy.test.service.CustomProxyService‘ that could not be found.

Action:

Consider defining a bean of type ‘com.fzk.proxy.test.service.CustomProxyService‘ in your configuration.

别忘了启动类加上注解

@DefaultProxy(packages = { "com.fzk" })

就会发现实现了IProxyService接口(并不需要实现类)可以被@Autowired,调用方法时会输出

            System.out.println("invoke " + method.getName() + "(), args=" + arguments + ", target=" + target + ", interface=" + serviceInterface);

这只是个demo,具体怎么看情况


以上是关于参考MongoRepository,为接口生成bean实现注入的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 MongoRepository 接口更新 mongo db 中的特定字段?

使用spring data mongo 多数据源如何切换 使用注解形式 不影响使用 MongoRepository 接口方案。

解决SpringBoot 整合 MongoDB,继承MongoRepository的Repo接口无法注入异常

解决SpringBoot 整合 MongoDB,继承MongoRepository的Repo接口无法注入异常

解决SpringBoot 整合 MongoDB,继承MongoRepository的Repo接口无法注入异常

Spring MongoRepository,在哪里捕获异常?