1 Star 0 Fork 0

windynature / blog

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
content.json 228.98 KB
一键复制 编辑 原始数据 按行查看 历史
jinyoucheng 提交于 2020-03-18 13:55 . Site updated: 2020-03-18 13:55:15
{"meta":{"title":"奔跑的蜗牛","subtitle":"虚心若愚","description":null,"author":"windy","url":"http://windynature.oschina.io"},"pages":[{"title":"","date":"2017-04-04T05:17:16.000Z","updated":"2017-04-04T05:18:35.000Z","comments":false,"path":"categories/index.html","permalink":"http://windynature.oschina.io/categories/index.html","excerpt":"","text":""},{"title":"","date":"2017-04-04T05:17:06.000Z","updated":"2017-04-04T05:17:55.000Z","comments":false,"path":"tags/index.html","permalink":"http://windynature.oschina.io/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"openlayers 样式渲染","slug":"openlayers/openlayers样式渲染","date":"2020-03-18T05:54:38.403Z","updated":"2020-03-18T05:54:59.330Z","comments":true,"path":"2020/03/18/openlayers/openlayers样式渲染/","link":"","permalink":"http://windynature.oschina.io/2020/03/18/openlayers/openlayers样式渲染/","excerpt":"","text":"openlayers 样式渲染1. 样式渲染方式 ol.Feature.setStyle(style) 在每个Feature上设置响应的样式。 ol.layer.Vector.setStyle(style) 在图层设置样式 2. Feature.setStyle()要素设置样式在openlayers API中setStyle方法描述如下: Set the style for the feature. This can be a single style object, an array of styles, or a function that takes a resolution and returns an array of styles. If it is null the feature has no style (a null style). Name Type Description style ol.style.Style | Array.<[ol.style.Style]| ol.FeatureStyleFunction | ol.StyleFunction Style for this feature. 可以看到,setStyle()方法传入的参数有四种: 一个ol.style.Style对象 一个包含ol.style.Style对象的数组 ol.FeatureStyleFunction(openalyers 5以前,openlayer 5移除了ol.FeatureStyleFunction) ol.StyleFunction 在新的openlyers 5的api中移除了ol.FeatureStyleFunction,所以新的api中只包含了1,2,4三类参数。 Removal of ol.FeatureStyleFunctionThe signature of the vector style function passed to the feature has changed. The function now always takes the feature and the resolution as arguments, the feature is no longer bound to this. Old code: feature.setStyle(function(resolution) { var text = this.get('name'); ...}); New code: feature.setStyle(function(feature, resolution) { var text = feature.get('name'); ...}); 3. ol.layer.Vector.setStyle()矢量图层设置样式对于矢量图层设置样式。在图层中的要素没有设置要素的情况下才会生效。api中对该方法的描述如下: Set the style for features. This can be a single style object, an array of styles, or a function that takes a feature and resolution and returns an array of styles. If it is undefined the default style is used. If it is null the layer has no style (a null style), so only features that have their own styles will be rendered in the layer. See ol.style for information on the default style. 可以看到,传入的参数与要素的设置样式的参数差不多,如果样式没有定义(undefined),则会使用默认的样式,如果样式为空(null)则只有在要素上设置样式才会在图层中渲染。 4. 源码分析源码分析以openlayers 5为例: 4.1 在Feature.js中:/** * Set the style for the feature. This can be a single style object, an array * of styles, or a function that takes a resolution and returns an array of * styles. If it is `null` the feature has no style (a `null` style). **/setStyle(style) { this.style_ = style; this.styleFunction_ = !style ? undefined : createStyleFunction(style); this.changed(); } createStyleFunction() 作用是将给定的对象转为一个style function. /** * Convert the provided object into a feature style function. Functions passed * through unchanged. Arrays of module:ol/style/Style or single style objects wrapped * in a new feature style function. */export function createStyleFunction(obj) { if (typeof obj === 'function') { //如果是一个function则直接返回 return obj; } else { let styles; //style数组 if (Array.isArray(obj)) { styles = obj; } else { assert(obj instanceof Style, 41); styles = [obj]; } return function() { //返回一个function return styles; }; }} 4.2 在ol/layer/Vector.js中setStyle() setStyle(style) { this.style_ = style !== undefined ? style : createDefaultStyle; this.styleFunction_ = style === null ? undefined : toStyleFunction(this.style_); //toStyleFunction 调用了Style.js中的toFunction this.changed(); } ol/style/Style.js中的toFunction(),可以看见这个方法与Feature.js中的createStyleFunction内容完全一样,也是将给定 的对象转换为style function。 export function toFunction(obj) { let styleFunction; if (typeof obj === 'function') { styleFunction = obj; } else { let styles; if (Array.isArray(obj)) { styles = obj; } else { assert(obj instanceof Style,41); styles = [obj]; } styleFunction = function() { return styles; }; } return styleFunction;} 可见不管在矢量图层中还是在要素中,对于设置的style都会转换为一个style function。另外在渲染器ol/renderer/canvas/VectorLayer.js的prepareFrame()方法中我们可以看到如下代码: const render = function(feature) { let styles; const styleFunction = feature.getStyleFunction() || vectorLayer.getStyleFunction(); if (styleFunction) { styles = styleFunction(feature, resolution); } if (styles) { const dirty = this.renderFeature( feature, resolution, pixelRatio, styles, replayGroup); this.dirty_ = this.dirty_ || dirty; } }.bind(this); 从上面的代码可见,当矢量图层渲染要素的时候,先在要素上获取style function,如果要素没有则获取图层的style function对要素进行渲染。","categories":[{"name":"openlayers","slug":"openlayers","permalink":"http://windynature.oschina.io/categories/openlayers/"}],"tags":[{"name":"gis","slug":"gis","permalink":"http://windynature.oschina.io/tags/gis/"},{"name":"openlayers","slug":"openlayers","permalink":"http://windynature.oschina.io/tags/openlayers/"}]},{"title":"openlayers加载ArcGIS自定坐标切片服务","slug":"openlayers/openlayers加载ArcGIS自定义坐标系切片服务","date":"2020-03-18T05:37:37.975Z","updated":"2020-03-18T05:51:05.513Z","comments":true,"path":"2020/03/18/openlayers/openlayers加载ArcGIS自定义坐标系切片服务/","link":"","permalink":"http://windynature.oschina.io/2020/03/18/openlayers/openlayers加载ArcGIS自定义坐标系切片服务/","excerpt":"","text":"openlayers加载ArcGIS自定坐标切片服务一、概述在实际的WebGIS开发中我们通常遇到的是WGS84的web 墨卡托投影及其经纬度投影,对应的SRID为EPSG:3857及EPSG:4326。但是由于实际的使用需求,我们通常回遇到一些北京54、西安80以及一些地方坐标系发布的地图服务,如果对于坐标系的理解不够深入,通常大费周章,不得其解。 现在利用这边文章,整理一下在自定义坐标系或者非EPSG:3857 、EPSG:4326 坐标系ArcGIS切片服务的访问。对于坐标系的总结,详见坐标系总结一文。 二、使用到的库 openlayers :WebGIS javascript 类库 proj4js:一个免费的坐标转换工具库 三、主要步骤3.1 ArcGIS发布服务3.1.1 数据准备准备一份自定义坐标系数据,例如重庆区域的行政区域。 1555638149521 3.1.2 切片服务流程1. 选择切片方式 共享为服务 这里共享有两种方式: 地图包:改方式切片为本地包,可转移到web容器进行访问 服务:该方式通过arcgis server发布 2. 发布服务 发布服务 3.填写服务名称 服务名称 4.选择路径 选择路径 5. 填写服务参数 服务参数 6. 抗锯齿 抗锯齿 7.png设置 png 8.切片等级 切片等级 9.发布后的服务 3.2服务参数详解3.2.1 服务信息 服务信息 3.2.2 服务信息json格式参数:f=json 服务信息json格式 3.3.3 切片信息对于使用openlayers的方式访问ArcGIS Server发布的rest切片服务, 切片信息 3.3 openlayers XYZ方式访问ArcGIS的MapServer服务3.3.1 设置投影 /** * 添加投影 **/ function addProjection() { //定义坐标系 // 投影名称,投影参数 proj4.defs('projectionName', 'XXXXXX'); //将proj4注册到ol,使其具有坐标变化功能 ol.proj.setProj4(proj4); //定义转换 ol.proj.addCoordinateTransforms(\"EPSG:4326\", \"projectionName\", function (coordinate) { return proj4(\"EPSG:4326\", \"projectionName\", coordinate); }, function (coordinate) { return proj4(\"projectionName\", \"EPSG:4326\", coordinate);; } ); ol.proj.addCoordinateTransforms(\"EPSG:3857\", \"projectionName\", function (coordinate) { return proj4(\"EPSG:3857\", \"projectionName\", coordinate); }, function (coordinate) { return proj4(\"projectionName\", \"EPSG:3857\", coordinate);; } ); } 3.4 利用proj4扩展openlayers的投影","categories":[{"name":"openlayers","slug":"openlayers","permalink":"http://windynature.oschina.io/categories/openlayers/"}],"tags":[{"name":"gis","slug":"gis","permalink":"http://windynature.oschina.io/tags/gis/"},{"name":"openlayers","slug":"openlayers","permalink":"http://windynature.oschina.io/tags/openlayers/"}]},{"title":"openlayers 学习笔记","slug":"openlayers/openlayers学习笔记","date":"2020-03-18T05:32:47.053Z","updated":"2020-03-18T05:34:57.870Z","comments":true,"path":"2020/03/18/openlayers/openlayers学习笔记/","link":"","permalink":"http://windynature.oschina.io/2020/03/18/openlayers/openlayers学习笔记/","excerpt":"","text":"OpenLayers 学习笔记1.定制logonew ol.Map({ controls: ol.control.defaults({ attributionOptions: ({ collapsible: false }) }), // logo: false, // 不显示logo // logo: 'face_monkey.png', // 用一个图片 face_monkey.png 作为logo logo: { src: '../img/face_monkey.png', href: 'http://www.openstreetmap.org/' },// 点击能跳转到对应页面 layers: [ new ol.layer.Tile({source: new ol.source.OSM()}) ], view: new ol.View({ center: [0, 0], zoom: 2 }), target: 'map'}); 2.地图联动两个ol.Map使用同一个ol.view 3.交换地图实质是交换连个地图的容器。 function swapMap() { // 改变两个地图的容器 map1.setTarget('map2'); map2.setTarget('map1');} 4. View1. 地图的导航移动:主要是对地图中心的设置缩放:主要是对ol.View的zoom进行设置 2. 坐标转换view: new ol.View({ // 设置成都为地图中心,此处进行坐标转换, 把EPSG:4326的坐标, //转换为EPSG:3857坐标,因为ol默认使用的是EPSG:3857坐标 center: ol.proj.transform([104.06, 30.67], 'EPSG:4326', 'EPSG:3857'), zoom: 10 }), 3. Openlayers 使用的坐标系目前OpenLayers 3支持两种投影,一个是EPSG:4326,等同于WGS84坐标系,参见详情。另一个是EPSG:3857,等同于900913,由Mercator投影而来,经常用于web地图,参见详情。一个是全球通用的,一个是web地图专用的,自然OpenLayers 3支持它们。在使用过程中,需要注意OpenLayers 3默认使用的是EPSG:3857。这也是为什么前面的代码里需要进行坐标转换的原因。 4. 设置投影在ol.View中设置 new ol.View({ // 设置成都为地图中心 center: [104.06, 30.67], // 指定投影使用EPSG:4326 projection: 'EPSG:4326', zoom: 10 }) 5. view的使用设置边界extent: [102, 29, 104, 31],这行代码就实现了功能。extent参数类型为[minX, minY, maxX, maxY]的ol.Extent,很容易记住。 限制缩放级别 view: new ol.View({ // 设置成都为地图中心 center: [104.06, 30.67], projection: 'EPSG:4326', zoom: 10, // 限制地图缩放最大级别为14,最小级别为10 minZoom: 10, maxZoom: 14 }) 6. 自适配区域function fitToChengdu() { // 让地图最大化完全地显示区域[104, 30.6, 104.12, 30.74] map.getView().fit([104, 30.6, 104.12, 30.74], map.getSize()); } 5. Source和Layer1. Source首先需要明白的一点是,Source和Layer是一对一的关系,有一个Source,必然需要一个Layer,然后把这个Layer添加到Map上,就可以显示出来了。通过官网的API搜索ol.source可以发现有很多不同的Source,但归纳起来共三种:ol.source.Tile,ol.source.Image和ol.source.Vector。 ol.source.Tile对应的是瓦片数据源,现在网页地图服务中,绝大多数都是使用的瓦片地图,而OpenLayers 3作为一个WebGIS引擎,理所当然应该支持瓦片。 ol.source.Image对应的是一整张图,而不像瓦片那样很多张图,从而无需切片,也可以加载一些地图,适用于一些小场景地图。 ol.source.Vector对应的是矢量地图源,点,线,面等等常用的地图元素(Feature),就囊括到这里面了。这样看来,只要这两种Source就可以搞定80%的需求了。 从复杂度来分析,ol.source.Image和ol.source.Vector都不复杂,其数据格式和来源方式都简单。而ol.source.Tile则不一样,由于一些历史问题,多个服务提供商,多种标准等诸多原因,导致要支持世界上大多数的瓦片数据源,就需要针对这些差异提供不同的Tile数据源支持。在更进一步了解之前,我们先来看一下OpenLayers 3现在支持的Source具体有哪些: 我们先了解最为复杂的ol.source.Tile,其叶子节点类有很多,大致可以分为几类: 在线服务的Source,包括ol.source.BingMaps(使用的是微软提供的Bing在线地图数据),ol.source.MapQuest(使用的是MapQuest提供的在线地图数据)(注: 由于MapQuest开始收费,ol v3.17.0就移除了ol.source.MapQuest),ol.source.OSM(使用的是Open Street Map提供的在线地图数据),ol.source.Stamen(使用的是Stamen提供的在线地图数据)。没有自己的地图服务器的情况下,可直接使用它们,加载地图底图。 支持协议标准的Source,包括ol.source.TileArcGISRest,ol.source.TileWMS,ol.source.WMTS,ol.source.UTFGrid,ol.source.TileJSON。如果要使用它们,首先你得先学习对应的协议,之后必须找到支持这些协议的服务器来提供数据源,这些服务器可以是地图服务提供商提供的,也可以是自己搭建的服务器,关键是得支持这些协议。 ol.source.XYZ,这个需要单独提一下,因为是可以直接使用的,而且现在很多地图服务(在线的,或者自己搭建的服务器)都支持xyz方式的请求。国内在线的地图服务,高德,天地图等,都可以通过这种方式加载,本地离线瓦片地图也可以,用途广泛,且简单易学,需要掌握。 ol.source.Image虽然有几种不同的子类,但大多比较简单,因为不牵涉到过多的协议和服务器提供商。而ol.source.Vector就更加的简单了,但有时候其唯一的子类ol.source.Cluster在处理大量的Feature时,我们可能需要使用。 2. Layer在大概了解了整个Source之后,紧接着该介绍它的搭档Layer了,同样的,我们还是先从OpenLayers 3现有的Layer类图大致了解一下: 6. Openlayers 离线文档慢的原因由于离线文档和示例中的html使用到了一个样式文件,服务器在国外,无法访问,注释即可。 @import url(https://fonts.googleapis.com/css?family=Quattrocento+Sans:400,400italic,700); 7. 常用知识点Scale:表示的是比例尺,即地图上的一厘米代表着实际上的多少厘米。例如地图上1厘米代表实地距离500千米,可写成:1 ∶ 50,000,000或写成:1/50,000,000。 Resolution:表示的是分辨率。Resolution 的实际含义代表当前地图范围内,1像素代表多少地图单位(X地图单位/像素),地图单位取决于数据本身的空间参考。可见Resolution跟 dpi有关系(dpi代表每英寸的像素数,dots per inch),跟地图的单位也有关系。 英寸转厘米的国际参数是:2.5399998 AGS的参数是:2.54000508001016 当系统是经纬度时:resolution可以直接使用切图文档中的resolution.当系统是平面系统时:resolution = scale * inch2cetimeter / dpi (scale是地图比例尺,inch2centimeter为英寸转厘米的参数,dpi为1英寸所包含的像素。) EPSG:(The European Petroleum Survey Group, http://www.epsg.org/ )维护着空间参照对象的数据集,OGC标准中空间参照系统的SRID(Spatial Reference System Identifier)与EPSG的空间参照系统ID相一致。 8. 瓦片地图行列号换算原理影像图切成离散型图后文件的组织形式其实是按照瓦片的级别、行、列号来组织的。并且,标准的WMS请求中也涉及到行列号的换算,WMS请求中有一个Bbox的参数,而这个参数也与行列号的换算有关系。而标准的WMTS请求中,TILEMATRIX、TILEROW、TILECOL这三个参数代表的就是瓦片的级别、行、列号。 由此可见,不管是针对哪种离线或在线的地图的瓦片请求中,得到瓦片的level、col、row是请求能够实现的核心。","categories":[{"name":"openlayers","slug":"openlayers","permalink":"http://windynature.oschina.io/categories/openlayers/"}],"tags":[{"name":"gis","slug":"gis","permalink":"http://windynature.oschina.io/tags/gis/"},{"name":"openlayers","slug":"openlayers","permalink":"http://windynature.oschina.io/tags/openlayers/"}]},{"title":"切片请求工具类","slug":"gis基本原理/瓦片计算工具类","date":"2020-03-18T05:13:00.856Z","updated":"2020-03-18T05:13:57.992Z","comments":true,"path":"2020/03/18/gis基本原理/瓦片计算工具类/","link":"","permalink":"http://windynature.oschina.io/2020/03/18/gis基本原理/瓦片计算工具类/","excerpt":"","text":"javascript module.exports = { /** * 根据经纬度和缩放等级,求得瓦片路径 * @param {*} lat 纬度 * @param {*} lon 精度 * @param {*} zoom 缩放级别 */ getTileNumber(lat, lon, zoom) { var xtile = parseInt(Math.floor((lon + 180) / 360 * (1 << zoom))); var ytile = parseInt(Math.floor((1 - Math.log(Math.tan(this.toRadians(lat)) + 1 / Math.cos(this.toRadians(lat))) / Math.PI) / 2 * (1 << zoom))); if (xtile < 0) xtile = 0; if (xtile >= (1 << zoom)) xtile = ((1 << zoom) - 1); if (ytile < 0) ytile = 0; if (ytile >= (1 << zoom)) ytile = ((1 << zoom) - 1); return (zoom + \"/\" + xtile + \"/\" + ytile); }, /** * 根据瓦片获取范围 * @param {*} x * @param {*} y * @param {*} zoom */ tile2boundingBox(x, y, zoom) { x = parseInt(x); y = parseInt(y); zoom = parseInt(zoom); let bb = {}; bb.ymax = this.tile2lat(y, zoom); bb.ymin = this.tile2lat(y + 1, zoom); bb.xmin = this.tile2lon(x, zoom); bb.xmax = this.tile2lon(x + 1, zoom); return bb; }, /** * 瓦片转换精度 * @param {*} x * @param {*} z */ tile2lon(x, z) { return x / Math.pow(2.0, z) * 360.0 - 180; }, /** * 瓦片转换纬度 * @param {*} y * @param {*} z */ tile2lat(y, z) { let n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, z); return this.toDegrees(Math.atan(Math.sinh(n))); }, toDegrees(angrad) { return angrad * 180.0 / Math.PI; }, /** * 转弧度 * @param {*} angdeg */ toRadians(angdeg) { return angde / 180 * Math.PI; }} java public class TileUtil { /** * 根据经纬度和缩放等级,求得瓦片路径 * @param lat 纬度 * @param lon 经度 * @param zoom 缩放级别 * @return */ public static String getTileNumber(final double lat, final double lon, final int zoom) { int xtile = (int)Math.floor( (lon + 180) / 360 * (1<<zoom) ) ; int ytile = (int)Math.floor( (1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI) / 2 * (1<<zoom) ) ; if (xtile < 0) xtile=0; if (xtile >= (1<<zoom)) xtile=((1<<zoom)-1); if (ytile < 0) ytile=0; if (ytile >= (1<<zoom)) ytile=((1<<zoom)-1); return(\"\" + zoom + \"/\" + xtile + \"/\" + ytile); } /** * 根据瓦片获得范围 * @param x 行 * @param y 列 * @param zoom 缩放级别(z) * @return */ public static TileBoxEntity tile2boundingBox(final int x, final int y, final int zoom) { TileBoxEntity bb=new TileBoxEntity(); bb.setYmax(tile2lat(y, zoom)); bb.setYmin(tile2lat(y + 1, zoom)); bb.setXmin( tile2lon(x, zoom)); bb.setXmax(tile2lon(x + 1, zoom)); return bb; } /** * 瓦片转换经度 * @param x 行 * @param z 列 * @return 经纬度 */ public static double tile2lon(int x, int z) { return x / Math.pow(2.0, z) * 360.0 - 180; } /** *瓦片转换纬度 * @param y * @param z * @return */ public static double tile2lat(int y, int z) { double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, z); return Math.toDegrees(Math.atan(Math.sinh(n))); }}public class TileBoxEntity { private double ymax; private double ymin; private double xmax; private double xmin; public double getYmax() { return ymax; } public void setYmax(double ymax) { this.ymax = ymax; } public double getYmin() { return ymin; } public void setYmin(double ymin) { this.ymin = ymin; } public double getXmax() { return xmax; } public void setXmax(double xmax) { this.xmax = xmax; } public double getXmin() { return xmin; } public void setXmin(double xmin) { this.xmin = xmin; } @Override public String toString() { return \"[\" + xmin + \",\" + ymin + \",\" + xmax + \",\" + ymax + \"]\"; }}","categories":[{"name":"GIS基本原理","slug":"GIS基本原理","permalink":"http://windynature.oschina.io/categories/GIS基本原理/"}],"tags":[{"name":"gis","slug":"gis","permalink":"http://windynature.oschina.io/tags/gis/"},{"name":"切片","slug":"切片","permalink":"http://windynature.oschina.io/tags/切片/"}]},{"title":"mapbox源码分析(一)","slug":"mapbox/mapbox源码分析","date":"2020-03-18T01:02:47.817Z","updated":"2020-03-18T01:06:47.494Z","comments":true,"path":"2020/03/18/mapbox/mapbox源码分析/","link":"","permalink":"http://windynature.oschina.io/2020/03/18/mapbox/mapbox源码分析/","excerpt":"","text":"Mapbox原码分析一、基本架构 基本架构 1. 接口层Marker在Mapbox GL JS中Marker类似与openlayers中的overlay,以DOM元素的方式呈现信息。默认的是一个浅蓝色的svg小图标。可以在marker的DOM元素中使用svg、其他dom元素、以及canvas呈现其他功能,方式较为灵活,例如做动画效果;其缺点是由于是dom结构的所以在数据量大的情况下渲染效率低。 示例 var marker = new mapboxgl.Marker() .setLngLat([104,30]) .addTo(map); MapMapbox GL JS的承载对象,表示页面上的地图,公开了方法和属性,使得我们可以通过编码的方式来改变地图,并在用户与其交互的时候触发事件。p 示例 var map = new mapboxgl.Map({ container: 'map', center: [-122.420679, 37.772537], zoom: 13, style: style_object, hash: true, transformRequest: (url, resourceType)=> { if(resourceType === 'Source' && url.startsWith('http://myHost')) { return { url: url.replace('http', 'https'), headers: { 'my-custom-header': true}, credentials: 'include' // Include cookies for cross-origin requests } } }}); Control控件类,与地图交互的控件,默认的有: NavigationControl:导航控件 GeolocateControl:地理定位控件 AttributionControl:版权信息控件 ScaleControl:缩放控件 FullscreenControl:全屏控件 2. 逻辑层CameraMap的父类,主要是控制地图视角的展示。 Handler与地图交互的处理函数,有鼠标交互,触摸交互和其他手势交互方式。 BoxZoomHandler ScrollZoomHandler DragPanHandler DragRotateHandler KeyboardHandler DoubleClickZoomHandler TouchZoomRotateHandler 3. 渲染模块3.1 数据模块3.1.1 StyleMapbbox GL JS中对样式的管理,source的加载及各个图层的加载。 3.1.2 StyleLayer图层的样式类,其子类主要有 StyleLayer子类 3.1.3 SourceCacheSourceCache的主要职责有: 创建Source的一个实例 转发Source的事件 缓存从Source中加载的tile 加载指定视图窗口需要渲染的tile 将指定窗口不需要渲染的tile进行卸载 3.1.4 Transform单个变换,通常用于单个瓦片的缩放、旋转和缩放。 3.1.5 Source数据源Source接口必须由每个源类型实现,包括“核心”类型(“vector”、”raster”、”video”等)和所有定制的第三方类型。 Source接口实现类: 3.1.6 WrokerSource主要负责数据的加载和解析。 WrokerSource实现类 3.1.7 Tiletile对象是坐标的组合,坐标定义了它的位置,以及它的内容的惟一ID和数据跟踪。 3.1.8 workerwebworker线程,这里加载tile,放入对应图层的source中。 3.1.9 bucketBucket接口是将矢量切片转换为WebGL缓冲区的单点知识(single point of knowledge)。 Bucket接口是一个抽象接口。在每个样式图层类型中都有实现,创建一个bucket可以通过StyleLayer#createBucket方法。 具体的bucket使用样式图层中的布局选项(layout options),将要素几何转换为顶点和索引数据,以供顶点着色器使用。它们(通过ProgramConfiguration)使用要素属性和缩放级别来填充数据驱动样式所需的属性。 Bucket设计为构建在worker线程上,然后序列化并传输回主线程进行渲染。在worker线程,Bucket的顶点、索引和属性数据存储在bucket.arrays:ArrayGroup中。当一个bucket的数据被序列化并发送回主线程时,将被反序列化(使用new Bucket(serializedBucketData,数组数据现在存储在bucket.buffers:BufferGroup中,BufferGroups保存与ArrayGroups相同的数据,但是被WebGL调整为消费。 3.1.10 dispatcher分发器Dispatcher的职责是从Source发送消息给相关的WorkerSource。 3.2 Painter对webgl的封装,包含了各类draw的功能以及ImageManager(图片管理)、GlyphManager(字体管理)、Texture(纹理)等。 例如draw功能的封装。 const draw = { symbol, circle, heatmap, line, fill, 'fill-extrusion': fillExtrusion, hillshade, raster, background, debug, custom}; 在painter#renderLayer()中 renderLayer(painter: Painter, sourceCache: SourceCache, layer: StyleLayer, coords: Array<OverscaledTileID>) { if (layer.isHidden(this.transform.zoom)) return; if (layer.type !== 'background' && layer.type !== 'custom' && !coords.length) return; this.id = layer.id; //根据具体的style类型来绘制图层 draw[layer.type](painter, sourceCache, layer, coords, this.style.placement.variableOffsets);} 3.2.1 draw src->render 封装了各图层的绘制功能,在源码的render目录下封装了各中方式的绘制。 draw_background.js draw_circle.js draw_collision_debug.js draw_custom.js draw_debug.js draw_fill_extrusion.js draw_fill.js draw_heatmap.js draw_hillshade.js draw_line.js draw_raster.js draw_symbol.js 3.2.2 manager src->render 主要有ImageManager和GlyphManager。 3.2.2.1 ImageManager在mapbox-gl中,添加一个图标的基本流程如下。 //添加imagemap.loadImage('imageUrl', function (error, image) { if (error) throw error; map.addImage('cameraIcon', image);}); map.addLayer({ \"id\": \"eventDetailLayer\", \"type\": \"symbol\", \"source\": \"eventDetail\", \"layout\": { \"visibility\": 'none', \"icon-image\": \"cameraIcon\", //指定image \"icon-size\": 1, \"icon-offset\": [0, -14] //设置偏移量 }, }); 在mapbox-gl的源码中,ui/map.js中封装了addImage()方法,改方法实际是调用了style/style.js中的addImage()方法。style/style.js中的addImage()方法如下: //style/sytle.js中的addImage方法addImage(id: string, image: StyleImage) { if (this.getImage(id)) { return this.fire(new ErrorEvent(new Error('An image with this name already exists.'))); } this.imageManager.addImage(id, image); this.fire(new Event('data', {dataType: 'style'}));}//render/image_manager.js中的addImage方法 addImage(id: string, image: StyleImage) { assert(!this.images[id]); this.images[id] = image; } 以上代码可见,在mapbox-gl的api中添加图片是调用了样式中的addImage()方法,其中样式中的图片是由ImageManager在做管理。ImageManager中内置了图片数组对添加的图片进行缓存。 在mapbo-gl的api中描述如下 Add an image to the style. This image can be used in icon-image, background-pattern, fill-pattern, and line-pattern. An Map#error event will be fired if there is not enough space in the sprite to add this image. 可见这些图片可用做图标、背景填充、多边形填充以及线填充。 3.2.2.2 GlyphManager矢量切片中字体的管理 3.2.3 gl对webgl渲染过程及参数的一些封装。 3.2.4 programs渲染需要的变量等的封装。 3.2.5 Shaders着色器封装。 二、数据处理流程 数据处理流程 三、数据加载与渲染流程 数据加载与渲染流程 参考文章 mapbox.gl源码解析——基本架构与数据渲染流程","categories":[{"name":"mapbox源码分析","slug":"mapbox源码分析","permalink":"http://windynature.oschina.io/categories/mapbox源码分析/"}],"tags":[{"name":"gis","slug":"gis","permalink":"http://windynature.oschina.io/tags/gis/"},{"name":"mapbox-gl-js","slug":"mapbox-gl-js","permalink":"http://windynature.oschina.io/tags/mapbox-gl-js/"}]},{"title":"瓦片请求原理","slug":"gis基本原理/瓦片请求原理","date":"2020-03-17T15:37:55.660Z","updated":"2020-03-18T04:05:29.308Z","comments":true,"path":"2020/03/17/gis基本原理/瓦片请求原理/","link":"","permalink":"http://windynature.oschina.io/2020/03/17/gis基本原理/瓦片请求原理/","excerpt":"","text":"瓦片请求原理影像金字塔 原始单张图标比较大,客户端请求时间长,渲染速度慢,或者渲染不了这么大的图像。 对不同分辨率的图像进行切片缓存,提高访问速度。 原理如下: tileMatrix 地图分辨率换算原理分辨率 地图距离代表的实际距离是多少 比例尺地图距离/实际距离 DPI (dot per inch)每英寸有多少个像素点 换算 1英寸 = 2.54 厘米 1英寸 = 96像素 1像素 = 0.0254 / 96 米 resolution=scale*inch2centimeter/dpi。 其中scale是地图比例尺,inch2centimeter为英寸转厘米的参数,dpi为1英寸所包含的像素。 示例: 则根据1:125000000比例尺,图上1像素代表实地距离是 125000000*0.0254/96 = 33072.9166666667米,则该比例尺的分辨率为33072.9166666667米。 注意: 其他坐标系可根据实际地图单位进行替换 对于 WMTS 1.0.0 标准服务来说,其分辨率是通过像元大小(0.28mm=0.00028m)来界定的,转换为屏幕分辨率,即每英寸像元数为:1inch/(0.00028m/0.0254(m/inch))=0.0254/0.00028≈90.714。 瓦片行列号换算原理基本上所有的地图引擎访问地图服务都是通过切片的方式访问。地图引擎获取当前比例尺,然后获取屏幕的可视区域,通过可视区范围向地图服务器发送请求获取该可视区域的切片,然后拼接成为一副地图。前端的展示也多种多样,根据web技术的发展,主要有img标签,svg,canvas以及webgl。 假设,地图切图的原点是(x0,y0),地图的瓦片大小是tileSize,地图屏幕上1像素代表的实际距离是resolution。计算坐标点(x,y)所在的瓦片的行列号的公式是: col = floor((x0 - x)/( tileSize*resolution)) row = floor((y0 - y)/( tileSize*resolution)) 这个公式应该不难理解,简单点说就是,首先算出一个瓦片所包含的实际长度是多少LtileSize,然后再算出此时屏幕上的地理坐标点离瓦片切图的起始点间的实际距离LrealSize,然后用实际距离除以一个瓦片的实际长度,即可得此时的瓦片行列号:LrealSize/LtileSize。 tile 获取瓦片的换算流程原理(1)得到画布的高度和宽度以及此时需要显示的地图的几何范围 (2)得到画布的高度和宽度以及此时需要显示的地图的几何范围,同时也得到了需要显示的地图的级别 最后,我们需要得到在这两种需求下的瓦片行列号范围。 flowchart","categories":[{"name":"GIS基本原理","slug":"GIS基本原理","permalink":"http://windynature.oschina.io/categories/GIS基本原理/"}],"tags":[{"name":"gis","slug":"gis","permalink":"http://windynature.oschina.io/tags/gis/"},{"name":"切片","slug":"切片","permalink":"http://windynature.oschina.io/tags/切片/"}]},{"title":"计算不规则多边形的质心","slug":"gis基本原理/计算不规则多边形的质心","date":"2020-03-17T15:20:06.231Z","updated":"2020-03-18T04:05:21.792Z","comments":true,"path":"2020/03/17/gis基本原理/计算不规则多边形的质心/","link":"","permalink":"http://windynature.oschina.io/2020/03/17/gis基本原理/计算不规则多边形的质心/","excerpt":"","text":"计算不规则多边形的质心/*** 获取不规则多边形重心点* @param points* @return*/export function getCenterOfGravityPoint(points) { let area = 0;//多边形面积 let gx = 0, gy = 0;// 重心的x、y for (let i = 1, len = points.length; i <= len; i++) { const index = i % len; const ix = points[index].x; const iy = points[index].y; const nx = points[i - 1].x; const ny = points[i - 1].y; const temp = (ix * ny - iy * nx) / 2; area += temp; gx += temp * (ix + nx) / 3; gy += temp * (iy + ny) / 3; } gx = gx / area; gy = gy / area; return { x: gx, y: gy };}","categories":[{"name":"GIS基本原理","slug":"GIS基本原理","permalink":"http://windynature.oschina.io/categories/GIS基本原理/"}],"tags":[{"name":"gis","slug":"gis","permalink":"http://windynature.oschina.io/tags/gis/"},{"name":"质心","slug":"质心","permalink":"http://windynature.oschina.io/tags/质心/"}]},{"title":"公网地图最大比例尺","slug":"gis基本原理/比例尺及分辨率换算规则","date":"2020-03-17T15:11:42.882Z","updated":"2020-03-18T04:05:13.724Z","comments":true,"path":"2020/03/17/gis基本原理/比例尺及分辨率换算规则/","link":"","permalink":"http://windynature.oschina.io/2020/03/17/gis基本原理/比例尺及分辨率换算规则/","excerpt":"","text":"公网地图最大比例尺 地图名称 级别 scale Resolution(像素/m) 智图 16 1:9027.977411 2.388657133974685 天地图 20 1:564.24971667206887 0.149291070 Google地图 19 1:1128.49717634498 0.298582141738936 百度地图 18 1:3779.53 1.00000064 高德地图 18 1:1732.211455035507 0.4583142808114779 比例尺Scale表示的是比例尺,即地图上的一厘米代表着实际上的多少厘米。例如地图上1厘米代表实地距离500千米,可写成:1 ∶ 50,000,000或写成:1/50,000,000。 分辨率Resolution表示的是分辨率。Resolution 的实际含义代表当前地图范围内,1像素代表多少地图单位(X地图单位/像素),地图单位取决于数据本身的空间参考。可见Resolution跟 dpi有关系(dpi代表每英寸的像素数),跟地图的单位也有关系。 比例尺计算规则1英寸=2.54厘米; 1英寸=96像素; 最终换算的单位是米; 如果当前地图比例尺为1:125000000,则代表图上1米等于实地125000000米; 米和像素间的换算公式: 1英寸=0.0254米=96像素 1像素=0.0254/96 米 则根据1:125000000比例尺,图上1像素代表实地距离是 125000000*0.0254/96 = 33072.9166666667米。 google切片规则<?xml version=\"1.0\" encoding=\"utf-8\"?><Scales xmlns=\"http://www.supermap.com.cn/desktop\" version=\"6.1.2\"> <Scale>1:591658710.909131</Scale> <Scale>1:295829355.454566</Scale> <Scale>1:147914677.727283</Scale> <Scale>1:73957338.8636414</Scale> <Scale>1:36978669.4318207</Scale> <Scale>1:18489334.7159103</Scale> <Scale>1:9244667.35795517</Scale> <Scale>1:4622333.67897759</Scale> <Scale>1:2311166.83948879</Scale> <Scale>1:1155583.4197444</Scale> <Scale>1:577791.709872198</Scale> <Scale>1:288895.854936099</Scale> <Scale>1:144447.92746805</Scale> <Scale>1:72223.9637340248</Scale> <Scale>1:36111.9818670124</Scale> <Scale>1:18055.9909335062</Scale> <Scale>1:9027.9954667531</Scale> <Scale>1:4513.99773337655</Scale> <Scale>1:2256.99886668827</Scale> <Scale>1:1128.49943334414</Scale> <Scale>1:564.249716672069</Scale> <Scale>1:282.124858336034</Scale></Scales>","categories":[{"name":"GIS基本原理","slug":"GIS基本原理","permalink":"http://windynature.oschina.io/categories/GIS基本原理/"}],"tags":[{"name":"比例尺","slug":"比例尺","permalink":"http://windynature.oschina.io/tags/比例尺/"},{"name":"分辨率","slug":"分辨率","permalink":"http://windynature.oschina.io/tags/分辨率/"},{"name":"gis","slug":"gis","permalink":"http://windynature.oschina.io/tags/gis/"}]},{"title":"线程同步","slug":"java/多线程/线程同步","date":"2017-04-12T11:36:10.000Z","updated":"2020-03-18T04:10:04.257Z","comments":true,"path":"2017/04/12/java/多线程/线程同步/","link":"","permalink":"http://windynature.oschina.io/2017/04/12/java/多线程/线程同步/","excerpt":"","text":"线程安全问题非线程安全主要是指多个线程对一个对象中的同一个实例变量进行操作时会出现。 同步代码块为了解决线程安全问题,Java多线程引入了同步监视器来解决这个问题,同步监视器的通用方法就是同步代码块。定义如下: synchronized(obj) { //此处的代码就是同步代码块} obj就是同步监视器,上面代码的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。 任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。 同步监视器的目的:阻止两个线程对同一个共享资源进行并发访问,因此推荐使用可能被并发访问的共享资源充当同步监视器。 如一个经典的问题—银行取钱问题。 Account.java public class Account { private String accountNo; //编号 private double balance; //余额 public Account(String accountNo,double balance) { this.accountNo = accountNo; this.balance = balance; } public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } @Override public int hashCode() { return accountNo.hashCode(); } @Override public boolean equals(Object obj) { if(this==obj) return true; if(obj !=null && obj.getClass() == Account.class) { Account target = (Account)obj; return target.getAccountNo().equals(accountNo); } return false; }} DrawThread.java public class DrawThread extends Thread { //模拟账户用户 private Account account; //取钱数 private double drawAmount; public DrawThread(String name, Account account, double drawAmount) { super(name); this.account = account; this.drawAmount = drawAmount; } @Override public void run() { /** * 使用account作为同步监视器,任何线程进入线面的同步代码块之前 * 必须先获得对account账户的锁定---其他线程无法获得锁,也就服务修改它 * 这种做法符合:\"加锁->修改->释放锁\"的逻辑 */ synchronized (account) { if (account.getBalance() >= drawAmount) { System.out.println(getName() + \"取钱成功!吐出钞票:\" + drawAmount); try { Thread.sleep(1); } catch (InterruptedException ex) { ex.printStackTrace(); } account.setBalance(account.getBalance()-drawAmount); System.out.println(\"\\t余额为:\" + account.getBalance()); } else { System.out.println(getName()+\"取钱失败!余额不足!\"); } } }} DrawTest.java public class DrawTest { public static void main(String[] args) { Account ac = new Account(\"1234567\",1000); //模拟两个线程对一个账户取钱 new DrawThread(\"\",ac,800).start(); new DrawThread(\"\",ac,800).start(); }} 通过这种方式就可以保证并发线程在任何一个时刻只有一个线程可以进入修改共享资源的代码区(临界区),所以同一时刻最多只有一个线程处于临界区内,从而保证了线程的安全性。 同步方法同步方法是指用synchronized关键字来修饰某个方法。 对于synchronized修饰的实例方法(非static方法)而言,不需要指定同步监视器,同步方法的监视器是this,也就是调用该方法的对象。 修改Account.java public synchronized void draw(double drawAmount) { if (account.getBalance() >= drawAmount) { System.out.println(getName() + \"取钱成功!吐出钞票:\" + drawAmount); try { Thread.sleep(1); } catch (InterruptedException ex) { ex.printStackTrace(); } account.setBalance(account.getBalance()-drawAmount); System.out.println(\"\\t余额为:\" + account.getBalance()); } else { System.out.println(getName()+\"取钱失败!余额不足!\"); }} 修改DrawThread.java public void run() { /** * 直接调用account对象的draw()方法来执行取钱操作 * 同步方法的同步监视器是this,this代表调用draw()方法的对象 * 也就是说,线程进入draw()方法之前,必须先对account对象加锁 **/ account.draw(drawAmount);} 可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全带来的负面影响,可采用如下策略: 只对那些会改变竞争资源的方法同步。 可变类应该提供单线程环境和多线程环境即线程不安全版本和线程安全版本。如jdk中的StringBuilder和StringBuffer 释放同步监视器的锁定释放同步监视器的锁定的几种情况: 当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行,当前线程会释放同步监视器 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、该方法异常结束,当前线程将会释放同步监视器。 当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。 以下几种方法不会释放同步监视器: 程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器。 程序调用线程的suspend()方法将该线程挂起,该线程不会释放同步监视器。程序应尽量避免使用suspend()和resume方法来控制线程。 同步锁(Lock)Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源前应先获得Lock对象。 某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock、ReadWriteLock是Java5提供的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。 jdk中ReentrantLock的使用示例: class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } }} 使用ReentrantLock对象来进行同步,加锁和释放锁出现在不同的作用范围内,通常见识使用finally块来确保在必要时释放锁。 提示:使用Lock与使用同步方法有点像是,只是使用Lock时显示使用Lock对象作为同步锁,而使用同步锁方法时系统隐式使用当前对象作为同步监视器,同样都符合”加锁->修改->释放锁”的操作模式,而且使用Lock对象时每个Lock对象对应一个Account对象,一样可以保证对于同一个Account对象,同一时刻只能有一个线程进入临界区。 同步方法或同步块代码使用与资源相关的、隐式的同步监视器,并且强制要求加锁和释放锁要出现在一个代码块结构中,而且当获取了多个锁时,他们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。 ReentrantLock锁具有可重入性,也就是说,一个线程可以对已被加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。 死锁当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。 class A { public synchronized void foo(B b) { System.out.println(\"当前线程名: \" + Thread.currentThread().getName() + \" 进入了A实例的foo方法\"); try { Thread.sleep(200); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println(\"当前线程名: \" + Thread.currentThread().getName() + \"企图调用B实例的last方法\"); b.last(); } public synchronized void last() { System.out.println(\"进入了A实例的last方法内部\"); }}class B { public synchronized void bar(A a) { System.out.println(\"当前线程名: \" + Thread.currentThread().getName() + \" 进入了B实例的bar方法\"); try{ Thread.sleep(200); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println(\"当前线程名: \" + Thread.currentThread().getName() + \" 企图调用A实例的last方法\"); a.last(); } public synchronized void last() { System.out.println(\"进入了B实例的last方法内部\"); }}public class DeadLock implements Runnable { A a = new A(); B b = new B(); public void init() { Thread.currentThread().setName(\"主线程\"); a.foo(b); System.out.println(\"进入了主线程后\"); } @Override public void run() { Thread.currentThread().setName(\"副线程\"); //调用b对象的bar()方法 b.bar(a); System.out.println(\"进入了副线程之后\"); } public static void main(String[] args) { DeadLock d1 = new DeadLock(); //以di为target启动新线程 new Thread(d1).start(); d1.init(); }} 上面的代码有两个线程 主线程调用init()方法,调用A对象的foo()方法,A被锁住,且主线程暂停200ms, 这时CPU切换到副线程,副线程调用B的bar()方法,B被锁住,副线程暂停200ms; 主线程醒来想调动B的last()方法,但这是B被锁住所以主线程阻塞; 副线程醒来想调用A的last()方法,但主线程阻塞没有释放A的锁。 这时主线程保持着A对象的锁,等待对B对象加锁,而副线程保持着B对象的锁,等待对A对象加锁就造成了死锁。","categories":[{"name":"多线程","slug":"多线程","permalink":"http://windynature.oschina.io/categories/多线程/"}],"tags":[{"name":"线程同步","slug":"线程同步","permalink":"http://windynature.oschina.io/tags/线程同步/"}]},{"title":"控制线程","slug":"java/多线程/控制线程","date":"2017-04-12T04:44:58.000Z","updated":"2020-03-18T04:10:07.587Z","comments":true,"path":"2017/04/12/java/多线程/控制线程/","link":"","permalink":"http://windynature.oschina.io/2017/04/12/java/多线程/控制线程/","excerpt":"","text":"join线程Thread提供了让一个线程等待另一个线程完成的方法–join()方法当在某个程序执行流中调用其他线程的join()方法时,调用线程被阻塞,直到被join()方法加入的join线程执行完为止。 public class JoinThread extends Thread { public JoinThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + \" \" + i); } } public static void main(String[] args) throws Exception { //启动子线程 new JoinThread(\"新线程\").start(); for (int i = 0; i < 100; i++) { if (i == 20) { JoinThread jt = new JoinThread(\"被Join的线程\"); jt.start(); //mian线程调用了jt线程的join()方法,main线程 //必须等jt执行结束后才会向下执行 jt.join(); } System.out.println(Thread.currentThread().getName() + \" \" + i); } }} 上面的程序有三个线程:主线程、新线程并发执行,当主线程的循环变量到20的时候,启动了“被Join的线程”,由于是主线程join的,所以主线程将一直处于阻塞状态,直到“被join的线程”执行完成后才执行。 后台线程在后台运行,其任务是为其他线程提供服务的线程被称为后台线程(Daemon Thread),也称为守护线程或精灵线程。JVM的垃圾回收线程就是典型的后台线程。 特征:如果所有的前台线程都死亡,后台线程会自动死亡。 public class DaemonThread extends Thread { //定义后台线程执行体 @Override public void run() { for (int i = 0; i < 10000; i++) { System.out.println(getName() + \" \" + i); } } public static void main(String[] args) { DaemonThread daemonThread = new DaemonThread(); daemonThread.setDaemon(true); //设置为后台线程 daemonThread.start(); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread()+ \" \" + i); } //=====程序执行到此处,前台线程(main线程)结束 //后台线程也应该随之结束 }} 上面的代码将执行线程设置成后台线程,可以看到当所有的前台线程死亡时,后台线程随之死亡。 注意: setDaemon(true)必须在start()方法之前调用,否则会引发IllegalThreadStateException 线程睡眠:sleep如果要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现。 线程让步:yieldyield()方法可以让当前正在执行的线程暂停,但不会阻塞该线程,只是将该线程转入就绪状态。yield()方法只是让当前的线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。 public class YieldTest extends Thread { public YieldTest(String name) { super(name); } @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(getName() + \" \" + i); //当i等于20的时候,使用yield方法让当前线程让步 if(i == 20) { System.out.println(\"--------------\"); Thread.yield(); } } System.out.println(getName()+ \" 执行完了======\"); } public static void main(String[] args) { YieldTest yt1 = new YieldTest(\"高级\"); yt1.setPriority(Thread.MAX_PRIORITY); yt1.start(); YieldTest yt2 = new YieldTest(\"低级\"); yt2.setPriority(Thread.MIN_PRIORITY); yt2.start(); }} 上面的例子中,优先级高的线程调用yield()方法后会给与自己优先级相同或优先级更高的线程执行机会。注意:多CPU下效果不明显。 sleep()和yield()方法的区别: sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法只会给优先级相同,或优先级更高的线程执行机会。 sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yield()方法不会讲线程转入阻塞状态,它只是强制将当前线程进入就绪状态。 sleep()方法声明抛出InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要不显式声明抛出该异常;而yield()方法则没有声明抛出任何异常。 sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。","categories":[{"name":"多线程","slug":"多线程","permalink":"http://windynature.oschina.io/categories/多线程/"}],"tags":[{"name":"join","slug":"join","permalink":"http://windynature.oschina.io/tags/join/"},{"name":"yield","slug":"yield","permalink":"http://windynature.oschina.io/tags/yield/"}]},{"title":"线程的生命周期及优先级","slug":"java/多线程/线程的生命周期及优先级","date":"2017-04-12T04:38:19.000Z","updated":"2020-03-18T04:09:59.555Z","comments":true,"path":"2017/04/12/java/多线程/线程的生命周期及优先级/","link":"","permalink":"http://windynature.oschina.io/2017/04/12/java/多线程/线程的生命周期及优先级/","excerpt":"","text":"线程的生命周期线程的5中状态:新建(NEW)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡 线程启动后,它不可能一直霸占着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。 新建和就绪状态使用new关键字穿件之后就处于新建状态,此时和普通java对象一样。当对象调用start()方法后,该线程就处于就绪状态,Java虚拟机会为其创建方法低啊用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程什么时候开始运行,取决于JVM里的线程调度器的调度。 注意: 启动线程使用start()方法,而不是run()方法!永远不要调用线程对象的run()方法!调用start()方法来启动线程,系统会把该run()方法当成线程执行体来处理;但如果直接调用线程对象的run()方法,则run()方法立即就会被执行,而且在run()方法返回之前其他线程无法执行—也就是说,系统会把线程对象当成一个普通对象,而run()方法也是一个普通方法,而不是线程执行体。 只能对处于新建状态的线程调用start()方法,否则将引发IllegalThreadStateException异常。 希望调用子线程的start()方法后线程立即开始执行,程序可以使用Thread.sleep(1)来让当前运行的线程(主线程)睡眠1秒。 运行和阻塞状态如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。线程调度的细节取决于底层平台采用的策略,对于采用抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务,当任务用完后,该系统狗就会剥夺该线程所占用的资源,让其他线程获得执行的激活。在选择下一个线程的时候,系统就会考虑线程的优先级。所有现代桌面和服务器操作系统都采用抢占式调度策略。一些小型设备可能采用协作式调度策略,在这样的系统中,当一个线程代用了他的sleep()或yield方法后才会放弃所占用的资源,也就是该线程主动放弃所占用的资源。 如下情况会进入阻塞状态: 调用sleep()方法,主动放弃所占用的处理器资源。 线程调用了一个阻塞式的IO方法,该方法返回之前,该线程被阻塞。 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。(被锁住了) 线程正在等待某个通知(notify) 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,应避免使用。 线程死亡 run()或call()方法执行完成,线程正常结束 线程抛出一个为补货的Exception或Error 直接调用该线程的stop()方法来结束该线程–该方法容易导致死锁,不推荐。 注意: 当主线程结束时,其他线程不受影响,并不会随之结束。一旦子线程启动起来后,它就用后和主线程相同的地位,它不会受主线程的影响。 测试线程是否死亡可以使用线程对象的isAlive()方法,当线程处于就绪、运行、阻塞为true;当线程处于新建、死亡2中状态时,该方法返回false。 线程的优先级线程的优先级设置线程的优先级是为了在多线程环境中便于系统对线程的调度,优先级高的线程将优先执行。 一个线程的优先级设置遵从一下原则: 线程创建时,子集成父的优先级。 线程创建后,可调用setPriority()方法改变优先级。 线程的优先级是1-10之间的正整数。 1- MIN_PRIORITY10-MAX_PRIORITY5-NORM_PRIORITY 如果什么都没有设置,默认值是5。 但是不能依靠线程的优先级来决定线程的执行顺序。 线程的调度策略线程调度器选择优先级最高的线程运行。但是发生一下情况,就会终止线程的运行: 线程体中调用了yield()方法,让出了对CPU的占用权。 线程体重调用了sleep()方法,是线程进入睡眠状态。 线程由于I/O操作而受阻塞。 另一个更高优先级的线程出现。 在支持时间片的系统中,该线程的时间片用完。","categories":[{"name":"多线程","slug":"多线程","permalink":"http://windynature.oschina.io/categories/多线程/"}],"tags":[{"name":"生命周期","slug":"生命周期","permalink":"http://windynature.oschina.io/tags/生命周期/"}]},{"title":"创建线程的第三种方式","slug":"java/多线程/创建线程的第三种方式(使用Callable和Future创建线程)","date":"2017-04-12T04:15:29.000Z","updated":"2020-03-18T04:09:49.306Z","comments":true,"path":"2017/04/12/java/多线程/创建线程的第三种方式(使用Callable和Future创建线程)/","link":"","permalink":"http://windynature.oschina.io/2017/04/12/java/多线程/创建线程的第三种方式(使用Callable和Future创建线程)/","excerpt":"","text":"使用Callable和Future创建线程从Java5开始,Java提供了Callable接口,该接口提供了一个call()方法作为线程执行体。提供了如下功能。 call()方法可以有返回值。 call()方法可以声明抛出异常。 如何获取call()方法的返回值?Callable接口是Java 5新增的接口,而且不是Runnable接口的子接口,所以不能直接作为Thread的target。call()方法并不是直接调用,而是作为线程执行体被调用的。 Java5提供Future接口来代表call()方法的返回值,并提供了一个FutureTask实现类,该实现类实现了Future接口和Runnable接口,所以可以作为Thread类的target。 Future接口主要公共方法 boolean cancel(boolean mayInterruptIfRunning):试图取消该Future里关联的Callable任务。 V get():返回Callable任务里call方法 V get(long timeout, TimeUnit unit):返回Callable任务里call()方法的返回值。指定阻塞时间,指定时间无返回值抛TimeoutException boolean isCancelled():如果在Callable任务完成前被取消,则返回true. boolean isDone():如果任务已完成,则返回true。 Callable和Future实现例子/**实现Callable接口来实现线程类**/public class ThirdThread implements Callable<Integer> { //实现call方法,作为线程体 public Integer call() { int i = 0; for(;i<100;i++) { System.out.println(Thread.currentThread().getName() + \"的循环变量i的值:\" + i); } return i; } public static void main(String[] args) { ThirdThread rt = new ThirdThread(); //使用FutureTask来包装Callable对象 FutureTask<Integer> task = new FutureTask<Integer>(rt); for(int i=0; i<100;i++) { System.out.println(Thread.currentThread().getName + \" 的循环变量i的值:\" + i); if(i==20) { //是指还是以Callable对象来创建并启动线程 new Thread(task,\"有返回值的线程\").start(); } } try{ //获取线程返回值 System.out.println(\"子线程的返回值:\" + task.get()); } catch(Execption e) { e.printStackTracke(); } }} Callable接口有泛型限制,Callable接口里的泛型形参类型与call()方法返回值类型相同。 实现步骤创建并启动返回值的线程的步骤如下: 创建Callable的接口实现类,并实现call方法,该方法作为线程执行体,且该call方法具有返回值。 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值。 使用FutureTask对象作为Thread对象的target创建并启动新线程。 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。 监视器","categories":[{"name":"多线程","slug":"多线程","permalink":"http://windynature.oschina.io/categories/多线程/"}],"tags":[{"name":"Callable","slug":"Callable","permalink":"http://windynature.oschina.io/tags/Callable/"},{"name":"Future","slug":"Future","permalink":"http://windynature.oschina.io/tags/Future/"}]},{"title":"什么时候使用CountDownLatch","slug":"java/多线程/CountDownLatch详解","date":"2017-04-05T13:40:17.000Z","updated":"2020-03-18T04:10:13.522Z","comments":true,"path":"2017/04/05/java/多线程/CountDownLatch详解/","link":"","permalink":"http://windynature.oschina.io/2017/04/05/java/多线程/CountDownLatch详解/","excerpt":"","text":"原文地址译文地址:什么时候使用CountDownLatch 什么时候使用CountDownLatch正如每个Java文档所描述的那样,CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。在Java并发中,countdownlatch的概念是一个常见的面试题,所以一定要确保你很好的理解了它。 CountDownLatch是什么CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它们都存在于java.util.concurrent包下。CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。 CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。 countdownlatch CountDownLatch的伪代码如下所示: Main thread startCreate CountDownLatch for N threadsCreate and start N threadsMain thread wait on latchN threads completes there tasks are returnsMain thread resume execution CountDownLatch如何工作CountDownLatch.java类中定义的构造函数: //Constructs a CountDownLatch initialized with the given count.public void CountDownLatch(int count) {...} 构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值。 与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,知道其他线程完成各自的任务。 其他N个线程必须引用闭锁对象,因为他们要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过CounDownLatch.countDown()方法来完成的;每调用一次这个方法,在这个构造函数中的初始化的count值就减1。所以当N个线程都调用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。 在实时系统中的使用场景1. 实现最大的并行性有事我们想同事启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用一次countDown()方法就可以让所有的等待线程同时恢复执行。 2. 开始执行前等待n个线程完成各自任务例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。 3. 死锁检测一个非常方便的使用场景,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。 CountDownLatch使用例子在这个例子中,我模拟了一个应用程序启动类,它开始启动了n个线程,这些线程将检查外部系统并通知闭锁,并且启动类一直在闭锁上等待着。一旦验证和检查了所有外部服务,那么启动类恢复执行。 BaseHealthChecker.java:这个类是一个Runnable,负责所有特定的外部服务健康的检测。它删除了重复的代码和闭锁的中心控制代码。 public abstract class BaseHandlerChecker implements Runnable { private CountDownLatch _latch; private String _serviceName; private boolean _serviceUp; //Get latch object in constructor so that after completing the task, thread can countdown() the latch public BaseHandlerChecker(String serviceName,CountDownLatch latch) { super(); this._latch = latch; this._serviceName = serviceName; this._serviceUp = false; } @Override public void run() { try { verifyService(); _serviceUp = true; } catch (Throwable t) { t.printStackTrace(System.err); _serviceUp = false; } finally { if(_latch!=null) { _latch.countDown(); } } } public String getServiceName() { return _serviceName; } public boolean isServiceUp() { return _serviceUp; } //This methos needs to be implemented by all specific service checker public abstract void verifyService();} NetworkHealthChecker.java:这个类继承了BaseHealthChecker,实现了verifyService()方法。DatabaseHealthChecker.java和CacheHealthChecker.java除了服务名和休眠时间外,与NetworkHealthChecker.java是一样的。 public class NetworkHealthChecker extends BaseHealthChecker{ public NetworkHealthChecker (CountDownLatch latch) { super(\"Network Service\", latch); } @Override public void verifyService() { System.out.println(\"Checking \" + this.getServiceName()); try { Thread.sleep(7000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.getServiceName() + \" is UP\"); }} ApplicationStartupUtil.java:这个类是一个主启动类,它负责初始化闭锁,然后等待,直到所有服务都被检测完。 public class ApplicationStartupUtil{ //List of service checkers private static List<BaseHealthChecker> _services; //This latch will be used to wait on private static CountDownLatch _latch; private ApplicationStartupUtil() { } private final static ApplicationStartupUtil INSTANCE = new ApplicationStartupUtil(); public static ApplicationStartupUtil getInstance() { return INSTANCE; } public static boolean checkExternalServices() throws Exception { //Initialize the latch with number of service checkers _latch = new CountDownLatch(3); //All add checker in lists _services = new ArrayList<BaseHealthChecker>(); _services.add(new NetworkHealthChecker(_latch)); _services.add(new CacheHealthChecker(_latch)); _services.add(new DatabaseHealthChecker(_latch)); //Start service checkers using executor framework Executor executor = Executors.newFixedThreadPool(_services.size()); for(final BaseHealthChecker v : _services) { executor.execute(v); } //Now wait till all services are checked _latch.await(); //Services are file and now proceed startup for(final BaseHealthChecker v : _services) { if( ! v.isServiceUp()) { return false; } } return true; }} 现在你可以写测试代码去检测一下闭锁的功能了。 public class Main { public static void main(String[] args) { boolean result = false; try { result = ApplicationStartupUtil.checkExternalServices(); } catch (Exception e) { e.printStackTrace(); } System.out.println(\"External services validation completed !! Result was :: \"+ result); }} 结果: Output in console: Checking Network ServiceChecking Cache ServiceChecking Database ServiceDatabase Service is UPCache Service is UPNetwork Service is UPExternal services validation completed !! Result was :: true 常见面试题 解释一下CountDownLatch概念? CountDownLatch 和CyclicBarrier的不同之处? 给出一些CountDownLatch使用的例子? CountDownLatch 类中主要的方法?","categories":[{"name":"多线程","slug":"多线程","permalink":"http://windynature.oschina.io/categories/多线程/"}],"tags":[{"name":"CountDownLatch","slug":"CountDownLatch","permalink":"http://windynature.oschina.io/tags/CountDownLatch/"}]},{"title":"STOMP协议详解","slug":"java/STOMP协议详解","date":"2017-04-04T04:15:24.000Z","updated":"2020-03-18T04:07:28.837Z","comments":true,"path":"2017/04/04/java/STOMP协议详解/","link":"","permalink":"http://windynature.oschina.io/2017/04/04/java/STOMP协议详解/","excerpt":"","text":"转自:http://blog.csdn.net/chszs/article/details/46592777 STOMP协议介绍STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。 STOMP协议的前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。 STOMP是一个非常简单和容易实现的协议,其设计灵感源自于HTTP的简单性。尽管STOMP协议在服务器端的实现可能有一定的难度,但客户端的实现却很容易。例如,可以使用Telnet登录到任何的STOMP代理,并与STOMP代理进行交互。 STOMP协议与2012年10月22日发布了最新的STOMP 1.2规范。要查看STOMP 1.2规范,见: 1.2规范 STOMP的实现业界已经有很多优秀的STOMP的服务器/客户端的开源实现,下面就介绍一下这方面的情况。 STOMP服务器 项目名 兼容STOMP的版本 描述 Apache Apollo 1.0 1.1 1.2 ActiveMQ的继承者 http://activemq.apache.org/apollo Apache ActiveMQ 1.0 1.1 流行的开源消息服务器 http://activemq.apache.org/ HornetQ 1.0 来自JBoss的消息中间件 http://www.jboss.org/hornetq RabbitMQ 1.0 1.1 1.2 基于Erlang、支持多种协议的消息Broker,通过插件支持STOMP协议 http://www.rabbitmq.com/plugins.html#rabbitmq-stomp Stampy 1.2 STOMP 1.2规范的一个Java实现 http://mrstampy.github.com/Stampy/ StompServer 1.0 一个轻量级的纯Ruby实现的STOMP服务器 http://stompserver.rubyforge.org/ STOMP客户端库 项目名 兼容STOMP的版本 描述 activemessaging 1.0 Ruby客户端库 http://code.google.com/p/activemessaging/ onstomp 1.0 1.1 Ruby客户端库 https://rubygems.org/gems/onstomp Apache CMS 1.0 C++客户端库 http://activemq.apache.org/cms/ Net::STOMP::Client 1.0 1.1 1.2 Perl客户端库 http://search.cpan.org/dist/Net-STOMP-Client/ Gozirra 1.0 Java客户端库 http://www.germane-software.com/software/Java/Gozirra/ libstomp 1.0 C客户端库,基于APR库 http://stomp.codehaus.org/C Stampy 1.2 Java客户端库 http://mrstampy.github.com/Stampy/ stomp.js 1.0 1.1 JavaScript客户端库 http://jmesnil.net/stomp-websocket/doc/ stompest 1.0 1.1 1.2 Python客户端库,全功能实现,包括同步和异步 https://github.com/nikipore/stompest StompKit 1.2 Objective-C客户端库,事件驱动 https://github.com/mobile-web-messaging/StompKit/ stompngo 1.0 1.1 1.2 Go客户端库 https://github.com/gmallard/stompngo stomp.py 1.0 1.1 1.2 Python客户端库 https://github.com/jasonrbriggs/stomp.py tStomp 1.1 TCL客户端库 https://github.com/siemens/tstomp STOMP协议分析STOMP协议与HTTP协议很相似,它基于TCP协议,使用了以下命令: CONNECT SEND SUBSCRIBE UNSUBSCRIBE BEGIN COMMIT ABORT ACK NACK DISCONNECT STOMP的客户端和服务器之间的通信是通过“帧”(Frame)实现的,每个帧由多“行”(Line)组成。 第一行包含了命令,然后紧跟键值对形式的Header内容。 第二行必须是空行。 第三行开始就是Body内容,末尾都以空字符结尾。 STOMP的客户端和服务器之间的通信是通过MESSAGE帧、RECEIPT帧或ERROR帧实现的,它们的格式相似。","categories":[{"name":"消息队列","slug":"消息队列","permalink":"http://windynature.oschina.io/categories/消息队列/"}],"tags":[{"name":"STOMP协议","slug":"STOMP协议","permalink":"http://windynature.oschina.io/tags/STOMP协议/"}]},{"title":"装饰模式总结","slug":"设计模式/装饰模式","date":"2017-04-03T03:59:13.000Z","updated":"2020-03-18T04:07:13.290Z","comments":true,"path":"2017/04/03/设计模式/装饰模式/","link":"","permalink":"http://windynature.oschina.io/2017/04/03/设计模式/装饰模式/","excerpt":"","text":"装饰模式概念装饰模式(Decorator):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。 结构图 Component:是定义一个对象接口,可以给这些对象动态第添加职责。ConcreteComponent:是定义了一个具体的对象,也可以给这个对象添加一些职责。Decorator:装饰抽象类,继承了Component,从外来类来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。ConfreteDecorator:是具体的装饰对象,起到给Component添加职责的功能。 示例代码Component类 public abstract class Component { public abstract void operation();} ConcreteComponent类 public class ConcreateComponent extends Component{ @Override public void operation() { System.out.println(\"具体的对象操作\"); }} Decorator类 public abstract class Decorator extends Component { protected Component component; public void setComponent(Component component) { this.component = component; } //重写Operation,实际执行的是Component的operation @Override public void operation() { if(component!=null) { component.operation(); } }} ConcreteDecoratorA类 public class ConcreteDecoratorA extends Decorator { private String addedState; @Override public void operation() { /* * 1.运行原Component的operation() * 2.再执行本类的功能,如addedState,相当于对原Component进行了装饰 * */ super.operation(); addedState = \"New State\"; System.out.println(\"具体装饰对象A的操作\"); }} ConcreteDecoratorB类 public class ConcreteDecoratorB extends Decorator { /** * 1.首先运行原Component的operation() * 2.指定本类的功能,如addedBehavior(),响度昂与对原Component进行了装饰 */ @Override public void operation() { super.operation(); addedBehavior(); System.out.println(\"具体装饰对象B的操作\"); } /** * 本类独有的方法,以区别于ConcreteDecoratorB */ private void addedBehavior(){ }} 客户端代码 public class Client { public static void main(String[] args) { ConcreateComponent c = new ConcreateComponent(); ConcreteDecoratorA d1 = new ConcreteDecoratorA(); ConcreteDecoratorB d2 = new ConcreteDecoratorB(); //先执行被包装的对象 d1.setComponent(c); d2.setComponent(d1); d2.operation(); }}out:具体的对象操作具体装饰对象A的操作具体装饰对象B的操作 装饰模式是利用setComponent来对对象进行包装的。这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。 如果只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类。同样道理,如果只有一个ConcreteDecorator类,那么久没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。 装饰模式总结装饰模式是为以后功能动态第添加更多功能的方式。当系统需要新功能的时候,是向就得类中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为,这种在主类中添加新的字段,新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。而装饰模式确提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所需要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象优点:把类中的装饰功能从类中搬移去除,这样可以简化原有的类,好处是有效地把类的核心职责和装饰功能分开,而且可以去除相关类中重复的装饰逻辑。","categories":[{"name":"设计模式","slug":"设计模式","permalink":"http://windynature.oschina.io/categories/设计模式/"}],"tags":[{"name":"装饰模式","slug":"装饰模式","permalink":"http://windynature.oschina.io/tags/装饰模式/"}]},{"title":"RabbitMQ基本概念","slug":"java/rabbitmq基本概念","date":"2017-04-01T13:05:48.000Z","updated":"2020-03-18T04:06:28.064Z","comments":true,"path":"2017/04/01/java/rabbitmq基本概念/","link":"","permalink":"http://windynature.oschina.io/2017/04/01/java/rabbitmq基本概念/","excerpt":"","text":"转自1:http://www.ostest.cn/archives/497转自2:http://blog.csdn.net/whycold/article/details/41119807 一、使用场景 两个或多个系统间通过定时任务来同步某些数据。 异构系统的不同进程间相互调用、通讯的问题。 消息服务擅长于解决多系统、异构系统间的数据交换(消息通知/通讯)问题,你也可以把它用户系统间服务的相互调用(RPC)。 二、RabbitMQ简介1. AMQPAMQP:(Advanced Message Queuing Protocol,高级消息队列协议),是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。AMQP的主要特征是:面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 2. RabbitMQRabbitMQ的主要特征是一个开源的AMQP实现,服务器端使用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用户在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 三、RabbitMQ中的概念1. ConnectionFactory、Connection、ChannelConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。 Connection:是RabbitMQ的socket连接,它封装了socket协议相关部分逻辑。 ConnectionFactory:为Connection的制造工厂。 Channel:是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue、发布消息等。 2. Queue队列(queue):是由消费者简历的,是messages的重点,可以理解成装消息的容器。消息一直存在队列里,知道有客户点或者成为Consumer消费者连接到这个队列并将message取走位置。队列可以有多个。Queue是RabbitMQ的内部对象,用于存储消息,用下图表示。 RabbitMQ中的消息都只能存储在Queue中,生产者(下图中的P)生产消息并最终投递到Queue中,消费者(下图中的C)可以从Queue中获取消息并消费。 多个消费者可以订阅同一个Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。 3. Message Acknowledgment在实际应用中,可能会发生消费者收到Queue中的消息,但没有处理完成就宕机(或者出现其他意外)的情况,这种情况下就可能导致消息丢失。为了避免这种情况发生,我们可以要求消费者在消费完消息后发送一个回执给RabbitMQ,RabbitMQ收到消息回执(Message acknowledgement)后才降该消息从Queue中移除;如果RabbitMQ没有收到回执并检测到消费者的RabbitMQ连接断开,则RabbitMQ会将消息发送给其他消费者(如果存在多个消费者)进行处理。这里不存在timeout概念,一个消费者处理消息时间再长也不会导致该消息被发送个其他消费者,除非它的RabbitMQ连接断开。这里会产生另外一个问题,如果我们的开发人员在处理完业务逻辑后,忘记发送回执给RabbitMQ,这将会导致严重的bug-Queue中堆积的消息会越来越多;消费者重启后会重复消费这些消息并重复执行业务逻辑。另外pub message是没有ack的。 4. Message durability如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。但依然解决不了小概率丢失时间的发生(比如RabbitMQ 服务器已经收到生产者的消息,但还没有来得及持久化该消息时RabbitMQ服务器就断电了),如果我们需要对这种小概率事件也要管理起来,那么我们要用到事务。 5. Prefetch count前面我们讲到如果有多个消费者同事订阅同一个Queue中的消息,Queue中的消息平坦给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就会处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数量,比如我们设置prefetchCount = 1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。 6. Exchange交换机:可以理解成具有路由表的路由程序。每个消息都有一个路由键(routing key),就是一个简单的字符串。交换机中有一系列的绑定(bingding),即路由规则(routes)。交换机可以有多个。多个队列可以和同一个交换机绑定,同时多个交换机也可以和同一个队列绑定。(多对多的关系) 在上一节我们看到生产者将消息投递到Queue中,实际上这在RabbitMQ中是永远不会发生的。实际的情况是,生产者将消息发送到Exchange,由Exchange将消息路由到一个或多个Queue中(或者丢弃)。 Exchange是按照什么逻辑将消息路由到Queue的?这个将在Binding一节介绍。RabbitMQ中的Exchange有四种类型,不同的类型有着不同的路由策略,这将在Exchange Types一节介绍。 6.1. routing key生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能最终生效。 在Exchange Type与binding key固定的情况下(在正常使用时,一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定routing key来决定消息流向哪里。 RabbitMQ为routing key设定的长度限制为255 bytes。 6.2. BindingRabbitMQ中通过Binding将Exchange与Queue关联起来,这样RabbitMQ就知道如何正确地将消息路由到指定的Queue了。 binding ### 6.3 Binding key 在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;消费者将消息发送给Exchange时,一般会指定一个routing key;当binding key与routing key相匹配时,消息将会被路由到对应的Queue中。这个将在Exchange Types章节会列举实际的例子加以说明。在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的binding key。binding key 并不是在所有情况下都生效,它依赖于Exchange Type,比如fanout类型的Exchange就会无视binding key,而是将消息路由到所有绑定到该Exchange的Queue。 6.4 Exchange TypesRabbitMQ常用的Exchange Type有fanout、direct、topic、headers这四种(AMQP规范里还提到两种Exchange Type,分别为system与自定义,这里不予以描述),下面分别进行介绍。 1. fanoutfanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中(类似广播)。 上图中,生产者(P)发送到Exchange(X)的所有消息都会路由到图中的两个Queue,并最终被两个消费者(C1与C2)消费。 2. directdirect类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。 以上图的配置为例,我们以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…);如果我们以routingKey=”info”或routingKey=”warning”来发送消息,则消息只会路由到Queue2。如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。 3. topic前面讲到direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同(通配符),它约定: routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit” binding key与routing key一样也是句点号“. ”分隔的字符串 binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个) 以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1与Q2,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。 4. headersheaders类型的Exchange不依赖于 routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。 在绑定Queue与Exchange时指定一组键值对,当消息发送到Exchange时,RabbitMQ会渠道该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定石指定的键值对。如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。 5. RPCMQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败(甚至连有没有消费者来处理这条消息都不知道)。但知己的应用场景中,我们很可能需要到一些同步处理,需要同步等待服务端将我们的消息处理完成后再进行下一步处理。这相当于RPC。在RabbitMQ中也支持RPC。 RabbitMQ中实现RPC的机制是: 客户端发送请求(消息)时,在消息的属性(MessageProperties,在AMQP秀一种定义了14种properties,这些属性会随着消息一起发送)中设置了两个值replyTo(一个Queue名称,用于告诉服务器处理完成后将通知我的消息发送到这个Queue中)和correlationId(此次请求的标志号,服务器处理完成后需要将此属性返回,客户端将根据这个id了解哪条请求被成功执行了或执行失败)。 服务器端收到消息并处理 服务器端处理完消息后,并将生成一条应答消息到replayTo指定的Queue,同时带上correlationId属性 客户端之前已经订阅replayTo指定的Queue,从中收到服务器的应答消息后,根据其中的correlationId属性分析哪条请求被执行了,根据执行结果进行后续业务处理。 7. 虚拟主机(virtual host)一个虚拟主机持有一组交换机、队列和绑定。为什么需要多个虚拟主机呢?RabbitMQ中,用户只能在虚拟主机的力度进行权限控制。因此,如果需要禁止A组访问B组的交换机/队列/绑定,必须为A和B分别创建一个虚拟主机。每一个RabbicMQ服务器都有一个默认的虚拟主机”/“。 8. 几个概念 Borker:简单来说就是消息队列服务器实体,一个broker中可开设多个多个vhost,用作不同用户的权限分离。 Exchange:消息交换机,它指定消息按照什么规则,路由到哪个队列。 Queue:消息队列载体,每个消息都会被投入到一个或多个队列。 Binding:绑定,它的作用是把exchange和Queue按照路由规则绑定起来。 Routing key:路由关键字,exchange根据这个关键字进行消息投递。 vhost:虚拟主机,一个broker可以开设多个vhost,用作不同用户的权限分离 producer:消息生产者,就是投递消息的程序。 consumer:消息消费者,就是接受消息的程序。 channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。 connection:就是一个TCP连接。Producer和Consumer都是通过TCP连接到RabbitMQ Server的。 Channels:虚连接。它建立在上述的TCP连接中。数据流动都是在Channel中进行的。也就是说,一般情况是程序建立TCP连接,第二部就是建立这个Channel。 9. 为什么使用Channel,而不是直接使用TCP连接对于OS来说,建立和关闭连接是由代价的,频繁的建立关闭TCP连接对于系统性能有很大的影响,而且TCP的连接数也有限制,这页限制了系统处理高并发的能力。但是,在TCP连接中建立Channel是没有上述代价的。对于Producer或者Consumer来说,可以并发的使用多个Channel进行Publish或者Receive。有实验表明,1s的数据可以Publish10K的数据包。淡然对于不同的硬件环境,不同的数据包大小这个数据肯定不一样。 10. 消息队列使用过程 客户端连接到消息队列服务器,打开一个Channel。 客户端声明一个excheange,并设置相关属性。 客户点声明一个queue,并设置相关属性。 客户端使用routing key,在exchange和queue之间建立好绑定关系。 客户端投递消息到exchange。 exchange收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息路由投递到一个或多个队列里。 RabbitMQ的优点 基于erlang语言开发具有高可用高并发的优点,适合集群服务器。 健壮、稳定、易用、跨平台、支持多种语言、文档齐全。 有消息确认机制和持久化机制,可靠性高。 开源 其他MQ的优势: Apache ActiveMQ曝光率最高,但是可能会丢消息。 ZeroMQ延迟很低、支持灵活拓扑,但是不支持消息持久化和崩溃恢复。","categories":[{"name":"消息队列","slug":"消息队列","permalink":"http://windynature.oschina.io/categories/消息队列/"}],"tags":[{"name":"RabbitMQ","slug":"RabbitMQ","permalink":"http://windynature.oschina.io/tags/RabbitMQ/"}]},{"title":"类加载机制和反射","slug":"java/类加载机制与反射","date":"2017-02-13T05:35:47.000Z","updated":"2020-03-18T04:03:56.779Z","comments":true,"path":"2017/02/13/java/类加载机制与反射/","link":"","permalink":"http://windynature.oschina.io/2017/02/13/java/类加载机制与反射/","excerpt":"","text":"类的加载、连接和初始化","categories":[{"name":"Java基础","slug":"Java基础","permalink":"http://windynature.oschina.io/categories/Java基础/"}],"tags":[{"name":"反射","slug":"反射","permalink":"http://windynature.oschina.io/tags/反射/"},{"name":"类加载","slug":"类加载","permalink":"http://windynature.oschina.io/tags/类加载/"}]},{"title":"代理模式","slug":"java/proxy","date":"2017-02-12T12:54:32.000Z","updated":"2020-03-18T04:05:05.518Z","comments":true,"path":"2017/02/12/java/proxy/","link":"","permalink":"http://windynature.oschina.io/2017/02/12/java/proxy/","excerpt":"","text":"代理示例来源来自大话设计模式中的一个例子。替别人做嫁衣卓贾衣喜欢娇娇,让同学戴励去给娇娇送花,然而戴励和娇娇日久生情,他们俩那个好上了。 代码被追求者SchoolGirl: public class SchoolGirl { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }} 代理接口: public interface GiveGift { void giveDolls(); void giveFlowers(); void giveChocolate();} 追求者Pursuit: public class Pursuit implements GiveGift { SchoolGirl mm; public Pursuit(SchoolGirl mm) { this.mm = mm; } @Override public void giveDolls() { System.out.println(mm.getName() + \" 送你洋娃娃\"); } @Override public void giveFlowers() { System.out.println(mm.getName() + \" 送你花\"); } @Override public void giveChocolate() { System.out.println(mm.getName() + \" 送你巧克力\"); }} 代理类: public class Proxy implements GiveGift { Pursuit gg; public Proxy(SchoolGirl mm) { gg = new Pursuit(mm); } @Override public void giveDolls() { gg.giveDolls(); } @Override public void giveFlowers() { gg.giveFlowers(); } @Override public void giveChocolate() { gg.giveChocolate(); }} 测试类: public class ProxyTest { public static void main(String[] args) { SchoolGirl jiaojiao = new SchoolGirl(); jiaojiao.setName(\"李娇娇\"); Proxy dali = new Proxy(jiaojiao); dali.giveDolls(); dali.giveFlowers(); dali.giveChocolate(); }} 代理模式代理模式:为其他对象提供一种代理以控制对这个对象的访问。 Subject类,定义了RealSubject和Proxy的公用接口,这样在任何时候使用RealSubject的地方都可以使用Proxy。 abstract class Subject { public abstract void request();} RealSubject类,定义Proxy所代表的真实实体。 public class RealSubject extends Subject { @Override public void request() { System.out.println(\"真实的请求\"); }} Prox类,保存一个引用是的代理可以访问实体,并提供一个与Subject的接口相同的接口,这样代理就可以用来代替实体。 public class Proxy extends Subject { RealSubject realSubject; @Override public void request() { if(realSubject == null) { realSubject = new RealSubject(); } realSubject.request(); }} 客户端代码 public static void main(String[] args) { Proxy proxy = new Proxy(); proxy.Request();} 代理模式的应用远程代理也就是为一个对象在不同的地址空间提供具体代表。这样可以隐藏一个对象存在不同地址空间的事实。 例如:在C#项目中,在应用程序的项目中加入一个Web引用,引用一个WebService,此时会在项目中生成一个WebReference的文件夹和一些文件,其实它们就是代理,这就使得客户端程序调用代理就可以解决远程访问的问题。(远程方法调用) 虚拟代理是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化。 例如:打开一个一大的HTML网页时,里面可能有喝多的文字和图片,但你还是可以很快打开它,此时你所看到的是所有的文字,但图片是一张一张地下载后才能看到。那些为打开的图片框,就是通过虚拟代理来提到了真实的图片,此时代理存储了真实的路径和尺寸。 安全代理用来控制真实对象访问时的权限。一样用于对象应该有不同的访问权限的时候。 智能指引调用真实对象时,代理处理另外一些事情。 例如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它;或当第一次引用一个持久对象时,将它装入内存;或在访问一个实际对象钱,检查是否已经锁定它,以确保其他对象不能改变它。它们都是通过代理在访问一个对象时附加一些内务处理。 与其他相关模式适配器模式Adapter适配器Adapter 为它所适配的对象提供了一个不同的接口。相反,代理提供了与它的实体相同的接口。然而,用于访问保护的代理可能会拒绝执行实体会执行的操作,因此,它的接口实际上可能只是实体接口的一个子集。 装饰器模式Decorator尽管Decorator的实现部分与代理相似,但Decorator的目的不一样。Decorator为对象添加一个或多个功能,而代理则控制对对象的访问。 总结代理模式在很多情况下都非常有用,特别是你想强行控制一个对象的时候,比如:延迟加载,监视状态变更的方法等等 “增加一层间接层”是软件系统中对许多负责问题的一种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的proxy对象便是解决这一问题的常用手段。 具体proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象作细粒度的控制,有些可能对组件模块提供抽象代理层,在架构层次对对象作proxy。 proxy并不一定要求保持接口的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。例如上面的那个例","categories":[{"name":"设计模式","slug":"设计模式","permalink":"http://windynature.oschina.io/categories/设计模式/"}],"tags":[{"name":"代理模式","slug":"代理模式","permalink":"http://windynature.oschina.io/tags/代理模式/"},{"name":"设计模式","slug":"设计模式","permalink":"http://windynature.oschina.io/tags/设计模式/"}]},{"title":"泛型初解","slug":"java/泛型一","date":"2017-02-11T09:39:57.000Z","updated":"2020-03-18T04:04:03.813Z","comments":true,"path":"2017/02/11/java/泛型一/","link":"","permalink":"http://windynature.oschina.io/2017/02/11/java/泛型一/","excerpt":"","text":"来自疯狂Java讲义第三版,泛型章节 概念泛型:就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。Java5中改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象向时传入类型实参。例如List、ArrayList。 当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。例如,为Apple类定义构造器,其构造器名依然是Apple,而不是Apple。Java7提供了菱形语法,允许省略<>中的类型实参。 深入泛型定义泛型接口、类包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在。 可以为任何类、接口增加泛型声明(并不是只有集合类才可以使用泛型声明,虽然集合类是泛型的重要使用场合)。下面是一个包含泛型声明的类。 public class Apple<T> { //使用T类型形参定义实例变量 private T info; public Apple() {} //使用T类型形参定义构造器 public Apple(T info) { this.info = info; } public T getInfo() { return info; } public void setInfo(T info) { this.info = info; } public static void main(String[] args) { Apple<String> a1 = new Apple<>(\"苹果\"); System.out.println(a1.getInfo()); Apple<Double> a2 = new Apple<>(5.67); System.out.println(a2.getInfo()); }} 从泛型类派生子类 方法中的形参代表变量、常量、表达式等数据,称为形参,或者称为数据形参。定义方法时可以声明数据形参,调用方法(使用方法)时必须为这些数据形参传入实际的数据;与此类似的是,定义类、接口、方法时可以声明类型形参,使用类、接口、方法时应该为类型形参传入实际的类型。 当你车床件了带泛型声明的接口、父类之后,可以为该接口创建实现类,或者从该父类派生子类;需要指出的是: 当使用这些接口、父类时不能再包含类型形参。 错误写法: //定义类A继承Apple类,Apple类不能跟类型形参public class A extends Apple<T> {} 正确写法: //使用Apple类时,为T形参参入String类型public class A extends Apple<String> {} 调用方法时必须为所有的数据形参传入参数值,与调用方法不同的是,使用类、接口时也可以不为类型参数传入实际的类型参数。如以下代码: //传入Apple类时,没有为T形参传入实际的类型参数public class A extends Apple 如果从Apple类派生子类,则在Apple类中所有使用T类型形参的地方都被替换成String类型,即它的子类将会继续继承到String getInfo() 和 void setInfo(String info)两个方法,如果子类需要重写父类的方法,就必须注意这一点。 public class A1 extends Apple<String> { //正确重写了父类的方法,返回值 //与父类Apple<String>的返回值完全相同 public String getInfo() { return \"子类\" + super.getInfo(); } //下面的方法是错误的,重写父类方法时返回值类型不一致 public Object getInfo() { return \"子类\" }} 并不存在泛型类List<String> l1 = new ArrayList<>();List<Integer> L2 = new ArrayList<>();//比较l1和l2的类是否相等,结果为相等System.out.println(l1.getClass() == l2.getClass()); 从上面的例子可以看出,不管泛型的世界类型参数是什么,它们在运行时总有同样的类(class)。 不管为泛型的类型参数传入哪一种类型实参,对于java来说,它们依然别当成同一个类处理,在内存中只占用一块内存空间,因此在静态方法、静态初始化或者静态变量的声明和初始化中不允许使用类型形参。 public class R<T> { //下面代码错误,不能再静态变量声明中使用类型形参 static T info; T age; public void foo(T msg) {} //下面代码错误,不能再静态方法中声明中使用类型形参 public static void bar(T msg) {}} 类型通配符public void test(List c) { for(init i=0; i < c.size(); i++){ System.out.print(c.get(i)); }} 上面的例子中,使用List接口没有传入实际的类型参数,会引起泛型警告。 public void test(List<Object> c) { for(init i=0; i < c.size(); i++){ System.out.print(c.get(i)); }} 上面的例子中,表面上看方法声明没有问题,但是传入实际参数值时可能不是我们所期望的,例如以下调用: List<String> strList = new ArrayList<>();//将strList作为参数来调用前面的test方法test(strList) 编译时会发生以下错误:无法将Test中的test(java.util.List<java.lang.Object>)应用于(java.util.List) 这表明List对象不能被当成List对象使用,也就是说,List类并不是List类的子类。 注意:如果Foo是Bar的子类型(子类或者子接口),而G是具有泛型声明的类或接口,G并不是G的子类型。这一点非常值得注意,因为它与大部分人的习惯认为是不同的。 数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或者接口),那么Foo[]依然是Bar[]的子类型。但G不是G的子类型。 使用类型通配符使用类型通配符可以解决上面的问题。 public void test(List<?> c) { for(int i=0;i < c.size(); i++) { System.out.println(c.get(i)); }} 这个问号(?)被称为通配符,它的元素类型 可以匹配任何类型。但是这种带通配符的List紧表示它是各种泛型List的父类,并不能把元素加入到其中。因为程序无法确定c集合中元素的类型,所以不能向其中添加对象。 设定通配符的上限考虑一个问题,假如有三个形状类,其中Shape是一个抽象父类,该抽象父类有两个子类:Circle和Rectangle。接下来定义一个Canvas类,该画布类可以画数量不定的形状(Shape子类的对象),那应该如何定义这个Canvas类呢? public abstract class Shape { public abstract void draw(Canvas c);} public class Circle extends Shape { //画圆 public void draw(Canvas c) { System.out.println(\"在画布\" + c + \"上画一个圆\"); }} public class Rectangle extends Shape { //画矩形 public void draw(Canvas c) { System.out.println(\"在画布\" + c + \"上画一个矩形\"); }} Canvas实现: public class Canvas{ // public void draw(List<? extends Shape> shapes) { for(Shape s:shapes) { s.draw(this); } }} 设定类型形参的上限public class Apple<T extends Number> { T col; public static void main(String[] args) { Apple<Integer> ai = new Apple<>(); Apple<Double> ad = new Apple<>(); //下面代码将会引起编译异常,下面代码试图把String类型传给T形参 //但String不是Number的子类型,所以引起变异错误。 Apple<String> as = new Apple<>(); }} 多个上限 public class Apple<T extends Number & java.io.Serializable> {} 泛型方法定义泛型方法假设有这样一个方法:该方法负责将一个Object数组的所有元素添加到一个Collection集合中。 static void fromArrayToCollection(Object[] a, Collection<Object> c) { for(Object o : a) { c.add(o); }} 上面方法中的形参c的数据类型是Collection,假如想放入Object[] a中的内容是String类型的,Collection不是Collection的子类型,所以这个方法很有限。 String[] strArr = {\"a\",\"b\"};List<String> strList = new ArrayList<>();//Collection<String>不是Collection<Object>的子类型//编译错误formArrayToCollection(strArr,strList); 泛型方法: static <T> void formArrayToCollection(T[] a, Collection<T> c) { for(T o : a) { c.add(o); }} 泛型方法和类型通配符的区别:大多数时候都可以使用泛型方法来代替类型通配符。 通配符形式 public interface Collection<E> { boolean containsAll(Collection<?> c); boolen addAll(Collection<? extends E> c);} 泛型方法形式 public interface Collection<E> { <T> boolearn containsAll(Collection<T> c); <T extends E> boolean addAll(Collection<T> c);} 上面的方法中使用了泛型形式,这是定义类型形参时设定上限(其中E是Collection接口里定义的类型形参,在接口里E可以当成普通类型使用)。 上面两个方法中类型形参T只使用了一次,类型形参T产生的唯一效果是可以在不同的调用点传入不同的实际类型。对于这种情况,应该使用通配符:通配符就是被设计用来支持灵活的子类化的。 泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的依赖关系,就不应该使用泛型方法。 同时使用泛型方法和通配符:如java的Collections.copy()方法 public class Collections{ public static <T> void copy(List<T> dest, List<? extends T> src) {...}} 上面的copy方法中的dest和src存在明显的依赖关系,从源List复制出来的元素,必须可以“丢进”目标List中,所以源List集合元素的类型只能是目标集合元素的类型的子类型或者它本身。但JDK定义src形参类型时使用的是类型通配符,而不是泛型方法。这是因为:该方法无须向src集合添加元素,也无须修改src集合里的元素,所以可以使用类型通配符,无须使用泛型方法。 如果改写为泛型方法如下: public class Collections{ public static <T , S extends T> copy<List<T> dest, List<S> src> {...}} 这份泛型方法可以替代前面的方法,但是注意上面的类型形参S,它仅使用了一次,其他参数的类型、方法返回值的类型都不依赖于它,那类型形参S就没有存在的必要,即可以使用通配符来代替S。使用通配符比使用泛型方法(在方法签名中显式声明类型形参)更加清晰。 类型通配符与泛型方法(在方法签名中显式声明类型形参)还有一个显著的区别:类型通配符既可以在方法签名中定义类型形参的类型,也可以用于定义变量的类型;但泛型方法中的类型形参必须在对应方法中显式声明。 Java7的“菱形”语法与泛型构造器就像泛型方法允许在方法中声明类型形参一样,Java也允许在构造签名中声明类型形参,这样就产生了所谓的泛型构造器。 作用:在调用构造器时,不仅可以让Java根据数据参数的类型来“推断”类型形参的类型,而且程序员也可以显式地为构造器中的类型形参指定实际的类型。 例子: class Foo { public <T> Foo(T t) { System.out.println(t); }}public class GenericConstructor { public static void main(String[] args) { //泛型构造器中的T参数为String new Foo(\"疯狂Java讲义\"); //泛型构造器中的T参数为Integer new Foo(200); //显式指定泛型构造器的T参数为String //传给Foo构造器的实参也是String对象,完全正确 new <String> Foo(\"疯狂Java讲义\"); //显式指定泛型构造器中的T参数为String //但传给Foo构造器的实参是Double对象,下面代码错误 new <String> Foo(12.3); }} 菱形语法:允许调用构造器后使用一对尖括号来代表泛型信息。但是如果程序显式指定了泛型构造器中声明的类型形参的实际类型,则不可以使用“菱形”语法。 如下: class MyClass<E> { public <T> MyClass(T t) { System.out.println(\"t 参数的值为:\" + t); }}public class GenericDiamondTest { public static void main(Stirng[] args) { //MyClass 声明中的E形参是String类型 //泛型构造器中声明的T形参是Integer类型 MyClass<String> mc1 = new MyClass<>(5); //显式指定泛型构造器申明的T是Integer类型 MyClass<String> mc2 = new <Integer> MyClass<String>(5); //MyClass 类声明中的E形参是String类型 //如果显式指定泛型构造器中声明的T形参是Integer类型 //此时就不能使用“菱形语法”,下面代码是错误的 MyClass<String> mc3 = new <Integer> MyClass<>(5); }} 设定通配符下限假设自己实现一个工具方法:实现将src集合里的元素复制到dest集合里的功能,并返回最后一个被复制的元素,因为dest集合里的所有元素,所以dest集合元素的类型应该是src集合元素类型的父类。为了表示两个参数之间的类型依赖,考虑同时使用通配符、泛型参数来实现该方法。如下: public static <T> void copy(Collections<T> dest,Collection<? extends T> src) { T last = null; for (T ele : src) { last = ele; dest.add(ele); } return last;} 上面的方法看起来没有问题,实际上有一个问题:当遍历src集合的元素时,src元素类型是不确定的(只可以坑定它是T的子类),程序只能用T来笼统地表示各宗src集合 的元素类型。例如如下代码: List<Number> ln = new ArrayList<>();List<Integer> li = new ArrayList<>();//下面的代码引起编译错误Integer last = copy(ln,li); 上面代码中ln的菱形是List,与copy()方法签名的形参类型进行对比即得到T的实际类型是Number,而不是Integer类型—即返回值类型也是Number类型。实际上返回的值一定是Integer类型的,也就是说在复制集合元素的过程中,丢失了src元素的类型。 对于上面的copy()方法,可以这样理解两个集合参数之间的依赖关系:不管集合元素的类型是什么,只要dest集合元素的类型与前者相同或是前者的父类即可。为了表达这种关系,Java允许设定通配符的下限:<? super Type>,这个通配符表示它必须是Type本身,或是Type的父类。 例子: public class MyUtils { // public staic <T> T copy(Collection<? super T> dest, Collection<T> src) { T last = null; For(T ele: src) { last = ele; dest.add(ele); } return last; } public static void main(String[] args) { List<Number> ln = new ArrayList<>(); List<Integer> li = new ArrayList<>(); li.add(5); //此处可准地知道最后一个被复制的元素时Integer类型 //与src集合元素的类型相同 Integer last = copy(ln,li); //① Systme.out.println(ln); }} 使用这种语句,就可以保证程序的①处调用的后推断出最后一个被复制的元素类型是Integer,而不是笼统的Number类型。 JDK中TreeSet有一个构造器也用到了这种设定通配符下限的语法,如下: TreeSet(Comparator<? super E> c) TreeSet会对集合中的元素按自然顺序或定制顺序进行排序。如果需要TreeSet对集合中的所有元素进行定制排序,则要求TreeSet对象有一个与之关联的Comparator对象。上面构造器中的参数c就是定制排序的Comparator对象。Comparator接口也是一个带泛型声明的接口: public interface Comparator<T> { int compare(T fst, T snd);} 通过这种带下限的通配符的语法,可以创建TreeSet对象时灵活地选择合适的Comparator。假定需要创建一个TreeSet集合,并传入一个可以比较String大小的Comparator,这个Comparator既可以是Comparator,也可以是Comparator—只要尖括号里传入的类型是String的父类型(或它本身)即可。例子: public class TreeSetTest { public static void main(String[] args) { //Comparator的实际类型是TreeSet的元素类型的父类,满足要求 TreeSet<String> ts1 = new TreeSet<>(new Comparator<Object>() { @Override public int compare(Object fst, Object snd) { return hashCode() > snd.hashCode() ? 1 : hashCode() < snd.hashCode() ? -1 : 0; } }); ts1.add(\"hello\"); ts1.add(\"wa\"); //Comparator的实际类型是TreeSet的元素的类型,满足要求 TreeSet<String> ts2 = new TreeSet<>(new Comparator<String>() { @Override public int compare(String first, String second) { return first.length() > second.length() ? -1:first.length() < second.length() ? 1 : 0; } }); ts2.add(\"hello\"); ts2.add(\"wa\"); System.out.println(ts1); System.out.println(ts2); }} 通过这种通配符下限的方式来定义TreeSet构造器的参数,就可以将所有可用的Comparator作为参数传入,从而增加了程序的灵活性。TreeMap也有类似的用法。 Java8改进的类型推断两个方面: 可通过调用方法的上下文来推断类型参数的目标类型。 可在方法调用链4中,将推断得到的类型参数传递到最后一个方法。 例子: class MyUtil<E> { public static <Z> MyUtil nil() { return null; } public static <Z> myUtil<Z> cons(Z head, MyUtil<Z> tail) { return null; } E head(){ return null; }}public class InferenceTest { //可以方法赋值的目标参数来推断类型参数为String MyUtil<String> ls = MyUtil.nil(); //① //无须使用下面语句在调用nil()方法时指定类型参数的类型 MyUtil<String> mu = MyUtil.<String>nil(); //② //可调用cons()方法所需的参数类型来推断类型参数为Integer MyUtil.cons(42, MyUtil.nil()); //③ //无须使用下面的语句在调用nil()方法是指定类型参数的类型 MyUtil.cons(42, MyUtil.<Integer>nil()); //④} ①②代码的作用完全相同。。。 擦除和转换在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定实际的类型参数。如果没有这个泛型类指定实际的类型参数。如果没有为这个泛型类型指定实际的类型参数,则该类型参数被称作raw type,默认是声明该类型参数时指定的第一个上限类型。 当把一个具有泛型类型信息的对象赋给一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉。比如一个List类型转换为List,则该List对集合元素的类型检查变成了类型参数的上限(Object)。 例子: class Apple<T extends Number> { T size; public Apple() { } public Apple(T size) { this.size = size; } public void setSize(T size) { this.size = size; } public void getSize() { return this.size; }}public class ErasureTest { public static void mian(String[] args) { Apple<Integer> a = new Apple<>(6); //① //a 的 getSize() 方法返回Integer对象 Integer as = a.getSize(); //把a对象赋给Apple变量,丢失尖括号里的类型信息 Apple b = a; //② //b只知道size的类型是Number Number size1 = b.getSize(); //下面代码引起编译错误 Integer size2 = b.getSize(); //③ }} 程序在①处创建了一个Apple对象,该Apple对象传入了Integer作为类型形参的值,所以调用a的getSize()方法时返回Integer类型的值。在②处,把a赋值给一个不带泛型信息的b变量时,编译器就会丢失a对象的泛型信息,即所有尖括号里的信息都会丢失–因为Apple的类型形参的上限是Number类,所以编译器依然知道b的getSize()方法返回Number类型,但具体是哪个子类就不清楚了。 从逻辑上看,List是List的子类,如果直接把一个List对象赋给一个List 对象应该引起编译错误,但实际上不会。对泛型而言,可以直接把一个List对象赋给一个List对象,编译器仅仅提示“未经检查的转换”。 public class ErasureTest2 { public static void main(String[] args) { List<Integer> li = new ArrayList<>(); li.add(6); li.add(9); List list = li; //丢失类型信息 //下面代码引起“未经检查的转换”警告,编译、运行时完全正常 List<String> ls = list; //① //但只要访问ls里的元素,如下面代码将引起运行时异常 System.out.println(ls.get(0)); }} 上面程序中定义了一个List对象,这个List对象保留了集合元素的类型信息。当把这个List对象赋给一个List类型的list后,编译器就会丢失前者的泛型信息,即丢失list集合元素的类型信息,这是典型的擦除。Java又允许直接把List对象赋给一个List(Type 可以是任何类型)类型的变量,所以程序在 ①处可以编译通过,只是发出“未经检查的转换”警告。但对list变量实际上引用的是List集合,所以当试图把该集合里的元素当成String类型的对象取出时,将引发运行时异常。 下面代码与上面代码的行为完全类似。 public class ErasureTest3 { public static void main(String[] args) { List li = new ArrayList(); li.add(6); li.add(9); System.out.println((Stirng)li.get(0)); }} 程序从li中获取一个元素,并试图强制类型转换为String类型,将一引起运行时异常。 泛型与数组Java泛型有一个很重要的设计原则–如果一段代码在编译时没有提出“[unchecked]未经检查的转换”警告,则程序在运行时不会引发ClassCastException异常。正是基于这个原因,所以数组元素的类型不能包含类型变量或类型形参,除非是无上限的类型通配符。但可以声明元素类型包含类型变量或类型形参的数组。也就是说,只能声明List[]形式的数组,但不能创建ArrayList[10]这样的数组对象。 例如:加入Java支持创建ArrayList[10]这样的数组对象,则有如下程序: //下面代码编译时有“[unchecked]未经检查的转换”警告List<String> lsa = new ArrayList<String>[10]; //①//将lsa向上转型为Object[]lsa;Object[] oa = (Object[])lsa;List<Integer> li = new ArrayList<Integer>();li.add(new Integer(3));//将List<Integer>对象作为oa的第二个元素//下面代码没有任何警告oa[1] = li;//下面代码也不会有任何警告,但将引发ClassCastException异常String s = lsa[1].get(0); //② ①处的代码声明了List[]类型的数组变量,这是允许的;但不允许创建List[]类型的对象,所以创建了一个类型为ArrayList[10]的数组对象,这是允许的。只是把ArrayList[10]对象赋给 List[]变量时会有编译警告“[unchecked]未经检查的转换”,即编译器并不保证这段代码是类型安全的。上面的代码同样会在②处引起运行时异常,但因为编译器已经提出了警告,所以完全可能出现这种异常。 Java允许创建无上限的通配符泛型数组,例如new ArrayList<?>[10],因此也可以将第一段代码该为使用无上限的通配符泛型数组,在这种情况下,程序不得不进行强制类型转换。 List<?>[] lsa = new ArrayList<?>[10];Object[] oa = lsa;List<Ingeger> li = new ArrayList<Integer>();li.add(new Integer(3));oa[1] = li;//下面的代码引发ClassCastException异常String s = (String)lsa[1].get(0); 上面的代码会引起ClassCastException,因为程序需要将ls的第一个数组元素的第一个集合元素强制类型转换为String类型,所以程序应该自己通过instanceof运算符来保证它的数据类型。如: List<?>[] lsa = new ArrayList<?>[10];Object[] oa = lsa;List<Integer> li = new ArrayList<Integer>();oa[1] = li;Object target = lsa[1].get(0);if(target instanceof String) { //下面代码安全了 String s = (String) target;} 与此类似的是,创建元素类型是类型变量的数组对象也将导致编译错误。如: <T> T[] makeArray(Collection<T> coll) { //导致编译错误 return new T[coll.size()];} 由于类型变量在运行时并不存在,而编译器无法确定实际类型是什么,因此会导致编译错误。","categories":[{"name":"泛型","slug":"泛型","permalink":"http://windynature.oschina.io/categories/泛型/"}],"tags":[{"name":"泛型","slug":"泛型","permalink":"http://windynature.oschina.io/tags/泛型/"}]},{"title":"Git远程操作详解","slug":"工具/git远程操作详解","date":"2017-01-26T09:21:28.000Z","updated":"2020-03-18T04:09:22.434Z","comments":true,"path":"2017/01/26/工具/git远程操作详解/","link":"","permalink":"http://windynature.oschina.io/2017/01/26/工具/git远程操作详解/","excerpt":"","text":"原文地址 Git远程操作详解Git有很多又有,其中之一就是远程操作非常简便。本文详细介绍5个Git命令,他们的概念和用法,理解这些内容,你就会完全掌握Git远程操作。 git clone git remote git fetch git pull git push 本文针对初级用户,从最简单的讲起,但是需要读者对Git的基本用法有所了解。同时,本文覆盖了上面5个命令的几乎所有的常用用法,所以对于熟练用户也有参考价值。 一、git clone远程操作的第一步,通常是从远程主机克隆一个版本库,这时就要用到git clone命令。 $ git clone <版本库的网址> 比如,克隆jQuery的版本库。 $ git clone https://github.com/jquery/jquery.git 该命令会在本地主机生成一个目录,与远程主机的版本库同名。如果要指定不同的目录名,可以将目录名作为git clone命令的第二个参数。 $ git clone <版本库的网址> <本地目录名> git clone支持多种协议,除了HTTP(s)以外,还支持SSH、Git、本地文件协议等,下面是一些例子。 $ git clone http[s]://example.com/path/to/repo.git/$ git clone ssh://example.com/path/to/repo.git/$ git clone git://example.com/path/to/repo.git/$ git clone /opt/git/project.git $ git clone file:///opt/git/project.git$ git clone ftp[s]://example.com/path/to/repo.git/$ git clone rsync://example.com/path/to/repo.git/ SSH协议还有另一种写法。 $ git clone [user@]example.com:path/to/repo.git/ 通常来说,Git协议下载速度最快,SSH协议用于需要用户认证的场合。各种协议优劣的详细讨论请参考官方文档。 二、git remote为了便于管理,Git要求每个远程主机都必须制定一个主机名。git remote命名就用于管理主机名。不带选项的时候,git remote命令列出所有远程主机。 $ git remoteorigin 使用-v选项,可以查看远程主机的网址。 $ git remote -vorigin git@github.com:jquery/jquery.git (fetch)origin git@github.com:jquery/jquery.git (push) 上面命令表示,当前只有一台远程主机,叫做origin,以及它的网址。克隆版本库的时候,所使用的远程主机自动被Git命名为origin。如果想使用其他的主机名,需要使用git clone命令的-o选项指定。 $ git clone -o jQuery https://github.com/jquery/jquery.git$ git remotejQuery 上面命令表示,克隆的时候,指定远程主机叫做Jquery.git remote show命令加上主机名,可以查看该主机的详细信息。 $ git remote show <主机名> git remote add命令用户添加远程主机。 $ git remote add <主机名> <网址> git remote rm命令用于删除远程主机。 $ git remote rm <主机名> git remote rename命令用于远程主机的改名。 $ git remote rename <原主机名> <新主机名> 三、git fetch一旦远程主机的版本库更新(git 属于叫做commit),需要将这些更新取回本地,这时候就需要用到git fetch命令。 $ git fetch <远程主机名> 上面命令将某个远程主机的更新,全部取回本地。git fetch命令通常用来查看其他人的进程,因为它取回的代码对你本地的开发代码没有影响。默认情况下,git fetch取回所有分支(branch)的更新。如果只想取回特定分支的更新,可以指定分支名。 $ git fetch <远程主机名> <分支名> 比如取回origin主机的master分支。 $ git fetch origin master 所取回的更新,在本地主机上要用"远程主机名/分支名"的形式读取。比如origin主机的master,就要用origin/master读取。 git branch命令的-r选项,可以用来查看远程分支,-a选项查看所有分支。 $ git branch -rorigin/master$ git branch -a* master remotes/origin/master 上面的命令表示,本地主机的当前分支是master,远程分支是origin/master。取回远程主机的更新以后,可以在它的基础上,使用git checkout命令创建一个新的分支。 $ git checkout -b newBranch origin/master 上面命令表示,在origin/master的基础上,创建一个新分支。此外,也可以使用git merge命令或者git rebase命令,在本地分支上合并远程分支。 $ git merge origin/master 或者$ git rebase origin/master 上面命令表示在当前分支上,合并origin/master。 四、git pullgit pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂。 $ git pull <远程主机名> <远程分支名>:<本地分支名> 比如取回origin的next分支,与本地的master合并,需要协整下面这样。 $ git pull origin next:master 如果远程分支是与当前分支合并,则冒号后面的部分可以省略。 $git pull origin next 上面命令表示,取回origin/next分支,再与当前分支合并。实质上,这等同于先做git fetch,再做git merge $ git fetch origin$ git merge origin/next 在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动”追踪”origin/master分支。Git也允许手动建立追踪关系。 $ git branch --set-upstream master origin/next 上面命令指定master分支追踪origin/next分支。如果当前分支与远程分支存在追踪关系,git pull 就可以省略远程分支名。 $ git pull origin 上面命令表示,本地的当前分支自动与对应的”origin”主机”追踪分支”(remote-tracking branch)进行合并。如果当前分支只有一个最终分支,连接远程主机名都可以省略。 $ git pull 上面的命令表示,当前分支自动与唯一一个追踪分支进行合并。如果合并需要采用rebase模式,可以使用--rabase选项。 $ git pull --rebase <远程主机名> <远程分支名>:<本地分支名> 如果远程主机删除了某个分支,在默认情况下,git pull不会再拉去远程分支的时候,删除对应的本地分支。只是为了防止,由于其他人操作了远程主机,导致git pull不知不觉删除了本地分支。但是,你可以改变这个行为,加上参数-p就会在本地删除远程已经删除的分支。 $ git pull -p等同于下面的命令$ get fetch --prune origin$ git fetch -p 五、git pushgit push命令用于将本地分支的更新,推送到远程主机。它的格式与git pull命令相仿。 git push <远程主机名> <本地分支名>:<远程分支名> 注意,分支推送顺序的写法是<来源地>:目的地,所以git pull是<远程分支>:<本地分支>,而git push是<本地分支>:<远程分支>。如果省略远程分支名,则表示本地分支推送与之存在”追踪”关系的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。 $ git push origin master 上面命令表示,将本地的master分支推送到origin主机的master分支。如果后者不存在,则会被新建。如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。 $ git push origin :master等同于$ git push origin --delete master 上面命令表示删除origin主机的master分支。如果当前分支只有一个追踪分支,那么主机名都可以省略。 $ git push 如果当前分支与多个主机存在追踪关系,则可以使用-u选项指定一个默认主机这样后面就可以不加任何参数使用git push。 $ git push -u origin master 上面命令将本地的master分支推送到远程origin主机,同时指定origin为默认主机,后面就可以不加任何参数使用git push了。 不带任何参数的git push,默认值推送当前分支,这叫做simple方式。此外还有一种matching方式,会推送到所有对应的远程分支的本地分支。Git2.0版本之前,默认采用matching方法,现在改为默认采用simple方式。如果要修改这个设置,可以采用git config命令。 $ git config --global push.default matching或者$ git config --global push.default simple 还有一种情况,就是不管是否存在对应的远程分支,将本地的所有分支都推送到远程主机,只是需要使用--all选项。 $ git push --all origin 上面命令表示,将所有本地分支都推送到origin主机。如果远程主机版本比本地版本更新,推送时Git会报错,要求现在本地做git pull合并差异,然后再推送到远程主机。这是如果你一定要推送,可以使用--force选项。 $ git push --all origin 上面命令使用--force选项,如果导致远程主机上更新的版本被覆盖。除非你很确定要这样做,否则应该尽量避免使用--force选项。最后,git push不会推送标签(tag),除非使用–tags选项。 $ git push origin --tags","categories":[{"name":"版本管理","slug":"版本管理","permalink":"http://windynature.oschina.io/categories/版本管理/"}],"tags":[{"name":"git","slug":"git","permalink":"http://windynature.oschina.io/tags/git/"}]},{"title":"深入理解Java注解","slug":"java/深入理解Java注解","date":"2017-01-26T08:07:00.000Z","updated":"2020-03-18T04:05:43.831Z","comments":true,"path":"2017/01/26/java/深入理解Java注解/","link":"","permalink":"http://windynature.oschina.io/2017/01/26/java/深入理解Java注解/","excerpt":"","text":"深入理解Java注解1. 什么是注解定义1:注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。(java编程思想) 定义2:Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。 Annotation(注解)是JDK5.0及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。从某些方面看,annotation就像修饰符一样被使用,并应用于包、类 型、构造方法、方法、成员变量、参数、本地变量的声明中。这些信息被存储在Annotation的“name=value”结构对中。 Annotation的成员在Annotation类型中以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。在此有一个特定的默认语法:允许声明任何Annotation成员的默认值:一个Annotation可以将name=value对作为没有定义默认值的Annotation成员的值,当然也可以使用name=value对来覆盖其它成员默认值。这一点有些近似类的继承特性,父类的构造函数可以作为子类的默认构造函数,但是也可以被子类覆盖。 Annotation能被用来为某个程序元素(类、方法、成员变量等)关联任何的信息。需要注意的是,这里存在着一个基本的规则:Annotation不能影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。另外,尽管一些annotation通过java的反射api方法在运行时被访问,而java语言解释器在工作时忽略了这些annotation。正是由于java虚拟机忽略了Annotation,导致了annotation类型在代码中是“不起作用”的; 只有通过某种配套的工具才会对annotation类型中的信息进行访问和处理。本文中将涵盖标准的Annotation和meta-annotation类型,陪伴这些annotation类型的工具是java编译器(当然要以某种特殊的方式处理它们)。 2. 什么是元数据元数据从metadata一词译来,就是“关于数据的数据”的意思。 元数据的功能作用有很多,比如:你可能用过Javadoc的注释自动生成文档。这就是元数据功能的一种。总的来说,元数据可以用来创建文档,跟踪代码的依赖性,执行编译时格式检查,代替已有的配置文件。如果要对于元数据的作用进行分类,目前还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类: 1. 编写文档:通过代码里标识的元数据生成文档 2. 代码分析:通过代码里标识的元数据对代码进行分析 3. 编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查 在Java中元数据以标签的形式存在于Java代码中,元数据标签的存在并不影响程序代码的编译和执行,它只是被用来生成其它的文件或针在运行时知道被运行代码的描述信息。 综上所述: 第一,元数据以标签的形式存在于Java代码中。 第二,元数据描述的信息是类型安全的,即元数据内部的字段都是有明确类型的。 第三,元数据需要编译器之外的工具额外的处理用来生成其它的程序部件。 第四,元数据可以只存在于Java源代码级别,也可以存在于编译之后的Class文件内部。 3. Java内置的注解 名称 含义 @Override 表示当前的方法定义将覆盖父类中的方法,如果不小心拼写错误,或者方法签名对不上覆盖的方法,编译器就会发出错误提示。 @Deprecated 用于表示某个程序元素(类、方法等)已过时,当程序使用已过时类、方法时,编译器会给出警告。 @SuppressWarnings 抑制编译器警告,指被修饰的元素(及该程序元素中的所有子元素)取消显示指定的编译器警告 4. JDK的元Annotation元注解的作用激素是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们用来对其他annotation类型作说明。 @Target @Retention @Documented @Inherited 4.1 @Target@Target说明了annotation所修饰的对象范围,annotation可被用于packages、types(类、接口、枚举、annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catche参数)。在annotation类型的生命中使用了target可更加明晰其修饰的目标。作用:用于描述注解的使用范围使用实例: @Target(ElementType.TYPE)public @interface Table { /** * 数据表名称注解,默认值为类名称 * @return */ public String tableName() default \"className\";}@Target(ElementType.FIELD)public @interface NoDBColumn {} 注解Table可以用于注解类、接口(包括注解类型)或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。其中ElementType为一个枚举类型,取值如下 CONSTRUCTOR:用于描述构造器 FIELD:用于描述域 LOCAL_VARIABLE:用于描述局部变量 METHOD:用于描述方法 PACKAGE:用于描述包 PARAMETER:用于描述参数 TYPE:用于描述类、接口(包括注解类型) 或enum声明 ANNOTATION_TYPE:用于描述注解类型 4.2 @Retention@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。取值(RetentionPoicy)有: SOURCE:在源文件中有效(即源文件保留) CLASS:在class文件中有效(即class保留) RUNTIME:在运行时有效(即运行时保留) 使用实例: @Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Column { public String name() default \"fieldName\"; public String setFuncName() default \"setField\"; public String getFuncName() default \"getField\"; public boolean defaultDBValue() default false;} Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理。 4.3 @Documented@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。 @Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Column { public String name() default \"fieldName\"; public String setFuncName() default \"setField\"; public String getFuncName() default \"getField\"; public boolean defaultDBValue() default false;} 4.4 @Inherited @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。 注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。 当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。 @Inheritedpublic @interface Greeting { public enum FontColor{ BULE,RED,GREEN}; String name(); FontColor fontColor() default FontColor.GREEN;} 5. 自定义注解使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。 5.1 定义注解格式:public @interface 注解名 {定义体} 5.2 注解参数的可支持数据类型: 所有基本数据类型(int,float,boolean,byte,double,char,long,short) String类型 Class类型 enum类型 Annotation类型 以上所有类型的数组 Annotation类型里面的参数该怎么设定: 第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型; 第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String; 第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.例:下面的例子FruitName注解就只有一个参数成员。 5.3 简单的自定义注解和使用注解实例:package annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 水果名称注解 */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface FruitName { String value() default \"\";} package annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 水果颜色注解 */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface FruitColor { /** * 颜色枚举 * @author peida * */ public enum Color{ BULE,RED,GREEN}; /** * 颜色属性 * @return */ Color fruitColor() default Color.GREEN;} package annotation;import annotation.FruitColor.Color;public class Apple { @FruitName(\"Apple\") private String appleName; @FruitColor(fruitColor=Color.RED) private String appleColor; public void setAppleColor(String appleColor) { this.appleColor = appleColor; } public String getAppleColor() { return appleColor; } public void setAppleName(String appleName) { this.appleName = appleName; } public String getAppleName() { return appleName; } public void displayName(){ System.out.println(\"水果的名字是:苹果\"); }} 5.4 注解元素的默认值: 注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。例如: package annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 水果供应者注解 * @author peida */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface FruitProvider { /** * 供应商编号 * @return */ public int id() default -1; /** * 供应商名称 * @return */ public String name() default \"\"; /** * 供应商地址 * @return */ public String address() default \"\";} 定义了注解,并在需要的时候给相关类,类属性加上注解信息,如果没有响应的注解信息处理流程,注解可以说是没有实用价值。如何让注解真真的发挥作用,主要就在于注解处理方法,下一步我们将学习注解信息的获取和处理! 6. 注解处理器如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。 6.1 注解处理器类库(java.lang.reflect.AnnotatedElement)Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类: Class:类定义 Constructor:构造器定义 Field:累的成员变量定义 Method:类的方法定义 Package:类的包定义 java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。 AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息: 方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。 方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false. 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。 一个简单的注解处理器: //注解处理器public class FruitInfoUtil { public static void getFruitInfo(Class<?> clazz){ String strFruitName=\" 水果名称:\"; String strFruitColor=\" 水果颜色:\"; String strFruitProvicer=\"供应商信息:\"; Field[] fields = clazz.getDeclaredFields(); for(Field field :fields){ if(field.isAnnotationPresent(FruitName.class)){ FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class); strFruitName=strFruitName+fruitName.value(); System.out.println(strFruitName); } else if(field.isAnnotationPresent(FruitColor.class)){ FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class); strFruitColor=strFruitColor+fruitColor.fruitColor().toString(); System.out.println(strFruitColor); } else if(field.isAnnotationPresent(FruitProvider.class)){ FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class); strFruitProvicer=\" 供应商编号:\"+fruitProvider.id()+\" 供应商名称:\"+fruitProvider.name()+\" 供应商地址:\"+fruitProvider.address(); System.out.println(strFruitProvicer); } } }}/***********输出结果***************/public class FruitRun { public static void main(String[] args) { FruitInfoUtil.getFruitInfo(Apple.class); }} 【3,4节出处:】http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html","categories":[{"name":"Java基础","slug":"Java基础","permalink":"http://windynature.oschina.io/categories/Java基础/"}],"tags":[{"name":"Annotation","slug":"Annotation","permalink":"http://windynature.oschina.io/tags/Annotation/"}]},{"title":"rabbitMQ 用户管理","slug":"java/一、用户管理","date":"2017-01-26T05:19:30.000Z","updated":"2020-03-18T04:07:37.870Z","comments":true,"path":"2017/01/26/java/一、用户管理/","link":"","permalink":"http://windynature.oschina.io/2017/01/26/java/一、用户管理/","excerpt":"","text":"rabbitMQ用户管理出于安全因素的考虑,guest用户只能通过localhost登陆使用,并建议修改guest用户的密码以及新建其他账号管理使用rabbitmq(该功能是在3.3.0版本引入的)。通过brew安装的地址:/usr/local/Cellar/rabbitmq/3.6.4/sbin ##1.用户管理用户管理包括增加用户、删除用户,插卡用户列表,修改用户密码。相应的命令 (1)新增一个用户rabbitmqctl add_user Username Password (2)删除一个用户rabbitmqctl delete_user Username (3)修改用户的密码rabbitmqctl change_password Username NewPassword (4)查看当前用户列表rabbitmyctl list_users 2.用户角色按照个人理解,用户角色可分为五大类,超级管理员、监控者、策略指定值、普通管理者以及其他。 (1)超级管理员administrator可登陆管理控制台(启用management plugin的情况下),可查看所有的信息,并且可以对用户,策略(policy)进行操作。 (2)监控者monitoring可登陆管理控制台(启用management plugin的情况下),同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等) (3)策略指定值policymaker可登陆管理控制台(启用management plugin的情况下),同事可以对policy进行管理。但无法查看节点的相关信息 (4)普通管理者management仅可以登陆管理控制台(启用magagement plugin的情况下),无法看到节点信息,也无法对策略进行管理。 (5)其他无法登陆管理控制台,通常就是普通的生产者和消费者了解这些后,就尅根据需要给不同的用户设置不同的角色,以便按需管理。设置用户角色的命令为:rabbitmqctl set_user_tags User TagUser:用户名,Tag:角色名也可以给用一个用户设置多个角色,例如:rabbitmqctl set_user_tags hncscwc monitoring policymaker 3.用户权限用户权限指的是用户对exchange,queue的操作权限,包括配置权限,读写权限。配置权限会影响到exchange,queue的声明和删除。读写权限影响到从queue里取消息,向exchange发送消息以及queue和exchange的绑定(bind)操作。 例如: 将queue绑定到某exchange上,需要具有queue的可写权限,以及exchange的可读权限;向exchange发送消息需要具有exchange的可写权限;从queue里取数据需要具有queue的可读权限。详细请参考官方文档中”How permissions work”部分。 相关命令为: (1)设置用户权限rabbitmqctl set_permissions -p VHostPath User ConfP WriteP ReadP (2)查看(指定hostpath)所有用户的权限信息rabbitmqctl list_permissions [-p VHostPath] (3)查看指定用户的权限信息rabbitmqctl list_usesr_permissions User(4)清楚用户的权限信息rabbitmqctl clear_permissions [-p VHostPath] User 命令详细参考官方文档:http://www.rabbitmq.com/man/rabbitmqctl.1.man.html","categories":[{"name":"消息队列","slug":"消息队列","permalink":"http://windynature.oschina.io/categories/消息队列/"}],"tags":[{"name":"RabbitMQ","slug":"RabbitMQ","permalink":"http://windynature.oschina.io/tags/RabbitMQ/"}]},{"title":"Maven pom.xml文件详解","slug":"java/Maven-Pom.xml文件详解","date":"2017-01-26T04:01:22.000Z","updated":"2020-03-18T04:07:20.524Z","comments":true,"path":"2017/01/26/java/Maven-Pom.xml文件详解/","link":"","permalink":"http://windynature.oschina.io/2017/01/26/java/Maven-Pom.xml文件详解/","excerpt":"","text":"Maven pom.xml文件详解 <project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4_0_0.xsd\"> <!--父项目的坐标。如果项目中没有规定某个元素的值,那么父项目中的对应值即为项目的默认值。 坐标包括group ID,artifact ID和 version。--> <parent> <!--被继承的父项目的构件标识符--> <artifactId/> <!--被继承的父项目的全球唯一标识符--> <groupId/> <!--被继承的父项目的版本--> <version/> <!-- 父项目的pom.xml文件的相对路径。相对路径允许你选择一个不同的路径。默认值是../pom.xml。Maven首先在构建当前项目的地方寻找父项 目的pom,其次在文件系统的这个位置(relativePath位置),然后在本地仓库,最后在远程仓库寻找父项目的pom。--> <relativePath/> </parent> <!--声明项目描述符遵循哪一个POM模型版本。模型本身的版本很少改变,虽然如此,但它仍然是必不可少的,这是为了当Maven引入了新的特性或者其他模型变更的时候,确保稳定性。--> <modelVersion>4.0.0</modelVersion> <!--项目的全球唯一标识符,通常使用全限定的包名区分该项目和其他项目。并且构建时生成的路径也是由此生成, 如com.mycompany.app生成的相对路径为:/com/mycompany/app--> <groupId>asia.banseon</groupId> <!-- 构件的标识符,它和group ID一起唯一标识一个构件。换句话说,你不能有两个不同的项目拥有同样的artifact ID和groupID;在某个 特定的group ID下,artifact ID也必须是唯一的。构件是项目产生的或使用的一个东西,Maven为项目产生的构件包括:JARs,源 码,二进制发布和WARs等。--> <artifactId>banseon-maven2</artifactId> <!--项目产生的构件类型,例如jar、war、ear、pom。插件可以创建他们自己的构件类型,所以前面列的不是全部构件类型--> <packaging>jar</packaging> <!--项目当前版本,格式为:主版本.次版本.增量版本-限定版本号--> <version>1.0-SNAPSHOT</version> <!--项目的名称, Maven产生的文档用--> <name>banseon-maven</name> <!--项目主页的URL, Maven产生的文档用--> <url>http://www.baidu.com/banseon</url> <!-- 项目的详细描述, Maven 产生的文档用。 当这个元素能够用HTML格式描述时(例如,CDATA中的文本会被解析器忽略,就可以包含HTML标 签), 不鼓励使用纯文本描述。如果你需要修改产生的web站点的索引页面,你应该修改你自己的索引页文件,而不是调整这里的文档。--> <description>A maven project to study maven.</description> <!--描述了这个项目构建环境中的前提条件。--> <prerequisites> <!--构建该项目或使用该插件所需要的Maven的最低版本--> <maven/> </prerequisites> <!--项目的问题管理系统(Bugzilla, Jira, Scarab,或任何你喜欢的问题管理系统)的名称和URL,本例为 jira--> <issueManagement> <!--问题管理系统(例如jira)的名字,--> <system>jira</system> <!--该项目使用的问题管理系统的URL--> <url>http://jira.baidu.com/banseon</url> </issueManagement> <!--项目持续集成信息--> <ciManagement> <!--持续集成系统的名字,例如continuum--> <system/> <!--该项目使用的持续集成系统的URL(如果持续集成系统有web接口的话)。--> <url/> <!--构建完成时,需要通知的开发者/用户的配置项。包括被通知者信息和通知条件(错误,失败,成功,警告)--> <notifiers> <!--配置一种方式,当构建中断时,以该方式通知用户/开发者--> <notifier> <!--传送通知的途径--> <type/> <!--发生错误时是否通知--> <sendOnError/> <!--构建失败时是否通知--> <sendOnFailure/> <!--构建成功时是否通知--> <sendOnSuccess/> <!--发生警告时是否通知--> <sendOnWarning/> <!--不赞成使用。通知发送到哪里--> <address/> <!--扩展配置项--> <configuration/> </notifier> </notifiers> </ciManagement> <!--项目创建年份,4位数字。当产生版权信息时需要使用这个值。--> <inceptionYear/> <!--项目相关邮件列表信息--> <mailingLists> <!--该元素描述了项目相关的所有邮件列表。自动产生的网站引用这些信息。--> <mailingList> <!--邮件的名称--> <name>Demo</name> <!--发送邮件的地址或链接,如果是邮件地址,创建文档时,mailto: 链接会被自动创建--> <post>banseon@126.com</post> <!--订阅邮件的地址或链接,如果是邮件地址,创建文档时,mailto: 链接会被自动创建--> <subscribe>banseon@126.com</subscribe> <!--取消订阅邮件的地址或链接,如果是邮件地址,创建文档时,mailto: 链接会被自动创建--> <unsubscribe>banseon@126.com</unsubscribe> <!--你可以浏览邮件信息的URL--> <archive>http:/hi.baidu.com/banseon/demo/dev/</archive> </mailingList> </mailingLists> <!--项目开发者列表--> <developers> <!--某个项目开发者的信息--> <developer> <!--SCM里项目开发者的唯一标识符--> <id>HELLO WORLD</id> <!--项目开发者的全名--> <name>banseon</name> <!--项目开发者的email--> <email>banseon@126.com</email> <!--项目开发者的主页的URL--> <url/> <!--项目开发者在项目中扮演的角色,角色元素描述了各种角色--> <roles> <role>Project Manager</role> <role>Architect</role> </roles> <!--项目开发者所属组织--> <organization>demo</organization> <!--项目开发者所属组织的URL--> <organizationUrl>http://hi.baidu.com/banseon</organizationUrl> <!--项目开发者属性,如即时消息如何处理等--> <properties> <dept>No</dept> </properties> <!--项目开发者所在时区, -11到12范围内的整数。--> <timezone>-5</timezone> </developer> </developers> <!--项目的其他贡献者列表--> <contributors> <!--项目的其他贡献者。参见developers/developer元素--> <contributor> <name/><email/><url/><organization/><organizationUrl/><roles/><timezone/><properties/> </contributor> </contributors> <!--该元素描述了项目所有License列表。 应该只列出该项目的license列表,不要列出依赖项目的 license列表。如果列出多个license,用户可以选择它们中的一个而不是接受所有license。--> <licenses> <!--描述了项目的license,用于生成项目的web站点的license页面,其他一些报表和validation也会用到该元素。--> <license> <!--license用于法律上的名称--> <name>Apache 2</name> <!--官方的license正文页面的URL--> <url>http://www.baidu.com/banseon/LICENSE-2.0.txt</url> <!--项目分发的主要方式: repo,可以从Maven库下载 manual, 用户必须手动下载和安装依赖--> <distribution>repo</distribution> <!--关于license的补充信息--> <comments>A business-friendly OSS license</comments> </license> </licenses> <!--SCM(Source Control Management)标签允许你配置你的代码库,供Maven web站点和其它插件使用。--> <scm> <!--SCM的URL,该URL描述了版本库和如何连接到版本库。欲知详情,请看SCMs提供的URL格式和列表。该连接只读。--> <connection> scm:svn:http://svn.baidu.com/banseon/maven/banseon/banseon-maven2-trunk(dao-trunk) </connection> <!--给开发者使用的,类似connection元素。即该连接不仅仅只读--> <developerConnection> scm:svn:http://svn.baidu.com/banseon/maven/banseon/dao-trunk </developerConnection> <!--当前代码的标签,在开发阶段默认为HEAD--> <tag/> <!--指向项目的可浏览SCM库(例如ViewVC或者Fisheye)的URL。--> <url>http://svn.baidu.com/banseon</url> </scm> <!--描述项目所属组织的各种属性。Maven产生的文档用--> <organization> <!--组织的全名--> <name>demo</name> <!--组织主页的URL--> <url>http://www.baidu.com/banseon</url> </organization> <!--构建项目需要的信息--> <build> <!--该元素设置了项目源码目录,当构建项目的时候,构建系统会编译目录里的源码。该路径是相对于pom.xml的相对路径。--> <sourceDirectory/> <!--该元素设置了项目脚本源码目录,该目录和源码目录不同:绝大多数情况下,该目录下的内容 会被拷贝到输出目录(因为脚本是被解释的,而不是被编译的)。--> <scriptSourceDirectory/> <!--该元素设置了项目单元测试使用的源码目录,当测试项目的时候,构建系统会编译目录里的源码。该路径是相对于pom.xml的相对路径。--> <testSourceDirectory/> <!--被编译过的应用程序class文件存放的目录。--> <outputDirectory/> <!--被编译过的测试class文件存放的目录。--> <testOutputDirectory/> <!--使用来自该项目的一系列构建扩展--> <extensions> <!--描述使用到的构建扩展。--> <extension> <!--构建扩展的groupId--> <groupId/> <!--构建扩展的artifactId--> <artifactId/> <!--构建扩展的版本--> <version/> </extension> </extensions> <!--当项目没有规定目标(Maven2 叫做阶段)时的默认值--> <defaultGoal/> <!--这个元素描述了项目相关的所有资源路径列表,例如和项目相关的属性文件,这些资源被包含在最终的打包文件里。--> <resources> <!--这个元素描述了项目相关或测试相关的所有资源路径--> <resource> <!-- 描述了资源的目标路径。该路径相对target/classes目录(例如${project.build.outputDirectory})。举个例 子,如果你想资源在特定的包里(org.apache.maven.messages),你就必须该元素设置为org/apache/maven /messages。然而,如果你只是想把资源放到源码目录结构里,就不需要该配置。--> <targetPath/> <!--是否使用参数值代替参数名。参数值取自properties元素或者文件里配置的属性,文件在filters元素里列出。--> <filtering/> <!--描述存放资源的目录,该路径相对POM路径--> <directory/> <!--包含的模式列表,例如**/*.xml.--> <includes/> <!--排除的模式列表,例如**/*.xml--> <excludes/> </resource> </resources> <!--这个元素描述了单元测试相关的所有资源路径,例如和单元测试相关的属性文件。--> <testResources> <!--这个元素描述了测试相关的所有资源路径,参见build/resources/resource元素的说明--> <testResource> <targetPath/><filtering/><directory/><includes/><excludes/> </testResource> </testResources> <!--构建产生的所有文件存放的目录--> <directory/> <!--产生的构件的文件名,默认值是${artifactId}-${version}。--> <finalName/> <!--当filtering开关打开时,使用到的过滤器属性文件列表--> <filters/> <!--子项目可以引用的默认插件信息。该插件配置项直到被引用时才会被解析或绑定到生命周期。给定插件的任何本地配置都会覆盖这里的配置--> <pluginManagement> <!--使用的插件列表 。--> <plugins> <!--plugin元素包含描述插件所需要的信息。--> <plugin> <!--插件在仓库里的group ID--> <groupId/> <!--插件在仓库里的artifact ID--> <artifactId/> <!--被使用的插件的版本(或版本范围)--> <version/> <!--是否从该插件下载Maven扩展(例如打包和类型处理器),由于性能原因,只有在真需要下载时,该元素才被设置成enabled。--> <extensions/> <!--在构建生命周期中执行一组目标的配置。每个目标可能有不同的配置。--> <executions> <!--execution元素包含了插件执行需要的信息--> <execution> <!--执行目标的标识符,用于标识构建过程中的目标,或者匹配继承过程中需要合并的执行目标--> <id/> <!--绑定了目标的构建生命周期阶段,如果省略,目标会被绑定到源数据里配置的默认阶段--> <phase/> <!--配置的执行目标--> <goals/> <!--配置是否被传播到子POM--> <inherited/> <!--作为DOM对象的配置--> <configuration/> </execution> </executions> <!--项目引入插件所需要的额外依赖--> <dependencies> <!--参见dependencies/dependency元素--> <dependency> ...... </dependency> </dependencies> <!--任何配置是否被传播到子项目--> <inherited/> <!--作为DOM对象的配置--> <configuration/> </plugin> </plugins> </pluginManagement> <!--使用的插件列表--> <plugins> <!--参见build/pluginManagement/plugins/plugin元素--> <plugin> <groupId/><artifactId/><version/><extensions/> <executions> <execution> <id/><phase/><goals/><inherited/><configuration/> </execution> </executions> <dependencies> <!--参见dependencies/dependency元素--> <dependency> ...... </dependency> </dependencies> <goals/><inherited/><configuration/> </plugin> </plugins> </build> <!--在列的项目构建profile,如果被激活,会修改构建处理--> <profiles> <!--根据环境参数或命令行参数激活某个构建处理--> <profile> <!--构建配置的唯一标识符。即用于命令行激活,也用于在继承时合并具有相同标识符的profile。--> <id/> <!--自动触发profile的条件逻辑。Activation是profile的开启钥匙。profile的力量来自于它 能够在某些特定的环境中自动使用某些特定的值;这些环境通过activation元素指定。activation元素并不是激活profile的唯一方式。--> <activation> <!--profile默认是否激活的标志--> <activeByDefault/> <!--当匹配的jdk被检测到,profile被激活。例如,1.4激活JDK1.4,1.4.0_2,而!1.4激活所有版本不是以1.4开头的JDK。--> <jdk/> <!--当匹配的操作系统属性被检测到,profile被激活。os元素可以定义一些操作系统相关的属性。--> <os> <!--激活profile的操作系统的名字--> <name>Windows XP</name> <!--激活profile的操作系统所属家族(如 'windows')--> <family>Windows</family> <!--激活profile的操作系统体系结构 --> <arch>x86</arch> <!--激活profile的操作系统版本--> <version>5.1.2600</version> </os> <!--如果Maven检测到某一个属性(其值可以在POM中通过${名称}引用),其拥有对应的名称和值,Profile就会被激活。如果值 字段是空的,那么存在属性名称字段就会激活profile,否则按区分大小写方式匹配属性值字段--> <property> <!--激活profile的属性的名称--> <name>mavenVersion</name> <!--激活profile的属性的值--> <value>2.0.3</value> </property> <!--提供一个文件名,通过检测该文件的存在或不存在来激活profile。missing检查文件是否存在,如果不存在则激活 profile。另一方面,exists则会检查文件是否存在,如果存在则激活profile。--> <file> <!--如果指定的文件存在,则激活profile。--> <exists>/usr/local/hudson/hudson-home/jobs/maven-guide-zh-to-production/workspace/</exists> <!--如果指定的文件不存在,则激活profile。--> <missing>/usr/local/hudson/hudson-home/jobs/maven-guide-zh-to-production/workspace/</missing> </file> </activation> <!--构建项目所需要的信息。参见build元素--> <build> <defaultGoal/> <resources> <resource> <targetPath/><filtering/><directory/><includes/><excludes/> </resource> </resources> <testResources> <testResource> <targetPath/><filtering/><directory/><includes/><excludes/> </testResource> </testResources> <directory/><finalName/><filters/> <pluginManagement> <plugins> <!--参见build/pluginManagement/plugins/plugin元素--> <plugin> <groupId/><artifactId/><version/><extensions/> <executions> <execution> <id/><phase/><goals/><inherited/><configuration/> </execution> </executions> <dependencies> <!--参见dependencies/dependency元素--> <dependency> ...... </dependency> </dependencies> <goals/><inherited/><configuration/> </plugin> </plugins> </pluginManagement> <plugins> <!--参见build/pluginManagement/plugins/plugin元素--> <plugin> <groupId/><artifactId/><version/><extensions/> <executions> <execution> <id/><phase/><goals/><inherited/><configuration/> </execution> </executions> <dependencies> <!--参见dependencies/dependency元素--> <dependency> ...... </dependency> </dependencies> <goals/><inherited/><configuration/> </plugin> </plugins> </build> <!--模块(有时称作子项目) 被构建成项目的一部分。列出的每个模块元素是指向该模块的目录的相对路径--> <modules/> <!--发现依赖和扩展的远程仓库列表。--> <repositories> <!--参见repositories/repository元素--> <repository> <releases> <enabled/><updatePolicy/><checksumPolicy/> </releases> <snapshots> <enabled/><updatePolicy/><checksumPolicy/> </snapshots> <id/><name/><url/><layout/> </repository> </repositories> <!--发现插件的远程仓库列表,这些插件用于构建和报表--> <pluginRepositories> <!--包含需要连接到远程插件仓库的信息.参见repositories/repository元素--> <pluginRepository> <releases> <enabled/><updatePolicy/><checksumPolicy/> </releases> <snapshots> <enabled/><updatePolicy/><checksumPolicy/> </snapshots> <id/><name/><url/><layout/> </pluginRepository> </pluginRepositories> <!--该元素描述了项目相关的所有依赖。 这些依赖组成了项目构建过程中的一个个环节。它们自动从项目定义的仓库中下载。要获取更多信息,请看项目依赖机制。--> <dependencies> <!--参见dependencies/dependency元素--> <dependency> ...... </dependency> </dependencies> <!--不赞成使用. 现在Maven忽略该元素.--> <reports/> <!--该元素包括使用报表插件产生报表的规范。当用户执行“mvn site”,这些报表就会运行。 在页面导航栏能看到所有报表的链接。参见reporting元素--> <reporting> ...... </reporting> <!--参见dependencyManagement元素--> <dependencyManagement> <dependencies> <!--参见dependencies/dependency元素--> <dependency> ...... </dependency> </dependencies> </dependencyManagement> <!--参见distributionManagement元素--> <distributionManagement> ...... </distributionManagement> <!--参见properties元素--> <properties/> </profile> </profiles> <!--模块(有时称作子项目) 被构建成项目的一部分。列出的每个模块元素是指向该模块的目录的相对路径--> <modules/> <!--发现依赖和扩展的远程仓库列表。--> <repositories> <!--包含需要连接到远程仓库的信息--> <repository> <!--如何处理远程仓库里发布版本的下载--> <releases> <!--true或者false表示该仓库是否为下载某种类型构件(发布版,快照版)开启。 --> <enabled/> <!--该元素指定更新发生的频率。Maven会比较本地POM和远程POM的时间戳。这里的选项是:always(一直),daily(默认,每日),interval:X(这里X是以分钟为单位的时间间隔),或者never(从不)。--> <updatePolicy/> <!--当Maven验证构件校验文件失败时该怎么做:ignore(忽略),fail(失败),或者warn(警告)。--> <checksumPolicy/> </releases> <!-- 如何处理远程仓库里快照版本的下载。有了releases和snapshots这两组配置,POM就可以在每个单独的仓库中,为每种类型的构件采取不同的 策略。例如,可能有人会决定只为开发目的开启对快照版本下载的支持。参见repositories/repository/releases元素 --> <snapshots> <enabled/><updatePolicy/><checksumPolicy/> </snapshots> <!--远程仓库唯一标识符。可以用来匹配在settings.xml文件里配置的远程仓库--> <id>banseon-repository-proxy</id> <!--远程仓库名称--> <name>banseon-repository-proxy</name> <!--远程仓库URL,按protocol://hostname/path形式--> <url>http://192.168.1.169:9999/repository/</url> <!-- 用于定位和排序构件的仓库布局类型-可以是default(默认)或者legacy(遗留)。Maven 2为其仓库提供了一个默认的布局;然 而,Maven 1.x有一种不同的布局。我们可以使用该元素指定布局是default(默认)还是legacy(遗留)。--> <layout>default</layout> </repository> </repositories> <!--发现插件的远程仓库列表,这些插件用于构建和报表--> <pluginRepositories> <!--包含需要连接到远程插件仓库的信息.参见repositories/repository元素--> <pluginRepository> ...... </pluginRepository> </pluginRepositories> <!--该元素描述了项目相关的所有依赖。 这些依赖组成了项目构建过程中的一个个环节。它们自动从项目定义的仓库中下载。要获取更多信息,请看项目依赖机制。--> <dependencies> <dependency> <!--依赖的group ID--> <groupId>org.apache.maven</groupId> <!--依赖的artifact ID--> <artifactId>maven-artifact</artifactId> <!--依赖的版本号。 在Maven 2里, 也可以配置成版本号的范围。--> <version>3.8.1</version> <!-- 依赖类型,默认类型是jar。它通常表示依赖的文件的扩展名,但也有例外。一个类型可以被映射成另外一个扩展名或分类器。类型经常和使用的打包方式对应, 尽管这也有例外。一些类型的例子:jar,war,ejb-client和test-jar。如果设置extensions为 true,就可以在 plugin里定义新的类型。所以前面的类型的例子不完整。--> <type>jar</type> <!-- 依赖的分类器。分类器可以区分属于同一个POM,但不同构建方式的构件。分类器名被附加到文件名的版本号后面。例如,如果你想要构建两个单独的构件成 JAR,一个使用Java 1.4编译器,另一个使用Java 6编译器,你就可以使用分类器来生成两个单独的JAR构件。--> <classifier></classifier> <!--依赖范围。在项目发布过程中,帮助决定哪些构件被包括进来。欲知详情请参考依赖机制。 - compile :默认范围,用于编译 - provided:类似于编译,但支持你期待jdk或者容器提供,类似于classpath - runtime: 在执行时需要使用 - test: 用于test任务时使用 - system: 需要外在提供相应的元素。通过systemPath来取得 - systemPath: 仅用于范围为system。提供相应的路径 - optional: 当项目自身被依赖时,标注依赖是否传递。用于连续依赖时使用--> <scope>test</scope> <!--仅供system范围使用。注意,不鼓励使用这个元素,并且在新的版本中该元素可能被覆盖掉。该元素为依赖规定了文件系统上的路径。需要绝对路径而不是相对路径。推荐使用属性匹配绝对路径,例如${java.home}。--> <systemPath></systemPath> <!--当计算传递依赖时, 从依赖构件列表里,列出被排除的依赖构件集。即告诉maven你只依赖指定的项目,不依赖项目的依赖。此元素主要用于解决版本冲突问题--> <exclusions> <exclusion> <artifactId>spring-core</artifactId> <groupId>org.springframework</groupId> </exclusion> </exclusions> <!--可选依赖,如果你在项目B中把C依赖声明为可选,你就需要在依赖于B的项目(例如项目A)中显式的引用对C的依赖。可选依赖阻断依赖的传递性。--> <optional>true</optional> </dependency> </dependencies> <!--不赞成使用. 现在Maven忽略该元素.--> <reports></reports> <!--该元素描述使用报表插件产生报表的规范。当用户执行“mvn site”,这些报表就会运行。 在页面导航栏能看到所有报表的链接。--> <reporting> <!--true,则,网站不包括默认的报表。这包括“项目信息”菜单中的报表。--> <excludeDefaults/> <!--所有产生的报表存放到哪里。默认值是${project.build.directory}/site。--> <outputDirectory/> <!--使用的报表插件和他们的配置。--> <plugins> <!--plugin元素包含描述报表插件需要的信息--> <plugin> <!--报表插件在仓库里的group ID--> <groupId/> <!--报表插件在仓库里的artifact ID--> <artifactId/> <!--被使用的报表插件的版本(或版本范围)--> <version/> <!--任何配置是否被传播到子项目--> <inherited/> <!--报表插件的配置--> <configuration/> <!--一组报表的多重规范,每个规范可能有不同的配置。一个规范(报表集)对应一个执行目标 。例如,有1,2,3,4,5,6,7,8,9个报表。1,2,5构成A报表集,对应一个执行目标。2,5,8构成B报表集,对应另一个执行目标--> <reportSets> <!--表示报表的一个集合,以及产生该集合的配置--> <reportSet> <!--报表集合的唯一标识符,POM继承时用到--> <id/> <!--产生报表集合时,被使用的报表的配置--> <configuration/> <!--配置是否被继承到子POMs--> <inherited/> <!--这个集合里使用到哪些报表--> <reports/> </reportSet> </reportSets> </plugin> </plugins> </reporting> <!-- 继承自该项目的所有子项目的默认依赖信息。这部分的依赖信息不会被立即解析,而是当子项目声明一个依赖(必须描述group ID和 artifact ID信息),如果group ID和artifact ID以外的一些信息没有描述,则通过group ID和artifact ID 匹配到这里的依赖,并使用这里的依赖信息。--> <dependencyManagement> <dependencies> <!--参见dependencies/dependency元素--> <dependency> ...... </dependency> </dependencies> </dependencyManagement> <!--项目分发信息,在执行mvn deploy后表示要发布的位置。有了这些信息就可以把网站部署到远程服务器或者把构件部署到远程仓库。--> <distributionManagement> <!--部署项目产生的构件到远程仓库需要的信息--> <repository> <!--是分配给快照一个唯一的版本号(由时间戳和构建流水号)?还是每次都使用相同的版本号?参见repositories/repository元素--> <uniqueVersion/> <id>banseon-maven2</id> <name>banseon maven2</name> <url>file://${basedir}/target/deploy</url> <layout/> </repository> <!--构件的快照部署到哪里?如果没有配置该元素,默认部署到repository元素配置的仓库,参见distributionManagement/repository元素--> <snapshotRepository> <uniqueVersion/> <id>banseon-maven2</id> <name>Banseon-maven2 Snapshot Repository</name> <url>scp://svn.baidu.com/banseon:/usr/local/maven-snapshot</url> <layout/> </snapshotRepository> <!--部署项目的网站需要的信息--> <site> <!--部署位置的唯一标识符,用来匹配站点和settings.xml文件里的配置--> <id>banseon-site</id> <!--部署位置的名称--> <name>business api website</name> <!--部署位置的URL,按protocol://hostname/path形式--> <url> scp://svn.baidu.com/banseon:/var/www/localhost/banseon-web </url> </site> <!--项目下载页面的URL。如果没有该元素,用户应该参考主页。使用该元素的原因是:帮助定位那些不在仓库里的构件(由于license限制)。--> <downloadUrl/> <!--如果构件有了新的group ID和artifact ID(构件移到了新的位置),这里列出构件的重定位信息。--> <relocation> <!--构件新的group ID--> <groupId/> <!--构件新的artifact ID--> <artifactId/> <!--构件新的版本号--> <version/> <!--显示给用户的,关于移动的额外信息,例如原因。--> <message/> </relocation> <!-- 给出该构件在远程仓库的状态。不得在本地项目中设置该元素,因为这是工具自动更新的。有效的值有:none(默认),converted(仓库管理员从 Maven 1 POM转换过来),partner(直接从伙伴Maven 2仓库同步过来),deployed(从Maven 2实例部 署),verified(被核实时正确的和最终的)。--> <status/> </distributionManagement> <!--以值替代名称,Properties可以在整个POM中使用,也可以作为触发条件(见settings.xml配置文件里activation元素的说明)。格式是<name>value</name>。--> <properties/> </project>","categories":[{"name":"版本管理","slug":"版本管理","permalink":"http://windynature.oschina.io/categories/版本管理/"}],"tags":[{"name":"Maven","slug":"Maven","permalink":"http://windynature.oschina.io/tags/Maven/"}]},{"title":"Spring Web MVC相关注解","slug":"java/springmvc相关注解","date":"2017-01-25T15:19:06.000Z","updated":"2020-03-18T04:06:59.891Z","comments":true,"path":"2017/01/25/java/springmvc相关注解/","link":"","permalink":"http://windynature.oschina.io/2017/01/25/java/springmvc相关注解/","excerpt":"","text":"Spring Web MVC相关注解@Controller控制器定义 和Struct1一样,Spring的Controller是Singleton的。这就意味着会被许多个请求线程共享。因此,我们将控制器设计成无状态类。 在Spring 3.0中,通过@Controoler标注可将class定义为一个Controller类。为使Spring能找到定义为Controller的bean,需要在spring-context配置文件中增加如下定义: <context:component-scan base-package=\"com.sxt.web\"/> 注:实际上,使用@Component,也可以气到@Controller同样的作用。 @RequestMapping 在类前面定义,则将url和类绑定。在方法前面定义,则将url和类的方法绑定,如下所示。 package com.sxt.web;import javax.annotation.Resource;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import com.sxt.service.UserService;@Controller@RequestMapping(\"/user.do\")public class UserController { @Resource private UserService userService; @RequestMapping(params=\"method=reg\") public String reg(String uname) { System.out.println(\"HelloController.HandleRequest()\"); userService.add(uname); return \"index\"; }[mybatis3动态创建表](media/mybatis3%E5%8A%A8%E6%80%81%E5%88%9B%E5%BB%BA%E8%A1%A8-1.markdown) public UserService getUservice() { return userService; } public void setUserService(UserService userService) { this.userSErvice = userService; }} @RequestParam 一般用于将指定的请求参数付给方法中形参。示例代码如下: @RequestMappint(params=\"method=reg5\")public String reg5(@RequestParam(\"name\")String uname, ModelMap map) { System.out.println(\"HelloController.handleRequest()\"); System.out.println(uname); return \"index\";}[mybatis3动态创建表](media/mybatis3%E5%8A%A8%E6%80%81%E5%88%9B%E5%BB%BA%E8%A1%A8.markdown) 这样就把name参数的值付给uname。当然,如果请求参数名称和形参名称保持一致,则不需要这种写法。 @SessionAttributes 将ModelMap中指定的属性放到Session中。示例代码如下: @Controoler@RequestMapping(\"/user.do\")@SessionAttributes({\"u\",\"a\"}) //将ModelMap中属性名字为u、a的再放入session中。这样,request和session中都有了。public class UserController { @RequestMapping(params=\"method=reg4\") public String reg4(ModelMap map) { System.out.println(\"HelloController.handleRequest()\"); //将u放入request作用域中,这样转发页面也可以取到这个数据。 map.addAttribute(\"u\",\"uuuu\"); }} <body> <h1>*********${requestScope.u.uname}<h1> <h1>*********${sessionScope.u.uname}<h1></body> 注:名字为”user”的属性再结合使用注解@SessionAttributes可能会报错 @ModelAttribute 这个注解可以跟@SessionAtributes配合在一起使用。可以将ModelMap中属性的值通过该注解自动复制给指定变量。示例代码如下: @Controller@RequestMapping(\"/user.do\")@SessionAttributes({\"u\",\"a\"})public class UserController { @RequestMapping(params=\"method=reg4\") public String reg4(ModelMap map) { Sout.pringln(\"HelloController.handlerRequest()\"); map.addAttribute(\"u\",\"尚学堂高琪\"); return \"index\"; } //将属性u的值赋给形参uname public String reg5(@ModelAttribute(\"u\")String uname,ModelMap map) { System.out.pirntln(\"HelloController.handleRequest\"); System.out.println(uname); return \"index\"; }} 注:先调用reg4方法,在调用reg5方法。控制台输出:尚学堂高琪 Controller类中方法返回值的处理 返回String(建议) 根据返回值找对应的显示页面。路径规则为:prefix前缀+返回值+suffix后缀组成 代码如下: public String reg4(ModelMap map) { System.out.println(\"HelloController.handleReuest()\"); return \"index\"; } ``` 2. 也可以返回ModelMap、ModelAndView、Map、List、Set、Object、无返回值。一般建议返回字符串!## 请求转发和重定向---代码示例:```java@Controller@RequestMapping(\"/user.do\")public class UserController { @RequestMapping(params=\"method=reg4\") public String reg4(ModelMap map) { System.out.println(\"HelloController.handleReuest\"); //reutrn \"forward:index.jsp\"; //return \"forward:user.do?method=reg5\"; //转发 //return \"redirect:user.do?method=reg5\";//重定向 return \"redirect:http://www.baidu.com\"; //重定向 } @RequestMapping(params=\"method=reg5\") public String reg5(String uname,ModelMap map) { System.out.println(\"HelloController.handleRequest()\"); System.out.println(uname); return \"index\"; }} 访问reg4方法,既可以看到效果。 获得request对象、session对象 普通的Controller类,示例代码如下: @Controller@RequestMapping(\"/user.do\")public class UserController { @RequestMapping(params=\"method=reg2\") public String reg2(String uname, HttpServletRequest req, ModelMap map) { req.setAttribute(\"a\",\"aa\"); ret.getSession().setAttribute(\"b\",\"bb\"); return \"index\"; }} ModelMap ModelMap是map的实现,可以在其中存放属性,作用域同request。下面这个示例,我们可以在modelMap中存放数据,然后在forward页面上显示这些数据。通过el表达式、JSTL、java代码均可。代码如下: @Controller@RequestMapping(\"/user.do\")public class UserController extends MutiActionController { @ReuestMapping(params=\"method=reg\") public String reg(String uname,ModelMap map) { map.put(\"a\",\"aaa\"); return \"index\"; }} <%@ page language=\"java\" import=\"java.util.*\" pageEncoding=\"gbk\"%><%@ taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\"%><!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"><html> <head></head> <body> <h1>${requestScope.a}</h1> <c:out value=\"${requestScope.a}\"></c:out> </body></html> ModelAndView模型视图类 见名知意,从名字上我们可以知道ModelAndView中的Model代表模型,View代表视图。即,这个类把要显示的数据存放到Model属性中,要跳转的信息存储到啊了view属性。从ModelAndView的源代码可以知道其中的关系。部分源代码如下: public class ModelAndView { /** View instance or view name String */ private Object view; /** Model Map */ private ModelMap model; /** * Indicates whether or not this instance has been cleared with a call to {@link #clear()}. */ private boolean cleared = false; /** * Default constructor for bean-style usage: populating bean * properties instead of passing in constructor arguments. * @see #setView(View) * @see #setViewName(String) */ public ModelAndView() { } /** * Convenient constructor when there is no model data to expose. * Can also be used in conjunction with <code>addObject</code>. * @param viewName name of the View to render, to be resolved * by the DispatcherServlet's ViewResolver * @see #addObject */ public ModelAndView(String viewName) { this.view = viewName; } /** * Convenient constructor when there is no model data to expose. * Can also be used in conjunction with <code>addObject</code>. * @param view View object to render * @see #addObject */ public ModelAndView(View view) { this.view = view; } /** * Creates new ModelAndView given a view name and a model. * @param viewName name of the View to render, to be resolved * by the DispatcherServlet's ViewResolver * @param model Map of model names (Strings) to model objects * (Objects). Model entries may not be <code>null</code>, but the * model Map may be <code>null</code> if there is no model data. */ public ModelAndView(String viewName, Map<String, ?> model) { this.view = viewName; if (model != null) { getModelMap().addAllAttributes(model); } } /** * Creates new ModelAndView given a View object and a model. * <emphasis>Note: the supplied model data is copied into the internal * storage of this class. You should not consider to modify the supplied * Map after supplying it to this class</emphasis> * @param view View object to render * @param model Map of model names (Strings) to model objects * (Objects). Model entries may not be <code>null</code>, but the * model Map may be <code>null</code> if there is no model data. */ public ModelAndView(View view, Map<String, ?> model) { this.view = view; if (model != null) { getModelMap().addAllAttributes(model); } } /** * Convenient constructor to take a single model object. * @param viewName name of the View to render, to be resolved * by the DispatcherServlet's ViewResolver * @param modelName name of the single entry in the model * @param modelObject the single model object */ public ModelAndView(String viewName, String modelName, Object modelObject) { this.view = viewName; addObject(modelName, modelObject); } /** * Convenient constructor to take a single model object. * @param view View object to render * @param modelName name of the single entry in the model * @param modelObject the single model object */ public ModelAndView(View view, String modelName, Object modelObject) { this.view = view; addObject(modelName, modelObject); } /** * Set a view name for this ModelAndView, to be resolved by the[mybatis3动态创建表](media/mybatis3%E5%8A%A8%E6%80%81%E5%88%9B%E5%BB%BA%E8%A1%A8-2.markdown) * DispatcherServlet via a ViewResolver. Will override any * pre-existing view name or View. */ public void setViewName(String viewName) { this.view = viewName; } /** * Return the view name to be resolved by the DispatcherServlet * via a ViewResolver, or <code>null</code> if we are using a View object. */ public String getViewName() { return (this.view instanceof String ? (String) this.view : null); } /** * Set a View object for this ModelAndView. Will override any * pre-existing view name or View. */ public void setView(View view) { this.view = view; } /** * Return the View object, or <code>null</code> if we are using a view name * to be resolved by the DispatcherServlet via a ViewResolver. */ public View getView() { return (this.view instanceof View ? (View) this.view : null); } /** * Indicate whether or not this <code>ModelAndView</code> has a view, either * as a view name or as a direct {@link View} instance. */ public boolean hasView() { return (this.view != null); } /** * Return whether we use a view reference, i.e. <code>true</code> * if the view has been specified via a name to be resolved by the * DispatcherServlet via a ViewResolver. */ public boolean isReference() { return (this.view instanceof String); } /** * Return the model map. May return <code>null</code>. * Called by DispatcherServlet for evaluation of the model. */ protected Map<String, Object> getModelInternal() { return this.model; } /** * Return the underlying <code>ModelMap</code> instance (never <code>null</code>). */ public ModelMap getModelMap() { if (this.model == null) { this.model = new ModelMap(); } return this.model; } /** * Return the model map. Never returns <code>null</code>. * To be called by application code for modifying the model. */ public Map<String, Object> getModel() { return getModelMap(); } /** * Add an attribute to the model. * @param attributeName name of the object to add to the model * @param attributeValue object to add to the model (never <code>null</code>) * @see ModelMap#addAttribute(String, Object) * @see #getModelMap() */ public ModelAndView addObject(String attributeName, Object attributeValue) { getModelMap().addAttribute(attributeName, attributeValue); return this; } /** * Add an attribute to the model using parameter name generation. * @param attributeValue the object to add to the model (never <code>null</code>) * @see ModelMap#addAttribute(Object) * @see #getModelMap() */ public ModelAndView addObject(Object attributeValue) { getModelMap().addAttribute(attributeValue); return this; } /** * Add all attributes contained in the provided Map to the model. * @param modelMap a Map of attributeName -> attributeValue pairs * @see ModelMap#addAllAttributes(Map) * @see #getModelMap() */ public ModelAndView addAllObjects(Map<String, ?> modelMap) { getModelMap().addAllAttributes(modelMap); return this; } /** * Clear the state of this ModelAndView object. * The object will be empty afterwards. * <p>Can be used to suppress rendering of a given ModelAndView object * in the <code>postHandle</code> method of a HandlerInterceptor. * @see #isEmpty() * @see HandlerInterceptor#postHandle */ public void clear() { this.view = null; this.model = null; this.cleared = true; } /** * Return whether this ModelAndView object is empty, * i.e. whether it does not hold any view and does not contain a model. */ public boolean isEmpty() { return (this.view == null && CollectionUtils.isEmpty(this.model)); } /** * Return whether this ModelAndView object is empty as a result of a call to {@link #clear} * i.e. whether it does not hold any view and does not contain a model. * <p>Returns <code>false</code> if any additional state was added to the instance * <strong>after</strong> the call to {@link #clear}. * @see #clear() */ public boolean wasCleared() { return (this.cleared && isEmpty()); } /** * Return diagnostic information about this model and view. */ @Override public String toString() { StringBuilder sb = new StringBuilder(\"ModelAndView: \"); if (isReference()) { sb.append(\"reference to view with name '\").append(this.view).append(\"'\"); } else { sb.append(\"materialized View is [\").append(this.view).append(']'); } sb.append(\"; model is \").append(this.model); return sb.toString(); }} 测试代码如下: package com.sxt.web;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.mvc.multiaction.MultiActionController;import com.sxt.po.User;@Controller@RequestMapping(\"/user.do\")public class UserController extends MultiActionController { @RequestMapping(params=\"method=reg\") public ModelAndView reg(String uname){ ModelAndView mv = new ModelAndView(); mv.setViewName(\"index\");// mv.setView(new RedirectView(\"index\")); User u = new User(); u.setUname(\"高淇\"); //查看源代码,得知,直接放入对象。属性名为”首字母小写的类名”。 一般建议手动增加属性名称。 mv.addObject(u); mv.addObject(\"a\", \"aaaa\"); return mv; }} <%@ page language=\"java\" import=\"java.util.*\" pageEncoding=\"gbk\"%><%@ taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\" %><!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"><html> <head> </head> <body> <h1>${requestScope.a}</h1> <h1>${requestScope.user.uname}</h1> </body></html> 地址栏输入:http://localhost:8080/springmvc03/user.do?method=reg结果为:aaaa高琪","categories":[{"name":"Java Web","slug":"Java-Web","permalink":"http://windynature.oschina.io/categories/Java-Web/"}],"tags":[{"name":"SpringMVC","slug":"SpringMVC","permalink":"http://windynature.oschina.io/tags/SpringMVC/"}]},{"title":"适配器模式Adapter","slug":"设计模式/适配器模式","date":"2017-01-25T14:44:38.000Z","updated":"2020-03-18T04:07:03.672Z","comments":true,"path":"2017/01/25/设计模式/适配器模式/","link":"","permalink":"http://windynature.oschina.io/2017/01/25/设计模式/适配器模式/","excerpt":"","text":"设计模式(五)适配器模式Adapter(结构型) 1.概述: 接口的改变,是一个需要程序员们必须(虽然很不情愿)接受和处理的普遍问题。程序提供者们修改他们的代码;系统库被修正;各种程序语言以及相关库的发展和进化。 例子1:iphone4,你即可以使用UBS接口连接电脑来充电,假如只有iphone没有电脑,怎么办呢?苹果提供了iphone电源适配器。可以使用这个电源适配器充电。这个iphone的电源适配器就是类似我们说的适配器模式。(电源适配器就是把电源变成需要的电压,也就是适配器的作用是使得一个东西适合另外一个东西。) 例子2:最典型的例子就是很多功能手机,每一种机型都自带有从电器,有一天自带充电器坏了,而且市场没有这类型充电器可买了。怎么办?万能充电器就可以解决。这个万能充电器就是适配器。 2.问题 你如何避免因外部库的API改变而带来的不便?假如你写了一个库,你能否提供一种方法允许你软件的现有用户进行完美地升级,即使你已经改变了你的API?为了更好地适宜于你的需要,你应该如何改变一个对象的接口? 3.解决方案 适配器(Adapter)模式为兑现给提供了一种完全不同的接口。你可以运用适配器来实现一个不同类的常见接口,同事避免了因神级和拆解客户代码所引起的纠纷。 适配器模式(Adapter Pattern),把一个类的接口变换成客户端所期待的另一种接口,Adapter模式使原本因接口不匹配(或者不兼容)而无法再一起工作的两个类能够在一起工作。又称为转换器模式、变压器模式、包装(Wrapper)器模式(把以后的一些类包装起来,使之能有满足需要的接口)。 考虑一下当(不是假设!)一个第三方库的API改变将会发生什么。过去你只能是咬紧牙关修改所有的客户代码,而情况往往还不那么简单。你可能正从事一项新的项目,它要用到新版本的库所带来的特性,但你已经拥有许多旧的应用程序,并且它们与以前旧版本的库交互运行地很好。你将无法证明这些新特性的利用价值,如果这次升级意味着将要涉及到其它应用程序的客户代码。 4.分类 共有两类适配器模式:1.类的适配器模式(采用继承实现)2.对象适配器(采用对象组合方式实现) 1.类适配器模式: 适配器继承自己实现的类(一般多重继承)。 Adapter与Adaptee是继承关系 用一个具体的Adapter类和Target进行匹配。结果是当我们想要一个匹配一个类以及所有它的子类时,类Adapter将不能胜任工作 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子集 仅仅引入一个对象,并不需要额外的指针以间接取得adaptee 2.对象适配器模式: 适配器容纳一个它包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体Adapter与Adaptee是委托关系 允许一个Adapter与多个Adaptee同时工作。Adapter也可以一次给所有的Adaptee添加功能 使用重定义Adaptee的行为比较困难 无论哪种适配器,它的宗旨都是:保留现有类所提供的服务,向客户提供接口,以满足客户的期望。即在不改变原有系统的基础上,提供新的接口服务。 5. 适用性 以下情况使用Adapter模式: 你想使用一个已经存在的类,而它的接口不符合你的需求。 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。 (仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。即仅仅引入一个对象,并不需要额外的指针以间接取得adaptee。 6. 结构 类适配器使用多重继承对一个接口与另一个接口进行匹配,如下图所示: 对象匹配器依赖于对象组合,如下图所示: 注:Target是客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口Adaptee:需要适配的接口Adapter通过在内部包装一个Adaptee对象,把源接口转换成目标接口 7. 构建模式的组成 目标角色(Target):定义Client使用的与特定领域相关的接口。 客户角色(Client):与符合Target接口的对象协同。 被适配角色(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。 适配器角色(Adapte) :适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配.8.适配器模式实现在GoF的设计模式中,对适配器讲了两种类型,即类适配器模式和对象适配器模式,由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而C#、VB.NET、Java等语言都不支持多继承,也就是只有一个父类,所以我们这里主要讲得是对象适配器。 Target(这是客户所期待的接口。目标可以是具体的类或抽象的类)代码如下: //客户所期待的接口public class Target { public void request(){ System.out.println(\"普通的请求\"); }} Adaptee(需要适配的类)代码如下: //需要适配的类public class Adaptee { public void sepecificRequest(){ System.out.println(\"特殊的请求\"); }} Adapter(所需要适配的类)代码如下: //所需要适配的类public class Adapter extends Target{ private Adaptee adaptee = new Adaptee(); @Override public void request() { adaptee.sepecificRequest(); }} 客户端代码如下: public class Client { public static void main(String[] args) { Target target1 = new Target(); target1.request(); Target target2 = new Adapter(); target2.request(); }}","categories":[{"name":"设计模式","slug":"设计模式","permalink":"http://windynature.oschina.io/categories/设计模式/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"http://windynature.oschina.io/tags/设计模式/"}]},{"title":"设计模式概论","slug":"设计模式/设计模式概论","date":"2017-01-25T14:44:18.000Z","updated":"2020-03-18T04:06:42.514Z","comments":true,"path":"2017/01/25/设计模式/设计模式概论/","link":"","permalink":"http://windynature.oschina.io/2017/01/25/设计模式/设计模式概论/","excerpt":"","text":"最近想学习一点设计模式方面的知识,在网上找到了别人总结的这方面的内容,觉得写得很好,一下是出处。[出处:http://blog.csdn.net/hguisu/article/category/1133340/2] 设计模式概论1.设计模式 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。 模式的经典定义:每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心,通过这种方式,我们可以无数次地重用那些已有的解决方案,无需再重复相同的工作。即模式是在特定环境中解决问题的一种方案 2.设计模式目的 其目的就是一方面教你如何利用真实可靠的设计来组织代码的模板。 简单地说,就是从前辈们在程序设计过程中总结、抽象出来的通用优秀经验。主要目的一方面是为了增加程序的灵活性、可重用性。 另一方面也有助于程序设计的标准化和提高系统开发进度。 也有人忠告:不要过于注重程序的“设计模式”。 有时候,写一个简单的算法,要比引入某种模式更容易。在多数情况下,程序代码应是简单易懂,甚至清洁工也能看懂。不过呢在大项目或者框架中,没有设计模式来组织代码,别人是不易理解的。 一个软件设计模型也仅仅只是一个引导。它必须根据程序设计语言和你的应用程序的特点和要求而特别的设计。 3.设计模式的四个基本要素 设计模式使人们可以更加简单方便地复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。 所有的设计模式都有一些常用的特性:一个标识(a pattern name),一个问题陈述(a problem statement)和一个解决方案(a solution),效果(consequences) 模式名称(pattern name): 描述模式的问题、解决方案和效果一个设计模式的标识(模式名称)是重要的,因为它会让其他的程序员不用进行太深入的学习就能立刻理解你的代码的目的(至少通过这个标识程序员会很熟悉这个模式)。没有这个模式名,我们便无法与其他人交流设计思想及设计结果。 问题(problem):描述是用来说明这个模式的应用的领域。 描述了应该在何时使用模式。它解释了设计问题和问题存在的前因后果,它可能描述了特定的设计问题,如怎样用对象表示算法等。也可能描述了导致不灵活设计的类或对象结构。有时候,问题部分会包括使用模式必须满足的一系列先决条件。 解决方案(solution) : 描述了这个模型的执行。描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。因为模式就像一个模板,可应用于多种不同场合,所以解决方案并不描述一个特定而具体的设计或实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题。 效果(consequences):描述了模式应用的效果及使用模式应权衡的问题。 尽管我们描述设计决策时,并不总提到模式效果,但它们对于评价设计选择和理解使用模式的代价及好处具有重要意义。软件效果大多关注对时间和空间的衡量,它们也表述了语言和实现问题。因为复用是面向对象设计的要素之一,所以模式效果包括它对系统的灵活性、扩充性或可移植性的影响,显式地列出这些效果对理解和评价这些模式很有帮助。一个好的设计模式的论述应该覆盖使用这个模型的优点和缺点。 一个模式是解决特定问题的有效方法。一个设计模式不是一个库(能在你的项目中直接包含和使用的代码库)而是一个用来组织你的代码的模板(Java bean)。事实上,一个代码库和一个设计模式在应用上是有很多不同的。 比如,你从店铺里面买的一件衬衫是一个代码库,它的颜色,样式和大小都由设计师和厂商决定,但它满足了你的需求。 然而,如果店里面没有什么衣服适合你,那你就能自己创建自己的衬衫(设计它的形状,选择布料,然后裁缝在一起)。但是如果你不是一个裁缝,你可能会发现自 己很容易的去找一个合适的模式然后按着这个模式去设计自己的衬衫。使用一个模型,你可以在更少的时间内得到一个熟练设计的衬衫。 回到讨论软件上来,一个数据提取层或者一个CMS(content management system)就是一个库——它是先前设计好而且已经编码好了的,如果它能准确的满足你的需要那它就是一个好的选择。但如果你正在读这本书《设计模式》,可能你会发现 库存的(原有的)解决方案并不是总是对你有效。至今你知道什么是你所要的,而且你能够实现它,你仅仅需要一个模型来引导你。 最后一个想法:就象一个裁缝模型,一个设计本身而言是没有什么用处的。毕竟,你不可能穿一个服装模型——它仅仅是由很薄的纸拼凑起来的。类似的,一个软件设计模型也仅仅只是一个引导。它必须根据程序设计语言和你的应用程序的特点和要求而特别的设计。 4. 设计模式分类: 4.1 根据目的分可分为创建型(Creational),结构型(Structural)和行为型(Behavioral)三种: 创建型模式主要用于创建对象。 结构型模式主要用于处理类或对象的组合。 行为型模式主要用于描述对类或对象怎样交互和怎样分配职责。 4.2 根据范围分 类模式: 处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是属于静态的。 对象模式:处理对象间的关系,这些关系在运行时刻变化,更具动态性。","categories":[{"name":"设计模式","slug":"设计模式","permalink":"http://windynature.oschina.io/categories/设计模式/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"http://windynature.oschina.io/tags/设计模式/"}]},{"title":"mybatis动态sql语句","slug":"java/mybatis动态sql语句","date":"2016-12-23T02:24:09.000Z","updated":"2020-03-18T04:07:24.366Z","comments":true,"path":"2016/12/23/java/mybatis动态sql语句/","link":"","permalink":"http://windynature.oschina.io/2016/12/23/java/mybatis动态sql语句/","excerpt":"","text":"概述mybatis的动态sql语句是基于OGNL表达式的。可以方便的在sql语句中实现某些逻辑,总体来说mybatis动态sql语句主要有一下几类。 if语句(简单的条件判断) choose(when,otherwise),相当于java语言中的switch,与jstl中的choose很类似。 trim(对包含的内容加上prefix,或者suffix等,前缀,后缀) where(主要是用来贱货sql语句中where条件判断的,能智能的处理and or,不必担心多余导致语法错误) set(主要用户更新时) foreach(在实现mybatis in语句查询时特别有用) [toc] if 语句<select id=\"dynamicIfTest\" parameterType=\"Blog\" resultType=\"Blog\"> select * from t_blog where 1 = 1 <if test=\"title != null\"> and title = #{title} </if> <if test=\"content != null\"> and content = #{content} </if> <if test=\"owner != null\"> and owner = #{owner} </if></select> 这条语句的意思非常简单,如果你提供了title参数,那么就要满足title=#{title},同样如果你提供了Content和Owner的时候,它们也需要满足相应的条件,之后就是返回满足这些条件的所有Blog,这是非常有用的一个功能,以往我们使用其他类型框架或者直接使用JDBC的时候, 如果我们要达到同样的选择效果的时候,我们就需要拼SQL语句,这是极其麻烦的,比起来,上述的动态SQL就要简单多了 choose (when,otherwize)语句<select id=\"dynamicChooseTest\" parameterType=\"Blog\" resultType=\"Blog\"> select * from t_blog where 1 = 1 <choose> <when test=\"title != null\"> and title = #{title} </when> <when test=\"content != null\"> and content = #{content} </when> <otherwise> and owner = \"owner1\" </otherwise> </choose></select> when元素表示当when中的条件满足的时候就输出其中的内容,跟JAVA中的switch效果差不多的是按照条件的顺序,当when中有条件满足的时候,就会跳出choose,即所有的when和otherwise条件中,只有一个会输出,当所有的条件都不满足的时候就输出otherwise中的内容。所以上述语句的意思非常简单, 当title!=null的时候就输出and titlte = #{title},不再往下判断条件,当title为空且content!=null的时候就输出and content = #{content},当所有条件都不满足的时候就输出otherwise中的内容。 trim语句<select id=\"dynamicTrimTest\" parameterType=\"Blog\" resultType=\"Blog\"> select * from t_blog <trim prefix=\"where\" prefixOverrides=\"and |or\"> <if test=\"title != null\"> title = #{title} </if> <if test=\"content != null\"> and content = #{content} </if> <if test=\"owner != null\"> or owner = #{owner} </if> </trim></select> trim元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是prefix和suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是prefixOverrides和suffixOverrides;正因为trim有这样的功能,所以我们也可以非常简单的利用trim来代替where元素的功能。 where语句<select id=\"dynamicWhereTest\" parameterType=\"Blog\" resultType=\"Blog\"> select * from t_blog <where> <if test=\"title != null\"> title = #{title} </if> <if test=\"content != null\"> and content = #{content} </if> <if test=\"owner != null\"> and owner = #{owner} </if> </where></select> where元素的作用是会在写入where元素的地方输出一个where,另外一个好处是你不需要考虑where元素里面的条件输出是什么样子的,MyBatis会智能的帮你处理,如果所有的条件都不满足那么MyBatis就会查出所有的记录,如果输出后是and 开头的,MyBatis会把第一个and忽略,当然如果是or开头的,MyBatis也会把它忽略;此外,在where元素中你不需要考虑空格的问题,MyBatis会智能的帮你加上。像上述例子中,如果title=null, 而content != null,那么输出的整个语句会是select * from t_blog where content = #{content},而不是select * from t_blog where and content = #{content},因为MyBatis会智能的把首个and 或 or 给忽略。 set语句<update id=\"dynamicSetTest\" parameterType=\"Blog\"> update t_blog <set> <if test=\"title != null\"> title = #{title}, </if> <if test=\"content != null\"> content = #{content}, </if> <if test=\"owner != null\"> owner = #{owner} </if> </set> where id = #{id}</update> set元素主要是用在更新操作的时候,它的主要功能和where元素其实是差不多的,主要是在包含的语句前输出一个set,然后如果包含的语句是以逗号结束的话将会把该逗号忽略,如果set包含的内容为空的话则会出错。有了set元素我们就可以动态的更新那些修改了的字段。 foreachforeach的主要擢用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有item、index、collection、open、separator、close。 item:表示集合中每一个元素进行迭代时的别名。 index:指定一个名字,用于表示在迭代过程中,每次迭代到的位置。 open:表示该语句什么时候可以开始。 separator:表示在每一进行迭代之间以什么符号作为分隔符。 close:表示以什么结束。 在foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有以下三种情况: 如果传入的是单参数类型是一个List的时候,collection属性为list 如果传入的是但参数类型是一个array数组的时候,collection属性值为array 如果传入的参数是多个的时候,我们就需要把他们封装成一个Map了,当然参数也可以封装成map,实际上如果你在传入参数的时候,在MyBati里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入List或array对象在自己封装的map里面的key 单参数List的类型<select id=\"dynamicForeachTest\" resultType=\"Blog\"> select * from t_blog where id in <foreach collection=\"list\" index=\"index\" item=\"item\" open=\"(\" separator=\",\" close=\")\"> #{item} </foreach> </select> 上述collection的值为list,对应的Mapper是这样的 public List<Blog> dynamicForeachTest(List<Integer> ids); 测试代码: public void dynamicForeachTest() { SqlSession session = Util.getSqlSessionFactory().openSession(); BlogMapper blogMapper = session.getMapper(BlogMapper.class); List<Integer> ids = new ArrayList<Integer>(); ids.add(1); ids.add(3); ids.add(6); List<Blog> blogs = blogMapper.dynamicForeachTest(ids); for(Blog blog:blogs) System.out.println(blog); session.close();} 数组类型的参数<select id=\"dynamicForeach2Test\" resultType=\"Blog\"> select * from t_blog where id in <foreach collection=\"array\" index=\"index\" item=\"item\" open=\"(\" separator=\",\" close=\")\"> #{item} </foreach></select> 对应mapper public List<Blog> dynamicForeach2Test(int[] ids); Map 类型的参数<select id=\"dynamicForeach3Test\" resultType=\"Blog\"> select * from t_blog where title like \"%\"#{title}\"%\" and id in <foreach collection=\"ids\" index=\"index\" item=\"item\" open=\"(\" separator=\",\" close=\")\"> #{item} </foreach> </select> mapper接口 public List<Blog> dynamicForeach3Test(Map<String,Object> params); 通过以上方法,就能完成一般的mybatis的动态sql。最常用的就是if where foreach这几个,一定要重点掌握。","categories":[{"name":"mybatis","slug":"mybatis","permalink":"http://windynature.oschina.io/categories/mybatis/"},{"name":"数据库","slug":"mybatis/数据库","permalink":"http://windynature.oschina.io/categories/mybatis/数据库/"}],"tags":[{"name":"mybatis","slug":"mybatis","permalink":"http://windynature.oschina.io/tags/mybatis/"},{"name":"ORM","slug":"ORM","permalink":"http://windynature.oschina.io/tags/ORM/"}]},{"title":"动态代理机制详解","slug":"java/动态代理","date":"2016-10-03T17:40:14.000Z","updated":"2020-03-18T04:04:10.760Z","comments":true,"path":"2016/10/04/java/动态代理/","link":"","permalink":"http://windynature.oschina.io/2016/10/04/java/动态代理/","excerpt":"","text":"转自:java的动态代理机制详解 动态代理机制详解在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾。 InvocationHandler在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的: InvocationHandler is the interface implemented by the invocation handler of a proxy instance. Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler. 每一个动态代理都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler。当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHander这个接口的invoke()方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个invoke()方法: ProxyObject invoke(Object proxy, Method method, Object[] args) throws Throwable 我满看到这个方法一共接收三个参数,那么这三个参数分别代表什么呢? proxy:指代我们所代理的那个正式对象 method:指代的是我们所要调用真实对象的牟飞方法的Method对象。 args:指代的是调用真实对象某个方法时接受的参数。 接下来看看Proxy类: Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods. Proxy 类的作用是:用来创建一个代理对象的类,它提供了许多方法,但是我们用的最多的就是newProxyInstance()这个方法。 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler. 这个方法的作用就是得到一个动态代理对象,它接收三个参数,含义如下 loader:一个ClassLoader对象,定义了由那个ClassLoader对象对生成的代理对象进行加载。interface:一个Ingerface对象的数组,表示的是我们要将给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就应实现该接口(多态),这样我们就能调用这组接口中的方法了。h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。 JDK动态代理实现引用自 Java动态代理的两种实现方法 定义接口和实现UserService接口 public interface UserService { public String getName(int id); public Integer getAge(int id);} UserServiceImpl public class UserServiceImpl implements UserService { @Override public String getName(int id) { System.out.println(\"------getName------\"); return \"Tom\"; } @Override public Integer getAge(int id) { System.out.println(\"------getAge------\"); return 10; }} jdk动态代理实现MyInvocationHandler public class MyInvocationHandler implements InvocationHandler { private Object target; //需要代理的真实对象 MyInvocationHandler() { super(); } //给需要代理的真实对象赋值 MyInvacationHandler(Object target) { super(); this.target = target; } @Override pulibc Object invoke(Object o, Method method, Object[] args) throws Throwable { if(\"getName\".equals(method.getName()){ //调用真实对象方法前的操作 System.out.println(\"++++++before\" + method.getName() + \"++++++\"); //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 Object result = method.invoke(target,args); //调用真实对象方法前的操作 System.out.println(\"++++++after\" + method.getName + \"++++++\"); return result; } else { Object result = method.invoke(target.args); return result; } }} 客户端代码 public static void main(String[] args) { //① 我们需要代理的真实对象 UserService userService = new UserServiceImpl(); //② 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的 InvocationHandler invocationHandler = new MyInvocationHandler(userService); //③ 通过Proxy的newProxyInstance方法来创建我们的代理对象 UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass() .getClassLoader(), userService.getClass().getInterfaces(), invocationHandler); System.out.println(userServiceProxy.getClass().getName()); System.out.println(userServiceProxy.getName(1)); System.out.println(UserviceProxy.getAge(1));} 运行结果:$Proxy0++++++before getName++++++------getName------++++++after getName++++++Tom------getAge------10 代码分析可能我们回认为发挥的userServiceProxy对象会是UserService类型的对象,或者是InvocationHandler对象,结果却不是,首先解释一下为什么可以将其转化为UserService类型的对象。原因是:在newProxyInstane()这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么这个代理对象就会实现这组接口,这个时候我们当然可以将这个代理对象转化为这组接口中的任意一个,因为这里的接口是UserService类型,所以就可以将其转换为UserService类型了。同时我们要记住,通过Proxy.newProxyInstance()创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy,然后以数字表示对象的标号。 System.out.println(userServiceProxy.getName(1));System.out.println(UserviceProxy.getAge(1)); 这里是通过代理对象来调用实现的那组接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的invocationHandler中的invoke()方法去执行,而我们的invocationHandler对象又接受了一个UserServiceImpl类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用invocationHandler中的invoke()方法执行。 还可以参考:JDK动态代理实现原理","categories":[{"name":"设计模式","slug":"设计模式","permalink":"http://windynature.oschina.io/categories/设计模式/"}],"tags":[{"name":"动态代理","slug":"动态代理","permalink":"http://windynature.oschina.io/tags/动态代理/"}]}]}
HTML
1
https://gitee.com/windynature/blog.git
git@gitee.com:windynature/blog.git
windynature
blog
blog
master

搜索帮助