FactoryBean 實現介面代理
阿新 • • 發佈:2020-12-19
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 的原始碼,不過本人猜想,實現思路應該差不多。通過此方法也可以自己實現遠端呼叫等工具。。。。