RequestBodyAdvice和ResponseBodyAdvice详解,@ControllerAdvice注解

摘要:
>extendsHttpMessageConverter<contextClass=参数.getContainingClass();类<}HttpMethodhttpMethod=HTTP请求的输入消息实例;((HttpRequest)inputMessage).getMethod();

一、源码解析

这是spring 4.2新加的两个接口

1、RequestBodyAdvice

public interface RequestBodyAdvice {
    boolean supports(MethodParameter var1, Type var2, Class<? extends HttpMessageConverter<?>> var3);

    HttpInputMessage beforeBodyRead(HttpInputMessage var1, MethodParameter var2, Type var3, Class<? extends HttpMessageConverter<?>> var4) 
      throws IOException; Object afterBodyRead(Object var1, HttpInputMessage var2, MethodParameter var3, Type var4, Class
<? extends HttpMessageConverter<?>> var5); @Nullable Object handleEmptyBody(@Nullable Object var1, HttpInputMessage var2, MethodParameter var3, Type var4,
            Class
<? extends HttpMessageConverter<?>> var5); }

查看一下谁调用了这个接口的这些方法,可以看到AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters()方法调用了这个接口的方法。

@Nullable
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) 
              throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { boolean noContentType
= false; MediaType contentType; try { contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException var16) { throw new HttpMediaTypeNotSupportedException(var16.getMessage()); } if (contentType == null) { noContentType = true; contentType = MediaType.APPLICATION_OCTET_STREAM; } Class<?> contextClass = parameter.getContainingClass(); Class<T> targetClass = targetType instanceof Class ? (Class)targetType : null; if (targetClass == null) { ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); targetClass = resolvableType.resolve(); } HttpMethod httpMethod = inputMessage instanceof HttpRequest ? ((HttpRequest)inputMessage).getMethod() : null; Object body = NO_VALUE; AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage message; try { label94: { message = new AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage(inputMessage); Iterator var11 = this.messageConverters.iterator(); HttpMessageConverter converter; Class converterType; GenericHttpMessageConverter genericConverter; while(true) { if (!var11.hasNext()) { break label94; } converter = (HttpMessageConverter)var11.next(); converterType = converter.getClass(); genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null; if (genericConverter != null) { if (genericConverter.canRead(targetType, contextClass, contentType)) { break; } } else if (targetClass != null && converter.canRead(targetClass, contentType)) { break; } } if (message.hasBody()) { HttpInputMessage msgToUse = this.getAdvice().beforeBodyRead(message, parameter, targetType, converterType); body = genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                converter.read(targetClass, msgToUse); body
= this.getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); } else { body = this.getAdvice().handleEmptyBody((Object)null, message, parameter, targetType, converterType); } } } catch (IOException var17) { throw new HttpMessageNotReadableException("I/O error while reading input message", var17, inputMessage); } if (body != NO_VALUE) { LogFormatUtils.traceDebug(this.logger, (traceOn) -> { String formatted = LogFormatUtils.formatValue(body, !traceOn); return "Read "" + contentType + "" to [" + formatted + "]"; }); return body; } else if (httpMethod != null && SUPPORTED_METHODS.contains(httpMethod) && (!noContentType || message.hasBody())) { throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); } else { return null; } }

可以看到这接口的方法,主要是在HttpMessageConverter处理request body的前后做一些处理和body为空的时候做处理。

从这个可以看出,我们可以在使用这些HandlerMethodArgumentResolver的时候,我们能对request body进行前处理和解析后处理。

2、ResponseBodyAdvice

public interface ResponseBodyAdvice<T> {
    boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2);

    @Nullable
    T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4, 
            ServerHttpRequest var5, ServerHttpResponse var6); }

此可以对@ResponseBody的返回结果在输出到响应之前做处理。

通过泛型,指定需要被“拦截”的响应体对象类型。该接口的实现会在 Controller 方法返回数据,并且匹配到了 HttpMessageConverter 之后,HttpMessageConverter 进行序列化之前执行。可以通过覆写 beforeBodyWrite 来统一的对响应体进行修改

二、RequestBodyAdvice的使用

首先一个实现类实现RequestBodyAdvice,后在类上加上注解@ControllerAdvice,这俩个缺一不可。比如有些请求的请求体已经做加密处理,可以在此将请求体解密。

核心的方法就是 supports,该方法返回的boolean值,决定了是要执行 beforeBodyRead 方法。

而我们主要的逻辑就是在beforeBodyRead方法中,对客户端的请求体进行解密。

注意:不用加@Component注解

//@Component
@ControllerAdvice
public class RequestBodyDecrypt implements RequestBodyAdvice {
    @Reference
    private EquipmentService equipmentService;
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;  // 必须为true才会执行beforeBodyRead和afterBodyRead方法
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, 
                                    Class<? extends HttpMessageConverter<?>> aClass) throws IOException { String equipmentNo = httpInputMessage.getHeaders().getFirst("equipmentNo"); // 从请求头中获取equipmentNo,getFirst()方法根据请求头的名称获取值 String privateKey = null; List<EquipmentDTO> mapList = equipmentService.getByEquipmentNo(equipmentNo); if (mapList.size() > 0) { EquipmentDTO equipmentDTO = mapList.get(0); privateKey = equipmentDTO.getProdPriKey(); } // 提取数据 InputStream is = httpInputMessage.getBody(); // 从HTTPInputMessage中获取请求体,得到字节输入流 byte[] data = new byte[is.available()]; is.read(data); String dataStr = new String(data, StandardCharsets.UTF_8); JSONObject json = JSONObject.parseObject(dataStr); String decrypt = null; try { decrypt = RSAUtils.decryptByPrivateKey(json.getString("applyData"), privateKey); // 私钥解密后的请求体 } catch (Exception e) { throw new RuntimeException("数据错误"); }
     // 将解密后的请求体封装到HttpInputMessage中返回
return new DecodedHttpInputMessage(httpInputMessage.getHeaders(), new ByteArrayInputStream(decrypt.getBytes(StandardCharsets.UTF_8))); } @Override public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type,
                                              Class<? extends HttpMessageConverter<?>> aClass) { return body; } @Override public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type,
                                               Class<? extends HttpMessageConverter<?>> aClass) { return body; } static class DecodedHttpInputMessage implements HttpInputMessage { HttpHeaders headers; InputStream body; public DecodedHttpInputMessage(HttpHeaders headers, InputStream body) { this.headers = headers; this.body = body; } @Override public InputStream getBody() throws IOException { return body; } @Override public HttpHeaders getHeaders() { return headers; } } }

 decryptByPrivateKey:

public static String decryptByPrivateKey(String encryptData, String priKey) throws NoSuchAlgorithmException, 
   InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
byte[] sourceBytes = Base64.getDecoder().decode(encryptData); byte[] keyBytes = Base64.getDecoder().decode(priKey); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); Key priK = keyFactory.generatePrivate(pkcs8KeySpec); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, priK); int inputLen = sourceBytes.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; // 对数据分段解密 while (inputLen - offSet > 0) { if (inputLen - offSet > MAX_DECRYPT_BLOCK) { cache = cipher.doFinal(sourceBytes, offSet, MAX_DECRYPT_BLOCK); } else { cache = cipher.doFinal(sourceBytes, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * MAX_DECRYPT_BLOCK; } String decryptedData = out.toString("UTF-8"); out.close(); return decryptedData; }

三、ResponseBodyAdvice的使用

 首先一个实现类实现ResponseBodyAdvice,后在类上加上注解@ControllerAdvice,

@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Result> {
 
    /**
     * 加密串一
     */
    private static String md5_keyone;
    /**
     * 加密串二
     */
    private static String md5_keytwo;
 
    @PostConstruct
    public void init() throws Exception {
        md5_keyone = Utils.PT.getProps("md5_keyone");
        md5_keytwo = Utils.PT.getProps("md5_keytwo");
    }
 
    /**
     * 判断支持的类型
     * 
     * @param returnType
     * @param converterType
     * @return
     * @see org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#supports(org.springframework.core.MethodParameter,
     *      java.lang.Class)
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return returnType.getMethod().getReturnType().isAssignableFrom(Result.class);
    }
 
    /**
     * 对于结果进行加密
     * 
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     * @see org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#beforeBodyWrite(java.lang.Object,
     *      org.springframework.core.MethodParameter,
     *      org.springframework.http.MediaType, java.lang.Class,
     *      org.springframework.http.server.ServerHttpRequest,
     *      org.springframework.http.server.ServerHttpResponse)
     */
    @Override
    public Result beforeBodyWrite(Result body, MethodParameter returnType,org.springframework.http.MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,ServerHttpResponse response) {
        String jsonString = JSON.toJSONString(body.getData());
        System.out.println(jsonString);
        // 第一次加密
        String data_encode_one = MD5.md5(md5_keyone + jsonString);
        // 第二次加密
        String data_encode_two = MD5.md5(data_encode_one + md5_keytwo);
        body.setToken(data_encode_two);
        return body;
    }
 
}

四、为什么说要加上@ControllerAdvice注解,且实现RequestBodyAdvice或ResponseBodyAdvice接口

 现在来分析为什么需要实现RequestBodyAdvice接口的同时要加上ControllerAdvice注解。

1、为什么要实现RequestBodyAdvice或ResponseBodyAdvice接口?

简单来说,要想执行afterBodyRead方法,必须实现ResponseBodyAdvice接口。

RequestResponseBodyAdviceChain的afterBodyRead方法:调用getMatchingAdvice方法,获取RequestBodyAdvice类型的advice

其中:class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object>

public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, 
    Class<? extends HttpMessageConverter<?>> converterType) { Iterator var6 = this.getMatchingAdvice(parameter, RequestBodyAdvice.class).iterator(); while(var6.hasNext()) { RequestBodyAdvice advice = (RequestBodyAdvice)var6.next(); // 此advice就是我们定义的类 if (advice.supports(parameter, targetType, converterType)) { //如果supports方法的返回值为true,则执行RequestBodyAdvice的afterBodyRead方法 body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType); } } return body; }

getMatchAdvice:获取RequestBodyAdvice类型的advice(此advice是我们定义的),如果不是RequestBodyAdvice类型就不会加到结果集,所以这就是我们实现RequestBodyAdvice的原因

private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
     List<Object> availableAdvice = this.getAdvice(adviceType);//获取RequestBodyAdvice类型的advice(此advice是我们定义实现RequestBodyAdvice接口的类)
        if (CollectionUtils.isEmpty(availableAdvice)) {
            return Collections.emptyList();
        } else {
            List<A> result = new ArrayList(availableAdvice.size());
            Iterator var5 = availableAdvice.iterator();

            while(true) {
                Object advice;
                while(true) {
                    if (!var5.hasNext()) {
                        return result;
                    }

                    advice = var5.next();
                    if (!(advice instanceof ControllerAdviceBean)) {
                        break;
                    }

                    ControllerAdviceBean adviceBean = (ControllerAdviceBean)advice;
                    if (adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
                        advice = adviceBean.resolveBean(); // 返回的是我们定义的Advice,即根据Bean的名称从BeanFactory中获取Bean对象
                        break;
                    }
                }
          // 判断这个类是否是RequestBodyAdvice类型,如果不是就不会加到结果集,所以这就是我们实现RequestBodyAdvice的原因
                if (adviceType.isAssignableFrom(advice.getClass())) {
                    result.add(advice);
                }
            }
        }
    }

getAdvice方法:

private List<Object> getAdvice(Class<?> adviceType) {
        if (RequestBodyAdvice.class == adviceType) {
            return this.requestBodyAdvice;
        } else if (ResponseBodyAdvice.class == adviceType) {
            return this.responseBodyAdvice;
        } else {
            throw new IllegalArgumentException("Unexpected adviceType: " + adviceType);
        }
    }

resolveBean方法:this.beanOrName是string类型的,从beanFactory中再拿到对应的bean对象。

public Object resolveBean() {
        if (this.resolvedBean == null) {
            Object resolvedBean = this.obtainBeanFactory().getBean((String)this.beanOrName); // obtainBeanFactory()返回BeanFactory对象
            if (!this.isSingleton) {
                return resolvedBean;
            }

            this.resolvedBean = resolvedBean;
        }

        return this.resolvedBean;
    }

2、为什么要加上@ControllerAdvice注解?

简单说,只有加上@ControllerAdvice,才能找到ControllerAdviceBean。

HandlerAdapter字面上的意思就是处理适配器,它的作用用一句话概括就是调用具体的方法对用户发来的请求来进行处理。当handlerMapping获取到执行请求的controller时,DispatcherServlte会根据controller对应的controller类型来调用相应的HandlerAdapter来进行处理。

RequestBodyAdvice和ResponseBodyAdvice详解,@ControllerAdvice注解第1张

 RequestMappingHandlerAdapter就比较复杂了,可以说,该类是整个SpringMVC中最复杂的类了,却也是目前SpringMVC中使用到的频率最高的类。目前在SpringMVC的使用过程中,对请求的处理主要就是依赖RequestMappingHandlerMappingRequestMappingHandlerAdapter类的配合使用。下面重点介绍下RequestMappingHandlerAdapter

RequestMappingHandlerAdapter初始化流程:

RequestMappingHandlerAdapter实现了InitializingBean接口,Spring容器会自动调用其afterPropertiesSet方法。

public void afterPropertiesSet() {
        this.initControllerAdviceCache();  // 获取ControllerAdviceBean的集合
        List handlers;
        if (this.argumentResolvers == null) {
            handlers = this.getDefaultArgumentResolvers();
            this.argumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);
        }

        if (this.initBinderArgumentResolvers == null) {
            handlers = this.getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);
        }

        if (this.returnValueHandlers == null) {
            handlers = this.getDefaultReturnValueHandlers();
            this.returnValueHandlers = (new HandlerMethodReturnValueHandlerComposite()).addHandlers(handlers);
        }

    }
  • argumentResolvers:用于给处理器方法设置参数
  • initBinderArgumentResolvers:用于给注释了@InitBinder方法设置参数
  • returnValueHandlers:用于对处理器返回值进行相应处理

(1) 获取@ControllerAdvice注解的类封装为ControllerAdviceBean的集合,遍历ControllerAdviceBean集合

  提取没有@RequestMapping注解标注的,但有@ModelAttribute注解标注的方法

  提取@InitBinder注解标注的方法

  提取实现了RequestBodyAdvice或ResponseBodyAdvice接口的类

(2) 没有设置argumentResolvers则获取默认的HandlerMethodArgumentResolver

(3) 没有设置initBinderArgumentResolvers则获取默认的处理参数绑定的HandlerMethodArgumentResolver

(4) 没有设置returnValueHandlers则获取默认的HandlerMethodReturnValueHandler

RequestMappingHandlerAdapter的getDefaultReturnValueHandlers方法:初始化了RequestResponseBodyMethodProcessor

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList(20);
        handlers.add(new ModelAndViewMethodReturnValueHandler());
        handlers.add(new ModelMethodProcessor());
        handlers.add(new ViewMethodReturnValueHandler());
        handlers.add(new ResponseBodyEmitterReturnValueHandler(this.getMessageConverters(), this.reactiveAdapterRegistry, 
          this.taskExecutor, this.contentNegotiationManager)); handlers.add(new StreamingResponseBodyReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); handlers.add(new HttpHeadersReturnValueHandler()); handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); handlers.add(new ServletModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager,
            this.requestResponseBodyAdvice)); handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); if (this.getCustomReturnValueHandlers() != null) { handlers.addAll(this.getCustomReturnValueHandlers()); } if (!CollectionUtils.isEmpty(this.getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(this.getModelAndViewResolvers())); } else { handlers.add(new ServletModelAttributeMethodProcessor(true)); } return handlers; }

requestResponseBodyAdvice:用来保存实现了RequestBodyAdviceResponseBodyAdvice接口的类

RequestMappingHandlerAdapter类的initControllerAdviceCache方法:初始化List集合requestResponseBodyAdvice

private void initControllerAdviceCache() {
        if (this.getApplicationContext() != null) {
        // 获取有@ControllerAdvice注解的Bean的集合 List
<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext()); List<Object> requestResponseBodyAdviceBeans = new ArrayList(); Iterator var3 = adviceBeans.iterator(); while(var3.hasNext()) { // 遍历集合 ControllerAdviceBean adviceBean = (ControllerAdviceBean)var3.next(); Class<?> beanType = adviceBean.getBeanType(); if (beanType == null) { throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); } Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS); if (!attrMethods.isEmpty()) { this.modelAttributeAdviceCache.put(adviceBean, attrMethods); } 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); // 将ControllerAdviceBean放入List集合中 } } if (!requestResponseBodyAdviceBeans.isEmpty()) {
          // 将存有ControllerAdviceBean的集合存入requestResponseBodyAdvice集合中   
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans); } if (this.logger.isDebugEnabled()) { int modelSize = this.modelAttributeAdviceCache.size(); int binderSize = this.initBinderAdviceCache.size(); int reqCount = this.getBodyAdviceCount(RequestBodyAdvice.class); int resCount = this.getBodyAdviceCount(ResponseBodyAdvice.class); if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) { this.logger.debug("ControllerAdvice beans: none"); } else { this.logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize + "
@InitBinder,
" + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice"); } } } }

ControllerAdviceBean(有@ControllerAdvice注解的Bean)的findAnnotatedBeans方法:获得所有的ControllerAdvice类,之后封装为ControllerAdviceBean

public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
        ListableBeanFactory beanFactory = context;
        if (context instanceof ConfigurableApplicationContext) {
            beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
        }

        List<ControllerAdviceBean> adviceBeans = new ArrayList();
        String[] var3 = BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory)beanFactory, Object.class);
        int var4 = var3.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String name = var3[var5];
            if (!ScopedProxyUtils.isScopedTarget(name)) {
                ControllerAdvice controllerAdvice = (ControllerAdvice)((ListableBeanFactory)beanFactory).findAnnotationOnBean(name, 
                                ControllerAdvice.class); // 从application中找到ControllerAdvice注解 if (controllerAdvice != null) {
            // 将有@ControllerAdvice注解的Bean添加到List集合中并返回 adviceBeans.add(
new ControllerAdviceBean(name, (BeanFactory)beanFactory, controllerAdvice)); } } } OrderComparator.sort(adviceBeans); return adviceBeans; }

会从applicationContext中获取有ControllerAdvice注解的bean,

免责声明:文章转载自《RequestBodyAdvice和ResponseBodyAdvice详解,@ControllerAdvice注解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Hive的web端配置——HWIecharts中的一些配置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

[npm]npm audit fix

npm官网上查阅了对于npm audit fix的相关介绍。 npm audit : npm@5.10.0 & npm@6,允许开发人员分析复杂的代码,并查明特定的漏洞和缺陷。 npm audit fix :npm@6.1.0,  检测项目依赖中的漏洞并自动安装需要更新的有漏洞的依赖,而不必再自己进行跟踪和修复。 npm-audit 官网地址:do...

[DM8168]Linux下控制GPIO控制12864液晶屏(ST7565控制器)

首先加载驱动模块,应用程序通过调用API实现GPIO控制功能。 驱动函数: 1 /* 2 * fileName: st7565_driver.c 3 * just for LCD12864 driver 4 * GP1_14(46) -> D6(SCK) 5 * GP1_15(47) -> D7(SDA) 6...

Mybatis 笔记

1.# 和 $ 的区别 1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id".  2. $将传入的数据直接显示生成在sql中。如:order by $use...

js前端使用jOrgChart插件实现组织架构图的展示

项目要做组织架构图,要把它做成自上而下的树形结构。  需要购买阿里云产品和服务的,点击此链接领取优惠券红包,优惠购买哦,领取后一个月内有效: https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=fp9ccf07 一、说明 (1)通过后台查询数据库,生成树形数组结构,返回到前台。...

JS拖动滑块验证

使用这种验证方法的目的:证明当前的用户不是机器人~防止恶意操作。 实现思路:   1、获取silde滑块(获取元素)   2、为元素注册事件———鼠标点击事件(onmousedown)鼠标点击之后获得当前鼠标的X坐标。   3、如何获取到鼠标的x坐标——使用clientX事件(当事件被触发时,鼠标指针的水平坐标)。   4、鼠标移动事件发生后根据从最开始点...

JS控制div跳转到指定的位置的解决方案总结

总结一下自己在写这个需求遇到的问题,相信大家应该是经常遇到的。即要求滚轮滚动到指定的位置。先看下基本的解决方案。 1.给链接a加个#的方式来实现跳转。(锚点方法)这里直接贴下代码:    html页面: <div id="container"> <a href="http://t.zoukankan.com/jtjds-...