@ControllerAdvice注解使用及原理探究 | 京东物流技术团队
最近在新项目的开发过程中,遇到了个问题,需要将一些异常的业务流程返回给前端,需要提供给前端不同的响应码,前端再在次基础上做提示语言的国际化适配。这些异常流程涉及业务层和控制层的各个地方,如果每个地方都写一些重复代码显得很冗余。
然后查询解决方案时发现了@ControllerAdvice这个注解,可以对业务异常进行统一处理。经过仔细了解后,发现这个注解还有更多的用处,都很实用。
1 ControllerAdvice介绍
@ControllerAdvice一般和三个以下注解一块使用,起到不同的作用,
- @ExceptionHandler: 该注解作用于方法上,,可以捕获到controller中抛出的一些自定义异常,统一进行处理,一般用于进行一些特定的异常处理。
- @InitBinder:该注解作用于方法上,用于将前端请求的特定类型的参数在到达controller之前进行处理,从而达到转换请求参数格式的目的。
- @ModelAttribute:该注解作用于方法和请求参数上,在方法上时设置一个值,可以直接在进入controller后传入该参数。
2 ControllerAdvice应用场景
2.1@ExceptionHandler统一处理业务异常
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 这里就是对各个层返回的异常进行统一捕获处理
@ExceptionHandler(value = BusinessException.class)
public ResponseData<Void> bizException(BusinessException e){log.error("业务异常记录",e);return ResponseData.error(e.getCode(),e.getMessage());
}
}
//业务异常处代码示例:
if(CollectionUtil.isNotEmpty(companies)){
// 通过BusinessExceptionEnum枚举对业务异常进行统一管理
throw new BusinessException(BusinessExceptionEnum.ERROR_10003);
}
需要注意的是,如果这里有多个ExceptionHandler,按照异常类的层次体系,越高层的异常,优先级越低。
2.2@InitBinder做日期格式的统一处理
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 将前端传入的字符串时间格式转换为LocalDate时间
@InitBinderprotected void initBinder(WebDataBinder binder) {
//将前端传入的字符串格式时间数据转为LocalDate格式的数据binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));}});
//将前端传入的字符串格式时间数据转为LocalDateTime格式的数据binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalDateTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));}});
//将前端传入的字符串格式时间数据转为LocalTim格式的数据binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));}});}
}
// controller进行参数绑定
public ResponseData<List<WorkCalendarVo>> listWorkCalendar(@RequestParam LocalDate date){}
2.3 ModelAttribute提前绑定全局user对象
// 这里@ModelAttribute("loginUser")标注的modelAttribute()方法表示会在Controller方法之前将user设置到contoller里的已绑定参数里@ModelAttribute("loginUser")public User setLoginUser(HttpServletRequest request) {return LoginContextUtils.getLoginUser(request);}
// 使用@PostMapping("/list")public ResponseData<IPage<EmployeeVo>> listEmployee(@ModelAttribute("loginUser") User user, @RequestBody EmployeeSearch employeeSearch){return ResponseData.success(employeeService.listEmployee(user, employeeSearch));}
3 ControllerAdvice作用原理探究
在探究ControllerAdvice如何生效时,不得不提到springMvc绕不过的DispatcherServlet,这个类是SpringMVC统一的入口,所有的请求都通过它,里面的一些初始化方法如下。
public class DispatcherServlet extends FrameworkServlet {// ......protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);
//请求处理的adapterinitHandlerAdapters(context);
// 异常响应处理的resolverinitHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}// ......
}
3.1@initBinder和@ModelAttribute的作用原理
@initBinder和@ModelAttribute都是请求过程中的处理,我们知道springMvc通过HandlerApapter定位到具体的方法进行请求处理,因此查看HandlerHaper的实现类,发现RequestMappingHandlerAdapter比较符合我们的目标
点进去RequestMappingHandlerAdapter后发现里面的一个方法如下
@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beans
// 这里会添加ResponseBody advice beansinitControllerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.initBinderArgumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}
// 这里找到contollerAdvice注解的类,缓存里面的方法
private void initControllerAdviceCache() {if (getApplicationContext() == null) {return;}
// 找到@ControllerAdvice注解标注的类List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}
// 找到所有ModelAttribute标注的方法进行缓存,就可以使用了Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);if (!attrMethods.isEmpty()) {this.modelAttributeAdviceCache.put(adviceBean, attrMethods);}
// 找到所有initBinder注解标注的方法进行缓存,就可以使用了Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);if (!binderMethods.isEmpty()) {this.initBinderAdviceCache.put(adviceBean, binderMethods);}if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {requestResponseBodyAdviceBeans.add(adviceBean);}}if (!requestResponseBodyAdviceBeans.isEmpty()) {this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);}
// ......日志处理}
3.2@ExceptionHandler注解的作用原理
相同的思路,@ExceptionHandler是响应时的处理,因此需要找到对应的Resolver,进入initHandlerExceptionResolvers(context)方法,
属性填充后会进行afterPropertiesSet方法,这个方法可以用在一些特殊情况中,也就是某个对象的某个属性需要经过外界得到,比如说查询数据库等方式,这时候可以用到spring的该特性,只需要实现InitializingBean。
@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBodyAdvice beansinitExceptionHandlerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}
// 这里找到ExceptionHandler注解标注的方法进行缓存,后面就可以使用了ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}
// ......日志处理}
在启动spring时debug发现最终也会走到这里对@ExceptionHander注解的方法已经缓存
当Controller抛出异常时,DispatcherServlet通过ExceptionHandlerExceptionResolver来解析异常,而ExceptionHandlerExceptionResolver又通过ExceptionHandlerMethodResolver 来解析异常, ExceptionHandlerMethodResolver 最终解析异常找到适用的@ExceptionHandler标注的方法是这里:
@Nullablepublic Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {Method method = this.exceptionLookupCache.get(exceptionType);if (method == null) {method = getMappedMethod(exceptionType);this.exceptionLookupCache.put(exceptionType, method);}return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);}
4 用具体的调用过程,验证上面的推测
本部分通过对DispatcherServlet的调用过程跟踪,梳理出ControllerAdvice的作用原理,以@InitBinder主节点生效过程为例。
首选是dispathServlet在初始化过程中,初始化RequestMappingHandlerAdapter过程中打断点发现,initBinder已经缓存进来了。
然后是dispatcherServlet的调用流程图,验证下是initBinder注解是否生效。
DispatcherServlet 通过doService()方法开始调用,主要逻辑包括 设置 request ,通过doDispatch() 进行请求分发处理。
doDispatch() 的主要过程是通过 HandlerMapping 获取 Handler,再找到用于执行它的 HandlerAdapter,执行 Handler 后得到 ModelAndView ,ModelAndView 是连接“业务逻辑层”与“视图展示层”的桥梁。
4.1 DispathcerServlet的doDispatch方法
在入口处找到要执行的HandlerAdapter,调用handle方法继续
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.
// 找到执行链,根据请求路径匹配到controller的方法mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.
// 找到对应的HandlerAdapter,执行链中的handler类型为HandlerMethod的.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler. 真正进行处理的地方mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);..........}
4.2 RequestmappingHanderApapter对@initBInder注解缓存方法进行处理
找到对应的handlerAdapter后进入invokeHandlerMethod()方法,在这里通过构建WebDataBinderFactory对initBinder注解进行构建,供后续使用,具体逻辑如下。
通过getDataBinderFactory()方法从之前缓存的Map> initBinderAdviceCache中生成binderFactory
@Nullableprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {
//根据initBinder注解,获取对应的factory,主要成员是InvocableHandlerMethod,就包括之前缓存的。WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 创建可调用的对象,进行调用逻辑处理ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}
// binderFactory设置进invocableMethod,invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}
// 继续进行处理invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}}
// 生成WebDataBinderFactory的具体逻辑
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {Class<?> handlerType = handlerMethod.getBeanType();Set<Method> methods = this.initBinderCache.get(handlerType);if (methods == null) {methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);this.initBinderCache.put(handlerType, methods);}List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();// Global methods first 获取之前项目启动缓存的initMethodthis.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {Object bean = controllerAdviceBean.resolveBean();for (Method method : methodSet) {initBinderMethods.add(createInitBinderMethod(bean, method));}}});for (Method method : methods) {Object bean = handlerMethod.getBean();initBinderMethods.add(createInitBinderMethod(bean, method));}return createDataBinderFactory(initBinderMethods);}
经过上面的处理,发现initBinder标注的注解方法已经成功缓存进bindFactory。
4.3 继续调用getMethodArgumentValues进行后续处理
继续往下跟踪,进入InvocableHandlerMethod的invokeForRequest方法,里面有getMethodArgumentValues方法,会对请求参数进行处理。
最终使用AbstractNamedValueMethodArgumentResolver的resolveArgument()方法对请求字符串格式数据进行处理
// 请求Controller方法如下
public ResponseData<IPage<CompanyVo>> listCompany(HttpServletRequest servletRequest, @RequestBody CompanySearch companySearch, @RequestParam LocalDate localDate){getLoginUser(servletRequest);return ResponseData.success(companyService.listCompany(companySearch));}protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
// 得到方法的参数列表MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args = new Object[parameters.length];
// 循环如处理请求参数for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {
// 真正进行参数处理的地方args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg = ex.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;}// 最终会使用AbstractNamedValueMethodArgumentResolver来进行处理
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();
// 得到请求参数名称为"localdate"Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");}
// 获取请求的locadate的值,此时为字符串格式"yyyy-mm-dd"Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg == null) {if (namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}
// 这里就会使用bindFactory进行处理if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {
// 经过这里进行处理,输入的string类型就会转为LocalDate了arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}catch (ConversionNotSupportedException ex) {throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}catch (TypeMismatchException ex) {throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}// Check for null value after conversion of incoming argument valueif (arg == null && namedValueInfo.defaultValue == null &&namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);}}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;}
最后附上上面调用过程中一些类的介绍
以上就是ControllerAdivce的全介绍。通过对源码的学习,加深了对HTTP请求过程的理解。
参考:https://blog.csdn.net/zmm__1377445292/article/details/116158554
作者:京东物流 付鹏嘎
来源:京东云开发者社区 自猿其说Tech
相关文章:
![](https://img-blog.csdnimg.cn/img_convert/573b33a8fdbf4a376e765f7cc7ca3db7.jpeg)
@ControllerAdvice注解使用及原理探究 | 京东物流技术团队
最近在新项目的开发过程中,遇到了个问题,需要将一些异常的业务流程返回给前端,需要提供给前端不同的响应码,前端再在次基础上做提示语言的国际化适配。这些异常流程涉及业务层和控制层的各个地方,如果每个地方都写一些…...
![](https://img-blog.csdnimg.cn/d7df5d671b0b49c8805cc5e970ad62de.png)
Error: Design has unresolved cell reference
我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 所有的unresolved cell reference问题都是cell信息没读到引起的,在dc/pt里就是db没读到,在ICC2里就是ndm没读。 ICC2中午饭这个问题可以report_design_…...
![](https://img-blog.csdnimg.cn/img_convert/f294273116940ef16ee89596bc6a2bb7.png)
uni-app 封装api请求
前端封装api请求 前端封装 API 请求可以提高代码的可维护性和重用性,同时使得 API 调用更加简洁和易用。 下面是一种常见的前端封装 API 请求的方式: 创建一个 API 封装模块或类:可以使用 JavaScript 或 TypeScript 创建一个独立的模块或类来…...
![](https://img-blog.csdnimg.cn/642ef3be33eb44f98b0440908432e80b.png)
SpringCloud实用篇1——eureka注册中心 Ribbon负载均衡原理 nacos注册中心
目录 1 微服务1.1 微服务的演变1.2 微服务1.3 SpringCloud1.4 小结 2 服务拆分及远程调用2.1 服务拆分2.2 服务拆分案例2.3 实现远程调用2.4 提供者与消费者 3 Eureka注册中心3.1 Eureka的结构和作用3.2 搭建eureka-server3.3 服务注册3.4 服务发现 4 Ribbon负载均衡4.1 负载均…...
![](https://img-blog.csdnimg.cn/img_convert/e0b6fe49d7ecd87c5029e5ba2615ee90.png)
【MySQL】sql字段约束
在MySQL中,我们需要存储的数据在特定的场景中需要不同的约束。当新插入的数据违背了该字段的约束字段,MySQL会直接禁止插入。 数据类型也是一种约束,但数据类型这个约束太过单一;比如我需要存储的是一个序号,那就不可…...
![](https://img-blog.csdnimg.cn/img_convert/93a76a7d5cd9b3210db5453b1e2d4b3e.png)
森海塞尔为 CUPRA 首款纯电轿跑 SUV – CUPRA Tavascan 注入音频魅力
森海塞尔为 CUPRA 首款纯电轿跑 SUV – CUPRA Tavascan 注入音频魅力 音频专家森海塞尔携手富有挑战精神的 CUPRA,雕琢时代新贵车型,打造畅快尽兴的驾驶体验 全球知名音频专家森海塞尔与以颠覆传统、充满激情、不甘现状而闻名的汽车品牌 CUPRA 展开合作…...
![](https://www.ngui.cc/images/no-images.jpg)
Java、Android 加解密、编码、压缩、解压缩、Hash
对称加密: 算法:AES (128位)/ DES (56位)....等 加密原理: 原数据--->加密算法(密钥)------>密文 解密原理: 密文---->解密算法(密钥)------>原数据 非对称加密 算法&#…...
![](https://img-blog.csdnimg.cn/2df5cdf9298946c78f0f2aee6d278010.jpeg#pic_center)
11_Pulsar Adaptors适配器、kafka适配器、Spark适配器
2.3. Pulsar Adaptors适配器 2.3.1.kafka适配器 2.3.2.Spark适配器 2.3. Pulsar Adaptors适配器 2.3.1.kafka适配器 Pulsar 为使用 Apache Kafka Java 客户端 API 编写的应用程序提供了一个简单的解决方案。 在生产者中, 如果想不改变原有kafka的代码架构, 就切换到Pulsar的…...
![](https://img-blog.csdnimg.cn/img_convert/d7b2825afcffc9917f5e324f69fc9ab0.png)
jupyter文档转换成markdown
背景 上一篇文章**《如何优雅地用python生成模拟数据》**我就使用jupyter写的,这个真的是万能的,可以插入markdown格式的内容,也可写代码,关键是像ipython一样,可以分步执行。 我可以这样自由的写我的博客内容&#x…...
![](https://img-blog.csdnimg.cn/52667c8e6a4c45cdae7e9ea51283efa5.png)
日志框架及其使用方法
log4j和logBack,同一个人写的,logBack为log4j的升级版,SpringBoot中默认集成logBack 作用:记录软件发布后的一些bug,以及数据是怎样被操作的 传统开发弊端: 1.日志直接输出在控制台,关闭控制台后,日志消…...
![](https://img-blog.csdnimg.cn/6d9f30bba4e841748e198aec38b9149e.png)
ZIG:理解未来编程语言的视角
文章目录 摘要:引言:性能简洁性和模块化避免常见错误和陷阱总结:参考资料📑: 摘要: 本文介绍了新兴编程语言ZIG的目标和特点,包括高性能、简洁性和模块化,并分析了这些特点是如何通过语言设计来…...
![](https://img-blog.csdnimg.cn/img_convert/7b466709b221bb2934139181743ba1bb.gif)
让三驾马车奔腾:华为如何推动空间智能化发展?
上个月,国务院常务会议审议通过了《关于促进家居消费的若干措施》,其中明确提出了“推动单品智能向全屋智能发展创新培育智能消费”“开展数字家庭建设试点”等推动全屋智能拼配发展的建议与方案。 可以说,以整屋为单位的空间智能品类&#x…...
![](https://img-blog.csdnimg.cn/57ee072cba534f45b620689a122d85d7.png)
2022年03月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试
一、单选题(共25题,每题2分,共50分) 第1题 已知a“161”,b“16”,c“8”,执行语句da>b and a>c,变量d的值为是? A:0 B:1 C:True D&am…...
![](https://img-blog.csdnimg.cn/b91ca03795284a4488b2a34a94e703be.png#pic_center)
WIN大恒工业相机SDK开发
大恒工业相机SDK开发概览 一、开发环境搭建1、C# 环境配置(VS2019)2、C 环境配置(VS2019)3、python 环境配置(Pycharm) 二、相机二次开发流程三、相机相机属性参数配置四、图像采集单帧采集回调采集 注意事…...
![](https://www.ngui.cc/images/no-images.jpg)
qt qml中各种Layout之间是如何对齐的?
问题描述: qt qml中下一个RowLayout如何对齐顶部到上方的ColumnLayout的底部略低一些间隔的位置? 我们怎么使用achors去锚定位置? 这些都是可以用anchors锚定属性,以及margin来设置的。 解决办法: 要实现将下一个R…...
![](https://www.ngui.cc/images/no-images.jpg)
Immutable.js 进行js的复制
介绍 在提供不可变(Immutable)数据结构的支持。不可变数据是指一旦创建后就不能被修改的数据,每次对数据进行更新都会返回一个新的数据对象,而原始数据保持不变。 使用 日常中我们使用的拷贝 (1) var arr { } ; arr2 arr ; …...
![](https://img-blog.csdnimg.cn/d82ab2a87a99461fa91a01d7c73f7e53.png)
java动态生成excel并且需要合并单元格
java动态生成excel并且需要合并单元格 先上图看一下预期效果 集成poi <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-base</artifactId><version>4.0.0</version> </dependency> <dependency><…...
![](https://img-blog.csdnimg.cn/1b934526ee52476195f3be8365be53ee.png)
JMeter启动时常见的错误
很多小伙伴在学工具这一块时,安装也是很吃力的一个问题,之前记得有说过怎么安装jmeter这个工具。那么你要启动jmeter的时候,一些粉丝就会碰到如下几个问题。 1.解压下载好的jmeter安装,Windows 平台,双击 jmeter/bin …...
![](https://www.ngui.cc/images/no-images.jpg)
python pandas 排序
Series的排序: Series.sort_values(ascendingTrue, inplaceFalse) 参数说明: ascending:默认为True升序排序,为False降序排序inplace:是否修改原始Series DataFrame的排序: DataFrame.sort_values(by, as…...
![](https://img-blog.csdnimg.cn/606fad8498d74b7bbc38e9c41bfe11e6.png)
前后端分离式项目架构流程复盘之宿舍管理系统
文章目录 🐒个人主页🏅JavaEE系列专栏📖前言:【🎇前端】先创建Vue-cli项目(版本2.6.10,仅包含babel),请选择此项目并创建 【整理简化项目模板】【🎀创建路由】…...
![](https://www.ngui.cc/images/no-images.jpg)
Linux nohup 命令详解
nohup是Linux/Unix系统中非常有用的命令之一。它允许您在后台运行命令或脚本,并且在退出终端会话后仍然保持运行。这对于长时间运行的任务或进程非常有用,特别是当您需要离开终端但希望任务继续运行时。 nohup命令语法 nohup命令的基本语法如下&#x…...
![](https://img-blog.csdnimg.cn/img_convert/2b7431fbecfed7f85876371620f72aa8.png)
VoxWeekly|The Sandbox 生态周报|20230731
欢迎来到由 The Sandbox 发布的《VoxWeekly》。我们会在每周发布,对上一周 The Sandbox 生态系统所发生的事情进行总结。 如果你喜欢我们内容,欢迎与朋友和家人分享。请订阅我们的 Medium 、关注我们的 Twitter,并加入 Discord 社区…...
![](https://www.ngui.cc/images/no-images.jpg)
编程导航算法村第九关 | 二分查找
编程导航算法村第九关 | 二分查找 LeetCode852.这个题的要求有点啰嗦,核心意思就是在数组中的某位位置i开始,从0到i是递增的,从i1 到数组最后是递减的,让你找到这个最高点。 详细要求是:符合下列属性的数组 arr 称为山…...
![](https://www.ngui.cc/images/no-images.jpg)
linux 下安装部署flask项目
FlaskDemo 命名为test.py # codingutf-8 from flask import Flaskapp Flask(__name__)app.route("/") def index():return "test"if __name__ __main__:app.debug True# 这里host一定要写0.0.0.0 写127.0.0.1的无法访问 ——_——app.run(host"0.…...
![](https://www.ngui.cc/images/no-images.jpg)
在Vue里,将当前窗口截图,并将数据base64转为png格式传给服务器
目录 前言 1、将当前窗口截图,并将数据存储下来 2、定义将base64转png的方法 3、完整代码 总结 前言 记录来源于需求 1、将当前窗口截图,并将数据存储下来 export default { data() {return {image: // 存储数据} }mounted() {setTimeout(() >…...
![](https://www.ngui.cc/images/no-images.jpg)
Echarts图表Java后端生成Base64图片格式,POI写入Base64图片到Word中
Echarts图表Java后端生成请看上篇,此篇为Base64图片插入Word文档中Java后台生成ECharts图片,并以Base64字符串返回_青冘的博客-CSDN博客 try {XWPFParagraph xwpfParagraphimage doc.createParagraph(); // 创建图片段落xwpfParagraphimage.setAlignment(Paragraph…...
![](https://www.ngui.cc/images/no-images.jpg)
【AI】《动手学-深度学习-PyTorch版》笔记(十二):从零开始实现softmax回归
AI学习目录汇总 1、什么是特征? 对于图像算法,每个像素可以视为一个特征,例如图像的分辨率为28x28,则有784个特征。而且常常将二维的图像像素矩阵展开为长度为784的向量。 2、权重和偏置的规模 本例中,将使用Fashion-MNIST数据集,它是一个服装分类数据集,可以将服装…...
![](https://img-blog.csdnimg.cn/img_convert/954195688ceed25c73496afa8e5e44f2.jpeg)
汽车用功率电感器
支持车载用被动元件的可靠性认证测试标准“AEC-Q200”的绕线铁氧体功率电感器 LCXH 系列实现商品化,推出了“LCXHF3030QK”等 6 个尺寸的 64 款商品。 这些商品均是用于汽车车身类及信息娱乐等信息类的电源电路用扼流线圈及噪音滤波器的功率电感器。 LCXH 系列与民生…...
![](https://img-blog.csdnimg.cn/72f267ec0c384d33ac05ce960a2b65d0.png)
上传图片视频
分布式文件系统MinIo MinIO提供多个语言版本SDK的支持,下边找到java版本的文档: 地址:https://docs.min.io/docs/java-client-quickstart-guide.html MinIO测试(上传、删除、下载) public class MinioTest {MinioC…...
![](https://www.ngui.cc/images/no-images.jpg)
【UE5】UE5与Python Socket通信中文数据接收不全
最近在使用UE的Socket模块与Python服务器进行通信时遇到了一些坑,特此记录一下。 先来复现一下问题,这里只截取关键代码。 UE端: bool ASoc::SendMsg(const FString& Msg) {TSharedRef<FInternetAddr> TargetAddr ISocketSubsy…...
![](/images/no-images.jpg)
申请网站空间是申请域名吗/网站内容优化关键词布局
http://www.oracle.com/technetwork/java/javase/archive-139210.html 转载于:https://www.cnblogs.com/tianlai/p/4928312.html...
![](/images/no-images.jpg)
安康免费做网站/成都移动seo
1.无论是读书还是学习教程第一步都是大方向上掌握 2.回想,从框架往细节一点点回想。不要以为自己能看懂就是掌握,能自己回想出来才是掌握,回想的时间应该占工作、学习的一半。...
![](/images/no-images.jpg)
山东聊城做网站/网站关键词排名查询
双缓冲法解决重绘和闪屏问题参考文章: (1)双缓冲法解决重绘和闪屏问题 (2)https://www.cnblogs.com/renyuan/p/3474802.html 备忘一下。...
![](https://img-blog.csdnimg.cn/img_convert/8efe400443b2852438ccc7805b996c31.png)
网站建设结束的售后服务/黄石市seo关键词优化怎么做
文章目录声明语句声明文件第三方声明文件书写全局变量声明变量声明函数声明类声明枚举类型声明命名空间声明全局的接口或类型声明合并为NPM包创建声明文件找到已存在的声明文件编写声明文件export导出变量export namesapceUMD扩展全局变量的类型自动生成声明文件发布声明文件参…...
![](/images/no-images.jpg)
做防水广告在哪个网站最好/夸克搜索网页版
第一步:对制品2D图及3D图的分析和消化,其内容包括以下几个方面: 1、制品的几何形状。 2、制品的尺寸、公差及设计基准。 3、制品的技术要求(即技术条件)。 4、制品所用塑料名称、缩水及颜色。 5、制品的表面要求。 第二步&am…...
![](https://img-blog.csdnimg.cn/img_convert/18a699c6cc4efb9d557db44938123d5c.png)
网站建设费税率/网站的seo
译文出自:登链翻译计划译者:翻译小组校对:Tiny 熊原文链接:learnblockchain.cn/article…欢迎阅读跟我学习 Solidity系列中的另一篇文章。在上一篇文章,中,我们了解了数据位置的工作方式以及何时可以使用以下三个位置&…...