SpringBoot 基于注解实现接口的代理Bean注入
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
*/
SpringBoot 基于注解实现接口的代理Bean注入的更多相关文章
- 基于注解的接口限流+统一session认证
代码心得: 一个基本的做法:对于用户身份认证做到拦截器里,针对HandlerMethod进行统一拦截认证,根据方法上的注解标识,判别是否需要身份验证,并将查找出来的User实体存入ThreadLoca ...
- SpringBoot | 问题 | 注解方式下无法发现Bean
在排除注解的问题后,考虑扫描类的位置, [SpringBoot项目的Bean装配默认规则是根据Application类所在的包位置从上往下扫描! “Application类”是指SpringBoot项 ...
- springboot基于注解动态配置多数据源以及多数据源的事务统一
参考文档:https://www.cnblogs.com/zhangboyu/p/7622412.html https://blog.csdn.net/qq_34322777/article/deta ...
- springboot 基于@Scheduled注解 实现定时任务
前言 使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式: 一.基于注解(@Scheduled) 二.基于接口(SchedulingConfigurer) 前者相信大家都很熟悉, ...
- Java开发学习(十三)----基于注解开发定义第三方bean及注解开发总结
在前面的博客中定义bean的时候都是在自己开发的类上面写个注解就完成了,但如果是第三方的类,这些类都是在jar包中,我们没有办法在类上面添加注解,这个时候该怎么办? 遇到上述问题,我们就需要有一种更加 ...
- Spring学习笔记 - 第二章 - 注解开发、配置管理第三方Bean、注解管理第三方Bean、Spring 整合 MyBatis 和 Junit 案例
Spring 学习笔记全系列传送门: Spring学习笔记 - 第一章 - IoC(控制反转).IoC容器.Bean的实例化与生命周期.DI(依赖注入) [本章]Spring学习笔记 - 第二章 - ...
- Spring 代理对象,cglib,jdk的问题思考,AOP 配置注解拦截 的一些问题.为什么不要注解在接口,以及抽象方法.
可以被继承 首先注解在类上是可以被继承的 在注解上用@Inherited /** * Created by laizhenwei on 17:49 2017-10-14 */ @Target({Ele ...
- 8 -- 深入使用Spring -- 4...5 AOP代理:基于注解的“零配置”方式
8.4.5 基于注解的“零配置”方式 AspectJ允许使用注解定义切面.切入点和增强处理,而Spring框架则可识别并根据这些注解来生成AOP代理.Spring只是使用了和AspectJ 5 一样的 ...
- Springboot + redis + 注解 + 拦截器来实现接口幂等性校验
Springboot + redis + 注解 + 拦截器来实现接口幂等性校验 1. SpringBoot 整合篇 2. 手写一套迷你版HTTP服务器 3. 记住:永远不要在MySQL中使用UTF ...
- 基于注解的springboot+mybatis的多数据源组件的实现
通常业务开发中,我们会使用到多个数据源,比如,部分数据存在mysql实例中,部分数据是在oracle数据库中,那这时候,项目基于springboot和mybatis,其实只需要配置两个数据源即可,只需 ...
随机推荐
- C#——接口
先来看看微软官方对接口的定义与说明. 接口定义协定. 实现接口的类或结构必须遵循它的协定. 接口可以从多个基接口继承,类或结构可以实现多个接口. 接口可以包含方法.属性.事件和索引器. 接口本身不提供 ...
- 浅谈地址,section,vstart
地址:地址只是数字,描述各种符号在源程序中的位置,它是源代码文件中各符号偏移文件开头的距离.由于指令和变量所占内存大小不同,故他们的偏移量参差不齐.由编译器给各符号编址,编译器给程序中各符号(变量名和 ...
- git 怎么将某个开发分支最近几次的提交合并成一次提交
1. 切换到开发分支: git checkout dev 2. 运行交互式 rebase 命令,并指定要合并的提交数量(在这个例子中是最近的3次提交): git rebase -i HEAD~3 3. ...
- [rCore学习笔记 08]内核第一条指令
了解QEMU 启动指令 qemu-system-riscv64 \ -machine virt \ -nographic \ -bios ../bootloader/rustsbi-qemu.bin ...
- 从30个角度对比 PostgreSQL 和 MySQL
比较版本:PostgreSQL 11 VS MySQL5.7(innodb引擎) Oracle官方社区版 版权情况:PostgreSQL 11(免费开源).MySQL5.7 Oracl ...
- CF301B Yaroslav and Time 题解
CF301B 这不最短路的板子题吗? 思路 用 \(ak\) 代表走到第 \(k\) 点时的可恢复单位时间的值. \(i\) 到 \(j\) 的距离是 \(\left ( \left | xi-xj ...
- Python 实现行为驱动开发 (BDD) 自动化测试详解
在当今的软件开发领域,行为驱动开发(Behavior Driven Development,BDD)作为一种新兴的测试方法,逐渐受到越来越多开发者的关注和青睐.Python作为一门功能强大且易于使 ...
- 【Scala】08 模式匹配 Match Case
由Scala封装的一套match case处理,功能比原Java的更为强大 package cn import scala.collection.immutable.IndexedSeqDefault ...
- 【Mybatis】13 动态SQL
还是先准备演示环境 数据库: CREATE TABLE `t_user` ( `id` int NOT NULL AUTO_INCREMENT, `last_name` varchar(10) DEF ...
- 实现一个终端文本编辑器来学习golang语言
欢迎!这个系列的博文会带你使用golang语言来编写一个你自己的文本编辑器. 首先想说说写这个系列文章的动机. 其实作为校招生加入某头部互联网大厂一转眼已经快4年了.可以说该大厂算是比较早的用gola ...