精华内容
下载资源
问答
  • 如何进行CodeView

    千次阅读 2019-04-17 16:59:04
    关于Code Review的重要性,我相信好的工程师都能认识到。 参考 "让Code Review称为一种习惯" 和 "从Code Review谈如何做技术"。 同时引用一下有人对Google Code Review的描述: The biggest thing that makes Google...

    关于Code Review的重要性,我相信好的工程师都能认识到。 参考 "让Code Review称为一种习惯" 和 "从Code Review谈如何做技术"。

    同时引用一下有人对Google Code Review的描述:

    The biggest thing that makes Google’s code so good is simple: code review. At Google, no code, for any product, for any project, gets checked in until it gets a positive review.

    Code Review 主要Revivew什么

    Architecture/Design

    单一职责原则.

    这是经常被违背的原则。一个类只能干一个事情, 一个方法最好也只干一件事情。 比较常见的违背是处理促销的时候,同时处理验券逻辑。

    行为是否统一

    比如缓存是否统一,错误处理是否统一, 日志打印是否统一, 方法实现是否统一等等。

    同一逻辑/同一行为 有没有走同一Code Path?低质量程序的另一个特征是,同一行为/同一逻辑,因为出现在不同的地方或者被不同的方式触发,没有走同一Code Path 或者各处有一份copy的实现, 导致非常难以维护。

    代码污染

    使用魔数,例如直接比对数字。编写不容易阅读的代码,应用了很多复杂的设计。

    重复代码

    主要看有没有把公用组件,可复用的代码,函数抽取出来。

    Open/Closed 原则

    就是好不好扩展。 Open for extension, closed for modification.(做好接口设计会很好解决这些问题)。

    面向接口编程 和 不是 面向实现编程

    主要就是看有没有进行合适的抽象, 把一些行为抽象为接口。

    健壮性

    对Corner case有没有考虑完整,逻辑是否健壮?有没有潜在的bug?

    有没有内存泄漏?有没有循环依赖?(针对特定语言,比如Objective-C) ?有没有野指针?

    有没有考虑线程安全性, 数据访问的一致性

    错误处理

    有没有很好的Error Handling?比如Price接口出错。

    有没有增加必要的监控,及时报警。

    改动是不是对代码的提升

    新的改动是打补丁,让代码质量继续恶化,还是对代码质量做了修复?

    效率/性能

    两层循环,数据请求量的限制,较大数据等耗时操作是否处理得当。

    关键算法的时间复杂度多少?有没有可能有潜在的性能瓶颈。

    复杂需求,是否有必要的设计, 可预见的效率问题, 开发模式一致性的问题 应该尽早在Design Review阶段解决。如果Design阶段没有解决,那至少在Code Review阶段也要把它找出来。

    Style

    可读性

    衡量可读性的可以有很好实践的标准,就是Reviewer能否非常容易的理解这个代码。 如果不是,那意味着代码的可读性要进行改进。

    命名对可读性非常重要,我倾向于函数名/方法名长一点都没关系,必须是能自我阐述的。

    英语用词尽量准确一点(哪怕有时候需要借助Google Translate,是值得的)

    函数长度/类长度

    函数太长的不好阅读。 类太长了,比如超过了1000行,那你要看一下是否违反的“单一职责” 原则。

    恰到好处的注释。 但更多我看到比较差质量的工程的一个特点是缺少注释。

    参数个数(不要超过5个参数)

    Review Your Own Code First

    跟著名的橡皮鸭调试法(Rubber Duck Debugging)一样,每次提交前整体把自己的代码过一遍非常有帮助,尤其是看看有没有犯低级错误。

    如何进行Code Review

    多问问题。多问 “这块儿是怎么工作的?” “如果有XXX case,你这个怎么处理?”

    每次提交的代码不要太多,最好不要超过1000行,否则review起来效率会非常低。

    当面讨论代替Comments。 大部分情况下小组内的同事是坐在一起的,face to face的 code review是非常有效的。

    区分重点,不要舍本逐末。 优先抓住 设计,可读性,健壮性等重点问题。

    Code Review的意识

    作为一个Developer , 不仅要Deliver working code, 还要Deliver maintainable code.

    必要时进行重构,随着项目的迭代,在计划新增功能的同时,开发要主动计划重构的工作项。

    开放的心态,虚心接受大家的Review Comments。

    审核人及职责分配
    所谓职责归属:即指谁对这个Code负责

    提交人

    审核人

    职责归属

    P2.1及以下

    P2.3

    审核人

    P2.2及以上

    P2.3

    提交人&审核人

    p2.3及以上

    p3.1/p3.2

    提交人&审核人

    对于comments的检查。周会上抽查。

    审核流程
    开始
    RD完成开发自测
    自查不过
    自查通过
    Review your
    own code
    RD按照comment意见修改
    重新自测并重新提交代码


    是否可以Merge
    Merge至主分支
    1.只有主分支权限的人才能Merge

    结束


    是否需要
    代码走读
    RD发起Code Review
    跟著名的橡皮鸭调试法(Rubber Duck Debugging)一样,每次提交前整体把自己的代码过一遍非常有帮助,尤其是看看有没有犯低级错误。
    是否走读标准
    1.修改了核心链路代码;
    2.修改代码行数> 600 行;
    3.修改公用业务组件或逻辑;
    4.审核人提出走读意见;
    1.按照要求选择审核人;
    2.设置PR完成时间;
    3.补充code主要变动范围
    4.跟进PR完成状态
    发起人组织
    Code Review会
    stash上同步记录 commits

    Review
    是否通过
    merge条件:
    1.approve >= 2人;
    2.comment 都完成修改;

    备注标签
    流程节点
    分支判断
    图示例

    常见代码开发规范

    thrift服务提供一般情况都需要在finally里输出info级的入参和返回参数的日志

    实现Object的toString方法而不是使用json序列化工具输出日志

    Integer的等值使用equals而不是==

    使用apache commons 包而不是自己造轮子

    组织参数抽取方法来完成而不要在service里面写,方法命名规范成buildXXXXX

    线程池的使用需要统一到一个Utils类中,避免线程池定义泛滥。

    所有远程服务调用都需要封装delegate类,禁止直接使用octo客户端进行调用。在真正调用一般都要finally里输出info日志 入参和返回值。

    静态变量需要统一在一个类中定义。

    OCTO生成的类不要传递到service层,构造一个本地类进行使用。

    不要把配置写在代码里,充分利用MCC和*.properties

    MCC里配置开关和频繁修改的属性,长期不修改的放到*.properties

    给方法起名字是为了自己和其他人能看懂

    变量命名以及方法名不要使用拼音缩写

    Json序列化统一使用travel-insurance-common表里的JsonUtils

    不要在ThriftService里写业务逻辑。下沉service

    不要拷贝代码

    执行update语句一定要注意 where 条件里 一定要走索引,这句话的意思是,把where 条件单独放一个select里 然后explain一下,如果是全表扫描 那这个SQL会锁全表。

    不要在返回boolean的方法里面写if (xxxxx&xxxx){return true}else{ return false}

    永远不要在事务里rpc

    抛出异常最好子类化,这样从cat看到异常就知道是啥原因。

    逻辑嵌套不要超过3层

    @Transactional(rollbackFor =Exception.class) 使用事务的时候 一般情况这么用。

    查询不要使用mybatis生成的condition来拼,mybatis生成工具只负责生成表的po和最基本的insert 和update ,如果有分表,请所有sql全部手写,必须带分表键。

    不要在static{}代码块里做初始化动作,禁止在static{}代码块里rpc

    转载于:https://blog.51cto.com/1348916/2380274

    展开全文
  • @RequestParam注解:用于绑定单个请求参数,常用于简单类型参数(Integer、String、Float......)绑定。 不使用@ReuestParam要求请求参数的名称和方法形参名一致方可进行绑定。 对于简单类型参数中的日期型,...

    知识回顾

    springmvc框架
    用户请求url到DispatcherServlet前端控制器,相当于中央调度器,降低系统各组件之间的耦合度。
    DispatcherServlet前端控制器通过HandlerMapping处理器映射器根据url找到Handler。
    DispatcherServlet前端控制器通过HandlerAdapter处理器适配器执行Handler。
    DispatcherServlet前端控制器得到Handler返回的ModelAndView通过视图解析器ViewResolver进行视图解析。
    视图解析:将程序中写的逻辑视图名,转成真正的视图(springmvc通过view表示各个类型不同的视图)。DispatcherServlet前端控制器调用View的渲染方法进行视图渲染(将ModelAndView中的model放到request域)

    重点:SpringMVC的注解开发,企业中常用springmvc的注解开发
    使用专门注解处理器映射器(RequestMappingHandlerMapping)和处理器适配器(RequestMappingHandlerAdapter)
    注意:使用<mvc:annotation-driven/>可以替代上边的注解处理器映射器和注解处理器适配器的配置。
    在Handler(controller)中定义很多的方法,一个方法通过@RequestMapping对url进行映射。
    方法返回值:ModelAndView、String(jsp的逻辑视图名)、void(通过response将数据输出成json)。
    方法输入参数(形参):springmvc需要将请求的key/value(串,id=001&id=002)、解析绑定到Handler(controller)中方法的形参上。
    springmvc默认支持多类型的参数绑定。
    默认支持的类型:HttpServletRequest、HttpServletResponse、HttpSession、Model(用于将数据填充到request域)。
    @RequestParam注解:用于绑定单个请求参数,常用于简单类型参数(Integer、String、Float......)绑定。
    不使用@ReuestParam要求请求参数的名称和方法形参名一致方可进行绑定。
    对于简单类型参数中的日期型,建议使用自定义参数绑定,对日期类型数据个性化定义日期的格式。
    自定义参数绑定:建议使用Converter进行参数绑定。
    还可以绑定pojo、包装的pojo.

    高级知识清单

    注解开发:
      数据回显:表单提交错误,重新回到表单,用户重新填写数据,刚才提交的参数在页面上回显。
      集合类型(String[]、List<>、Map)的参数绑定
      springmvc上传图片(重点)
      json数据交互(提交json数据、响应json数据)(重点)
      Validation(springmvc使用校验方式: 使用Hibernate Validator(和Hibernate的ORM没有任何关系) )
    拦截器(用于权限控制)

    数据回显

    需求  

    表单提交出现错误,重新回到表单,用户重新填写数据,刚才提交的数据在页面上显示。

    对简单类型的数据回显

    对商品修改数据回显:
    注意在进入修改页面的controller方法中和提交修改商品信息方法model.addAttribute方法设置key一致。

    修改商品显示方法:
     @RequestMapping(value="/editItems",method={RequestMethod.GET}) 
    	  public String editItems(Model model,Integer id) throws Exception{
    	  
    	 // 将id传递到页面
    	  model.addAttribute("id",id);
    		  
    	  // 调用Service查询商品信息 
    	  ItemsCustom itemsCustom = itemsService.findItemsById(id); //将模型数据传到jsp 
    	  model.addAttribute("item",itemsCustom);
    	  
    	  //return "editItem_2";
    	  return "editItem"; 
    	  }
    修改页面
    <form id="itemForm" action="${pageContext.request.contextPath }/items/editItemSubmit.action" method="post" >
    <input type="hidden" name="id" value="${id }"/>
    修改商品信息:
    <table width="100%" border=1>
    <tr>
    	<td>商品名称</td>
    	<td><input type="text" name="name" value="${item.name }"/></td>
    </tr>
     ......
    修改商品提交方法
    @RequestMapping("/editItemSubmit")
    	public String editItemSubmit(Model model,Integer id, ItemsCustom itemsCustom) throws Exception {
    
    		// 调用Service接口更新商品信息
    		itemsService.updateItems(id, itemsCustom);
    
    		// 进行数据回显
    		model.addAttribute("id", id);
    		
    		// 提交后回到修改页面
    		return "editItem";
    	}
    提交后查看网页源码

    可以发现只有id回显到了修改页面
    其过程原理是这样的:
    第一次点击修改跳转到修改页面,从request域取值为name的id的属性赋值。
    点击提交后将name的值(请求参数)提交到提交方法中为形参id赋值。
    然后在提交方法内将id存入request域,再跳转到编辑页面,进行回显。

    pojo数据类型回显

    回显方法1:

    使用model.addAttribute方法进行数据回显
    @RequestMapping("/editItemSubmit")
    	public String editItemSubmit(Model model,Integer id, ItemsCustom itemsCustom) throws Exception {
    
    		// 调用Service接口更新商品信息
    		itemsService.updateItems(id, itemsCustom);
    
    		// 进行数据回显
    		model.addAttribute("id", id);
    		model.addAttribute("item",itemsCustom);
    		
    		// 提交后回到修改页面
    		return "editItem";
    }
    过程就是:提交后表单数据传入到提交方法的形参id和itemsCustom中。
    然后将形参存入到request域,属性命名保持与编辑页面取数据的名称一致。
    跳转到编辑页面即可从request取值 进行回显。

    回显方法2:

    使用@ModelAttribute, 作用于将请求pojo数据放到Model中回显到页面
    	public String editItemSubmit(Model model,Integer id, @ModelAttribute(value="item")ItemsCustom itemsCustom) throws Exception {
    在@ModelAttribute方法指定的名称就是要填充到Model中的key,在页面上就要通过key取数据。

    @ModelAttribute将方法返回值传到页面

    需求:商品类别信息在商品信息页面展示
    // 单独将商品类型的方法提出来,将方法返回值填充到request,在页面显示
    	@ModelAttribute("itemsType")
    	public Map<String, String> getItemsType() throws Exception{
    		HashMap<String, String> itemsType = new HashMap<String, String>();
    		itemsType.put("001", "数码");
    		itemsType.put("002", "服装");
    		return itemsType;
    	}
    页面
    商品类别:
    	<select> 
    		<c:forEach items="${itemsType }" var="item">
    			<option value="${item.key }">${item.value }</option>
    		</c:forEach>
    	</select>
    使用@ModelAttribute将公用的取数据的方法返回值传递到页面,不用在controller的每一个方法中通过Model将数据传到页面。

    参数绑定集合类型

    绑定数组

    需求:在商品查询列表页面,用户选择要删除的商品,批量删除商品。
    在controller方法中如何将批量提交的数据绑定成数组类型。

    页面定义

      <td><input type="checkbox" name="delete_id" value="${item.id }"/></td>

    controller方法定义

    // 删除商品
    	@RequestMapping("/deleteItems")
    	public String deleteItems(Integer[] delete_id) throws Exception{
    		System.out.println(delete_id);
    		return "success";
    	}
    运行结果:

    绑定List<Object>

    需求:批量修改商品信息提交
    先进入批量修改商品页面,填写信息,点击提交

    页面定义

    <c:forEach items="${itemsList }" var="item" varStatus="s">
    <tr>
    	<td><input type="text" name="itemsList[${s.index }].name" value="${item.name }"/></td>
    	<td><input type="text" name="itemsList[${s.index }].price" value="${item.price }"/></td>
    	......
    </tr>
    注释:name值中的itemList
    itemsList:controller方法形参包装类型中list的属性名
    itemsList[0]或itemsList[0]......[]中是序号,从0开始
    itemsList[index].name就是controller方法形参包装类型中list中pojo的属性名。

    Controller方法定义

    使用包装类型接收页面批量提交的数据,绑定成list
    public class ItemsQueryVo {
    
    	// 商品信息
    	private ItemsCustom itemsCustom;
    
    	// 定义一个List
    	private List<ItemsCustom> itemsList;
            ......
    }
    使用包装类型作为形参 接收参数
    // 批量修改商品提交
    	@RequestMapping("/editItemsListSubmit")
        public String editItemsListSubmit(ItemsQueryVo itemsQueryVo){
    		return "success";
    	}

    springmvc和struts的区别

    SpringMVC是通过方法的形参接受参数,在使用时可以以单例方式使用,建议使用单例。
    Struts是通过成员变量接收参数,在使用时必须以多例方式使用。

    SpringMVC是基于方法开发,Struts是基于类开发。
    SpringMVC将一个请求的Method和Handler进行关联绑定,一个method对应一个Handler。
    SpringMVC开发是以方法为单位进行开发,方法更贴近Service(业务方法)。
    经过实际测试,发现Struts标签解析速度比较慢,建议在实际开发中使用jstl。

    商品图片上传

    需求

    在商品修改页面,增加图片上传的功能。
    操作流程:
        用户进入商品修改页面
        上传图片
        点击提交(提交的是图片和商品的信息)
        再次进入修改页面,图片在商品修改页面展示

    图片存储的问题

    切记:不要将图片上传到工程目录,不方便进行工程维护。
    实际电商项目中使用专门的图片服务器(比如Apache、Tomcat)
    在Tomcat中进行虚拟目录的设置
    设置方法如下:
    在Tomcat下,找到conf文件下的server.xml,打开,在<Host>和</host>之间加上如下代码,然后就部署完成,重启服务器,浏  览器可以访问:
      <Context path="/虚拟目录名" docBase="目标目录位置" debug="0" reloadable="true" ></Context>
     虚拟目录名:浏览器访问的地址
     目标目录位置:项目所在目录的绝对路径

     reloadable="true" :服务器配置动态加载

    测试:拖进目录中一张图片,启动服务器进行测试:


    注意:图片目录中尽量进行目录分级存储,提高IO的访问速度。

    配置图片上传解析器

    springmvc使用commons-fileupload进行图片上传
    commons-fileupload对应的springmvc的图片上传解析器:org.springframework.web.multipart.commons.CommonsMultipartResolver
    在springmvc.xml中配置如下:
    <!-- 文件上传 -->
    	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    		<!-- 设置上传文件的最大尺寸为5MB -->
    		<property name="maxUploadSize">
    			<value>5242880</value>
    		</property>
    	</bean>
    加入commons-fileupload的jar包

    编写上传图片的页面

    表单属性的enctype要设置为multipart/form-data
    <form id="itemForm" action="${pageContext.request.contextPath }/items/editItemSubmit.action" method="post" enctype="multipart/form-data">
    添加图片上传组件<input type="file" name=""> 其中name的属性值要与控制器中接收图片的方法中的形参一致。
    <td>商品图片</td>
    	<td>
    		<c:if test="${item.pic != null }">
    			<img src="/pic/${item.pic}" width="100px" height="100px"/>
    			<br/>
    		</c:if>
    		<input type="file" name="pictureFile"/>
    	</td>

    编写控制器方法

    @RequestMapping("/editItemSubmit")
    	// public String editItemSubmit(Integer id,ItemsCustom
    	// itemsCustom,ItemsQueryVo itemsQueryVo) throws Exception{
    	public String editItemSubmit(Model model,Integer id, 
    			@ModelAttribute(value="item")ItemsCustom itemsCustom,
    			// 上传图片
    			MultipartFile pictureFile
    			) throws Exception {
    		// 进行数据回显
    		model.addAttribute("id", id);
    		//model.addAttribute("item",itemsCustom);
    		
    		//进行图片上传
    		if (pictureFile!=null && pictureFile.getOriginalFilename()!=null && pictureFile.getOriginalFilename().trim().length()>0) {
    			// 图片上传成功后,将图片的地址写到数据库
    			String filePath = "/Users/liuxun/Desktop/pictures";
    			// 上传文件原始名称
    			String originFileName = pictureFile.getOriginalFilename();
    			// 新的图片的名称
    			String newFileName = UUID.randomUUID()+originFileName.substring(originFileName.lastIndexOf("."));
    			// 新文件
    			File file = new File(filePath+File.separator+newFileName);
    			
    			// 将内存中的文件写入磁盘
    			pictureFile.transferTo(file);
    			
    			// 图片上传成功,将图片地址写入数据库
    			itemsCustom.setPic(newFileName);
    		}
    		
    		// 调用Service接口更新商品信息
    		itemsService.updateItems(id, itemsCustom);
    		
    		// 提交后回到修改页面
    		return "editItem";
    		// 请求重定向
    		//return "redirect:queryItems.action";
    		// 转发
    		// return "forward:queryItems.action";
    	}
    提交后页面效果如下


    json数据交互

    需求

    json数据格式是比较简单和容易理解的,json数据格式常用于远程接口传输,http传输json数据,非常方便页面进行 提交/请求结果解析,对json数据的解析。

    SpringMVC解析json加入json解析包

    springmvc默认采用MappingJacksonHttpMessageConverter对json数据进行转换,需要加入jackson的包如下所示:

    在处理器适配器中注入MappingJacksonHttpMessageConverter

    让处理器适配器支持json数据解析,需要注入MappingJacksonHttpMessageConverter
     <!-- 注解适配器 -->
        <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        	<!-- 在webBindingInitializer中注入自定义属性编辑器,自定义转换器 -->
        	<property name="webBindingInitializer" ref="customBinder"/>
        	<!-- 加入json数据的消息转换器 MappingJacksonHttpMessageConverter依赖的Jackson包 -->
        	<property name="messageConverters">
        		<list>
        			<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
        		</list>
        	</property>
        </bean>
    需要将支持json数据解析的消息转换器注入到注解适配器中。

    @RequestBody和@RespoonseBody

    @RequestBody:将请求的json数据转成Java对象
    @ResponseBody:将Java对象转成json数据输出。

    请求json响应json

    controller方法
    //请求的json响应json,请求商品信息,商品信息使用json格式,输出商品信息
    	@RequestMapping("/requestJson")
    	public @ResponseBody ItemsCustom requestJson(@RequestBody ItemsCustom itemsCustom) throws Exception{
    		return itemsCustom;
    	}
    页面(需要引入jQuery)
    // 请求json响应json
    	function requestJson(){
    		$.ajax({
    			url:"${pageContext.request.contextPath}/requestJson.action",
    			type:"post",
    			contentType:"application/json;charset=utf-8",
    			//请求json数据 使用json表示商品信息
    			data:'{"name":"手机","price":1999}',
    			success:function(data){
    				alert(data.name);
    			}
    		});
    	}
    测试:

    请求key/value响应json

    controller方法
    @RequestMapping("/responseJson")
    	public @ResponseBody ItemsCustom responseJson(ItemsCustom itemsCustom) throws Exception{
    		return itemsCustom;
    	}
    页面
    // 请求key/value响应json
    	function responseJson(){
    		$.ajax({
    			url:"${pageContext.request.contextPath}/responseJson.action",
    			type:"post",
    			//contentType:"application/json;charset=utf-8",
    			//请求key/value数据 使用地址拼接表示商品信息
    			data:"name=手机&price=1999",
    			success:function(data){
    				alert(data.name);
    			}
    		});
    	}
    测试:

    总结:

    如果前端处理没有特殊要求建议使用第二种,请求key/value, 返回json, 方便客户端解析请求结果。

    validation校验

    对前台的校验大多数通过js在页面校验,这种方法比较简单,如果出于安全性考虑,还需要在后台进行校验。
    SpringMVC使用JSR-303(JavaEE规范第一部分)校验规范,SpringMVC使用的是Hibernate Validator

    加入Hibernate Validator的jar


    在处理器适配器中配置校验器

    在注解适配器中注入自定义的webBinder
      <!-- 注解适配器 -->
        <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        	<!-- 在webBindingInitializer中注入自定义属性编辑器,自定义转换器 -->
        	<property name="webBindingInitializer" ref="customBinder"/>
        	<!-- 加入json数据的消息转换器 MappingJacksonHttpMessageConverter依赖的Jackson包 -->
        	<property name="messageConverters">
        		<list>
        			<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
        		</list>
        	</property>
        </bean>
    在webBinder中注入自定义的校验器
    <!-- 自定义webBinder -->
    	<bean id="customBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
    		<!-- 使用converter进行参数转换 -->
    		<property name="conversionService" ref="conversionService"/>
    		<!-- 配置Validator -->
    		<property name="validator" ref="validator"/>
    	</bean>
    配置自定义校验器
    <!-- 校验器 -->
    	<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
    		<!-- 校验器 -->
    		<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
    		<!-- 指定校验所使用的资源文件,如果不指定则默认使用classpath下的ValidationMessages.properties-->
    		<property name="validationMessageSource" ref="messageSource"/>
    	</bean>
    	<!-- 校验错误信息配置文件 -->
    	<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    		<!-- 资源文件名 -->
    		<property name="basenames">
    			<list>
    				<value>classpath:CustomValidationMessages</value>
    			</list>
    		</property>
    		<!-- 资源文件编码方式 -->
    		<property name="fileEncodings" value="utf-8"/>
    		<!-- 对资源文件内容缓存时间,单位秒 -->
    		<property name="cacheSeconds" value="120"/>
    	</bean>

    创建CustomValidationMessages

    在classpath下新建CustomValidationMessages.properties

    校验规则

    需求:编辑商品信息提交时校验,商品生产日期不能为空,商品名称在1-30个字符之间
    public class Items {
        private Integer id;
    
        //商品名称的长度限制在1到30个字符
        @Size(min=1,max=30,message="{items.name.length.error}")
        private String name;
    
        private Float price;
    
        private String pic;
    
        //请输入商品生产日期
        @NotNull(message="{items.createtime.is.notnull}")
        private Date createtime;
        ......
    }

    捕获错误

    需要修改controller方法,在需要校验的形参pojo前边加@Validated, 在pojo的后边加上参数BindingResult用来接收错误信息
    @RequestMapping("/editItemSubmit")
        //注意:每个校验pojo的前边必须加@Validated, 每个校验的pojo后边必须加BindingResult接收错误信息
    	public String editItemSubmit(Model model,Integer id, 
    			@Validated @ModelAttribute(value="item")ItemsCustom itemsCustom,
    			BindingResult bindingResult,
    			// 上传图片
    			MultipartFile pictureFile
    			) throws Exception {
    错误信息输出
    // 输出错误信息
    		// 如果参数绑定时有错误
    		if (bindingResult.hasErrors()) {
    			// 获取错误
    			List<ObjectError> errors = bindingResult.getAllErrors();
    			// 准备在页面输出errors,页面使用jstl遍历
    			model.addAttribute("errors",errors);
    			for (ObjectError error : errors) {
    				// 输出错误信息
    				System.out.println(error.getDefaultMessage());
    			}
    			// 如果校验错误,回到商品修改页面
    			return "editItem";
    		}

    在页面上展示错误

    <!-- 错误信息 -->
    <div style="color: red;">
     <c:forEach items="${errors }" var="error">
    	${error.defaultMessage }<br>
    </c:forEach>
    </div>
    效果如下:

    分组校验

    需求:针对不同的controller方法通过分组校验达到个性化校验的目的,修改商品的修改功能,只校验商品的生产日期不能为空。
    第一步:创建分组接口
    package liuxun.ssm.controller.validation;
    /**
     * 校验分组: 用于商品修改的校验分组
     * @author liuxun
     *
     */
    public interface ValidGroup1 {
    	//接口不定义方法,就是只标识 哪些校验规则属于ValidGroup1分组
    }
    
    第二步:定义校验规则属于哪个分组
     //请输入商品生产日期
        //通过groups指定此校验属于哪个分组,可以指定多个分组 之间用逗号隔开groups={ValidGroup1.class,ValidGroup2.class}
        @NotNull(message="{items.createtime.is.notnull}",groups={ValidGroup1.class})
        private Date createtime;
    第三步:在controller方法中使用定义的校验分组
    public String editItemSubmit(Model model,Integer id, 
    			@Validated(value={ValidGroup1.class}) @ModelAttribute(value="item")ItemsCustom itemsCustom,
    			BindingResult bindingResult,
    			// 上传图片
    			MultipartFile pictureFile
    			) throws Exception {
    运行如下:


    统一异常处理

    需求

    一般项目中都需要做异常处理,基于系统架构的设计考虑,使用统一的异常处理方法。
    系统中的异常类型:
    包括预期可能发生的异常、运行时异常(RuntimeException),运行时异常不是预期会发生的。
    针对预期可能发生的异常,在代码中手动处理异常可以try/catch捕获,可以向上抛出。
    针对运行时异常,只能通过规范代码质量、在系统测试时详细测试等排除运行时异常。

    统一异常处理解决方案

    自定义异常

    针对预期可能发生的异常,定义很多异常类型,这些异常类型通常继承于Exception。
    这里定义一个系统自定义异常类。
    CustomException:用于测试。
    public class CustomException extends Exception {
    	
    	//异常信息
    	private String message;
    
    	public CustomException(String message) {
    		super();
    		this.message = message;
    	}
    
    	public String getMessage() {
    		return message;
    	}
    
    	public void setMessage(String message) {
    		this.message = message;
    	}
    	
    }

    异常处理

    要在一个统一异常处理的类中要处理系统抛出的所有异常,根据异常类型处理。
    统一异常处理类是什么?
    前端控制器DispatcherServlet在进行HandlerMapping、调用HandlerAdapter执行Handler的过程中,如果遇到异常进行异常处理

    在系统中自定义统一的异常处理器,写系统自己的异常处理代码。

    自定义异常处理器类

    统一异常处理器实现HandlerExceptionResolver接口
    public class CustomExceptionResolver implements HandlerExceptionResolver {
    
    	//前端控制器DispatcherServlet在进行HandlerMapping、调用HandlerAdapter执行Handler过程中,如果遇到异常就会执行此方法
    	@Override
    	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
    			Exception ex) {
    		// 统一处理异常代码
    		// ......
    		return null;
    	}
    
    }

    配置统一异常处理类

    <!-- 定义统一异常处理器类 -->
    	<bean class="liuxun.ssm.exception.CustomExceptionResolver"/>

    异常处理逻辑

    根据不同的异常类型进行异常处理。
    系统自定义的异常类是CustomException,在Controller方法中、Service方法中手动抛出此类异常。
    针对系统自定义的CustomException异常,就可以直接从异常类中获取异常信息,将异常处理在错误页面展示。
    针对非CustomException异常,对这类异常重新构造成一个CustomException,异常信息为"未知错误", 此类错误需要在系统测试阶段进行排除。
    在统一异常处理器CustomExceptionResolver中实现上边逻辑。
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
    			Exception ex) {
    		//输出异常
    		ex.printStackTrace();
    		
    		//统一处理异常代码
    		//针对系统自定义的CustomException异常,就可以直接从异常类中获取异常信息,将异常处理在错误页面进行展示
    		//异常信息
    		String message = null;
    		CustomException customException = null;
    		//如果ex是自定义异常信息,直接取出异常信息
    		if (ex instanceof CustomException) {
    			customException = (CustomException) ex;
    		} else {
    			//针对非CustomException异常,对这类重新构成一个CustomException,异常信息为"未知错误"
    			customException = new CustomException("未知错误");
    		}
    		
    		//错误信息
    		message = customException.getMessage();
    		request.setAttribute("message", message);
    		
    		try {
    			//转向到错误页面
    			request.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(request, response);
    		} catch (ServletException | IOException e) {
    			e.printStackTrace();
    		}
    		
    		return new ModelAndView();
    	}
    展示错误信息页面
    <body>
    ${message }
    </body>

    测试抛出异常由统一异常处理器捕获

    可以在controller方法、service方法、dao实现类中抛出异常,要求dao、service、controller遇到异常全部向上抛出异常,方法向上抛出异常throws Exception
    public ItemsCustom findItemsById(int id) throws Exception {
    		Items items = itemsMapper.selectByPrimaryKey(id);
    		//如果查询的商品信息为空,抛出系统自定义异常
    		if (items == null) {
    			throw new CustomException("修改商品信息不存在");
    		}
    		// 在这里随着需求的变量,需要查询商品的其他相关信息,返回到controller
    
    		ItemsCustom itemsCustom = new ItemsCustom();
    		// 将items的属性拷贝到itemsCustom
    		BeanUtils.copyProperties(items, itemsCustom);
    
    		return itemsCustom;
    	}
    测试修改方法 参数改成一个不存在的值

    流程图解如下:

    RESTful支持

    什么是RESTful

    一、REST,即Representational State Transfer的缩写。我对这个词组的翻译是"表现层状态转化"。
    如果一个架构符合REST原则,就称它为RESTful架构。
    要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计。
    二、资源(Resources)
    REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。
    所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
    所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。
    三、表现层(Representation)
    "资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)
    比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
    URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。
    四、状态转化(State Transfer)
    访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
    互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"
    客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源
    五、综述
    综合上面的解释,我们总结一下什么是RESTful架构:
      (1)每一个URI代表一种资源;
      (2)客户端和服务器之间,传递这种资源的某种表现层;
      (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

    六、误区
    RESTful架构有一些典型的设计误区。
    最常见的一种设计错误,就是URI包含动词。因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。
    举例来说,某个URI是/posts/show/1,其中show是动词,这个URI就设计错了,正确的写法应该是/posts/1,然后用GET方法表示show。
    如果某些动作是HTTP动词表示不了的,你就应该把动作做成一种资源。比如网上汇款,从账户1向账户2汇款500元,错误的URI是:
      POST /accounts/1/transfer/500/to/2
    正确的写法是把动词transfer改成名词transaction,资源不能是动词,但是可以是一种服务:
      POST /transaction HTTP/1.1
      Host: 127.0.0.1
      
      from=1&to=2&amount=500.00
    另一个设计误区,就是在URI中加入版本号:
      http://www.example.com/app/1.0/foo
      http://www.example.com/app/1.1/foo
      http://www.example.com/app/2.0/foo
    因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URI。版本号可以在HTTP请求头信息的Accept字段中进行区分
      Accept: vnd.example-com.foo+json; version=1.0
      Accept: vnd.example-com.foo+json; version=1.1
      Accept: vnd.example-com.foo+json; version=2.0

    url的RESTful实现

    非RESTful的http的url:http://localhost:8080/items/editItems.action?id=1&....
    RESTful的url是简洁的:http:// localhost:8080/items/editItems/1
    参数通过url传递,rest接口返回json数据

    需求

    根据id查看商品信息,商品信息查看的连接使用RESTful方式实现,商品信息以json方式返回。

    第一步更改DispatcherServlet配置

    在web.xml中添加如下配置
    <!-- restful配置 -->
    	<servlet>
    		<servlet-name>springmvc_rest</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<!-- 加载springmvc配置 -->
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<!-- 配置文件的地址 如果不配置contextConfigLocation,
    			 默认查找的配置文件名称classpath下的:servlet名称+"-serlvet.xml"
    			 即:springmvc-serlvet.xml 
    			 -->
    			<param-value>classpath:spring/springmvc.xml</param-value>
    		</init-param>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>springmvc_rest</servlet-name>
    		<!-- rest方式配置为/ -->
    		<url-pattern>/</url-pattern>
    	</servlet-mapping>

    第二步参数通过url传递

    //根据id查看商品信息rest接口
    	 //@RequestMapping中指定restful方式的url参数,参数要使用{}包起来
    	 //@PathVariable将url中的{}包起来的参数和形参进行绑定
    	  @RequestMapping("/viewItems/{id}")
    	  public @ResponseBody ItemsCustom viewItems(@PathVariable("id") Integer id) throws Exception{
    		  //调用Service查询商品信息
    		  ItemsCustom itemsCustom = itemsService.findItemsById(id);
    		  return itemsCustom;
    	  }

    第三步设置静态资源解析

    当DispatcherServlet拦截/开头的所有请求,对静态资源的访问就会报错

    需要在springmvc.xml中通过设置对静态资源解析
    <!-- 静态资源解析 -->
    	<mvc:resources location="/js/" mapping="/js/**"/>
    	<mvc:resources location="/img/" mapping="/img/**"/>
    访问/js/**的url从工程下/js/下解析
    测试结果:

    注意:使用<mvc:annotation-driven/>可以替代注解映射器和注解适配器的配置,而且原先需要向注解适配器中注入webBinder(包含validator校验器以及converter参数转换器或者属性编辑器) 使用<mvc:annotation-driven/>替代后不用webBinder 可以直接设置到<mvc:annotation-driven/>标签的节点属性中,如下所示:

    springmvc拦截器

    拦截器的通常场合

    用户请求到DIspatcherServlet中,DispatcherServlet调用HandlerMapping查找Handler,HandlerMapping返回一个拦截器的链,springmvc的拦截器是通过HandlerMapping发起的。
    在企业开发中,使用拦截器实现用户认证(用户登陆后进行身份校验拦截器),用户权限拦截。

    springmvc拦截器方法

    自定义拦截器,需要实现HandlerInterceptor接口
    public class HandlerInterceptor1 implements HandlerInterceptor{
    	//在执行handler之前执行的
    	//用于用户认证校验、用户权限校验
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            
    		System.out.println("HandlerInterceptor1...preHandle");
    		
    		//如果返回false表示拦截器不继续执行handler,如果返回true表示放行
    		return false;
    	}
    
    	//在执行handler返回modelAndView之前执行
    	//如果需要向页面提供一些公用的数据或配置一些视图信息,使用此方法实现 从modelAndView入手
    	@Override
    	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
    			throws Exception {
    		System.out.println("HandlerInterceptor1...postHandle");
    	}
    
    	//执行handler之后执行此方法
    	//作为系统统一异常处理,进行方法执行性能监控,在preHandler中设置一个时间点 在afterCompletion设置一个时间点 二者时间差就是执行时长
    	//实现系统,统一日志记录
    	@Override
    	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
    			throws Exception {
    		System.out.println("HandlerInterceptor1...afterCompletion");
    	}
    
    }

    测试拦截器

    定义两个拦截器


    配置拦截器

    配置全局拦截器,DispatcherServlet将配置的全局拦截器加载到所有的HandlerMapping映射器。
    在springmvc.xml中配置:
    <!-- 拦截器 -->
    	<mvc:interceptors>
    		<!-- 多个拦截器,顺序执行 -->
    		<mvc:interceptor>
    			<mvc:mapping path="/**"/>
    			<bean class="liuxun.ssm.controller.interceptor.HandlerInterceptor1"></bean>
    		</mvc:interceptor>
    		<mvc:interceptor>
    			<mvc:mapping path="/**"/>
    			<bean class="liuxun.ssm.controller.interceptor.HandlerInterceptor2"></bean>
    		</mvc:interceptor>
    	</mvc:interceptors>

    测试1:1号和2号都放行

    测试结果
    HandlerInterceptor1...preHandle
    HandlerInterceptor2...preHandle

    HandlerInterceptor2...postHandle
    HandlerInterceptor1...postHandle

    HandlerInterceptor2...afterCompletion
    HandlerInterceptor1...afterCompletion
    总结:
    执行preHandler是顺序执行的
    执行postHandler、afterHandler是倒序执行的

    测试2:1号放行,2号不放行

    HandlerInterceptor1...preHandle
    HandlerInterceptor2...preHandle
    HandlerInterceptor1...afterCompletion
    总结:
    如果当前拦截器的preHandler不放行,postHandler、afterHandler都不放行。
    拦截器链中只要有一个拦截器不放行,所有拦截器的postHandler都不会执行,controller就不能执行完成

    测试3:1号和2号都不放行

    测试结果:
    HandlerInterceptor1...preHandle
    总结:
    只有前边的拦截器preHandler方法放行,下边的拦截器的preHandler才会执行

    日志拦截器或异常拦截器要求

    将日志拦截器或异常拦截器放在拦截器链中的第一个位置,且preHandler方法放行。

    拦截器应用(用户认证拦截器)

    需求

    用户访问系统的资源(url),如果用户没有进行身份认证,进行拦截,系统跳转登录页面,如果用户已经认证通过,用户可以访问系统资源。

    用户登录及退出功能开发

    @Controller
    public class LoginController {
    
    	//用户登录提交方法
    	@RequestMapping("/login")
    	public String login(HttpSession session,String usercode,String password) throws Exception{
    		//调用service校验用户账号和密码的正确性
    		//...
    		
    		//如果Service校验通过,将用户身份记录到session
    		session.setAttribute("usercode", usercode);
    		//重定向到商品查询页面
    		return "redirect:/items/queryItems.action";
    	}
    	
    	//用户退出
    	@RequestMapping("/logout")
    	public String logout(HttpSession session) throws Exception{
    		//session失效
    		session.invalidate();
    		//重定向到商品查询页面
    		return "redirect:/items/queryItems.action";
    	}
    }

    用户身份认证拦校验截器

    拦截器实现思路:http请求URL,如果URL是公开地址(不需要认证即可访问的URL) 放行,如果用户在Session中存在 放行,如果用户在Session中不存在,跳转到登录页面。

    自定义登录拦截器

    @Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            
    		//得到请求的url
    		String url = request.getRequestURI();
    		
    		//判断是否是公开地址
    		//实际开发中需要将公开地址配置在配置文件中
    		//...
    		if (url.indexOf("login.action")>=0) {
    			//如果是公开地址 则放行
    			return true;
    		}
    		
    		//判断用户身份在Session中是否存在
    		HttpSession session = request.getSession();
    		String usercode = (String) session.getAttribute("usercode");
    		//如果用户身份在session中存在则放行
    		if (usercode!=null) {
    			return true;
    		}
    		//执行到这里拦截,跳转到登录页面,用户进行身份认证
    		request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
    		
    		//如果返回false表示拦截器不继续执行handler,如果返回true表示放行
    		return false;
    	}
    在springmvc.xml中配置拦截器
        <mvc:interceptors>
    	    <mvc:interceptor>
    		   	<!-- /**可以拦截多层路径 -->
    		   	<mvc:mapping path="/**"/>
    		   	<bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean>
    	    </mvc:interceptor>
    	</mvc:interceptors>
    效果如下:


    实例源代码如下

    源码已经上传GitHub   https://github.com/LX1993728/springmvc_mybatis_1
    工程目录结构如下:




    web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    	id="WebApp_ID" version="2.5">
    	<display-name>springmvc_mybatis_1</display-name>
    
    	<!-- 配置Spring容器监听器 -->
    	<context-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>classpath:spring/applicationContext-*.xml</param-value> 
    	</context-param>
    	<listener>
    		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    	</listener>
    
    	<!-- 前端控制器 -->
    	<servlet>
    		<servlet-name>springmvc</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<!-- 加载springmvc配置 -->
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<!-- 配置文件的地址 如果不配置contextConfigLocation,
    			 默认查找的配置文件名称classpath下的:servlet名称+"-serlvet.xml"
    			 即:springmvc-serlvet.xml 
    			 -->
    			<param-value>classpath:spring/springmvc.xml</param-value>
    		</init-param>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>springmvc</servlet-name>
    		<!-- 可以配置/ ,此工程 所有请求全部由springmvc解析,此种方式可以实现 RESTful方式,需要特殊处理对静态文件的解析不能由springmvc解析 
    			可以配置*.do或*.action,所有请求的url扩展名为.do或.action由springmvc解析,此种方法常用 不可以/*,如果配置/*,返回jsp也由springmvc解析,这是不对的。 -->
    		<url-pattern>*.action</url-pattern>
    	</servlet-mapping>
    	
    	<!-- restful配置 -->
    	<servlet>
    		<servlet-name>springmvc_rest</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<!-- 加载springmvc配置 -->
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<!-- 配置文件的地址 如果不配置contextConfigLocation,
    			 默认查找的配置文件名称classpath下的:servlet名称+"-serlvet.xml"
    			 即:springmvc-serlvet.xml 
    			 -->
    			<param-value>classpath:spring/springmvc.xml</param-value>
    		</init-param>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>springmvc_rest</servlet-name>
    		<!-- rest方式配置为/ -->
    		<url-pattern>/</url-pattern>
    	</servlet-mapping>
    	
    	<!-- post乱码处理 -->
    	<filter>
    		<filter-name>CharacterEncodingFilter</filter-name>
    		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    		<init-param>
    			<param-name>encoding</param-name>
    			<param-value>utf-8</param-value>
    		</init-param>
    	</filter>
    	<filter-mapping>
    		<filter-name>CharacterEncodingFilter</filter-name>
    		<url-pattern>/*</url-pattern>
    	</filter-mapping>
    
    
    	<welcome-file-list>
    		<welcome-file>index.html</welcome-file>
    		<welcome-file>index.htm</welcome-file>
    		<welcome-file>index.jsp</welcome-file>
    		<welcome-file>default.html</welcome-file>
    		<welcome-file>default.htm</welcome-file>
    		<welcome-file>default.jsp</welcome-file>
    	</welcome-file-list>
    </web-app>
    SqlMapConfig.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
    	<!-- 定义别名 -->
    	<typeAliases>
    	 	<!-- 批量别名定义 指定包路径,自动扫描包下边的pojo, 定义别名,别名默认为类名(首字母小写或大写) -->
    		<package name="liuxun.ssm.po" />
    	</typeAliases>
    
    	<!-- 由于使用Spring和mybatis整合的mapper扫描器,这里可以不用配置了 
    	<mappers>
    		<package name="liuxun.ssm.mapper"/>
    	</mappers>
    	-->
    </configuration>
    applicationContext-dao.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans 
    		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
    		http://www.springframework.org/schema/mvc 
    		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
    		http://www.springframework.org/schema/context 
    		http://www.springframework.org/schema/context/spring-context-3.2.xsd 
    		http://www.springframework.org/schema/aop 
    		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
    		http://www.springframework.org/schema/tx 
    		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
    
    	<!-- 加载配置文件 -->
    	<context:property-placeholder location="classpath:db.properties" />
    	<!-- 数据库连接池 -->
    	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    		destroy-method="close">
          <property name="driverClassName" value="${jdbc.driver}"/>
          <property name="url" value="${jdbc.url}"/>
          <property name="username" value="${jdbc.username}"/>
          <property name="password" value="${jdbc.password}"/>
          <property name="maxActive" value="10"/>
          <property name="maxIdle" value="5"/>
    	</bean>
    	
    	<!-- SqlSessionFactory -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    	    <!-- 数据源 -->
    	    <property name="dataSource" ref="dataSource"/>
    	    <!-- mybatis配置文件 -->
    	    <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml"/>
        </bean>
        
        <!-- 
        MapperScannerConfigurer: mapper扫描器,将包下边的mapper接口自动创建代理对象,
        自动创建到Spring容器中,bean的id是mapper的类名(首字母小写)
         -->
         <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    	     <!-- 配置扫描包的路径
    	     如果要扫描多个包,中间使用半角逗号隔开
    	     要求mapper.xml和mapper.java同名且在同一目录
    	      -->
    	     <property name="basePackage" value="liuxun.ssm.mapper"/>
    	     <!-- 使用SqlSessionFactoryBeanName -->
    	     <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
         </bean>
    </beans>
    applicationContext-service.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans 
    		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
    		http://www.springframework.org/schema/mvc 
    		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
    		http://www.springframework.org/schema/context 
    		http://www.springframework.org/schema/context/spring-context-3.2.xsd 
    		http://www.springframework.org/schema/aop 
    		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
    		http://www.springframework.org/schema/tx 
    		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
          
       <!-- 商品管理的Service -->
       <bean  id="itemsService" class="liuxun.ssm.service.impl.ItemsServiceImpl"></bean>
    </beans>
    applicationContext-transaction.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans 
    		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
    		http://www.springframework.org/schema/mvc 
    		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
    		http://www.springframework.org/schema/context 
    		http://www.springframework.org/schema/context/spring-context-3.2.xsd 
    		http://www.springframework.org/schema/aop 
    		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
    		http://www.springframework.org/schema/tx 
    		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
    
    	<!-- 使用声明式事务,可以有效规范代码 -->
    
    	<!-- 事务管理器 -->
    	<bean id="transactionManager"
    		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    		<property name="dataSource" ref="dataSource" />
    	</bean>
    
    	<!-- 通知 -->
    	<tx:advice id="txAdvice" transaction-manager="transactionManager">
    		<tx:attributes>
    			<tx:method name="save*" propagation="REQUIRED" />
    			<tx:method name="insert*" propagation="REQUIRED" />
    			<tx:method name="update*" propagation="REQUIRED" />
    			<tx:method name="delete*" propagation="REQUIRED" />
    			<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
    			<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
    			<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
    		</tx:attributes>
    	</tx:advice>
    
    	<!-- aop -->
    	<aop:config>
    		<aop:advisor advice-ref="txAdvice" pointcut="execution(* liuxun.ssm.service.impl.*.*(..))" />
    	</aop:config>
    </beans>
    springmvc.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans 
    		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
    		http://www.springframework.org/schema/mvc 
    		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
    		http://www.springframework.org/schema/context 
    		http://www.springframework.org/schema/context/spring-context-3.2.xsd 
    		http://www.springframework.org/schema/aop 
    		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
    		http://www.springframework.org/schema/tx 
    		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
    	<!-- 使用spring组件扫描 -->
    	<context:component-scan base-package="liuxun.ssm.controller" />
    	
    	<!-- 静态资源解析 -->
    	<mvc:resources location="/js/" mapping="/js/**"/>
    	<mvc:resources location="/img/" mapping="/img/**"/>
    	
    	<!-- 通过使用mvc的annotation-driven 可以替代下边的处理器映射器和适配器 -->
    	<!--  <mvc:annotation-driven conversion-service="conversionService" >
    	 </mvc:annotation-driven> -->
    
    	<!-- 注解处理器映射器 -->
    	<bean
    		class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
    
        <!-- 注解适配器 -->
        <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        	<!-- 在webBindingInitializer中注入自定义属性编辑器,自定义转换器 -->
        	<property name="webBindingInitializer" ref="customBinder"/>
        	<!-- 加入json数据的消息转换器 MappingJacksonHttpMessageConverter依赖的Jackson包 -->
        	<property name="messageConverters">
        		<list>
        			<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
        		</list>
        	</property>
        </bean>
    
        <!-- 配置视图解析器 要求将jstl的包加到classpath -->
    	<!-- ViewResolver -->
    	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    		<property name="prefix" value="/WEB-INF/jsp/"/>
    		<property name="suffix" value=".jsp"/>
    	</bean>
    	
    	<!-- 自定义webBinder -->
    	<bean id="customBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
    		<!-- 配置Validator -->
    		<property name="validator" ref="validator"/>
    		
    		<!-- 使用converter进行参数转换 -->
    		<property name="conversionService" ref="conversionService"/>
    	
    		<!-- propertyEditorRegistrars用于属性编辑器 -->
    		<!--  
    		<property name="propertyEditorRegistrars">
    			<list>
    				<ref bean="customPropertyEditor"/>
    			</list>
    		</property>
    		-->
    	</bean>
    	
    	<!-- 注册属性编辑器 -->
    	<bean id="customPropertyEditor" class="liuxun.ssm.controller.propertyeditor.CustomPropertyEditor"/>
    
    	<!-- 注册转换器 -->
    	<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    		<property name="converters">
    			<list>
    				<bean class="liuxun.ssm.controller.converter.CustomDateConverter"/>
    				<bean class="liuxun.ssm.controller.converter.StringTrimConverter"/>
    			</list>
    		</property>
    	</bean>
    	
    	<!-- 拦截器 -->
    	<mvc:interceptors>
    		<!-- 多个拦截器,顺序执行 -->
       <!-- <mvc:interceptor>
    			<mvc:mapping path="/**"/>
    			<bean class="liuxun.ssm.controller.interceptor.HandlerInterceptor1"></bean>
    		</mvc:interceptor>
    		<mvc:interceptor>
    			<mvc:mapping path="/**"/>
    			<bean class="liuxun.ssm.controller.interceptor.HandlerInterceptor2"></bean>
    		</mvc:interceptor>  -->	
    		
    	    <mvc:interceptor>
    		   	<!-- /**可以拦截多层路径 -->
    		   	<mvc:mapping path="/**"/>
    		   	<bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean>
    	    </mvc:interceptor>
    	</mvc:interceptors>
    	
    	<!-- 文件上传 -->
    	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    		<!-- 设置上传文件的最大尺寸为5MB -->
    		<property name="maxUploadSize">
    			<value>5242880</value>
    		</property>
    	</bean>
    	
    	<!-- 定义统一异常处理器类 -->
    	<bean class="liuxun.ssm.exception.CustomExceptionResolver"/>
    	
    	<!-- 校验器 -->
    	<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
    		<!-- 校验器 -->
    		<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
    		<!-- 指定校验所使用的资源文件,如果不指定则默认使用classpath下的ValidationMessages.properties-->
    		<property name="validationMessageSource" ref="messageSource"/>
    	</bean>
    	<!-- 校验错误信息配置文件 -->
    	<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    		<!-- 资源文件名 -->
    		<property name="basenames">
    			<list>
    				<value>classpath:CustomValidationMessages</value>
    			</list>
    		</property>
    		<!-- 资源文件编码方式 -->
    		<property name="fileEncodings" value="utf-8"/>
    		<!-- 对资源文件内容缓存时间,单位秒 -->
    		<property name="cacheSeconds" value="120"/>
    	</bean>
    	
    </beans>
    CustomValidationMessages.properties

    db.properties
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/mybatis
    jdbc.username=root
    jdbc.password=root
    
    log4j.properties
    # Global logging configuration\uff0c\u5efa\u8bae\u5f00\u53d1\u73af\u5883\u4e2d\u8981\u7528debug
    log4j.rootLogger=DEBUG, stdout
    # Console output...
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
    
    ItemsController.java
    package liuxun.ssm.controller;
    
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
    
    import java.io.File;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.UUID;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.propertyeditors.CustomDateEditor;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.ObjectError;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.WebDataBinder;
    import org.springframework.web.bind.annotation.InitBinder;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.multipart.MultipartFile;
    import org.springframework.web.servlet.ModelAndView;
    
    import liuxun.ssm.controller.validation.ValidGroup1;
    import liuxun.ssm.po.ItemsCustom;
    import liuxun.ssm.po.ItemsQueryVo;
    import liuxun.ssm.service.ItemsService;
    
    /**
     * 商品管理
     * 
     * @author liuxun
     *
     */
    @Controller
    // 定义url的根路径,访问时 根路径+方法的url
    @RequestMapping("/items")
    public class ItemsController {
    
    	// 注入Service
    	@Autowired
    	private ItemsService itemsService;
    
    	// 单独将商品类型的方法提出来,将方法返回值填充到request,在页面显示
    	@ModelAttribute("itemsType")
    	public Map<String, String> getItemsType() throws Exception{
    		HashMap<String, String> itemsType = new HashMap<String, String>();
    		itemsType.put("001", "数码");
    		itemsType.put("002", "服装");
    		return itemsType;
    	}
    	
    	// 查询商品信息方法
    	@RequestMapping("/queryItems")
    	public ModelAndView queryItems(HttpServletRequest request) throws Exception {
    
    		// 调用Service查询商品列表
    		List<ItemsCustom> itemsList = itemsService.findItemsList(null);
    
    		ModelAndView modelAndView = new ModelAndView();
    		modelAndView.addObject("itemsList", itemsList);
    		// 指定逻辑视图名
    		modelAndView.setViewName("itemsList");
    
    		return modelAndView;
    	}
    
    	// 批量修改商品查询
    	@RequestMapping("/editItemsList")
    	public ModelAndView editItemsList(HttpServletRequest request) throws Exception {
    		
    		// 调用Service查询商品列表
    		List<ItemsCustom> itemsList = itemsService.findItemsList(null);
    		
    		ModelAndView modelAndView = new ModelAndView();
    		modelAndView.addObject("itemsList", itemsList);
    		// 指定逻辑视图名
    		modelAndView.setViewName("editItemsList");
    		
    		return modelAndView;
    	}
    	// 批量修改商品提交
    	@RequestMapping("/editItemsListSubmit")
        public String editItemsListSubmit(ItemsQueryVo itemsQueryVo){
    		return "success";
    	}
    	// 商品修改页面
    	// 使用method=RequestMethod.GET限制使用get方法
    	/*
    	  @RequestMapping(value="/editItems",method={RequestMethod.GET}) public
    	  ModelAndView editItems() throws Exception{
    	  
    	  ModelAndView modelAndView = new ModelAndView();
    	  
    	  // 调用Service查询商品信息 ItemsCustom itemsCustom =
    	  itemsService.findItemsById(1); //将模型数据传到jsp
    	  modelAndView.addObject("item", itemsCustom); // 指定逻辑视图名
    	  modelAndView.setViewName("editItem");
    	  
    	  return modelAndView; }
    	 */
    
    	// 方法返回字符串,字符串就是逻辑视图名,Model作用就是将数据填充到request域,在页面展示
    	  @RequestMapping(value="/editItems",method={RequestMethod.GET}) 
    	  public String editItems(Model model,Integer id) throws Exception{
    	  
    	 // 将id传递到页面
    	  model.addAttribute("id",id);
    		  
    	  // 调用Service查询商品信息 
    	  ItemsCustom itemsCustom = itemsService.findItemsById(id); //将模型数据传到jsp 
    	  model.addAttribute("item",itemsCustom);
    	  
    	  //return "editItem_2";
    	  return "editItem"; 
    	 }
    	 
    	 //根据id查看商品信息rest接口
    	 //@RequestMapping中指定restful方式的url参数,参数要使用{}包起来
    	 //@PathVariable将url中的{}包起来的参数和形参进行绑定
    	  @RequestMapping("/viewItems/{id}")
    	  public @ResponseBody ItemsCustom viewItems(@PathVariable("id") Integer id) throws Exception{
    		  //调用Service查询商品信息
    		  ItemsCustom itemsCustom = itemsService.findItemsById(id);
    		  return itemsCustom;
    	  }
    
    	// 方法返回void
    	/*
    	@RequestMapping(value = "/editItems", method = { RequestMethod.GET })
    	public void editItems(HttpServletRequest request, HttpServletResponse response,
    			// @RequestParam(value="item_id",required=false,defaultValue="1")Integer id
    			Integer id) throws Exception {
    
    		// 调用Service查询商品信息
    		ItemsCustom itemsCustom = itemsService.findItemsById(id);
    		request.setAttribute("item", itemsCustom);
    		// 注意如果使用request转向页面,这里指定页面的完整路径
    		request.getRequestDispatcher("/WEB-INF/jsp/editItem.jsp").forward(request, response);
    	}
    	*/
    
    	// 商品修改提交
    	// itemsQueryVo是包装类型的pojo
    	@RequestMapping("/editItemSubmit")
    	// public String editItemSubmit(Integer id,ItemsCustom
    	// itemsCustom,ItemsQueryVo itemsQueryVo) throws Exception{
        //注意:每个校验pojo的前边必须加@Validated, 每个校验的pojo后边必须加BindingResult接收错误信息
    	public String editItemSubmit(Model model,Integer id, 
    			@Validated(value={ValidGroup1.class}) @ModelAttribute(value="item")ItemsCustom itemsCustom,
    			BindingResult bindingResult,
    			// 上传图片
    			MultipartFile pictureFile
    			) throws Exception {
    		// 输出错误信息
    		// 如果参数绑定时有错误
    		if (bindingResult.hasErrors()) {
    			// 获取错误
    			List<ObjectError> errors = bindingResult.getAllErrors();
    			// 准备在页面输出errors,页面使用jstl遍历
    			model.addAttribute("errors",errors);
    			for (ObjectError error : errors) {
    				// 输出错误信息
    				System.out.println(error.getDefaultMessage());
    			}
    			// 如果校验错误,回到商品修改页面
    			return "editItem";
    		}
    		
    		// 进行数据回显
    		model.addAttribute("id", id);
    		//model.addAttribute("item",itemsCustom);
    		
    		//进行图片上传
    		if (pictureFile!=null && pictureFile.getOriginalFilename()!=null && pictureFile.getOriginalFilename().trim().length()>0) {
    			// 图片上传成功后,将图片的地址写到数据库
    			String filePath = "/Users/liuxun/Desktop/pictures";
    			// 上传文件原始名称
    			String originFileName = pictureFile.getOriginalFilename();
    			// 新的图片的名称
    			String newFileName = UUID.randomUUID()+originFileName.substring(originFileName.lastIndexOf("."));
    			// 新文件
    			File file = new File(filePath+File.separator+newFileName);
    			
    			// 将内存中的文件写入磁盘
    			pictureFile.transferTo(file);
    			
    			// 图片上传成功,将图片地址写入数据库
    			itemsCustom.setPic(newFileName);
    		}
    		
    		// 调用Service接口更新商品信息
    		itemsService.updateItems(id, itemsCustom);
    		
    		// 提交后回到修改页面
    		return "editItem";
    		// 请求重定向
    		//return "redirect:queryItems.action";
    		// 转发
    		// return "forward:queryItems.action";
    	}
    	
    	// 删除商品
    	@RequestMapping("/deleteItems")
    	public String deleteItems(Integer[] delete_id) throws Exception{
    		System.out.println(delete_id);
    		return "success";
    	}
    
    	//自定义属性编辑器
    	/*
    	@InitBinder
    	public void initBinder(WebDataBinder binder) throws Exception {
    		// Date.class必须是与controler方法形参pojo属性一致的date类型,这里是java.util.Date
    		binder.registerCustomEditor(Date.class, new CustomDateEditor(
    				new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true));
    	}
    	*/
    
    }
    
    JsonTest.java
    package liuxun.ssm.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import liuxun.ssm.po.ItemsCustom;
    
    /**
     * json测试
     * @author liuxun
     *
     */
    @Controller
    public class JsonTest {
    	//请求的json响应json,请求商品信息,商品信息使用json格式,输出商品信息
    	@RequestMapping("/requestJson")
    	public @ResponseBody ItemsCustom requestJson(@RequestBody ItemsCustom itemsCustom) throws Exception{
    		return itemsCustom;
    	}
    	
    	@RequestMapping("/responseJson")
    	public @ResponseBody ItemsCustom responseJson(ItemsCustom itemsCustom) throws Exception{
    		return itemsCustom;
    	}
    }
    
    LoginController.java
    package liuxun.ssm.controller;
    
    import javax.servlet.http.HttpSession;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * 登录和退出
     * @author liuxun
     *
     */
    @Controller
    public class LoginController {
    
    	//用户登录提交方法
    	@RequestMapping("/login")
    	public String login(HttpSession session,String usercode,String password) throws Exception{
    		//调用service校验用户账号和密码的正确性
    		//...
    		
    		//如果Service校验通过,将用户身份记录到session
    		session.setAttribute("usercode", usercode);
    		//重定向到商品查询页面
    		return "redirect:/items/queryItems.action";
    	}
    	
    	//用户退出
    	@RequestMapping("/logout")
    	public String logout(HttpSession session) throws Exception{
    		//session失效
    		session.invalidate();
    		//重定向到商品查询页面
    		return "redirect:/items/queryItems.action";
    	}
    }
    CustomDateConverter.java
    package liuxun.ssm.controller.converter;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import org.springframework.core.convert.converter.Converter;
    
    /**
     * 自定义日期转换器
     * @author liuxun
     *
     */
    public class CustomDateConverter implements Converter<String, Date> {
    
    	@Override
    	public Date convert(String source) {
    		if (source!=null&&source.trim().length()>0) {
    			// 进行日期转换
    			try {
    				return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(source);
    			} catch (ParseException e) {
    				e.printStackTrace();
    			}
    		}
    		
    		return null;
    	}
    
    }
    
    StringTrimConverter.java
    package liuxun.ssm.controller.converter;
    
    import org.springframework.core.convert.converter.Converter;
    /**
     * 自定义去除字符串前后空格的转换器
     * @author liuxun
     *
     */
    public class StringTrimConverter implements Converter<String, String>{
    
    	@Override
    	public String convert(String source) {
    		
    		//去掉字符串两边空格,如果去除后为空设置为null
    		if (source!=null) {
    			source = source.trim();
    			if (source.equals("")) {
    				return null;
    			}
    		}
    		
    		return source;
    	}
    
    }
    
    LoginInterceptor.java
    package liuxun.ssm.controller.interceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    /**
     * 测试拦截器1
     * @author liuxun
     *
     */
    public class LoginInterceptor implements HandlerInterceptor{
    	//在执行handler之前执行的
    	//用于用户认证校验、用户权限校验
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            
    		//得到请求的url
    		String url = request.getRequestURI();
    		
    		//判断是否是公开地址
    		//实际开发中需要将公开地址配置在配置文件中
    		//...
    		if (url.indexOf("login.action")>=0) {
    			//如果是公开地址 则放行
    			return true;
    		}
    		
    		//判断用户身份在Session中是否存在
    		HttpSession session = request.getSession();
    		String usercode = (String) session.getAttribute("usercode");
    		//如果用户身份在session中存在则放行
    		if (usercode!=null) {
    			return true;
    		}
    		//执行到这里拦截,跳转到登录页面,用户进行身份认证
    		request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
    		
    		//如果返回false表示拦截器不继续执行handler,如果返回true表示放行
    		return false;
    	}
    
    	//在执行handler返回modelAndView之前执行
    	//如果需要向页面提供一些公用的数据或配置一些视图信息,使用此方法实现 从modelAndView入手
    	@Override
    	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
    			throws Exception {
    		System.out.println("HandlerInterceptor2...postHandle");
    	}
    
    	//执行handler之后执行此方法
    	//作为系统统一异常处理,进行方法执行性能监控,在preHandler中设置一个时间点 在afterCompletion设置一个时间点 二者时间差就是执行时长
    	//实现系统,统一日志记录
    	@Override
    	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
    			throws Exception {
    		System.out.println("HandlerInterceptor2...afterCompletion");
    	}
    
    }
    
    CustomPropertyEditor.java
    package liuxun.ssm.controller.propertyeditor;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import org.springframework.beans.PropertyEditorRegistrar;
    import org.springframework.beans.PropertyEditorRegistry;
    import org.springframework.beans.propertyeditors.CustomDateEditor;
    
    /**
     * 自定义属性编辑器
     * @author liuxun
     *
     */
    public class CustomPropertyEditor implements PropertyEditorRegistrar {
    
    	public void registerCustomEditors(PropertyEditorRegistry binder) {
    		binder.registerCustomEditor(Date.class,
    				new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true));
    	}
    
    }
    
    ValidGroup1.java
    package liuxun.ssm.controller.validation;
    /**
     * 校验分组: 用于商品修改的校验分组
     * @author liuxun
     *
     */
    public interface ValidGroup1 {
    	//接口不定义方法,就是只标识 哪些校验规则属于ValidGroup1分组
    }
    
    CustomException.java
    package liuxun.ssm.exception;
    
    public class CustomException extends Exception {
    	
    	//异常信息
    	private String message;
    
    	public CustomException(String message) {
    		super();
    		this.message = message;
    	}
    
    	public String getMessage() {
    		return message;
    	}
    
    	public void setMessage(String message) {
    		this.message = message;
    	}
    	
    }
    
    CustomExceptionResolver.java
    package liuxun.ssm.exception;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.web.servlet.HandlerExceptionResolver;
    import org.springframework.web.servlet.ModelAndView;
    
    public class CustomExceptionResolver implements HandlerExceptionResolver {
    
    	//前端控制器DispatcherServlet在进行HandlerMapping、调用HandlerAdapter执行Handler过程中,如果遇到异常就会执行此方法
    	@Override
    	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
    			Exception ex) {
    		//输出异常
    		ex.printStackTrace();
    		
    		//统一处理异常代码
    		//针对系统自定义的CustomException异常,就可以直接从异常类中获取异常信息,将异常处理在错误页面进行展示
    		//异常信息
    		String message = null;
    		CustomException customException = null;
    		//如果ex是自定义异常信息,直接取出异常信息
    		if (ex instanceof CustomException) {
    			customException = (CustomException) ex;
    		} else {
    			//针对非CustomException异常,对这类重新构成一个CustomException,异常信息为"未知错误"
    			customException = new CustomException("未知错误");
    		}
    		
    		//错误信息
    		message = customException.getMessage();
    		request.setAttribute("message", message);
    		
    		try {
    			//转向到错误页面
    			request.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(request, response);
    		} catch (ServletException | IOException e) {
    			e.printStackTrace();
    		}
    		
    		return new ModelAndView();
    	}
    
    }
    
    ItemsMapperCustom.java
    package liuxun.ssm.mapper;
    
    import java.util.List;
    
    import liuxun.ssm.po.ItemsCustom;
    import liuxun.ssm.po.ItemsQueryVo;
    
    /**
     * 商品自定义Mapper
     * @author liuxun
     *
     */
    
    public interface ItemsMapperCustom {
      // 商品查询列表
    	public List<ItemsCustom> findItemsList(ItemsQueryVo itemsQueryVo) throws Exception;
    }
    
    ItemsMapperCustom.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="liuxun.ssm.mapper.ItemsMapperCustom">
      <!-- 商品查询的sql片段
      建议以单表为单位定义查询条件
      建议将常用的查询条件都写出来
       -->
       <sql id="query_items_where">
         <if test="itemsCustom!=null">
         	<if test="itemsCustom.name!=null and itemsCustom.name.trim().length >0">
         	 and name like '%${itemsCustom.name.trim()}%'
         	</if>
         	<if test="itemsCustom.id!=null">
         		and id = #{itemsCustom.id}
         	</if>
         </if>
       </sql>
       
       <!-- 商品查询
       parameterType:输入查询条件
        -->
      <select id="findItemsList" parameterType="liuxun.ssm.po.ItemsQueryVo" resultType="liuxun.ssm.po.ItemsCustom">
    	  SELECT * FROM items
    	  <where>
    	  	<include refid="query_items_where"/>
    	  </where>
      </select>  
    </mapper>
    ItemsCustom.java
    package liuxun.ssm.po;
    /**
     * 商品信息的扩展类
     * @author liuxun
     *
     */
    public class ItemsCustom extends Items {
    
    }
    
    ItemsQueryVo.java
    /**
     * 商品的包装类
     * @author liuxun
     *
     */
    public class ItemsQueryVo {
    
    	// 商品信息
    	private ItemsCustom itemsCustom;
    
    	// 定义一个List
    	private List<ItemsCustom> itemsList;
    	
    	public ItemsCustom getItemsCustom() {
    		return itemsCustom;
    	}
    
    	public void setItemsCustom(ItemsCustom itemsCustom) {
    		this.itemsCustom = itemsCustom;
    	}
    
    	public List<ItemsCustom> getItemsList() {
    		return itemsList;
    	}
    
    	public void setItemsList(List<ItemsCustom> itemsList) {
    		this.itemsList = itemsList;
    	}
    	
    }
    修改自动生成的Items 添加校验
    public class Items {
        private Integer id;
    
        //商品名称的长度限制在1到30个字符
        @Size(min=1,max=30,message="{items.name.length.error}")
        private String name;
    
        private Float price;
    
        private String pic;
    
        //请输入商品生产日期
        //通过groups指定此校验属于哪个分组,可以指定多个分组 之间用逗号隔开groups={ValidGroup1.class,ValidGroup2.class}
        @NotNull(message="{items.createtime.is.notnull}",groups={ValidGroup1.class})
        private Date createtime;
        ....
        ....
    }
    ItemsService
    package liuxun.ssm.service;
    
    import java.util.List;
    
    import liuxun.ssm.po.ItemsCustom;
    import liuxun.ssm.po.ItemsQueryVo;
    
    /**
     * 商品Service接口
     * 
     * @author liuxun
     *
     */
    public interface ItemsService {
    	// 商品查询列表
    	public List<ItemsCustom> findItemsList(ItemsQueryVo itemsQueryVo) throws Exception;
    
    	// 根据商品id查询商品信息
    	public ItemsCustom findItemsById(int id) throws Exception;
    	
    	// 更新商品信息
    	/**
    	 * 定义Service接口,遵循单一职责,将业务参数细化(不要使用包装类型,比如Map)
    	 * @param id  修改商品的id
    	 * @param itemsCustom  修改商品的信息
    	 * @throws Exception
    	 */
    	public void updateItems(Integer id,ItemsCustom itemsCustom) throws Exception;
    
    }
    
    ItemsServiceImpl.java
    package liuxun.ssm.service.impl;
    
    import java.util.List;
    
    import org.springframework.beans.BeanUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import liuxun.ssm.exception.CustomException;
    import liuxun.ssm.mapper.ItemsMapper;
    import liuxun.ssm.mapper.ItemsMapperCustom;
    import liuxun.ssm.po.Items;
    import liuxun.ssm.po.ItemsCustom;
    import liuxun.ssm.po.ItemsQueryVo;
    import liuxun.ssm.service.ItemsService;
    
    public class ItemsServiceImpl implements ItemsService {
    
    	// 注入mapper
    	@Autowired
    	private ItemsMapperCustom itemsMapperCustom;
    
    	@Autowired
    	private ItemsMapper itemsMapper;
    
    	// 商品查询列表
    	public List<ItemsCustom> findItemsList(ItemsQueryVo itemsQueryVo) throws Exception {
    		return itemsMapperCustom.findItemsList(itemsQueryVo);
    	}
    
    	public ItemsCustom findItemsById(int id) throws Exception {
    		Items items = itemsMapper.selectByPrimaryKey(id);
    		//如果查询的商品信息为空,抛出系统自定义异常
    		if (items == null) {
    			throw new CustomException("修改商品信息不存在");
    		}
    		// 在这里随着需求的变量,需要查询商品的其他相关信息,返回到controller
    
    		ItemsCustom itemsCustom = new ItemsCustom();
    		// 将items的属性拷贝到itemsCustom
    		BeanUtils.copyProperties(items, itemsCustom);
    
    		return itemsCustom;
    	}
    
    	public void updateItems(Integer id, ItemsCustom itemsCustom) throws Exception {
    		// 写业务代码
    		
    		// 对于关键业务数据的非空校验
    		if (id == null) {
    			// 抛出异常,提示调用接口的用户,id不能为空
    			// ...
    		}
    		
    		itemsMapper.updateByPrimaryKeySelective(itemsCustom);
    	}
    
    }
    
    itemsList.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>查询商品列表</title>
    <script type="text/javascript">
    	function deleteItems(){
    	  // 将form的action指向删除商品的地址
    	  document.itemsForm.action = "${pageContext.request.contextPath }/items/deleteItems.action";
    	  // 进行form提交
    	  document.itemsForm.submit();
    	}
    </script>
    </head>
    <body> 
    当前用户:${usercode }
    <c:if test="${usercode != null}">
    	<a href="${pageContext.request.contextPath}/logout.action">退出</a>
    </c:if>
    
    <form name="itemsForm" action="${pageContext.request.contextPath }/queryItems.action" method="post">
    查询条件:
    <table width="100%" border=1>
    	<tr>
    		<td>
    		商品类别:
    		<select> 
    			<c:forEach items="${itemsType }" var="item">
    				<option value="${item.key }">${item.value }</option>
    			</c:forEach>
    		</select>
    		</td>
    	</tr>  
    	<tr>
    		<td><input type="submit" value="查询"/>
    		<input type="button" value="批量删除" οnclick="deleteItems()"/>
    		</td>
    	</tr>
    </table>
    商品列表:
    <table width="100%" border=1>
    <tr>
    	<td>选择</td>
    	<td>商品名称</td>
    	<td>商品价格</td>
    	<td>生产日期</td>
    	<td>商品描述</td>
    	<td>操作</td>
    </tr>
    <c:forEach items="${itemsList }" var="item">
    <tr>
        <td><input type="checkbox" name="delete_id" value="${item.id }"/></td>
    	<td>${item.name }</td>
    	<td>${item.price }</td>
    	<td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
    	<td>${item.detail }</td>
    	
    	<td><a href="${pageContext.request.contextPath }/items/editItems.action?id=${item.id}">修改</a></td>
    
    </tr>
    </c:forEach>
    
    </table>
    </form>
    </body>
    
    </html>
    editItem.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>修改商品信息</title>
    
    </head>
    <body> 
    <!-- 错误信息 -->
    <div style="color: red;">
    <c:forEach items="${errors }" var="error">
    	${error.defaultMessage }<br>
    </c:forEach>
    </div>
    
    <form id="itemForm" action="${pageContext.request.contextPath }/items/editItemSubmit.action" method="post" enctype="multipart/form-data">
    <input type="hidden" name="id" value="${id }"/>
    修改商品信息:
    <table width="100%" border=1>
    <tr>
    	<td>商品名称</td>
    	<td><input type="text" name="name" value="${item.name }"/></td>
    </tr>
    <tr>
    	<td>商品价格</td>
    	<td><input type="text" name="price" value="${item.price }"/></td>
    </tr>
    <tr>
    	<td>商品生产日期</td>
    	<td><input type="text" name="createtime" value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>"/></td>
    </tr>
    <tr>
    	<td>商品图片</td>
    	<td>
    		<c:if test="${item.pic != null }">
    			<img src="/pic/${item.pic}" width="100px" height="100px"/>
    			<br/>
    		</c:if>
    		<input type="file" name="pictureFile"/>
    	</td>
    </tr>
    <tr>
    	<td>商品简介</td>
    	<td>
    	<textarea rows="3" cols="30" name="detail">${item.detail }</textarea>
    	</td>
    </tr>
    <tr>
    <td colspan="2" align="center"><input type="submit" value="提交"/>
    </td>
    </tr>
    </table>
    
    </form>
    </body>
    
    </html>
    editItemsList.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>查询商品列表</title>
    <script type="text/javascript">
    	function updateItems(){
    	  // 将form的action指向修改商品的地址
    	  document.itemsForm.action = "${pageContext.request.contextPath }/items/editItemsListSubmit.action";
    	  // 进行form提交
    	  document.itemsForm.submit();
    	}
    </script>
    </head>
    <body> 
    <form name="itemsForm" action="${pageContext.request.contextPath }/queryItems.action" method="post" >
    查询条件:
    <table width="100%" border=1>
    	<tr>
    		<td>
    		商品类别:
    		<select> 
    			<c:forEach items="${itemsType }" var="item">
    				<option value="${item.key }">${item.value }</option>
    			</c:forEach>
    		</select>
    		</td>
    	</tr>  
    	<tr>
    		<td><input type="submit" value="查询"/>
    		<input type="button" value="批量修改提交" οnclick="updateItems()"/>
    		</td>
    	</tr>
    </table>
    商品列表:
    <table width="100%" border=1>
    <tr>
    	<td>商品名称</td>
    	<td>商品价格</td>
    	<td>生产日期</td>
    	<td>商品描述</td>
    	<td>操作</td>
    </tr>
    <c:forEach items="${itemsList }" var="item" varStatus="s">
    <tr>
    	<td><input type="text" name="itemsList[${s.index }].name" value="${item.name }"/></td>
    	<td><input type="text" name="itemsList[${s.index }].price" value="${item.price }"/></td>
    	<td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
    	<td>${item.detail }</td>
    	
    	<td><a href="${pageContext.request.contextPath }/items/editItems.action?id=${item.id}">修改</a></td>
    
    </tr>
    </c:forEach>
    
    </table>
    </form>
    </body>
    
    </html>
    jsontest.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    	pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>json测试</title>
    <script type="text/javascript"
    	src="${pageContext.request.contextPath}/js/jquery-1.4.4.min.js"></script>
    <script type="text/javascript">
    	// 请求json响应json
    	function requestJson(){
    		$.ajax({
    			url:"${pageContext.request.contextPath}/requestJson.action",
    			type:"post",
    			contentType:"application/json;charset=utf-8",
    			//请求json数据 使用json表示商品信息
    			data:'{"name":"手机","price":1999}',
    			success:function(data){
    				alert(data.name);
    			}
    		});
    	}
    	// 请求key/value响应json
    	function responseJson(){
    		$.ajax({
    			url:"${pageContext.request.contextPath}/responseJson.action",
    			type:"post",
    			//contentType:"application/json;charset=utf-8",
    			//请求key/value数据 使用地址拼接表示商品信息
    			data:"name=手机&price=1999",
    			success:function(data){
    				alert(data.name);
    			}
    		});
    	}
    </script>
    </head>
    <body>
    	<input type="button" value="请求json响应json" οnclick="requestJson()"/>
    	<input type="button" value="请求key/value响应json" οnclick="responseJson()"/>
    </body>
    </html>


    展开全文
  • Code Complete总结(一)

    千次阅读 2018-08-12 23:14:25
    1.什么是软件构建: 主要编码、调试过程、详细设计和测试。 计算机软件开发的主要方面(没看明白,还是记下来方便以后看): 问题定义 需求分析 实现计划 总体设计 详细设计 创建即实现 系统集成 单元测...

    Code Complete

    本书是一本比较老的书,但是对于现今的编码来讲,任是一本经典的书籍,对提高我们的编程风格和提升编码思想仍有很大的指导意义。

    第一章 欢迎进入软件创建世界

    • 1.什么是软件构建:
      主要指编码、调试过程、详细设计和测试。

      计算机软件开发的主要方面(没看明白,还是记下来方便以后看):

      • 问题定义
      • 需求分析
      • 实现计划
      • 总体设计
      • 详细设计
      • 创建即实现
      • 系统集成
      • 单元测试
      • 系统测试
      • 校正性的维护
      • 功能强化
    • 2.软件创建的重要性
      论述了软件创建在开发中很重要,应该把主要精力放在创建活动中。

    第二章 利用隐喻对编程进行更深刻的理解

    • 1.隐喻的重要性
      论述了通过隐喻在科学上取得的一些成就。。。
    • 2.如何使用软件隐喻
      好的软件隐喻会给你启发。
      使用软件隐喻帮助你在编程过程中的内在理解。
    • 3.通常的软件隐喻
      • 软件书写(Writing Code)
        作者认为这种隐喻不适合现在的软件构建,现在的系统开发的投资相当于建一栋办公楼。并且这种比喻向写信一样,写了就不管也不能 修改。
      • 软件播种
        作者也认为这种比喻十分拙劣,这种比喻的弱点是对于软件开发失去了直接的控制,并且就像是只给系统计划施肥,让代码自由生长,这显然是不合适的。
      • 软件珍珠培植法
        这个隐喻强调了积累的重要性,每次都想系统加一点东西,最终完成它。
      • 建造软件
        建造房子,首先的设计很重要,然后再试选好房址、打地基、建造房屋框架等等。作者认为这和软件创建基本差不多。建造房子材料成本更贵,建造软件劳动成本更贵。
      • 智能工具箱
        作者认为开发人员在十几年的积累中,有了很多的工具和开发技巧。但要明确,有些方法在某些场合有用,有些却是不适合。
      • 复合隐喻
        作者认为隐喻更像是一种启发而不是公式,只要你愿意,你可以用多种隐喻组合,关键是他能够激发你的思想。

    第三章 软件创建的先决条件

    • 1.准备不足的原因
      忠告:
      • 阅读工作的内容提示,或许会发现一些没想到的问题。
      • 注意自己的问题。
    • 2.问题定义先决条件
      问题定义只描述要解决的问题是什么,不涉及解决方法。通俗地说,它应该是一个简短的说明。
    • 3.需求分析先决条件
      明确的需求很重要。
      稳定需求对软件开发很重要。
      在创建阶段要做好需求的变化。
    • 4.结构涉及先决条件
      如果要进行结构变动,越早越好。
      要考虑的要素:
      • 程序的组织形式:找出最终组织形式的集中方案,并且应该知道为什么选中了现在的组织形式。
      • 变动策略:变动是不可避免的(创建软件是逐渐学习的过程),因此在设计中应该考虑到某些变动。
      • 能购买的尽量不建造
      • 主要的数据结构:数据结构对系统维护有举足轻重的影响。
      • 关键算法:在选择算法时要给定选择该算法的原因。
      • 主要对象:在面向对象的系统中,应该规定每一个对象的责任并指出对象之间是如何相互作用的。
      • 通用功能:用户界面、输入/输出、内存管理和字符串存储。
      • 错误处理:如果可以纠正错误,程序尝试从错误状态下恢复。
      • 坚固性:裕度设计(大于需求定义的规定)、断言、容错性和性能。
      • 通用的结构设计质量准则:一个好的结构设计,应该是被看到时是为他的解决方案的自然和简单而感到折服,而不会把问题和答案生拼硬凑一起的感觉。
    • 5.选择编程语言先决条件:对于不同的工作,不同的语言的表现差异很大。
    • 6.编程约定:高质量的软件中,结构设计的概念完整性与较低层次之间的密切联系。
    • 7.应花在先决条件上的时间:大概在20~30%(不包括详细设计)。
    • 8.改变先决条件以适应你的项目

    第四章 建立子程序的步骤

    • 1.建立程序步骤概述:设计程序、检查设计、子程序编码、检查代码。
    • 2.程序设计语言(PDL):一种详细设计的工具。
    • 3.设计子程序:分析好程序的功能(输入、输出、是否是交互式、要处理的信息、影响的全局变量等等)
      • 给子程序命名:动词、做什么的、是什么、需要什么条件等等。
      • 考虑效率:模块化和性能。(优化的主要受益主要来自高层设计,而不是个别子程序)
      • 研究算法和数据结构
      • 编写PDL
      • 编写工作应该从抽象到具体
      • 考虑数据
      • 检查PDL
      • 逐步细化
    • 4.子程序编码:把原来的抽象说明用程序语言实现。
      • 书写子程序说明(先写上和编程风格一样的注释,再一步转化成代码???)
      • 非正式地检查代码:想一下有什么因素可能跑坏目前的块
      • 进行收尾工作:检查子程序的接口、检查通用设计质量(子程序只完成一项任务并且完成得很好)、检查子程序的数据、检查子程序的控制结构(循环、嵌套等)、检查子程序设计、检查子程序的文档。
    • 5.检查子程序
      • 在心里对子程序进行查错处理
      • 编译子程序(到这一步才编译不容易混乱)
      • 使用计算机来检查子程序错误
      • 消除子程序中的错误

    第五章 高质量子程序特点

    • 1.生成子程序的原因:
      • 降低复杂性(避免代码重复)
      • 限制了改动带来的影响
      • 隐含顺序
      • 改进性能(改进一个地方,几个地方受益)
      • 进行集中控制
      • 隐含数据结构(使得在不改变绝大多数程序的条件下,改变数据结构成为可能)
      • 隐含全局变量(不必改变程序就改变数据结构)
      • 隐含指针操作(指针操作可读性差)
      • 提高代码的可读性
      • 提高代码的可移植性
      • 分隔负责操作
    • 2.子程序名称恰当
      • 用动词
      • 对于函数名字,可以使用返回值的描述(Cos()、PrinterReady()、CurrentPenColor())
      • 避免无异议或者模棱两可的动词
      • 描述子程序所做的一切
      • 名字长度要符合需要(9~15)
      • 建立用于通用操作的协定
    • 3.强内聚性:每个子程序只需要做好一项工作,而不必考虑其他任务。
      • 功能内聚性:在功能上只做好一项工作。
      • 顺序内聚性:子程序内包含需要按特定顺序进行、逐步分享数据而不形成一个完整功能的操作。(把操作分隔开,没有产生独立的功能)。难命名,通常意味着你要重新组织和设计子程序,以使它是功能内聚性的。
      • 通讯内聚性:GetNameAndChangePhoneNumber(),其中的Name和PhoneNumber是放在同一用户记录的,就是通讯内聚性。
      • 临时内聚性:因为同时执行才被放在同一个子程序里,用子程序去调用其他专门功能的子程序。
        注:到此为止的内聚性都在接受范围,但最可取的是功能内聚,一下是不可取的。
      • 过程内聚性:和顺序内聚不同的是,过程内聚性操作的是不同的数据。
      • 逻辑内聚性:操作由传递进来的控制标志所选择。
      • 偶然内聚性:同一个子程序之间没有任何联系。
    • 4.松散耦合性:任何一个子程序很容易被其他子程序调用(尽量避免它对其他子程序有依赖性)。
      • 简单数据耦合:两个子程序之间传递的数据是非结构化的,并且全部都是通过参数表进行的,这个最好。
      • 数据结构耦合:两个子程序之间传递的数据是结构化的,并且是通过参数表实现传递的。
      • 控制耦合:一个子程序通过传入另一个子程序的数据通知它该做什么。这种不好,它要指导另一个子程序里干了什么。
      • 全局数据耦合:两个子程序使用同一个全局数据,就是全局数据耦合(公共耦合或全局耦合)。这种作者不太认同,他觉得这么做子程序之间的联系不密切又不可见。
      • 不合理耦合:一个子程序使用另外一个子程序中的代码,改变了其中的局部变量。
    • 5.子程序长度:不要超过200行
    • 6.防错性编程:即使一个程序被传入坏数据,也不会受到伤害。
      • 断言
      • 输入垃圾不要输出也是垃圾
      • 异常情况处理
      • 预计改动
      • 计划去掉调试帮助
      • 检查函数返回值
      • 在最终软件中保留一些防错性编程(保留查找重要错误和延缓终止的代码
      • 去掉无关紧要和引起程序终止的代码)
    • 7.子程序参数:
      • 确保实际参数和形式参数匹配(养成检查参数表中的参数变量类型和类型匹配)
      • 几个子程序使用了相似的参数,应按照不变的顺序排列这些参数
      • 使用所有参数
      • 把状态和“错误”变量防止最后
      • 不要把子程序中的参数当做工作变量
      • 说明参数的接口假设
      • 应该把一个子程序的参数个数限制在7个左右
      • 仅传递子程序需要的那部分结构化变量

    第六章 模块化设计

    • 1.内聚性与耦合性
    • 2.信息隐蔽
    • 3.建立模块的理由(模块不支持继承性)
      • 用户接口
      • 对硬件有以来的区域
      • 输入和输出
      • 操作系统依赖部分
      • 数据管理
      • 真实目标与抽象数据类型
      • 可再使用的代码
      • 可能发生变动的相互联系的操作
      • 相互联系的操作(相互联系的子程序和数据放在一起有更强的组织原则)
    展开全文
  • CodeWarrior相关概述

    千次阅读 2012-12-20 12:20:04
    CodeWarrior 能做些什么?  当你知道自己能写更好的程序时,你一定不会再使用别人开发的应用程序。但是常常会发生这种情况,就是当你写了无数行代码后,却找不到使得整个程序出错的那一行代码,导致根本没法编译...

    第一课认识 CodeWarrior Translated from CodeWarriorU.COM

    CodeWarrior 能做些什么?

        当你知道自己能写更好的程序时,你一定不会再使用别人开发的应用程序。但是常常会发生这种情况,就是当你写了无数行代码后,却找不到使得整个程序出错的那一行代码,导致根本没法编译和链接整个程序。这可能更令人灰心丧气。

        本文将告诉你如何使用 CodeWarrior 这一工具解决上述问题。

        从现在开始,我们将集中精力学习如何在 CodeWarrior 中使用 C/C++ 进行编程。为了学习本课程,你必须已经能够比较熟练地使用上述两种语言之一。CodeWarrior 也可以支持 Java 开发,但那是另一门课程的内容。本课程仅限于在Windows 平台上使用 CodeWarrior 进行的开发。一旦你精通了 CodeWarrior 编程后,你可以试试在其它平台上使用CodeWarrior。本文中讨论过的大部分内容都可以应用到开发 Mac 应用程序中。

        CodeWarrior 能够自动地检查代码中的明显错误,它通过一个集成的调试器和编辑器来扫描你的代码,以找到并减少明显的错误,然后编译并链接程序以便计算机能够理解并执行你的程序。你所使用过的每个应用程序都经过了使用象CodeWorrior 这样的开发工具进行编码、编译、编辑、链接和调试的过程。现在你在我们的指导下,自己也可以去做这些工作了。

        你可以使用 CodeWarrior 来编写你能够想象得到的任何一种类型的程序。如果你是一个初学者,你可以选择编写一个应用程序(比如一个可执行程序),比如象微软公司的文本编辑器 WordPad 这样的应用程序。

    CodeWarrior 入门

        CodeWarrior 是一个复杂的应用程序,你必须花点时间来了解它的各种各样的组件和功能。第一课将讲述 CodeWarrior IDE 的安装。我们将学习菜单、窗口和其它的一些方面。建议你最好一边学习本课程,一边学习使用 CodeWarrior 的集成开发环境。我们从运行 CodeWarrior 开始,如果你按照默认方式安装的 CodeWarrior 软件,那么在 Windows 的桌面上将会有一个该软件的快捷方式图标。双击该图标启动 CodeWarrior 的集成开发环境。如果在 Windows 桌面上没有这个快捷图标(这种情况经常发生),你可以在 C:/Program Files/Metrowerks/CodeWarrior/Bin 中找到 IDE.exe 文件,然后运行它。

    工具条

        启动 CodeWarrior 后,你将会看到,在屏幕上方的菜单下面有一个工具条。这个工具条(见图1-1)包含了一些常用菜单项的快捷方式。在后面的章节中,你将学到如何设置这个工具条,甚至是整个 IDE 环境。现在,你应记住,这个工具条用于快捷使用 IDE 中的功能。

    图 1-1: CodeWarrior IDE 的工具条

    文件菜单

        CodeWarrior IDE 的菜单是按照标准方式设置的。其中的文件菜单用于处理和文件相关的一些操作,比如创建、打开、保存和打印等等。

    编辑菜单

        CodeWarrior 的编辑菜单和其它的 Windows 应用程序也很相像。其中包括了剪切、复制和粘贴等操作,以及其它一些使得程序员能够更方便地管理源码版面布局的选项。在编辑菜单中还有一个 Preferences 项,我们在这一课的后面将提到它。

    查看菜单

        查看菜单(1-2)用于安排工具条和其它窗口在 CodeWarrior 环境中如何显示的选项。所谓的“其它窗口”包括许多特殊的窗口,比如观察点(Watchpoints)窗口,表达式(Expressions)窗口,过程(Processes)窗口和全局变量(Global Variables)窗口等。

    图 1-2: 查看菜单中的工具条子菜单

    查找菜单

        查找菜单用于在单个文件或硬盘的目录中查找指定的代码。你可以使用它来方便地替换文本块或在你的代码中搜索指定的项目。即使是在小工程中使用这个工具也非常方便。

    工程菜单

        工程菜单中的工具用于管理 CodeWarrior 工程。一个工程包括组成你正在编写的程序的所有的文件,包括头文件、源代码和库文件。工程窗口显示了所有这些文件的名称。我们将在第二课中深入讨论这部分内容。

    调试菜单

        这是在编制程序中最常用到的工具。我们将在第五课中详细介绍它的使用方法。

    窗口菜单

        用于在 CodeWarrior 环境管理窗口显示方式的菜单。

    帮助菜单

        通过帮助菜单可以到网上寻求关于 CodeWarrior 任何问题的在线解答。够酷的吧?

     

    了解 CodeWarrior 集成开发环境的设置

        CodeWarrior IDE 提供了许多设置以便让你定制你的工作环境。当你选择了编辑菜单中的 Preferences 项时,你将会看到一个设置对话框(如图1-3)。在该对话框中,有控制 CodeWarrior 编辑器、调试器和其它许多功能的界面和行为的选项。在这一节中,我们将学习这些设置的使用。

    图 1-3:  设置对话框

        你可以在自己的 CodeWarrior 中试试上述这些设置项。你可以先点击问号标志,然后点击你感兴趣的项目,就可以得到一个有关该项目的用途的简短介绍,也可以从帮助菜单中得到更详细的信息。

    通用设置

    • 编译设置(Build Settings): 选择是否在执行编译之前保存已打开的源文件,以及有多少内存被用于编译工作;
    • IDE 之外(IDE Extras): 几个独立的设置。比如指定 CodeWarrior 是否使用一个第三方的文本编辑器——因为集成的编辑器并不是很完美,这可以通过指定一个你惯用的编辑器来替代它;
    • 插件设置(Plug-In Settings): 供插件开发商调试他们的插件;
    • 隐藏文件夹(Shielded Folders): 在这里指定的文件夹在工程设计期间,或执行查找和比较操作期间,将要被忽略掉。如果在你的工程级有一个巨大的“数据”文件目录,而你又不想让这些文件降低 CodeWarrior 的操作速度时,这个设置就很管用了;
    • 资料树(Source Trees): 用于指定 CodeWarrior 在编译程序时用不着的目录。

    编辑器设置

    • 编辑器设置(Editor Settings): 几个用于定制编辑器显示、管理文本和窗口的设置项;
    • 字体和制表符(Fonts and Tabs): 设置编辑器中的文本大小、字体、制表符和其它显示设置;
    • 文本颜色(Text Colors): 用于指定特定语言元素(比如程序的注释)在编辑窗口中的显示的颜色。

    调试器设置

    • 显示设置(Display Settings): 几个用于定制调试器显示的设置项;
    • 视窗化(Windowing): 设定调试器如何管理它的窗口(比如隐藏所有打开的编辑器窗口)
    • 全局设置(Global Settings): 几个用于定制调试器在全局层次如何工作的设置。比如当一个包含了程序调试信息的文件被打开时,是否启动这个程序;
    • 远程连接(Remote Connections): 允许通过 TCP/IP 进行远程调试。这样,你就可以在地球的另一边调试你的软件了;
    • Java 设置/Java 调试(Java Settings/Java Debugging): 允许你设置特定的 Java 编程语言选项。本课程不涉及这部分内容。

    RAD 工具

    • Layout 编辑器: 几个用于在 CodeWarrior 中定制快速应用程序开发工具的使用的设置。

    正如你所见到的,CodeWarrior 有许多设置项和选项。设置完后点击“Save”按钮保存你所作的修改,点击“Close”按钮将忽略你所作的所有修改。在进入下一节课之前,多练习几次。最后请确认“默认的文本文件格式(Default Text File Format)”(在编辑器设置面板中)保持为 DOS 设置。

    第二课  显示和定制工程和目标文件(1)

        本课将讲述 CodeWarrior 操作文件的方式以及介绍它的工程(Project)窗口。

    什么是工程文件

        为了使用 CodeWarrior 来创建一个应用程序,你必须创建许多文件来构成一个工程(Project)。该工程的设置和所有这些文件的指针都被存放在一个工程文件中。这些设置包括编译和链接设置、源文件、库文件以及它们之间用于产生最终程序的相互关系。你可以将这个工程文件看作该工程的大脑:它保存了所有文件相互依存的关系并知道如何将它们组合成为一个可用的应用程序。工程窗口依次显示了这些关于你的程序和该程序所包含的文件的信息,并允许你只需轻松点击就可以修改这个工程。图2-1显示了工程窗口的外观。

    图 2-1: 显示工程信息的工程窗口(图示为空的情况)

        大多数组成你的程序的文件都是原始的文本文件。这些文件包含了你在 CodeWarrior 的编辑器中键入的源代码。在某些情况下,你还将使用一些预编译的源码和头文件。在进行数学运算和 I/O 操作时,你还要使用到一些库文件,比如Metrowerks 标准库(Metrowerks Standard LibraryMSL)

        通常,你都是使用 或 C++ 来编写源代码,但通过给 CodewWarrior 的集成开发环境加装相应的插件(plug-in),你也可以在 CodeWarrior 中使用其它语言来进行编程。别忘了,CodeWarrior 可是一个可扩展的集成开发环境。通过给它安装一些插件,你就可以扩展它的功能。比如说,加入有人开发了一种新的叫做 Z++ 的编程语言,你就可以为 CodeWarrior创建一个相应的插件使之能够支持 Z++ 语言,而且你还可以拿这个插件卖好多钱,让所有使用 CodeWarrior 编程的人都使上 Z++ 语言。那该多好!:

        现在你知道了 CodeWarrior 是如何使用文件的了。下面我们来详细地学习有关工程窗口的知识。

    工程窗口

        当你启动 CodeWarrior 并打开或创建一个工程时,工程窗口就会显示出来。该窗口用于管理整个工程的所有文件以及这个工程将要产生的目标(targets)文件。一个工程包含了足够的信息来编译一个或多个目标文件。所谓的目标(target)文件就是当你编译程序时,由 CodeWariior 创建出来的(通常是)一个应用程序或库文件。一些程序可以编译产生多个目标文件。比如,你可以编写一个程序来创建一个 DLL 和一个调用该 DLL 的主应用程序。在 CodeWarrior 的工程中,你可以定义一个目标来产生 DLL,定义另一个目标来产生应用程序。通过这种方式,你的工程只需编译一次就可产生所有需要的目标文件或片断。图 2-2 显示了Hello World程序的工程窗口。

     2-2: Hello World 程序的工程窗口(其中显示了所有包含于该程序中或用于编译该程序的文件)

     

        你可以看到,在这个工程窗口中确实包含了一些项目。在窗口的顶端有三个标签(tab)页:文件标签(如图2-2显示)、链接顺序标签页和目标文件标签页(后两个标签页将在后续课程中讲述)。在这几个标签页的上方,有一个用于选择要编译的目标文件的下拉菜单。在本例中,我们选择的是 Hello World 工程的调试版本。在下拉菜单的右边有几个图标,它们的存在允许你不必使用菜单命令就可以方便地编译、链接和运行这个工程。

    注意: 在某些版本的 CodeWarrior 中,链接标签被称为段(segments)标签。有些人可能在某些 X86 的处理器上编程时使用的是分段(segmented)代码。但使用 Windows 版的 CodeWarrior 工具时就不是这样,这是产生的 X86 代码使用的是"扁平的"(flat),或者叫做非分段的(unsegmented) 内存空间。

        顾名思义,文件标签页列出了在工程中所有可能用到的文件。你可以通过创建一些组(用文件夹图标表示)来分门别类地管理这些文件,并可以帮助你方便地了解哪些文件将被使用。在本例中,我们将 程序(.c为后缀名的文件)全都放到一个叫做 Source 的组中,同时将库文件放到另一个组中,以便整洁明了地管理这些文件。

        在 Sources 组中有一个 main.c 文件。因为我们还没有编译这个工程,所以在每个文件对应的 Code 和 Data 栏中显示数字的都是零。一旦我们编译了这个工程,这些数字就将显示出来,表示源码转换为机器代码后实际的代码量和数据量。可是,库文件 ANSICX86.LIB 和 MWCRTL.LIB 后面显示的是 n/a 。这表示索虽然这些文件被显示在这里,但是它们并不是 Hello World 目标文件的一部分。这两个文件是用于不可调试(non-debugging)开发的,而本例不是这种情况。如果我们从菜单中修改本例的目标文件为 Release(发布版本时,这两个文件就用得上了。这时这些文件后面的 Code Data 栏目就会显示相应的数值。

    注意 在 Data 和 Code 栏右端的 Target 栏目中的小子弹头也是用来表示该文件是否被当前编译生成的目标文件使用到。

        再往右边去,是 Debug (用一个绿色的小虫子表示),它是用于告诉你对应的文件在编译时是否要产生调试信息。我们将在第五课中详细论述这部分内容。最后,我们来看看在每一行最右端的弹出菜单,它是用于打开文件、更新源码、打开包含的头文件等等操作的快捷方式,具体是什么才作,要看它所代表的项目的类型而定。

        现在让我们来编译这个 Hello World 工程并看看编译后的工程窗口的情况。我们从 Project 菜单中选择 Make 命令来编译该工程。这将更新所有需要编译的文件并产生相应的输出文件——在本例中是 Hello World 这个应用程序。

    图 2-3: 编译工程后的工程窗口的显示情况

        从图2-3中我们可以看到,工程窗口发生了一些变化。Code 和 Data 栏都显示了当前工程中对应项的相应的数字。如果你打算看看编译前后存放这些文件的目录的话,你会发现编译后在该目录下产生了一些新的文件,如图2-4所示。

    图 2-4: 编译后将在工程目录中产生一些新的文件

        在本例中,编译工程后在工程目录中产生了一个新的文件。这就是 X86 目标文件输出——生成一个叫做 Hello World x86 的应用程序。其它一些一直存在于这个目录中的文件是: .mcp 文件工程文件本省,以及 .c 源文件包含了应用程序源码的文件。Hello World Data 目录中还包含了由 CodwWarrior 生成的各种各样的支持文件。现在你还不必去关心这些文件。如果你正在编译目标文件的调试版本,你会看到更多的文件被产生出来,这些文件中可能包含着符号(symbolic)调试信息。集成开发环境的调试器使用这些符号信息来记录在高层次源码中的操作轨迹。我们将在第五课中详细讲述这方面内容。

        现在你已经了解了这些文件是如何结合在一起被编译生成一个目标文件的。让我们来运行这个目标文件并看看它的运行结果是什么样。你双击 Hello World x86 应用程序,就会有如图 2-5所示的结果显示出来。

    图 2-5: Hello World 应用程序的运行结果

    Putting It into Play

    Someday, you too may create something as lyrical and profound as Hello World. Possibly, with hard work and determination, you can do this by the end of the day. The source code to make this work is as follows:

              

    #include

              

    void main(void) { int c; printf("Hello World on Windows!/n"); c = getchar();

              }

    That's it! Who knew it could be so easy? Go on, you know you want to try it, but with some sort of profound statement instead of "Hello World on Windows!" Soon you'll be writing your own applications that not only can display text but can also perform other tasks for you. Before you know it, you'll be customizing AIBO dogs! That's Lesson 4. Well, okay, not really. Lesson 4 is Linking.

    What's That Other Function Doing There?

    Those already seasoned in the C language might be wondering what the character input  function, getchar(), is doing here. Typically, when an application completes its job, it terminates, and the OS cleans up after the program. This means the Hello World window disappears moments after the application finishes drawing the phrase "Hello World on Windows!" So that we can admire our handiwork, we put the brakes on the program by having it wait for a final keystroke before it terminates.

    This is not a bug, but normal behavior. Honest. CodeWarrior does provide an alternate library that provides the C console I/O functions used here. This alternate library will not only halt the application after it finishes writing to a window but will also let you save the text output to a file.

    Link Order

    When you build a program using multiple source files, the link order is very important. After compiling your source code, you must link the items in the proper order. You will use the Link Order tab to determine this order. In the case of the Hello World x86 target we've been working with (Figure 2-6), note that the main.c source file is first on the list, with the libraries used by the program following. To reach the Link Order tab, open the project and click the tab. To change the order by which files are linked, you simply drag items up and down the list within this window. We will discuss Linking in more detail in Lesson 4.

     

    Figure 2-6: Back in the Project window, clicking the Link Order tab displays a list of component files included in the current project.

    Targets

    Figure 2-7: The Hello World project's Target tab.

    After you've chosen the files and defined their link order, you must configure the target. The target is essentially the output file -- in our case, the Hello World application. There are also a lot of settings that must be set behind the scenes to make it possible for you to hit the target. If you double-click the Hello World target in the Targets Tab (Figure 2-7), you should see the dialog box shown in Figure 2-8.

     

    Figure 2-8: Set targeting preferences in the Settings window.

    Configuring Target Settings

    The preferences you learned about in Lesson 1 are global settings used throughout the CodeWarrior IDE. Now, let's take a look at options for configuring target options. I will briefly examine the most important of these options. Follow along in your copy of CodeWarrior by double-clicking an item under the Target tab to bring up the Settings panel.

    If you want a little extra guidance, move your cursor over an item in the panel, right-click it, and read the pop-up help text that appears.

    Note: Some of the items listed below are specific to the target we are currently viewing, in this case the Hello World target. If you're using AMD's K6 or Athlon processors, some of these panels may contain items that use features specific to that chip. Consult the documentation for the compiler/linker that you are using to ensure that you are making the best use of your development environment.

    Target Preferences

    Target preferences include the following items, each with its own panel:

    • Target Settings: Choose the target's name (the name that appears in the target pop-up menu), which linker(s) to use, and the output directory here.
    • Access Paths: Access paths tell CodeWarrior where to search for required files that are a part of the project but not necessarily project-specific (such as header files). CodeWarrior will not, by default, search your entire hard disk for files. It will only look where you explicitly tell it to look with access paths.
    • Build Extras: A few miscellaneous settings to improve the build speed.
    • Runtime Settings: In order to debug non-application code, such as a plug-in, you must have an application assist you. The application does this by calling the suspect plug-in code. This panel lets you specify the host application to use in this situation. This topic will be mentioned in Lesson 8.
    • File Mappings: Every file must be mapped so as to identify it to the compiler. That is, this panel tells CodeWarrior that files ending in .cpp are C++ source files and should therefore be compiled using the C++ compiler. See Figure 2-9 for more examples.

     

    Figure 2-9: The File Mappings options allow you to specify the treatment of files, based on their extension.

    • Source Trees: Source trees are similar to access paths. Here you can enter file locations, such as file servers, that are specific to your project.
    • x86 Target: This panel allows you to set the type of project (application, library, or DLL), and various aspects of the project -- including how much memory the resulting program requires. It is also where you specify the name of the file output by the IDE. This name can -- and usually will -- be different from the target's name (the one entered into the Target Settings panel).

     

    Setting Other Preferences

    CodeWarrior includes a variety of additional preferences that can help you customize your environment before beginning to work on your own programs.

    Language Settings

    Language Settings include the following items: (discussed further in Lesson 3)

    • C/C++ Language: There are an extremely large number of settings for the C/C++ compiler. Suffice it to say that you can alter them here. See Figure 2-11.

     

    Figure 2-10: C++ Language settings.

    • C/C+ Warnings: These settings (Figure 2-10) tell the C/C++ compiler whether to provide warnings as your code is being compiled. Sometimes source code can contain elements that aren't quite correct but are not necessarily errors. These are called warnings. You can instruct the compiler to treat all warnings as errors so that you can quickly examine the questionable code in the editor.
    • Windows RC: A Windows-specific resource compiler. Resources are graphic elements (such as windows, buttons, and menu items) that can be built independent of your program code. This panel lets you specify a header file that the resource compiler uses when generating these interface elements.

    Code Generation

    Code Generation preferences include the following items:

    • x86 Processor: There are a number of decisions your compiler must make as it generates the machine code from your C or C++ source code. The settings in this panel help the compiler to optimize your code. This item will be labeled differently on each CodeWarrior platform. Figure 2-11 shows the x86 version. This is discussed more in Lesson 3.

     

    Figure 2-11: x86 Processor settings are specific to projects intended for a x86-based computer. CodeWarrior includes settings for each operating system it supports (directly, or through plug-ins).

    • Global Optimizations: This has got to be one of the most exciting and useful panels in all of CodeWarrior (see Figure 2-12). This panel allows you to set the global optimization level of the compiler. This means that you can control how efficient and fast the compiler tries to make your source code. However, speed comes at a price. The smaller and faster the source code, the more difficult it is to debug, and the longer it takes to compile. Fortunately, the panel spells it all out for you so you can make that decision every time you optimize! If you want to, I mean. You will have a great deal of fun with this panel as you adjust the settings to suit your needs and your projects.

     

    Figure 2-12: Use Global Optimization settings to speed up compiling of the code (Optimizations Off), or make your code execute faster (Levels 1 through 4).

    Linker Preferences

    Linker preferences include the following items:

    • x86 Disassembler: The name and specific settings in this panel may differ a bit by platform, but the options are similar. They allow you to configure specific settings that the Linker must use when disassembling and debugging your target.
    • x86 Linker: The name and specific settings in this panel may differ a bit by platform, but the settings are similar (Figure 2-14). They allow you to configure specific settings that the Linker must use when building your target, such as specifying the entry point into your code. See Lesson 4.

     

    Figure 2-13: Each supported operating has a Linker settings panel. Here is the x86 version.

    Settings for the editor can be found on the Custom Keywords panel. This panel allows you to configure the text editor to display various custom keywords in different colors. For example, if you like to use a keyword in your source code to label certain areas of the code in a different color, you can list it here. It will appear in the color you choose.

    The Debugger Settings panel allows you to choose how the debugger should log data, handle watch points, etc. This is discussed in more detail in Lesson 5.

    Now you can begin to see how CodeWarrior's features help make programming easier. Now go check out your homework. Yes, you will have some homework assignments in this class. Then I'll see you back here for Lesson 3.

    第三课            

    第一步:理解编译阶段

    我们的前面的课程中已经学到,源程序输入完之后的工作就是要编译它。编译源程序就是计算机把你所写(通常是用C或 C++编写的)的源代码进行分解、分析,然后转化为机器语言。机器语言是一种计算机能够理解阿语言,而且它运行起来比 或 C++ 也要快得多。编译的大致过程如下:

     1. 用 或 C++ 编写代码:

    #include 
    void main(void) { 
       int c; 
       printf("Hello World on Windows!/n"); 
       c = getchar(); 
    }

    2. 进行编译。 CodeWarrior 编译上述代码并把它翻译成机器语言,结果如下:

              符号名:

     1: _main 2: _@8 3: _printf 4: ___files 5: _fwide 6: ___get_char =============================================================== 
    SECTION SIZE = 0x0000003C; NAME =?.text DEFINED SYMBOLS: name = _main offset = 0x00000000; type = 0x0020; class = 0x0002 00000000: 68 00 00 00 00 push offset _@8 00000005: E8 00 00 00 00 call _printf 0000000A: 59 pop ecx 0000000B: 6A FF push -1 0000000D: 68 00 00 00 00 push offset ___files 00000012: E8 00 00 00 00 call _fwide 00000017: 85 C0 test eax,eax 00000019: 59 pop ecx 0000001A: 59 pop ecx 0000001B: 7D 1E jge $+32 ; --> 0x003b 0000001D: 83 2D 2C 00 00 00 sub dword ptr ___files+44,1 00000023: 01 00000024: 72 0A jb $+12 ; --> 0x0030 00000026: FF 05 28 00 00 00 inc dword ptr ___files+40 0000002C: EB 0D jmp $+15 ; --> 0x003b 0000002E: 89 C0 mov eax,eax 00000030: 68 00 00 00 00 push offset ___files 00000035: E8 00 00 00 00 call ___get_char 0000003A: 59 pop ecx 0000003B: C3 ret near 
    ============================================================= SECTION SIZE = 0x00000019; NAME = .data 00000000: 48 65 6C 6C 6F 20 57 6F 72 6C 64 20 6F 6E 20 57 Hello World on W 00000010: 69 6E 64 6F 77 73 21 0A 00 indows! DEFINED SYMBOLS: name = _@8 offset = 0x00000000; type = 0x0000; class = 0x0003 ==============================================================

        上述机器代码难于阅读,不要去管它。机器代码相比 或 C++ 而言,要难理解多了。但是,计算机只能理解机器语言。只有将你的程序编译或翻译成机器代码,然后再执行它,这样运行起来才能快一些,而不是每次运行时才去翻译它,那样运行速度就很慢了。

        你只需选定一个源文件,然后从工程菜单中选择反汇编项,你就能看到该文件的机器语言清单。实际上上面我们看到的机器语言清单就是这样得到的。如果你仔细地对照阅读一下你的 或 C++ 源代码和它编译后的机器代码,不难发现它们之间的关系。

    CodeWarrior 中编译选项的详细设置

        在正式开始编译源代码之前,CodeWarrior 还要对其做预处理。在这个阶段,是对 或 C++ 代码进行编译前的一些准备工作。在编写程序的过程中,往往会有很多相同的P代码输入,于是程序员使用一些快捷方式,比如所谓的宏(macros)来代替这些相同的输入。例如,你可以使用 APPNAME 作为一个宏,来表示Metrowerks CodeWarrior,以此来减少输入的工作量。预处理就是要把这些宏转换为它们实际表示的代码,此外还要替换一些定义符号(比如 #define THREE 3)为实际的源代码。为了更好地理解预处理所做的工作,你可以查看一下预处理结果的清单。首先在工程窗口中选中一个源文件,然后从工程菜单中选择预处理项,你就可以看到源代码进行了预处理之后,编译之前的结果清单了。.

    定制 CodeWarrior 的编译方式

        在第二课中,我们已经了解了一些控制 CodeWarrior 编译代码的选项对话框。现在我们再来详细地看看一些标准 C++ 编译器的设置。请你按照下面所学内容在你的 CodeWarrior 上进行实际的操作练习。

    图 3-1: 控制 CodeWarrior 进行  C/C++ 编译的语言设置

        从编辑菜单中选择 Hello World X86 setting 项来打开设置窗口。现在,点击图3-1中语言设置类(Language Settings category)下面的 C/C++ 语言标签(C/C++ Language label),你就可以看到C/C++ 语言设置对话框中的许多选项。我们逐个来学习这些选项,以便了解它们是如何影响编译的过程的。

    • 激活 C++ 编译器(Activate C++ Compiler): 这个选项允许你把所有后缀名为 .c 的源文件当作 C++ 文件来进行编译。如果你想在 源码中使用一些 C++ 语言的特色时,这个功能就很有用了;
    • ARM 一致性(ARM Conformance): 编译器要求你的代码遵循 ANSI C++ 的标准。但你可以通过选中这个选项来指定你的编译器遵循注释 C++ 参考手册(Annotated C++ Reference ManualARM) 中的标准;
    • 允许 C++ 例外(Enable C++ Exceptions): 选中这个选项将允许你在 C++ 代码中使用 try/catch/throw 等块(blocks)。这些方法用于书写错误管理器(writing error handlers)
    • 允许 RTTI(Enable RTTI): RTTI 表示 Run Time Type Information(运行类型信息)。这个选项运息编译器判定一个C++ 对象在运行中的类型。这是 C++ 的高级特色,在很多情况下都是很有用的。如果你想了解更多关于 RTTI 的信息,请查阅你的 C++ 手册;
    • 内联深度/自动内联/延期内联(Inline Depth/Auto-Inline/Deferred Inlining): 这些是关于源代码中使用的内联函数的一些设置项。所谓内联函数,就是在编译时该函数的源代码将被直接插入到程序体中,而不是产生这个函数的调用。在某些情况下,使用内联函数可以提高代码的性能。这是编译器的一个高级设置项;
    • 字符串池(Pool Strings): 通常,编译器将编译后代码中的所有字符串对象存储到它们自己的数据空间中。如果你想将所有的这些字符串存放到一个数据空间中,就应该选中此项。如果你的源代码中有非常多的字符串(比如上面我们提到的 APPNAME ),那么你就应该选中此项来节省内存空间。这个功能只能在使用 PowerPC  Mac OS 平台上编程时使用;
    • 不重用字符串(Don't Reuse Strings): 所谓重用字符串,就是指当你的程序中有几个完全一样的字符串时,编译器会将它们全都存放到同一个数据空间里。但是,有时你可能想修改某个字符串,这就会造成其它和要修改字符串共享数据空间的字符串也要被修改了。如果你想避免这种情况,就要选中这个选项。这样,即使程序中有完全一样的字符串,它们也将存放到不同的数据空间中;
    • 要求有函数原型(Require Function Prototypes): 建议最好选中此项。使用函数原型可以帮助编译器在检查传递给函数的参数类型时发现代码中的错误。所谓函数原型就是在程序的前端对函数进行声明。也就是说,你应当在使用一个函数之前,定义或声明这个函数。既然这个选项这么好,那么什么时候应该关掉它呢?通常是,当你使用一些老版本的 进行编程时,并不需要对函数进行事先声明,这时你就应该关掉这个选项来检查整个程序代码。你一定愿意在程序前端书写函数原型,并选中此选项,因为它能帮助你解决这么多的编码错误;
    • 允许支持布尔变量(Enable bool Support): 为了使用 C++ 的布尔变量—— true ( false (这两个关键字,必须选中此项;
    • 允许支持wchar_t(Enable wchar_t Support): 为了使用 C++ 的内置类型 wchar_t 而不是 char 类型来表示字符类型,必须选中此项;
    • 严格遵循ANSI/只能使用 ANSI 关键字(ANSI Strict/ANSI Keywords Only): 默认情况下,编译器允许你使用Metrowerks 扩展和 C/C++ 语言的附加关键字。但是如果你想在这种情况时编译报错,那么就应该选中这两项。这样,编译出来的程序就是 100% ANSI 兼容代码;
    • 扩展通配符(Expand Trigraphs): 默认情况下,通配符是不允许的。为了能够使用通配符,就要选中此项。所谓通配符,就是在你的源代码中代表字符常量的方式。例如, '????' 就是一个通配符;
    • 多字节敏感(Multi-Byte Aware): 如果你是有能够的编程语言要求使用多字节字符(例如 Kanji  Unicode),就要选中此项。这样编译器才能正确地处理源代码中的多字节字符;
    • 指示到 SOM (Direct to SOM): 这是 Macintosh 平台上才有的功能,它允许你在 CodeWarrior 中直接创建 SOM 代码。SOM 是一种使用于苹果机上的开放文档环境的代码类型,但现在已没人用了;
    • 使用 CR 为换行符(Map Newlines to CR): 这个选项允许你交换 '/n'  '/r' (这是用于标识源码行结束的符号)。此选项只对 Mac OS 上的编程有用;
    • 不严格的指针类型规则(Relaxed Pointer Type Rules): 选中此项将把 char *, unsigned char *, void *  Ptr 当作是同一种类新。当你从另一个并没有正确管理指针类型的源代码中,或者是从一个使用还不能正确支持这些类型的老编译平台上开发的源代码中继承代码时,这个选项就很有用了;
    • 枚举类型总是整型(Enums Always Ints): 通常情况下,编译器将一个枚举类型分配与之最接近的类型同样的空间。如果你想使枚举类型的空间总是和整型一样,那么就要选中此项。所谓枚举类型就像这样:enum {itemone, itemtwo = 7, itemthree}。其中,itemone 等于 0itemtwo 等于 7itemthree 等于 8
    • 使用无符号字符类型(Use Unsigned Chars): 选中此项将把所有字符数据类型当作无符号字符类型来处理;
    • EC++ 兼容模式(EC++ Compatibility Mode): 使用 CodeWarrior 编译嵌入式 C++ (EC++) 代码时,要选中此项。请注意,此时 C++ 中的诸如模板(templates)、例外(exceptions)和其它一些 C++ 的高级功能就不可用了。具体情况请查阅 C++ 手册;
    • 允许 Objective C(Enable Objective C): 为了使用 Objective C ( NeXT 计算机操作系统上很著名的编程语言),要选中此项。此选项只能在 Mac OS 下只用;
    • 前缀文件(Prefix File): 如果需要在每个源文件中包括一个头或预编译头文件,就要将该文件名输入在此处。适用情况:当所有源文件都要访问一个特殊的定义,但你又不想在每个源文件中键入 #include 来包括该定义时,使用此选项很方便。

    注意: 上述许多选项在 Mac OS 和 Windows 平台上的 CodeWarrior 编译器版本中都是一样的。但根据我们在上面对这些编译器选项的描述可知,一些选项在两个平台上还是有一些不同的。然而,和 C++ 时平台无关的编程语言,因此大多数概念都是可以应用于任何平台的。

    第四课      

    第二步:连接程序各部件

        当你的程序编译完毕以后,接着就要使用连接器来把这些组成你的程序的各个文件连接起来。

        连接器是用来组合各个源文件被编译后得到编译代码的,同时还要将这些文件与它们所需要的库连接在一起。连接确保了每片代码都知道如何去找到你程序中的其它元素。例如,连接器修改机器代码以便使用 MSL 库函数中的 printf() (该函数用来实现功能强大的控制台输入/输出)来替换你在源文件中使用的 printf() 函数调用。

        当你编写一个程序时,常常将代码写成函数并存放在不同的文件中,然后将这些文件编译得到单个的输出文件。就像我们在前面使用的 Hello World 这个程序,它的输出文件是一个应用程序。但是在某些时候,同一个程序中使用的函数在输出时不一定是存放在同一个输出文件中的。例如,假设你编写一个应用程序时,其中包含了许多被程序的多个部分重复使用的代码,你就可以将这些被重复使用的代码编译成为一个可共享的动态链接库(DLL)。这样,该应用程序仍然可以访问到这些代码,但是它就需要弄明白应该到哪个文件以及如何找到这些它需要使用的函数。这种情况,就需要做一些连接的工作了。

    连接的类型

        在程序的编制中,一个连接就是一个指针,指向一个象编译函数这样的对象,或者指向一个变量或一个数组这样的数据内容。连接有两种方式:硬连接(hard linking)和软连接(soft linking)

        硬连接发生于连接过程中,说明代码的时候。换句话说,如果一个函数被使用了,在连接时连接器确切地知道这个函数在哪里并创建一个连接把这个函数连接起来。

        软连接(也称为弱连接)发生于进行连接时,一些代码可以不需要进行说明,因为你知道这些代码位于一个 DLL 中,并在运行时可以从该 DLL 中进行调用。在这种情况下,连接器会告诉应用程序到哪里去找它所需要的代码。         软连接也是计算机操作系统中许多元素使用的方法。接下来,我们一起来学习一些关于连接器的选项设置。

    连接Detailed Look at Linking

        为了将来能更好地领略 CodeWarrior for Windows 平台的卓越功能,你首先需要了解如何配置 CodeWarrior 的连接器来满足你的要求。

    根据你的要求来配置连接器

        本课中使用的示例是运行在 x86 平台上的  CodeWarrior 的连接器设置面板(如图4-1)。但是在这其中使用的很多术语也是适用于其它平台上的 CodeWarrior 连接器的。

     4-1  x86 连接器设置窗口

        通过选择编辑菜单中的“工程名”项来打开这个设置窗口。(“工程名”是指当前的活动工程——在本例中就是 Hello World x86),然后点击设置窗口中的 x86 连接器(或类似的名字)标签。

        下面我们来看看一些连接器设置项:

    • 入口点用法(Entry Point Usage):  Windows OS 体系中,每一个代码模块都有许多可能可以或者不可以选择的入口点,这取决于你所编译的代码类型。这些入口点的自然属性依赖于你正在编写的程序的输出文件(应用程序、库或 DLL )的类型和其它的一些因素。当一个应用程序需要调用一个 DLL 函数时,主机操作系统就使用这个入口点信息来正确地调用该 DLL 地初始化函数。大多数情况下,你应当设置此选项为默认方式(如图4-1所示)。这将让CodeWarrior 根据你在目标文件设置面板中选择的目标文件类型,来自动地为输出文件设置正确的入口点;
    • 子系统(Subsystem): 子系统有三种选择:Native Windows CUI  Windows GUINative 选项是用于开发驱动程序和其它异种系统模块的; Windows CUI 选项是用于开发使用控制台风格用户界面( console-style user interface,所谓的CUI )的应用程序的;Windows GUI 选项用于开发使用 Windows 图形用户界面(Windows graphic user interface,所谓的 GUI )的应用程序的。因为我们是用的示例程序是简单的基于控制台的 Windows 应用程序,所以我们选择了 CUI 子系统。通常情况下,CodeWarrior 会根据你在开始创建工程时选择的工程模板来自动地设置子系统的选项;
    • 子系统 ID/用户 ID(Subsystem ID/UserID): 和子系统项一样,这个选项也是由 CodeWarrior 自动设置的。具体系统对应的 ID 为:Windows 3.1Windows 95  Windows NT 3.5  ID  3.10Windows NT 4  ID  4.00
    • 生成连接映射(Generate Link Map): 这个选项允许你生成一个文本文件,其中包含了程序中使用到的所有的类和函数的详细信息。当需要通过匹配变量或函数名的地址来调试代码时,这个选项就非常有用了;
    • 生成 SYM 文件(Generate SYM File): 这个选项允许你在连接过程中生成一个符号文件。大多数的调试器在进行调试时需要一个符号文件来逐步调试你的源代码。我们将在第五课中详细讨论这个内容。此外,你还可以通过选中生成 CV 信息项(Generate CV Info item)来生成一个可用于 CodeView 的符号文件。CodeView 是一个流行的Windows 调试工具;
    • 生成 CV 信息(Generate CV Info): 生成一个可用于 CodeView 的符号文件。CodeView 是一个流行的 Windows调试工具;
    • 命令文件(Command File): 这个选项要你选择一个用于指示复杂的连接选项设置的文本文件。该文件称为连接器命令文件。它被使用来指定要导入和导出哪些符号。除非你正在编写一个特殊目的的代码,你才不需要一个连接器文件。

    正如你所知的,连接器的设置过程是非常复杂的。但是,在多数情况下,你不需要修改连接器的默认设置。Metrowerks 公司的开发人员已经帮助你很好地解决了这个问题。

    第五课             

    关于调试

    Purging pesky pests proves productive for programmers!

        调试就是在你的程序代码中找到一个问题并着手去解决它的过程。不管是谁,在编程时都难免出现一些输入或程序逻辑上的错误,一旦出现这种问题,很可能你想破了头也找不到错误在哪。这个问题可能只是一个输入上的小错误或者只是一个变量用错了,因此你很难发现它。但是因为这个错误的存在使得你的程序无法实现预期的功能。有时候会更糟糕,以致你的机器都崩溃了。这时,你就必须借助调试来解决问题。

        CodeWarrior 带有一个集成的调试器,可以让你在 CodeWarrior 主窗口中打开一个窗口来进行调试工作。使用调试器的方法是,从工程菜单中选择 Enable Debugger 项,然后重新编译你的目标文件。完成上述步骤后,你的程序就会在调试窗口中运行,此时你就可以隔离并改正你找到的任何一个问题了。

        通常进行调试时总要生成源程序的一个调试目标文件。该目标文件的源文件保持不变,但是需要预先设置置一些选项才能产生该程序的调试版本。通过点击工程窗口的弹出菜单中的调试目标文件,你就可以进入调试状态。

    图 5-1: 调试器界面

         5-1显示了 Hello World 这个程序在 CodeWarrior 调试器中进行调试时的情况。为了能够更方便地演示调试器的工作过程,我们在程序中添加了一小段循环代码(其中使用了 i y 等三个变量)。调试器允许对代码执行单步逐行调试。当你在代码中移动时,你还可以查看程序中所有变量的值。在这种“慢速移动(slow motion)”的方式下调试程序,你可以非常清晰地查看到代码的运行情况并很容易地解决问题。

    但实际情况是,不是所有的问题都很容易解决的。有些问题就有可能要花费你长达数周的时间去解决。不管是经验多么丰富的编程老手,同样不可避免地要遇到这样的问题。作为一个初学者,你更容易遇到这样的问题,但是,你千万别被这种情况吓倒。请记住,从错误中学习,你能学到更多的东西。

        请注意在调试器窗口左上角的那些按钮,它们是用于帮助你进行调试工作的。这些按钮从左到右分别是:运行程序(Run Program)、停止(Stop)、退出(Kill (Quit))、跳过(Step Over)、转入(Step Into)和转出(Step Out)。最后两个命令按钮是让你决定在单步调试程序时,如果遇到一个函数,是否要进入函数内部进行代码调试。或者你也可以使用跳过(Step Over)命令让该函数一次执行完毕,而不进入函数体中单步调试函数的代码,这样可以节省一些时间。如果你不能肯定函数是否正确无误,那么你可以转入(step into)该函数体内部对其进行单步调试。对于初学者,你应该单步调试每一个函数,至少可以练练手嘛!

         5-1 所示的调试窗口包含三个主要部分:

    • 栈窗格(The Stack pane)位于左上方的窗格是显示调用栈(也叫做调用栈的记录)的区域。当你在单步调试源码的过程中调用了多个函数时,在这个区域就显示出调试到达当前状态之前调用过的函数名清单。这个清单会随着你的调试过程,根据你是否转入函数内部的情况随时增减;
    • 变量窗格(The Variables pane):  位于右上方的窗格是显示变量名和其值的区域。其中显示的是各个变量的名字和它们的值,而且还可以依据变量的类型让你查看(或修改)这些数据。在图5-1中显示的 Hello World x86 程序这个例子中,有四个变量:ci y。当你在代码中执行单步调试时,这些变量的值就会根据程序执行的过程实时地更新。例如,在调试过程中,你可以注意到后三个变量是如何取得它们的值的(这些值是在循环代码中赋给这些变量的),而变量 c 显示的是一个无意义的值(因为你还没有给 getchar() 输入任何值)。你可以使用这个窗格来发现不正常的变量值,从而发现程序设计上的错误这对于程序的调试成功,非常关键;
    • 源代码窗格(The Source pane)位于下方的窗格显示了 Hello World x86 这个程序的 C 语言源代码,它也可以用来显示该代码的汇编语言版本。随着调试的深入进行,仅仅是程序的 C 语言代码可能不能提供足够的调试信息,这是你就需要查看该程序的汇编语言代码。你可以通过该窗口下端的弹出菜单来选择查看程序的源代码、汇编代码或者两者的混合显示。

        你可能已经注意到在源代码窗格左边的红色停止标记,该标记表示这是一个断点,它用于告诉调试器在此处停止执行程序。此功能可以帮助你很方便地快速到达程序中地某一位置,然后从此位置处开始调试程序。当你设置了一个断点,调试器运行到断点所在的行就停止下来,等待你的控制命令。在某行程序左边显示的蓝色小箭头是当前程序的计数器,它表示指向的程序行为即将要执行到的语句。在图5-1中,我们已经设置了一个断点以便程序执行到完源代码中的循环块后在第二个输出语句前停下来。设置断点的操作也很简单,只需在你希望调试器停止的程序行的左端点一下鼠标即可。

        下面我们来看看关于 CodeWarrior 调试器的一些参数设置。

    全局调试器参数设置

        通过选择编辑菜单中的“Preferences”项,可以看见 CodeWarrior 调试器有许多全局的参数设置。

    设置内容

    图 5-2: 调试器全局参数设置显示

        设置显示面板(见图5-2)包含了一些决定调试器窗口上如何显示信息的参数设置。当你刚开始使用调试器时,你可以在这里把调试器窗口上的某些文字设置得大一些或小一些,或者用一些特殊的颜色来显示它们。

        颜色设置允许你选择在源代码中用于突显观察点(watchpoints)和变量名的文字颜色。这种设置非常有用,可以帮助你在调试代码时很容易地观察到观察点或变量值的变化。观察点可用来帮助你在整个程序中跟踪某个值并将其显示在一个窗口中。

        其它设置的用途都解释得很明白了,你也不需要去更它们,默认设计就可以了。当你对调试器了解得更多了一些时,你才可能会的想去修改这些设置。

    关于视窗的设置

    图 5-3: 关于视窗的设置

        在调试程序的过程中,你会发现桌面上很会就挤满了许多打开的窗口。关于视窗的设置(见图5-3)允许你设定在调试中隐藏或显示某些特定的窗口来管理这些窗口。默认的设置是隐藏非调试用的窗口,这在大多数情况下就可以了,不必修改它。

    全局设置

        全局设置窗格(如图5-4所示)包含了一些用于控制调试器操作方式的选项。

    图 5-4: 全局设置

        调试过程中缓存修改后的文件这一设置是用于决定修改后的文件需要缓存多长时间用的。缓存文件能够提高调试器的速度,但是要牺牲一些磁盘空间。

        其余的设置解释得很清楚了。如果你只是一个初学者,那么默认设置就可以了。

    远程连接

    图 5-5: 远程连接

        最后是远程连接设置(见图5-5)。该设置面板用于设置通过一个 TCP/IP 网络进行远程调试。这是一些高级设置,作为初学者,使用默认设置就可以了。

        至于 Java 设置和 Java 调试面板用于使用 Java 语言进行编程的情况的,本课程不涉及这个内容。你可以学习一些相应的课程。

    目标文件和工程特定的调试器参数设置

        以下是关于被调试的目标文件和工程的设置项。当在 CodeWarrior 的工程窗口的目标文件标签中有一个目标文件被选中时,你再选择编辑菜单中的工程名设置(Project Name Settings)项即可看到这个设置窗口。

    图 5-6: 目标文件和工程调试器设置

    调试器设置

    • 重定位后的库...的位置(Location of Relocated Libraries, etc)如果你正在调试的文件被移到其它位置了,或者你正在通过一个 TCP/IP 网络来进行远程调试,你就必须将这些文件所在的目录输入到这个文本框中;
    • 在应用程序启动时在临时断点处停止(Stop at temp breakpoint on application launch)默认情况下,这个选项在程序的起始位置中断调试器的运行。但你也可以设置为可在任何断点处中断;
    • 自动查找目标库(Auto-target Libraries)选中这个选项可以自动地访问库。此选项用于正在调试的代码是一个库的一部分时,当你调试这些代码时,调试器将“做应做的事”(例如装载库并调用任何初始化函数)
    • 记录系统消息(Log System Messages)这个选项用于强制将所有的系统消息写入一个记录文件中。当你遇到代码导致系统崩溃,并因系统崩溃又看不到屏幕显示时,就很有用了。当你重启机器后,你可以从记录文件中查看导致系统崩溃的消息。特别是今后你调试一些大型程序时,记录文件就很有用了,所以一定要学会读懂记录文件!
    • 数据更新时间间隔(Data Update Interval)选中这个选项并给它设置一个值,这样你就可以决定数据查看(data view)多长时间更新一次。数据查看更新得越频繁,调试器显示的数据值就越新;
    • 缓存运行中的符号文件(Cache symbolics between runs)在调试器运行时,符号文件(在编译过程中产生的一种文件)将被缓存起来。这可以提高调试的进程;
    • 在观察点处停止(Stop at Watchpoints)选中此项,当观察点值为真时就中断调试。观察点和断点一样是很有用的。不同的是,断点每次都是在一个特定的代码行处停止下来,而观察点是监视一个变量或内存的一块区域,当此被观察的对象发生改变时,它就中断调试过程。这和 VC 中的条件断点相似,都是在设定的条件被满足时中断调试过程的。这是一个很有用的调试工具。

    x86 异常(Exceptions)

    图 5-7:  x86 出错信息面板

        当你调试有问题的代码时, CodeWarrior 调试器会截取到由程序的 bug 引起的某些奇怪的操作(即所谓的 异常——Exceptions) IDE 会将这些异常传递给一个特殊的代码——一个被称之为处理器的东西——由它来处理这些异常问题。

        但是,对于大多数的异常,调试器只是简单地中断它的工作而已。为什么会这样呢?

        如果你正在做一个高级开发,例如开发一个系统库或一个设备驱动程序,你可能想测试一下这些代码是如何处理这些异常的。只有调试器不截取这些异常时,你才能这么做。如果为了一些特定的原因,你希望调试器抓住某些特定异常发生时的控制("seize control of the moment" for specific exceptions) ,你就应该在这个设置面板上做一些设置。在该面板上显示的异常是 CodeWarrior 的集成开发环境可以处置的情况。点击那些你希望由调试器来处理的异常情况,然后点击“保存”按钮保存设置。就像前面提到的,这是很高级的编程设置,因此我们在这将不作详细讨论。总之,CodeWarrior 的调试器是一个非常先进的,而且很易于使用的附件。如果你学好了调试器的使用,它将会成为你开发软件的最重要的一个工具。

    另外还有“其它的可执行程序”和“远程调试”设置面板,它们是针对高级用户设置的,本课程不打算再作介绍。如果你需要同时调试多个可执行程序或通过 TCP/IP 网络来调试程序的话,你可以自行参看这几个设置面板。

    第六课         

    概      

        这将是最简单的一课,但它却能够帮助帮助你更好地组织你的思想和开展工作。    

       正如我们在第一课中学到的,可以有许多中方式来为手头某个特定的任务定制 CodeWarrior。你可以定制 CodeWarrior的外观和许多设置项,以便使得编译、连接和调试你的程序变得更快、更简单。    

        下面我们来看看可用于帮助你更有效地使用 CodeWarrior 的定制选项。因为定制 CodeWarrior 会改变 IDE 的所有行为,所以我们可以想到在编辑菜单下找到这些设置项。在那儿你将找到选择、命令和键绑定(Key Bindings)等菜单。点击其中的命令项就会显示定制 IDE 命令设置的窗口。定制 IDE 命令的面板包含两个主标签页。命令标签页允许你很容易的定制出现在每个 CodeWarrior 菜单中的命令。通过该标签页你也可以修改在内置的文本编辑器中使用的命令,例如选择文本和移动光标等命令。在这个设置窗口中,你还可以创建任何用于触发某个菜单命令项的组合键(也称之为键绑定——key binding),以此来启动一个应用程序,或者执行一个脚本。当然,你也可以设定一个某个命令项是否出现在一个菜单中。   

        定制 IDE 命令的面板中的另一个标签页是工具条项目标签页,在这个标签页中你可以看到在 CodeWarrior 的工具条中显示的项目,例如显示在每个文本编辑器窗口上方(或下方,取决于你的参数设置)。    

        在工具条项目标签页中,你只需点击一个工具条图标然后把它拖到主菜单下面的工具条或者文本编辑器窗口的工具条中,就可以往相应的工具条中增加一个命令。还可以把工具条中的一个图标拉到工具条的最后。从 CodeWarrior 的窗口菜单中的工具条子菜单中选择重设窗口的工具条(Reset Window Toolbar) 或重设浮动工具条菜单项,还可以重新设置该工具条。    

        在大多数情况下,CodeWarrior 的默认设置就可以满足你的要求了。偶尔你也可能发现一个通过改变一个菜单或工具条的方式来自动完成一些任务。关于 Codewarrior 的定制,你可以参考它的在线文档获得更多的信息。

    CodeWarrior 定制示例

        通过定制 CodeWarrior,你可以增强你的工作环境和提高工作效率。     除了对菜单和工具条的简单修改之外,你可能还想只需点击一个图标或键入一个组合键就可以执行一个脚本或启动一个应用程序。有时当你正在参与一个非常庞大的开发项目的时候,需要做一些定制工作来解决自动设置带来的混乱问题。例如,你可以创建一个脚本来完成以下这些工作:

    • 自动删除编译过程中产生的多余的文件;
    • 将输出文件复制到一个本地或网络上的目录中以做备份;
    • 为你的工程创建一个安装应用程序。

        对于 Windows 用户而言,可以通过 VBScriptPerl或其它使用通用对象模型(Common Object ModelCOM)接口的脚本语言来驱动 CodeWarrior Pro 5 IDE 完成一系列复杂的操作。如果使用 VBscript,你还需要一个可以执行 VBScript的的应用程序(如 Internet Explorer 5),或者是一个 Scripting Host 工具——可以从以下网址下载http://msdn.microsoft.com/scripting/windowshost/。  

       下面的 VBScript 脚本用于指导 CodeWarrior IDE 删除当前工程中的所有目标(object)文件,以便执行一个干净的构建(build),然后再执行构建工作(编译和连接操作)。该脚本还打开一个编辑器窗口来显示操作结果(成功或失败的)的总结。这个脚本是有点长,但设计得很好,因为其中包含了许多用于错误检查的代码。

          ' 文件名:Build.vbs

          ' 作者: Jim Trudeau, Metrowerks

          ' 以及版权声明信息

            option explicit                    

             '所有的变量都必须进行声明

          dim CW 
               dim project                      '
    默认的工程
          dim textDocument           '用于保存报告的文本文档 
          dim textEngine                 '用于处理文本的对象
          dim eol                            '行尾字符格式
          dim result                        '返回的值

    eol = chr(13)                     '设置行尾字符

    '创建 CodeWarrior 的一个实例
    set CW = CreateObject("CodeWarrior.CodeWarriorApp")

    '创建一个文本文档并获得其引擎(Engine)
    set textDocument = CW.OpenUntitledTextDocument() 
    set textEngine = textDocument.TextEngine

    '得到默认的工程
    set project = CW.DefaultProject

    '错误控制
    if TypeName(project) = "Nothing" then 
    textEngine.InsertText("Script operates on default project." &eol)     textEngine.InsertText("There must be at least one open project." &eol) 
    else 
    dim target   '
    当前目标
    dim buildmessages '错误和警告

    '*** 获得当前目标
    set target = project.GetCurrentTarget

    textEngine.InsertText("Build Information" &eol)

    '显示名字
    result = target.name

    textEngine.InsertText("Building target " &result &eol)

    '*** 删除所有的对象代码目标
    RemoveObjectCode true

    '*** 获得构建代码后的消息
    set buildMessages = target.BuildAndWaitToComplete

    ProcessMessages (buildMessages) 
    end if

    '========================================================= ' ProcessMessages - get errors and warnings, process them ' receives build messages '=========================================================

    sub ProcessMessages (messages)

    dim result           '返回值
    dim messageList '消息收集

    '*** 获得错误的数量
    result = messages.ErrorCount

    if result = 0 then textEngine.InsertText(eol &"Build Succeeded." &eol) 
    else textEngine.InsertText(eol &"!!!BUILD FAILED!!!" &eol)

    '*** 显示错误的数量
    textEngine.InsertText("Number of errors: " &result &eol)

    '*** 获得错误清单
    set messageList = messages.Errors

    '*** 处理错误
    ProcessMessageList (messageList) 
    end if

    '*** 检测是否有警告信息
    result = messages.WarningCount

    '*** 显示数量
    textEngine.InsertText("Number of warnings: " &result &eol)

    '*** 取得警告信息并处理之
    if result then 
    '*** 
    获得警告信息清单
    set messageList = messages.Warnings

    '*** 处理警告信息
    ProcessMessageList (messageList) 
    end if

    end sub

    '========================================================= ' ProcessMessagelist - loop through messages, report info ' receives message collection, could be errors or warnings '=========================================================

    sub ProcessMessageList (messageList)

    dim result '返回值
    dim index '循环计数器
    dim message '个人信息

    '*** 遍历消息清单
    for index = 0 to messageList.Count-1 
    '*** 
    获得个人信息
    set message = messageList.Item(index)

    '*** 获得消息文本
    result = message.MessageText

    '*** 显示消息文本
    textEngine.InsertText(result &eol)

    ****在错误中忽略一行(skip a line between errors )
    textEngine.InsertText(eol) 
    next

    end sub

    因为使用了微软公司的 OLE/COM 查看器工具,这个 IDE 支持许多 COM 对象。一个脚本语言可以使用这些对象来与 CodeWarrior IDE 进行通信。关于 CodeWarrior 定制与脚本机制,还有很多优秀的功能,但在这里就不详述了,因为CodeWarrior 把这些功能实现得非常好。打开上面所描述到得窗口自己看看吧!当你熟练使用了 CodeWarrior 的一些功能之后,你会发现它是非常的易用!

    第七课  库 和 Microsoft Foundation Classes (MFC) 

        库是什么?这是一个你必须知道如何回答的问题!一个库就是一个包含了你程序运行时需要调用的函数的文件。在库中提供的典型函数有:文件和屏幕的 I/O 函数,内存管理服务,3-D 图形显示以及科学计算函数。使用库可以缩短你的开发时间并简化你的应用程序。在开发程序当中,你会经常地使用到库。以下是几种在编程中使用库的方法:

    • 将你需要用到的函数写在一个源文件中,然后把它编译成为一个库或 DLL
    • 使用别人提供的库;
    • 使用一个其他人提供的共享库( DLL)

    编写你自己的库或 DLL

        第一种使用库的方法很简单。你只需在一个 或 C++ 源文件中写入源代码,然后编译、连接并执行它。这是我们在本课程中早已学到的知识。就这样你就可以创建你自己的一个库,就这么简单!

    使用别人的库

        使用别人提供的库稍稍有些不同。由第三方开发商提供的库通常包括以下几个文件:库文件(通常是以 .lib 为后缀名的文件),一个头文件(后缀名为 .h) 以及(我们希望有但不一定有)一些文档(后缀名为.doc)。头文件是用于让编译器将你的程序中使用的函数与库中的函数匹配起来的。你只需将头文件包括到你的源文件中,然后就可以象调用操作系统的函数一样来调用库函数了。在大多数情况下,通常将 .h 和 .lib 的文件放置于源文件目录中,就像 CodeWarrior 定义的一样。这个路径通常在你的工程文件所在的目录,或者其下的子目录中。需要注意的是,CodeWarrior 提供的某些特定的库,例如它的 MSL,就不是放在你的工程所在的目录中。实际上,CodeWarrior 有一个特定的存取路径,叫做 system paths, 它指向 CodeWarrior 自带的库和头文件存放的目录。

    使用一个共享库

        第三方厂商将库函数与它们的产品打包起来发行,而不是以源文件的方式发行这些库。如果你是一个开发商,使用这样的方式来发行你的库会更安全一些,特别是你希望由此来保护你的知识产权时。但是这种方法也存在一个问题,就是如果在库中存在一个 bug 的话,用户是没办法对之进行修改的,只能从开发商那里取得一个修正后的版本。但是这做起来也有许多困难。

    共享的 DLL

         DLL 和库文件非常相似,两者之间唯独的不同就是库文件通常都会被编译并连接到你的应用程序中,而 DLL 则是存放在你的系统目录下或者是在编译后的应用程序的目录下的一个独立的文件。当应用程序运行时,它会自己去找到该 DLL并使用它的服务。DLL 文件的后缀名为 .dll

    使用共享的DLLs 的好处

        DLL 的优点就是它能够被共享。那么,共享的好处在哪里呢?首先,可以让多个应用程序使用一个 DLL。当几个程序都使用一个占用很大磁盘和内存空间的 DLL 时,就很有用了。另外,如果一个 DLL 包含了一个 bug,然后被修正了,那么所有使用这个 DLL 的程序都会被自动更新,这样就可以大大地节约你的开发时间。那么哪种库应该由多个应用程序共享呢?比如操作系统中的每一个视窗就是使用共享的 DLL 得来的。

    使用共享 DLL 需要注意的地方

        CodeWarrior IDE 的 CD-ROM 中有许多库,在因特网上还有成千上万的库。记住,当你使用第三方厂商的库时,千万要先查查毒。这就如同出门前要关门一样的有必要。

        CodeWarrior 自带的库分为两类。一个是 MSL,它提供 ANSI C/C++ 的标准函数。MSL 已经被移植到许多平台上了,因此可以在 WindowsMac OS 和 Solaris 系统中使用它的函数。另一个是供特定平台使用的库。例如,在Windows 版的光盘中,你可以发现提供了使用 Windows APIs 和 MFC 库的Windows 32 支持库,它提供了编写面向对象的 Windows 应用程序的框架。随着你的编程经验的增长,你将发现这些特定库的更多的用途。

    MFC是什么?

        Microsoft Foundation Classes (MFC) 提供了快速开发 Windows 应用程序的函数集。虽然你已经可以自由地直接调用Win32 APIs,但使用 MFC 会更简单一些,因为 MFC 提供了诸如创建一个窗口或者使用智能默认设置和错误检查代码来编写文件这样的基本服务。MFC 可以在 Windows 版的 CodeWarrior 光盘中找到,但不一定是最新的版本。你可以查看Metrowerks 的帮助页面来获得相关信息,因为微软公司会为 CodeWarrior 的发行版本提供一些升级补丁。

        对于特定平台,比如Macintosh 的开发者可以使用 Metrowerks 自己的 PowerPlant 类库。PowerPlant 是为编写 Mac应用程序而设计的面向对象的应用程序框架。我们在本课程中并不打算讨论 PowerPlant,但你应知道,它与 MFC 在许多方面是类似的。你在这里学到的知识也将帮助你理解 PowerPlant。我们讨论 MFC 时,其中的许多概念也可以应用到PowerPlant 上。

    注意: 在 Macintosh 版的 CodeWarrior 光盘中也有一个 MFC 的库文件,但不像 Windows 版的光盘,这里不包含源代码。使用 MFC,你可以编写跨平台的代码,还可以同时为 Macintosh(使用PowerPlant) 和 PC(使用 MFC) 编译这个代码。是不是很酷?

    应用程序框架

        就像前面提到过的一样,MFC 是一个应用程序框架。也就是说,MFC 是一个源代码或库格式的 C++ 类集。使用这个框架可以在较短时间内创建一个支持高级操作系统功能的完整的应用程序。此外,MFC 还包含了用以构造你的用户界面的工具。这些工具,特别是图象编辑器、对话框编辑器和资源编译器,能够帮助你构造用户界面中的图形元素(在本课最后一节中将解释如何安装这些有用的组件)。一个应用程序框架只涉及到应用程序的标准用户界面,而与该应用程序的特有的内容无关。换句话说,这个框架能够帮助你构造一个功能强大的用户界面,把你的时间节省下来以便让你有更多时间去编写实现应用程序目的的其它函数。因此你不必担心会花很多时间来创建一个和其它一些运行在操作系统环境中具有同样Windows 化界面的应用程序,因为你只需去运用那些库就可以达到这些目的了。

     7-1 MFC 的用户界面构造器

        应用程序框架还能处理分发消息(例如用户击键的动作、鼠标点击、绘画消息等等)到应用程序中的许多按钮、列表、窗口和控件上。这个功能帮助你能够集中注意力到实现应用程序的功能而不是界面上,把那些创建界面的工作留给应用程序框架吧。

        应用程序框架提供了许多超过老式风格的 roll-your-own 方法。首先,这些代码是成熟和经过严格测试的。每天都有成百上千的开发者使用 MFC 来开发应用程序。每当 Windows 操作系统增加了一些新的功能的时候,MFC 也会不断地被升级,你可以通过使用新版本的 MFC 来编译你的应用程序,以便获得所有新的功能特色,而且几乎不用对应用程序的源代码做任何修改。

        应用程序框架还提供可重用的代码。代码重用是象 C++ 这样的面向对象的编程方法得以发展的主要原因之一。通过将可重用的代码封装为类,你可以很容易地使用已经存在的类来添加需要的功能。代码重用使得你可以使用经过严格测试的代码来构造你的应用程序,并很容易获得预期的功能。而且你还可以很容易地将你编写的类与其它需要同样功能的人进行共享。

        与你自己创建所需框架而言,使用应用程序框架有时会使得你的程序稍大一些。因为现代计算机通常都配置了足够数量的内存,而且这个框架可以作为一个 DLL 来使用,所以增加这么一些程序体积还是很值得的。PowerPlant  MFC 是两个很值得你去熟练使用的高效的工具。   

    如何安装和使用 MFC 接口工具

        MFC 接口工具(包括对话编辑器和图象编辑器)可以在 CodeWarrior 安装光盘上找到。但是,它们并不是作为CodeWarrior 安装程序的一部分存在的,因此在完成了 CodeWarrior 的安装后,在硬盘上你还是找不到 MFC。为了安装这些工具,请按照以下步骤进行:

    • 找到 iTOOLS.Exe 文件。这是安装 MFC 接口工具必须的安装程序。iTools.Exe 文件可以在 C:/Program Files/Metrowerks/CodeWarrior/BinSDK/bin/ 目录下找到。如果在那儿找不到这个文件,那么就到 CodeWarrior 的安装光盘上去找。该文件应该在 Extras/SKDs/Win32/Microsoft Win32 SDK Tools/目录下。

    注意: 该安装文件 (iTOOLS.Exe) 将会询问你想在哪里保存 MFC 接口工具。但不行的是,它却没有提供定位目录用的浏览(browser)按钮可用,因此你必须手工的输入一个路径。如果你并不能确定你输入的路径是否就是你想要保存这些工具的路径,你可以先在那个目录中找到一个文件,然后通过查看该文件的属性(在该文件上点击右键然后选择 Properties ),从这个属性框上你可以复制到完整的路径,并把它填入到安装过程中提示要输入安装路径的编辑框中。

    • 这些工具将要被安装到目的安装路径的 /bin/ 目录中。在这个路径中你将可以找到对话框编辑器(DlgEdit.Exe),图象编辑器(ImagEdit.Exe)和其它一些有用的工具;
    • 如果你正在开发 Windows 95  Windows NT,需要的工具可以在 /bin/win95  /bin/winnt 目录中找到。
    展开全文
  • Visual Studio Code 如何编写运行 C、C++ 程序?

    万次阅读 多人点赞 2019-04-17 11:35:12
    作者:知乎用户 ...来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。...阅读本文能在一定程度上了解程序编译的知识,以及使用VS Code的知识。 本文大部分内容都可从VS Co...
  • http code 400

    千次阅读 2012-06-20 16:00:12
    最近服务器异常,nginx访问日志中出现大量的如下形式的请求: x23.1x3.21x.xx - - [08/Jun/2012:04:46:39 +0800] "-" 400 0 "-" "-" x23.1x3.21x.xx - - [08/Jun/2012:04:46:39 +0800] "-" 408 0 "-" "-" 每天这种...
  • CodeWarrior 使用教程

    万次阅读 2009-09-25 16:35:00
    CodeWarrior 使用教程第一课认识 CodeWarrior Translated from CodeWarriorU.COMCodeWarrior 能做些什么? 当你知道自己能写更好的程序时,你一定不会再使用别人开发的应用程序。但是常常会发生这种情况,就是当你写...
  • Visual Studio Code 配置 gcc

    千次阅读 2018-08-20 22:58:00
    作者:谭九鼎链接:https://www.zhihu.com/question/30315894/answer/154979413来源:知乎著作权归作者所有。商业转载请联系作者获得授权,...毕竟百度“VS Code C”出来的第一条就是这个网页</del>现在不是了...
  • Windows Error Code

    万次阅读 2011-07-27 01:25:28
    1064 当处理控制请求时, 在服务中发生异常. 1065 指定的数据库不存在. 1066 服务已返回特定的服务错误码. 1067 进程意外终止. 1068 依存服务或组无法启动. 1069 由于登录失败而无法启动服务. 1070 启动后, 服务停留...
  • JVM是如何处理异常的?

    千次阅读 2018-09-11 18:21:00
    JVM是如何处理异常的? 版本:2018/9/11-1(19:00) JVM是如何处理异常的? 基础(13) 异常的创建(6) 异常的捕获(8) Supressed异常(5) try-with-resources 知识扩展 栈帧 局部变量表 栈轨迹 问题汇总 参考...
  • Java异常面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:15:59
    受检异常与非受检异常受检异常非受检异常Java异常关键字Java异常处理声明异常抛出异常捕获异常如何选择异常类型常见异常处理方式直接抛出异常封装异常再抛出捕获异常自定义异常try-catch-finallytry-with-r...
  • HTTP状态码(StatusCode

    万次阅读 2016-09-13 10:17:58
    HTTP状态码(StatusCode
  • Entity Framework 5.0 Code First全面学习

    万次阅读 热门讨论 2013-11-01 17:20:11
    Code First 约定 借助 CodeFirst,可通过使用 C# 或Visual Basic .NET 类来描述模型。模型的基本形状可通过约定来检测。约定是规则集,用于在使用 Code First 时基于类定义自动配置概念模型。约定是在 System.Data....
  • Android漏洞扫描工具Code Arbiter

    千次阅读 2017-08-17 19:04:00
    目前Android应用代码漏洞扫描工具种类繁多,效果良莠不齐,这些工具有一个共同的特点,都是在应用打包完成后对应用进行解包扫描。...Code Arbiter正是为解决上述两个问题而开发的,专门对Android Stu...
  • Javascript codemirror 高级应用

    千次阅读 2018-08-13 10:55:31
    语句,并且捕获异常,将捕获的异常信息做为 bootstrap-treeview 插件的数据来显示具有层次结果的错误信息。(这里我可能描述的不够准备,捕获的是Error. 总结几个特点: 1. 使用最新的的 CodeMirrror ,包含了...
  • Python Windows error code

    万次阅读 2013-11-14 15:58:31
    1064 当处理控制请求时, 在服务中发生异常. 1065 指定的数据库不存在. 1066 服务已返回特定的服务错误码. 1067 进程意外终止. 1068 依存服务或组无法启动. 1069 由于登录失败而无法启动服务. 1070...
  • 《CleanCode-代码整洁之道》读书笔记

    万次阅读 多人点赞 2018-08-03 00:31:42
    1.可控异常,就是在方法签名中标注异常。但有时候会产生多层波及,有时候你对较底层的代码修改,可能会波及很多上层代码 给出异常发生的环境说明 1.抛出的每个异常,都应当提供足够的环境说明,以便判断错误...
  • 此处所说的参数验证的是实体类型的参数验证,通过在实体的属性上添加特性的方式来实现。 简单实现 创建名为 ValidationDemoController 的 API 类,代码如下: using System; using System.Collections.Generic; ...
  • 编程思想 之「异常及错误处理」

    千次阅读 2018-03-10 12:27:15
    在 Java 的异常及错误处理机制中,用Throwable这个类来表示可以作为异常被抛出的类。Throwable对象可以细分为两种类型(从Throwable继承而得到的类型),分别为: Error ,表示编译时和系统错误; Exception,表...
  • Java异常

    千次阅读 2014-06-01 05:07:39
    异常处理机制是Java许多特性的zhn
  • windows 异常处理

    千次阅读 2016-08-16 23:29:08
    为了程序的健壮性,windows 中提供了异常处理机制,称为结构化异常异常一般分为硬件异常和软件异常,硬件异常一般是在执行机器指令时发生的异常,比如试图向一个拥有只读保护的页面写入内容,或者是硬件的除0...
  • 深入理解Java虚拟机-Java内存区域与内存溢出异常

    万次阅读 多人点赞 2020-01-03 21:42:24
    在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。「属于线程私有的内存区域」...
  • Oops中的error code解释

    千次阅读 2016-11-23 16:20:13
    当内核发生异常(比如非法地址访问)时,产生Oops,常见的打印如下: ... BUG:unable to handle kernel paging request at f80c6131 *pdpt=0000000000d08001 *pde=00000000372a0067 *pte=0000000000000000 0ops:...
  • 问题的表现特征一般都是抛出java.lang.NoSuchMethodError异常。那么,今天就聊聊怎么解决此类问题。分析此问题跟java的类加载机制有关。JVM装载类时使用“全盘负责委托机制”,此问题跟“委托机制”有关。它是先...
  • 0、前言 无论任何项目,都避免不了... 在项目中异常处理所抛出的异常码、异常提示,都需要进行一定的封装,以确保异常的统一,提高程序的可维护性。而不是随心所欲的来进行异常提醒,如:一些硬编码异常信息(thro...
  • Java中常见的RunTime异常异常详解

    万次阅读 2016-08-11 09:52:49
    IllegalArgumentException - 传递非法参数异常。 ArithmeticException - 算术运算异常 ArrayStoreException - 向数组中存放与声明类型不兼容对象异常 IndexOutOfBoundsException - 下标越界异常 ...
  • Java异常面试题

    万次阅读 多人点赞 2019-09-29 11:55:48
    2. 运行时异常和一般异常区别是什么?3.NoClassDefFoundError 和 ClassNotFoundException 区别?4. JVM 是如何处理异常的?5. throw 和 throws 的区别是什么?6. 常见的 RuntimeException 有哪些?7. Java常见异常有...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 87,001
精华内容 34,800
关键字:

code参数异常是指什么