当前位置: 首页 > 编程日记 > 正文

SpringMVC4.x源码分析(五):request请求寻址HandlerMethod原理

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

mvc:annotation-driven会将每一个Controllor内的被@RequestMapping注解标注的方法解析为HandlerMethod对象,并存储在RequestMappingHandlerMapping的MappingRegistry属性中,寻址就是根据request请求信息,找到正确的HandlerMethod对象的过程。

HandlerMethod和RequestMappingInfo的关系

HandlerMethod意为方法相关的信息封装,RequestMappingInfo则是方法所要求的条件信息,HandlerMethod和RequestMappingInfo的映射关系,通过MappingRegistry的Map<T, HandlerMethod>已经做好了映射,T即为RequestMappingInfo。

HandlerMethod

public class HandlerMethod {// method所在的Controllor实例private final Object bean;private final BeanFactory beanFactory;// Controllor的class类型private final Class<?> beanType;// method本身private final Method method;private final Method bridgedMethod;// method的参数信息private final MethodParameter[] parameters;private HttpStatus responseStatus;private String responseStatusReason;private HandlerMethod resolvedFromHandlerMethod;
//...
}

RequestCondition

public interface RequestCondition<T> {// 类上的条件和方法上的条件进行合并(并集)T combine(T other);// 返回该请求所匹配的条件T getMatchingCondition(HttpServletRequest request);// 如果条件要求是一个人,但来了一个胖子和瘦子,该方法决定选胖子,还是瘦子int compareTo(T other, HttpServletRequest request);}

combine()举例:Class条件是GET,Method条件是POST,combine()的结果是GET或者POST。

getMatchingCondition()举例:条件是GET或者POST,当前request是GET请求,则返回GET条件。

compareTo()举例:条件有/emp*和/emp?,现在的request请求是/emp1,/emp1同时符合/emp*和/emp?,选谁呢?最终选择/emp?,因为/emp?粒度更小,范围更小,compareTo()具有决定权。

RequestCondition的各种实现类类图:

d353d24e504c56213ac10fbcbdb694a2b5e.jpg

(Made In IntelliJ IDEA)

2d305fba568a8b337ebb76d90c352025488.jpg

(Made In Edraw Max)

再对照这幅图,读者大概就知道每个条件对应哪一个配置了。

RequestMappingInfo

RequestMappingInfo较为特殊,它是一个典型的装饰器设计模式(Decorator),它装饰了具体的条件。

public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {private final PatternsRequestCondition patternsCondition;private final RequestMethodsRequestCondition methodsCondition;private final ParamsRequestCondition paramsCondition;private final HeadersRequestCondition headersCondition;private final ConsumesRequestCondition consumesCondition;private final ProducesRequestCondition producesCondition;private final RequestConditionHolder customConditionHolder;
//...
}

我们看看它的combine()方法,都是调用了具体的条件的combine()方法,将结果返回。

public RequestMappingInfo combine(RequestMappingInfo other) {String name = combineNames(other);PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);return new RequestMappingInfo(name, patterns,methods, params, headers, consumes, produces, custom.getCondition());
}

PatternsRequestCondition.combine()

我们以PatternsRequestCondition.combine()为例,它调用了AntPathMatcher.combine()方法:

public String combine(String pattern1, String pattern2) {if (!StringUtils.hasText(pattern1) && !StringUtils.hasText(pattern2)) {return "";}if (!StringUtils.hasText(pattern1)) {return pattern2;}if (!StringUtils.hasText(pattern2)) {return pattern1;}boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1);if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) {// /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html// However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/barreturn pattern2;}// /hotels/* + /booking -> /hotels/booking// /hotels/* + booking -> /hotels/bookingif (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) {return concat(pattern1.substring(0, pattern1.length() - 2), pattern2);}// /hotels/** + /booking -> /hotels/**/booking// /hotels/** + booking -> /hotels/**/bookingif (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) {return concat(pattern1, pattern2);}int starDotPos1 = pattern1.indexOf("*.");if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) {// simply concatenate the two patternsreturn concat(pattern1, pattern2);}String ext1 = pattern1.substring(starDotPos1 + 1);int dotPos2 = pattern2.indexOf('.');String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2));String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2));boolean ext1All = (ext1.equals(".*") || ext1.equals(""));boolean ext2All = (ext2.equals(".*") || ext2.equals(""));if (!ext1All && !ext2All) {throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2);}String ext = (ext1All ? ext2 : ext1);return file2 + ext;
}

作者的注释,已经明确了路径映射合并的规则。

不过,在路径合并之前,它做了一项操作,将不以/开头的路径,加上/,譬如“index”,会被处理为"/index"。

PatternsRequestCondition.prependLeadingSlash():

private static Set<String> prependLeadingSlash(Collection<String> patterns) {if (patterns == null) {return Collections.emptySet();}Set<String> result = new LinkedHashSet<String>(patterns.size());for (String pattern : patterns) {if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {pattern = "/" + pattern;}result.add(pattern);}return result;
}

RequestMethodsRequestCondition.combine()

RequestMethodsRequestCondition.combine()直接采用了取并集的操作,这也是类条件为GET,method条件为POST,最终是GET或者POST关系的原因。

public RequestMethodsRequestCondition combine(RequestMethodsRequestCondition other) {Set<RequestMethod> set = new LinkedHashSet<RequestMethod>(this.methods);set.addAll(other.methods);return new RequestMethodsRequestCondition(set);
}

其余的条件合并,大都于此类似,读者自行查看。

RequestCondition条件选择优先级

我们还是得从装饰器角色RequestMappingInfo入手,其条件优先级基本是:pattern > param > header > consume > produce > method > custom。

public int compareTo(RequestMappingInfo other, HttpServletRequest request) {int result;// HEAD请求时,method条件优先if (HttpMethod.HEAD.matches(request.getMethod())) {result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);if (result != 0) {return result;}}result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);if (result != 0) {return result;}result = this.paramsCondition.compareTo(other.getParamsCondition(), request);if (result != 0) {return result;}result = this.headersCondition.compareTo(other.getHeadersCondition(), request);if (result != 0) {return result;}result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);if (result != 0) {return result;}result = this.producesCondition.compareTo(other.getProducesCondition(), request);if (result != 0) {return result;}result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);if (result != 0) {return result;}result = this.customConditionHolder.compareTo(other.customConditionHolder, request);if (result != 0) {return result;}return 0;
}

如何理解pattern > param > header > consume > produce > method > custom的优先级顺序呢?

原理是:

1、当多个HandlerMethod均满足条件,此时使用pattern排序规则,取排序后的第一个HandlerMethod。如果pattern排序规则返回0(未获得实质排序),则再选择param排序规则,以此类推。最后,选择第一个HandlerMethod作为目标方法。

举例:请求URL为/emp1,发现HandlerMethod(/emp*)和HandlerMethod(/emp?)均满足要求,此时使用pattern条件的排序规则,排序后变成[/emp?, /emp*],然后取第一个HandlerMethod,那么HandlerMethod(/emp?)就被选中了。

再举例:当URL相同时,URL参数?name=张三&pwd=123,此时HandlerMethod({name=张三})和HandlerMethod({name=张三, pwd=123})均满足条件,根据param排序规则,参数多的优先级高,于是HandlerMethod({name=张三, pwd=123})会被最终选中。

@RequestMapping(path = {"/index"}, params = {"pwd=123"})
public String index(Model model, HttpServletRequest request) {
model.addAttribute("msg", "Hello controllor.");
return "hello";
}@RequestMapping(path = {"/index"}, params = {"name=张三", "pwd=123"})
public String index2(Model model, HttpServletRequest request) {
model.addAttribute("msg", "Hello controllor.");
return "hello";
}

AntPatternComparator.compare()

PatternsRequestCondition通过AntPatternComparator.compare()方法,来指定排序规则:

public int compare(String pattern1, String pattern2) {PatternInfo info1 = new PatternInfo(pattern1);PatternInfo info2 = new PatternInfo(pattern2);// path为空和path=/**等价if (info1.isLeastSpecific() && info2.isLeastSpecific()) {return 0;}else if (info1.isLeastSpecific()) {return 1;}else if (info2.isLeastSpecific()) {return -1;}// path直接匹配pattern,譬如path=/emp/emp*, pattern=/emp/emp*boolean pattern1EqualsPath = pattern1.equals(path);boolean pattern2EqualsPath = pattern2.equals(path);if (pattern1EqualsPath && pattern2EqualsPath) {return 0;}else if (pattern1EqualsPath) {return -1;}else if (pattern2EqualsPath) {return 1;}// path=/emp/emp/**和path=/emp/emp/abcif (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) {return 1;}else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {return -1;}// 比较Uri变量数量+*数量+**的数量if (info1.getTotalCount() != info2.getTotalCount()) {return info1.getTotalCount() - info2.getTotalCount();}if (info1.getLength() != info2.getLength()) {return info2.getLength() - info1.getLength();}// 比较*的数量if (info1.getSingleWildcards() < info2.getSingleWildcards()) {return -1;}else if (info2.getSingleWildcards() < info1.getSingleWildcards()) {return 1;}if (info1.getUriVars() < info2.getUriVars()) {return -1;}else if (info2.getUriVars() < info1.getUriVars()) {return 1;}return 0;
}

查找HandlerMethod

AbstractHandlerMethodMapping#lookupHandlerMethod()

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<Match>();// 解析时不带通配符(*|?)条件直接放入urlLookup中// 此处直接使用urlLookup查找List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {addMatchingMappings(directPathMatches, matches, request);}if (matches.isEmpty()) {// 遍历系统中所有的RequestMappingInfo,找到满足条件的HandlerMethod集合addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}if (!matches.isEmpty()) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));// 使用RequestMappingInfo的compareTo方法进行排序Collections.sort(matches, comparator);//...// 取第一个HandlerMethodMatch bestMatch = matches.get(0);//...// 返回HandlerMethodreturn bestMatch.handlerMethod;}else {return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}
}

request请求寻址HandlerMethod实战

@Controller
@RequestMapping("/emp")
public class EmpControllor {@RequestMapping(value = "/emp1")public ModelAndView index1(ModelAndView mav) {mav.setViewName("employee");mav.addObject("path", "/emp/emp1");return mav;}@RequestMapping(value = "/emp?")public ModelAndView index2(ModelAndView mav) {mav.setViewName("employee");mav.addObject("path", "/emp/emp?");return mav;}@RequestMapping("/emp*")public ModelAndView list(ModelAndView mav) {mav.setViewName("employee");mav.addObject("path", "/emp/emp*");return mav;}@RequestMapping("/emp/*")public ModelAndView add(ModelAndView mav) {mav.setViewName("employee");mav.addObject("path", "/emp/emp/*");return mav;}@RequestMapping("/emp/**")public ModelAndView update(ModelAndView mav) {mav.setViewName("employee");mav.addObject("path", "/emp/emp/**");return mav;}}

创建一个WEB-INF/jsp/employee.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false"%>
<html>
<head><title>Title</title>
</head>
<body>
path = <b>${path}</b>
</body>
</html>

1、http://localhost:8080/emp/emp1

输出:path = /emp/emp1

原理:由于不带*|?通配符,urlLookup直接寻址/emp1

fd36ee681a575ee81dcbf43616dd96e53eb.jpg

(Made In Postman)

2、http://localhost:8080/emp/emp2

输出:path = /emp/emp?

原理:/emp2同时匹配/emp*和/emp?,但是/emp*的*号数量大于/emp?,最终选择了/emp?

8ee19d06645bdcc6a17a47752b353b9b44d.jpg

(Made In Postman)

3、http://localhost:8080/emp/emp*

输出:path = /emp/emp*

原理:pattern1.equals(path)直接比较匹配的结果

f97ec1a6ae4539666dbf67b43a3961b76ea.jpg

(Made In Postman)

4、http://localhost:8080/emp/emp/abc

输出:path = /emp/emp/*

原理:/emp/**的通配符多于/emp*,最终选择了/emp/*

3c431f74bb0703d6fa99b1637451aebaa8e.jpg

(Made In Postman)

5、方法多的会优先么?

@RequestMapping(value = "/emp/**", method = {RequestMethod.GET})
public ModelAndView update1(ModelAndView mav) {
mav.setViewName("employee");
mav.addObject("path", "/emp/emp/**[GET]");
return mav;
}@RequestMapping(value = "/emp/**", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView update2(ModelAndView mav) {
mav.setViewName("employee");
mav.addObject("path", "/emp/emp/**[GET, POST]");
return mav;
}

http://localhost:8080/emp/emp/abc/123

请求:GET

输出:java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'xxx'

原理:当条件是GET或者POST,当前request是GET请求,getMatchingCondition()方法将返回GET条件,即当前reqeust请求的GET条件,于是系统中找到了2个完全一样的GET方法,报错退出。

我们将GET修改为POST请求后,输出:path = /emp/emp/**[GET, POST]

原理:条件是GET或者POST,当前request是POST请求,getMatchingCondition()方法将返回POST条件,唯一找到上面的update2()方法,执行成功。

d25ae34d4f9c066efd8033a17da1a1c20f0.jpg

(Made In Postman)

所以,一定要注意,匹配时使用getMatchingCondition(request)返回的当前reqeust的条件进行匹配,而不是我们配置的所有条件。

原文出处:http://my.oschina.net/zudajun

转载于:https://my.oschina.net/zudajun/blog/1829630

相关文章:

在ASP.NET中指定出错页面,不让代码外泄!

在ASP.NET中指定出错页面&#xff0c;不让代码外泄&#xff01;在ASP.NET中原始的出错页面会暴露部分源代码&#xff0c;由此带来了潜在的安全隐患。ASP.NET允许应用程序出错时显示用户指定的页面&#xff0c;方法是在web.config文件中修改配置信息。 <? xml version"…

【经验】提高github的下载(克隆)速度

原理&#xff1a; 利用码云来克隆GitHub项目&#xff0c;然后从码云下载。 参见博客&#xff1a; 最详细的图文教程帮你解决GitHub下载慢或下载失败问题&#xff08;2019.8.10亲测有效&#xff09;

Erlang之父给程序员的两点忠告 | 缅怀

整理 | 伍杏玲出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;北京时间 4月20日&#xff0c;据Erlang Solutions、Erlang Factories的创始人Francesco Cesarini的推特称&#xff0c;Erlang之父Joe Armstrong于4月20日去世&#xff0c;享年68岁。Francesco怀念道&am…

如何为你的博客文章自动添加版权信息?

转自:http://www.chinaz.com/web/2016/0616/541282.shtml https://www.feifanblog.com/ 好长时间没有分享关于网络技术的文章了&#xff0c;昨天有位朋友问我&#xff1a;“你博客每篇文章末尾的版权声明都是我自己手动添加的吗&#xff1f;”&#xff0c;看到这个问题我想很多…

2011-11-27

大三&#xff0c;这个关键时刻&#xff0c;目前自己正在写软件课程设计程序-教材订购系统&#xff0c;加油&#xff01;努力&#xff01;转载于:https://blog.51cto.com/3122770/728090

【Qt】Qt编码风格、命名约定

1、类名称以大写字母开头、函数名称以小写字母开头&#xff0c;并使用驼峰命名法&#xff1b;类名使用名词或名词短语、函数名使用动词或者动词短语&#xff1b; 2、常量应当大写并且尽可能在类的作用域内创建成枚举值&#xff0c;全局常量和宏通常是大写&#xff1b; 3、布尔变…

UC伯克利开源照片“隐写术”StegaStamp,打印照片能当二维码用!| 技术头条

参加「CTA 核心技术及应用峰会」&#xff0c;请扫码报名 ↑↑↑作者 |CV君来源 | 我爱计算机视觉&#xff08;id&#xff1a;aicvml&#xff09;要说目前最火的用到手机摄像头的应用是什么&#xff0c;毫无疑问非二维码识别莫属了。微信带起来的二维码热&#xff0c;几乎已经改…

你为世界杯而战,我为生活而战!

这什么23时直播揭幕战俄罗斯vs沙特 开始时间!很庆幸见到了本世界坏中第1个球!(四年一次比赛&#xff0c;四年一博!)接下来看看我在奋斗什么?(人生随时进行比赛&#xff0c;错了、对了、再错了、再对了、只要不放弃学习就有未来!)********************************************…

【C++】重载、重写、隐藏

1、重载&#xff1a;在同一个作用域中两个及以上的函数名相同但是参数个数或类型不同时构成重载&#xff0c;重载的本质是&#xff0c;编译后的函数会有不同的签名&#xff1b; 2、重写&#xff1a;这是类继承中的概念&#xff0c;基类中virtual标记的函数&#xff0c;在派生类…

有了它,AI甚至可以让你知道对方是否真的爱你?

“你到底爱不爱我&#xff1f;”这或许是恋爱双方出现频率最高的问题&#xff0c;想要知道对方大脑在想什么&#xff0c;并不是什么天方夜谭&#xff0c;通过科学技术还真的有望实现。不过&#xff0c;让如此大胆的想法变为现实&#xff0c;我们需要借助什么技术呢&#xff1f;…

基于jwt的用户登录认证

最近在app的开发过程中&#xff0c;做了一个基于token的用户登录认证&#xff0c;使用vuenodemongoDB进行的开发&#xff0c;前来总结一下。 token认证流程&#xff1a; 1&#xff1a;用户输入用户名和密码&#xff0c;进行登录操作&#xff0c;发送登录信息到服务器端。 2&…

跨平台网络游戏趋势和优势

跨平台网络游戏趋势和优势 前几年还是网页游戏蓬勃发展的状态&#xff0c;就有分析指出从明年开始网页游戏市场已经饱和&#xff0c;想想几年前客户端游戏也是同样的窘境&#xff0c;如果将桌面、移动设备、网页统称一个词汇的话&#xff0c;那就是终端&#xff0c;现在各种的终…

IAR生产HEX文件

刚刚接触IAR&#xff0c;编译默认生成的是.a90文件&#xff0c;但直接用avr studio下载时提示非正规HEX文件&#xff0c;不给我下载&#xff0c;郁闷了。 其实也简单&#xff0c;如下图所示配置文件后面加几行字就OK了。 //Output File -Ointel-extended,(XDATA).eep -Ointel-e…

【Qt】重新认识QObject

1、QObject父子对象的是设计模式中组合模式的实现。父对象和基对象不是一个概念&#xff0c;前者是在运行时管理子对象&#xff0c;是动态的&#xff0c;后者是在编译时判定派生关系&#xff0c;是静态的。 2、每个QObject至多有一个父对象&#xff0c;父对象中将子对象的指针放…

抛弃VS Code,我还能用啥编辑器?| 技术头条

作者 | Abhishek Prakash译者 | 苏本如责编 | 屠敏转载自 CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;微软的VS Code无论对Web开发人员或其他程序员来说&#xff0c;都是一款优秀的代码编辑器。凭借其出色的功能&#xff0c;VS Code被认为是最好的开源代码编辑器之一。…

WebClient UI和Tomcat的启动器

WebClient UI 我们在WebClient UI的开发工具里点了Test按钮&#xff0c; 会在浏览器以测试模式打开选中的view。这背后发生了什么事&#xff1f;注意浏览器地址栏的bspwd_cmp_test&#xff0c;这是什么东西&#xff1f;Jerry倾向于把它当作是CRM WebClient UI component在测试模…

通过网络安装VMware ESX Server 5

VMware ESX Server的宿主系统是基于Linux定制开发的&#xff0c;所以&#xff0c;它也和所有的Linux系统一样&#xff0c;除了支持光盘引导安装外&#xff0c;还支持基于PXE的网络引导与安装。在VMware ESX Server 4及其以前的版本中&#xff0c;在通过网络远程安装的时候&…

实战:CNN+BLSTM+CTC的验证码识别从训练到部署 | 技术头条

作者|_Coriander转载自Jerry的算法和NLP&#xff08;ID: gh_36eba310d433&#xff09;1.前言本项目适用于Python3.6&#xff0c;GPU>NVIDIA GTX1050Ti&#xff0c;原master分支已经正式切换为CNNLSTMCTC的版本了&#xff0c;是时候写一篇新的文章了。长话短说&#xff0c;开…

MySql练习题参考答案

表结构&#xff1a; /*Navicat Premium Data TransferSource Server : localhostSource Server Type : MySQLSource Server Version : 50624Source Host : localhostSource Database : sqlexamTarget Server Type : MySQLTarget Server Version …

【Qt】Qt源码中涉及到的设计模式

1、单例模式 qApp宏返回指向QApplication的单例 #define qApp (static_cast<QApplication *>(QCoreApplication::instance()))2、MVC模型视图控制器框架 模型是应用程序对象、视图是它的屏幕显示、控制器定义了用户界面对用户输入的反应。 Qt的模型视图框架是经典的MV…

从当前日期算起,获取几天前的日期和几个月前的日期

/*** 从当前日期算起&#xff0c;获取N天前的日期&#xff08;当前日不算在内&#xff09;&#xff0c;日期格式为yyyy-MM-dd* * param daily 天数* return */public static String getDateByDay(Integer daily) {Date date new Date();int year Integer.parseInt(new Simple…

清华大学人工智能研究院成立听觉智能研究中心,将专注基础研究和成果产业化

4月22日&#xff0c;清华大学人工智能研究院听觉智能研究中心&#xff08;以下简称听觉中心&#xff09;成立仪式暨学术前沿报告会在清华大学FIT楼举行。 清华大学副校长、人工智能研究院管委会主任尤政院士在致辞中表示&#xff0c;成立听觉中心是清华大学加速推进人工智能发…

两道面试题,带你解析Java类加载机制

2019独角兽企业重金招聘Python工程师标准>>> 在许多Java面试中&#xff0c;我们经常会看到关于Java类加载机制的考察&#xff0c;例如下面这道题&#xff1a; class Grandpa {static{System.out.println("爷爷在静态代码块");} } class Father extend…

【Qt】监视文件和目录的修改:QFileSystemWatcher

一、说明 QFileSystemWatcher用于监视指定文件或目录列表的更改。 注意:监视文件或目录时需要打开相应的文件描述符,因此被监视的文件或目录数据是有限的,受系统允许的文件描述符数据等限制。 二、常用成员函数 1、public 构造、析构函数: QFileSystemWatcher(const QS…

asp.net 2.0中新增的AppendDataBoundItems .dropdownlist 添加第一项

在asp.net 2.0中&#xff0c;新增了一个AppendDataBoundItems属性&#xff0c;十分方便&#xff0c;使可以在执行数据绑定之前将项添加到 listcontrol 对象中。执行数据绑定之后&#xff0c;项集合中包含数据源中的项以及以前添加的项。如果不在绑定数据之前清除列表项&#xf…

用Python实现OpenCV特征提取与图像检索 | Demo

参加「CTA 核心技术及应用峰会」&#xff0c;请扫码报名 ↑↑↑作者&#xff5c;Andrey Nikishaev翻译 | 张蔚敏审校 | reason_W来源 | Python大本营&#xff08;id&#xff1a;pythonnews)“拍立淘”“一键识花”“街景匹配”……不知道大家在使用这些神奇的功能的时候&#x…

【ffmpeg】编译时报错:error: undefined reference to `av...

1、问题描述 昨天使用ffmpeg库编译demo一切正常,今天再次链接ffmpeg库时报了一堆错误: error: undefined reference to `av_frame_alloc() error: undefined reference to `avio_close(AVIOContext*) error: undefined reference to `avcodec_find_encoder(AVCodecID) erro…

Spring Initializr 构建Spring Boot/Cloud工程

2019独角兽企业重金招聘Python工程师标准>>> 在之前的所有Spring Boot和Spring Cloud相关博文中&#xff0c;都会涉及Spring Boot工程的创建。而创建的方式多种多样&#xff0c;我们可以通过Maven来手工构建或是通过脚手架等方式快速搭建&#xff0c;也可以通过《Sp…

linux下用phpize给PHP动态添加扩展

使用php的常见问题是&#xff1a;编译php时忘记添加某扩展&#xff0c;后来想添加扩展&#xff0c;但是因为安装php后又装了一些东西如PEAR等&#xff0c;不想删除目录重装&#xff0c;别说&#xff0c;php还真有这样的功能。 我没有在手册中看到。 如我想增加bcmath支持&…

“996 是福利,007 才是常态”?!千万程序员怒怼每日优鲜!

呔&#xff01;热度不是你想蹭&#xff0c;想蹭就能蹭……作者 | 仲培艺封图 | CSDN 付费下载自东方IC出品 | 程序人生&#xff08;ID&#xff1a;coder_life&#xff09;App Store 评分一日之间从 4.5 断崖直降至 1.5&#xff0c;每日优鲜这是怎么了&#xff1f;究其原因——大…