1. 程式人生 > 其它 >FactoryBean 實現介面代理

FactoryBean 實現介面代理

FactoryBean是一個介面,當在IOC容器中的Bean實現了FactoryBean後,通過getBean(String BeanName)獲取到的Bean物件並不是FactoryBean的實現類物件,而是這個實現類中的getObject()方法返回的物件。

一、自定義 FactoryBean 重寫 getObject方法 (通過動態代理生成對應介面的實現類)

/**
 * 介面例項工廠,這裡主要是用於提供介面的例項物件
 * @author lichuang
 * @param <T>
 */
public class ServiceFactory<T> implements
FactoryBean<T> { private Class<T> interfaceType; public ServiceFactory(Class<T> interfaceType) { this.interfaceType = interfaceType; } @Override public T getObject() throws Exception { //這裡主要是建立介面對應的例項,便於注入到spring容器中 //ServiceProxy 為代理實現 具體見 jdk動態代理
InvocationHandler handler = new ServiceProxy<>(interfaceType); return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class[] {interfaceType},handler); } @Override public Class<T> getObjectType() { return interfaceType;
} @Override public boolean isSingleton() { return true; } }

二、呼叫invoke 方法生成代理類(具體見jdk動態代理寫法)

/**
 * 動態代理,需要注意的是,這裡用到的是JDK自帶的動態代理,代理物件只能是介面,不能是類
 * @author lichuang
 */
 
public class ServiceProxy<T> implements InvocationHandler {
 
    private Class<T> interfaceType;
 
    public ServiceProxy(Class<T> intefaceType) {
        this.interfaceType = interfaceType;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this,args);
        }
        Annotation[] annotations1=  method.getDeclaredAnnotations();
        for (Annotation annotation : annotations1) {
            System.out.println("方法註解:"+annotation.toString());
        }
        Annotation[][] annotations= method.getParameterAnnotations();
        for (int i = 0; i < annotations.length; i++) {
            System.out.println("-------------------------"+args[i].toString());
            for (Annotation annotation1 : annotations[i]) {
                System.out.println("引數註解:"+ annotation1.toString());
            }
        }
        System.out.println("呼叫前,引數:{}" + Arrays.toString(args));
        //這裡可以得到引數陣列和方法等,可以通過反射,註解等,進行結果集的處理
        //mybatis就是在這裡獲取引數和相關注解,然後根據返回值型別,進行結果集的轉換
        Object result ="77777777777777777777777777777777";
        System.out.println("呼叫後,結果:{}" + result);
        return result;
    }
}

動態代理時也可以拿到方法的註解和引數的註解,通過這些註解可以做一些業務方面的問題。

三、注入BeanDefinition

/**
 * 用於Spring動態注入自定義介面
 *
 * @author lichuang
 */
@Component
public class ServiceBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, ApplicationContextAware {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //這裡一般我們是通過反射獲取需要代理的介面的clazz列表
        //比如判斷包下面的類,或者通過某註解標註的類等等
		//1、掃描要被實現的介面
        Set<Class<?>> beanClazzs = scannerPackages("com.example.yuanmayuedu.service");
        for (Class beanClazz : beanClazzs) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
            GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();

            //在這裡,我們可以給該物件的屬性注入對應的例項。
            //比如mybatis,就在這裡注入了dataSource和sqlSessionFactory,
            // 注意,如果採用definition.getPropertyValues()方式的話,
            // 類似definition.getPropertyValues().add("interfaceType", beanClazz);
            // 則要求在FactoryBean(本應用中即ServiceFactory)提供setter方法,否則會注入失敗
            // 如果採用definition.getConstructorArgumentValues(),
            // 則FactoryBean中需要提供包含該屬性的構造方法,否則會注入失敗
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);

            //注意,這裡的BeanClass是生成Bean例項的工廠,不是Bean本身。
            // FactoryBean是一種特殊的Bean,其返回的物件不是指定類的一個例項,
            // 其返回的是該工廠Bean的getObject方法所返回的物件。
            //2、在bean定義裡面設定 FactoryBean 即上述 ServiceFactory(spring 會根據 getObject 方法生成代理類bean)
            definition.setBeanClass(ServiceFactory.class);

            //這裡採用的是byType方式注入,類似的還有byName等
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);
            //3、將bean 定義 注入到 註冊器裡
            registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
        }
    }

四、這個是將要被代理的介面(在 com.example.yuanmayuedu.service 包下)

public interface CalculateService {
    @Addressing
    String getResult(@Deprecated String name, @Deprecated @WebParam String a);
}

五、啟動spring 測試

@SpringBootApplication
public class YuanmayueduApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context= SpringApplication.run(YuanmayueduApplication.class, args);
        CalculateService calculateService=  context.getBean(CalculateService.class);
        calculateService.getResult("張三","王五");

    }

}

六、測試結果

在這裡插入圖片描述

還沒有讀過 Fegin 和mybatis 的原始碼,不過本人猜想,實現思路應該差不多。通過此方法也可以自己實現遠端呼叫等工具。。。。