@ControllerAdvice注解使用及原理探究

2023-11-15

最近在新项目的开发过程中,遇到了个问题,需要将一些异常的业务流程返回给前端,需要提供给前端不同的响应码,前端再在次基础上做提示语言的国际化适配。这些异常流程涉及业务层和控制层的各个地方,如果每个地方都写一些重复代码显得很冗余。

然后查询解决方案时发现了@ControllerAdvice这个注解,可以对业务异常进行统一处理。经过仔细了解后,发现这个注解还有更多的用处,都很实用。

1 ControllerAdvice介绍

@ControllerAdvice一般和三个以下注解一块使用,起到不同的作用,

  1. @ExceptionHandler: 该注解作用于方法上,,可以捕获到controller中抛出的一些自定义异常,统一进行处理,一般用于进行一些特定的异常处理。
  2. @InitBinder:该注解作用于方法上,用于将前端请求的特定类型的参数在到达controller之前进行处理,从而达到转换请求参数格式的目的。
  3. @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时间  
@InitBinder
    protected void initBinder(WebDataBinder binder) {
//将前端传入的字符串格式时间数据转为LocalDate格式的数据
        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
            }
        });
//将前端传入的字符串格式时间数据转为LocalDateTime格式的数据
        binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalDateTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
            }
        });
//将前端传入的字符串格式时间数据转为LocalTim格式的数据
        binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {
            @Override
            public 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);
//请求处理的adapter
        initHandlerAdapters(context);
// 异常响应处理的resolver
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
    // ......
}

3.1@initBinder和@ModelAttribute的作用原理

@initBinder和@ModelAttribute都是请求过程中的处理,我们知道springMvc通过HandlerApapter定位到具体的方法进行请求处理,因此查看HandlerHaper的实现类,发现RequestMappingHandlerAdapter比较符合我们的目标

点进去RequestMappingHandlerAdapter后发现里面的一个方法如下

@Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
// 这里会添加ResponseBody advice beans
        initControllerAdviceCache();

        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。

@Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBodyAdvice beans
        initExceptionHandlerAdviceCache();

        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标注的方法是这里:

    @Nullable
    public 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

@Nullable
    protected 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 获取之前项目启动缓存的initMethod
        this.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 value
            if (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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

@ControllerAdvice注解使用及原理探究 的相关文章

  • eclipse行号状态行贡献项是如何实现的?

    我需要更新状态行编辑器特定的信息 我已经有了自己的实现 但我想看看 eclipse 贡献项是如何实现的 它显示状态行中的行号 列位置 谁能指点一下 哪里可以找到源代码 提前致谢 亚历克斯 G 我一直在研究它 它非常复杂 我不确定我是否了解完
  • 为什么即使我的哈希码值相同,“==”也会返回 false

    我写了一个像这样的课程 public class HashCodeImpl public int hashCode return 1 public static void main String args TODO Auto generat
  • 什么是抽象类? [复制]

    这个问题在这里已经有答案了 当我了解抽象类时 我说 WT H 问题 创建一个无法实例化的类有什么意义呢 为什么有人想要这样的课程 什么情况下需要抽象类 如果你明白我的意思 最常见的是用作基类或接口 某些语言有单独的interface构建 有
  • 如何在 Java 中向时间戳添加/减去时区偏移量?

    我正在使用 JDK 8 并且玩过ZonedDateTime and Timestamp很多 但我仍然无法解决我面临的问题 假设我得到了格式化的Timestamp在格林威治标准时间 UTC 我的服务器位于某处 假设它设置为Asia Calcu
  • Mockito 使用 @Mock 时将 Null 值注入到 Spring bean 中?

    由于我是 Spring Test MVC 的新手 我不明白这个问题 我从以下代码中获取了http markchensblog blogspot in search label Spring http markchensblog blogsp
  • 如何在单个查询中搜索 RealmObject 的 RealmList 字段

    假设我有一堂课 public class Company extends RealmObject private String companyId private RealmList
  • 断言 Kafka 发送有效

    我正在使用 Spring Boot 编写一个应用程序 因此要写信给 Kafka 我这样做 Autowired private KafkaTemplate
  • 如何在spring mvc框架中运行后台进程

    我有一个网络应用程序 使用 spring mvc 框架 它使得以下内容 用户将文件上传到服务器 立即进入成功页面 使用该文件运行后台进程 那么 我如何使用 spring mvc 框架来做到这一点呢 先感谢您 使用异步标记方法 你在这里有例子
  • 如何使用 JMagick 转换色彩空间?

    如何使用 JMagick API 转换色彩空间 例如 CMYK gt RGB 和 RGB gt CMYK None
  • Java继承,扩展类如何影响实际类

    我正在查看 Sun 认证学习指南 其中有一段描述了最终修饰符 它说 如果程序员可以自由地扩展我们所知的 String 类文明 它可能会崩溃 他什么意思 如果可以扩展 String 类 我是否不会有一个名为 MyString 的类继承所有 S
  • 在 Java 中获取并存储子进程的输出

    我正在做一些需要我开始子处理 命令提示符 并在其上执行一些命令的事情 我需要从子进程获取输出并将其存储在文件或字符串中 这是我到目前为止所做的 但它不起作用 public static void main String args try R
  • 如何在 Spring 3.1 中构造函数自动装配 HttpServletResponse?

    我有一个请求范围的 bean 并且需要访问 HttpServletResponse 和 HttpServletRequest 对象 我需要在构造函数中访问这些对象 因此属性自动装配不是一个选项 我做了以下事情 Component Scope
  • 不可变的最终变量应该始终是静态的吗? [复制]

    这个问题在这里已经有答案了 在java中 如果一个变量是不可变的并且是final的 那么它应该是一个静态类变量吗 我问这个问题是因为每次类的实例使用它时创建一个新对象似乎很浪费 因为无论如何它总是相同的 Example 每次调用方法时都会创
  • Spring Boot中ServletContext初始化后如何创建bean?

    我有一个 bean 它实现 ServletContextAware 和 BeanFactoryPostProcessor 接口 我需要在 ServletContext 完成初始化后将此 bean 注册到 applicationContext
  • 使用 HtmlUnit 定位弹出窗口

    我正在构建一个登录网站并抓取一些数据的程序 登录表单是一个弹出窗口 所以我需要访问这个www betexplorer com网站 在页面的右上角有一个登录链接 写着 登录 我单击该链接 然后出现登录弹出表单 我能够找到顶部的登录链接 但找不
  • 将 JScrollPane 添加到 JFrame

    我有一个关于向 Java 框架添加组件的问题 我有一个带有两个按钮的 JPanel 和一个添加了 JTable 的 JScrollPane 我想将这两个添加到 JFrame 中 我可以将 JPanel 添加到 JFrame 或将 JScro
  • 手动设置Android Studio的JDK路径

    如何为 Android Studio 使用自定义 JDK 路径 我不想弄乱 PATH 因为我没有管理员权限 是否有某个配置设置文件允许我进行设置 如果您查看项目设置 您可以从那里访问 jdk 在标准 Windows 键盘映射上 您可以在项目
  • Android View Canvas onDraw 未执行

    我目前正在开发一个自定义视图 它在画布上绘制一些图块 这些图块是从多个文件加载的 并将在需要时加载 它们将由 AsyncTask 加载 如果它们已经加载 它们只会被绘制在画布上 这工作正常 如果加载了这些图片 AsyncTask 就会触发v
  • 如何更改 Spring OAuth2 上的response_type

    这是我使用 Instagram 进行 OAuth2 登录的配置 instagram client clientId clientId clientSecret clientSeret accessTokenUri https api ins
  • Spring RESTful控制器方法改进建议

    我是 Spring REST 和 Hibernate 的新手 也就是说 我尝试组合一个企业级控制器方法 我计划将其用作未来开发的模式 您认为可以通过哪些方法来改进 我确信有很多 RequestMapping value user metho

随机推荐

  • vue调用高德地图实现定位

    vue调用高德地图实现定位 第一步创建高德地图key 第二步 下载vue amap npm install vue amap save 第三步 在项目main js引入vue amap import AMap from vue amap V
  • RedmiBook pro15 2023款折腾笔记(7840HS)amd-pstat解决APU睿频问题

    这里记录一下RedmiBook pro15 2023款笔记本 7840HS 搭建ubuntu 22 04系统的记录 留给正好需要在这个笔记本上安装linux环境的童鞋参考一下 其他AMD的APU都是类似的 拿到机器过后安装ubuntu 22
  • 操作系统终端输入大小限制

    问题描述 使用c 写算法题时 测试用例是一个长字符串 上万字符 但运行结果显示字符串读入的长度远远小于输入长度 并且不同操作系统下面显示出了不同的bug macos 输入字符串长度只有1000 超过这个长度成都就会卡在cin那行代码 ubu
  • 裸片IC的简单介绍以及裸片IC的封装设计

    最近项目一直用到一种比较旧的PCB生产工艺 裸片IC 也就是绑定的工艺 1 什么是裸片IC 裸片既是在加工厂生产出来的芯片 即是晶圆经过切割测试后没有经过封装的芯片 大小一般在几毫米左右 边上有用于连接金属线的的焊盘或者小孔 金属线这是连接
  • 02rapidJson学习之数组的创建、遍历

    02rapidJson学习之数组的创建 遍历 概述 创建数组时我们需要注意以下内容 1 不能直接返回数组 我试过不行 因为数组必须依赖某一对象 即若我们不将数组添加到doc上 而直接返回数组是不行的 1 数组的创建 从doc中获取strin
  • Github 本地合并 (merge) 他人提交的 pr

    Github 上有些项目可能作者长时间没有进行维护 会出现有些新的 pr 没有合并到主分支 master 上 这时如果想在本地应用这个新的 pr 呢 一般来说主要有以下几种方式 针对提交的pr 查看具体的改动文件和改动内容 然后在本地进行对
  • git log 后一直出现:(冒号)的原因以及处理方法

    博客主页 https blog csdn net mukes 欢迎点赞 收藏 留言 如有错误敬请指正 本文由 mukes 原创 首发于 csdn 问题重现 在 git bash 中输入 git log 时出现 冒号 如下图所示 问题描述 一
  • Android酷炫实用的开源框架(UI框架)

    Android酷炫实用的开源框架 UI框架 前言 忙碌的工作终于可以停息一段时间了 最近突然有一个想法 就是自己写一个app 所以找了一些合适开源控件 这样更加省时 再此分享给大家 希望能对大家有帮助 此博文介绍的都是UI上面的框架 接下来
  • web初始

    个人学习开始 写的不好 请大佬指教
  • 机器学习实战——Kmeans聚类算法

    机器学习实战 Kmeans聚类算法 1 聚类算法介绍 1 1 K 均值聚类 1 2 聚类效果的评价 2 sklearn中的实现 1 聚类算法介绍 在无监督学习中 训练样本的标记是未知的 目标是通过对无标记训练样本的学习来揭示数据的内在性质及
  • 纷玩岛演唱会下单代码

    继大麦M端之后 再发现一个演唱会平台 纷玩岛 此平台不像大麦 猫眼那么火爆 相对来说比较容易研究 通过抓包软件发现下单很简单 就一个JWT登录后的头部token而已 下载地址 https download csdn net download
  • 结构体对函数指针的高级封装应用

    分层设计考虑 作用 降低对底层应用程序的高耦合度 示例 include mac h typedef struct phy t char channel char snd fail count char name char open flag
  • 软件测试用例覆盖率怎么算,如何计算增量测试覆盖率

    为了保证代码质量 一般会要求提交的源码要有测试用例覆盖 并对测试覆盖率有一定的要求 在实践中不仅会考核存量代码覆盖率 总体覆盖率 还会考核增量代码的覆盖率 或者说增量覆盖率更有实际意义 测试用例要随源码一并提交 实时保证源码的质量 而不是代
  • 进程和线程的区别,以及应用场景

    什么是线程 Linux下线程用进程PCB模拟描述 也叫轻量级进程 线程是进程内部的一个执行流 也就是线程在进程的地址空间内运行 一个进程内的所有线程共享进程资源 线程是CPU调度的基本单位 CPU调度是按照PCB进行调度的 创建 销毁一个线
  • Mule入门——DB、Rest、Soap接口开发

    一 DB查询接口开发 这里我用的mysql数据库 首先我们先查询下我们的数据库这里有很多数据 然后我们用AnypointStudio进行我们的接口开发 首先我们先新建一个Mule工程 File gt New gt Mule project
  • 计算机网络---传输层

    两个端的会话层之间提供建立 维护和取消传输连接的功能 这一层 数据传送的协议单元成为报文 网络层只是根据网络地址将源节点发出的数据包送到目的终点 而传输层负责将数据可靠的传送到相应的端口 传输层负责将上层数据分段提供端到端 可靠不可靠的传输
  • vue3-admin-template页面

    vue3 admin template 本人学习视频网址为 视频地址 源码 github 网页采用技术框架 本管理模板采用vue3开发 使用vue router来作为路由跳转 将登录成功后产生的菜单 token放入到vuex中存储 通过ax
  • 一般Python开发面试中可能会问到的大部分问题

    python语法以及其他基础部分 可变与不可变类型 浅拷贝与深拷贝的实现方式 区别 deepcopy如果你来设计 如何实现 new 与 init 的区别 你知道几种设计模式 编码和解码你了解过么 列表推导list comprehension
  • Linux嵌入式学习——c语言选择结构设计

    Linux嵌入式学习 c语言选择结构设计 一 if语句 1 1if语句的一般格式 1 2if语句常用的3种形式 1 3if语句的嵌套 二 关系运算符和关系表达式 2 1关系运算符及其优先次序 2 2关系表达式 三 逻辑运算符和逻辑表达式 3
  • @ControllerAdvice注解使用及原理探究

    最近在新项目的开发过程中 遇到了个问题 需要将一些异常的业务流程返回给前端 需要提供给前端不同的响应码 前端再在次基础上做提示语言的国际化适配 这些异常流程涉及业务层和控制层的各个地方 如果每个地方都写一些重复代码显得很冗余 然后查询解决方