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 的源码,不过本人猜想,实现思路应该差不多。通过此方法也可以自己实现远程调用等工具。。。。