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,Object proxy, 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,proxy,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,Object proxy, 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();
}
}
}
補全Http請求代理接口
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @ProxyCustomizer(HttpClientProxyHandler.class) public @interface HttpClient { }
import com.sinosoft.demo.componment.proxy.core.ProxyHandler;
import javafx.util.Builder;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;
import java.util.*;
import static org.springframework.objenesis.instantiator.util.ClassUtils.newInstance;
public class HttpClientProxyHandler implements ProxyHandler {
@Override
public Object execute(Class<?> proxyType, Object proxy ,Method proxyMethod, Object[] args) {
if(proxyMethod.isDefault()){ //不對default 的方法進行代理
try {
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
constructor.setAccessible(true);
Class<?> declaringClass = proxyMethod.getDeclaringClass();
int allModes = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE;
return constructor.newInstance(declaringClass, allModes)
.unreflectSpecial(proxyMethod, declaringClass)
.bindTo(proxy)
.invokeWithArguments(args);
}catch(Exception ex) {
throw new RuntimeException(ex);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
RequestMapping requestMapping = AnnotationUtils.getAnnotation(proxyMethod, RequestMapping.class);
String url = getRequestUrl(requestMapping);
Object invokeParam = handleRequestObject(proxyMethod,args);
Class<?> returnType = getReturnType(proxyMethod);
MultiValueMap<String, String> httpAttributes = getHttpAttributes(proxyType, proxyMethod);
RestTemplate template = createRestTemplate(httpAttributes,proxyType,proxyMethod);
HttpEntity entity = new HttpEntity(invokeParam,headers(httpAttributes));
ResponseEntity<?> responseEntity = template.exchange(url,getHttpMethod(requestMapping),entity,returnType,args);
return handleReturnObject(proxyMethod,invokeParam,responseEntity);
}
private String getRequestUrl(RequestMapping requestMapping){
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(requestMapping);
String[] path = (String[]) annotationAttributes.get("path");
if(path.length>0)
return path[0];
throw new UnsupportedOperationException("url not be null!");
}
private Class<?> getReturnType(Method proxyMethod){
Class<?> returnType = proxyMethod.getReturnType();
if(ClassUtils.isAssignable(CallBack.class,returnType)){
Type[] interfaces = returnType.getGenericInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (interfaces[i] instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) interfaces[i];
if (parameterizedType.getRawType() == CallBack.class) {
return (Class<?>) parameterizedType.getActualTypeArguments()[0];
}
}
}
}
if(ClassUtils.isAssignable(ResponseEntity.class,returnType)){
Type genericReturnType = proxyMethod.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
return (Class<?>) actualTypeArguments[0];
}
}
return returnType;
}
private Object handleRequestObject(Method proxyMethod,Object[] args){
Object invokeParam;
if(args.length==1){
if(args[0] instanceof Builder)
invokeParam = ((Builder)args[0]).build();
else
invokeParam = args[0];
}else{
Map<String,Object> paramsMap = new LinkedHashMap<>();
Parameter[] parameters = proxyMethod.getParameters();
for(int i=0;i<parameters.length;i++){
RequestParam annotation = AnnotationUtils.findAnnotation(parameters[i], RequestParam.class);
if(annotation!=null)
paramsMap.put((String)AnnotationUtils.getDefaultValue(annotation),args[i]);
else
paramsMap.put(parameters[i].getName(),args[i]);
}
invokeParam = paramsMap;
}
return invokeParam;
}
private Object handleReturnObject(Method proxyMethod,Object invokeParam,ResponseEntity<?> responseEntity){
Object result = null;
if(ClassUtils.isAssignable(CallBack.class,proxyMethod.getReturnType())){
CallBack callBack = (CallBack) newInstance(proxyMethod.getReturnType());
callBack.call(responseEntity,invokeParam);
result = callBack;
}else if(ClassUtils.isAssignable(ResponseEntity.class,proxyMethod.getReturnType())){
result = responseEntity;
}else{
result = responseEntity.getBody();
}
return result;
}
private HttpMethod getHttpMethod(RequestMapping requestMapping){
RequestMethod[] requestMethod = requestMapping.method();
if(requestMethod.length>0)
return HttpMethod.valueOf(requestMethod[0].name());
return HttpMethod.POST;
}
private MultiValueMap<String,String> getHttpAttributes(Class<?> proxyType, Method proxyMethod){
MultiValueMap<String,String> attributes= new LinkedMultiValueMap<>();
HttpAttribute[] proxyTypeAttributeAnnotations = proxyType.getAnnotationsByType(HttpAttribute.class);
for(HttpAttribute attribute : proxyTypeAttributeAnnotations)
attributes.add(attribute.name(),attribute.value());
HttpAttribute[] proxyMethodAttributeAnnotations = proxyMethod.getAnnotationsByType(HttpAttribute.class);
for(HttpAttribute attribute : proxyMethodAttributeAnnotations)
attributes.add(attribute.name(),attribute.value());
return attributes;
}
private HttpHeaders headers(MultiValueMap<String,String> multiValueMap){
if(multiValueMap.containsKey(HttpAttribute.CONNECTION_TIMEOUT))
multiValueMap.remove(HttpAttribute.CONNECTION_TIMEOUT);
if(multiValueMap.containsKey(HttpAttribute.SOCKET_TIMEOUT))
multiValueMap.remove(HttpAttribute.SOCKET_TIMEOUT);
return new HttpHeaders(multiValueMap);
}
private RestTemplate createRestTemplate(MultiValueMap<String,String> attributes,Class<?> proxyType, Method proxyMethod){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
if(attributes.containsKey(HttpAttribute.CONNECTION_TIMEOUT))
factory.setConnectTimeout(Integer.valueOf(attributes.getFirst(HttpAttribute.CONNECTION_TIMEOUT)));
if(attributes.containsKey(HttpAttribute.SOCKET_TIMEOUT))
factory.setReadTimeout(Integer.valueOf(attributes.getFirst(HttpAttribute.SOCKET_TIMEOUT)));
RestTemplate template = new RestTemplate();
template.setRequestFactory(new BufferingClientHttpRequestFactory(factory));
HashSet<Class<? extends ClientHttpRequestInterceptor>> interceptors = new LinkedHashSet<>();
HttpRequestInterceptors proxyTypeAnnotation = proxyType.getAnnotation(HttpRequestInterceptors.class);
HttpRequestInterceptors proxyMethodAnnotation = proxyMethod.getAnnotation(HttpRequestInterceptors.class);
if(proxyTypeAnnotation!=null) {
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyTypeAnnotation.include())
interceptors.add(interceptor);
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyTypeAnnotation.unInclude())
interceptors.remove(interceptor);
}
if(proxyMethodAnnotation!=null) {
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyMethodAnnotation.include())
interceptors.add(interceptor);
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyMethodAnnotation.unInclude())
interceptors.remove(interceptor);
}
List<ClientHttpRequestInterceptor> interceptorsList = template.getInterceptors();
for( Class<? extends ClientHttpRequestInterceptor> interceptor :interceptors)
interceptorsList.add(newInstance(interceptor));
return template;
}
}
public interface CallBack<T,R> { void call(ResponseEntity<T> entity, R requestParam); }
@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(HttpAttribute.HttpAttributes.class) public @interface HttpAttribute { String name(); String value(); String SOCKET_TIMEOUT = "http.socket.timeout"; String CONNECTION_TIMEOUT = "http.connection.timeout"; @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface HttpAttributes { HttpAttribute[] value(); } }
@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface HttpRequestInterceptors { Class<? extends ClientHttpRequestInterceptor>[] include() default {}; Class<? extends ClientHttpRequestInterceptor>[] unInclude() default {}; }
CallBack(補充)---"集合"類結果數據
CallBack類型返回值作為對響應報文實體的進一步封裝,Http接口為我們有時可能希望返回集合類型的數據結果(List<Pojo>),該代理類沒有直接提供針對集合類型返回值的封裝,
比如保險系統針對被保人進行風控校驗(第三方接口來校驗,自己解析結果,"集合">0表示有風控風險,進行提示或者其他后續處理)
這里通過實現callBack來變相實現對象的迭代(foreach)操作,
public class InsuredRiskWarnInfo implements CallBack<ResponseDto,RequestDto>,Iterable<InsuredRiskWarnInfo>{ private List<InsuredRiskWarnInfo> data; @Getter private String name; //姓名 @Getter private String code; //錯誤碼 @Getter private String message;//錯誤信息 public void merge(InsuredRiskWarnInfo info){ if(this.data == null) this.data = info.data; else this.data.addAll(info.data); } @Override public void call(ResponseEntity<ResponseDto> entity, RequestDto requestParam) { data = new ArrayList<>(); // if(entity.getStatusCode() != HttpStatus.OK) // throw new RuntimeException(entity.getStatusCode().getReasonPhrase()); /**這里是具體的對響應對象的數據封裝邏輯,假設一些數據*/ Map<String,String> map = new HashMap<>(); map.put("1","錯誤1"); map.put("2","錯誤2"); map.put("3","錯誤3"); for(Map.Entry<String,String> item : map.entrySet()){ InsuredRiskWarnInfo info = new InsuredRiskWarnInfo(); info.name = "某某人"; //可以從請求對象requestParam中取 info.code = item.getKey(); info.message = item.getValue(); info.data = this.data; this.data.add(info); } } @Override public Iterator<InsuredRiskWarnInfo> iterator() { return data.iterator(); }
調用示例 :
/**模擬請求后*/ InsuredRiskWarnInfo insuredRiskWarnInfo = new InsuredRiskWarnInfo(); insuredRiskWarnInfo.call(null,null); String format = "%s風控失敗%s:%s"; for (InsuredRiskWarnInfo info : insuredRiskWarnInfo){ System.err.println(String.format(format,info.getName(),info.getCode(),info.getMessage())); } /** 某某人風控失敗1:錯誤1 某某人風控失敗2:錯誤2 某某人風控失敗3:錯誤3 */