SpringBoot 基於註解實現介面的代理Bean注入
阿新 • • 發佈:2021-11-03
SpringBoot 基於註解實現介面的代理Bean注入
在springboot載入時需自己手動將介面的代理bean注入到spring容器中,這樣在service層注入該介面型別即可,
1.在SpringBoot啟動類上新增EnableProxyBeanScan註解
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.測試,
測試這裡沒有細緻的測,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();
}
}
}