spring boot 動態生成介面實現類
阿新 • • 發佈:2021-11-26
目錄
在某些業務場景中,我們只需要業務程式碼中定義相應的介面或者相應的註解,並不需要實現對應的邏輯。
比如 mybatis和feign: 在 mybatis 中,我們只需要定義對應的mapper介面;在 feign 中,我們只需要定義對應業務系統中的介面即可。
那麼在這種場景下,具體的業務邏輯時怎麼執行的呢,其實原理都是動態代理。
我們這裡不具體介紹動態代理,主要看一下它在springboot專案中的實際應用,下面我們模仿feign來實現一個呼叫三方介面的 httpclient。
一: 定義註解
package com.mysgk.blogdemo.annotation; /** * @author mysgk * @link https://www.cnblogs.com/mysgk/p/15607900.html */ public @interface MyHttpClient { }
二: 建立動態代理類
package com.mysgk.blogdemo.proxy; import org.springframework.beans.factory.FactoryBean; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author mysgk * @link https://www.cnblogs.com/mysgk/p/15607900.html */ public class RibbonAopProxyFactory<T> implements FactoryBean<T>, InvocationHandler { private Class<T> interfaceClass; public Class<T> getInterfaceClass() { return interfaceClass; } public void setInterfaceClass(Class<T> interfaceClass) { this.interfaceClass = interfaceClass; } @Override public T getObject() throws Exception { return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{interfaceClass}, this); } @Override public Class<?> getObjectType() { return interfaceClass; } @Override public boolean isSingleton() { return true; } /** 真正執行的方法 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return "invoke " + proxy.getClass().getName() + "." + method.getName() + " , do anything .."; } }
三: 注入spring容器
package com.mysgk.blogdemo.start; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.StrUtil; import com.mysgk.blogdemo.annotation.MyHttpClient; import com.mysgk.blogdemo.proxy.RibbonAopProxyFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.util.Set; /** * @author mysgk * @link https://www.cnblogs.com/mysgk/p/15607900.html */ @Component public class ScanHttpClients implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware { private final Logger logger = LoggerFactory.getLogger(ScanHttpClients.class); private ApplicationContext ctx; public void run(BeanDefinitionRegistry registry) { Set<Class<?>> scanPackage = ClassUtil.scanPackageByAnnotation("com.mysgk", MyHttpClient.class); for (Class<?> cls : scanPackage) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(cls); GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition(); definition.getPropertyValues().add("interfaceClass", definition.getBeanClassName()); definition.setBeanClass(RibbonAopProxyFactory.class); definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE); String beanName = StrUtil.removePreAndLowerFirst(cls.getSimpleName(), 0) + "RibbonClient"; registry.registerBeanDefinition(beanName, definition); } } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { run(registry); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.ctx = ctx; } }
四: 編寫攔截器
package com.mysgk.blogdemo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
/**
* @author mysgk
* @link https://www.cnblogs.com/mysgk/p/15607900.html
*/
@Component
@Aspect
public class InterceptAnnotation {
@Autowired
private RestTemplate ribbonLoadBalanced;
@Pointcut("@annotation(com.mysgk.blogdemo.annotation.MyHttpClient)")
public void execute() {
}
@Around("execute()")
public Object interceptAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
/**
* 此處省略 獲取 url, httpMethod, requestEntity, responseType 等引數的處理過程
*/
ResponseEntity<?> exchange = ribbonLoadBalanced.exchange("url", HttpMethod.GET, HttpEntity.EMPTY, Object.class);
return exchange.getBody();
}
}
五: 新建測試類
package com.mysgk.blogdemo.client;
import com.mysgk.blogdemo.annotation.MyHttpClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@MyHttpClient
public interface MyHttpClientTest {
@PostMapping(value = "test/t1")
Object test(String param);
}
專案結構: