|
2012年7月15日
在用 extjs editorgridpanel 进行输入编辑的时候, 默认情况下只支持使用 tab 键可以实现焦点切换, 如果想让editorgridpanel 在编辑时通过方向键来实现焦点跳转切换,只需加上以下代码: //让grid支持方向键盘 by liongis@163.com Ext.override(Ext.grid.CellSelectionModel, { onEditorKey : function(field, e) { var smodel = this; var k = e.getKey(), newCell, g = smodel.grid, ed = g.activeEditor; switch(k){ case e.TAB: e.stopEvent(); ed.completeEdit(); if (e.shiftKey) { newCell = g.walkCells(ed.row, ed.col-1, -1, smodel.acceptsNav, smodel); } else { newCell = g.walkCells(ed.row, ed.col+1, 1, smodel.acceptsNav, smodel); } if (ed.col == 1) { if (e.shiftKey) { newCell = g.walkCells(ed.row, ed.col+1, -1, smodel.acceptsNav, smodel); } else { newCell = g.walkCells(ed.row, ed.col+1, 1, smodel.acceptsNav, smodel); } } break; case e.UP: e.stopEvent(); ed.completeEdit(); newCell = g.walkCells(ed.row-1, ed.col, -1, smodel.acceptsNav, smodel); break; case e.DOWN: e.stopEvent(); ed.completeEdit(); newCell = g.walkCells(ed.row+1, ed.col, 1, smodel.acceptsNav, smodel); break; case e.LEFT: e.stopEvent(); ed.completeEdit(); newCell = g.walkCells(ed.row, ed.col-1, -1, smodel.acceptsNav, smodel); break; case e.RIGHT: e.stopEvent(); ed.completeEdit(); newCell = g.walkCells(ed.row, ed.col+1, 1, smodel.acceptsNav, smodel); break; } if (newCell) { g.startEditing(newCell[0], newCell[1]); } } }); 注意:这里重写的是:CellSelectionModel ,而不是RowSelectionMode 原文出自: http://www.cnblogs.com/liongis/p/3284620.html
序言: 1.本文摘自网络,看控件命名像是4.0以前的版本,但控件属性配置仍然可以借鉴(不足之处,以后项目用到时再续完善)。 Ext.form.TimeField: 配置项: maxValue:列表中允许的最大时间 maxText:当时间大于最大值时的错误提示信息 minValue:列表中允许的最小时间 minText:当时间小于最小值时的错误提示信息 increment:两个相邻选项间的时间间隔,默认为15分钟 format:显示格式,默认为“g:i A”。一般使用“H:i:s” H:带前缀0的24小时 i:带前缀0的分钟 s:带前缀0的秒 invalidText:当时间值非法时显示的提示信息 altFormats:多个时间输入格式组成的字符串,不同的格式之间使用“|”进行分割 Ext.form.FieldSet animCollapse:动画折叠,默认为false checkboxToggle:设置是否显示字段集的checkbox选择框,默认为false checkboxName:指定字段集中用于展开或隐藏字段集面板的checkbox的名字,该属性只有在checkboxToggle为true时生效 labelWidth:字段标签的宽度,可以级联到子容器 layout:布局,默认为form Ext.form.DateFied maxValue:允许选择的最大日期 maxText:当日期大于最大值时的错误提示信息 minValue:允许选择的最小时间 minText:当日期小于最小值时的错误提示信息 format:日期显示格式,默认为“m/d/y”,一般使用“Y-m-d” Y:四位年份 m:带前缀0的月份 d:带前缀0的日期 y:两位年份 n:不带前缀0的月份 j:不带前缀0的日期 w:星期的数字,0表示星期日,1代表星期一 showToday:是否显示今天按钮,默认为true altFormats:多个日期输入格式组成的字符串,不同的格式之间使用“|”进行分割,默认值为'm/d/Y|n/j/Y|n/j/y|m/j /y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d' disabledDates:禁止选择的日期组成的数组 disabledDatesText:选择禁选日期时显示的提示信息 disabledDays:禁止选择的星期组成的数组,0代表星期日,1代表星期一 disabledDaysText:选择禁选星期时显示的提示信息 invalidText:当日期值非法时显示的提示信息 方法: getValue():取得日期值 Ext.form.ComboBox displayField:被显示在下拉框中的字段名 editable:是否可编辑,默认为true forceSelection:输入值是否严格为待选列表中存在的值。如果输入不存在的值,会自动选择第一个最接近的值。 hiddenName:隐藏字段的名字,如果提供该参数则一个隐藏字段将被创建,用来存储所选值,当表单提交时在服务器端可以通过该名字取得列表中的所选值 listWidth:下拉列表的宽度 minListWidth:下拉列表的最小宽度,默认为70像素 loadingText:当下拉框加载数据时显示的提示信息,只有当mode='remote'时才会生效 maxHeight:下拉列表框的最大高度,默认为300像素 minChars:下拉列表框自动选择前用户需要输入的最小字符数量。mode='remote'默认为4,mode='local'默认为0 mode:下拉列表框的数据读取模式。remote读取远程数据,local读取本地数据 pageSize:下拉列表框的分页大小。该项设置只在mode='remote'时生效 queryParam:查询的名字,默认为'query',将被传递到查询字符串中 allQuery:一个发往服务器用来查询全部信息的查询字符串,默认为空字符串'' selectOnFocus:当获得焦点时立刻选择一个已存在的列表项。默认为false,此项只有在editable=true时才会生效 store:列表框绑定的数据源 transform:将页面中已存在的元素转换为组合框 lazyInit:延时初始化下拉列表,默认为true lazyRender:延时渲染,默认为false triggerAction:设置单击触发按钮时执行的默认操作,有效值包括all和query,默认为query,如果设置为all则会执行allQuery中设置的查询 typeAhead:设置在输入过程中是否自动选择匹配的剩余部分文本(选择第一个满足条件的),默认为false value:初始化组合框中的值 valueField:组合框的值字段 valueNotFoundText:值不存在时的提示信息 tpl:Ext模板字符串或模板对象,可以通过该配置项自定义下拉列表的显示方式 方法: clearValue():清空字段当前值 doQuery( String query, Boolean forceAll ): getValue(): getStore(): setValue( String value ): Ext.from.RadioGroup allowBlank: blankText: Ext.form.Radio; getGroupValue(): setValue( value {String/Boolean} ): Ext.form.CheckboxGroup allowBlank:是否允许不选择,默认为true blankText: columns:显示的列数,可选值包括:固定值auto、数值、数组(整数、小数) items:对象数组 vertical:是否垂直方向显示对象,默认为false Ext.form.Checkbox boxLabel:复选框的文字描述 checked:复选框是否被选择,默认为false handler:当checked值改变时触发的函数,函数包含两个参数:checkbox、checked inputValue: 方法: getValue():返回复选框的checked状态 setValue( Boolean/String checked ): Ext.form.NumberField allowDecimals:是否允许输入小数,默认为true allowNegative:是否允许输入负数,默认为true baseChars:输入的有效数字集合,默认为'0123456789' decimalPrecision:数字的精度,默认保留小数点后2位 decimalSeparator:十进制分隔符,默认为'.' maxValue:允许输入的最大数值 maxText:超过最大值之后的提示信息 minValue:允许输入的最小数值 minText:超过最小值之后的提示信息 nanText:输入非有效数值之后的提示信息 Ext.form.TextArea preventScrollbars:是否禁止出现滚动条,默认为false Ext.form.TextField allowBlank:是否允许为空,默认为true blankText:空验证失败后显示的提示信息 emptyText:在一个空字段中默认显示的信息 grow:字段是否自动伸展和收缩,默认为false growMin:收缩的最小宽度 growMax:伸展的最大宽度 inputType:字段类型:默认为text maskRe:用于过滤不匹配字符输入的正则表达式 maxLength:字段允许输入的最大长度 maxLengthText:最大长度验证失败后显示的提示信息 minLength:字段允许输入的最小长度 minLengthText:最小长度验证失败后显示的提示信息 regex:正则表达式 regexText:正则表达式验证失败后显示的提示信息 vtype:验证类型的名字 alpha:限制只能输入字母 alphanum:限制只能输入字母和数字 email url vtypeText:验证失败时的提示信息 validator:自定义验证函数 selectOnFocus:当字段得到焦点时自动选择已存在的文本,默认为false Ext.form.Field name:字段名 value:字段的初始化值 disabled:字段是否不可用,默认为false fieldLabel:字段标签说明 hideLabel:隐藏字段标签,默认为false labelSeparator:字段标签与字段之间的分隔符,默认为':' labelStyle:字段标签样式 inputType:默认为text invalidClass:默认为x-form-invalid invalidText:字段非法文本提示 msgTarget:错误信息显示的位置,默认为qtip qtip:显示一个浮动的提示信息 title:显示一个浏览器的浮动提示信息 under:在字段下方显示一个提示信息 side:在字段右边显示一个提示信息 readOnly:字段是否只读,默认为false validateOnBlur:字段在失去焦点时被验证,默认为true 方法: clearInvalid(): getRawValue() setRawValue( Mixed value ) getValue() setValue( Mixed value ) isDirty():字段值在装载后是否被修改过 isValid( Boolean preventMark ):当前字段值是否合法 markInvalid( [String msg] ) validate() reset() Ext.form.FormPanel items:一个元素或元素数组 buttons:一个按钮配置对象的数组,按钮将被添加到表单页脚中 buttonAlign:按钮的对齐方式,可选值有left、center、right,默认为center labelWidth:表单标签的宽度 labelAlign:表单标签的对齐方式,可选值有left、top、right,默认为left labelSeparator:字段标签与字段之间的分隔符,默认为':' minButtonWidth:按钮的最小宽度,默认为75 方法: getForm() : Ext.form.BasicForm load( Object options ) startMonitoring() stopMonitoring() Ext.form.BaseicForm baseParams:传递到请求中的参数 method:表单的提交方式,有效值包括GET、POST url:表单默认的提交路径 fileUpload:表单是否进行文件上传 timeout:表单动作的超时时间,默认为30秒 trackResetOnLoad:是否在表单初次创建时清楚数据 方法: doAction( String/Object actionName, [Object options] ):执行一个预订的动作,可用选项包括: url:动作提交的路径 method:表单的提交方式,有效值包括GET、POST params:传递到请求中的参数 headers: success:执行成功后回调的函数,包括两个参数:form和action failure:执行失败后回调的函数,包括两个参数:form和action clientValidation:是否客户端验证 clearInvalid():清除表单中所有的无效验证信息 findField( String id ):查找表单字段 getValues( [Boolean asString] ): isDirty():表单数据是否被更改过 isValid():客户端验证是否成功 load( Object options ):执行表单读取动作 loadRecord( Record record ):从一个数据记录中读取数据到表单中 markInvalid( Array/Object errors ):成批设置表单字段为验证无效 reset():重置表单 setValues( Array/Object values ):成批设置表单字段值 submit( Object options ):执行表单提交动作 updateRecord( Record record ):持久化表单数据到记录集中 Ext.form.Action success:执行成功后回调的函数,包括两个参数:form和action failure:执行失败后回调的函数,包括两个参数:form和action method:表单的提交方式,有效值包括GET、POST params:传递到请求中的参数 url:动作提交的路径 waitMsg:动作执行时显示的等待信息 属性: Action.CLIENT_INVALID:客户端验证错误 Action.CONNECT_FAILURE:通信错误 Action.LOAD_FAILURE:加载数据时,没有包含data属性的字段被返回 Action.SERVER_INVALID:服务端验证错误 failureType:错误类型 result:包含布尔类型的success属性和其他属性,如{success: true, msg: 'ok'} type:动作类型,可选值有submit和load Ext.form.Action.Submit:返回的信息中要包含一个布尔类型的success属性和一个可选的errors属性 Ext.form.Action.Load:返回的信息中要包含一个布尔类型的success属性和一个data属性 Ext.grid.EditorGridPanel clicksToEdit:设置点击单元格进入编辑模式的点击次数,默认为2 autoEncode:是否自动编码/解码HTML内容,默认为false selModel:默认为Ext.grid.CellSelectionModel 主要方法: startEditing( Number rowIndex, Number colIndex ):开始编辑指定单元格 stopEditing( [Boolean cancel] ):结束编辑操作 Ext.grid.GroupinView enableGroupingMenu:是否在表头菜单中进行分组控制,默认为true groupByText:表头菜单中分组控制的菜单文字,默认为'Group By This Field' enableNoGroups:是否允许用户关闭分组功能,默认为true showGroupsText:在表头菜单中启用分组和禁用分组的菜单文字,默认为'Show in Groups' groupTextTpl:用于渲染分组信息的模板,默认为'{text}',常用的可选值有: text:列标题:组字段值 gvalue:组字段的值 startRow:组行索引 enableGrouping:是否对数据分组,默认为true hideGroupedColumn:是否隐藏分组列,默认为false ignoreAdd:在向表格中添加数据时是否刷新表格,默认为false showGroupName:是否在分组行上显示分组字段的名字,默认为true startCollapsed:初次显示时分组是否处于收缩状态,默认为false 主要方法: collapseAllGroups():收缩所有分组行 expandAllGroups():展开所有分组行 getGroupId( String value ):根据分组字段值取得组id toggleAllGroups( [Boolean expanded] ):切换所有分组行的展开或收缩状态 toggleGroup( String groupId, [Boolean expanded] ):切换指定分组行的展开或收缩状态 2、Ext.data.GroupingStore groupField:分组字段 groupOnSort:是否在分组字段上排序,默认为false remoteGroup:是否远程分组数据,默认为false。如果是远程分组数据,则通过groupBy参数发送分组字段名 Ext.grid.GridPanel: store:表格的数据集 columns:表格列模式的配置数组,可自动创建ColumnModel列模式 autoExpandColumn:自动充满表格未用空间的列,参数为列id,该id不能为0 stripeRows:表格是否隔行换色,默认为false cm、colModel:表格的列模式,渲染表格时必须设置该配置项 sm、selModel:表格的选择模式,默认为Ext.grid.RowSelectionModel enableHdMenu:是否显示表头的上下文菜单,默认为true enableColumnHide:是否允许通过标题中的上下文菜单隐藏列,默认为true loadMask:是否在加载数据时显示遮罩效果,默认为false view:表格视图,默认为Ext.grid.GridView viewConfig:表格视图的配置对象 autoExpandMax:自动扩充列的最大宽度,默认为1000 autoExpandMin:自动扩充列的最小宽度,默认为50 columnLines:是否显示列分割线,默认为false disableSelection:是否禁止行选择,默认为false enableColumnMove:是否允许拖放列,默认为true enableColumnResize:是否允许改变列宽,默认为true hideHeaders:是否隐藏表头,默认为false maxHeight:最大高度 minColumnWidth:最小列宽,默认为25 trackMouseOver:是否高亮显示鼠标所在的行,默认为true 主要方法: getColumnModel():取得列模式 getSelectionModel():取得选择模式 getStore():取得数据集 getView():取得视图对象 reconfigure( Ext.data.Store store, Ext.grid.ColumnModel colModel ):使用一个新的数据集和列模式重新配置表格组件
2、Ext.grid.Column 主要配置项: id:列id header:表头文字 dataIndex:设置列与数据集中数据记录的对应关系,值为数据记录中的字段名称。如果没有设置该项则使用列索引与数据记录中字段的索引进行对应 width:列宽 align:列数据的对齐方式 hidden:是否隐藏列,默认为false fixed:是否固定列宽,默认为false menuDisabled:是否禁用列的上下文菜单,默认为false resizable:是否允许改变列宽,默认为true sortable:是否允许排序,默认为true renderer:设置列的自定义单元格渲染函数 传入函数的参数有: value:数据的原始值 metadata:元数据对象,用于设置单元格的样式和属性,该对象包含的属性有: css:应用到单元格TD元素上的样式名称 attr:一个HTML属性定义字符串,例如'style="color:blue"' record:当前数据记录对象 rowIndex:单元格的行索引 colIndex:单元格的列索引 store:数据集对象 xtype:列渲染器类型,默认为gridcolumn,其它可选值有booleancolumn、numbercolumn、datecolumn、templatecolumn等 editable:是否可编辑,默认为true editor:编辑器 groupName: emptyGroupText: groupable:
3、Ext.grid.ColumnModel 主要配置项: columns:字段数组 defaultSortable:是否进行默认排序,默认为false defaultWidth:默认宽度 主要方法: findColumnIndex( String col ):根据给定的dataIndex查找列索引 getColumnById( String id ):取得指定id对应的列 getColumnCount( Boolean visibleOnly ):取得列总数 getColumnHeader( Number col ):取得列的表头 getColumnId( Number index ):取得列id getDataIndex( Number col ):取得列对应的数据字段名 getIndexById( String id ):取得列索引 getTotalWidth( Boolean includeHidden ) isCellEditable( Number colIndex, Number rowIndex ) isFixed() isHidden( Number colIndex ) setColumnHeader( Number col, String header ) setColumnWidth( Number col, Number width, Boolean suppressEvent ) setDataIndex( Number col, String dataIndex ) setEditable( Number col, Boolean editable ) setEditor( Number col, Object editor ) setHidden( Number colIndex, Boolean hidden ) setRenderer( Number col, Function fn ) 4、Ext.grid.AbstractSelectionModel 主要方法: lock():锁定选择区域 unlock():解锁选择区域 isLocked():当前选择区域是否被锁定 5、Ext.grid.CellSelectionModel 主要方法: clearSelections( Boolean preventNotify ):清除选择区域 getSelectedCell():取得当前选择的单元格,返回一数组,其格式:[rowIndex, colIndex] hasSelection():当前是否有选择区域 select( Number rowIndex, Number colIndex, [Boolean preventViewNotify], [Boolean preventFocus], [Ext.data.Record r] ):选择指定单元格 6、Ext.grid.RowSelectionModel 主要配置项: singleSelect:是否单选模式,默认为false,即可以选择多条数据
主要方法: clearSelections( [Boolean fast] ):清除所有选择区域 deselectRange( Number startRow, Number endRow ):取消范围内的行选择 deselectRow( Number row, [Boolean preventViewNotify] ):取消指定行的选择状态 each( Function fn, [Object scope] ):遍历所有选择行,并调用指定函数。当前被选行将传入该函数中 getCount():得到选择的总行数 getSelected():得到第一个被选记录 getSelections():得到所有被选记录的数组 hasNext():判断当前被选行之后是否还有记录可以选择 hasPrevious():判断当前被选行之前是否还有记录可以选择 hasSelection():是否已选择了数据 isIdSelected( String id ):判断指定id的记录是否被选择 isSelected( Number/Record index ):判断指定记录或记录索引的数据是否被选择 selectAll():选择所有行 selectFirstRow():选择第一行 selectLastRow( [Boolean keepExisting] ):选择最后行 keepExisting:是否保持已有的选择 selectNext( [Boolean keepExisting] ):选择当前选择行的下一行 selectPrevious( [Boolean keepExisting] ):选择当前选择行的上一行 selectRange( Number startRow, Number endRow, [Boolean keepExisting] ):选择范围内的所有行 selectRecords( Array records, [Boolean keepExisting] ):选择一组指定记录 selectRow( Number row, [Boolean keepExisting], [Boolean preventViewNotify] ):选择一行 row:行索引 selectRows( Array rows, [Boolean keepExisting] ):选择多行 rows:行索引数组 7、Ext.grid.CheckboxSelectionModel 主要配置项: singleSelect:是否单选模式,默认为false,即可以选择多条数据 checkOnly:是否只能通过点击checkbox列进行选择,默认为false sortable:是否允许checkbox列排序,默认为false width:checkbox列的宽度,默认为20 8、Ext.grid.RowNumberer 主要配置项: header:行号列表头显示的内容 width:列宽,默认为23 9、Ext.grid.GridView 主要配置项: enableRowBody:是否包含行体 sortAscText:表格标题菜单中升序的文字描述 sortDescText:表格标题菜单中降序的文字描述 columnsText:表格标题菜单中列对应的文字描述 autoFill:是否自动扩展列以充满整个表格,默认为false forceFit:是否强制调整表格列宽以适用表格的整体宽度,防止出现水平滚动条,默认为false
主要方法: focusCell( Number row, Number col ):将焦点移到指定单元格 focusRow( Number row ):将焦点移动指定行 getCell( Number row, Number col ):取得指定单元格对应的td元素 getHeaderCell( Number index ):取得指定表头对应的td元素 getRow( Number index ):取得指定行对应的tr元素 getRowClass( Record record, Number index, Object rowParams, Store store ):得到附加到表格行上的样式名 record:当前行的数据记录对象 index:当前行的索引 rowParams:渲染时传入到行模板中的配置对象,通过它可以为行体定制样式,该对象只在enableRowBody为true时才生效,可能的属性如下: body:渲染到行体中的HTML代码片段 bodyStyle:应用到行体tr元素style属性的字符串 cols:应用到行体td元素colspan属性的值,默认为总列数 store:表格数据集 refresh( [Boolean headersToo] ):刷新表格组件 scrollToTop():滚动表格到顶端 Ext.TabPanel: activeTab:初始激活的tab,索引或者id值,默认为none autoTabs:是否自动将带有'x-tab'样式类的div转成tabs添加到TabPanel中,默认为false。 当该配置项设为true时,需要设置deferredRender为false,还必须使用applyTo。 deferredRender:是否延迟渲染,默认为true。 autoTabSelector:默认为'div.x-tab'。 resizeTabs:是否可以改变tab的尺寸,默认为false。 minTabWidth:tab的最小宽度,默认为30。 tabWidth:每个新增加的tab宽度,默认为120。 tabTip:tab的提示信息 tabPosition:tab位置,可选值有top、bottom,默认为top。 enableTabScroll:是否允许Tab溢出时可以滚动,默认为false。 closable:tab是否可关闭,默认为false scrollDuration:每次的滚动时长,默认为0.35毫秒。 scrollIncrement:每次的滚动步长,默认为100像素。 wheelIncrement:每次鼠标滑轮的滚动步长,默认为20像素。 2、主要方法: activate( String/Panel tab ) getActiveTab():获取当前活动的tab get( String/Number key ):根据组件id或者索引获取组件 getItem(String id):根据tab id获取tab setActiveTab( String/Number item ) remove( Component/String component, [Boolean autoDestroy] ) removeAll( [Boolean autoDestroy] ) 在使用TabPanel时需要注意: 1、在创建Ext.TabPanel时deferredRender配置项经常会被忽略。该配置项的默认值是true。true表示只有在用户第一次访问 选项卡时,该选项卡的panel才会被渲染。 所以当我们有可能使用脚本操作选项卡时,谨记将该配置项设置为false。 2、在FormPanel中使用TabPanel,如果在TabPanel中不定义deferredRender的值为false,那么,当你使用 Load方法为Form加载数据,或使用setValue为没有激活过的Panel的控件赋值时,将会发生错误。原因是,在默认设置下 deferredRender为true,TabPanel并不会渲染所有Panel上的控件,只有在该Panel被激活时才渲染控件,所以当你为这些控 件设置数据时,将会找不到这些控件,会出现错误。因而,在FormPanel中使用TabPanel,一定要在TabPanel中设置 deferredRender的值为false,强制TabPanel在Layout渲染时同时渲染所有Panel上的控件。 本文转自: http://www.cnblogs.com/knowledgesea/p/3284404.html
摘要: 前言:最近公司有个Web要发布,但是以前都是由实施到甲方去发布,配置,这几天有点闲,同事让我搞一个一键发布,就和安装软件那样的程序,好让实施直接 配置一下数据库就可以了,然后到网上搜了下,找到一些相关的教程,现在整理了一下,花了一个下午的时间来写笔记,写好了,首先奉献给博客园的小伙伴们,和 大伙儿分享一下,好了,下面进入主题~~~ 1,首先打开VS2010,新建一个项目,如图1-1所示: &nbs... 阅读全文
大家可以参考下这个网站http://eoffice.im.fju.edu.tw/phpbb/viewtopic.php?p=28685
1.先启动项目上的h2/bin下的h2.bat或h2w.bat文件,把h2数据库启动起来 2.SSH2框架和h2数据库整合方法 2.1先在数据库下创建 schema目录(相当于一个数据库实例) create schema fdrkftcode 目的是解决这种异常org.h2.jdbc.JdbcSQLException: Schema "fdrkftcode" not found; ... 2.2在schema目录下创建表,如创建系统用户表admin create table fdrkftcode.admin( id int primary key, adminname varchar(50), username varchar(50), userpwd varchar(50), adminrights varchar(50), createdate datetime, usedtimes int, lastlogin datetime, curstatus int, remark varchar(200) ) 3.为了使用hibernate操作h2,需要作如下设置,在sql编辑窗口输入下面这些脚本 对于实体pojo对象的映射,我是用的annotation,关键是id主键的映射,如下: @Column(name = "ID", nullable = false) @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ABC_ID_SEQ") @SequenceGenerator(name = "ABC_ID_SEQ", sequenceName = "ABC_ID_SEQ") protected Long id; 注意这里的GeneratedValue和SequenceGenerator的使用,这属于JPA规范,全部来自javax.persisten 4.配置applicationContext.xml文件,主要有三个地方要注意: 4.1修改连接数据库的JDBC驱动 driverClass的值为org.h2.Driver 4.2修改连接数据库所用的URL字符串 jdbcUrl的值为jdbc:h2:tcp://localhost/~/FDRKFTCODE;MODE=MySQL;AUTO_SERVER=TRUE 4.3修改Hibernate的数据库方言hibernate.dialect为org.hibernate.dialect.H2Dialect 5.h2数据库一些常用操作 5.1帮助命令help 5.2表中某字段重命名 ALTER TABLE fdrkftcode.admin ALTER COLUMN usepwd rename to userpwd 5.3表中新增字段 ALTER TABLE fdrkftcode.admin ADD IF NOT EXISTS abc varchar(50) 5.4表中删除字段 ALTER TABLE fdrkftcode.admin DROP COLUMN IF EXISTS abc 5.5查找表中记录 SELECT * from fdrkftcode.admin 5.6往表中插入记录 INSERT INTO fdrkftcode.admin VALUES (1,'管理员','admin','admin','10000000000000000000','2013-05-1 00:12:34',3,'2013-05-1 15:32:57',1,'超过级管理员') 5.7修改表中某记录 UPDATE fdrkftcode.admin SET fdrkftcode.admin.adminname='超级管理员' where fdrkftcode.admin.id=1 5.8删除表中某记录 DELETE FROM fdrkftcode.admin WHERE fdrkftcode.admin.id=1 6.下面是我项目的applicationContext.xml配置方法,大家可以参考下 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- 定义使用C3P0连接池的数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 指定连接数据库的JDBC驱动 --> <property name="driverClass"> <value>org.h2.Driver</value> </property> <!-- 连接数据库所用的URL --> <property name="jdbcUrl"> <value>jdbc:h2:tcp://localhost/~/FDRKFTCODE;MODE=MySQL;AUTO_SERVER=TRUE</value> </property> <!-- 连接数据库的用户名 --> <property name="user"> <value>sa</value> </property> <!-- 连接数据库的密码 --> <property name="password"> <value></value> </property> <!-- 设置数据库连接池的最大连接数 --> <property name="maxPoolSize"> <value>50</value> </property> <!-- 设置数据库连接池的最小连接数 --> <property name="minPoolSize"> <value>5</value> </property> <!-- 设置数据库连接池的初始化连接数 --> <property name="initialPoolSize"> <value>5</value> </property> <!-- 设置数据库连接池的连接的最大空闲时间,单位为秒 --> <property name="maxIdleTime"> <value>20</value> </property> </bean> <!-- 定义Hibernate的SessionFactory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <!-- 依赖注入上面定义的数据源dataSource --> <property name="dataSource" ref="dataSource"/> <!-- 注册Hibernate的ORM映射文件 --> <property name="mappingResources"> <list> <value>com/sungoal/ORM/Admin.hbm.xml</value> </list> </property> <!-- 设置Hibernate的相关属性 --> <property name="hibernateProperties"> <props> <!-- 设置Hibernate的数据库方言 --> <prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop> <!-- 设置Hibernate是否在控制台输出SQL语句,开发调试阶段通常设为true --> <prop key="show_sql">true</prop> <!-- 设置Hibernate一个提交批次中的最大SQL语句数 --> <prop key="hibernate.jdbc.batch_size">50</prop> </props> </property> </bean> <!--定义Hibernate的事务管理器HibernateTransactionManager --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <!-- 依赖注入上面定义的sessionFactory --> <property name="sessionFactory" ref="sessionFactory"/> </bean> <!-- 装配通用数据库访问类BaseDAOImpl --> <bean id="dao" class="com.sungoal.DAO.BaseDAOImpl"> <!-- 依赖注入上面定义的sessionFactory --> <property name="sessionFactory" ref="sessionFactory"/> </bean> <!-- 部署系统用户管理业务控制器AdminAction --> <bean id="adminAction" class="com.sungoal.struts.action.AdminAction" scope="prototype"> <property name="dao" ref="dao"/> </bean> </beans>
hibernate.properties ###################### ### Query Language ### ###################### ## define query language constants / function names hibernate.query.substitutions yes 'Y', no 'N' ## select the classic query parser #hibernate.query.factory_class org.hibernate.hql.classic.ClassicQueryTranslatorFactory ################# ### Platforms ### ################# ## JNDI Datasource #hibernate.connection.datasource jdbc/test #hibernate.connection.username db2 #hibernate.connection.password db2 ## HypersonicSQL hibernate.dialect org.hibernate.dialect.HSQLDialect hibernate.connection.driver_class org.hsqldb.jdbcDriver hibernate.connection.username sa hibernate.connection.password hibernate.connection.url jdbc:hsqldb:./build/db/hsqldb/hibernate #hibernate.connection.url jdbc:hsqldb:hsql://localhost #hibernate.connection.url jdbc:hsqldb:test ## H2 (www.h2database.com) #hibernate.dialect org.hibernate.dialect.H2Dialect #hibernate.connection.driver_class org.h2.Driver #hibernate.connection.username sa #hibernate.connection.password #hibernate.connection.url jdbc:h2:mem:./build/db/h2/hibernate #hibernate.connection.url jdbc:h2:testdb/h2test #hibernate.connection.url jdbc:h2:mem:imdb1 #hibernate.connection.url jdbc:h2:tcp://dbserv:8084/sample; #hibernate.connection.url jdbc:h2:ssl://secureserv:8085/sample; #hibernate.connection.url jdbc:h2:ssl://secureserv/testdb;cipher=AES ## MySQL #hibernate.dialect org.hibernate.dialect.MySQLDialect #hibernate.dialect org.hibernate.dialect.MySQLInnoDBDialect #hibernate.dialect org.hibernate.dialect.MySQLMyISAMDialect #hibernate.connection.driver_class com.mysql.jdbc.Driver #hibernate.connection.url jdbc:mysql:///test #hibernate.connection.username gavin #hibernate.connection.password ## Oracle #hibernate.dialect org.hibernate.dialect.OracleDialect #hibernate.dialect org.hibernate.dialect.Oracle9Dialect #hibernate.connection.driver_class oracle.jdbc.driver.OracleDriver #hibernate.connection.username ora #hibernate.connection.password ora #hibernate.connection.url jdbc:oracle:thin:@localhost:1521:orcl #hibernate.connection.url jdbc:oracle:thin:@localhost:1522:XE ## PostgreSQL #hibernate.dialect org.hibernate.dialect.PostgreSQLDialect #hibernate.connection.driver_class org.postgresql.Driver #hibernate.connection.url jdbc:postgresql:template1 #hibernate.connection.username pg #hibernate.connection.password ## DB2 #hibernate.dialect org.hibernate.dialect.DB2Dialect #hibernate.connection.driver_class com.ibm.db2.jcc.DB2Driver #hibernate.connection.driver_class COM.ibm.db2.jdbc.app.DB2Driver #hibernate.connection.url jdbc:db2://localhost:50000/somename #hibernate.connection.url jdbc:db2:somename #hibernate.connection.username db2 #hibernate.connection.password db2 ## TimesTen #hibernate.dialect org.hibernate.dialect.TimesTenDialect #hibernate.connection.driver_class com.timesten.jdbc.TimesTenDriver #hibernate.connection.url jdbc:timesten:direct:test #hibernate.connection.username #hibernate.connection.password ## DB2/400 #hibernate.dialect org.hibernate.dialect.DB2400Dialect #hibernate.connection.username user #hibernate.connection.password password ## Native driver #hibernate.connection.driver_class COM.ibm.db2.jdbc.app.DB2Driver #hibernate.connection.url jdbc:db2://systemname ## Toolbox driver #hibernate.connection.driver_class com.ibm.as400.access.AS400JDBCDriver #hibernate.connection.url jdbc:as400://systemname ## Derby (not supported!) #hibernate.dialect org.hibernate.dialect.DerbyDialect #hibernate.connection.driver_class org.apache.derby.jdbc.EmbeddedDriver #hibernate.connection.username #hibernate.connection.password #hibernate.connection.url jdbc:derby:build/db/derby/hibernate;create=true ## Sybase #hibernate.dialect org.hibernate.dialect.SybaseDialect #hibernate.connection.driver_class com.sybase.jdbc2.jdbc.SybDriver #hibernate.connection.username sa #hibernate.connection.password sasasa #hibernate.connection.url jdbc:sybase:Tds:co3061835-a:5000/tempdb ## Mckoi SQL #hibernate.dialect org.hibernate.dialect.MckoiDialect #hibernate.connection.driver_class com.mckoi.JDBCDriver #hibernate.connection.url jdbc:mckoi:/// #hibernate.connection.url jdbc:mckoi:local://C:/mckoi1.0.3/db.conf #hibernate.connection.username admin #hibernate.connection.password nimda ## SAP DB #hibernate.dialect org.hibernate.dialect.SAPDBDialect #hibernate.connection.driver_class com.sap.dbtech.jdbc.DriverSapDB #hibernate.connection.url jdbc:sapdb://localhost/TST #hibernate.connection.username TEST #hibernate.connection.password TEST #hibernate.query.substitutions yes 'Y', no 'N' ## MS SQL Server #hibernate.dialect org.hibernate.dialect.SQLServerDialect #hibernate.connection.username sa #hibernate.connection.password sa ## JSQL Driver #hibernate.connection.driver_class com.jnetdirect.jsql.JSQLDriver #hibernate.connection.url jdbc:JSQLConnect://1E1/test ## JTURBO Driver #hibernate.connection.driver_class com.newatlanta.jturbo.driver.Driver #hibernate.connection.url jdbc:JTurbo://1E1:1433/test ## WebLogic Driver #hibernate.connection.driver_class weblogic.jdbc.mssqlserver4.Driver #hibernate.connection.url jdbc:weblogic:mssqlserver4:1E1:1433 ## Microsoft Driver (not recommended!) #hibernate.connection.driver_class com.microsoft.jdbc.sqlserver.SQLServerDriver #hibernate.connection.url jdbc:microsoft:sqlserver://1E1;DatabaseName=test;SelectMethod=cursor ## The New Microsoft Driver #hibernate.connection.driver_class com.microsoft.sqlserver.jdbc.SQLServerDriver #hibernate.connection.url jdbc:sqlserver://localhost ## jTDS (since version 0.9) #hibernate.connection.driver_class net.sourceforge.jtds.jdbc.Driver #hibernate.connection.url jdbc:jtds:sqlserver://1E1/test ## Interbase #hibernate.dialect org.hibernate.dialect.InterbaseDialect #hibernate.connection.username sysdba #hibernate.connection.password masterkey ## DO NOT specify hibernate.connection.sqlDialect ## InterClient #hibernate.connection.driver_class interbase.interclient.Driver #hibernate.connection.url jdbc:interbase://localhost:3060/C:/firebird/test.gdb ## Pure Java #hibernate.connection.driver_class org.firebirdsql.jdbc.FBDriver #hibernate.connection.url jdbc:firebirdsql:localhost/3050:/firebird/test.gdb ## Pointbase #hibernate.dialect org.hibernate.dialect.PointbaseDialect #hibernate.connection.driver_class com.pointbase.jdbc.jdbcUniversalDriver #hibernate.connection.url jdbc:pointbase:embedded:sample #hibernate.connection.username PBPUBLIC #hibernate.connection.password PBPUBLIC ## Ingres ## older versions (before Ingress 2006) #hibernate.dialect org.hibernate.dialect.IngresDialect #hibernate.connection.driver_class ca.edbc.jdbc.EdbcDriver #hibernate.connection.url jdbc:edbc://localhost:II7/database #hibernate.connection.username user #hibernate.connection.password password ## Ingres 2006 or later #hibernate.dialect org.hibernate.dialect.IngresDialect #hibernate.connection.driver_class com.ingres.jdbc.IngresDriver #hibernate.connection.url jdbc:ingres://localhost:II7/database;CURSOR=READONLY;auto=multi #hibernate.connection.username user #hibernate.connection.password password ## Mimer SQL #hibernate.dialect org.hibernate.dialect.MimerSQLDialect #hibernate.connection.driver_class com.mimer.jdbc.Driver #hibernate.connection.url jdbc:mimer:multi1 #hibernate.connection.username hibernate #hibernate.connection.password hibernate ## InterSystems Cache #hibernate.dialect org.hibernate.dialect.Cache71Dialect #hibernate.connection.driver_class com.intersys.jdbc.CacheDriver #hibernate.connection.username _SYSTEM #hibernate.connection.password SYS #hibernate.connection.url jdbc:Cache://127.0.0.1:1972/HIBERNATE ################################# ### Hibernate Connection Pool ### ################################# hibernate.connection.pool_size 1 ########################### ### C3P0 Connection Pool### ########################### #hibernate.c3p0.max_size 2 #hibernate.c3p0.min_size 2 #hibernate.c3p0.timeout 5000 #hibernate.c3p0.max_statements 100 #hibernate.c3p0.idle_test_period 3000 #hibernate.c3p0.acquire_increment 2 #hibernate.c3p0.validate false ############################## ### Proxool Connection Pool### ############################## ## Properties for external configuration of Proxool hibernate.proxool.pool_alias pool1 ## Only need one of the following #hibernate.proxool.existing_pool true #hibernate.proxool.xml proxool.xml #hibernate.proxool.properties proxool.properties ################################# ### Plugin ConnectionProvider ### ################################# ## use a custom ConnectionProvider (if not set, Hibernate will choose a built-in ConnectionProvider using hueristics) #hibernate.connection.provider_class org.hibernate.connection.DriverManagerConnectionProvider #hibernate.connection.provider_class org.hibernate.connection.DatasourceConnectionProvider #hibernate.connection.provider_class org.hibernate.connection.C3P0ConnectionProvider #hibernate.connection.provider_class org.hibernate.connection.ProxoolConnectionProvider ####################### ### Transaction API ### ####################### ## Enable automatic flush during the JTA beforeCompletion() callback ## (This setting is relevant with or without the Transaction API) #hibernate.transaction.flush_before_completion ## Enable automatic session close at the end of transaction ## (This setting is relevant with or without the Transaction API) #hibernate.transaction.auto_close_session ## the Transaction API abstracts application code from the underlying JTA or JDBC transactions #hibernate.transaction.factory_class org.hibernate.transaction.JTATransactionFactory #hibernate.transaction.factory_class org.hibernate.transaction.JDBCTransactionFactory ## to use JTATransactionFactory, Hibernate must be able to locate the UserTransaction in JNDI ## default is java:comp/UserTransaction ## you do NOT need this setting if you specify hibernate.transaction.manager_lookup_class #jta.UserTransaction jta/usertransaction #jta.UserTransaction javax.transaction.UserTransaction #jta.UserTransaction UserTransaction ## to use the second-level cache with JTA, Hibernate must be able to obtain the JTA TransactionManager #hibernate.transaction.manager_lookup_class org.hibernate.transaction.JBossTransactionManagerLookup #hibernate.transaction.manager_lookup_class org.hibernate.transaction.WeblogicTransactionManagerLookup #hibernate.transaction.manager_lookup_class org.hibernate.transaction.WebSphereTransactionManagerLookup #hibernate.transaction.manager_lookup_class org.hibernate.transaction.OrionTransactionManagerLookup #hibernate.transaction.manager_lookup_class org.hibernate.transaction.ResinTransactionManagerLookup ############################## ### Miscellaneous Settings ### ############################## ## print all generated SQL to the console #hibernate.show_sql true ## format SQL in log and console hibernate.format_sql true ## add comments to the generated SQL #hibernate.use_sql_comments true ## generate statistics #hibernate.generate_statistics true ## auto schema export #hibernate.hbm2ddl.auto create-drop #hibernate.hbm2ddl.auto create #hibernate.hbm2ddl.auto update #hibernate.hbm2ddl.auto validate ## specify a default schema and catalog for unqualified tablenames #hibernate.default_schema test #hibernate.default_catalog test ## enable ordering of SQL UPDATEs by primary key #hibernate.order_updates true ## set the maximum depth of the outer join fetch tree hibernate.max_fetch_depth 1 ## set the default batch size for batch fetching #hibernate.default_batch_fetch_size 8 ## rollback generated identifier values of deleted entities to default values #hibernate.use_identifer_rollback true ## enable bytecode reflection optimizer (disabled by default) #hibernate.bytecode.use_reflection_optimizer true ##################### ### JDBC Settings ### ##################### ## specify a JDBC isolation level #hibernate.connection.isolation 4 ## enable JDBC autocommit (not recommended!) #hibernate.connection.autocommit true ## set the JDBC fetch size #hibernate.jdbc.fetch_size 25 ## set the maximum JDBC 2 batch size (a nonzero value enables batching) #hibernate.jdbc.batch_size 5 #hibernate.jdbc.batch_size 0 ## enable batch updates even for versioned data hibernate.jdbc.batch_versioned_data true ## enable use of JDBC 2 scrollable ResultSets (specifying a Dialect will cause Hibernate to use a sensible default) #hibernate.jdbc.use_scrollable_resultset true ## use streams when writing binary types to / from JDBC hibernate.jdbc.use_streams_for_binary true ## use JDBC 3 PreparedStatement.getGeneratedKeys() to get the identifier of an inserted row #hibernate.jdbc.use_get_generated_keys false ## choose a custom JDBC batcher # hibernate.jdbc.factory_class ## enable JDBC result set column alias caching ## (minor performance enhancement for broken JDBC drivers) # hibernate.jdbc.wrap_result_sets ## choose a custom SQL exception converter #hibernate.jdbc.sql_exception_converter ########################## ### Second-level Cache ### ########################## ## optimize chache for minimal "puts" instead of minimal "gets" (good for clustered cache) #hibernate.cache.use_minimal_puts true ## set a prefix for cache region names hibernate.cache.region_prefix hibernate.test ## disable the second-level cache #hibernate.cache.use_second_level_cache false ## enable the query cache #hibernate.cache.use_query_cache true ## store the second-level cache entries in a more human-friendly format #hibernate.cache.use_structured_entries true ## choose a cache implementation #hibernate.cache.provider_class org.hibernate.cache.EhCacheProvider #hibernate.cache.provider_class org.hibernate.cache.EmptyCacheProvider hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider #hibernate.cache.provider_class org.hibernate.cache.TreeCacheProvider #hibernate.cache.provider_class org.hibernate.cache.OSCacheProvider #hibernate.cache.provider_class org.hibernate.cache.SwarmCacheProvider ## choose a custom query cache implementation #hibernate.cache.query_cache_factory ############ ### JNDI ### ############ ## specify a JNDI name for the SessionFactory #hibernate.session_factory_name hibernate/session_factory ## Hibernate uses JNDI to bind a name to a SessionFactory and to look up the JTA UserTransaction; ## if hibernate.jndi.* are not specified, Hibernate will use the default InitialContext() which ## is the best approach in an application server #file system #hibernate.jndi.class com.sun.jndi.fscontext.RefFSContextFactory #hibernate.jndi.url file:/ #WebSphere #hibernate.jndi.class com.ibm.websphere.naming.WsnInitialContextFactory #hibernate.jndi.url iiop://localhost:900/
摘要: 这几天学习了一下Spring Security3.1,从官网下载了Spring Security3.1版本进行练习,经过多次尝试才摸清了其中的一些原理。本人不才,希望能帮助大家。还有,这次我第二次写博客啊,文体不是很行。希望 能让观看者不产生疲惫的感觉,我已经心满意足了。 一、数据库结构 先来看一下数据库结构,采用的是基于角色-资源-用户的权限... 阅读全文
我们知道,EXT的全部js是比较大的,一个ext-all-debug.js就达2m多,它的压缩版(去掉js中的换行及空格),也达600多k,这对于在网速不太快的时,下载js就得漫长的等待。 JOffice中的日历任务控件,js多达四五个,每个js大小都达70多k,尽管我们采用了后加载的方式,则当用户点击我的任务功能时,才下载该js,但这样仍然很慢,因为下载的js很慢 ,鉴于此,在互联网上使用类似Joffice类似的程序,速度会使很多开发商不敢选用ext作为开发技术。据本人当时参与移动一个内部采购平台的开发,就是因为其运行程序慢,遭到移动的终端用户的弃骂, 所以,要想用EXT来开发应用,需要解决其运行慢的特点。
我们可以从以下几种方法来提高应用程序的运行速度:
一.前期尽量少加载js.
这点在Joffice中有比较好的运用,采用的是由ScriptMgr.load方法来完成,加载完成后,其会在body中插入一个div,只要当前页面不被刷新,下次再访问该功能时,不需要再加载js function $ImportJs(viewName,callback) { var b = document.getElementById(viewName+'-hiden'); if (b != null) { var view = eval('new ' + viewName + '()'); callback.call(this, view); } else { var jsArr = eval('App.importJs.' + viewName); if(jsArr==undefined){ var view = eval('new ' + viewName + '()'); callback.call(this, view); return ; } ScriptMgr.load({ scripts : jsArr, callback : function() { Ext.DomHelper.append(document.body,"<div id='" + viewName + "-hiden' style='display:none'></div>"); var view = eval('new ' + viewName + '()'); callback.call(this, view); } }); } } package com.htsoft.core.web.filter; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class GzipJsFilter implements Filter { Map headers = new HashMap(); public void destroy() { } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { if(req instanceof HttpServletRequest) { doFilter((HttpServletRequest)req, (HttpServletResponse)res, chain); }else { chain.doFilter(req, res); } } public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); for(Iterator it = headers.entrySet().iterator();it.hasNext();) { Map.Entry entry = (Map.Entry)it.next(); response.addHeader((String)entry.getKey(),(String)entry.getValue()); } chain.doFilter(request, response); } public void init(FilterConfig config) throws ServletException { String headersStr = config.getInitParameter("headers"); String[] headers = headersStr.split(","); for(int i = 0; i < headers.length; i++) { String[] temp = headers[i].split("="); this.headers.put(temp[0].trim(), temp[1].trim()); } } }
3.在WEB.xml 文件中,添加以下配置: <filter> <filter-name>GzipJsFilter</filter-name> <filter-class>com.htsoft.core.web.filter.GzipJsFilter</filter-class> <init-param> <param-name>headers</param-name> <param-value>Content-Encoding=gzip</param-value> </init-param> </filter> <filter-mapping> <filter-name>GzipJsFilter</filter-name> <url-pattern>*.gzjs</url-pattern> lt;/filter-mapping> <servlet-mapping>
4.在index.jsp中引入该压缩文件: <script type="text/javascript" src="<%=request.getContextPath()%>/ext3/ext-all.gzjs"></script> 可以看到浏览器解压后,其代码是一样的: 大家可以看到以上,这块是在外网使用的,其速度是比较快的。当然,浏览器解压这个文件需要一点时间,不过在本地解压是非常快的,可以不用管。 三、通过Js缓存,更加可以提高EXT的加载速度,关于缓存,本文不作讨论。 原文出自: http://man1900.iteye.com/blog/515058
我们常常在代码中读取一些资源文件(比如图片,音乐,文本等等)。在单独运行的时候这些简单的处理当然不会有问题。但是,如果我们把代码打成一个jar包以后,即使将资源文件一并打包,这些东西也找不出来了。看看下面的代码: //源代码1: package edu.hxraid; import java.io.*; public class Resource { public void getResource() throws IOException{ File file=new File("bin/resource/res.txt"); BufferedReader br=new BufferedReader(new FileReader(file)); String s=""; while((s=br.readLine())!=null) System.out.println(s); } }
这段代码写在Eclipse建立的java Project中,其目录为:(其中将资源文件res.txt放在了bin目录下,以便打成jar包) 1、src/ src/edu/hxraid/Resource.java 2、bin/ bin/resource/res.txt bin/edu/hxraid/Resource.class 很显然运行源代码1是能够找到资源文件res.txt。但当我们把整个工程打成jar包以后(ResourceJar.jar),这个jar包内的目录为: edu/hxraid/Resource.class resource/res.txt 而这时jar包中Resource.class字节 码:ldc <String "bin/resource/res.txt"> [20] 将无法定位到jar包中的res.txt位置上。就算把bin/目录去掉:ldc <String "resource/res.txt"> [20] 仍然无法定位到jar包中res.txt上。 这主要是因为jar包是一个单独的文件而非文件夹,绝对不可能通过"file:/e:/.../ResourceJar.jar/resource /res.txt"这种形式的文件URL来定位res.txt。所以即使是相对路径,也无法定位到jar文件内的txt文件(读者也许对这段原因解释有些费解,在下面我们会用一段代码运行的结果来进一步阐述)。 那么把资源打入jar包,无论ResourceJar.jar在系统的什么路径下,jar包中的字节码程序都可以找到该包中的资源。这会是幻想吗? 当然不是,我们可以用类装载器(ClassLoader)来做到这一点: (1) ClassLoader 是类加载器的抽象类。它可以在运行时动态的获取加载类的运行信息。 可以这样说,当我们调用ResourceJar.jar中的Resource类时,JVM加载进Resource类,并记录下Resource运行时信息 (包括Resource所在jar包的路径信息)。而ClassLoader类中的方法可以帮助我们动态的获取这些信息: ● public URL getResource(String name) 查找具有给定名称的资源。资源是可以通过类代码以与代码基无关的方式访问的一些数据(图像、声音、文本等)。并返回资源的URL对象。 ● public InputStream getResourceAsStream(String name); 返回读取指定资源的输入流。这个方法很重要,可以直接获得jar包中文件的内容。 (2) ClassLoader是abstract的,不可能实例化对象,更加不可能通过ClassLoader调用上面两个方法。所以我们真正写代码的时候,是通过Class类中的getResource()和getResourceAsStream()方法,这两个方法会委托ClassLoader中的getResource()和getResourceAsStream()方法 。好了,现在我们重新写一段Resource代码,来看看上面那段费解的话是什么意思了: //源代码2: package edu.hxraid; import java.io.*; import java.net.URL; public class Resource { public void getResource() throws IOException{ //查找指定资源的URL,其中res.txt仍然开始的bin目录下 URL fileURL=this.getClass().getResource("/resource/res.txt"); System.out.println(fileURL.getFile()); } public static void main(String[] args) throws IOException { Resource res=new Resource(); res.getResource(); } } 运行这段源代码结果:/E:/Code_Factory/WANWAN/bin/resource/res.txt (../ Code_Factory/WANWAN/.. 是java project所在的路径) 我们将这段代码打包成ResourceJar.jar ,并将ResourceJar.jar放在其他路径下(比如 c:\ResourceJar.jar)。然后另外创建一个java project并导入ResourceJar.jar,写一段调用jar包中Resource类的测试代码: import java.io.IOException; import edu.hxraid.Resource; public class TEST { public static void main(String[] args) throws IOException { Resource res=new Resource(); res.getResource(); } } 这时的运行结果是:file:/C:/ResourceJar.jar!/resource/res.txt 我们成功的在运行时动态获得了res.txt的位置。然而,问题来了,你是否可以通过下面这样的代码来得到res.txt文件? File f=new File("C:/ResourceJar.jar!/resource/res.txt"); 当然不可能,因为".../ResourceJar.jar!/resource/...."并不是文件资源定位符的格式 (jar中资源有其专门的URL形式: jar:<url>!/{entry} )。所以,如果jar包中的类源代码用File f=new File(相对路径);的形式,是不可能定位到文件资源的。这也是为什么源代码1打包成jar文件后,调用jar包时会报出FileNotFoundException的症结所在了。 (3) 我们不能用常规操作文件的方法来读取ResourceJar.jar中的资源文件res.txt,但可以通过Class类的getResourceAsStream()方法来获取 ,这种方法是如何读取jar中的资源文件的,这一点对于我们来说是透明的。我们将Resource.java改写成: //源代码3: package edu.hxraid; import java.io.*; public class Resource { public void getResource() throws IOException{ //返回读取指定资源的输入流 InputStream is=this.getClass().getResourceAsStream("/resource/res.txt"); BufferedReader br=new BufferedReader(new InputStreamReader(is)); String s=""; while((s=br.readLine())!=null) System.out.println(s); } } 我们将java工程下/bin目录中的edu/hxraid /Resource.class和资源文件resource/res.txt一并打包进ResourceJar.jar中,不管jar包在系统的任何目录 下,调用jar包中的Resource类都可以获得jar包中的res.txt资源,再也不会找不到res.txt文件了。 原文出自: http://www.iteye.com/topic/483115
编辑代码常用快捷键 格式化代码的快捷键 Ctrl + Shift + F 格式化缩进的快捷键是 Ctrl + I,只能对选中的文本进行缩进 删除一行的快捷键是 Ctrl + D 当前窗口最大化最小化切换 Ctrl + M 转到最后进行修改的位置 Ctrl + Q 快速查找选中的字符 Ctrl + K(向下) Ctrl + Shift + K(向上) 光标放到一个括号,切换到另一个成对的括号 Ctrl + Shirt + P 在编辑过的位置进行切换 Alt + 左右方向键 阅读代码常用的快捷键 F3不解释(一些人喜欢用Ctrl + 鼠标左键) 选中方法或者变量 Ctrl + Alt + H,查找在哪些地方调用,快速阅读代码和评估代码修改必须要用到的 继承关系 F4,了解代码的框架 快速查找函数和变量 Ctrl + O,输入函数或变量的名字,比在Outline中一个一个找要快很多,但是要对代码有了解 全工程查找 Ctrl + H,代码巨多的情况下必不可少。 由于水平有限,笔者只用到了这些快捷键 如果想知道其他的快捷键 Ctrl + Shift + L 自定义格式化代码 在Preference中打开Java的Format 内建的模版是不能修改的,点击New...,随便输入一个名字,新建一个自己的模板,弹出自定义Edit窗口 可以定义的项目非常丰富,在右边还可以进行预览,就算对英文不感冒,也可以捉摸出大致的意思。对代码进行格式化的好处是不仅仅是美观,便于阅读,在 进行团队开发的时候,使用统一的格式,在合并代码的时候可以避免许多的冲突。修改完成的模版就是使用Ctrl + Shift + F格式化时的模板 自动去除无用的import,自动补全@Override和@deprecated,eclipse的Clean up 在Code Style中,除了Format还有Clean Up 和Format进行同样的操作,新建一个模板,有几个地方我决定有必要改一下 在Code Organizing标签选择Remove trailing whitespace(移除尾部的空格) 和Organzie imports 选择Organzie imports前效果 选择Organzie imports后效果 切换到Code Style标签 Use blocks in if/while/for/do statements 为if/while/for/do自动添加括号,这个因人而异,我决定即使只用一行,也应该添加括号。 点击菜单里的Source - Clean up,可以对代码进行清理,清理代码最大的好处是——移除没用的import,自动添加@Deprecated和@Override 特别是自动添加@Override,可以很清楚的明白那些函数是继承的。 代码提示 用过visual assistx的一定非常系统它的代码提示功能,我是个没有代码提示就无法Coding的人,点击菜单Windows-Preference,切换到以下窗口 找到Auto Activation,也就是红色方框中的部分,将Auto activation delay(ms): 修改为 50 将Auto activation triggers for Java:修改为 .abcdefghigklmnoprstuvwxyz,这样就能随时提示了。 最后介绍两个工具,Search Everything 根据文件名快速查找文件,ClipX剪贴板历史记录。 原文出自:http://www.cnblogs.com/sw926/p/3209615.html
1 @Entity 2 @Table(name="T_ADM_USER") 3 public class User extends GenericEntity implements Serializable { 4 5 @OneToOne(cascade = CascadeType.PERSIST) 6 @JoinColumn(name="grade_id") 7 public Grade getGrade() { 8 return grade; 9 } 10 public void setGrade(Grade grade) { 11 this.grade = grade; 12 } 13 @Column(name="user_name", insertable = true, updatable = true, nullable = true) 14 public String getUsername() { 15 return username; 16 } 17 public void setUsername(String username) { 18 this.username = username; 19 } 20 21 }
1 public class TableUtil { 2 3 public static void main(String[] args) { 4 try{ 5 6 AnnotationConfiguration cfg = new AnnotationConfiguration(); 7 cfg.addAnnotatedClass(com.mygogo.grade.user.entity.User.class); 9 SchemaExport se = new SchemaExport(cfg); 10 se.setDelimiter(";"); 11 se.drop(true, true); 12 se.create(true, true); 13 }catch(Exception e){ 14 e.printStackTrace(); 15 } 16 17 } 18 }
Servlet和Filter的url匹配以及url-pattern详解 Servlet和filter是J2EE开发中常用的技术,使用方便,配置简单,老少皆宜。估计大多数朋友都是直接配置用,也没有关心过具体的细节,今天 遇到一个问题,上网查了servlet的规范才发现,servlet和filter中的url-pattern还是有一些文章在里面的,总结了一些东西, 放出来供大家参考,以免遇到问题又要浪费时间。一 servlet容器对url的匹配过当一个请求发送到servlet容器的时候,容器先会将请求的url减去当前应用上下文的路径作为servlet的映射url,比如我访问的是http://localhost/test/aaa.html,我的应用上下文是test,容器会将http://localhost/test去掉,剩下的/aaa.html部分拿来做servlet的映射匹配。这个映射匹配过程是有顺序的,而且当有一个servlet匹配成功以后,就不会去理会剩下的servlet了(filter不同,后文会提到)。其匹配规则和顺序如下:<o:p></o:p>
1. 精确路径匹配。例子:比如servletA 的url-pattern为 /test,servletB的url-pattern为 /* ,这个时候,如果我访问的url为http://localhost/test ,这个时候容器就会先进行精确路径匹配,发现/test正好被servletA精确匹配,那么就去调用servletA,也不会去理会其他的servlet了。<o:p></o:p>
2. 最长路径匹配。例子:servletA的url-pattern为/test/*,而servletB的url-pattern为/test/a/*,此时访问http://localhost/test/a时,容器会选择路径最长的servlet来匹配,也就是这里的servletB。<o:p></o:p>
3. 扩展匹配,如果url最后一段包含扩展,容器将会根据扩展选择合适的servlet。例子:servletA的url-pattern:*.action<o:p></o:p>
4. 如果前面三条规则都没有找到一个servlet,容器会根据url选择对应的请求资源。如果应用定义了一个default servlet,则容器会将请求丢给default servlet(什么是default servlet?后面会讲)。
根据这个规则表,就能很清楚的知道servlet的匹配过程,所以定义servlet的时候也要考虑url-pattern的写法,以免出错。 对于filter,不会像servlet那样只匹配一个servlet,因为filter的集合是一个链,所以只会有处理的顺序不同,而不会出现只选择一 个filter。Filter的处理顺序和filter-mapping在web.xml中定义的顺序相同。 二 url-pattern详解- 在web.xml文件中,以下语法用于定义映射:
- 以”/’开头和以”/*”结尾的是用来做路径映射的。
- 以前缀”*.”开头的是用来做扩展映射的。
- “/” 是用来定义default servlet映射的。
- 剩下的都是用来定义详细映射的。比如: /aa/bb/cc.action
所以,为什么定义”/*.action”这样一个看起来很正常的匹配会错?因为这个匹配即属于路径映射,也属于扩展映射,导致容器无法判断 出自: http://foxty.iteye.com/blog/39332
oracle监听不能启动的问题及处理过程! oracle环境如下: SQL> select * from V$version 2 ; BANNER ---------------------------------------------------------------- Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod PL/SQL Release 10.2.0.1.0 - Production CORE 10.2.0.1.0 Production TNS for 32-bit Windows: Version 10.2.0.1.0 - Production NLSRTL Version 10.2.0.1.0 - Production 出错过程回忆:在此之前用windows优化大师对系统注册表进行了修复! Q:链接oracle时报错:ORA-12541: TNS: 无监听程序 A: 1〉查看监听有没有启动: 一:运行lsnrctl C:\Documents and Settings\Admin>lsnrctl LSNRCTL for 32-bit Windows: Version 10.2.0.1.0 - Production on 20-4月 -2007 09: 1:43 Copyright (c) 1991, 2005, Oracle. All rights reserved. 欢迎来到LSNRCTL, 请键入"help"以获得信息。 二:查看stauts LSNRCTL> status 正在连接到 (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC1))) TNS-12541: TNS: 无监听程序 TNS-12560: TNS: 协议适配器错误 TNS-00511: 无监听程序 32-bit Windows Error: 2: No such file or directory 正在连接到 (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=zxt)(PORT=1521))) TNS-12541: TNS: 无监听程序 TNS-12560: TNS: 协议适配器错误 TNS-00511: 无监听程序 32-bit Windows Error: 61: Unknown error 三:发现监听没有启动,现在启动监听 LSNRCTL> start 启动tnslsnr: 请稍候... Failed to start service, error 3. TNS-12560: TNS: 协议适配器错误 TNS-00530: 协议适配器错误 2〉发现监听启动不起来,估计是注册表有点问题,登录注册表 C:\Documents and Settings\Admin>regedit 进入注册表到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\OracleOraDb10g_home1TNSListener 发现ImagePath关键值没有了,增加可扩充字符串值,取名为ImagePathImagePath,编辑字符串的数值数据为:G:\oracle\product\10.2.0\db_2\BIN\TNSLSNR(对应oracle的TNSLSNR的位置) ,退出注册表。 3〉启动监听 LSNRCTL> start 启动tnslsnr: 请稍候... TNSLSNR for 32-bit Windows: Version 10.2.0.1.0 - Production 系统参数文件为d:\oracle\product\10.2.0\db_1\network\admin\listener.ora 写入d:\oracle\product\10.2.0\db_1\network\log\listener.log的日志信息 监听: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(PIPENAME=\\.\pipe\EXTPROC1ipc))) 监听: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=zxt)(PORT=1521))) 正在连接到 (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC1))) LISTENER 的 STATUS ------------------------ 别名 LISTENER 版本 TNSLSNR for 32-bit Windows: Version 10.2.0.1.0 - Produ ction 启动日期 20-4月 -2007 09:41:29 正常运行时间 0 天 0 小时 0 分 3 秒 跟踪级别 off 安全性 ON: Local OS Authentication SNMP OFF 监听程序参数文件 d:\oracle\product\10.2.0\db_1\network\admin\listener.o ra 监听程序日志文件 d:\oracle\product\10.2.0\db_1\network\log\listener.log 监听端点概要... (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(PIPENAME=\\.\pipe\EXTPROC1ipc))) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=zxt)(PORT=1521))) 服务摘要.. 服务 "PLSExtProc" 包含 1 个例程。 例程 "PLSExtProc", 状态 UNKNOWN, 包含此服务的 1 个处理程序... 命令执行成功 4>监听启动成功后尝试登录 C:\Documents and Settings\Admin>sqlplus zxt@orcl SQL*Plus: Release 10.2.0.1.0 - Production on 星期五 4月 20 09:42:33 2007 Copyright (c) 1982, 2005, Oracle. All rights reserved. 输入口令: 连接到: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production With the Partitioning, OLAP and Data Mining options 登录成功! 总结:估计是windows优化大师或者别的工具在修复注册表时候删掉了ImagePath字段,补上后就可以了! 补充:登录sqlplus时报 ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务错误! 处理办法: 1〉oracle_home\NETWORK\ADMIN\tnsnames.ora中修改(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521)) 为(ADDRESS = (PROTOCOL = TCP)(HOST = 本地计算机名)(PORT = 1521)),保存即可。 2〉有的人介绍oracle_home\NETWORK\ADMIN\sqlnet.ora中修改SQLNET.AUTHENTICATION_SERVICES = (NTS)为 SQLNET.AUTHENTICATION_SERVICES = (NONE) 这2种方法,第一个最佳!
Ext GridPanel实现复选框选择框: 1 var selectModel = new Ext.grid.CheckboxSelectionModel({ 2 singleSelect : false 3 }); 4 但是这样每一行都会有复选框,如果需求为:某行数据的某个列满足什么条件我才有复选框选项就不太好实现了,
这样就需要重写Ext.grid.CheckboxSelectionModel的渲染,行点击涵数来实现. 1 var selectModel = new Ext.grid.CheckboxSelectionModel({ 2 singleSelect : false, 3 renderer : function(v, p, record){ 4 if (record.data['结果状态'] == '0'){ 5 return ''; 6 } 7 return '<div class="x-grid3-row-checker"> </div>'; 8 }, 9 onHdMouseDown : function(e, t) { 10 if (t.className == 'x-grid3-hd-checker') { 11 e.stopEvent(); 12 var hd = Ext.fly(t.parentNode); 13 var isChecked = hd.hasClass('x-grid3-hd-checker-on'); 14 if (isChecked){ 15 hd.removeClass('x-grid3-hd-checker-on'); 16 this.clearSelections(); 17 }else { 18 hd.addClass('x-grid3-hd-checker-on'); 19 if (this.locked){ 20 return; 21 } 22 this.selections.clear(); 23 for (var i = 0, len = this.grid.store.getCount(); i < len; i++ ){ 24 if (this.grid.store.getAt(i).data["结果状态"] != '0'){ 25 this.selectRow(i, true); 26 } 27 } 28 } 29 } 30 }, 31 handleMouseDown : function(g, rowIndex, e){ 32 if (e.button !== 0 || this.isLocked()) { 33 return; 34 } 35 var view = this.grid.getView(); 36 if (e.shiftKey && !this.singleSelect && this.last != false ) { 37 var last = this.last; 38 this.selectRange(last, rowIndex, e.ctrlKey); 39 this.last = last; 40 view.focusRow(rowIndex); 41 }else{ 42 var isSelected = this.isSelected(rowIndex); 43 if (e.ctrlKey && isSelected) { 44 this.deselectRow(rowIndex); 45 }else if(!isSelected || this.getCount() > 1){ 46 if(this.grid.store.getAt(rowIndex).data["结果状态"] != '0'){ 47 this.selectRow(rowIndex, e.ctrlKey || e.shiftKey); 48 } 49 view.focusRow(rowIndex); 50 } 51 } 52 } 53 }); 原文: http://fordream.iteye.com/blog/1179252
摘要: 在学习中Ext.grid.EditorGridPanel 的时候碰到一个知识点,如何用复选框来表示真假值,当然我们可以直接这样 Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1 {2 heade... 阅读全文
Ext.grid.GridPanel可以设置stripeRows: true的属性来实现隔行换颜色的效果,如果你想自定义每行的颜色,那么你可以按照下边地方法来实现: Ext.ux.GridView=Ext.extend( Ext.grid.GridView, { getRowClass:function(record,index) { if(index%2==0) return 'red'; else return 'green'; } } )
使用自定义的view
var grid = new Ext.grid.GridPanel({ //other code store: store, view:new Ext.ux.GridView(), //other code });
样式定义: .red { background-color:#FF0000; } .green { background-color:#00FF00; }
通过firebug可以看到,给每行的div添加了自定义的样式 原文出自: http://love4j.iteye.com/blog/516007
摘要: 先上图 grid分页: 把grid和page工具绑定在一起 // create the Data Store var store = new Ext.data.Store... 阅读全文
http://yourgame.iteye.com/blog/252853
Ext中的combobox有属性typeAhead:true 可以实现模糊匹配,但是是从开始匹配的,如果需要自定的的匹配,则需要监听beforequery方法,实现自己的匹配查询方法: 代码如下: var gfxmComb = new Ext.form.ComboBox({ id : 'gfxmComb', store : gfxmStore, typeAhead : true, mode : 'local', editable : true, displayField :'xmMc', valueField :'xmBm', triggerAction : 'all', selectOnFocus : true, listeners : { 'beforequery':function(e){ var combo = e.combo; if(!e.forceAll){ var input = e.query; // 检索的正则 var regExp = new RegExp(".*" + input + ".*"); // 执行检索 combo.store.filterBy(function(record,id){ // 得到每个record的项目名称值 var text = record.get(combo.displayField); return regExp.test(text); }); combo.expand(); return false; } } } }); 原文出自:http://weibaojun.iteye.com/blog/1098731
Javascript是一门非常灵活的语言,我们可以随心所欲的书写各种风格的代码,不同风格的代码也必然也会导致执行效率的差异,开发过程中零零散散地接触到许多提高代码性能的方法,整理一下平时比较常见并且容易规避的问题 Javascript自身执行效率 Javascript中的作用域链、闭包、原型继承、eval等特性,在提供各种神奇功能的同时也带来了各种效率问题,用之不慎就会导致执行效率低下。 1、全局导入 我们在编码过程中多多少少会使用到一些全局变量(window,document,自定义全局变量等等),了解javascript作用域链的人都 知道,在局部作用域中访问全局变量需要一层一层遍历整个作用域链直至顶级作用域,而局部变量的访问效率则会更快更高,因此在局部作用域中高频率使用一些全 局对象时可以将其导入到局部作用域中,例如: 1 //1、作为参数传入模块 2 (function(window,$){ 3 var xxx = window.xxx; 4 $("#xxx1").xxx(); 5 $("#xxx2").xxx(); 6 })(window,jQuery); 7 8 //2、暂存到局部变量 9 function(){ 10 var doc = document; 11 var global = window.global; 12 } 2、eval以及类eval问题 我们都知道eval可以将一段字符串当做js代码来执行处理,据说使用eval执行的代码比不使用eval的代码慢100倍以上(具体效率我没有测试,有兴趣同学可以测试一下) JavaScript 代码在执行前会进行类似“预编译”的操作:首先会创建一个当前执行环境下的活动对象,并将那些用 var 申明的变量设置为活动对象的属性,但是此时这些变量的赋值都是 undefined,并将那些以 function 定义的函数也添加为活动对象的属性,而且它们的值正是函数的定义。但是,如果你使用了“eval”,则“eval”中的代码(实际上为字符串)无法预先识 别其上下文,无法被提前解析和优化,即无法进行预编译的操作。所以,其性能也会大幅度降低 其实现在大家一般都很少会用eval了,这里我想说的是两个类eval的场景(new Function{} ,setTimeout ,setInterver ) setTimtout("alert(1)",1000);
setInterver("alert(1)",1000);
(new Function("alert(1)"))(); 上述几种类型代码执行效率都会比较低,因此建议直接传入匿名方法、或者方法的引用给setTimeout方法 3、闭包结束后释放掉不再被引用的变量var f = (function(){ var a = {name:"var3"}; var b = ["var1","var2"]; var c = document.getElementByTagName("li"); //****其它变量 //***一些运算 var res = function(){ alert(a.name); } return res; })() 上述代码中变量f的返回值是由一个立即执行函数构成的闭包中返回的方法res,该变量保留了对于这个闭包中所有变量(a,b,c等)的引用,因此这两个变 量会一直驻留在内存空间中,尤其是对于dom元素的引用对内存的消耗会很大,而我们在res中只使用到了a变量的值,因此,在闭包返回前我们可以将其它变 量释放 var f = (function(){ var a = {name:"var3"}; var b = ["var1","var2"]; var c = document.getElementByTagName("li"); //****其它变量 //***一些运算 //闭包返回前释放掉不再使用的变量 b = c = null; var res = function(){ alert(a.name); } return res; })() 在web开发过程中,前端执行效率的瓶颈往往都是在dom操作上面,dom操作是一件很耗性能的事情,如何才能在dom操作过程中尽量节约性能呢? 1、减少reflow 什么是reflow? 当 DOM 元素的属性发生变化 (如 color) 时, 浏览器会通知 render 重新描绘相应的元素, 此过程称为 repaint。 如果该次变化涉及元素布局 (如 width), 浏览器则抛弃原有属性, 重新计算并把结果传递给 render 以重新描绘页面元素, 此过程称为 reflow。 减少reflow的方法 -
先将元素从document中删除,完成修改后再把元素放回原来的位置(当对某元素及其子元素进行大量reflow操作时,1,2两种方法效果才会比较明显) -
将元素的display设置为”none”,完成修改后再把display修改为原来的值 - 修改多个样式属性时定义class类代替多次修改style属性(for certain同学推荐)
-
大量添加元素到页面时使用documentFragment 例如 for(var i=0;i<100:i++){ var child = docuemnt.createElement("li"); child.innerHtml = "child"; document.getElementById("parent").appendChild(child); } 上述代码会多次操作dom,效率比较低,可以改为下面的形式 创建documentFragment,将所有元素加入到docuemntFragment不会改变dom结构,最后将其添加到页面,只进行了一次reflow var frag = document.createDocumentFragment(); for(var i=0;i<100:i++){ var child = docuemnt.createElement("li"); child.innerHtml = "child"; frag.appendChild(child); } document.getElementById("parent").appendChild(frag); 2、暂存dom状态信息 当代码中需要多次访问元素的状态信息,在状态不变的情况下我们可以将其暂存到变量中,这样可以避免多次访问dom带来内存的开销,典型的例子就是: var lis = document.getElementByTagName("li"); for(var i=1;i<lis.length;i++){ //*** } 上述方式会在每一次循环都去访问dom元素,我们可以简单将代码优化如下 var lis = document.getElementByTagName("li"); for(var i=1,j=lis.length ;i<j;i++){ //*** } 3、缩小选择器的查找范围 查找dom元素时尽量避免大面积遍历页面元素,尽量使用精准选择器,或者指定上下文以缩小查找范围,以jquery为例 - 少用模糊匹配的选择器:例如
$("[name*='_fix']") ,多用诸如id以及逐步缩小范围的复合选择器$("li.active") 等 - 指定上下文:例如
$("#parent .class") ,$(".class",$el) 等 4、使用事件委托 使用场景:一个有大量记录的列表,每条记录都需要绑定点击事件,在鼠标点击后实现某些功能,我们通常的做法是给每条记录都绑定监听事件,这种做法会导致页面会有大量的事件监听器,效率比较低下。 基本原理:我们都知道dom规范中事件是会冒泡的,也就是说在不主动阻止事件冒泡的情况下任何一个元素的事件都 会按照dom树的结构逐级冒泡至顶端。而event对象中也提供了event.target(IE下是srcElement)指向事件源,因此我们即使在 父级元素上监听该事件也可以找到触发该事件的最原始的元素,这就是委托的基本原理。废话不多说,上示例 $("ul li").bind("click",function(){ alert($(this).attr("data")); }) 上述写法其实是给所有的li元素都绑定了click事件来监听鼠标点击每一个元素的事件,这样页面上会有大量的事件监听器。 根据上面介绍的监听事件的原理我们来改写一下 $("ul").bind("click",function(e){ if(e.target.nodeName.toLowerCase() ==="li"){ alert($(e.target).attr("data")); } }) 这样一来,我们就可以只添加一个事件监听器去捕获所有li上触发的事件,并做出相应的操作。 当然,我们不必每次都做事件源的判断工作,可以将其抽象一下交给工具类来完成。jquery中的delegate()方法就实现了该功能 语法是这样的$(selector).delegate(childSelector,event,data,function) ,例如: $("div").delegate("button","click",function(){ $("p").slideToggle(); }); 参数说明(引自w3school) 参数 | 描述 | childSelector | 必需。规定要附加事件处理程序的一个或多个子元素。 | event | 必需。规定附加到元素的一个或多个事件。由空格分隔多个事件值。必须是有效的事件。 | data | 可选。规定传递到函数的额外数据。 | function | 必需。规定当事件发生时运行的函数。 | Tips:事件委托还有一个好处就是,即使在事件绑定之后动态添加的元素上触发的事件同样可以监听到哦,这样就不用在每次动态加入元素到页面后都为其绑定事件了 原文出自: http://www.cnblogs.com/gewei/archive/2013/03/29/2988180.html
摘要: 在Java中,它的内存管理包括两方面:内存分配(创建Java对象的时候)和内存回收,这 两方面工作都是由JVM自动完成的,降低了Java程序员的学习难度,避免了像C/C++直接操作内存的危险。但是,也正因为内存管理完全由JVM负责, 所以也使Java很多程序员不再关心内存分配,导致很多程序低效,耗内存。因此就有了Java程序员到最后应该去了解JVM,才能写出更高效,充分利用有 限的内存的程序。 1... 阅读全文
在设计产品时,要考虑到到很多的因素,其中一个重要的是配色方案。不同的颜色可以使您的产品创造出不同的效果。请务必检查颜色在一般和特定的文化含义,以获得最佳使用效果。 在这篇文章中,我想向大家介绍一些有用的设计工具将帮助您选择合适的调色板为您的设计。这里有15个在线工具分析和收集的色彩组合。
转自:http://www.cnblogs.com/58top/archive/2013/01/15/useful-color-combination-apps-for-designers.html
转: http://www.cnblogs.com/58top/archive/2013/01/11/free-jquery-tooltip-plugins.html
textarea元素已被广泛用于网页Web的IDE。通常网站自带的textarea编辑器不能满足我们的需求,作为一名开发者我们经常需要进行代码的在线编辑,高亮显示代码等,因此,通过其他的开源项目,我们可以添加一些实用的功能, 在这篇文章中,我将使用 JavaScript库 ACE来创建一个输入框效果。这是一个完全开源的脚本。该脚本允许开发人员创建支持语法高亮的输入框。然后你可以代码嵌入到网站中的任何地方 下载库 首先我们需要Github上下载ACE代码。 下载完成后解压缩,在你的header部分引入js文件 <script src="src-min/ace.js" type="text/javascript" charset="utf-8"></script> 添加代码到编辑器 首先设置一个id为editor的div 然后在script里面调用ace.edit()方法,代码如下
var editor = ace.edit("editor"); editor.getSession().setMode("ace/mode/javascript"); 您可以重命名变量,为了方便起见,我定义了var editor作为变量,你也可以定义var demoeditor作为变量 。第二行声明使用哪种类型的语言高亮显示。您可以从 src 目录选择其他语言集合。这里是一些支持支持的语言集合: - SQL
- Ruby
- SASS
- PHP
- Objectivec
- Csharp
- Java
- JSON
使用额外的参数editor.setTheme("ace/theme/dawn"); editor.getSession().setTabSize(2); editor.getSession().setUseWrapMode(true); 这3行代码是关于文本输入效果的,第一行改变代码默认的语法颜色和主题,在src目录下个有几十个新的主题,你可以从中任意选择 另外两个选项是关于用户体验。通常情况下,按一个键盘上的Tab键将输入4个空格,这里我设置成2个空格,此外,该文本在默认情况下将不会自动换行,超了会追加一个水平滚动条向外延伸。但使用这种方法setUseWrapMode(true) ,我们可以修复自动换行的问题。 还有一些其他的命令,你可以参考ACE向导。这里面包含了改变光标的位置,动态添加新内容,或复制的文本的全部内容。 CSS代码 #editor { margin-left: 15px; margin-top: 15px; width: 1000px; height: 400px; } 原文出自: http://www.cnblogs.com/58top/archive/2013/01/28/building-a-syntax-highlighted-input-box-with-javascript.html
Aspect Oriented Programming 面向切面编程。解耦是程序员编码开发过程中一直追求的。AOP也是为了解耦所诞生。 具体思想是:定义一个切面,在切面的纵向定义处理方法,处理完成之后,回到横向业务流。 AOP 在Spring框架中被作为核心组成部分之一,的确Spring将AOP发挥到很强大的功能。最常见的就是事务控制。工作之余,对于使用的工具,不免需要了解其所以然。学习了一下,写了些程序帮助理解。 AOP 主要是利用代理模式的技术来实现的。 1、静态代理:就是设计模式中的proxy模式 a、业务接口 /** * 抽象主题角色:声明了真实主题和代理主题的共同接口。 * * @author yanbin * */ public interface ITalk {
public void talk(String msg);
} b、业务实现 /** * 真实主题角色:定义真实的对象。 * * @author yanbin * */ public class PeopleTalk implements ITalk {
public String username; public String age;
public PeopleTalk(String username, String age) { this.username = username; this.age = age; }
public void talk(String msg) { System.out.println(msg + "!你好,我是" + username + ",我年龄是" + age); }
public String getName() { return username; }
public void setName(String name) { this.username = name; }
public String getAge() { return age; }
public void setAge(String age) { this.age = age; }
} c、代理对象 /** * 代理主题角色:内部包含对真实主题的引用,并且提供和真实主题角色相同的接口。 * * @author yanbin * */ public class TalkProxy implements ITalk {
private ITalk talker;
public TalkProxy(ITalk talker) { // super(); this.talker = talker; }
public void talk(String msg) { talker.talk(msg); }
public void talk(String msg, String singname) { talker.talk(msg); sing(singname); }
private void sing(String singname) { System.out.println("唱歌:" + singname); }
} d、测试类 /** * 代理测试类,使用代理 * * @author yanbin * */ public class ProxyPattern {
public static void main(String[] args) { // 不需要执行额外方法的。 ITalk people = new PeopleTalk("AOP", "18"); people.talk("No ProXY Test"); System.out.println("-----------------------------");
// 需要执行额外方法的(切面) TalkProxy talker = new TalkProxy(people); talker.talk("ProXY Test", "代理"); }
} 从这段代码可以看出来,代理模式其实就是AOP的雏形。 上端代码中talk(String msg, String singname)是一个切面。在代理类中的sing(singname)方法是个后置处理方法。 这样就实现了,其他的辅助方法和业务方法的解耦。业务不需要专门去调用,而是走到talk方法,顺理成章的调用sing方法 再从这段代码看:1、要实现代理方式,必须要定义接口。2、每个业务类,需要一个代理类。 2、动态代理:jdk1.5中提供,利用反射。实现InvocationHandler接口。 业务接口还是必须得,业务接口,业务类同上。 a、代理类: /** * 动态代理类 * * @author yanbin * */ public class DynamicProxy implements InvocationHandler {
/** 需要代理的目标类 */ private Object target;
/** * 写法固定,aop专用:绑定委托对象并返回一个代理类 * * @param delegate * @return */ public Object bind(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); }
/** * @param Object * target:指被代理的对象。 * @param Method * method:要调用的方法 * @param Object * [] args:方法调用时所需要的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; // 切面之前执行 System.out.println("切面之前执行"); // 执行业务 result = method.invoke(target, args); // 切面之后执行 System.out.println("切面之后执行"); return result; }
} b、测试类 /** * 测试类 * * @author yanbin * */ public class Test {
public static void main(String[] args) { // 绑定代理,这种方式会在所有的方法都加上切面方法 ITalk iTalk = (ITalk) new DynamicProxy().bind(new PeopleTalk()); iTalk.talk("业务说明"); } } 输出结果会是: 切面之前执行 people talk业务说法 切面之后执行 说明只要在业务调用方法切面之前,是可以动态的加入需要处理的方法。 从代码来看,如果再建立一个业务模块,也只需要一个代理类。ITalk iTalk = (ITalk) new DynamicProxy().bind(new PeopleTalk()); 将业务接口和业务类绑定到动态代理类。 但是这种方式:还是需要定义接口。 3、利用cglib CGLIB是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强。采用的是继承的方式。不细说,看使用 a、业务类 /** * 业务类 * * @author yanbin * */ public class PeopleTalk {
public void talk(String msg) { System.out.println("people talk" + msg); }
} b、cglib代理类 /** * 使用cglib动态代理 * * @author yanbin * */ public class CglibProxy implements MethodInterceptor {
private Object target;
/** * 创建代理对象 * * @param target * @return */ public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); }
@Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object result = null; System.out.println("事物开始"); result = methodProxy.invokeSuper(proxy, args); System.out.println("事物结束"); return result; }
} c.测试类 /** * 测试类 * * @author yanbin * */ public class Test {
public static void main(String[] args) { PeopleTalk peopleTalk = (PeopleTalk) new CglibProxy().getInstance(new PeopleTalk()); peopleTalk.talk("业务方法"); peopleTalk.spreak("业务方法"); }
} 最后输出结果: 事物开始 people talk业务方法 事物结束 事物开始 spreak chinese业务方法 事物结束 由于篇幅有限,这篇主要对AOP的原理简单实现做了演示和阐述,有助自己理解。至于Spring的AOP实现上面无外乎其右,不过实现方面复杂的多。 原文出自: http://www.cnblogs.com/yanbincn/archive/2012/06/01/2530377.html
摘要: 摘要:ComboBox是常用控件之一,但由于其数据来源分两种形式:本地和远程,故写的形式难度并不亚于ExtJS中的TreePanel和 GridPanel。鄙人也经常提醒自己的师弟师妹,ExtJS本身是面向对象写的,不能在应用的时候却不按照面向对象来写,面向对象最起码的好处就是代 码的复用,对于网页来讲,代码复用的好处就是加载的JS会少很多,这样网页渲染时就不会很慢。下面我将分别介绍扩展的四种Co... 阅读全文
首先定义一数据源,一般使用simpleStore,jsonStore。需要注意的是simpleStore用于读取二维数组的数据,而jsonStroe用于读取json数据格式。 Combox使用simpleStore 代码如下所示: var subjectField = new Ext.form.ComboBox({ fieldLabel : '分类名称', hiddenName : 'drug.subjectCode',// 传递到后台的参数 store : new Ext.data.SimpleStore({ autoLoad : true, url :'xxx', fields : ['subjectCode', 'subjectName'] }), valueField : 'subjectCode',// 域的值,对应于store里的fields displayField : 'subjectName',// 显示的域,对应于store里的fields typeAhead : true,// 设置true,完成自动提示 mode : 'local', // 设置local,combox将从本地加载数据 triggerAction : 'all',// 触发此表单域时,查询所有 selectOnFocus : true, anchor : '90%', forceSelection : true });
服务端返回的数据结构如下所示: [ ["00000003","硬膏剂"], ["00000005","滴眼剂"], ["00000016","栓剂"], ["00000017","注射剂"], ["00000018","软膏剂"] ] 当combox使用jsonStore时,一般运用于分页查询。页面效果如下所示: 示例代码如下所示: // 药品商品名 var itemNameField = new Ext.form.ComboBox({ width : 200, fieldLabel : '药品商品名', hiddenName : 'drug.itemName', store : advanceStore, valueField : 'itemName', displayField : 'itemName', typeAhead : true, mode : 'remote',// 分页查询必须设置为 remote,当我们点击下一页的时候是从服务端取数据,而不是本地 triggerAction : 'all', emptyText : '请选择一个分类名', selectOnFocus : true, minChars : 0, // 完成自动提示,当mode为‘local’时,默认为0,当mode为‘remote’时候,默认为4,这里设置为0 pageSize : 10,// 每页显示的记录数字 queryParam :'drug.itemName' // 在combox内敲入字符时候,combox向后台查询传递的参数,这里设置为'drug.itemName'是为了更好的封装,默认传递参数‘query’ });
这里还有一个问题,就是Combox设置初始值。 我是采用如下做法的,不知道各位知不知道其他用法? var subjectField = new Ext.form.ComboBox({ fieldLabel : '分类名称', hiddenName : 'drug.subjectCode', store : new Ext.data.SimpleStore({ autoLoad : true, url : 'xxx', fields : ['subjectCode', 'subjectName'], listeners : { load : function(){ subjectField.setValue(record.get("drug.subjectCode")); } } }), valueField : 'subjectCode', displayField : 'subjectName', typeAhead : true, mode : 'local', triggerAction : 'all', emptyText : '请选择一个分类名', selectOnFocus : true, anchor : '90%', forceSelection : true });
原文出自: http://www.iteye.com/topic/296710
所有的事件回调函数都有两个参数: event和 ui,浏览器自有event对象,和经过封装的ui对象 ui.helper - 表示sortable元素的JQuery对象,通常是当前元素的克隆对象 ui.position - 表示相对当前对象,鼠标的坐标值对象{top,left} ui.offset - 表示相对于当前页面,鼠标的坐标值对象{top,left} ui.item - 表示当前拖拽的元素
ui.placeholder - 占位符(如果有定义的话) ui.sender - 当前拖拽元素的所属sortable对象(仅当元素是从另一个sortable对象传递过来时有用) ·参数(参数名 : 参数类型 : 默认值) appendTo : String : 'parent' Defines where the helper that moves with the mouse is being appended to during the drag (for example, to resolve overlap/zIndex issues). 初始:$('.selector').sortable({ appendTo: 'body' }); 获取:var appendTo = $('.selector').sortable('option', 'appendTo'); 设置:$('.selector').sortable('option', 'appendTo', 'body'); axis : String : false 如果有设置,则元素仅能横向或纵向拖动。可选值:'x', 'y' 初始:$('.selector').sortable({ axis: 'x' }); 获取:var axis = $('.selector').sortable('option', 'axis'); 设置:$('.selector').sortable('option', 'axis', 'x'); cancel : Selector : ':input,button' 阻止排序动作在匹配的元素上发生。 初始:$('.selector').sortable({ cancel: 'button' }); 获取:var cancel = $('.selector').sortable('option', 'cancel'); 设置:$('.selector').sortable('option', 'cancel', 'button'); connectWith : Selector : false 允许sortable对象连接另一个sortable对象,可将item元素拖拽到另一个中。 初始:$('.selector').sortable({ connectWith: '.otherlist' }); 获取:var connectWith = $('.selector').sortable('option', 'connectWith'); 设置:$('.selector').sortable('option', 'connectWith', '.otherlist'); containment : Element, String, Selector : false 约束排序动作只能在一个指定的范围内发生。可选值:DOM对象, 'parent', 'document', 'window', 或jQuery对象 初始:$('.selector').sortable({ containment: 'parent' }); 获取:var containment = $('.selector').sortable('option', 'containment'); 设置:$('.selector').sortable('option', 'containment', 'parent'); cursor : String : 'auto' 定义在开始排序动作时,如果的样式。 初始:$('.selector').sortable({ cursor: 'crosshair' }); 获取:var cursor = $('.selector').sortable('option', 'cursor'); 设置:$('.selector').sortable('option', 'cursor', 'crosshair'); cursorAt : Object : false 当开始移动时,鼠标定位在的某个位置上(最多两个方向)。可选值:{ top, left, right, bottom }. 初始:$('.selector').sortable({ cursorAt: 'top' }); 获取:var cursorAt = $('.selector').sortable('option', 'cursorAt'); 设置:$('.selector').sortable('option', 'cursorAt', 'top'); delay : Integer : 0 以毫秒为单位,设置延迟多久才激活排序动作。此参数可防止误点击。 初始:$('.selector').sortable({ delay: 500 }); 获取:var delay = $('.selector').sortable('option', 'delay'); 设置:$('.selector').sortable('option', 'delay', 500); distance : Integer : 1 决定至少要在元素上面拖动多少像素后,才正式触发排序动作。 初始:$('.selector').sortable({ distance: 30 }); 获取:var distance = $('.selector').sortable('option', 'distance'); 设置:$('.selector').sortable('option', 'distance', 30); dropOnEmpty : Boolean : true 是否允許拖拽到一個空的sortable对象中。 初始:$('.selector').sortable({ dropOnEmpty: false }); 获取:var dropOnEmpty = $('.selector').sortable('option', 'dropOnEmpty'); 设置:$('.selector').sortable('option', 'dropOnEmpty', false); forceHelperSize : Boolean : false If true, forces the helper to have a size. 初始:$('.selector').sortable({ forceHelperSize: true }); 获取:var forceHelperSize = $('.selector').sortable('option', 'forceHelperSize'); 设置:$('.selector').sortable('option', 'forceHelperSize', true); forcePlaceholderSize : Boolean : false If true, forces the placeholder to have a size. 初始:$('.selector').sortable({ forcePlaceholderSize: true }); 获取:var forcePlaceholderSize = $('.selector').sortable('option', 'forcePlaceholderSize'); 设置:$('.selector').sortable('option', 'forcePlaceholderSize', true); grid : Array : false 将排序对象的item元素视为一个格子处理,每次移动都按一个格子大小移动,数组值:[x,y] 初始:$('.selector').sortable({ grid: [50, 20] }); 获取:var grid = $('.selector').sortable('option', 'grid'); 设置:$('.selector').sortable('option', 'grid', [50, 20]); handle : Selector, Element : false 限制排序的动作只能在item元素中的某个元素开始。 初始:$('.selector').sortable({ handle: 'h2' }); 获取:var handle = $('.selector').sortable('option', 'handle'); 设置:$('.selector').sortable('option', 'handle', 'h2'); helper : String, Function : 'original' 设置是否在拖拽元素时,显示一个辅助的元素。可选值:'original', 'clone' 初始:$('.selector').sortable({ helper: 'clone' }); 获取:var helper = $('.selector').sortable('option', 'helper'); 设置:$('.selector').sortable('option', 'helper', 'clone'); items : Selector : '> *' 指定在排序对象中,哪些元素是可以进行拖拽排序的。 初始:$('.selector').sortable({ items: 'li' }); 获取:var items = $('.selector').sortable('option', 'items'); 设置:$('.selector').sortable('option', 'items', 'li'); opacity : Float : false 定义当排序时,辅助元素(helper)显示的透明度。 初始:$('.selector').sortable({ opacity: 0.6 }); 获取:var opacity = $('.selector').sortable('option', 'opacity'); 设置:$('.selector').sortable('option', 'opacity', 0.6); placeholderType: StringDefault: false 设置当排序动作发生时,空白占位符的CSS样式。 初始:$('.selector').sortable({ placeholder: 'ui-state-highlight' }); 获取:var placeholder = $('.selector').sortable('option', 'placeholder'); 设置:$('.selector').sortable('option', 'placeholder', 'ui-state-highlight'); revert : Boolean : false 如果设置成true,则被拖拽的元素在返回新位置时,会有一个动画效果。 初始:$('.selector').sortable({ revert: true }); 获取:var revert = $('.selector').sortable('option', 'revert'); 设置:$('.selector').sortable('option', 'revert', true); scroll : Boolean : true 如果设置成true,则元素被拖动到页面边缘时,会自动滚动。 初始:$('.selector').sortable({ scroll: false }); 获取:var scroll = $('.selector').sortable('option', 'scroll'); 设置:$('.selector').sortable('option', 'scroll', false); scrollSensitivity : Integer : 20 设置当元素移动至边缘多少像素时,便开始滚动页面。 初始:$('.selector').sortable({ scrollSensitivity: 40 }); 获取:var scrollSensitivity = $('.selector').sortable('option', 'scrollSensitivity'); 设置:$('.selector').sortable('option', 'scrollSensitivity', 40); scrollSpeed : Integer : 20 设置页面滚动的速度。 初始:$('.selector').sortable({ scrollSpeed: 40 }); 获取:var scrollSpeed = $('.selector').sortable('option', 'scrollSpeed'); 设置:$('.selector').sortable('option', 'scrollSpeed', 40); tolerance : String : 'intersect' 设置当拖动元素越过其它元素多少时便对元素进行重新排序。可选值:'intersect', 'pointer' intersect:至少重叠50% pointer:鼠标指针重叠元素 初始:$('.selector').sortable({ tolerance: 'pointer' }); 获取:var tolerance = $('.selector').sortable('option', 'tolerance'); 设置:$('.selector').sortable('option', 'tolerance', 'pointer'); zIndex : Integer : 1000 设置在排序动作发生时,元素的z-index值。 初始:$('.selector').sortable({ zIndex: 5 }); 获取:var zIndex = $('.selector').sortable('option', 'zIndex'); 设置:$('.selector').sortable('option', 'zIndex', 5); ·事件
start 当排序动作开始时触发此事件。 定义:$('.selector').sortable({ start: function(event, ui) { ... } }); 绑定:$('.selector').bind('sortstart', function(event, ui) { ... }); sort 当元素发生排序时触发此事件。 定义:$('.selector').sortable({ sort: function(event, ui) { ... } }); 绑定:$('.selector').bind('sort', function(event, ui) { ... }); change 当元素发生排序且坐标已发生改变时触发此事件。 定义:$('.selector').sortable({ change: function(event, ui) { ... } }); 绑定:$('.selector').bind('sortchange', function(event, ui) { ... }); beforeStop 当排序动作结束之前触发此事件。此时占位符元素和辅助元素仍有效。 定义:$('.selector').sortable({ beforeStop: function(event, ui) { ... } }); 绑定:$('.selector').bind('sortbeforeStop', function(event, ui) { ... }); stop 当排序动作结束时触发此事件。 定义:$('.selector').sortable({ stop: function(event, ui) { ... } }); 绑定:$('.selector').bind('sortstop', function(event, ui) { ... }); update 当排序动作结束时且元素坐标已经发生改变时触发此事件。 定义:$('.selector').sortable({ update: function(event, ui) { ... } }); 绑定:$('.selector').bind('sortupdate', function(event, ui) { ... }); receive 当一个已连接的sortable对象接收到另一个sortable对象的元素后触发此事件。 定义:$('.selector').sortable({ receive: function(event, ui) { ... } }); 绑定:$('.selector').bind('sortreceive', function(event, ui) { ... }); over 当一个元素拖拽移入另一个sortable对象后触发此事件。 定义:$('.selector').sortable({ over: function(event, ui) { ... } }); 绑定:$('.selector').bind('sortover', function(event, ui) { ... }); out 当一个元素拖拽移出sortable对象移出并进入另一个sortable对象后触发此事件。 定义:$('.selector').sortable({ out: function(event, ui) { ... } }); 绑定:$('.selector').bind('sortout', function(event, ui) { ... }); activate 当一个有使用连接的sortable对象开始排序动作时,所有允许的sortable触发此事件。 定义:$('.selector').sortable({ activate: function(event, ui) { ... } }); 绑定:$('.selector').bind('sortactivate', function(event, ui) { ... }); deactivate 当一个有使用连接的sortable对象结束排序动作时,所有允许的sortable触发此事件。 定义:$('.selector').sortable({ deactivate: function(event, ui) { ... } }); 绑定:$('.selector').bind('sortdeactivate', function(event, ui) { ... }); ·方法
destory 从元素中移除拖拽功能。 用法:.sortable( 'destroy' ) disable 禁用元素的拖拽功能。 用法:.sortable( 'disable' ) enable 启用元素的拖拽功能。 用法:.sortable( 'enable' ) option 获取或设置元素的参数。 用法:.sortable( 'option' , optionName , [value] ) serialize 获取或设置序列化后的每个item元素的id属性。 用法:.sortable( 'serialize' , [options] ) toArray 获取序列化后的每个item元素的id属性的数组。 用法:.sortable( 'toArray' ) refresh 手动重新刷新当前sortable对象的item元素的排序。 用法:.sortable( 'refresh' ) refreshPositions 手动重新刷新当前sortable对象的item元素的坐标,此方法可能会降低性能。 用法:.sortable( 'refreshPositions' ) cancel 取消当前sortable对象中item元素的排序改变。 用法:.sortable( 'cancel' ) 排序后保存有两种方法, 一是cookie,二是 数据库配置文件等。 这个是cookie 的例子 大家可以参考 http://www.cnblogs.com/tianxiangbing/archive/2010/01/26/jquery_sortable.html 下面是数据库的部分代码 原作: $(function() { var show = $(".loader"); var orderlist = $(".orderlist"); var listleft = $("div[id = 'column_left']"); var listcenter = $("div[id = 'column_center']"); var listright = $("div[id = 'column_right']"); $( ".column" ).sortable({ opacity: 0.5,//拖动的透明度 revert: true, //缓冲效果 cursor: 'move', //拖动的时候鼠标样式 connectWith: ".column", //开始用update 结果执行两次,浪费资源,古改成stop //但是stop在元素没有改变位置的时候也会执行, //用update其他js会有问题,^_^ stop: function(){ var new_order_left = []; //左栏布局 var new_order_center = [];//中栏布局 var new_order_right = [];//右栏布局 listleft.children(".portlet").each(function() { new_order_left.push(this.title); }); listcenter.children(".portlet").each(function() { new_order_center.push(this.title); }); listright.children(".portlet").each(function() { new_order_right.push(this.title); }); var newleftid = new_order_left.join(','); var newcenterid = new_order_center.join(','); var newrightid = new_order_right.join(','); $.ajax({ type: "post", url: jsonUrl, //服务端处理程序 data: { leftid: newleftid, centerid: newcenterid, rightid:newrightid}, //id:新的排列对应的ID,order:原排列顺序 // beforeSend: function() { // show.html(" 正在更新"); // }, success: function(msg) { //alert(msg); show.html(""); } }); } }); 原文出自: http://hb-keepmoving.iteye.com/blog/1154618
图片预加载
1 (function($) { 2 var cache = []; 3 // Arguments are image paths relative to the current page. 4 $.preLoadImages = function() { 5 var args_len = arguments.length; 6 for (var i = args_len; i--;) { 7 var cacheImage = document.createElement('img'); 8 cacheImage.src = arguments[i]; 9 cache.push(cacheImage); 10 } 11 } 12 13 jQuery.preLoadImages("image1.gif", "/path/to/image2.png"); 在新窗口打开链接 (target=”blank”)1 $('a[@rel$='external']').click(function(){ 2 this.target = "_blank"; 3 }); 4 5 /* 6 Usage: 7 <a href="http://www.catswhocode.com" rel="external">catswhocode.com</a> 8 */ 当支持 JavaScript 时为 body 增加 class1 /* 该代码只有1行,但是最简单的用来检测浏览器是否支持 JavaScript 的方法,如果支持 JavaScript 就在 body 元素增加一个 hasJS 的 class */ 2 $('body').addClass('hasJS'); 平滑滚动页面到某个锚点 1 $(document).ready(function() { 2 $("a.topLink").click(function() { 3 $("html, body").animate({ 4 scrollTop: $($(this).attr("href")).offset().top + "px" 5 }, { 6 duration: 500, 7 easing: "swing" 8 }); 9 return false; 10 }); 11 }); 鼠标滑动时的渐入和渐出1 $(document).ready(function(){ 2 $(".thumbs img").fadeTo("slow", 0.6); // This sets the opacity of the thumbs to fade down to 60% when the page loads 3 4 $(".thumbs img").hover(function(){ 5 $(this).fadeTo("slow", 1.0); // This should set the opacity to 100% on hover 6 },function(){ 7 $(this).fadeTo("slow", 0.6); // This should set the opacity back to 60% on mouseout 8 }); 9 }); 制作等高的列1 var max_height = 0; 2 $("div.col").each(function(){ 3 if ($(this).height() > max_height) { max_height = $(this).height(); } 4 }); 5 $("div.col").height(max_height); 在一些老的浏览器上启用 HTML5 的支持 1 (function(){ 2 if(!/*@cc_on!@*/0) 3 return; 4 var e = "abbr,article,aside,audio,bb,canvas,datagrid,datalist,details,dialog,eventsource,figure,footer,header,hgroup,mark,menu,meter,nav,output,progress,section,time,video".split(','),i=e.length;while(i--){document.createElement(e[i])} 5 })() 6 7 //然后在head中引入该js 8 <!--[if lt IE 9]> 9 <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> 10 <![endif]--> 测试浏览器是否支持某些 CSS3 属性 1 var supports = (function() { 2 var div = document.createElement('div'), 3 vendors = 'Khtml Ms O Moz Webkit'.split(' '), 4 len = vendors.length; 5 6 return function(prop) { 7 if ( prop in div.style ) return true; 8 9 prop = prop.replace(/^[a-z]/, function(val) { 10 return val.toUpperCase(); 11 }); 12 13 while(len--) { 14 if ( vendors[len] + prop in div.style ) { 15 // browser supports box-shadow. Do what you need. 16 // Or use a bang (!) to test if the browser doesn't. 17 return true; 18 } 19 } 20 return false; 21 }; 22 })(); 23 24 if ( supports('textShadow') ) { 25 document.documentElement.className += ' textShadow'; 获取 URL 中传递的参数1 $.urlParam = function(name){ 2 var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href); 3 if (!results) { return 0; } 4 return results[1] || 0; 5 } 禁用表单的回车键提交1 $("#form").keypress(function(e) { 2 if (e.which == 13) { 3 return false; 4 } 5 });
摘要: 为什么需要元数据模型 您已经熟悉使用 Cognos 来创建报表,进行自助式设计分析,然而这些需要的创建的应用都依赖于对应的元数据模型,用户使用元数据模型对他们的数据源进行分析和报告。元数据模型是整 个 Cognos 应用的基础,它是一个或多个数据源中信息的业务演示。基于这个基础,您才能够创建报表,进行自助式设计分析。 Cognos 能支持多种数据源,包括关系型的和多维的数据库。元数据模型能隐藏底... 阅读全文
什么是自助式设计分析 自助式设计分析是指在业务人员在同一工具做即席查询、报表和分析,完全由业务用户自助式制作。并且能和自助式仪表盘、专业报表工具紧密集成。通过业务人员进行自助式设计分析可以提升业务敏捷度、提升客户满意度、快速报表制作、降低 IT 管理成本。 IBM Cognos Business Insight Advanced 是用于创建报表和分析数据的基于 Web 的工具。商业用户可通过此用户界面了解其业务。IBM Cognos Business Insight Advanced 属于一种新型报表使用环境,将为商业用户带来全面的商业智能体验。 在《第一个自助式仪表盘》介绍了使用 IBM Cognos Business Insight 创建复杂的交互式仪表盘并以预定义的方式浏览内容。在 Business Insight 仪表盘中,可处理现有的内容并进行基本分析、数据浏览和协作性决策的制定。如果要执行深入分析并要创建报表,可以切换到 Business Insight Advanced 来执行更高级的数据浏览,例如添加附加度量、条件格式化和高级计算。 在 IBM Cognos BI 10.1 版本以前,IBM Cognos Report Studio 中的“专业”和“快速”两种创建模式,从 IBM Cognos BI 10.1 版本开始,“快速”创建模式现已替换为 IBM Cognos Business Insight Advanced,专业 IT 报表创建者仍使用 Report Studio 工具创建高级报表。 Business Insight Advanced 由业务人员使用,提供比原来“快速”创建模式的功能更为强大,例如完全支持列表报表、图表和关系数据源,而且还提供了专用于数据浏览的全新用户体验。 Business Insight Advanced 用户界面重点是在浏览数据。因此某些默认行为与以前版本的 IBM Cognos Report Studio 中“快速”创建模有所不同。例如,默认情况下,在 Business Insight Advanced 双击项目现将执行向下追溯。 不论是 IBM Cognos BI 的 Report Studio、Query Studio、Analysis Studio 制作的报表,IBM Cognos Business Insight Advanced 都可以打开,并且可以由业务人员进行自助式设计分析。而 Query Studio、Analysis Studio 功能已经被 Business Insight Advanced 取代,所以 Cognos BI 10 的业务用户只需要使用 Business Insight Advanced 就足够了。Report Studio 则留给专业人员使用。 创建第一个自助式设计分析 - 在开始菜单中启动 IBM Cognos BI Developer Edition,运行 Developer Edition Manager,在确保左侧的服务都正常的情况下,点击右上角的启动,选择 Business Insight Advanced。
- 在“Cognos > 公共文件夹 > 示样 > 多维数据集”路径下,选择“Great Outdoors Sales (cube)”数据包,进入 IBM Cognos Business Insight Advanced 后选择“新建”。在选择报表类型的时候,选择“交叉表”,点击确定。
如果载入数据包报错,例如在内容存储库中找不到数据源“great_outdoors_sales_en”,则需要按照《第一次安装》的步骤检查是否正确建立了 PowerCube 数据源。 - 在右侧的“可插入对象”窗格中的“来源”选项卡显示了面向成员的数据视图。您可以看到维度数据源,也就是包括多维数据源或者按维度建模的关系数据源,在本教程中使用的是 PowerCube 多维数据源。
- 数据包是模型的子集,其中包含可以插入到报表中的项目。
- 维度是有关业务主要方面(如产品、日期或市场)的描述性数据的概括分组。
- 级别层级是维度内更具体的分组。例如,对于“年份”维度,可以将数据组织为较小的组,如“年份”、“当前月份”和“上一个月份”。
- 成员文件夹包含可用于层级或级别的成员。例如,“年”级别层级的“成员”文件夹包含“年”、“季度”和“月份”级别中的所有内容。
- 级别是包含相同详细程度信息并具有共同属性的维度层级内的位置。一个级别层级内可以存在多个级别,从根级别开始。例如,“年份”级别层级具有以下相关级别。
- 成员属性是每个成员所具有的属性。例如,性别可以是所有员工成员的属性。
图 1. 可插入对象 - 对于维度模型数据源,您可以通过单击树面板顶部的按钮来交替查看元数据树和面向成员的数据树。在本教程中选择“查看成员树”。然后把 Years 拖动到列,把 Products 拖动到行,把 Revenue 拖动到度量,如图 2 所示。
图 2. 查看成员树 - 接下来您可以了解一下在 Business Insight Advanced 展示数据的功能。使用维度数据源或按维度建模的关系数据源时,您可以向下追溯到较低级别数据集或向上追溯到较高级别数据集的报表。向上追溯和向下追溯允许 您在预定义的维度层级(例如“年 - 年 - 季度 - 月”)内查看有关数据的更加全面或更加详细的信息,而无需创建其他报表。
左键点击交叉表中的产品“Golf Equipment”两次,可以实现向下追溯的功能。双击第一列最后一行汇总标题上的“Golf Equipment”可以实现向上追溯的功能。 其实在交叉表里面的任意数据都可以实现向下追溯的功能,而选择汇总标题上的数据都可以实现向上追溯的功能。 - 在“Golf Equipment”的向下追溯的效果下,单击成员“Golf Accessories”,在数据菜单下选择“浏览”,再单击“替换”,然后单击“使用级别成员”,如图 3 所示。
图 3. 替换成员 这样就把“Golf Accessories”替换为相同级别的成员。为了帮助理解,可以参看图 4,展开面向成员的数据树,和“Golf Accessories”相同级别的成员是标注红色的那些数据项。 图 4. 级别成员 - 等待报表刷新后,右键选择任意产品类型,点击菜单“最高或最低”,选择“最高 5 (基于 Revenue, Years)”,这样就筛选出最高的 5 个产品类型。
图 5. 最高或最低 - 右键选择汇总标题上的“Golf Equipment”,点击“删除”,这样就可以把不需要的汇总行除去,如图 6 所示。
图 6. 删除汇总行 - 按住 Ctrl 键选择“Tents”和“Packs”两个产品类型,右键选择“排除成员 > 从初始集”,如图 7 所示。您会发现仍然是 5 个产品类型,但是已经不是原来的 5 个,“Tents”和“Packs”两个产品类型已经被换成另外两个。
而如果您刚才选择“排除成员 > 从当前集”,则就会剩下 3 个产品类型。 图 7. 排除成员 - 在右侧的“可插入对象”窗格中的“来源”选项卡中选择“Personal Accessories”产品类型下的后面四个成员拖拽到报表的最后一行后面,如图 8 所示。
图 8. 添加成员 - 您会看到报表有 9 个产品类型,它们分别来自两个数据集。其中 Watches 和 Eyewear 这两个产品类型在两个数据集里面都有,属于重复的产品类型。
您按住 Ctrl 键可以选择两个数据集的 Eyewear,这样就等同于选择了两个数据集。然后在数据菜单下选择“浏览”,再单击“合并到一个集中”,选择“删除重复项”,如图 9 所示。 图 9. 合并数据集 - 这样就剩下合并在一个数据集中的 7 个产品类型,Watches 和 Eyewear 也没有重复了。
右键选择任意产品类型,点击菜单“最高或最低”,选择“最低 5 (基于 Revenue, Years)”,这样就筛选出最低的 5 个产品类型。 - 按住 Ctrl 键选择 Packs 和 Navigation 两个产品类型,右键选择“移动成员”,点击 “到顶部”,如图 10 所示。Packs 和 Navigation 两个产品类型就会被放到报表的头两行。
图 10. 移动成员 - 刚才您做了很多数据集的操作,这些操作包括:
- 排除成员
- 将成员移到集的顶部或底部
- 联接多个集
- 应用最高或最低过滤器
- 过滤集
- 展开或折叠集合中的成员
在 IBM Cognos Business Insight Advanced 可以查看集定义,从而了解、更改、添加或删除可对该集执行的操作,集的定义以图形树的形式为您显示了对集执行的所有操作的历史记录。 您可以任意选择数据项,比如 Packs ,然后右键菜单选择“编辑集”。或者您也可以在“数据”菜单下选择“浏览”,找到“编辑集”的菜单项。 图形树显示了对成员集合执行的所有操作,如图 11 所示。您可以执行以下操作,在这里选择“取消”即可。 - 要查看操作详情,请将鼠标悬停在操作节点上。
- 要更改操作的顺序,请单击操作节点,然后单击向右箭头或向左箭头。
- 要编辑操作,请单击操作节点,然后单击“编辑”按钮。
- 要添加新操作,请单击“新建”按钮。
图 11. 编辑集 - 任意选择报表的数据项,右键选择“显示属性”,这样会展开报表的属性页。您可以设置报表的属性。
图 12. 设置属性 - 保存您的分析结果在“我的文件夹”下,命名为“第一个自助式设计分析”。
使用图表 IBM Cognos Business Insight Advanced 包含一种默认图表技术,该技术不同于 10.1 之前版本中使用的原始图表技术。“使用原始图表创建”选项,让您能够使用老版本的原始图表技术的报表,而不是默认的 Business Insight Advanced 图表来创建新的报表。 在《第一张交互式离线报表》,您已经去掉“使用原始图表创建”的选择,这里可以再检查一下。您可以在“工具”菜单中点击“选项”,在“高级选项”标签中,检查是否已经去掉“使用原始图表创建”的选择。 IBM Cognos Business Insight Advanced 包含丰富的图表类型: - 柱形图:对于比较离散数据或显示随时间变化的趋势非常有用。
- 折线图:对于显示随时间变化的趋势和比较多个数据序列非常有用。
- 饼形图:对于突出显示比例非常有用。
- 条形图:对于显示随时间变化的趋势和绘制多个数据序列非常有用。
- 面积图:对于强调随时间变化的更改量非常有用,堆积面积图还用于显示部分与整体的关系。
- 点状图:对于以非群集样式显示定量数据非常有用。
- 组合图:通过在一个图表内使用柱形图、面积图和折线图的组合,绘出多个数据序列。组合图对于突出显示各种数据序列之间的关系非常有用。
- 散点图:使用数据点来沿刻度的任何位置(不仅在常规刻度线处)绘出两个度量,对于浏览不同数据集之间的相互关系非常有用。
- 泡形图:使用数据点和气泡沿某一刻度的任意位置绘出度量,就像散点图一样,气泡的大小表示第三个度量,对于表示财务数据非常有用。
- 项目符号图表:是条形图的一个变体。它们可以将特色度量(项目符号)与目标度量(目标)进行对比,也可以将比对度量与背景中提供其他定性度量(如“非常满意”、“满意”和“不满意”)的彩色区域联系到一起,通常用于代替执行仪表盘中的仪表板图。
- 仪表板图:使用指针将信息显示为表盘上的读数。在有颜色的数据范围或图表轴上读取每个指针的值非常容易。此图表类型常用于执行仪表盘报表,以显示关键业务指示器。
- 排列图:通过标识事件的主要原因来帮助您改善这些过程。排列图按照从最频繁到最不频繁的顺序对类别进行排名。这些图表常用于质量控制数据,以便您确定并减少问题的主要来源。
- 渐进图:类似于堆积图,单个堆积的各分段都从下一分段垂直偏移,对于强调各分段占整体的比例非常有用。
- 象限图:将背景分成四个等区域的泡形图。象限图对于绘出包含三个度量(使用 X 轴、Y 轴和表示第三个度量值的气泡大小)的数据很有用。
- Marimekko 图表:是百分堆积图,其中列的宽度与列值的总和成正比,各个分段高度是各自列总值的百分比。
- 雷达图:将多个轴组合成一个放射状的图形。对于每个数字,均沿着从图表中心开始的单独轴绘出数据。
- 盈亏图表:是一种 Microchart 图,其中每列的值为 1 或 -1,通常表示盈利或亏损。
- 极坐标图:对于显示科学计算数据非常有用,是使用值和角度将信息显示为极坐标的圆形图。
- 在工具栏中找到“页面布局”,为报表选择预定义的页面布局。您选择 1x2 的布局,这样就能在报表旁边放置图表,如图 13 所示。页面刷新后,报表出现在左侧的单元格,而右侧的单元格是空着的。
图 13. 页面布局 - 在右侧的“工具箱”中选择图表,拖拽到右侧的单元格中。当“插入图表”提示出现时,选择饼形图,如图 14 所示。
图 14. 插入图表 - 在右侧的“来源”中选择 Gross Profit 拖拽到默认度量,把 Sales Regions 拖拽到序列(饼形图扇区)。
- 您可以尝试在饼形图上进行向下追溯和向上追溯的功能。右键选择饼形扇区,然后单击“向下追溯”或者“向上追溯”。
- 您可以将饼形扇区拉离剩余的饼形图,以突出显示饼形扇区。右键选择要拉出的饼形扇区,然后单击“分解扇区”。此时扇区会从图表中拉出。要使已拉出的扇区返回到饼形图中,请右键选择饼形图图表对象,然后单击“删除已分解的扇区”。
- 您在“查看”菜单下可以切换“页面设计”和“页面预览”,其中“页面预览”是缺省选择,这是给业务人员进行自助式设计分析用的,所见即所得,可以直接看到数据结果。而“页面设计”功能是给专业人员用的,界面类似即将在后面介绍的 Report Studio。
- 保存并退出 Business Insight Advanced。
在自助式仪表盘中打开 - 在开始菜单中启动 IBM Cognos BI Developer Edition,运行 Developer Edition Manager,在确保左侧的服务都正常的情况下,点击右上角的启动,选择 Business Insight。在 IBM Cognos Business Insight 的启动页上选择“新建”。
- 在右侧的可插入对象中选择下面的“内容”,在“我的文件夹”下,可以找到刚才保存的“第一个自助式设计分析”,展开可以发现有两个对象,分别是“交叉表 1”和“饼形图 1”。右键选择“第一个自助式设计分析”报表,选择“插入”。
- 在自助式仪表盘中,您如果想看除了报表里面 5 个产品类型以外的产品,比如想把 Lanterns 产品类型也添加到报表中。那就不能直接在 Business Insight 里面完成,可以通过选择菜单的齿轮形状的按钮“完成更多”来启动 Business Insight Advanced,如图 15 所示。等待一会以后,Business Insight Advanced 界面就出现了。
图 15. 完成更多 - 默认情况下,当您使用 IBM Cognos Business Insight Advanced 将源目录树中的成员插入到报表中时,成员将与其子项一起以集 [ 创建成员集 ] 的形式插入。您可以更改成员的插入方式。因为现在只想添加 Lanterns 产品类型,所以想要插入不带子项的成员。
为此,在“可插入对象”窗格的“来源”选项卡上,单击“插入带子项的成员”按钮,并选择插入成员的方式,选择“插入单个成员”,如图 16 所示。 图 16. 插入单个成员 - 然后在 Products > Camping Equipment 路径下,把 Lanterns 拖拽到报表的最后一行,注意等到有闪烁行线出现时再松开鼠标。
- 这样就有 6 个产品类型出现在报表中,Lanterns 产品类型出现在报表的最后一行。点击右上角的“完成”回到 Business Insight 的仪表盘窗口。
- 值得注意的是,现在修改的报表仅仅是改变自助式仪表盘的数据,而不影响原先的报表“第一个自助式设计分析”。因此您可以在自助式仪表盘里面引用自助式设计 分析的结果,也可以随时利用自助式设计分析的强大功能来弥补自助式仪表盘的不足。Business Insight 和 Business Insight Advanced 两者无缝集成,不需要切换。
总结 您可以使用两种不同的方式打开 Business Insight Advanced: - 从执行报表高级编辑(“完成更多”)的 Business Insight 仪表盘中打开。这种方式适合业务人员在自助式仪表盘里面想进一步进行自助分析。
- 从 IBM Cognos Connection 的“启动”菜单中打开或从欢迎页面(“创建者业务报表”)中打开。这种方式适合业务人员进行自助设计分析。
Business Insight Advanced 是给业务人员进行自助式设计分析用的。在《第一次安装》中,您曾经用 Report Studio 构建了第一张简单报表,而 Report Studio 是给专业人士使用的报表工具。 您可以用 Report Studio 来打开刚才保存的“第一个自助式设计分析”。 在开始菜单中启动 IBM Cognos BI Developer Edition,运行 Developer Edition Manager,在确保左侧的服务都正常的情况下,点击右上角的启动,选择 Report Studio。在“Cognos > 公共文件夹 > 示样 > 多维数据集”路径下,选择“Great Outdoors Sales (cube)”数据包,进入 IBM Report Studio 后选择“打开现有的”。在“我的文件夹”下,可以找到刚才保存的“第一个自助式设计分析”,点击“打开”。 您可以看到 Report Studio 提供了给专业报表创建者的许多功能,下面举例说明 Business Insight Advanced 不能实现而 Report Studio 可以完成的功能。 - 在 Report Studio 中间的“查询资源管理器”,您可以创建或修改关系报表 [ 使用关系查询 ] 或维度报表 [ 使用维度查询 ] 中的查询,以及执行复杂任务,如定义合并联接和写入 SQL 语句,如图 17 所示。在 Business Insight Advanced,您不能看到也不能操控查询。
图 17. 查询资源管理器 - 点击交叉表的左上方的三个小红点,选择交叉表,您可以看到交叉表的属性,如图 18 所示。这里有一些属性是 Business Insight Advanced 无法编辑的。比如在“其他”类别里面的“名称”,这里的名称就是您刚才在 Business Insight 中进行自助式仪表盘时候看到的对象名称。您可以在 Report Studio 把它修改成更有意义的名字,比如“产品线收入表”。
图 18. 交叉表属性
摘要: 熟悉 Report Studio Report Studio 是用来制作更加精细的专有报表的工具。IBM Cognos Report Studio 是一个基于 Web 的报表创建工具,专业报表创建者和开发人员可使用此工具针对多个数据库创建复杂的、具有多页并且可以进行多项查询的报表。使用 Report Studio,您可以创建公司所需的任何报表,如发票报表、财务报表以及每周销售和库存报表。报表... 阅读全文
什么是自助式仪表盘 IBM Cognos Business Insight 基于网页的界面可以让您建立先进的交互式的仪表盘,来提供见解并使协同决策变得简单。在 IBM Cognos Business Insight 中创建的仪表盘可以让业务用户将集成的商业智能体验与协作决策相结合。用户可以快速而轻松地完成绝大部分任务。例如可以查看并与报表交互、排列数据或者执 行额外的计算、或者与团队中其他成员共享信息。因为用户对报表和数据有不同的需求,所以可以利用自由形式布局也可以重新排列报表或添加新报表。 定制化内容 当启动 IBM Cognos Business Insight 的时候,您可以选择打开一个已存在的仪表盘或是创建一个仪表盘。在打开的工作区域中,您都可以添加或重新排列新小工具,所有仪表盘都是可编辑的。业务用户 可以将有权限访问各种系统内容组合成一个指定的仪表盘,并可以进行分析。高级业务用户或报表作者可以为一组业务用户创建报表和基础仪表盘,来包含该组用户 工作所需的所有信息。这样,业务用户就能定制仪表盘来契合特殊的需求。这些需求可能包括重排布局,更改图形,将数据排序,添加计算,新建过滤等以及搜索一 份补充报表并添加其到工作区域。 添加新内容 您可以从内容或工具箱标签拖拽新小工具到仪表盘来。使用相同的方式,你可以添加报表、报表组件、度量列表或独立度量、TM1 项目、或者任何小工具中描述的项目。您可以使用 IBM Cognos Business Insight 中增强的搜索功能来寻找并添加相关内容到仪表盘。这个功能是一种全文搜索,类似于流行的搜索引擎。 交互分析能力 除了更改报表中数据的显示,你也可以与报表交互,并自定义其排列。此外,你还可以利用报表中的数据添加基础性运算,可以过滤数据。 高级过滤 使用过滤小工具可以过滤报表中所有与其关联的报表。这样,如果你有一个地区选择过滤器,它过滤所有该地区一项的报表。当然,它只过滤那些与这个过滤器关联的报表中的数据。当你在过滤小工具上选择一个值,报表就会重新刷新来显示你选择的过滤好的数据项。 其他内容 除了报表内容之外,您也可以添加其他内容到仪表盘,例如 Web 页面、图像、我的收件箱、文本、RSS 订阅。 创建注释 对于仪表盘上的一份报表中的内容,评论或注释有助于用户与团队中其他成员进行合作。这些注释可以让其他查看相同报表的用户看见。这些用户也可以添加 关于此报表的进一步注释,来提供额外的信息。例如,注释可以提醒在某个地区调查销售较低的结果,也可以是某些异常数据的一个解释,例如一个最近发布且已在 市场上有数月的产品的销售数据较小。您可以注释实时报表并保存输出版本。当打印一个报表的 PDF 版本或是导出报表为 PDF 或 Excel 输出时,注释也会被包括在其中。 创建第一个自助式仪表盘 - 在开始菜单中启动 IBM Cognos BI Developer Edition,运行 Developer Edition Manager,在确保左侧的服务都正常的情况下,点击右上角的启动,选择 Business Insight。在 IBM Cognos Business Insight 的启动页上选择“新建”。
- 在右侧的可插入对象中选择下面的“内容”,在“公共文件夹 > 示样 > 模型 > GO 数据仓库 ( 查询 ) > Business Insight 源报表 > 收入数据”路径下,右键选择“Revenue by Country – bar - chart”报表,选择“插入”,如图 1 所示。再用同样的方式,把另外的 Revenue by Product Type - Combination Chart 和 Revenue by Order method - Pie Chart 两张报表插入到仪表盘中。
图 1. 选择报表 - 在仪表盘空白处点击右键,选择“排列所有小工具以符合内容的显示尺寸”来调整三张报表在仪表盘的大小和位置。您也可以单独调整每个报表的大小和位置,方法是鼠标移动到报表上,等到小工具菜单显示后选择“调整大小以适合内容”,如图 2 所示。
图 2. 调整大小 - 在右侧的可插入对象中选择下面的“工具箱”,可以看到有很多小工具,比如可以添加公司的 Web 页面,添加公司商标图像等。这里我们选择“文本”,输入“公司收入仪表盘”,选择合适的大小,把它添加到仪表盘中,如图 3 所示。
图 3. 添加文本 - 点击上方工具栏的保存按钮,保存到我的文件夹中,命名为“第一个交互式仪表盘”。不要关闭 IBM Cognos Business Insight。
您可以在 Business Insight 中保存的任意仪表盘设置为主页。在保存按钮旁边有“主页”按钮,您可以在旁边单击箭头,找到“将仪表盘设为主页”这个选项。 查看仪表盘的数据 - 选择“Revenue by Country – bar - chart”报表,在上方的小工具操作中选择“更改显示类型”,更改为“列表”,如图 4 所示。在“更改显示类型”旁边的按钮是“更改调色板”,您可以根据喜好来选择不同风格不同类型的显示方式来展现数据。
图 4. 更改显示类型 - 在列表中选择“中国”、“巴西”、“加拿大”和“法国”,然后在上方小工具操作中选择“过滤器”,过滤条件是“包含中国、巴西、加拿大、法国”,如图 5 所示。 。
图 5. 添加本地过滤条件 - 在上方的小工具操作中选择“更改显示类型”,更改为“条形图”。这样新的“Revenue by Country – bar - chart”报表就仅仅包含中国、巴西、加拿大、法国了。
- 点击左上方的展开按钮,可以查看已应用过滤器和已应用排序,如图 6 所示。点击过滤器右边的删除按钮,把刚才建立的“包含中国、巴西、加拿大、法国”过滤器删除。
图 6. 查看本地过滤条件 - 在右侧的可插入对象中选择下面的“工具箱”,右键点击“幻灯片过滤器”选择“插入”,把它添加到仪表盘中。在幻灯片过滤器属性窗口中,选择三张报表都有的数据项“收入”,把过滤器的内容设为值范围,修改描述文本后确定,如图 7 所示。
图 7. 幻灯片过滤器属性 - 您可以试着调整页面布局,使得仪表盘更美观。在页面的右上角有一个“内容”按钮,点击可以隐藏或者显示右侧的可插入对象,隐藏右侧的可插入对象可以让仪变盘区域变得更大一些。
您还可以试着拖拽幻灯片过滤器的滑条杆,仪表盘的三张报表图形都会随着变化。 图 8. 调整全局过滤条件 选择一张报表点击左上方的展开按钮,可以查看已应用过滤器和已应用排序,如图 9 所示。刚才建立的全局过滤条件已经显示在已应用过滤器中了,如果在这里删除过滤器,那么只会影响当前报表,而不会影响仪表盘中的其他报表,这是和本地过滤条件的区别所在。 图 9. 查看全局过滤条件 仪表盘的数据交互 - 在右侧的可插入对象中选择下面的“内容”,在“公共文件夹 > 示样 > 模型 > GO 数据仓库 ( 分析 ) > Report Studio 报表示样”路径下,右键选择“预算与实际”报表,选择“在新仪表盘中打开”。如果提示“是否保存仪表盘”,选择“是”。
- 点击上方工具栏的保存按钮,保存到我的文件夹中,命名为“数据交互”。
- 右键选择您需要评注的数据,比如美洲户外用品商店的差额百分比 69.16%,选择添加注释,如图 10 所示。
图 10. 添加注释 - 注释内容填写“69.16% - 增加预算”,确定以后可以看到在 69.16% 数据项上有红色三角形标记,表明有注释。您要查看注释,可将指针悬停于注释(由红色三角形标记指明)上。具有报表读取权限的所有用户都可以查看注释。 如果同一单元格或报表小工具有多个注释,它们将按逆时序显示。 对于每个注释,您可以看到撰写注释的用户的用户名、撰写日期和时间。
图 11. 查看注释 - 点击上方小工具操作的菜单,可以将包含报表内容的报表小工具导出为多种格式。要保存数据的快照,您可以创建报表小工具的 PDF 版本,刚才添加的注释也会保留在导出的 PDF 文件中。当您导出到 PDF 时,小工具将出现在 Adobe Reader 中,因此您必须在计算机上安装 Adobe Reader。
图 12. 将仪表盘小工具导出为不同格式 - 您可以查看报表数据项的 lineage 信息,以了解该报表数据项所代表的内容。Lineage 信息通过数据包和数据包使用的数据源追溯项目的元数据。Lineage 还显示报表创建者添加的或在数据模型中定义的所有数据项过滤器。
IBM Cognos BI Lineage 工具包括两种视图:业务视图和技术视图。业务视图显示了高级语篇信息,该信息描述了数据项及其所来自的数据包。技术视图是选定数据项的 lineage 的图形表示。Lineage 从数据包中的数据项一直跟踪到数据包使用的数据源。 图 13. 查看报表数据项的传承信息 - 下面按住 CTRL 键选中您关心的地区门店,比如“美洲 > 户外用品商店”、“美洲 > 直销”和“美洲 > 百货商店”,然后点击上方小工具操作的计算按钮,选择“+ > 直销 + 户外用品商店 + 百货商店”。您会发现刷新后的仪表盘把计算结果显示在新行中。默认情况下,将使用计算中所用的表达式作为标题名称。计算结果不会存储在基础数据源中。相 反,IBM Cognos Business Insight 会在每次刷新报表时重新运行计算。计算结果始终以数据源中的最新数据为基础。
图 14. 增加计算 您在上方小工具操作的按钮里面还能找到“分组 / 取消分组”、排序等按钮。如果是灰色,表示该功能不适用您目前的报表和数据。 排序按字母或数字的升序或降序组织数据。例如,您可以对列出产品销售额的列按降序进行排序,以便对产品销售额从高到低进行排序。 如果列表报表中的同一列多次出现同一个值,那么您可以将这些相同的值归为一组。分组操作将对选定报表项目的行进行重新排序,这样相同值会一起显示并抑制重 复显示的情况。由于分组的列会显示在未分组的列之前,因此分组和取消分组操作可能会更改报表项目的顺序。但是您可以对列表中的列重新排序来提高报表的可读 性。 总结 在本系列教程的“第 1 部分,第一次安装”中,您安装了样例数据和样例档案。您可以在“公共文件夹 > 示样 > 模型 > Business Insight 示样”目录下找到六个样例仪表盘。您可以依次打开来进行更深一步地研究和学习。 - 员工满意度仪表盘
该报表显示员工满意度的不同度量,例如培训投资、员工调查结果(按部门和按主题,包括与计划调查结果的比较),以及员工奖金列表(按国家 / 地区排序)。滑块过滤器适用于奖金列表。 - 市场营销仪表盘
该仪表盘显示不同促销活动的结果。活动名称的选择值过滤器适用于前两个图表。产品系列选择值过滤器适用于广告费图表,年份滑块过滤器适用于广告费交叉表。 - 招聘仪表盘
该仪表盘显示针对不同指标(根据组织、部门、分部)的招聘结果(填充职位的平均天数)、年份和有关不同招聘技巧的成功的详细信息。两个选择值过滤器控制其中三个小工具。 - 收入数据仪表盘
该仪表盘按区域、国家 / 地区(由选择值过滤器控制)、产品类型(由选择值过滤器控制)及订购方法显示收入。 - 销售(按年)仪表盘
该仪表盘显示由滑块过滤器控制的一年范围内不同的销售指标:利润率、毛利润、产品成本、销售数量、区域收入以及实际收入与计划收入之间的比较。滑块过滤器控制所有小工具。 - 销售仪表盘(交互式)
该仪表盘显示销售的不同方面:月毛利润、地区毛利润和产品系列毛利润,区域收入以及对该销售做出贡献的销售代表的数目。 该源对象基于“Go 数据仓库 ( 分析 )”数据包和“Go 数据仓库 ( 查询 )”数据包。 该销售仪表盘是交互式的,可以支持向上追溯和向下追溯的功能。 您可以在报表或报表组成部分中向上追溯或向下追溯。在 IBM Cognos Business Insight 中,对于列表和交叉表,将指针悬停在数据项上时,超链接会标识可追溯项目。在图表中,当您将指针悬停在可追溯项目上时,指针将变为手形,并且工具提示将指 明您将追溯的内容。在 Business Insight 中,仅当您使用按维度结构化的数据时,才能执行向上追溯和向下追溯。
什么是交互式离线报表 IBM Cognos Active Report 是可以与用户交互的离线报表,包含了数据和展现内容,它在无法访问企业内部网络和数据库的情况下仍然可以通过此类报表分析数据,获得有价值的信息。 IBM Cognos Active Report 非常适合移动办公的情况,如销售体系。使用者在离线的条件下浏览报表,深入挖掘数据,获取额外的信息。IBM Cognos Active Report 拓展了商务智能的应用场景,并让系统户的更好的性能和支持更大的使用规模。 用户使用 IBM Cognos Report Studio 来创建 Active Report。IBM Cognos Active Report 具有很强的交互性和易用性,报表的设计从用户需求出发,并确保的简洁美观流畅的用户体验。 IBM Cognos Active Report 是 IBM Cognos Report Studio 报表的拓展。数据需要以一种简洁易懂的组织方式呈现给客户。有些用户习惯于数字,而另一些则偏好于图表。为了方便设计人员设计出更简洁的报表,IBM Cognos Report Studio 在保持原有功能的同时加入了一些交互式的控件 , 如选项卡、下拉菜单等,用于定义交互报表,对数据进行排序和过滤。 作为一个高级业务人员,可以将刚刚完成的动态报表下载成为本地文件,并转发给公司其它成员 . 文件最终以 mht 格式保存 , 并可以以邮件附件形式发送给同事。 如果正在使用 Microsoft Internet Explorer 6.0,则无法将 MHT 格式的活动报表作为文件打开,交互式离线报表需要 Microsoft Internet Explorer 7 版本以上。要在 Mozilla Firefox 中查看 MHT 格式的活动报表,必须先下载一个 UnMHT 附加组件。 创建第一张交互式离线报表 - 在开始菜单中启动 IBM Cognos BI Developer Edition,运行 Developer Edition Manager,在确保左侧的服务都正常的情况下,点击右上角的启动,选择 Report Studio。
- 在“Cognos > 公共文件夹 > 示样 > 模型”路径下,选择“ GO 数据仓库 ( 查询 ) ”数据包,进入 IBM Report Studio 后选择“新建”。
图 1. 选择数据包 在选择报表类型的时候,选择“活动报表”,点击确定。 图 2. 选择报表类型 - 在左侧的可插入对象中先插入一个列表,然后展开“销售和市场营销(查询)”目录,再展开“销售(查询)”命名空间,按住 Control 键后选择“产品”下的产品类型以及“销售资料”下的收入和计划收入,拖动到右边报表页中,如图 3 所示。
准备工作 IBM Cognos Business Intelligence 10.1 是最新的商业智能解决方案,用于提供查询、报表、分析、仪表板和记分卡功能,并且可通过规划、方案建模、预测分析等功能进行扩展。它可以在人们尝试了解业绩并使用工具做出决策时,在思考和工作方式方面提供支持,以便人们可以搜索和组合与业务相关的所有方面,并与之进行交互。 - 查询和报表功能为用户提供根据事实做出决策所需的信息。
- 仪表板使任何用户都能够以支持其做出决策的方式来访问内容、与之交互,并对其进行个性化设置。
- 分析功能使您能够从多个角度和方面对信息进行访问,从而可以查看和分析信息,帮助您做出明智的决策。
- 协作功能包括通信工具和社交网络,用于推动决策过程中的意见交流。
- 记分卡功能可实现业务指标的捕获、管理和监控的自动化,使您可将其与自己的战略和运营目标进行比较。
在开始体验 Cognos BI 10.1 之前,您需要到 IBM developerWorks 去下载 Cognos 10.1 的试用版。IBM Cognos BI Developer Edition V10.1.0 下载地址是:http://www.ibm.com/developerworks/cn/downloads/im/cognosbi/。 您需要下载两个文件,IBM Cognos BI Developer Edition 10.1.0 Windows English 的下载文件名是 CZS56EN.tar.gz,IBM Cognos Business Intelligence Samples V10.1.0 for DB2 LUW Windows English 的下载文件名是 CZQ90EN.tar.gz。IBM Cognos BI Developer Edition V10.1.0 有 30 天的使用时间,足够让您完成本教程的学习了。 根据试用版的系统需求,您需要用 Windows 操作系统来进行安装,要求 Windows XP SP3 或者更高的版本。另外您要安装 Windows 的 Internet 信息服务 IIS,并且把浏览器 IE 升级到 7 版本以上。建议内存有 2G 以上,有 1.5 G 以上的临时空间和 2G 以上的磁盘空间来进行安装。 由于本教程需要数据库来存放 Cognos BI 10.1 的样例数据,所以您还需要到 IBM developerWorks 去下载 DB2 的社区版。DB2 Express-C 9.7.4 for Windows 下载地址是:http://www.ibm.com/developerworks/cn/downloads/im/udbexp/。下载文件名是 db2exc_974_WIN_x86.exe。 安装 DB2 双击 db2exc_974_WIN_x86.exe 然后选择任意目录进行解压缩。解压成功后点击 setup.exe 进入 DB2 安装启动板。 图 1. DB2 安装启动板 点击“安装新产品”,进入到安装向导界面,点击“下一步”继续。阅读并接受许可协议,点击“下一步”继续。 图 2. 选择安装类型 本教程选择典型安装,这个选项将安装 DB2 的主要部件和功能。 图 3. 选择创建响应文件 选择不创建响应文件而只进行安装,点击“下一步”继续。 图 4. 选择安装文件夹 一般使用默认的驱动器和目录设置就可以了,确保有 560 MB 可用空间,点击“下一步”继续。 图 5. 设置用户信息 安装 DB2 之后,某些 DB2 进程会作为系统服务运行。为了运行这些服务,需要一个操作系统帐户。在本教程中,使用默认的 db2admin 用户帐户,密码为 cognos,DB2 安装程序会在操作系统中创建它。当然您也可以指定使用一个现有的帐户,但是这个帐户必须具有本地管理员权力。点击“下一步”继续。 图 6. 配置 DB2 实例 可以认为 DB2 实例是数据库的容器。必须有一个实例,然后才能创建数据库。在 Windows 上进行安装时,会自动创建一个称为 DB2 的实例。在默认情况下,DB2 实例监听端口 50000 上的 TCP/IP 连接。可以点击配置来查看,在本教程中一切保持默认配置即可。点击“下一步”继续。 图 7. 开始复制文件 检查前面选择的安装选项。单击“安装”。结束以后,点击“完成”即可。 安装 Cognos 解压文件 CZS56EN.tar.gz,然后点击运行 Cognos 10 BI Server 安装程序 install.exe。 图 8. IBM Cognos BI Developer Edition 安装界面 选择简体中文后,点击“OK”。在简介这一步骤中,阅读许可协议并选择接受协议条款。 图 9. 选择安装文件夹 选择缺省文件夹即可以,要保证有可用空间。快捷方式文件夹是指产品图标的程序组,也可以采用缺省的 IBM Cognos BI Developer Edition。 图 10. 配置 在配置界面中可使用默认的端口。然后给 Cognos 的管理员设定用户名和密码,在本教程中设定管理员为 administrator,密码为 cognos。 图 11. 安装摘要 在安装摘要中可以看到整个安装需要 1.5G 以上的磁盘空间,点击“安装”继续。 图 12. 完成安装 完成安装界面时候,勾选启动 IBM Cognos BI Developer Edition Manager,然后点击“完成”。IBM Cognos BI Developer Edition Manager 是开发版特有的组件,可以用来安装和配置 IBM Cognos BI 和 Framework Manager,也可以用来管理用户和启动停止服务。 图 13. IBM Cognos Developer Edition Manager 第一次启动界面 第一次启动 IBM Cognos BI Developer Edition Manager,点击“完成”让 Manager 进行初始化的安装和配置。 图 14. 安装 IBM Cognos Developer Edition Manager 安装过程可能需要十几分钟,耐心等待直到登陆界面出现。 图 15. 登陆 IBM Cognos Developer Edition Manager 在登陆界面中输入在 图 10所示的用户名和密码,在本教程中是 administrator 和 cognos,然后点击确定。 图 16. 安装 Cogos BI Suite 选择 BI Suite,然后进行安装。IBM Cognos BI Suite 赋予了您完整的自助服务报告和即席查询能力,使其可以访问、修改和创建报表。访问任意类型的数据,包括关系 OLAP、分析 OLAP 或桌面文件。并通过 Web、PDF、Excel、电子邮件或门户发送您的报表。安装过程可能需要半小时,耐心等待直到直到登陆界面出现。在登陆界面中输入在图 10 所示的用户名和密码,在本教程中是 administrator 和 cognos,然后点击确定。 图 17. 安装 Framework Manager 选择 Framework Manager,然后进行安装,这个步骤非常快就完成了。Framework Manager 是元数据建模工具。它使得建模者可以创建和管理业务相关的元数据,以便在所有 Cognos BI 应用程序中使用。Framework Manager 的主要用户为数据仓库开发人员和数据建模者。 配置 Cognos 首先需要把 C:\Program Files\IBM\SQLLIB\java 目录下的 db2jcc.jar 和 db2jcc_license_cu.jar 拷贝到 C:\Program Files\IBM\Cognos Developer\tomcat\lib 目录下,然后在 IBM Cognos Developer Edition Manager 重启 Cognos 服务。 安装样例数据 解压文件 CZQ90EN.tar.gz,然后点击运行 install_DB2_samples.exe 进行安装。在 Instruction 这一步骤中,阅读许可协议并选择接受协议条款。 图 18. 选择安装路径 安装路径采用缺省的,这样会和 IBM Cognos BI Developer Edition 安装在同一目录下。完成安装后,样例数据文件会放在 C:\Program Files\IBM\Cognos Developer\webcontent\samples\datasources\db2,找到数据文件 GS_DB.tar.gz,并进行解压缩。 然后在点击开始菜单,选择运行,输入 db2cmd 再确定,进入到 DB2CLP 的窗口。安装过程可能需要五分钟,具体操作命令和显示参见清单 1。在这个过程中需要输入 DB2 的用户名和密码,在本教程是 db2admin 和 cognos。 清单 1. 创建数据库并导入数据
>CD C:\Program Files\IBM\Cognos Developer\webcontent\samples\datasources\db2\GS_DB\win >GOSalesConfig.bat >setupGSDB.bat ------------------------------------------------------------------- DB2 version 9 or later detected - using DB2 Version 9 syntax ------------------------------------------------------------------- Press Enter at the prompts to accept the default value shown Default values can be specifed in the file GOSalesConfig.sh ------------------------------------------------------------------- Please enter the name of the database ( or the alias ) to be used for the GOSales sample data (default=GS_DB) : ------------------------------------------------------------------- This script can create the GS_DB database. Creating the database will cause any existing databases with the same name to be dropped. If you choose not to recreate the database, existing objects within the database will be dropped. Would you like to create the database GS_DB (Y/N) Default=Y : Please wait ... Starting GOSALES_RUN_SCRIPTS Dropping existing database GS_DB if found Creating database GS_DB Please wait ... Starting GOSALES_RUN_SCRIPTS Connecting to GS_DB 输入 db2admin 的当前密码:cognos 数据库连接信息 数据库服务器 = DB2/NT 9.7.4 SQL 授权标识 = DB2ADMIN 本地数据库别名 = GS_DB Error dropping existing tables Creating tables. Loading data. Creating primary keys Creating indexes Creating constraints. Creating stored procedures Creating views Granting permissions Updating statistics Verifying row counts Table row count validation successful Adding table comments | 创建数据源连接 在开始菜单中启动 IBM Cognos BI Developer Edition,运行 Developer Edition Manager,在确保左侧的服务都正常的情况下,点击右上角的启动,如图 19 所示选择 IBM Cognos Administration。 图 19. 启动 Cognos Administration 在 IBM Cognos Administration 界面中选择配置页,选中“数据源连接”,点击右上角的图标,如图 20 所示新建数据源。 图 20. 在 Cognos Administration 中添加数据源 - 在指定名称和说明 - 新建数据源向导中,名称中输入“great_outdoors_sales”,点击下一步。
- 在指定连接 - 新建数据源向导中,选择类型为“IBM DB2”,点击下一步。
- 在指定 IBM DB2 连接字符串 - 新建数据源向导中,DB2 数据库名称写为“GS_DB”,在登陆勾选“密码”,用户 ID 中写为 “db2admin”,密码和确认密码写为 “cognos”。然后点击“测试连接”如图 21 所示。测试成功后点击下一步。如果测试有问题的时候,可以重启 Cognos 服务或者重启系统。
图 21. 指定 IBM DB2 连接字符串 - 在指定命令 - 新建数据源向导中,保持缺省,点击完成。
- 重复 1 到 4 的步骤,再创建“great_outdoors_warehouse”数据源,即名称为“great_outdoors_warehouse”,其余内容“great_outdoors_sales”一样。
- 在指定名称和说明 - 新建数据源向导中,名称中输入“sales_and_marketing”,点击下一步。
- 在指定连接 - 新建数据源向导中,选择类型为“IBM Cognos PowerCube”,点击下一步。
- 指定 IBM Cognos PowerCube 连接字符串 - 新建数据源向导中,Windows 位置输入“C:\Program Files\IBM\Cognos Developer\webcontent\samples\datasources\cubes\PowerCubes\EN \sales_and_marketing.mdc”。然后点击“测试连接”,测试成功后点击完成。
- 重复 6 到 8 的步骤,再创建“great_outdoors_sales_en”数据源,即名称为 “great_outdoors_sales_en”,Windows 位置输入“C:\Program Files\IBM\Cognos Developer\webcontent\samples\datasources\cubes\PowerCubes\EN \great_outdoors_sales_en.mdc”。
- 重复 6 到 8 的步骤,再创建“employee_expenses”数据源,即名称为“employee_expenses”,Windows 位置输入“C:\Program Files\IBM\Cognos Developer\webcontent\samples\datasources\cubes\PowerCubes\EN\ employee_expenses.mdc”。
- 重复 6 到 8 的步骤,再创建“go_accessories”数据源,即名称为“go_accessories”,Windows 位置输入“C:\Program Files\IBM\Cognos Developer\webcontent\samples\datasources\cubes\PowerCubes\EN\ go_accessories.mdc”。
- 重复 6 到 8 的步骤,再创建“go_americas”数据源,即名称为“go_americas”,Windows 位置输入“C:\Program Files\IBM\Cognos Developer\webcontent\samples\datasources\cubes\PowerCubes\EN\ go_americas.mdc”。
- 重复 6 到 8 的步骤,再创建“go_asia_pacific”数据源,即名称为“go_asia_pacific”,Windows 位置输入“C:\Program Files\IBM\Cognos Developer\webcontent\samples\datasources\cubes\PowerCubes\EN\ go_asia_pacific.mdc”。
回页首 导入示例档案库 接下来要把示例的部署档案库导入到 Cognos 环境中来。这些档案库的位置在 C:\Program Files\IBM\Cognos Developer\webcontent\samples\content\db2 目录下。选择拷贝三个文件到 C:\Program Files\IBM\Cognos Developer\deployment 目录下准备进行部署。 这三个文件是 IBM_Cognos_Samples.zip,本教程主要的 Great Outdoors 公司例子; IBM_Cognos_DrillThroughSamples.zip, Great Outdoors 公司显示钻透功能的例子; IBM_Cognos_PowerCube.zip 多维立方体的例子。 - 在 IBM Cognos Administration 界面中选择配置页,选中“内容管理”,点击右上角的图标,如图 22 所示新建导入。
图 22. 新建导入向导 - 在部署档案库中,首先选择 IBM_Cognos_Samples,点击下一步。
- 在指定名称和说明 - 新建导入向导,按照默认的名字,点击下一步。
- 在公共文件夹内容中,勾选档案库的内容“示样”,点击下一步。
- 在指定常规选项 - 新建导入向导,保留默认选项,点击下一步。
- 复查汇总 - 新建导入向导,点击下一步。
- 在选择操作 - 新建导入向导中,选择“保存并运行一次” ,点击完成。
- 在运行使用选项 - IBM_Cognos_Samples 中,选择“现在”,点击运行。
- 重复 1 到 8 的步骤,选择 IBM_Cognos_PowerCube 进行导入。
10. 重复 1 到 8 的步骤,选择 IBM_Cognos_DrillThroughSamples 进行导入。 第一张简单报表 在开始菜单中启动 IBM Cognos BI Developer Edition,运行 Developer Edition Manager,在确保左侧的服务都正常的情况下,点击右上角的启动,选择 Report Studio,如 图 19所示。 在“Cognos > 公共文件夹 > 示样 > 模型”路径下,选择“ GO 数据仓库 ( 查询 ) ”数据包,进入 IBM Report Studio 后选择“新建”。 图 23. 选择数据包 在选择报表类型的时候,选择“列表”,点击确定。 图 24. 选择报表类型 在左侧的可插入对象中,展开“销售和市场营销(查询)”目录,再展开“销售(查询)”命名空间,选择“产品”下的产品系列、产品类型和产品,选择“销售资料”下的数量和收入,一个一个拖动到右边的列表中。 图 25. 选择项目 剩下的工作就是点击工具栏的运行按钮,来等待您的第一张简单报表出炉了,如图 26 所示。 图 26. 运行报表 报表运行结果如图 27 所示。 图 27. 第一张简单报表 总结 在整个安装过程中,DB2 是用户数据源,我们把本教程的样例数据放在了 DB2 的 GS_DB 数据库中,并在 Cognos Administration 进行了数据源的配置。 Cognos Connection 是 Cognos 门户,提供信息的集成和用户访问的统一入口。管理员可以通过他实现用户、角色管理,服务器配置,权限控制等各种管理功能;最终用户可以通过 Cognos Connection 访问到文件夹、报表、个性化展现、访问 Cognos Viewer、Report Studio、Business Insight 和 Event Studio 的内容。 Report Studio 是专业的报表制作模块。报表制作人员可以通过他制作各种类型的报表,包括中国特色的非平衡报表,地图,动态仪表盘,KPI 报表等。报表制作人员可以分页面设计,每页可以有多个查询,每个查询可以连接多个数据源,甚至异构数据源。报表的内容采用的是化繁为简的方式,可以精确控 制报表中每一个对象的各种属性。 Business Insight 是业务用户的自定义仪表盘工具,用户可以拖拽任意 Cognos BI 内容(包含查询,报表,分析,TM1 数据集等)形成自定义的仪表盘。 Cognos Framework Manager 是一个专门对元数据进行管理的客户端开发工具。他可以连接多个数据源,能够连接 OLAP 和数据库等各种数据源,并提供对元数据的定制和管理以及安全性控制等相关控制。 图 28. IBM Cognos BI Developer Edition 架构和工作原理 当您运行第一张简单报表的时候,IBM Cognos BI Developer Edition 是按照下面步骤来运行的: - 在 IBM Cognos Framework Manager 工具中,建模人员确保元数据是按照业务人员可以理解的方式来进行组织的。建模人员把元数据从一个或多个数据库中导入,并按照业务需求添加到模型中。
- 建模人员把模型数据包发布到 IBM Cognos Connection,这样开发报表人员就可以利用它们来进行创建报表和仪表盘了。比如在本教程中的“ GO 数据仓库 ( 查询 ) ”数据包。
- 业务人员和报表开发人员利用已经发布的数据包来理解业务数据。
- 用户在 IBM Cognos Connection 运行、查看和管理他们的内容。根据不同的权限,他们可以简单运行和查看报表,或者管理计划、门户展示等。
源文出自:http://blog.csdn.net/thy822/article/details/7608305
两种方法一样,只是写法不同 第一种写法 1 listeners:{ 2 beforeedit:function(e){ 3 if(...) e.cancel = true;//true表示不可编辑 4 } 5 } 第二种写法 1 grid.on('beforeedit',function(e)){ 2 var record = e.record; 3 if(...){ 4 e.cancel = true;//true表示不可编辑 5 } 6 }
1.首先看效果 2.需要一个合计插件GridSummary.js GridSummary 插件,下载后将后缀名该为 js 3.页面需要引用的文件 1 <link rel="stylesheet" type="text/css" href="include/ext-3.4.0/resources/css/ext-all.css" /> 2 <link rel="stylesheet" type="text/css" href="include/ext-3.4.0/ux/css/ux-all.css" /> 3 <script type="text/javascript" src="include/ext-3.4.0/adapter/ext/ext-base.js"></script> 4 <script type="text/javascript" src="include/ext-3.4.0/ext-all.js"></script> 5 <script type="text/javascript" src="include/ext-3.4.0/ux/ux-all.js"></script> 6 <script type="text/javascript" src="include/ext-3.4.0/locale/ext-lang-zh_CN.js"></script> 7 <script type="text/javascript" src="include/ext-3.4.0/ux/GridSummary.js"></script> <!-- 合计插件 -->
8 <script type="text/javascript" src="demo.js"></script> <!-- 示例js -->
4.Grid代码 var url = "RequestAction/AT/Req_CT_SHIPMENTS.aspx?Action=GetDetailAll&S_SHIPMENTS_M_GUID=" + guid; //复选框 var sm = new Ext.grid.CheckboxSelectionModel(); var textFileldVehicle = new Ext.form.TextField ({ allowBlank: false, blankText: "请输入车号", maxLength: 50 });
var numField = new Ext.form.NumberField({ allowNegative: false, allowDecimals: true, allowFormat: true, decimalPrecision: 2, allowBlank: false, blankText: '金额必须大于零' }); //字段集合 var fields = [ { name: 'S_VEHICLE_NUMBER' }, { name: 'N_QUANTITY' }, { name: 'N_FREIGHT' }, { name: 'S_GUID' }, { name: 'S_SHIPMENTS_M_GUID' } ]; var proxy = new Ext.data.HttpProxy({ url: url }); //数据读取器 var reader = new Ext.data.JsonReader({ totalProperty: "totalPorperty", //数据总条数 root: "rows", //将要显示数据的数组 id: "S_GUID", //每一行数据的唯一记录 fields: fields });
//列集合 其中 summaryType: 'sum' 为求和
var cm = new Ext.grid.ColumnModel ({ columns: [sm, new Ext.grid.RowNumberer({ header: 'NO', width: 30, align: 'center' }), { header: '车号', dataIndex: 'S_VEHICLE_NUMBER', editor: textFileldVehicle, summaryRenderer: function (v, params, data) { return '合计'; } }, { header: '重量', dataIndex: 'N_QUANTITY', summaryType: 'sum', renderer: formatNumberDefault, align: 'right', editor: numField }, { header: '运费', dataIndex: 'N_FREIGHT', summaryType: 'sum', renderer: formatNumberDefault, align: 'right', editor: numField} ] }); //如果全部列都可排序否则单个设置 cm.defaultSortable = false;
//创建一个store var shipmentsDetailstore = new Ext.data.Store({ proxy: proxy, reader: reader, autoDestroy: true, autoLoad: { params: { start: 0, limit: pageSize} } });
//插入行按钮 var btn_Insert = new Ext.Button({ text: '插入行', iconCls: 'insert', handler: function () { //定义一个recode对象 var initValue = createShipmentDetailRow(); grid.stopEditing(); var maxRowIndex = grid.getStore().getCount(); grid.getStore().insert(maxRowIndex, initValue); //在第一个位置插入 grid.view.refresh(); grid.getSelectionModel().selectLastRow(); grid.getView().focusRow(maxRowIndex); //焦点标记行 grid.startEditing(maxRowIndex, 2); //单元格转换成编辑状态 } }); //删除行按钮 var btn_Remove = new Ext.Button({ text: '删除行', iconCls: 'delete', handler: function () { grid.stopEditing(); var rows = grid.getSelectionModel().getSelections(); if (rows == undefined || rows.length == 0) { setShipmentStatusBarText('error', '请选择需要删除的行!'); return; //判断记录集是否为空,为空返回 } grid.getStore().remove(rows); grid.view.refresh(); } });
//工具栏 var tbar = new Ext.Toolbar({ cls: 'top-toolbar', items: [btn_Insert, '-', btn_Remove] }); var summary = new Ext.ux.grid.GridSummary(); //创建GRID var grid = new Ext.grid.EditorGridPanel ({ id: 'ShipmentsDetailGirdPanel', deferredRender: false, enableColumnHide: false, enableHdMenu: false, columnLines: true, enableColumnMove: false, store: shipmentsDetailstore, sm: sm, cm: cm, loadMask: true, //自适应宽度 参数为列数 // autoExpandColumn: 4, //超过长度带自动滚动条 autoScroll: true, border: false, nocache: false, timeout: 10, clicksToEdit: 1, scripts: true, loadMask: { msg: '正在加载数据,请稍侯……' }, tbar: tbar, view: new Ext.ux.grid.BufferView({ rowHeight: 25, scrollDelay: true, forceFit: true, deferEmptyText: true, emptyText: "无数据" }), plugins: summary }); 5.此时会看到合计行字体偏小 添加样式 .x-grid3-summary-row .x-grid3-cell-inner { FONT: 12.5px tahoma,arial,helvetica,sans-serif } 6.此示例Ext版本为 3.4.0
人们对软件架构存在非常多的误解,其中一个最为普遍的误解就是:将架构(Architecture)和框架(Framework)混为一谈。 框架是一种特殊的软件,它并不能提供完整无缺的解决方案,而是为你构建解决方案提供良好的基础。框架是半成品。典型地,框架是系统或子系统的半成品;框架中的服务可以被最终应用直接调用,而框架中的扩展点是供应用开发人员定制的“可变化点”。 软件架构不是软件,而是关于软件如何设计的重要决策。软件架构决策涉及到如何将软件系统分解成不同的部分、各部分之间的静态结构关系和动态交互关系等。经 过完整的开发过程之后,这些架构决策将体现在最终开发出的软件系统中;当然,引入软件框架之后,整个开发过程变成了“分两步走”,而架构决策往往会体现在 框架之中。或许,人们常把架构和框架混为一谈的原因就在于此吧! 节选自《软件架构设计》书稿 原文出自: http://blog.csdn.net/lovingprince/article/details/3347248
Web集群是由多个同时运行同一个web应用的服务器组成,在外界看来就像一个服务器一样,这多台服务器共同来为客户提供更高性能的服务。集群更标准的定 义是:一组相互独立的服务器在网络中表现为单一的系统,并以单一系统的模式加以管理,此单一系统为客户工作站提供高可靠性的服务。 而负载均衡的任务就是负责多个服务器之 间(集群内)实现合理的任务分配,使这些服务器(集群)不会出现因某一台超负荷、而其他的服务器却没有充分发挥处理能力的情况。负载均衡有两个方面的含 义:首先,把大量的并发访问或数据流量分担到多台节点上分别处理,减少用户等待响应的时间;其次,单个高负载的运算分担到多台节点上做并行处理,每个节点 设备处理结束后,将结果汇总,再返回给用户,使得信息系统处理能力可以得到大幅度提高 因此可以看出,集群和负载均衡有本质上的不同,它们是解决两方面问题的不同方案,不要混淆。 集群技术可以分为三大类: 1、高性能性集群(HPC Cluster) 2、高可用性集群(HA Cluster) 3、高可扩展性集群 一、高性能性集群(HPC Cluster) 指以提高科学计算能力为目标的集群技术。该集群技术主要用于科学计算,这里不打算介绍,如果感兴趣可以参考相关的资料。 二、高可用性集群(HA Cluster) 指为了使群集的整体服务尽可能可用,减少服务宕机时间为目的的集群技术。如果高可用性集群中的某节点发生了故障,那么这段时间内将由其他节点代替它的工作。当然对于其他节点来讲,负载相应的就增加了。 为了提高整个系统的可用性,除了提高计算机各个部件的可靠性以外,一般情况下都会采用该集群的方案。 对于该集群方案,一般会有两种工作方式: ①主-主(Active-Active)工作方式 这是最常用的集群模型,它提供了高可用性,并且在只有一个节点时也能提供可以接受的性能,该模型允许最大程度的利用硬件资源。每个节点都通过网络对客户机 提供资源,每个节点的容量被定义好,使得性能达到最优,并且每个节点都可以在故障转移时临时接管另一个节点的工作。所有的服务在故障转移后仍保持可用,但 是性能通常都会下降。 这是目前运用最为广泛的双节点双应用的Active/Active模式。 支撑用户业务的应用程序在正常状态下分别在两台节点上运行,各自有自己的资源,比如IP地址、磁盘阵列上的卷或者文件系统。当某一方的系统或者资源出现故障时,就会将应用和相关资源切换到对方的节点上。 这种模式的最大优点是不会有服务器的“闲置”,两台服务器在正常情况下都在工作。但如果有故障发生导致切换,应用将放在同一台服务器上运行,由于服务器的处理能力有可能不能同时满足数据库和应用程序的峰值要求,这将会出现处理能力不够的情况,降低业务响应水平。 ②主-从(Active-Standby)工作方式 为了提供最大的可用性,以及对性能最小的影响,主-从工作方式需要一个在正常工作时处于备用状态的节点,主节点处理客户机的请求,而备用节点处于空闲状态,当主节点出现故障时,备用节点会接管主节点的工作,继续为客户机提供服务,并且不会有任何性能上影响。 两节点的Active/Standby模式是HA中最简单的一种,两台服务器通过双心跳线路组成一个集群。应用Application联合各个可选的系统组件如:外置共享的磁盘阵列、文件系统和浮动IP地址等组成业务运行环境。 PCL为此环境提供了完全冗余的服务器配置。这种模式的优缺点: - 缺点:Node2在Node1正常工作时是处于“闲置”状态,造成服务器资源的浪费。
- 优点:当Node1发生故障时,Node2能完全接管应用,并且能保证应用运行时的对处理能力要求。
三、高可扩展性集群 这里指带有负载均衡策略(算法)的服务器群集技术。带负载均衡集群为企业需求提供了更实用的方案,它使负载可以在计算机集群中尽可能平均地分摊处理。而需 要均衡的可能是应用程序处理负载或是网络流量负载。该方案非常适合于运行同一组应用程序的节点。每个节点都可以处理一部分负载,并且可以在节点之间动态分 配负载, 以实现平衡。对于网络流量也是如此。通常,单个节点对于太大的网络流量无法迅速处理,这就需要将流量发送给在其它节点。还可以根据每个节点上不同的可用资 源或网络的特殊环境来进行优化。 负载均衡集群在多节点之间按照一定的策略(算法)分发网络或计算处理负载。负载均衡建立在现有网络结构之上,它提供了一种廉价有效的方法来扩展服务器带宽,增加吞吐量,提高数据处理能力,同时又可以避免单点故障。 前面已经说过负载均衡的作用是在多个节点之间按照一定的策略(算法)分发网络或计算处理负载。负载均衡可以采用软件和硬件来实现。一般的框架结构可以参考下图。 后 台的多个Web节点上面有相同的Web应用,用户的访问请求首先进入负载均衡分配节点(可能是软件或者硬件),由它根据负载均衡策略(算法)合理地分配给 某个Web应用节点。每个Web节点相同的内容做起来不难,所以选择负载均衡策略(算法)是个关键问题。下面会专门介绍均衡算法。 web 负载均衡的作用就是把请求均匀的分配给各个节点,它是一种动态均衡,通过一些工具实时地分析数据包,掌握网络中的数据流量状况,把请求理分配出去。对于不 同的应用环境(如电子商务网站,它的计 算负荷大;再如网络数据库应用,读写频繁,服务器的存储子系统系统面临很大压力;再如视频服务应用,数据传输量大,网络接口负担重压。),使用的均衡策略 (算法)是不同的。 所以均衡策略(算法)也就有了多种多样的形式,广义上的负载均衡既可以设置专门的网关、负载均衡器,也可以通过一些专用软件与协议来实现。在OSI七层协 议模型中的第二(数据链路层)、第三(网络层)、第四(传输层)、第七层(应用层)都有相应的负载均衡策略(算法),在数据链路层上实现负载均衡的原理是 根据数据包的目的MAC地址选择不同的路径;在网络层上可利用基于IP地址的分配方式将数据流疏通到多个节点;而传输层和应用层的交换(Switch), 本身便是一种基于访问流量的控制方式,能够实现负载均衡。 目前,基于负载均衡的算法主要有三种:轮循(Round-Robin)、最小连接数(Least Connections First),和快速响应优先(Faster Response Precedence)。 ①轮循算法,就是将来自网络的请求依次分配给集群中的节点进行处理。 ②最小连接数算法,就是为集群中的每台服务器设置一个记数器,记录每个服务器当前的连接数,负载均衡系统总是选择当前连接数最少的服务器分配任务。 这要比"轮循算法"好很多,因为在有些场合中,简单的轮循不能判断哪个节点的负载更低,也许新的工作又被分配给了一个已经很忙的服务器了。 ③快速响应优先算法,是根据群集中的节点的状态(CPU、内存等主要处理部分)来分配任务。 这一点很难做到,事实上到目前为止,采用这个算法的负载均衡系统还很少。尤其对于硬件负载均衡设备来说,只能在TCP/IP协议方面做工作,几乎不可能深入到服务器的处理系统中进行监测。但是它是未来发展的方向。 上面是负载均衡常用的算法,基于以上负载均衡算法的使用方式上,又分为如下几种: 1、DNS轮询 最早的负载均衡技术是通过DNS来实现的,在DNS中为多个地址配置同一个名字,因而查询这个名字的客户机将得到其中一个地址,从而使得不同的客户访问不同的服务器,达到负载均衡的目的。 DNS负载均衡是一种简单而有效的方法,但是它不能区分服务器的差异,也不能反映服务器的当前运行状态。当使用DNS负载均衡的时候,必须尽量保证不同的 客户计算机能均匀获得不同的地址。由于DNS数据具备刷新时间标志,一旦超过这个时间限制,其他DNS服务器就需要和这个服务器交互,以重新获得地址数 据,就有可能获得不同IP地址。因此为了使地址能随机分配,就应使刷新时间尽量短,不同地方的DNS服务器能更新对应的地址,达到随机获得地址,然而将过 期时间设置得过短,将使DNS流量大增,而造成额外的网络问题。DNS负载均衡的另一个问题是,一旦某个服务器出现故障,即使及时修改了DNS设置,还是 要等待足够的时间(刷新时间)才能发挥作用,在此期间,保存了故障服务器地址的客户计算机将不能正常访问服务器 2、反向代理服务器 使用代理服务器,可以将请求转发给内部的服务器,使用这种加速模式显然可以提升静态网页的访问速度。然而,也可以考虑这样一种技术,使用代理服务器将请求均匀转发给多台服务器,从而达到负载均衡的目的。 这种代理方式与普通的代理方式有所不同,标准代理方式是客户使用代理访问多个外部服务器,而这种代理方式是代理多个客户访问内部服务器,因此也被称为反向代理模式。虽然实现这个任务并不算是特别复杂,然而由于要求特别高的效率,实现起来并不简单。 使用反向代理的好处是,可以将负载均衡和代理服务器的高速缓存技术结合在一起,提供有益的性能。然而它本身也存在一些问题,首先就是必须为每一种服务都专门开发一个反向代理服务器,这就不是一个轻松的任务。 代理服务器本身虽然可以达到很高效率,但是针对每一次代理,代理服务器就必须维护两个连接,一个对外的连接,一个对内的连接,因此对于特别高的连接请求, 代理服务器的负载也就非常之大。反向代理方式下能应用优化的负载均衡策略,每次访问最空闲的内部服务器来提供服务。但是随着并发连接数量的增加,代理服务 器本身的负载也变得非常大,最后反向代理服务器本身会成为服务的瓶颈。 3、地址转换网关 支持负载均衡的地址转换网关,可以将一个外部IP地址映射为多个内部IP地址,对每次TCP连接请求动态使用其中一个内部地址,达到负载均衡的目的。很多 硬件厂商将这种技术集成在他们的交换机中,作为他们第四层交换的一种功能来实现,一般采用随机选择、根据服务器的连接数量或者响应时间进行选择的负载均衡 策略来分配负载。由于地址转换相对来讲比较接近网络的低层,因此就有可能将它集成在硬件设备中,通常这样的硬件设备是局域网交换机。 原文出自: http://blog.csdn.net/lovingprince/article/details/3290871
//修复办法,谷歌浏览器中,table的单元格实际宽度=指定宽度+padding,所以只要重写gridview里的一个方法,如下:
1 //修复办法,谷歌浏览器中,table的单元格实际宽度=指定宽度+padding,所以只要重写gridview里的一个方法,如下: 2 Ext.override(Ext.grid.GridView,{ 3 getColumnStyle : function(colIndex, isHeader) { 4 var colModel = this.cm, 5 colConfig = colModel.config, 6 style = isHeader ? '' : colConfig[colIndex].css || '', 7 align = colConfig[colIndex].align; 8 9 if(Ext.isChrome){ 10 style += String.format("width: {0};", parseInt(this.getColumnWidth(colIndex))-2+'px'); 11 }else{ 12 style += String.format("width: {0};", this.getColumnWidth(colIndex)); 13 } 14 15 if (colModel.isHidden(colIndex)) { 16 style += 'display: none; '; 17 } 18 19 if (align) { 20 style += String.format("text-align: {0};", align); 21 } 22 23 return style; 24 }, 25 }); 26 [代码] [JavaScript]代码//看看修复过后的效果
原文出自:http://www.oschina.net/code/snippet_201314_15163
使用AES加密时,当密钥大于128时,代码会抛出java.security.InvalidKeyException: Illegal key size or default parameters Illegal key size or default parameters是指密钥长度是受限制的,java运行时环境读到的是受限的policy文件。文件位于${java_home}/jre/lib/security 这种限制是因为美国对软件出口的控制。 解决办法: 去掉这种限制需要下载Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files.网址如下。 下载包的readme.txt 有安装说明。就是替换${java_home}/jre/lib/security/ 下面的local_policy.jar和US_export_policy.jar jdk 5: http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-java-plat-419418.html#jce_policy-1.5.0-oth-JPR jdk6: http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html 参考http://stackoverflow.com/questions/6481627/java-security-illegal-key-size-or-default-parameters 原文出自:http://blog.csdn.net/shangpusp/article/details/7416603
随笔- 50 文章- 0 评论- 180 jQuery中所支持的异步模型为: - Callbacks,回调函数列队。
- Deferred,延迟执行对象。
- Promise,是Deferred只暴露非状态改变方法的对象。
这些模型都很漂亮,但我想要一种更帅气的异步模型。 Thread? 我们知道链式操作是可以很好的表征运行顺序的(可以参考我的文章《jQuery链式操作》),然而通常基于回调函数或者基于事件监听的异步模型中,代码的执行顺序不清晰。 Callbacks模型实际上类似一个自定义事件的回调函数队列,当触发该事件(调用Callbacks.fire())时,则回调队列中的所有回调函数。 Deferred是个延迟执行对象,可以注册Deferred成功、失败或进行中状态的回调函数,然后通过触发相应的事件来回调函数。 这两种异步模型都类似于事件监听异步模型,实质上顺序依然是分离的。 当然Promise看似能提供我需要的东西,比如Promise.then().then().then()。但是,Promise虽然成功用链式操作明确了异步编程的顺序执行,但是没有循环,成功和失败分支是通过内部代码确定的。 个人认为,Promise是为了规范化后端nodejs中I/O操作异步模型的,因为I/O状态只有成功和失败两种状态,所以他是非常成功的。 但在前端,要么只有成功根本没有失败,要么不止只有两种状态,不应当固定只提供三种状态的方案,我觉得应该提供可表征多状态的异步方案。 这个大家可以在something more看到。 我想要一种类似于线程的模型,我们在这里称为Thread,也就是他能顺序执行、也能循环执行、当然还有分支执行。 顺序执行 线程的顺序执行流程,也就是类似于: 这样就是依次执行do1,do2,do3。因为这是异步模型,所以我们希望能添加wait方法,即类似于: do1(); wait(1000); //等待1000ms do2(); wait(1000); //等待1000ms do3(); wait(1000); //等待1000ms
不使用编译方法的话,使用链式操作来表征顺序,则实现后的样子应当是这样的: 1 Thread(). //获取线程 2 then(do1). //然后执行do1 3 wait(1000). //等待1000ms 4 then(do2). //然后执行do2 5 wait(1000). //等待1000ms 6 then(do3). //然后执行do3 7 wait(1000); //等待1000ms 循环执行 循环这很好理解,比如for循环: 1 for(; true;){ 2 dosomething(); 3 wait(1000); 4 } 进行无限次循环执行do,并且每次都延迟1000ms。则其链式表达应当是这样的: 1 Thread(). //获取线程 2 loop(-1). //循环开始,正数则表示循环正数次,负数则表示循环无限次 3 then(dosomething). //然后执行do 4 wait(1000). //等待1000ms 5 loopEnd(); //循环结束 这个可以参考后面的例子。 分支执行 分支也就是if...else,比如: 1 if(true){ 2 doSccess(); 3 }else{ 4 doFail(); 5 } 6 7 那么其链式实现应当是: 1 Thread(). //获得线程 2 right(true). //如果表达式正确 3 then(doSccess). //执行doSccess 4 left(). //否则 5 then(doFail). //执行doFail 6 leftEnd(). //left分支结束 7 rightEnd(); //right分支结束 声明变量 声明变量也就是: 可被其它函数使用。那么我们的实现是:
1 Thread(). //得到线程 2 define("hello world!"). //将回调函数第一个参数设为hello world! 3 then(function(a){alert(a);}); //获取变量a,alert出来 顺序执行实现方案 Thread实际上是一个打包函数Fn队列。 所谓打包函数就是将回调函数打包后产生的新的函数,举个例子: 1 function package(callback){ 2 return function(){ 3 callback(); 4 // 干其他事情 5 } 6 } 这样我们就将callback函数打包起来了。 Thread提供一个fire方法来触发线程取出一个打包函数然后执行,打包函数执行以后回调Thread的fire方法。 那么我们就可以顺序执行函数了。 现在只要打包的时候设置setTimeout执行,则这个线程就能实现wait方法了。 循环执行实现方案 循环Loop是一个Thread的变形,只不过在执行里面的打包函数的时候使用另外一种方案,通过添加一个指针取出,执行完后触发Loop继续,移动指针取出下一个打包函数。 分支执行实现方案 分支Right和Left也是Thread的一种变形,开启分支的时候,主Thread会创建两个分支Right线程和Left线程,打包一个触发分支Thread的函数推入队列,然后当执行到该函数的时候判断触发哪个分支执行。 其中一个队列执行结束后回调主Thread,通知进行下一步。 例子 由于该方案和wind-asycn非常相似,所以我们拿wind.js中的clock例子进行改造看看其中的差别吧。 wind.js中的例子: 我的例子: Something more? 我们提供了on方法将事件转成分支来执行。 举个例子页面有个按钮“点我”,但是我们希望打开页面5秒内单击没有效,5秒后显示“请点击按钮”后,单击才会出现“你成功点击了”。 使用on分支是这样的:
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 2 <html> 3 <head> 4 <title>on - asThread.js Sample</title> 5 <meta http-equiv="X-UA-Compatible" content="IE=9" /> 6 <script src="asThread.js"></script> 7 </head> 8 <body> 9 <button id = "b">点我</button> 10 <script> 11 var ele = document.getElementById("b"); 12 13 Thread(). // 获得线程 14 then(function(){alert("请点击按钮")}, 5000). //然后等5秒显示"请点击按钮" 15 on(ele, "click"). // 事件分支On开始,如果ele触发了click事件 16 then(function(){alert("你成功点击了")}). //那么执行你成功点击了 17 onEnd(). // 事件分支On结束 18 then(function(){alert("都说可以的了")}). // 然后弹出"都说可以的了" 19 run(); //启动线程 20 </script> 21 </body> 22 </html> 自定义事件也可以哦,只要在.on时候传进去注册监听函数,和删除监听函数就行了。比如: 1 function addEvent(__elem, __type, __handler){ 2 //添加监听 3 } 4 5 function removeEvent(__elem, __type, __handler){ 6 //删除监听 7 } 8 9 Thread(). 10 on(ele, "success", addEvent, removeEvent). 11 then(function(){alert("成功!")}). 12 onEnd(). 13 run(); 当然实际上我们还可以注册多个事件分支。事件分支是并列的,也就是平级的事件分支没有现有顺序,所以我们能这样: 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 2 <html> 3 <head> 4 <title>on - asThread.js Sample</title> 5 <meta http-equiv="X-UA-Compatible" content="IE=9" /> 6 <script src="asThread.js"></script> 7 </head> 8 <body> 9 <button id = "b">点我</button> 10 <button id = "c">点我</button> 11 <script> 12 var ele0 = document.getElementById("b"), 13 ele1 = document.getElementById("c"); 14 15 Thread(). // 获得线程 16 then(function(){alert("请点击按钮")}, 5000). //然后等5秒显示"请点击按钮" 17 on(ele0, "click"). // 事件分支On开始,如果ele0触发了click事件 18 then(function(){alert("你成功点击了")}). //那么执行你成功点击了 19 onEnd(). // 事件分支On结束 20 on(ele1, "click"). // 事件分支On开始,如果ele1触发了click事件 21 then(function(){alert("你成功点击了")}). //那么执行你成功点击了 22 onEnd(). // 事件分支On结束 23 then(function(){alert("都说可以的了")}). // 然后弹出"都说可以的了" 24 run(); //启动线程 25 </script> 26 </body> 27 </html> 一个线程不够用?只要输入名字就能开辟或者得到线程了。 系统会自动初始化一个主线程,当不传参数时就直接返回主线程: 但如果主线程正在用想开辟一个线程时,只要给个名字就行,比如: Thread("hello") //得到名字是hello的线程 那么下次再想用该线程时只要输入相同的名字就行了: Thread("hello") //得到hello线程 默认只最多只提供10个线程,所以用完记得删掉: IE10已经提供了setImmediate方法,而其他现代浏览器也可以模拟该方法,其原理是推倒线程末端,使得浏览器画面能渲染,得到比setTimeout(0)更快的响应。 我们通过接口.imm来提供这一功能。比如: Thread(). imm(function(){alert("hello world")}). run(); 这方法和.then(fn)不太一样,.then(fn)是可能阻塞当前浏览器线程的,但.imm(fn)是将处理推到浏览器引擎列队末端,排到队了在运行。 所以如果你使用多个Thread(伪多线程),而又希望确保线程是并行运行的,那么请使用.imm来替代.then。 当然对于老版IE,只能用setTimeout(0)替代了。 分支Right传的参数如果只是布尔值肯定很不爽,因为这意味着分支是静态的,在初始化时候就决定了,但我们希望分支能在执行到的时候再判断是走 Right还是Left,所以我们提供了传参可以是函数(但是函数返回值需要是布尔值,否则……╮(╯▽╰)╭也会转成布尔值的……哈哈)。比如: 1 fucntion foo(boolean){ 2 return !boolean; 3 } 4 5 Thread(). 6 define(true). 7 right(foo). 8 then(function(){/*这里不会运行到*/}). 9 rightEnd(). 10 run(); Enjoy yourself!! 项目地址 https://github.com/miniflycn/asThread 原问出自: http://www.cnblogs.com/justany/archive/2013/01/25/2874602.html
如何在 WebSphere 中解决 jar 包冲突 郝 爱丽, 高级软件工程师, IBM 中国软件开发中心 简介: Jar 包冲突问题是在大型 Java 软件开发中经常遇到的问题,系统开发人员经常会为解决类似的问题耗费大量的时间进行调试和测试,本文根据各种际情况,结合 WebSphere 中类加载器,讨论了几种解决 jar 包冲突问题的办法,并给出了具体实现的步骤及源代码。 读者定位为具有 Java 和 WebSphere 开发经验的开发人员。 读者可以学习到在 WebSphere 中类加载器的定义以及解决 jar 包冲突问题的几种办法,并可以直接使用文章中提供的 Java 代码,从而节省他们的开发和调试时间,提高效率。 大型的基于 WebSphere 的项目开发中,同一个 WebSphere Application Server(以下简称 WAS)上会部署多个应用程序,而这多个应用程序必然会共用一些 jar 包,包括第三方提供的工具和项目内部的公共 jar 等。把这些共用的 jar 包提取出来在多个应用程序之间共享,不仅可以统一对这些 jar 包进行维护,同时也提高了 WAS 的性能。但是随着应用的不断扩大,新的应用程序的不断增加,新的应用程序会希望使用一些更高版本的共享 jar 包,而由于系统运行维护的需要,老的应用程序仍然希望用老版本的共享 jar 包,这样就必然造成了共享 jar 包的版本冲突。jar 包版本冲突问题是在大型应用项目的开发中经常遇到的问题,本文试图从 WebSphere 的类加载器入手,讨论几种在不同情况下解决 jar 包冲突问题的办法。 WebSphere 中类加载器介绍 Jar 包冲突实际上是应用程序运行时不能找到真正所需要的类,而影响类的查找和加载的是 JVM 以及 WebSphere 中的类加载器(class loader),为此,我们首先介绍一下 WebSphere 中的类加载器以及一些相关的概念。 WebSphere 中类加载器层次结构 Java 应用程序运行时,在 class 执行和被访问之前,它必须通过类加载器加载使之有效,类加载器是 JVM 代码的一部分,负责在 JVM 虚拟机中查找和加载所有的 Java 类和本地的 lib 库。类加载器的不同配置影响到应用程序部署到应用程序服务器上运行时的行为。JVM 和 WebSphere 应用程序服务器提供了多种不同的类加载器配置, 形成一个具有父子关系的分层结构。WebSphere 中类加载器的层次结构图 1 所示: 图 1:WebSphere 中类加载器的层次结构 如上图所示,WebSphere 中类加载器被组织成一个自上而下的层次结构,最上层是系统的运行环境 JVM,最下层是具体的应用程序,上下层之间形成父子关系。 - JVM Class loader:位于整个层次结构的最上层,它是整个类加载器层次结构的根,因此它没有父类加载器。这个类加载器负责加载 JVM 类, JVM 扩展类,以及定义在 classpath 环境变量上的所有的 Java 类。
- WebSphere Extensions Class loader:WebSphere 扩展类加载器 , 它将加载 WebSphere 的一些 runtime 类,资源适配器类等。
- WebSphere lib/app Class loader:WebSphere 服务器类加载器,它将加载 WebSphere 安装目录下 $(WAS_HOME)/lib/app 路径上的类。 在 WAS v4 版本中,WAS 使用这个路径在所有的应用程序之间共享 jar 包。从 WAS v5 开始, 共享库功能提供了一种更好的方式,因此,这个类加载器主要用于一些原有的系统的兼容。
- WebSphere "server" Class loader:WebSphere 应用服务器类加载器。 它定义在这个服务器上的所有的应用程序之间共享的类。WAS v5 中有了共享库的概念之后,可以为应用服务器定义多个与共享库相关联的类加载器,他们按照定义的先后顺序形成父子关系。
- Application Module Class Loader:应用程序类加载器,位于层次结构的最后一层,用于加载 J2EE 应用程序。根据应用程序的类加载策略的不同,还可以为 Web 模块定义自己的类加载器。
关于 WebSphere 的类加载器的层次结构,以下的几点说明可能更有助于进一步的理解类的查找和加载过程: - 每个类加载器负责在自身定义的类路径上进行查找和加载类。
- 一个子类加载器能够委托它的父类加载器查找和加载类,一个加载类的请求会从子类加载器发送到父类加载器,但是从来不会从父类加载器发送到子类加载器。
- 一旦一个类被成功加载,JVM 会缓存这个类直至其生命周期结束,并把它和相应的类加载器关联在一起,这意味着不同的类加载器可以加载相同名字的类。
- 如果一个加载的类依赖于另一个或一些类,那么这些被依赖的类必须存在于这个类的类加载器查找路径上,或者父类加载器查找路径上。
- 如果一个类加载器以及它所有的父类加载器都无法找到所需的类,系统就会抛出 ClassNotFoundExecption 异常或者 NoClassDefFoundError 的错误。
类加载器的委托模式 类加载器有一个重要的属性:委托模式(Delegation Mode,有时也称为加载方式:Classloader mode)。委托模式决定了类加载器在查找一个类的时候, 是先查找类加载器自身指定的类路径还是先查找父类加载器上的类路径。 类加载器的委托模式有两个取值: - Parent_First:在加载类的时候,在从类加载器自身的类路径上查找加载类之前,首先尝试在父类加载器的类路径上查找和加载类。
- Parent_Last:在加载类的时候,首先尝试从自己的类路径上查找加载类,在找不到的情况下,再尝试父类加载器类路径。
有了委托模式的概念,我们可以更加灵活的配置在类加载器的层次结构中类的加载和查找方式。表 1 中给出了在 WebSphere 的类加载器层次结构中各个类加载器的委托模式的定义,并给出了不同的类加载器内类的生命周期。 注意:在上表中,"JVM Class loader" 因为在类加载器的最顶层,它没有父类加载器,因此其委托模式为 N/A,"WebSphere Extensions Class loader"和"WebSphere lib/app Class loader"的委托模式固定为表中的取值,不可配置,其它的类加载器的委托模式都是可以配置的。 WebSphere 中的类加载器策略 WebSphere 中对类加载器有一些相关的配置,称为类加载器策略(class loader policy)。类加载器策略指类加载器的独立策略(class loader isolation policy), 通过类加载器策略设置,我们可以为 WAS 和应用程序的类加载器进行独立定义。 每个 WAS 可以配置自己的应用程序类加载器策略,WAS 中的每个应用程序也可以配置自己的 Web 模块类加载器策略,下面我们对这两种策略分别介绍。 1 .应用服务器(WAS)配置:应用程序类加载器策略 应用服务器对应用程序类加载器策略有两种配置: - Single:整个应用服务器上的所有应用程序使用同一个类加载器。在这种配置下,每个应用程序不再有自己的类加载器。
- Multiple:应用服务器上的每个应用程序使用自己的类加载器。
2 .应用程序配置:Web 模块类加载器策略 应用程序中对 Web 模块类加载器有两种配置: - Application:整个应用程序内的所有的实用程序 jar 包和 Web 模块使用同一个类加载器。
- Module:应用程序内的每个 Web 模块使用自己的类加载器。应用程序的类加载器仍然存在,负责加载应用程序中 Web 模块以外的其它类,包括所有的实用程序 jar 包。
从上面的定义可以看出,不同的类加载器策略的配置下,类加载器的层次结构上的某些类加载器可能不存在。比如在应用程序服务器的应用程序类加载 器策略定义为 single 的情况下,应用程序的类加载器将不存在,同一个应用服务器上的所有应用程序将共用同一个类加载器,这也就意味着不同的应用程序之间的类是共享的,应用程序 间不能存在同名的类。 回页首 在 WebSphere 中解决 jar 包冲突 Jar 包冲突问题实际上就是应用程序希望用某一个确定版本的 jar 包中的类,但是类加载器却找到并加载了另外一个版本的 jar 包中的类。在上一部分介绍了 WebSphere 中类加载器的基本概念和相关配置之后,我们来看如何在 WebSphere 中解决 jar 包冲突。 在 WAS v5 版本之前,使用共享 jar 包的方式是将 jar 包放在 $(WAS_HOME)/lib/app 路径下,从上一部分中,我们可以看到,这个路径正是"WebSphere lib/app Class loader" 类加载器的类查找路径,WebSphere 会查找这个路径以取得相应得 jar 包中的 Java 类,从而做到在 WebSphere ND 上的多个应用程序之间共享 jar 包的目的。但是这样做的一个缺点就是这些共享 jar 包暴露给 WebSphere ND 上所有的应用程序,对于那些希望使用 jar 包其它版本的应用程序,这些 jar 包也同样存在在了它们的类加载器类路径上,因此,就不可避免的会造成版本的冲突。在 WAS v5 版本及之后,增加了共享库(shared library)的概念,推荐的在多个应用程序间共享 jar 包并避免 jar 包冲突的方式是使用共享库。 具体分析引起 jar 包冲突的情况,主要有三种: - 多个应用程序间 jar 包冲突:多个应用程序间由于使用了共享 jar 包的不同版本而造成 jar 包版本冲突。
- 应用程序中多个 Web 模块间 jar 包冲突:同一个应用程序内部,不同的 Web 模块间同时使用一个 jar 包的不同版本而造成 jar 包版本冲突。
- 应用程序中同一个 Web 模块内 jar 包冲突:同一个应用程序内部,同一个 Web 模块内,由于需要同时使用同一个 jar 包的两个版本而造成的 jar 包冲突
本部分根据这三种 jar 包冲突的情况,讨论三种解决 jar 包冲突的办法,并具体讨论三种解决办法的实现步骤和适用情况: - 共享库方式解决 jar 包冲突:主要解决应用程序间的 jar 包冲突问题
- 打包到 Web 模块中解决 jar 包冲突:主要解决应用程序中多个 Web 模块间 jar 包冲突问题
- 命令行运行方式解决 jar 包冲突:主要解决应用程序中同一个 Web 模块内 jar 包冲突问题
共享库方式解决 jar 包冲突 在 WAS v5 中,提供了一种很好的机制,使得 jar 包只存在于需要这个 jar 包的应用程序的类加载器的路径上,而其它的应用程序不受它的任何影响,这就是共享库(Shared library)。共享库可以用在应用服务器级别和应用程序级别,使用应用程序级别的共享库,其好处就是在不同的应用程序之间使用共享 jar 包的不同版本。我们可以为一些通用 jar 包的每个不同版本定义成不同的共享库,应用程序希望使用哪个版本,就把这个版本的共享库放到应用程序的类加载器的类路径上,这种方式有效的解决了应用程序 之间 jar 包冲突的问题。 下面举例介绍定义和使用共享库的具体方法,本例中,假设存在 xerces.jar 包版本冲突。 1 . 定义共享库 系统管理员可以在 WebSphere 的 Admin console 中定义共享库,可以分别在 Cell、Node 以及 server 的级别上定义。 步骤一 : 进入 Admin console,选择 Environment > Shared Library > new。如图 2 所示: 图 2:WebSphere Admin Console 中进入共享库页面 步骤二: 给出共享库的名字,并指定共享的文件和目录。多个不同的文件 / 目录之间通过"Enter"键分隔,且不能有路径分隔符,如":"和";"等。如图 3 所示: 图 3:WebSphere Admin Console 中添加共享库 步骤三:点击 Apply 或者 OK 之后,就添加了一个名字为 Xerces V2.0 的共享库。记住添加完成后一定要在 admin console 保存配置。如图 4 所示: 图 4:WebSphere Admin Console 中共享库列表-
2 .安装应用程序 进入 Admin console,选择 Applications > Install New Application 安装应用程序。请参照 IBM WebSphere 的 Admin console 使用手册进行安装新的应用程序,此处不再详细介绍。 3 .将共享库关联到应用程序 步骤一:进入 Admin console,选择 Applications > Enterprise applications ,并选择需要使用共享库的应用程序。注意:因为要改变应用程序的设置,所以如果应用程序已经运行,需要先停掉应用程序。如图 5 所示: 图 5:WebSphere Admin Console 中选择需要配置的应用程序 步骤二 : 点击应用程序,进入后,选择 Libraries。如图 6 所示: 图 6:WebSphere Admin Console 中选择应用程序库属性步骤三 : 点击 Add,为应用程序添加共享库。如图 7 所示: 图 7:WebSphere Admin Console 中应用程序添加库 步骤四 : 从下拉列表中选择所需要的共享库,点击 OK。如图 8 所示: 图 8:WebSphere Admin Console 中应用程序添加库页面指定所用的共享库 这样,Xerces V2.0 共享库定义的 xerces 版本就存在于了应用程序类加载器的类加载路径上。注意,在添加完成后要保存服务器的设置。 4 .设置应用程序的类加载器的委托模式为 Parent_Last 为了进一步防止共享库定义的 jar 包的其它版本已经存在于 JVM 或者 WebSphere 的类加载器路径上,还需要设置应用程序的类加载器的委托方式为 Parent_Last。 通过上面的配置,即使 xerces 的其它版本已经存在于系统中,应用程序在运行时,其类加载器也会首先查找并加载指定的共享库中的 xerces 版本。这样我们就通过使用共享库的方式,解决了 jar 包版本冲突问题。 打包到 Web 模块中解决 jar 包冲突 共享库的方式,只是在应用程序的层次上,在多个应用程序之间解决了共享 jar 包造成的版本冲突问题,如果一个应用程序的内部,其中一个 Web 模块使用了一个 jar 包的 A 版本,而另一个 Web 模块使用这个 jar 包的 B 版本,在这种情况下造成的 jar 包冲突,共享库的方式是无法解决的,我们可以考虑将其中一个在多个应用程序间共享的 jar 包版本,比如 A 版本,定义成共享库,或者放在"WebSphere lib/app Class loader"类加载器路径上供多个应用程序使用,而将 B 版本的 jar 包打包到使用它的 Web 模块中的方式来解决冲突。 其次,目前很多在线的系统是 WAS v4 的遗留系统,其上运行的应用程序已经使用了"WebSphere lib/app Class loader"类加载器,将 jar 包放在 $(WAS_HOME)/lib/app 目录下进行共享。如果由于其中某个应用程序的升级或者新增加某个应用程序,需要使用某个共享 jar 包的其它版本,在这种情况下,为了减少对系统的影响,也可以考虑将这个共享 jar 包的新版本打包到升级(或新增)的应用程序中的方式来解决 jar 包冲突。 由于 Web 模块的 WebContent/WEB-INFO/lib 目录在应用程序 Web 模块的类加载器查找路径上,因此,我们可以把 jar 包放在这个目录下,Web 模块的类加载器将自动查找并加载这个 jar 包中的类。 步骤一:在 WSAD IE 集成开发环境中,将冲突 jar 包放在 Web 模块的 WebContent/WEB-INFO/lib 目录下。如图 11 所示: 图 11:WSAD IE 中为 Web 模块添加库步骤二:在 Admin console 中,将应用程序部署到 WebSphere server 上之后,进入 Applications > Enterprise Applications, 选择相应的应用程序,并确认应用程序不在运行状态 ( 参见前面章节中选择应用程序的步骤 )。点击进入应用程序,确认应用程序的类加载器的委托模式为 Parent_First, 应用程序的类加载器策略为 Module。如图 12 所示: 图 12:WebSphere Admin Console 中应用程序属性配置页面步骤三:在同一个页面上,选择 Web Modules,点击进入。如图 13 所示: 图 13:WebSphere Admin Console 中选择应用程序 Web 模块属性步骤四:点击相应的包含冲突 jar 包的 Web 模块,设置 Web 模块的类加载器的委托模式为 Parent_Last。注意:在设置完成后要保存服务器配置,并启动应用程序。如图 14 所示: 图 14:WebSphere Admin Console 中为 Web 模块指定类加载器委托模式 将冲突 jar 包打包到 Web 模块中,并设置相应 Web 模块的类加载器的委托模式为 Parent_Last,应用程序在运行过程中加载类的时候,这个 Web 模块的类加载器会首先查找 WebContent/WEB-INFO/lib 目录下的 jar 包进行类的加载;而对于其它的 Web 模块,由于其类加载器的委托模式仍然为缺省的 Parent_First,它们的类加载器仍然首先从应用程序的共享库或者 WebSphere 的共享路径上加载 jar 包中的类,从而解决了 jar 包冲突的问题。 命令行运行方式解决 jar 包冲突 不论是设置共享库,还是将冲突 jar 包打包到应用程序中,其解决的问题都是在应用程序的一个 Web 模块中只使用了冲突 jar 包的一个版本的情况。我们在开发中曾经遇到过这样的情况:应用程序的 Web 模块中已经使用了 1.4 版本的 xerces.jar,由于 Web 功能的扩展,在这个模块中又引入一个新的第三方工具,而这个第三方工具需要使用 2.0 版本的 xerces.jar 才能正常工作,这种情况下的 jar 包冲突如何解决呢? 在前面类加载器的部分已经介绍过,每个应用程序的一个 Web 模块最多只能有一个类加载器,而 Web 模块的类加载器中加载的类的生命周期为整个应用程序的运行期,也就是说,Web 模块加载器不可能同时加载一个类的两个版本,同时,Web 模块的类加载器的委托模式也是在应用程序运行前设置的,在应用程序运行期内无法改变的,因此,上面描述的在一个 Web 模块中同时使用两个版本的 jar 包的问题,象前两种方法那样配置运行在一个 JVM 内的类加载器的设置的方法是无法解决的。 唯一的解决办法就是在应用程序运行的 JVM 外,启动另外一个 JVM 来运行调用冲突 jar 包的代码,因为两个不同的 JVM 可以加载各自的类,从而解决 jar 包冲突问题。 这种情况下,原来使用 jar 包的老版本的方式(包括 jar 包放置路径,共享库设置方式,类加载器的委托模式等)不变,将对 jar 包新版本的调用通过命令行运行方式实现。具体做法是:将对 jar 包新版本内功能的调用,封装到一个可以单独运行的类中,在 Web 模块中以命令行方式运行这个类。同时把这个类以及 jar 包的新版本放在任意一个 was 可访问的路径上(比如 /usr/WebSphere),在命令行的 classpath 参数中包含这个路径(比如 /usr/WebSphere)。 下面通过举例说明命令行运行方式的编程过程,在本例中,假设 TestEar 应用程序的 Web 模块 TestWar 中,已经使用了 conflict_v1.jar,由于新添功能需要使用 conflict_v2.jar 中的 exampleCall() 功能。 冲突 jar 包 conflict_v2.jar 提供的功能: 代码 1:冲突 jar 包 conflict_v2.jar 功能 1 Package com.ibm.conflict; 2 3 Public class ConflictClass{ 4 …… . 5 Public static String exampleCall(string param){ 6 String rs; 7 …… ; 8 Return rs; 9 } 10 …… 不存在冲突问题时的编码举例: 如果没有 jar 包冲突问题,则对这个功能的调用是简单的,只需要将 conflict_v2.jar 放在应用程序自身或者其父类加载器的查找路径上,然后在 Web 模块中直接调用即可,如下: 代码 2:不存在冲突时的调用方式1 Public String methodA(String param){ 2 …… 3 String rs = ConflictClass.exampleCall(param); 4 …… 5 Return rs; 6 }
存在冲突后的命令行运行方式编码举例 针对 jar 包冲突问题,我们需要在 Web 模块中做如下的修改: - 步骤一:将冲突 jar 包放在 was 可访问的路径上,比如 /usr/WebSphere/conflict_v2.jar。
步骤二:将对包含冲突代码的调用封装到一组可单独运行的类中,它们将调用冲突 jar 包的功能,并将结果以系统输出的方式打印到系统标准输出上。 将这些类封装到一个单独的 jar 文件中,比如 workAroundConflict.jar,并将其放在 was 可访问的路径上,比如 /usr/WebSphere/workAroundConflict.jar。
1 Package com.ibm.test; 2 3 Import com.ibm.ConflictClass; 4 5 Public class WorkAround{ 6 Public static void main(String[] args){ 7 String param1=args[0]; 8 String returnStr=ConflictClass.exampleCall (); 9 System.out.println("<RTStr>"+returnStr+"</RTStr>"); 10 Return; 11 } 12 }
代码 3:将对冲突代码的调用写入一个单独的类 WorkAround 步骤三:在 Web 模块中通过命令行方式调用封装的类,通过 classpath 指定所有依赖的 jar 包和类路径。运行封装类,从系统标准输出中取得运行结果。
1 Public static String methodA (String param){ 2 …… 3 String rtStr = ""; 4 String lStr="<RTStr>"; 5 String rStr="<RTStr>"; 6 //put all the dependency jar here 7 String classPath= 8 "/usr/WebSphere/conflict_v2.jar; 9 /usr/WebSphere/workaroundConflict.jar; ……"; 10 String className="com.ibm.test.WorkAround"; 11 String cmdLine="java -classpath " +classPath +" " +className + " "+ param; 12 Try{ 13 Process process = Runtime.getRuntime().exec(cmdLine,null); 14 process.waitFor(); 15 BufferedReader br= new BufferedReader( 16 new InputStreamReader(process.getInputStream())); 17 while ((s = br.readLine())!=null) { 18 if (null == out) 19 out=s; 20 else 21 out+=s; 22 } 23 //get result from out 24 if (null != out){ 25 int lIndex = out.lastIndexOf(lStr); 26 int rIndex = out.lastIndexOf(rStr); 27 rsStr = out.substring(lIndex+lStr.length, rIndex); 28 } 29 30 } catch (Exception e){ 31 e.printStackTrace(); 32 } 33 …… . 34 return rsStr; 35 }
代码 4:在应用程序中通过命令行方式运行 WorkAround -
命令行运行方式通过启动另外一个 JVM 的方式运行冲突代码,在一个不同的 JVM 中加载冲突的类,从而解决了 jar 包冲突问题。 但是命令行运行方式毕竟不是一个很好的方式,它存在以下的弊端: - 命令行运行方式只适用于对冲突 jar 包的使用只是运行一段代码,或者只要求返回简单的字符串结果的情况。对于复杂的交互,命令行方式无法做到。但是如果在别无他法的情况下,可以适当地划分封 装对冲突代码调用的 jar 包的包含范围,尽量将命令行运行的代码接口简单化。
- 命令行运行方式因为启动了另外一个 JVM 来运行,降低了 WebSphere 的性能。
因此,命令行方式只适用于一些极为特殊的情况下解决 jar 包冲突问题。 回页首 结论 本文对基于 WebSphere 的大型项目开发中遇到的 jar 包冲突问题,结合 WebSphere 中类加载器的概念,给出了三种解决办法以及相应的操作步骤和实现代码,并分析了各种方式所适用的具体情况。 项目开发过程中的 jar 包冲突,主要存在以下三种情况: - 多个应用程序间的 jar 包冲突
- 应用程序内多个 Web 模块间的 jar 包冲突
- 应用程序内同一个 Web 模块内部 jar 包冲突
通过对类加载器的分析我们知道,解决 jar 包冲突问题的根本在于合理配置类加载器。在深入理解 WebSphere 中类加载器的层次结构的基础上,我们给出了"共享库解决 jar 包冲突"以及"打包到 Web 模块中解决 jar 包冲突"的办法,通过合理地配置 WebSphere 中类加载器及其委托模式,可以解决大多数的 jar 包冲突问题。但是由于这个层次结构中类加载器只配置到 Web 模块,因此,对于 Web 模块内部的 jar 包冲突问题,类加载器的配置是无法解决的,为此我们给出了"命令行运行方式解决 jar 包冲突"的办法。 表 2 中列出了在不同的 WAS 版本下,三种解决 jar 包冲突问题的办法所适用的 jar 包冲突情况。 表 2:Jar 包冲突解决办法适用的 jar 包冲突情况总结 参考资料 - IBM WebSphere Application Server V5 ClassloaderNamingSpace Guide。
- IBM WebSphere Application Server V5.1 System Management and Configuration。
作者简介 郝爱丽是一位 IBM CSDL 的软件工程师,从事多年 J2EE 开发工作,有丰富的 J2EE 开发经验。目前从事企业电子商务应用的开发和支持。 常红平是一位 IBM CSDL 的软件工程师,IBM certified DB2 DBA 和 IBM certified DB2 developer。他目前正在从事企业电子商务应用的开发。您可以通过 changhp@cn.ibm.com 和他联系。
CXF和Spring结合的非常紧密,默认发布Server端是需要用到Spring的,但是项目中用到的Spring jar包比较老2.0,CXF版本2.3.1,跟Spring不兼容,需要换乘Spring2.5,但是换jar包对原来的项目存在风险,上网搜了一个脱 离Spring运行的方法。 写一个类继承CXFNonSpringServlet,重写loadBus方法。 1 @SuppressWarnings("unchecked") 2 public void loadBus(ServletConfig servletConfig) throws ServletException { 3 super.loadBus(servletConfig); 4 Bus bus = this.getBus(); 5 BusFactory.setDefaultBus(bus); 6 7 Enumeration<String> enums = getInitParameterNames(); 8 while (enums.hasMoreElements()) { 9 String key = enums.nextElement(); 10 String value = getInitParameter(key); 11 try { 12 Class clz = Class.forName(value); 13 try { 14 Endpoint.publish(key, clz.newInstance()); 15 } catch (InstantiationException e) { 16 e.printStackTrace(); 17 } catch (IllegalAccessException e) { 18 e.printStackTrace(); 19 } 20 } catch (ClassNotFoundException e) { 21 e.printStackTrace(); 22 } 23 } 24 } 在web.xml里将要发布的类配置一下,可以配置多个 1 <servlet> 2 <servlet-name>CXFServlet</servlet-name> 3 <servlet-class> 4 com.infodms.ws.util.MyCXFNoSpringServlet 5 </servlet-class> 6 <init-param> 7 <param-name>/TestService</param-name> 8 <param-value>com.infodms.ws.test.TestServiceImpl</param-value> 9 </init-param> 10 <init-param> 11 <param-name>/HelloWorld</param-name> 12 <param-value>com.infodms.ws.test.HelloWorldImpl</param-value> 13 </init-param> 14 </servlet> 15 <servlet-mapping> 16 <servlet-name>CXFServlet</servlet-name> 17 <url-pattern>/ws/*</url-pattern> 18 </servlet-mapping>
配置完成,启动tomcat,报错,还是加载了spring,但是代码确实走了刚刚加的MyCXFNoSpringServlet
1 java.lang.RuntimeException: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.apache.cxf.bus.spring.BusApplicationListener' defined in class path resource [META-INF/cxf/cxf.xml]: Initialization of bean failed; nested exception is java.lang.NoSuchMethodError: org.springframework.context.support.AbstractApplicationContext.addApplicationListener(Lorg/springframework/context/ApplicationListener;)V 2 org.apache.cxf.bus.spring.SpringBusFactory.createBus(SpringBusFactory.java:96)
调试了一下源码,内部需要从环境变量中取得org.apache.cxf.bus.factory的值,如果为空就默认按照spring的方式加载。于是在类的最开始加入一行代码 1 System.setProperty("org.apache.cxf.bus.factory", "org.apache.cxf.bus.CXFBusFactory");
再次启动tomcat,报错,由异常可知WoodstoxValidationImpl类没有默认构造方法,通过反射实例化对象报错,看了一下这个类的源码,确实没有无参构造方法
1 Caused by: java.lang.InstantiationException: org.apache.cxf.wstx_msv_validation.WoodstoxValidationImpl 2 at java.lang.Class.newInstance0(Class.java:340) 3 at java.lang.Class.newInstance(Class.java:308) 4 at org.apache.cxf.bus.extension.Extension.load(Extension.java:110)
原文出自: http://liuqiang5151.iteye.com/blog/840496
摘要: 一、解决基本问题: 在做 RCP 项目的时候经常会遇到一个问题,就是要将一些控制信息输出到 RCP 自身的控制台,那么我们就可以扩展 Eclipse 扩展点 org.eclipse.ui.console.consoleFactories ,来实现我们自己的控制台,解决方法如下: 首先 ,在 plugin.xml 中定义扩展点: plugin.xml: <extension &n... 阅读全文
今天整理系统,发现系统很多页面,只有在IE6下显示正常,其它的都不正常,很是奇怪。所以上网找了一些关于浏览器兼容的问题和解决办法,在此我觉得大牛们总结的比较精彩,分享给网友们! 一、CSS兼容 以下两种方法几乎能解决现今所有兼容. 1, !important (不是很推荐,用下面的一种感觉最安全) 随着IE7对!important的支持, !important 方法现在只针对IE6的兼容.(注意写法.记得该声明位置需要提前.) 代码: <style> #wrapper { width: 100px!important; /* IE7+FF */ width: 80px; /* IE6 */ } </style> 2, IE6/IE77对FireFox <from 针对firefox ie6 ie7的css样式> *+html 与 *html 是IE特有的标签, firefox 暂不支持.而*+html 又为 IE7特有标签. 代码: <style> #wrapper { width: 120px; } /* FireFox */ *html #wrapper { width: 80px;} /* ie6 fixed */ *+html #wrapper { width: 60px;} /* ie7 fixed, 注意顺序 */ </style> 注意: *+html 对IE7的兼容 必须保证HTML顶部有如下声明: 代码: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://w3.org/TR/html4/loose.dtd"> 二、解决DIV错乱(非常重要) 可以用这个解决多个div对齐时的间距不对, 关于 clear float 的原理可参见 [How To Clear Floats Without Structural Markup] 将以下代码加入Global CSS 中,给需要闭合的div加上 class=”clearfix” 即可,屡试不爽. 代码: <style> /* Clear Fix */ .clearfix:after { content:"."; display:block; height:0; clear:both; visibility:hidden; } .clearfix { display:inline-block; } /* Hide from IE Mac \*/ .clearfix {display:block;} /* End hide from IE Mac */ /* end of clearfix */ </style> 三、其它零碎(不容忽视,细节决定成败) 1. 文字本身的大小不兼容。同样是font-size:14px的宋体文字,在不同浏览器下占的空间是不一样的,ie下实际占高16px,下留白3px,ff 下实际占高17px,上留白1px,下留白3px,opera下就更不一样了。解决方案:给文字设定 line-height 。确保所有文字都有默认的 line-height 值。这点很重要,在高度上我们不能容忍1px 的差异。
2.ff下容器高度限定,即容器定义了height之后,容器边框的外形就确定了,不会被内容撑大,而ie下是会被内容撑大,高度限定失效。所以不要轻易给容器定义height。
3.横向上的撑破容器问题,。如果float 容器未定义宽度,ff下内容会尽可能撑开容器宽度,ie下则会优先考虑内容折行。故,内容可能撑破的浮动容器需要定义width。
小实验:有兴趣大家可以看看这段实验。在不同浏览器下分别测试以下各项代码。
a.<div style=”border:1px solid red;height:10px”></div> b. <div style=”border:1px solid red;width:10px”></div>
c. <div style=”border:1px solid red;float:left”></div> d. <div style=”border:1px solid red;overflow:hidden”></div>
上 面的代码在不同浏览器中是不一样的,实验起源于对小height 值div 的运用,<div style=”height:10px;overflow:hidden”></div>,小height 值要配合overflow:hidden一起使用。实验好玩而已,想说明的是,浏览器对容器的边界解释是大不相同的,容器内容的影响结果各不相同。
4.最被痛恨的,double-margin bug。ie6下给浮动容器定义margin-left 或者margin-right 实际效果是数值的2倍。解决方案,给浮动容器定义display:inline。
5.mirror margin bug,当外层元素内有float元素时,外层元素如定义margin-top:14px,将自动生成margin-bottom:14px。 padding也会出现类似问题,都是ie6下的特产,该类bug 出现的情况较为复杂,远不只这一种出现条件,还没系统整理。解决方案:外层元素设定border 或 设定float。
引申:ff 和ie 下对容器的margin-bottom,padding-bottom的解释有时不一致,似乎与之相关。
6. 吞吃现象。还是ie6,上下两个div,上面的div设置背景,却发现下面没有设置背景的div 也有了背景,这就是吞吃现象。对应上面的背景吞吃现象,还有滚动下边框缺失的现象。解决方案:使用zoom:1。这个zoom好象是专门为解决ie6 bug而生的。
7.注释也能产生bug~~~“多出来的一只猪。”这是前人总结这个bug使用的文案,ie6的这个bug 下,大家会在页面看到猪字出现两遍,重复的内容量因注释的多少而变。解决方案:用“<!–[if !IE]> picRotate start <![endif]–>”方法写注释。
8.img 下的留白,大家看这段代码有啥问题:
<div> < img src=”" mce_src=”" /> < /div>
把div的border打开,你发现图片底部不是紧贴着容器底部的,是img后面的空白字符造成,要消除必须这样写
<div> <img src=”" mce_src=”" /></div>
后面两个标签要紧挨着。ie7下这个bug 依然存在。解决方案:给img设定 display:block。
9. 失去line-height。<div style=”line-height:20px”><img />文字</div>,很遗憾,在ie6下单行文字 line-height 效果消失了。。。,原因是<img />这个inline-block元素和inline元素写在一起了。解决方案:让img 和文字都 float起来。
引申:大家知道img 的align 有 text-top,middle,absmiddle啊什么的,你可以尝试去调整img 和文字让他们在ie和ff下能一致,你会发现怎么调都不会让你满意。索性让img 和文字都 float起来,用margin 调整。
10.clear层应该单独使用。也许你为了节省代码把clear属性直接放到下面的一个内容层,这样有问题,不仅仅是ff和op下失去margin效果,ie下某些margin值也会失效 <div style=”background:red;float:left;”>dd</div> < div style=”clear:both;margin-top:18px;background:green”>ff</div>
11.ie 下overflow:hidden对其下的绝对层position:absolute或者相对层 position:relative无效。解决方案:给overflow:hidden加position:relative或者position: absolute。另,ie6支持overflow-x或者overflow-y的特性,ie7、ff不支持。
12.ie6下严重的bug,float元素如没定义宽度,内部如有div定义了height或zoom:1,这个div就会占满一整行,即使你给了宽度。float元素如果作为布局用或复杂的容器,都要给个宽度的。
13.ie6下的bug,绝对定位的div下包含相对定位的div,如果给内层相对定位的div高度height具体值,内层相对层将具有100%的width值,外层绝对层将被撑大。解决方案给内层相对层float属性。
14.width:100%这个东西在ie里用很方便,会向上逐层搜索width值,忽视浮动层的影响,ff下搜索至浮动层结束,如此,只能给中间的所有浮动层加width:100%才行,累啊。opera这点倒学乖了跟了ie。 四、一句话解决所有兼容性问题(懒人有懒人的办法奥!) 在网站头部加上一句: 1. Google Chrome Frame也可以让IE用上Chrome的引擎: <meta http-equiv=“X-UA-Compatible” content=“chrome=1″/> 2.强制IE8使用IE7模式来解析 <meta http-equiv=“X-UA-Compatible”content=“IE=EmulateIE7″><!– IE7 mode –> //或者 <metahttp-equiv=“X-UA-Compatible”content=“IE=7″><!– IE7 mode –> 3.强制IE8使用IE6或IE5模式来解析 <metahttp-equiv=“X-UA-Compatible”content=“IE=6″><!– IE6 mode –> <metahttp-equiv=“X-UA-Compatible”content=“IE=5″><!– IE5 mode –> 4.如果一个特定版本的IE支持所要求的兼容性模式多于一种,如: <metahttp-equiv=“X-UA-Compatible”content=“IE=5; IE=8″/> 我用的是最后一种,不过还是没有彻底解决问题,我也在寻求方法,恳求大牛们帮忙解决! 原文出自: http://www.cnblogs.com/sybboy/archive/2013/01/19/2868153.html
1. Javascript没有类的概念。一般使用原型链继承(prototypal inheritance)来模拟类。
2. 除了null和undefined之外的任何数据类型都能表现成Object (behave like an object),包括Number类型和Function类型。 var n = 42; function f() { alert("foo"); };
alert("n is " + n.toString()); // "n is 42" alert(f.name + " is a function"); // "f is a function"
注意,是“表现为object”,而不是“是object”。事实上,number, string和boolean是基本类型(primitives),除了这三个之外的都可以算作object,比如一个正则表达式也是一个object。 当需要访问基本类型变量的属性时,那些基本类型变量将被临时转换成object。 例如: "foobar".big(); // is equivalent to new String("foobar").big(); .14.toFixed(); // is equivalent to new Number(3.14).toFixed() 另外,不能强行给基本类型变量(number, string, boolean)加上私有属性。
var a = "mystring", b = new String( "mystring" );
Object.defineProperty( b, 'foo', { value: 42, enumerable: false }); console.log(b.foo); // 42 Object.defineProperty( a, 'foo', { value: 42, enumerable: false }); // TypeError: Object.defineProperty called on non-object
// trying another way: a.foo = 42; // remember, this is equivalent to: // new Number(a).foo = 42; // …so the 'foo' property is defined on the wrapper, not on 'a' console.log(a.foo); // undefined 3. 如果变量名前不加上var,那么这个变量就是全局的。
function setGlobal() { a = 42; }
function setLocal() { var b = 23; }
setGlobal(); alert(a); // 42
setLocal(); alert(b); // ReferenceError: b is not defined
4. this指针是由调用函数赋予的, 而不是由被调函数自身定义的。 例如: var a = {}, b = {};
a.foo = 42; b.foo = 18; a.alertFoo = function() { alert(this.foo); };
a.alertFoo(); // 42 a.alertFoo.call(b); // 18
5. 严格的相等判断应该使用===。例如 : 0 == false 为真, 但是 0 === false 为假。
6. 0, undefined, null, "", NaN 都是假值 (falsy)。 这些值全都等同于false,但他们之间不能相互替换。 7. 变量声明会被提升到当前作用域的顶端。 例如:下述代码中,你认为调用foo函数会返回什么?
var a = 2;
function foo() { return a; var a = 5; }
它将返回undefined。 上述代码等同于下述代码: var a = 2;
function foo() { var a; // 'a' declaration is moved to top return a; a = 5; } 另一个例子: 下述代码中,调用函数foo后变量a的值是什么? var a = 42;
function foo() { a = 12; return a; function a(){} } 答案是42。因为foo函数中变相声明了a变量,因此a成了局部变量了。 function foo() { function a() {} // local 'a' is a function a = 12; // local 'a' is now a number (12) return a; // return the local 'a' (12) } 8. 参数在函数声明中可以省略, 例如: function hello(name, age) { alert("Hello "+name+", you’re "+age+" years old!"); }
hello("Anon", 42); // "hello Anon, you’re 42 years old!" hello("Baptiste"); // "hello Baptiste, you’re undefined years old!" hello("Bulat", 24, 42); // "hello Bulat, you’re 24 years old!" 9. 对于字符串,使用双引号""和单引号''的效果是一样的。
10. Javascript代码可以在浏览器环境之外运行, 比如在Terminal或者HTTP服务器上。(例如 Node.js)
原文出自:http://www.cnblogs.com/newyorker/archive/2013/01/18/2865820.html
本篇介绍可以在C#中使用的1D/2D编码解码器。条形码的应用已经非常普遍,几乎所有超市里面的商品上面都印有条形码;二维码也开始应用到很多场合,如火车票有二维码识别、网易的首页有二维码图标,用户只需要用手机扫描一下就可以看到手机版网易的网址,免去了输入长串字符的麻烦。 条形码的标准: 条形码的标准有ENA条形码、UPC条形码、二五条形码、交叉二五条形码、库德巴条形码、三九条形码和128条形码等,而商品上最常使用的就是EAN商品条形码。EAN商品条形码亦称通用商品条形码,由国际物品编码协会制定,通用于世界各地,是目前国际上使用最广泛的一种商品条形码。我国目前在国内推行使用的也是这种商品条形码。EAN商品条形码分为EAN-13(标准版)和EAN-8(缩短版)两种。 二维码的编码标准: 全球现有的二维码多达200种以上,其中常见的技术标准有PDF417(美系标准),QRCode(日系标准),Code49,Code16K,CodeOne,DM(韩系标准),GM(中国标准),CM(中国标准)等20余种。用得最多的是QRcode。 下面借助google的开源项目zxing来实现1D/2D的编码和解码,测试效果如下: zxing的官方地址是:http://code.google.com/p/zxing/ zxing的功能还是很强大的,最初是用java编写,并支持Android、ios、symbian等手机操作系统。 不过不知是何原因,该官网连一个例子也没有,文档也是字典式的把所有类列出来,一点都没为读者考虑。 下面我把如果使用zxing完成上图所示例子讲解一遍,供初学者参考: 1.我们新建一个Winform测试项目; 2.从官网下载zxing开源项目,大概16m的样子,解压缩后打开zxing-2.1\csharp目录,将该目录拷贝到我们新建的Winform项目下(方便调试和看源码,并非一定要如此); 3.winform项目中添加对zxing项目的引用; 4.按上图所示例子建好控件,“生成条形码”的代码如下:
1 //生成条形码 2 private void button1_Click(object sender, EventArgs e) 3 { 4 lbshow.Text = ""; 5 Regex rg = new Regex("^[0-9]{13}$"); 6 if (!rg.IsMatch(txtMsg.Text)) 7 { 8 MessageBox.Show("本例子采用EAN_13编码,需要输入13位数字"); 9 return; 10 } 11 12 try 13 { 14 MultiFormatWriter mutiWriter = new com.google.zxing.MultiFormatWriter(); 15 ByteMatrix bm = mutiWriter.encode(txtMsg.Text, com.google.zxing.BarcodeFormat.EAN_13, 363, 150); 16 Bitmap img= bm.ToBitmap(); 17 pictureBox1.Image =img; 18 19 //自动保存图片到当前目录 20 string filename = System.Environment.CurrentDirectory + "\\EAN_13" + DateTime.Now.Ticks.ToString() + ".jpg"; 21 img.Save(filename, System.Drawing.Imaging.ImageFormat.Jpeg); 22 lbshow.Text = "图片已保存到:" + filename; 23 } 24 catch(Exception ee) 25 { MessageBox.Show(ee.Message); } 26 } 其中需要注意BarcodeFormat参数,可以打开定义看到具体的编码方式,自己百度每种编码方式对输入的要求。 这里EAN_13编码要求是13位长度的数字,并且满足:把所有偶数序号位上的数相加求和,用求出的和乘3,再把所有奇数序号上的数相加求和,用求出的和加上刚才偶数序号上的数,然后得出和能被10整除。(这个规则校验在UPCEANReader类的checkStandardUPCEANChecksum方法里面,如果不需要,可以去掉) 生成二维码的代码与上面相似:
1 //生成二维码 2 private void button2_Click(object sender, EventArgs e) 3 { 4 lbshow.Text = ""; 5 try 6 { 7 MultiFormatWriter mutiWriter = new com.google.zxing.MultiFormatWriter(); 8 ByteMatrix bm = mutiWriter.encode(txtMsg.Text, com.google.zxing.BarcodeFormat.QR_CODE, 300, 300); 9 Bitmap img = bm.ToBitmap(); 10 pictureBox1.Image = img; 11 12 //自动保存图片到当前目录 13 string filename = System.Environment.CurrentDirectory + "\\QR" + DateTime.Now.Ticks.ToString() + ".jpg"; 14 img.Save(filename, System.Drawing.Imaging.ImageFormat.Jpeg); 15 lbshow.Text = "图片已保存到:" + filename; 16 } 17 catch (Exception ee) 18 { MessageBox.Show(ee.Message); } 19 } 注意编码问题,在com.google.zxing.qrcode.encoder.Encoder类中修改默认编码为utf-8,否则解码出现的是乱码。 System.String DEFAULT_BYTE_MODE_ENCODING = "UTF-8"; 此处之前是"ISO-8859-1",之所以改成UTF-8是因为,在解码的时候程序会猜测可能的编码,如果猜测失败则默认是UTF-8,代码在com.google.zxing.qrcode.decoder.DecodedBitStreamParser类的guessEncoding方法中。 所以此开源项目也缺少全局性思考,连编码和解码的默认编码方式都不一致。 4.实现图片解码,即把条形码或二维码图片解码成其真实内容,当然在pc上应用不大,但可能只是还没发现而已,代码如下: 1 //解码操作 2 private void button3_Click(object sender, EventArgs e) 3 { 4 MultiFormatReader mutiReader = new com.google.zxing.MultiFormatReader(); 5 Bitmap img = (Bitmap)Bitmap.FromFile(opFilePath); 6 if (img == null) 7 return; 8 LuminanceSource ls = new RGBLuminanceSource(img, img.Width, img.Height); 9 BinaryBitmap bb = new BinaryBitmap(new com.google.zxing.common.HybridBinarizer(ls)); 10 11 Result r= mutiReader.decode(bb); 12 txtMsg.Text = r.Text; 13 } opFilePath是图片路径,你可以用openFileDialog控件打开文件来得到路径。 原文出自: http://www.cnblogs.com/tuyile006/archive/2013/01/16/2863367.html
很多人都知道SQL注入,也知道SQL参数化查询可以防止SQL注入,可为什么能防止注入却并不是很多人都知道的。 本文主要讲述的是这个问题,也许你在部分文章中看到过这块内容,当然了看看也无妨。 首先:我们要了解SQL收到一个指令后所做的事情: 具体细节可以查看文章:Sql Server 编译、重编译与执行计划重用原理 在这里,我简单的表示为: 收到指令 -> 编译SQL生成执行计划 ->选择执行计划 ->执行执行计划。 具体可能有点不一样,但大致的步骤如上所示。 接着我们来分析为什么拼接SQL 字符串会导致SQL注入的风险呢? 首先创建一张表Users: CREATE TABLE [dbo].[Users]( [Id] [uniqueidentifier] NOT NULL, [UserId] [int] NOT NULL, [UserName] [varchar](50) NULL, [Password] [varchar](50) NOT NULL, CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] 插入一些数据: INSERT INTO [Test].[dbo].[Users]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),1,'name1','pwd1'); INSERT INTO [Test].[dbo].[Users]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),2,'name2','pwd2'); INSERT INTO [Test].[dbo].[Users]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),3,'name3','pwd3'); INSERT INTO [Test].[dbo].[Users]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),4,'name4','pwd4'); INSERT INTO [Test].[dbo].[Users]([Id],[UserId],[UserName],[Password])VALUES (NEWID(),5,'name5','pwd5'); 假设我们有个用户登录的页面,代码如下: 验证用户登录的sql 如下: select COUNT(*) from Users where Password = 'a' and UserName = 'b' 这段代码返回Password 和UserName都匹配的用户数量,如果大于1的话,那么就代表用户存在。 本文不讨论SQL 中的密码策略,也不讨论代码规范,主要是讲为什么能够防止SQL注入,请一些同学不要纠结与某些代码,或者和SQL注入无关的主题。 可以看到执行结果: 这个是SQL profile 跟踪的SQL 语句。 注入的代码如下: select COUNT(*) from Users where Password = 'a' and UserName = 'b' or 1=1—' 这里有人将UserName设置为了 “b' or 1=1 –”. 实际执行的SQL就变成了如下: 可以很明显的看到SQL注入成功了。 很多人都知道参数化查询可以避免上面出现的注入问题,比如下面的代码: class Program { private static string connectionString = "Data Source=.;Initial Catalog=Test;Integrated Security=True"; static void Main(string[] args) { Login("b", "a"); Login("b' or 1=1--", "a"); } private static void Login(string userName, string password) { using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); SqlCommand comm = new SqlCommand(); comm.Connection = conn; //为每一条数据添加一个参数 comm.CommandText = "select COUNT(*) from Users where Password = @Password and UserName = @UserName"; comm.Parameters.AddRange( new SqlParameter[]{ new SqlParameter("@Password", SqlDbType.VarChar) { Value = password}, new SqlParameter("@UserName", SqlDbType.VarChar) { Value = userName}, }); comm.ExecuteNonQuery(); } } } 实际执行的SQL 如下所示: exec sp_executesql N'select COUNT(*) from Users where Password = @Password and UserName = @UserName',N'@Password varchar(1),@UserName varchar(1)',@Password='a',@UserName='b' exec sp_executesql N'select COUNT(*) from Users where Password = @Password and UserName = @UserName',N'@Password varchar(1),@UserName varchar(11)',@Password='a',@UserName='b'' or 1=1—' 可以看到参数化查询主要做了这些事情: 1:参数过滤,可以看到 @UserName='b'' or 1=1—' 2:执行计划重用 因为执行计划被重用,所以可以防止SQL注入。 首先分析SQL注入的本质, 用户写了一段SQL 用来表示查找密码是a的,用户名是b的所有用户的数量。 通过注入SQL,这段SQL现在表示的含义是查找(密码是a的,并且用户名是b的,) 或者1=1 的所有用户的数量。 可以看到SQL的语意发生了改变,为什么发生了改变呢?,因为没有重用以前的执行计划,因为对注入后的SQL语句重新进行了编译,因为重新执行了语法解析。所以要保证SQL语义不变,即我想要表达SQL就是我想表达的意思,不是别的注入后的意思,就应该重用执行计划。 如果不能够重用执行计划,那么就有SQL注入的风险,因为SQL的语意有可能会变化,所表达的查询就可能变化。 在SQL Server 中查询执行计划可以使用下面的脚本: DBCC FreeProccache select total_elapsed_time / execution_count 平均时间,total_logical_reads/execution_count 逻辑读, usecounts 重用次数,SUBSTRING(d.text, (statement_start_offset/2) + 1, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(text) ELSE statement_end_offset END - statement_start_offset)/2) + 1) 语句执行 from sys.dm_exec_cached_plans a cross apply sys.dm_exec_query_plan(a.plan_handle) c ,sys.dm_exec_query_stats b cross apply sys.dm_exec_sql_text(b.sql_handle) d --where a.plan_handle=b.plan_handle and total_logical_reads/execution_count>4000 ORDER BY total_elapsed_time / execution_count DESC; 博客园有篇文章: Sql Server参数化查询之where in和like实现详解 在这篇文章中有这么一段: 这里作者有一句话:”不过这种写法和直接拼SQL执行没啥实质性的区别” 任何拼接SQL的方式都有SQL注入的风险,所以如果没有实质性的区别的话,那么使用exec 动态执行SQL是不能防止SQL注入的。 比如下面的代码: private static void TestMethod() { using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); SqlCommand comm = new SqlCommand(); comm.Connection = conn; //使用exec动态执行SQL //实际执行的查询计划为(@UserID varchar(max))select * from Users(nolock) where UserID in (1,2,3,4) //不是预期的(@UserID varchar(max))exec('select * from Users(nolock) where UserID in ('+@UserID+')') comm.CommandText = "exec('select * from Users(nolock) where UserID in ('+@UserID+')')"; comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.VarChar, -1) { Value = "1,2,3,4" }); //comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.VarChar, -1) { Value = "1,2,3,4); delete from Users;--" }); comm.ExecuteNonQuery(); } } 执行的SQL 如下: exec sp_executesql N'exec(''select * from Users(nolock) where UserID in (''+@UserID+'')'')',N'@UserID varchar(max) ',@UserID='1,2,3,4' 可以看到SQL语句并没有参数化查询。 如果你将UserID设置为” 1,2,3,4); delete from Users;—- ”,那么执行的SQL就是下面这样: exec sp_executesql N'exec(''select * from Users(nolock) where UserID in (''+@UserID+'')'')',N'@UserID varchar(max) ',@UserID='1,2,3,4); delete from Users;--' 不要以为加了个@UserID 就代表能够防止SQL注入,实际执行的SQL 如下: 任何动态的执行SQL 都有注入的风险,因为动态意味着不重用执行计划,而如果不重用执行计划的话,那么就基本上无法保证你写的SQL所表示的意思就是你要表达的意思。 这就好像小时候的填空题,查找密码是(____) 并且用户名是(____)的用户。 不管你填的是什么值,我所表达的就是这个意思。 最后再总结一句:因为参数化查询可以重用执行计划,并且如果重用执行计划的话,SQL所要表达的语义就不会变化,所以就可以防止SQL注入,如果不能重用执行计划,就有可能出现SQL注入, 存储过程也是一样的道理,因为可以重用执行计划。 原文出自: http://www.cnblogs.com/LoveJenny/archive/2013/01/15/2860553.html
背景 : 在使用搜索引擎和电商的搜索功能时,大家一定遇到过这样的情景:我想搜索电影“十二生肖”,可不小心输成“十二生效”了,不用担心搜不到你想要的结果,因为建立在大数据上的搜索引擎会帮你自动纠错,就这个例子Google和Baidu返回给我的分别是: 显示以下查询字词的结果: 十二生肖 和 您要找的是不是: 十二生肖 ,他们都做到了自动纠错,关于自动纠错我之前也写过一篇陋文,当时是自己实现的N-Gram模型,但是效果不是太好,主要是针对不同的语料库算法的精确度是不一样的,我想换个算法试试看,目前主流的计算串间的距离(相反的,你也可以理解为相似度)是Levenshtein,当要实现时,发现lucene已经做了这个事,那咱就站在巨人的肩膀上成长吧。 引用包: lucene-core-3.1.0.jar + lucene-spellchecker-3.1.0.jar,你可以在这里得到 使用示例: 在类SpellCorrector的main方法中加入以下代码 1 //创建目录 2 File dict = new File(""); 3 Directory directory = FSDirectory.open(dict); 4 5 //实例化拼写检查器 6 SpellChecker sp = new SpellChecker(directory); 7 8 9 //创建词典 10 File dictionary = new File(SpellCorrecter.class.getResource("dictionary.txt").getFile()); 11 12 //对词典进行索引 13 sp.indexDictionary(new PlainTextDictionary(dictionary)); 14 15 16 //有错别字的搜索 17 String search = "非常勿扰"; 18 19 20 //建议个数,这里我只想要最接近的那一个,你可以设置成别的数字,如3 21 int suggestionNumber = 1; 22 23 24 //获取建议的关键字 25 String[] suggestions = sp.suggestSimilar(search, suggestionNumber); 26 27 28 //显示结果 29 System.out.println("搜索:" + search); 30 31 32 for (String word : suggestions) { 33 System.out.println("你要找的是不是:" + word); 34 } 注:这之前你需要有个语料库,我这里是个存放正确视频名称的文件,格式如下:
红颜血泪 冰上火一般的激情 在敌之手 驰风竞艇王第二部 钓金龟 潇湘路一号 戏里戏外第二季 草原狼爵士乐 拯救大兵瑞恩 好了,接下来就直接运行吧,例如我搜索“十二生效”,则提示说是不是要找“十二生肖” 原文出自: http://www.cnblogs.com/wuren/archive/2013/01/16/2862873.html
严重: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'ResourceService': Invocation of init method failed; nested exception is javax.xml.ws.WebServiceException: java.lang.IllegalArgumentException: An operation with name [####] already exists in this service 可能是因为WebService 存在重载的方法,因此初始化时Spring无法找到对应的方法 使用的插件为: javax.jws-1.0.0_JDK1.5_1.0.0.jar cxf-bundle-2.5.2.jar spring-webmvc-3.1.1.RELEASE.jar
一般我们的前台代码Ext.grid.ColumnModel里会这样写,以便显示日期格式: ////////////////////////////////////////////////////////////// Js代码 .... { header:"birthday", dataIndex:"birthday" .... renderer:new Ext.util.Format.dateRenderer("Y-m-d"), .... } .... ////////////////////////////////////////////////////////////// 如果你前台这样写的话,那恭喜你,你的显示日期那列将不再正确显示时间, 而是显示为"NaN-NaN-NaN",是吧? 呵呵,问我怎么知道的?因为最开始我也是这么错的。 为什么会是错的呢? 让我们来看看Ext.format.dateRenderer的源代码(开源的东西就是好), 它的源代码是这样的: Js代码 ////////////////////////////////////////////////////////////// dateRenderer : function(format) { return function(v) { return Ext.util.Format.date(v, format);、 } } ////////////////////////////////////////////////////////////// 可以看出,我们传会来的值,被当做日期又被格式化了一次,我们传回来的是日期吗? 以前是,经过昨天后台代码的修改,我们传回来的仅仅是个字符串了,至于为什么要这么改, 请看昨天写的“Extjs日期格式问题(一) ”,那咋办?有的朋友应该已经想到了,既然是字符串, 那就直接显示呗,不用renderer了,于是前台代码Ext.grid.ColumnModel里就变成了: Js代码 ////////////////////////////////////////////////////////////// .... {header:"birthday",dataIndex:"birthday".......), .... ////////////////////////////////////////////////////////////// 可以负责任的告诉你,这样写,绝对可以正确显示了,这样是不是感觉更简单了呢? 但是,基于我项目里的要求,这个问题并没有解决完,因为在这里不是一个简单的gridpanel, 而是一个editgridpanel,所以还得定义一个editor,于是有了下面这样一段代码: Js代码 ////////////////////////////////////////////////////////////// .... {header:"birthday",dataIndex:"birthday"......., editor:new Ext.grid.GridEditor(new Ext.form.DateField({format:"Y-m-d"})), ..... ////////////////////////////////////////////////////////////// 加了这个DateField控件后,每次可以正常的选择日期,但是选择完日期后, grid里显示的格式就又不正确了,这次显示的内容成了: "Wed Mar 04 1970......", 这样的格式一看就是个日期,这样显示的原因当然是因为我们没有写renderer进行格式化处理的缘故。。。 说到这里,有人应该已经想到解决办法了,另外有些人可能就抓狂了,这renderer加了不能正常显示, 不加也不能正常显示,这很矛盾啊。。 问题往往到了最矛盾的时候,也是到了解决的时候,现在我把解决代码贴出来,大家一看就明白了。 多的不说,看代码: Js代码 ////////////////////////////////////////////////////////////// renderer:function(value){ if(value instanceof Date){ return new Date(value).format("Y-m-d"); }else{ return value; } } ////////////////////////////////////////////////////////////// 简单吧?自己写renderer就是了。 原文出自:http://ks2144634.blog.163.com/blog/static/133585503201081544950656/
错误”ora-04031 无法分配XXX字节的共享内存(XXX)”的解决办法: oracle 9i: sys用户以sysdba身份登录 先查看当前shared_pool_size值 sql>show parameter shared_pool_size; 然后 sql>alter system set shared_pool_size=’比原先值适当增加’ scope=spfile; 然后 sql>shutdown immediate sql>startup oracle 10g: oracle 10g shared_pool_size默认值为0,也就是系统自动管理shared pool内存,这时可以适当增加shared_pool_reserved_size的值,仍然让系统自动管理这部分内存 sql>alter system set shared_pool_reserved_size=’比原先值适当增加’ scope=spfile; sql>shutdown immediate sql>startup 原文出自:http://openwares.net/database/cant_alloc_share_memory.html
对依赖注入( http://www.oschina.net/code/snippet_871522_15682) 的进一步加强. 作为基本功能的注册,使用语法是:registration.register<Interface,Implementation>(). 可以到了实际的项目中,要注册的接口和类可能不止一个两个,会成百上千。无论是用C#代码来注册,还是用XML文件来配置,都是开发者的噩梦。 这里,让我们来个思维的反转: 变集中配置为分散声明。 1 为要注册的类,加入RegisterInContainer的声明 2 在系统启动的时候,扫描所有的类,对有RegisterInContainer的类,找到它所有实现的接口,把这些接口和该类绑定注册。 用于装饰要注入类的Attribute. 1 using System; 2 using System.Linq; 3 using Skight.eLiteWeb.Domain.BasicExtensions; 4 5 namespace Skight.eLiteWeb.Domain.Containers 6 { 7 [AttributeUsage(AttributeTargets.Class)] 8 public class RegisterInContainerAttribute : Attribute 9 { 10 public RegisterInContainerAttribute(LifeCycle life_cycle) { 11 this.life_cycle = life_cycle; 12 } 13 14 public LifeCycle life_cycle { get; set; } 15 public Type type_to_register_in_container { get; set; } 16 17 public void register_using(Registration registration) { 18 DiscreteItemResolver resolver = new TypeResolver(type_to_register_in_container); 19 if (life_cycle == LifeCycle.singleton) resolver = new SingletonResolver(resolver); 20 registration.register(resolver,type_to_register_in_container.all_interface().ToArray()); 21 } 22 } 23 } 注入声明Attribute参数,指定是否单件方式生成注册类。1 namespace Skight.eLiteWeb.Domain.Containers 2 { 3 public enum LifeCycle 4 { 5 single_call, 6 singleton 7 } 8 } 注册声明Attribute的使用示例 1 //调用Container.get_an<FrontController>()将会得到FrontControllerImpl的对象 2 3 using Skight.eLiteWeb.Domain.Containers; 4 5 namespace Skight.eLiteWeb.Presentation.Web.FrontControllers 6 { 7 [RegisterInContainer(LifeCycle.single_call)] 8 public class FrontControllerImpl : FrontControllers.FrontController 9 { 10 private CommandResolver command_resolver; 11 12 public FrontControllerImpl(CommandResolver commandResolver) 13 { 14 command_resolver = commandResolver; 15 } 16 17 public void process(WebRequest request) 18 { 19 command_resolver.get_command_to_process(request).process(request); 20 } 21 } 22 } Registration添加一个方法:反过来,为一个解析器(实现类)注册多个接口。1 void register(DiscreteItemResolver resolver, params Type[] contracts); 2 扫描注册机,在系统启动时调用一次。 1 using System.Collections.Generic; 2 using System.Reflection; 3 using Skight.eLiteWeb.Domain.BasicExtensions; 4 using Skight.eLiteWeb.Domain.Containers; 5 6 namespace Skight.eLiteWeb.Application.Startup 7 { 8 public class RegistrationScanner:StartupCommand 9 { 10 private readonly Registration registration; 11 private readonly IEnumerable<Assembly> assemblies; 12 13 public RegistrationScanner(Registration registration, params Assembly[] assemblies) 14 { 15 this.registration = registration; 16 this.assemblies = assemblies; 17 } 18 19 public void run() 20 { 21 assemblies 22 .each(assembly => 23 assembly.GetTypes() 24 .each(type => 25 type.run_againste_attribute<RegisterInContainerAttribute>( 26 attribute => 27 { 28 attribute.type_to_register_in_container = type; 29 attribute.register_using(registration); 30 }))); 31 } 32 } 33 } 系统启动:创建注册类,然后传入必要的Assembly,调用注册机注册所有加了Attribute的类。 1 using System; 2 using System.Collections.Generic; 3 using System.Reflection; 4 using Skight.eLiteWeb.Domain.Containers; 5 using Skight.eLiteWeb.Presentation.Web.FrontControllers; 6 7 namespace Skight.eLiteWeb.Application.Startup 8 { 9 public class ApplicationStartup 10 { 11 public void run() 12 { 13 var registration = create_registration(); 14 new RegistrationScanner(registration, 15 Assembly.GetAssembly(typeof (Container)), 16 Assembly.GetAssembly(typeof (FrontController)), 17 Assembly.GetAssembly(typeof (StartupCommand))) 18 .run(); 19 20 } 21 22 private Registration create_registration() 23 { 24 IDictionary<Type, DiscreteItemResolver> item_resolvers = new Dictionary<Type, DiscreteItemResolver>(); 25 Container.initialize_with(new ResolverImpl(item_resolvers)); 26 return new RegistrationImpl(item_resolvers); 27 } 28 } 29 } Global.asax系统入口,把所有的代码串起来。 1 <%@ Application Language="C#" %> 2 <%@ Import Namespace="Skight.eLiteWeb.Application.Startup" %> 3 <script RunAt="server"> 4 5 private void Application_Start(object sender, EventArgs e) 6 { 7 new ApplicationStartup().run(); 8 } 9 10 </script> 原文出自: http://www.oschina.net/code/snippet_871522_16735
摘要: http://www.umlchina.com/Tools/Newindex1.htmUML相关工具一览(截止2012年10月) 整理 本文的PDF版本在此下载>> 以下总结了全世界的各种UML相关工具,按工具名称字母排序。 工具(最新版本) 厂商&地址 版权 ... 阅读全文
1.活动图、用例图、类图 http://yuml.me/diagram/scruffy/class/draw 2.序列图 http://www.websequencediagrams.com/ 3.序列图、协作图 http://www.diagrammr.com/
摘要: Container注入框架的进入点Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 namespace Skight.LightWeb.Domain 2 { 3 &n... 阅读全文
在这里我们总结了Oracle临时表的集中用法,临时表创建之后基本不占用表空间,如果你没有指定临时表存放的表空的时候,你插入到临时表的数据是存在系统的临时表空间中。 Oracle临时表可以说是提高数据库处理性能的好方法,在没有必要存储时,只存储在Oracle临时表空间中。希望本文能对大家有所帮助。 1 、前言 目前所有使用 Oracle 作为数据库支撑平台的应用,大部分数据量比较庞大的系统,即表的数据量一般情况下都是在百万级以上的数据量。 当然在 Oracle 中创建分区是一种不错的选择,但是当你发现你的应用有多张表关联的时候,并且这些表大部分都是比较庞大,而你关联的时候发现其中的某一张或者某几张表关联 之后得到的结果集非常小并且查询得到这个结果集的速度非常快,那么这个时候我考虑在 Oracle 中创建“临时表”。 我对临时表的理解:在 Oracle 中创建一张表,这个表不用于其他的什么功能,主要用于自己的软件系统一些特有功能才用的,而当你用完之后表中的数据就没用了。 Oracle 的临时表创建之后基本不占用表空间,如果你没有指定临时表(包括临时表的索引)存放的表空的时候,你插入到临时表的数据是存放在 ORACLE 系统的临时表空间中( TEMP )。 2 、临时表的创建 创建Oracle 临时表,可以有两种类型的临时表: 会话级的临时表 事务级的临时表 。 1) 会话级的临时表因为这这个临时表中的数据和你的当前会话有关系,当你当前SESSION 不退出的情况下,临时表中的数据就还存在,而当你退出当前SESSION 的时候,临时表中的数据就全部没有了,当然这个时候你如果以另外一个SESSION 登陆的时候是看不到另外一个SESSION 中插入到临时表中的数据的。即两个不同的SESSION 所插入的数据是互不相干的。当某一个SESSION 退出之后临时表中的数据就被截断(truncate table ,即数据清空)了。会话级的临时表创建方法: 1 Create Global Temporary Table Table_Name 2 (Col1 Type1,Col2 Type2) On Commit Preserve Rows ;
举例: 1 create global temporary table Student 2 (Stu_id Number(5), 3 Class_id Number(5), 4 Stu_Name Varchar2(8), 5 Stu_Memo varchar2(200)) on Commit Preserve Rows ;
2) 事务级临时表是指该临时表与事务相关,当进行事务提交或者事务回滚的时候,临时表中的数据将自行被截断,其他的内容和会话级的临时表的一致(包括退出SESSION 的时候,事务级的临时表也会被自动截断)。事务级临时表的创建方法: 1 Create Global Temporary Table Table_Name 2 (Col1 Type1,Col2 Type2) On Commit Delete Rows ;
举例: 1 create global temporary table Classes 2 (Class_id Number(5), 3 Class_Name Varchar2(8), 4 Class_Memo varchar2(200)) on Commit delete Rows ;
3) 两中类型临时表的区别 会话级临时表采用 on commit preserve rows ;而事务级则采用 on commit delete rows ;用法上,会话级别只有当会话结束临时表中的数据才会被截断,而且事务级临时表则不管是 commit 、 rollback 或者是会话结束,临时表中的数据都将被截断 4 )什么时候使用临时表 1 )、当某一个 SQL 语句关联的表在 2 张及以上,并且和一些小表关联。可以采用将大表进行分拆并且得到比较小的结果集合存放在临时表中 2 )、程序执行过程中可能需要存放一些临时的数据,这些数据在整个程序的会话过程中都需要用的等等。 3 . 例子:略 4 .临时表的不足之处 1 )不支持 lob 对象,这也许是设计者基于运行效率的考虑,但实际应用中确实需要此功能时就无法使用临时表了。 2 )不支持主外键关系 所以,由于以上原因,我们可以自己创建临时表,以弥补 oracle 临时表的不足之处 上面的都是本人经过测试的,但下面是在网上搜索到的方法,本人具体没有测试过,不过觉得可行性很强,有时间测试下 创建方法: 1 、以常规表的形式创建临时数据表的表结构,但要在每一个表的主键中加入一个 SessionID 列以区分不同的会话。(可以有 lob 列和主外键) 2 、写一个用户注销触发器,在用户结束会话的时候删除本次会话所插入的所有记录 (SessionID 等于本次会话 ID 的记录 ) 。 3 、程序写入数据时,要顺便将当前的会话 ID(SessionID) 写入表中。 4 、程序读取数据时,只读取与当前会话 ID 相同的记录即可。 功能增强的扩展设计: 1 、可以在数据表上建立一个视图,视图对记录的筛选条件就是当前会话的SessionID 。 2 、数据表中的SessionID 列可以通过Trigger 实现,以实现对应用层的透明性。 3 、高级用户可以访问全局数据,以实现更加复杂的功能。 扩展临时表的优点: 1 、实现了与Oracle 的基于会话的临时表相同的功能。 2 、支持SDO_GEOMETRY 等lob 数据类型。 3 、支持表间的主外键连接,且主外键连接也是基于会话的。 4 、高级用户可以访问全局数据,以实现更加复杂的功能 原文出自: http://database.51cto.com/art/201001/180851.htm
Heritrix绑定主机IP 关键字:Heritrix 127.0.0.1 IP 主机 Heritrix默认绑定的IP是127.0.0.1。 在org.archive.crawler.Heritrix中 … final private static Collection<String> LOCALHOST_ONLY = Collections.unmodifiableList(Arrays.asList(new String[] { "127.0.0.1" })); … private static Collection<String> guiHosts = LOCALHOST_ONLY; protected static String doCmdLineArgs(final String [] args) throws Exception { … // Now look at options passed. for (int i = 0; i < options.length; i++) { switch(options[i].getId()) { … case 'b': Heritrix.guiHosts = parseHosts(options[i].getValue()); break; … default: assert false: options[i].getId(); } } … } | 首先定义了默认IP:127.0.0.1,然后赋给guiHost主机变量。当指定-b或--bind参数时,才会把指定的IP赋给主机变量。 另外,中间还有一步参数处理,对于--xxxx参数会转为-x的形式统一处理,所以--bind和-b有一样的效果。 Heritrix启动参数 关键字:Heritrix 启动 参数 bind admin properties Heritrix的启动参数,除了--bind外,都可以在heritrix.properties设置,而不用每次都在命令行中输入。 如常用的--port, --admin等。 heritrix.cmdline.admin = admin:admin heritrix.cmdline.port = 8080 heritrix.cmdline.run = false heritrix.cmdline.nowui = false heritrix.cmdline.order = heritrix.cmdline.jmxserver = false heritrix.cmdline.jmxserver.port = 8081 | 关于Heritrix的Extractor中文乱码 关键字:Heritrix 中文 乱码 GB2312 Extractor 继承从org.archive.crawler.extractor.Extractor的子类,在extract方法中可以从参数CrawlURI中取出要解析的内容。 curi.getHttpRecorder().getReplayCharSequence.toString() | 有中文时,不做处理会输出乱码。可以在取到的HttpRecorder后设置编码: HttpRecorder hr = curi.getHttpRecorder(); if ( hr == null ) { throw new IOException( "Why is recorder null here?" ); } hr.setCharacterEncoding( "gb2312" ); cs = hr.getReplayCharSequence(); System.out.println( cs.toString() ); | 原文出自: http://blog.chinaunix.net/uid-8464637-id-2461166.html
原文出自: http://blog.csdn.net/jbxiaozi/article/details/7351768
1.右键项目-》属性-》java bulid path-》jre System Library-》access rules-》resolution选择accessible,下面填上** 点击确定即可!!! 2. 在MyEclipse中编写Java代码时,用到了BASE64Decoder,import sun.misc.BASE64Decoder;可是Eclipse提示: Access restriction: The type BASE64Decoder is not accessible due to restriction on required library C:\Program files\java\jre6\lib\rt.jar Access restriction : The constructor BASE64Decoder() is not accessible due to restriction on required library C:\Program files\java\jre6\lib\rt.jar 解决方案1(推荐): 只需要在project build path中先移除JRE System Library,再添加库JRE System Library,重新编译后就一切正常了。
解决方案2: Windows -> Preferences -> Java -> Compiler -> Errors/Warnings -> Deprecated and trstricted API -> Forbidden reference (access rules): -> change to warning
项目中有时需要用到起始日期和结束日期,要做到起始日期必须小于结束日期。在extjs中已经有现成的函数,摘录如下: 1 Ext.apply(Ext.form.VTypes, { 2 daterange : function(val, field) { 3 var date = field.parseDate(val); 4 if (!date) { 5 return; 6 } 7 if (field.startDateField 8 && (!this.dateRangeMax || (date.getTime() != this.dateRangeMax 9 .getTime()))) { 10 var start = Ext.getCmp(field.startDateField); 11 start.setMaxValue(date); 12 start.validate(); 13 this.dateRangeMax = date; 14 } else if (field.endDateField 15 && (!this.dateRangeMin || (date.getTime() != this.dateRangeMin 16 .getTime()))) { 17 var end = Ext.getCmp(field.endDateField); 18 end.setMinValue(date); 19 end.validate(); 20 this.dateRangeMin = date; 21 } 22 /* 23 * Always return true since we're only using this vtype to set 24 * the min/max allowed values (these are tested for after the 25 * vtype test) 26 */ 27 return true; 28 } 29 }); 然后分别定义起始日期和结束日期控件: 1 var startDate = new Ext.form.DateField({ 2 fieldLabel : '开始日期', 3 emptyText : '请选择', 4 disabledDays : [1, 2, 5],//将星期一,二,五禁止.数值为0-6,0为星期日,6为星期六 5 labelWidth : 100, 6 readOnly : true, 7 allowBlank : false, 8 format : 'Y-m-d',//日期格式 9 name : 'startdt', 10 id : 'startdt', 11 vtype : 'daterange',//daterange类型为上代码定义的类型 12 endDateField : 'endDate'//必须跟endDate的id名相同 13 }) 14 var endDate = new Ext.form.DateField({ 15 fieldLabel : '结束日期', 16 emptyText : '请选择', 17 disabledDays : [1, 2, 5],//将星期一,二,五禁止.数值为0-6,0为星期日,6为星期六 18 readOnly : true, 19 allowBlank : false, 20 format : 'Y-m-d',//日期格式 21 name : 'enddt', 22 id : 'endDate', 23 vtype : 'daterange',//daterange类型为上代码定义的类型 24 startDateField : 'startdt'//必须跟startDate的id名相同 25 }) 效果如下两图: 图1.选择开始日期 图2.选择结束日期 原文出自: http://blog.csdn.net/security08/article/details/5070749
工作流管理联盟: 定义: 创建并完善了工作流的相关标准,开拓了相关市场,是唯一的致力于工作流标准化的专业组织。该组织推出了工作流XML(Wf-XML)和XML过程定义语言(XPDL) ,现在有超过80种有名的解决方案中使用了这两种语言来存储和交换过程模型。发布了用于工作流管理系统之间互操作的工作流参考模型,并且为了实现不同工作流产品之间的互操作,WfMC在工作流管理系统的相关术语、体系结构及应用编程接口等方面制定了一系列标准 工作流: 定义: 工作流是一类能够完全或者部分自动执行的经营过程,根据一系列过程规则,文档、信息或任务能够在不同的执行者之间传递、执行。从工作流的定义可以看出:(1)、有多个参与者:(2)、按照一定的规则进行活动(传递文档、信息、任务等);(3)、活动的推进是自动的或部分自动的。【工作流管理联盟】 工作流管理系统: 定义: 工作流管理系统是一个软件系统,它负责工作流的定义和管理,并按照在计算机中预先定义好的工作流逻辑推进过程实例的执行。工作流管理系统(Workflow Management System,WFMS)是通过对工作流程中涉及各步骤的人员和IT资 源的合理调整,从而起到对工作流的定义、管理和实现的确定性作用。工作流管理系统是支持企业实现业务过程管理和自动化的强有力的软件工具,它能完成工作流 的定义和管理,并按照在计算机中预先定义好的工作流逻辑推进工作流实例的执行。所以工作流是工作流管理系统的最重要的被管理的元素,就像表、试图是数据库 管理系统的管理对象一样。【工作流管理联盟】 工作流参考模型: 定义: 1.通用的工作流系统实现模型 2.把工作流系统中的主要功能组件和这些组件间的接口一起看成抽象的模型 作用: 1.这个模型可以与市场上的大多数产品相匹配,因此为开发协同工作的工作流系统奠定了基础 2.工作流参考模型的引入为人们讨论工作流技术提供了一一个规范的术语表,为在一般意义上讨论工作流系统的体系结构提供了基础:工作流参考模型为工作流管理系统的关键软件部件提供了功能描述,并描述了关键软件部件交互,而 且这个描述是独立于特定产品或技术的实现的:从功能的角度定义五个关键软件部件的交互接口,推动了信息交换的标准化,使得不同产品间的互操作成为可能。 组成: 1.软件组件:为工作流系统的各种功能提供支持。 2.各中类型的系统定义数据和控制数据:系统中的一个或多个软件构件使用的数据。 3.应用程序与应用程序数据库:外部系统或数据,被系统调用来完成整个或部分工作流管理的功能。 五类接口(WorkflowAPI,WAPI) 接口l:工作流执行服务与工作流建模工具间的接口,为实现对工作流过程定义的访问(如建立、修改、删除等)提供了一致的方法。 接口2:工作流服务和用户应用之间的接口,这是最主要的接口规范,它约定所有客户方应用和工作流服务之间的功能访问方式。 接口3:工作流引擎和应用服务间的直接接口,其目标是集成工作流和其它应用服务而无需考虑原有工作流管理系统。 接口4:工作流管理系统之间的互操作接口,用于描述不同工作流产品的互操作性。 一般必须的互操作有两个主要方面: (1)流程定义或子集的公共解释; (2)运行时间对各种控制信息转换,和在不同实施服务之间传递工作流相关数据和应用数据的支持。_个工作流引擎可以选择、实例化和执行其他工作流引擎所约定的流程定义。 接口5:工作流服务和工作流管理工具之间的接口,用于系统管理、应用访问工作流执行服务。 通用工作流系统各部分功能: 1.工作流执行服务 工作流执行服务是指由一个或者多个工作流引擎组成,以创建,管理和执行工作流实例,应用程序可能通过工作流应用程序接口(WAPI))与这个服务进行交互。工作流执行服务的主要功能是:解释流程定义,生成过程实例,并管理其实施过程;依据工作流相关数据实现流程活动导航,包括顺序或并行操作、期限设置等;与外部资源交互,完成各项活动;维护工作流控制数据和工作流相关数据,并向用户传送必要的相关数据。 工作流执行服务使用外部资源的两种途径: 1.用户应用接口:工作流引擎通过任务项列表管理资源,任务项列表管理器负责从任务项列表中选择并监督工作项的完成。任务项列表管理器或用户负责调用应用工具。 2.直接调用应用接口:工作流引擎直接调用相应的应用来完成一项任务。这主要是针对基于服务器的无需用户参与的应用,那些需要用户操作的活动则通过任务列表管理器来调用。 2.工作流引擎 工作流引擎是指为工作流实例提供运行时执行环境的软件服务或“引擎”。 主要提供以下功能:对过程定义进行解释;控制过程实例的生成、激活、挂起、终止等;控制过程活动间的转换,包括串行或并行的操作、工作流相关数据的解释 等;支持用户操作的界面;维护工作流控制数据和工作流相关数据,在应用或用户间传递工作流相关数据;提供用于激活外部应用并提供工作流相关数据的界面;提 供控制、管理和监督的功能。 3.过程定义工具 过程定义工具是管理流程定义的工具,它可以通过图形方式把复杂的流程定义显示出来并加以操作。流程定义工具同工作流执行服务交互,为用户提供一种对实际业务过程进行分析、建模的手段,并生成业务过程的可被计算机处理的形式化描述(过程定义)。这也是工作流系统建立阶段的主要任务。不同的工作流产品,其建模工具输出格式是不同的。接口l不仅使工作流的定义阶段和运行阶段分离,使用户可以分别选择建模工具和执行产品,并且提供了对工作流过程进行协同定义的潜在能力,这些产品提供了分布的运行服务。 4.管理和监控工具 管理和监控工具主要负责对组织机构、角色等数据的维护管理和工作流实例的运行进行监控。管理员可以通过工作流管理工具获得目前各个活动的运行情况报告,并可干预实例的推进。 5.客户端应用 客户端应用是通过请求的方式同工作流执行服务交互的应用,也就是说是客户端应用调用工作流执行服务,客户端应用同工作流执行服务交互。它提供给用户一种手段,以处理实例运行过程中需要人工参与的任务。 6.调用的应用 调用的应用指工作流执行服务在过程实例运行过程中调用的、用以对应用数据进行处理的应用程序和Web服务 原文出自: http://www.cnblogs.com/yuziyan/archive/2012/12/08/2809346.html
类图画法 类之间的几种关系:泛化(Generalization)、实现(Realization)、关联(Association)(又分一般关联、聚合(Aggregation)、组合(Composition))、依赖(Dependency) 一、类图画法 1、 类图的概念 A、显示出类、接口以及它们之间的静态结构和关系 B、用于描述系统的结构化设计 2、 类图的元素 类、接口、协作、关系,我们只简单介绍一下这四种元素。 同其他的图一样,类图也可以包含注解和限制。 类图中也可以包含包和子系统,这两者用来将元素分组。 有时候你也可以将类的实例放到类图中。 3、 类 A、 类是对一组具有相同属性、操作、关系和语义的对象的抽象,它是面向对象系统组织结构的核心,包括名称部分(Name)、属性部分(Attribute)和操作部分(Operation),见下图。 B、 类属性的语法为: [可见性] 属性名 [:类型] [=初始值] [{属性字符串}] 可见性:公有(Public)“+”、私有(Private)“-”、受保护(Protected)“#” 类操作的语法为: [可见性] 操作名 [(参数表)] [:返回类型] [{属性字符串}] 可见性:公有(Public)“+”、私有(Private)“-”、受保护(Protected)“#”、包内公有(Package)“~” 参数表: 定义方式:“名称:类型”;若存在多个参数,将各个参数用逗号隔开;参数可以具有默认值; 属性字符串: 在操作的定义中加入一些除了预定义元素之外的信息。 4、 接口 在没有给出对象的实现和状态的情况下对对象行为的描述。 一个类可以实现一个或多个接口。 使用两层矩形框表示,与类图的区别主要是顶端有<<interface>>显示: 也可以用一个空心圆表示: 5、 协作 协作是指一些类、接口和其他的元素一起工作提供一些合作的行为,这些行为不是简单地将元素 加能得到的。例如:当你为一个分布式的系统中的事务处理过程建模型时,你不可能只通过一个类来明白事务是怎样进行的,事实上这个过程的执行涉及到一系列的 类的协同工作。使用类图来可视化这些类和他们的关系。 6、 关系 这篇文章的重点,详见第二部分。 二、类之间的几种关系 1、 泛化(Generalization) A、 是一种继承关系,表示一般与特殊的关系,它指定了子类如何特化父类的所有特征和行为,描述了一种“is a kind of” 的关系。例如:老虎是动物的一种,即有老虎的特性也有动物的共性。 B、 用带空心箭头的实线表示,箭头指向父类,如下图: 2、 实现(Realization) A、 是一种类与接口的关系,表示类是接口所有特征和行为的实现。 B、 用带空心箭头的虚线表示,箭头指向接口,如下图: 3、 关联(Association) A、 一般关联 a、 关联关系是类与类之间的联结,它使一个类知道另一个类的属性和方法,指明了事物的对象之间的联系,如:老师与学生、丈夫与妻子。关联可以是双向的,也可以是单向的,还有自身关联。 b、 用带普通箭头的实心线表示。双向的关联可以有两个箭头或者没有箭头,单向的关联有一个箭头,如下图: B、 聚合(Aggregation) a、 它是整体与部分(整体 has a 部分)的关系,且部分可以离开整体而单独存在,如车和轮胎是整体和部分的关系,轮胎离开车仍然可以存在。聚合关系是关联关系的一种,是强的关联关系,关联和聚合在语法上无法区分,必须考察具体的逻辑关系。 b、 用带空心菱形的实线表示,菱形指向整体,如下图: C、 组合(Composition) a、 它是整体与部分的关系,但部分不能离开整体而单独存在。如公司和部门是整体和部分的关系,没有公司就不存在部门。组合关系是关联关系的一种,是比聚合关系还要强的关系,它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。 b、 用带实心菱形的实线表示,菱形指向整体,如下图: 4、 依赖(Dependency) A、 元素A的变化会影响元素B,那么B和A的关系是依赖关系,B依赖A。要避免双向依赖,一般来说,不应该存在双向依赖。关联、实现、泛化都是依赖关系。 B、 用带箭头的虚线表示,箭头指向被依赖元素。 5、 总结 各种关系的强弱顺序如下: 泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖 下面这张UML图,比较形象地展现了各种类图关系: 原文出自: http://www.cnblogs.com/fuhongxue2011/archive/2012/12/09/2810408.html
摘要: 近日来对Ext特别感兴趣,也许是它那种OO的设计思想吸引了我,也可以追溯到第一次见到EXT那种漂亮的界面开始吧.求神拜佛不如自食其力,为了一点小的问题找遍了GOOGLE也没个结果,自己甚少去BBS混,也不熟悉规矩,只能硬着头皮自己干了.翻源代码是一道必不可少的工序,说来惭愧,自己对JS的认识还停留在入门阶段.这里说说自己对于Ext验证这里浅薄的理解:首先看看如下一段代码Code highlight... 阅读全文
由于HTTP协议的无状态特性,导致在ASP.NET编程中,每个请求都会在服务端从头到执行一次管线过程, 对于ASP.NET页面来说,Page对象都会重新创建,所有控件以及内容都会重新生成, 因此,如果希望上一次的页面状态能够在后续页面中保留,则必需引入状态管理功能。 ASP.NET为了实现状态管理功能,提供了8种方法,可帮助我们在页面之间或者整个用户会话期间保留状态数据。 这些方法分为二类:视图状态、控件状态、隐藏域、Cookie 和查询字符串会以不同方式将数据发送到客户端上。 而应用程序状态、会话状态和配置文件属性(Profile)则会将数据存储到服务端。 虽然每种方法都有不同的优点和缺点,对于小的项目来说,可以选择自己认为最容易使用的方法, 然而,对于有着较高要求的程序,尤其是对于性能与扩展性比较关注的程序来说, 选择不同的方法最终导致的差别可能就非常大了。 在这篇博客中,我将谈谈自己对ASP.NET状态管理方面的一些看法。 注意:本文的观点可能并不合适开发小型项目,因为我关注的不是易用性。 hidden-input hidden-input 这个名字我是取的,表示所有type="hidden"的input标签元素。 在中文版的MSDN中,也称之为 隐藏域 。 hidden-input通常存在于HTML表单之内,它不会显示到页面中, 但可以随表单一起提交,因此,经常用于维护当前页面的相关状态,在服务端我们可以使用Request.Form[]来访问这些数据。 一般说来,我通常使用hidden-input来保存一些中间结果,用于在多次提交中维持一系列状态, 或者用它来保存一些固定参数用来提交给其它页面(或网站)。 在这些场景中,我不希望用户看到这些数据,因此,使用hidden-input是比较方便的。 关于表单的更多介绍可参考我的博客:细说 Form (表单) 在ASP.NET WebForm框架中,我们可以使用HiddenField控件来创建一个hidden-input控件,并可以在服务端操作它, 还可以直接以手写的方式使用隐藏域,例如: <input type="hidden" name="hidden-1" value="aaaaaaa" /> <input type="hidden" name="hidden-2" value="bbbbbbb" /> <input type="hidden" name="hidden-3" value="ccccccc" /> 另外,我们还可以调用ClientScript.RegisterHiddenField()方法来创建隐藏域: ClientScript.RegisterHiddenField("hidden-4", "ddddddddd"); 输出结果: <input type="hidden" name="hidden-4" id="hidden-4" value="ddddddddd" /> 这三种方法对于生成的HTML代码来说,主要差别在于它们出现位置不同: 1. HiddenField控件:由HiddenField的出现位置来决定(在form内部)。 2. RegisterHiddenField方法:在form标签的开头位置。 3. hidden-input:你写在哪里就是哪里。 优点: 1. 不需要任何服务器资源:隐藏域随页面一起发送到客户端。 2. 广泛的支持:几乎所有浏览器和客户端设备都支持具有隐藏域的表单。 3. 实现简单:隐藏域是标准的 HTML 控件,不需要复杂的编程逻辑。 缺点: 1. 不能在多页面跳转之间维持状态。 2. 用户可见,保存敏感数据时需要加密。 QueryString 查询字符串是存在于 URL 结尾的一段数据。下面是一个典型的查询字符串示例(红色部分文字): http://www.abc.com/demo.aspx?k1=aaa&k2=bbb&k3=ccc 查询字符串经常用于页面的数据过滤,例如: 1. 给列表页面增加分页参数,list.aspx?page=2 2. 给列表页面增加过虑范围,Product.aspx?categoryId=5 3. 显示特定记录,ProductInfo.aspx?page=3 关于查询字符串的用法,我补充二点: 1. 可以调用HttpUtility.ParseQueryString()来解析查询字符串。 2. 允许参数名重复:list.aspx?page=2&page=3,因此在修改URL参数时,使用替换方式而不是追加。 关于参数重名的读取问题,请参考我的博客:细说 Request[]与Request.Params[] 优点: 1. 不需要任何服务器资源:查询字符串的数据包含在每个URL中。 2. 广泛的支持:几乎所有的浏览器和客户端设备均支持使用查询字符串传递参数值。 3. 实现简单:在服务端直接访问Request.QueryString[]可读取数据。 4. 页面传值简单:<a href="url">或者 Response.Redirect(url) 都可以实现。 缺点: 1. 有长度限制。 2. 用户可见,不能保存敏感数据。 Cookie 由于HTTP协议是无状态的,对于一个浏览器发出的多次请求,WEB服务器无法区分它们是不是来源于同一个浏览器。所以,需要额外的数据用于维护会话。 Cookie 正是这样的一段随HTTP请求一起被传递的额外数据。 Cookie 是一小段文本信息,它的工作方式就是伴随着用户请求和页面在 Web 服务器和浏览器之间传递。Cookie 包含每次用户访问站点时 Web 应用程序都可以读取的信息。 与hidden-input, QueryString相比,Cookie有更多的属性,许多浏览器可以直接查看这些信息: 由于Cookie拥有这些属性,因此在客户端状态管理中可以实现更多的功能,尤其是在实现客户端会话方面具有不可替代的作用。 关于Cookie的更多讲解,请参考我的另一篇博客:细说Cookie 优点: 1. 可配置到期规则:Cookie可以在客户端长期存在,也可以在浏览器关闭时清除。 2. 不需要任何服务器资源:Cookie 存储在客户端。 3. 简单性:Cookie 是一种基于文本的轻量结构,包含简单的键值对。 4. 数据持久性:与其它的客户端状态数据相比,Cookie可以实现长久保存。 5. 良好的扩展性:Cookie的读写要经过ASP.NET管线,拥有无限的扩展性。 这里我要解释一下Cookie 【良好的扩展性】是个什么概念,比如: 1. 我可以实现把Cookie保存到数据库中而不需要修改现有的项目代码。 2. 把SessionId这样由ASP.NET产生的临时Cookie让它变成持久保存。 缺点: 1. 大小受到限制。 2. 增加请求头长度。 3. 用户可见,保存敏感数据时需要加密。 ApplicationState 应用程序状态是指采用HttpApplicationState实现的状态维持方式,使用代码如下: Application.Lock(); Application["PageRequestCount"] = ((int)Application["PageRequestCount"]) + 1; Application.UnLock(); 对于这种方法,我不建议使用,因为: 1. 与使用静态变量差不多,直接使用静态变量可以不需要字典查找。 2. 选择强类型的集合或者变量可以避免装箱拆箱。 ViewState,ControlState 视图状态,控件状态,二者是类似,在页面中表现为一个hidden-input元素: <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="......................" /> 控件状态是ASP.NET 2.0中引入,与视图状态相比,它不允许关闭。 由于它们使用方式一致,而且视图状态是基于控件状态的实现逻辑,所以我就不区分它们了。 在ASP.NET的早期,微软为了能帮助广大开发人员提高开发效率,引用入一大批的服务端控件,并为了能将事件编程机制引入ASP.NET中,又发明了ViewState。 这种方式虽然可以简化开发工作量,然而却有一些限制和缺点: 1. 视图状态的数据只能用于回发(postback)。 2. 视图状态的【滥用】容易导致生成的HTML较大,这会引起一个恶性循环: a. 过大的ViewState在序列化过程中会消耗较多的服务器CPU资源, b. 过大的ViewState最终生成的HTML输出也会很大,它会浪费服务端网络资源, c. 过大的ViewState输出导致表单在下次提交时,会占用客户端网络资源。 d. 过大的ViewState数据上传到服务端后,反序列化又会消耗较多的服务器CPU资源。 因此,整个交互过程中,用户一直在等待,用户体验极差。 在ASP.NET兴起的年代,ViewState绝对是个了不起的发明。 然而,现在很多关于ASP.NET性能优化的方法中,都会将【关闭ViewState】放在头条位置。 为什么会这样呢,大家可以自己思考一下了。 有些人认为:我现在做的程序只是在局域网内使用,使用ViewState完全没有问题! 然而,那些人或许没有想过: 1. 未来用户可能会把它部署在互联网上运行(对于产品来说就是遇到大客户了)。 2. 项目早期的设计与规划,对后期的开发与维护来说,影响是巨大的,因为许多基础部分通常是在早期开发的。 当这二种情况的任何一种发生时,想再禁用ViewState,可能已经晚了。 对于视图状态,我认为它解决的问题比它引入的问题要多要复杂, 因此,我不想花时间整理它的优缺点,我只想说一句:把它关了,在web.config中关了。 另外,我不排斥使用服务器控件,我认为:你可以使用服务端控件显示数据,但不要用它处理回发。 如果你仍然认为视图状态是不可缺少的,那我还是建议你看看ASP.NET MVC框架,看看没有视图状态是不是照样可以写ASP.NET程序。 Session Session是ASP.NET实现的一种服务端会话技术,它允许我们方便地在服务端保存与用户有关的会话数据。 我认为Session只有一个优点:最简单的服务端会话实现方式。 缺点: 1. 当mode="InProc"时,容易丢失数据,为什么?因为网站会因为各种原因重启。 2. 当mode="InProc"时,Session保存的东西越多,就越占用服务器内存,对于用户在线人数较多的网站,服务器的内存压力会比较大。 3. 当mode="InProc"时,程序的扩展性会受到影响,原因很简单:服务器的内存不能在多台服务器间共享。 4. 当采用进程外模式时,在每次请求中,不管你用不用会话数据,所有的会话数据都为你准备好了(反序列化),这其实很是浪费资源的。 5. 如果你没有关闭Session,SessionStateModule就一直在工作中,尤其是全采用默认设置时,会对每个请求执行一系列的调用,浪费资源。 6. 阻塞同一客户端发起的多次请求(默认方式)。 7. 无Cookie会话可能会丢失数据(重新生成已过期的会话标识符)。 Session的这些缺点也提醒我们: 1. 当网站的在线人数较多时,一定不要用Session保存较大的对象。 2. 在密集型的AJAX型网站或者大量使用iframe的网站中,要关注Session可能引起的服务端阻塞问题。 3. 当采用进程外模式时,不需要访问Session的页面,一定要关闭,否则会浪费服务器资源。 如果想了解更多的Session特点,以及我对Session的看法,可以浏览我的博客:Session,有没有必要使用它? Session的本质有二点: 1. SessionId + 服务端字典:服务端字典保存了某个用户的所有会话数据。 2. 用SessionId识别不同的客户端:SessionId通常以Cookie形式发送到客户端。 我认为了解Sesssion本质非常有用,因为可以借鉴并实现自己的服务端会话方法。 关于Session我还想说一点: 有些新手喜欢用Session来实现身份认证功能,这是一种【不正确】的方法。 如果你的ASP.NET应用程序需要身份认证功能,请使用 Forms身份认证 或者 Windows身份认证 Profile Profile 在中文版的MSDN中被称为 配置文件属性,这个功能是在 ASP.NET 2.0 中引入的。 ASP.NET提供这个功能主要是为了简化与用户相关的个性化信息的读写方式。 简化主要体现在3个方面: 1. 自动与某个用户关联,已登录用户或者未登录都支持。 2. 不需要我们设计用户的个性化信息的保存表结构,只要修改配置文件就够了。 3. 不需要我们实现数据的加载与保存逻辑,ASP.NET框架替我们实现好了。 为了使用Profile,我们首先在web.config中定义所需要的用户个性化信息: <profile> <properties> <add name="Address"/> <add name="Tel"/> </properties> </profile> 然后,就可以在页面中使用了: 为什么会这样呢? 原因是ASP.NET已经根据web.config为我们创建了一个新类型: using System; using System.Web.Profile; public class ProfileCommon : ProfileBase { public ProfileCommon(); public virtual string Address { get; set; } public virtual string Tel { get; set; } public virtual ProfileCommon GetProfile(string username); } 有了这个类型后,当我们访问HttpContext.Profile属性时,ASP.NET会创建一个ProfileCommon的实例。 也正是由于Profile的强类型机制,在使用Profile时才会有智能提示功能。 如果我们希望为未登录的匿名用户也提供这种支持,需要将配置修改成: <profile> <properties> <add name="Address" allowAnonymous="true" /> <add name="Tel" allowAnonymous="true"/> </properties> </profile> <anonymousIdentification enabled="true" /> Profile中的每个属性还允许指定类型和默认值,以及序列化方式,因此,扩展性还是比较好的。 尽管Profile看上去很美,然而,使用Profile的人却很少。 比如我就不用它,我也没见有人有过它。 为什么会这样?
我个人认为:它与MemberShip一样,是个鸡肋。 通常说来,我们会为用户信息创建一张User表,增加用户信息时,会通过增加字段的方式解决。 我认为这样集中的数据才会更好,而不是说,有一部分数据由我维护,另一部分数据由ASP.NET维护。
另一个特例是:我们根本不创建User表,直接使用MemberShip,那么Profile用来保存MemberShip没有信息是有必要的。 还是给Profile做个总结吧: 优点:使用简单。 缺点:不实用。 各种状态管理的对比与总结 前面分别介绍了ASP.NET的8种状态管理技术,这里打算给它们做个总结。
| 客户端 | 服务端 | 数据安全性 | 差 | 好 | 数据长度限制 | 有 | 受硬件限制 | 占用服务器资源 | 否 | 是 | 集群扩展性 | 好 | 差 | 表格中主要考察了数据保存与服务端水平扩展的相关重要指标。 下面我来解释表格的结果。 1. 客户端方式的状态数据(hidden-input, QueryString, Cookie): a. 数据对用户来说,可见可修改,因此数据不安全。 b. QueryString, Cookie 都有长度限制。 c. 数据在客户端,因此不占用服务端资源。这个特性对于在线人数很多的网站非常重要。 d. 数据在客户端,因此和服务端没有耦合关系,WEB服务器可以更容易实现水平扩展。 2. 服务端方式的状态数据(ApplicationState,ViewState,ControlState,Session,Profile): a. 数据对用户不可见,因此安全性好。(ApplicationState,Session,Profile) b. 数所长度只受硬件限制,因此,对于在线人数较多的网站,需谨慎选择。 c. 对于存放在内存中的状态数据,由于不能共享内存,因此会限制水平扩展能力。 d. 如果状态数据保存到一台机器,会有单点失败的可能,也会限制了水平扩展能力。 从这个表格我们还可以得到以下结论: 1. 如果很关注数据的安全性,应该首选服务端的状态管理方法。 2. 如果你关注服务端的水平扩展性,应该首选客户端的状态管理方法。 会话状态的选择 接下来,我们再来看看会话状态,它与状态管理有着一些关系,属于比较类似的概念。 谈到会话状态,首先我要申明一点:会话状态与状态不是一回事。 本文前面所说的状态分为二种: 1. 页面之间的状态。 2. 应用程序范围内的状态。 而会话状态是针对某个用户来说,他(她)在多次操作之间的状态。 在用户的操作期间,有可能状态需要在页面之间持续使用, 也有可能服务端程序做过重启,但数据仍然有效。 因此,这种状态数据更持久。 在ASP.NET中,使用会话状态有二个选择:Session 或者 Cookie 。 前者由ASP.NET实现,并有可能依赖后者。 后者则由浏览器实现,ASP.NET提供读写方法。 那么到底选择哪个呢? 如果你要问我这个问题,我肯定会说:我选 Cookie ! 下面是我选择Cookie实现会话状态的理由: 1. 不会有服务端阻塞问题。 2. 不占用服务端资源。 3. 水平扩展没有限制。 4. 也支持过期设置,而且更灵活。 5. 可以在客户端直接使用会话数据。 6. 可以实现更灵活的会话数据加载策略。 7. 扩展性较好(源于ASP.NET管线的扩展性) 如果选择使用Cookie实现会话状态,有3点需要特别注意: 1. 不建议保存敏感数据,除非已加密。 2. 只适合保存短小简单的数据。 3. 如果会话数据较大,可以在客户端保存用户标识,由服务端实现数据的加载保存逻辑。 或许有些人认为:每种技术都有它们的优缺点,有各自的适用领域。 我表示赞同这句话。 但是,我们要清楚一点:每个项目的规模不一样,性能以及扩展性要求也不同。 对于一个小的项目来说,选择什么方法都不是问题, 但是,对于规模较大的项目,我们一定需要取舍。 取舍的目标是:包装越少越好,因为人家做了过多的包装,就会有较多的限制, 所以,不要只关注现在的调用是否方便,其实只要你愿意包装,你也可以让复杂的调用简单化。 改变开发方式,发现新方法 回想一下:为什么在ASP.NET中需要状态管理? 答:因为与HTTP协议有关,服务端没有保存每个请求的上次页面状态。 为什么Windows计算器(这类)程序不用考虑会话问题呢? 答:因为这类程序的界面不需要重新生成,任何变量都可表示状态。 再来看这样一个场景: 图片左边是一个列表页面,允许调整每条记录的优先级,但是有2个要求: 1. 在移动每条记录时,必须输入一个调整理由。 2. 只要输入理由后,那条记录可以任意调整多次。 显然,完成这个任务必须要有状态才能实现。 面对这个问题,你可以思考一下:选择哪种ASP.NET支持的状态管理方法都很麻烦。 怎么办? 我的解决方法:创建一个JavaScript数组,用每个数组元素保存每条记录的状态, 所有用户交互操作用AJAX方式实现,这样页面不会刷新,JavaScript变量中的状态一直有效。 因此,很容易就能解决这个问题。 这个案例也提醒我们:当发现ASP.NET提供的状态管理功能全部不合适时, 我们需要改变开发方式了。 为什么WEB编程都有【无状态】问题,而桌面程序没有? 我认为与HTTP协议有关,但没有绝对的关系。 只要你能保证页面不刷新,也能像桌面程序那样,用JavaScript变量就能维护页面状态。 原文出自: http://www.cnblogs.com/fish-li/archive/2012/11/21/2780086.html
1.本地库导出
mysqldump -u 用户名 -p 数据库名 > 存放位置 例: mysqldump -uroot -p1234 testdb > /root/db/testdb.sql
2.从远程库导出
mysqldump -h 主机IP -u 用户名 -p 数据库名 > 存放位置 例: mysqldump -h192.168.30.30 -uroot -p1234 testdb > /root/db/testdb.sql
3.导出表结构及数据
mysqldump -u 用户名 -p 数据库名 表名 > 导出的文件名 例: mysqldump -uroot -p1234 testdb tab > /root/db/testdb.tab.sql
4.导入数据库
mysql -u用户名 -p密码 数据库名 < 数据库名 例: mysql -uroot -p1234 testdb < /root/db/back.sql 注意:testdb 要事先创建好.
原文出自: http://www.cnblogs.com/natgeo/archive/2012/12/08/2809027.html
在formPanel里加个Key事件 1 keys : [{ 2 key : Ext.EventObject.ENTER, 3 fn : function(keyCode, e) { 4 var field = Ext.getCmp(e.target.id); 5 if (Ext.isDefined(field) && field != null) { 6 if (Ext.isDefined(field.xtype)) { 7 if (field.isXType('datefield')) { 8 field.setValue(field.getValue()); 9 } 10 } 11 } 12 if (Ext.isIE) { 13 e.browserEvent.keyCode = Ext.EventObject.TAB; 14 } else { 15 var currentfield = Ext.getCmp(e.target.id); 16 var fields = refThis.HusbandView 17 .findByType('field'); 18 var i = 0; 19 for (; i < fields.length; i++) { 20 if (fields[i].id == currentfield.id) 21 break; 22 } 23 while (true) { 24 i++; 25 if (fields.length <= i) 26 break; 27 if (!fields[i].disabled 28 && fields[i].xtype != 'hidden' 29 && !fields[i].hidden) 30 break; 31 } 32 if (fields.length <= i) 33 return; 34 if (!fields[i].disabled) { 35 fields[i].focus(); 36 if (Ext.isDefined(fields[i].selectText)) 37 fields[i].selectText(); 38 } 39 } 40 } 41 }]
原文出自:http://xlong224.blog.163.com/blog/static/601214932011102810201224/
原先的EditGrid无法解决回车控制问题,它的回车控制是向下跑的。而我想让它横着走。搞了半天终于实现了。 1 Ext.override(Ext.grid.RowSelectionModel, { 2 onEditorKey : function(field, e) { 3 // alert('go'); 4 var k = e.getKey(), newCell, g = this.grid, ed = g.activeEditor; 5 var shift = e.shiftKey; 6 Ext.log('k:' + k); 7 if (k == e.ENTER) { 8 e.stopEvent(); 9 ed.completeEdit(); 10 if (shift) { 11 newCell = g.walkCells(ed.row, ed.col - 1, -1, 12 this.acceptsNav, this); 13 } else { 14 // alert('go'); 15 newCell = g.walkCells(ed.row, ed.col + 1, 1, 16 this.acceptsNav, this); 17 } 18 } else if (k == e.TAB) { 19 e.stopEvent(); 20 ed.completeEdit(); 21 if (this.moveEditorOnEnter !== false) { 22 if (shift) { 23 newCell = g.walkCells(ed.row - 1, ed.col, -1, 24 this.acceptsNav, this); 25 } else { 26 // alert('go'); 27 newCell = g.walkCells(ed.row + 1, ed.col, 1, 28 this.acceptsNav, this); 29 } 30 } 31 } else if (k == e.ESC) { 32 ed.cancelEdit(); 33 } 34 if (newCell) { 35 g.startEditing(newCell[0], newCell[1]); 36 } 37 } 38 }); 39 var sm2 = new Ext.grid.RowSelectionModel({ 40 moveEditorOnEnter : true, 41 singleSelect : true, 42 listeners : { 43 rowselect : function(sm, row, rec) { 44 centerForm.getForm().loadRecord(rec); 45 } 46 } 47 48 });
原文出自:http://erichua.iteye.com/blog/234698 2. 默认extjs中editorgrid编辑单元格的时候按回车是将焦点向下移动,按照一般的逻辑应该是向右移动。 其实只要将原先rowSelectionModel中onEditorKey方法override一下即可。 代码如下: 1 Ext.override(Ext.grid.RowSelectionModel, { 2 3 onEditorKey : function(field, e) { 4 var k = e.getKey(), newCell, g = this.grid, last = g.lastEdit, ed = g.activeEditor, shift = e.shiftKey, ae, last, r, c; 5 6 if (k == e.TAB) { 7 e.stopEvent(); 8 ed.completeEdit(); 9 if (shift) { 10 newCell = g.walkCells(ed.row, ed.col - 1, -1, this.acceptsNav, 11 this); 12 } else { 13 newCell = g.walkCells(ed.row, ed.col + 1, 1, this.acceptsNav, 14 this); 15 } 16 } else if (k == e.ENTER) { 17 if (this.moveEditorOnEnter !== false) { 18 if (shift) { 19 newCell = g.walkCells(last.row, last.col - 1, -1, 20 this.acceptsNav, this); 21 } else { 22 newCell = g.walkCells(last.row, last.col + 1, 1, 23 this.acceptsNav, this); 24 } 25 } 26 } 27 if (newCell) { 28 r = newCell[0]; 29 c = newCell[1]; 30 31 this.onEditorSelect(r, last.row); 32 33 if (g.isEditor && g.editing) { // *** handle tabbing while 34 // editorgrid is in edit mode 35 ae = g.activeEditor; 36 if (ae && ae.field.triggerBlur) { 37 // *** if activeEditor is a TriggerField, explicitly call 38 // its triggerBlur() method 39 ae.field.triggerBlur(); 40 } 41 } 42 g.startEditing(r, c); 43 } 44 } 45 })
方法1: JMX 很多人询问如何通过 JMX 来管理 Quertz,很奇怪的是 Quartz 的文档完全没有提及这方面的问题,你可以在 quartz.properties 中通过以下配置来启用 JMX 的支持: org.quartz.scheduler.jmx.export = true
然后你就可以使用标准的 JMX 客户端,例如 $JAVA_HOME/bin/jconsole 来连接到 Quartz 并进行远程管理。 方法2: RMI 另外一个远程管理 Quartz 的方法就是启用 RMI。如果你用的是 RMI 的方式,就会自动一个 Quartz 实例来作为 RMI 服务器,然后你可启动第二个 Quartz 实例来作为 RMI 客户端,二者通过 TCP 端口进行通讯。 服务器端调度实例配置方法(quartz.properties): org.quartz.scheduler.rmi.export = true org.quartz.scheduler.rmi.createRegistry = true org.quartz.scheduler.rmi.registryHost = localhost org.quartz.scheduler.rmi.registryPort = 1099 org.quartz.scheduler.rmi.serverPort = 1100
客户端调度器配置(quartz.properties): org.quartz.scheduler.rmi.proxy = true org.quartz.scheduler.rmi.registryHost = localhost org.quartz.scheduler.rmi.registryPort = 1099
关于 Quartz 的 RMI 特性的文档描述请看 这里. Quartz 不提供客户端 API,服务器端和客户端都是使用 org.quartz.Scheduler,只是配置不同而已。通过不同的配置来执行不同的行为。对服务器端来说,调度器用来执行所有的作业;而客户端只 是一个简单的代理,不运行任何作业,在关闭客户端的时候必须小心,因为它允许你也同时关闭服务器端。 这些配置都在我的示例程序 MySchedule 中有着重说明,如果你运行的是 Web 应用,你可以看到 这个演示,你将看到我们提供了很多 quartz 的示例配置用来做远程管理。 如果使用 RMI 方法,你可以用 MySchedule Web UI 做为一个代理来管理 Quartz,你可以查看作业,可停止服务器的运行。 根据我的经验,使用 RMI 方法的弊端就是创建一个新的单点故障,如果你的服务器端口宕掉了,就无法恢复。 原文出自: http://www.oschina.net/question/12_67413
1、Scheduler的配置 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="testTrigger"/> </list> </property> <property name="autoStartup" value="true"/> </bean>
说明:Scheduler包含一个Trigger列表,每个Trigger表示一个作业。 2、Trigger的配置 <bean id="testTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="testJobDetail"/> <property name="cronExpression" value="*/1 * * * * ?"/> <!-- 每隔1秒钟触发一次 --> </bean>
说明: 1)Cron表达式的格式:秒 分 时 日 月 周 年(可选)。 字段名 允许的值 允许的特殊字符 秒 0-59 , - * / 分 0-59 , - * / 小时 0-23 , - * / 日 1-31 , - * ? / L W C 月 1-12 or JAN-DEC , - * / 周几 1-7 or SUN-SAT , - * ? / L C # 年 (可选字段) empty, 1970-2099 , - * / “?”字符:表示不确定的值 “,”字符:指定数个值 “-”字符:指定一个值的范围 “/”字符:指定一个值的增加幅度。n/m表示从n开始,每次增加m “L”字符:用在日表示一个月中的最后一天,用在周表示该月最后一个星期X “W”字符:指定离给定日期最近的工作日(周一到周五) “#”字符:表示该月第几个周X。6#3表示该月第3个周五 2)Cron表达式范例: 每隔5秒执行一次:*/5 * * * * ? 每隔1分钟执行一次:0 */1 * * * ? 每天23点执行一次:0 0 23 * * ? 每天凌晨1点执行一次:0 0 1 * * ? 每月1号凌晨1点执行一次:0 0 1 1 * ? 每月最后一天23点执行一次:0 0 23 L * ? 每周星期天凌晨1点实行一次:0 0 1 ? * L 在26分、29分、33分执行一次:0 26,29,33 * * * ? 每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ? 3、JobDetail的配置 <bean id="testJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="testJob"/> <property name="targetMethod" value="execute"/> <property name="concurrent" value="false"/> <!-- 是否允许任务并发执行。当值为false时,表示必须等到前一个线程处理完毕后才再启一个新的线程 --> </bean>
4、业务类的配置 <bean id="testJob" class="com.cjm.web.service.quartz.TestJob"/> 5、业务类源代码 public class TestJob { public void execute(){ try{ //. }catch(Exception ex){ ex.printStackTrace(); } } }
说明:业务类不需要继承任何父类,也不需要实现任何接口,只是一个普通的java类。 注意: 在Spring配置和Quartz集成内容时,有两点需要注意 1、在<Beans>中不能够设置default-lazy-init="true",否则定时任务不触发,如果不明确指明default-lazy-init的值,默认是false。 2、在<Beans>中不能够设置default-autowire="byName"的属性,否则后台会报 org.springframework.beans.factory.BeanCreationException错误,这样就不能通过Bean名称自 动注入,必须通过明确引用注入
原文出自: http://www.oschina.net/question/8676_9032
缓存在 Solr 中充当了一个非常重要的角色,Solr 中主要有这三种缓存: - Filter cache(过滤器缓存),用于保存过滤器(fq 参数)和层面搜索的结果
- Document cache(文档缓存),用于保存 lucene 文档存储的字段
- Query result(查询缓存),用于保存查询的结果
还有第四种缓存,lucene 内部的缓存,不过该缓存外部无法控制到。 通过这 3 种缓存,可以对 solr 的搜索实例进行调优。调整这些缓存,需要根据索引库中文档的数量,每次查询结果的条数等。 在调整参数前,需要事先得到 solr 示例中的以下信息: - 索引中文档的数量
- 每秒钟搜索的次数
- 过滤器的数量
- 一次查询返回最大的文档数量
- 不同查询和不同排序的个数
这些数量可以在 solr admin 页面的日志模块找到。假设以上的值分别为: - 索引中文档的数量:1000000
- 每秒钟搜索的次数:100
- 过滤器的数量:200
- 一次查询返回最大的文档数量:100
- 不同查询和不同排序的个数:500
然后可以开始修改 solrconfig.xml 中缓存的配置了,第一个是过滤器缓存: <filterCache class="solr.FastLRUCache" size="200" initialSize="200" autowarmCount="100"/> 第二个是查询结果缓存: <queryResultCache class="solr.FastLRUCache" size="500" initialSize="500" autowarmCount="250"/> 第三个是文档缓存: <documentCache class="solr.FastLRUCache" size="11000" initialSize="11000" /> 这几个配置是基于以上的几个假设的值进行调优的。 原文出自: http://insolr.com/forum.php?mod=viewthread&tid=7&reltid=880&pre_thread_id=19&pre_pos=5&ext=
一.首先准备好solr的dataimport功能需要的东西,在solr的下载包中。分别在: 1》Solr-1.3.0\dist\apache-solr-dataimporthandler-1.3.0.jar 2》E:\education\search\Solr-1.3.0\example\example-DIH\solr\ 3》你是哪种数据库,提供该数据库的jdbc驱动。 二.如果你还不会运行solr,请参考本人的前几篇博客。这里要做的是,先把E:\education\search\Solr-1.3.0 \example\example-DIH\solr\下面的东西拷贝到solr的HOME目录,然后删除rss,这个是另外一个功能是导入rss订阅信 息到solr中,确实很强,这都想到了。将jar文件,实际就两个拷贝到tomcat的webapps下面的solr的WEB-INF的lib文件夹下 面。 三.更改solr Home目录下的conf/solrconfig.xml,其实就是提交一个solrRequestHandler,代码如下: - <requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
- <lst name="defaults">
- <str name="config">C:\solr-tomcat\solr\db\conf\db-data-config.xml</str>
- </lst>
- </requestHandler>
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler"> <lst name="defaults"> <str name="config">C:\solr-tomcat\solr\db\conf\db-data-config.xml</str> </lst> </requestHandler> 四.将solr Home目录下面的solrconfig.xml和schema.xml拷贝到db文件夹下面的conf中。 五.修改db\conf\db-data-config.xml - <dataConfig>
- <dataSource type="JdbcDataSource" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/tuitui" user="root" password="mysql"/>
- <document name="shop">
- <entity name="tuitui_shop" pk="shopId" query="select * from tuitui_shop">
- <field column="shopid" name="shopId" />
- <field column="shopName" name="shopName" />
- <field column="shopUrl" name="shopUrl" />
- <field column="keyword" name="keyword" />
- <field column="synopsis" name="synopsis" />
- <field column="province" name="province" />
- <field column="city" name="city" />
- <field column="domain" name="domain" />
- <field column="address" name="address" />
- <field column="coordinate" name="coordinate" />
- <field column="shopSspn" name="shopSspn" />
- <field column="phone" name="phone" />
- <field column="createTime" name="createTime" />
- </entity>
- </document>
- </dataConfig>
<dataConfig> <dataSource type="JdbcDataSource" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/tuitui" user="root" password="mysql"/> <document name="shop"> <entity name="tuitui_shop" pk="shopId" query="select * from tuitui_shop"> <field column="shopid" name="shopId" /> <field column="shopName" name="shopName" /> <field column="shopUrl" name="shopUrl" /> <field column="keyword" name="keyword" /> <field column="synopsis" name="synopsis" /> <field column="province" name="province" /> <field column="city" name="city" /> <field column="domain" name="domain" /> <field column="address" name="address" /> <field column="coordinate" name="coordinate" /> <field column="shopSspn" name="shopSspn" /> <field column="phone" name="phone" /> <field column="createTime" name="createTime" /> </entity> </document></dataConfig> 其中的意思我做简单解释,具体大家可以去看看官方wiki。 document:一个文档也就是lucene的document这个没什么解释的; entity:主要针对的是一个数据库表; filed:属性column是数据库的字段,name是filed的名字,即schema中的field name http://wiki.apache.org/solr/DataImportHandler 六.启动TOMCAT,输入地址进行导入,导入分为很多模式:我选用的全部倒入模式。 http://localhost/solr/dataimport?command=full-import 原文出自: http://insolr.com/forum.php?mod=viewthread&tid=128&reltid=880&pre_thread_id=19&pre_pos=4&ext=
先从网址 http://labs.mop.com/apache-mirror/lucene/solr/3.6.1/apache-solr-3.6.1.zip 下载程序文件 1.解压开找到apache-solr-3.6.1\apache-solr-3.6.1\dist目录下的apache-solr-3.6.1.war 直接放到tomcat/webapps下面 2.先别急着启动慢慢来,修改配置文件apache-solr-3.6.1\WEB-INF\web.xml 找到: <!-- People who want to hardcode their "Solr Home" directly into the WAR File can set the JNDI property here... --> <!-- <env-entry> <env-entry-name>solr/home</env-entry-name> <env-entry-value>/put/your/solr/home/here</env-entry-value> <env-entry-type>java.lang.String</env-entry-type> </env-entry> --> 更改这为 <!-- People who want to hardcode their "Solr Home" directly into the WAR File can set the JNDI property here... --> <env-entry> <env-entry-name>solr/home</env-entry-name> <env-entry-value>d:/solr_work/solr</env-entry-value> <env-entry-type>java.lang.String</env-entry-type> </env-entry> 注:红色字体的文件夹为apache-solr-3.6.1\example\solr 该应用(此为单核应用,多核以后会讲到)放大D盘的新建空目录solr_work目录下就可以 3.启动tomcat,输入 http://localhost:8080/solr solr应用搭建成功 原文出自: http://insolr.com/forum.php?mod=viewthread&tid=3&reltid=880&pre_thread_id=19&pre_pos=6&ext=
本文只是Solr 4.0的基础教程,本人不经常写东西,写的不好请见谅,欢迎到群233413850进行讨论学习。 先说一点部署之后肯定会有人用solrj,solr 4.0好像添加了不少东西,其中CommonsHttpSolrServer这个类改 名为HttpSolrServer,我是找了半天才发现,大家以后可以注意。 部署前准备: Solr 4.0 目录: 这里是我的部署方式,Tomcat安装好之后把apache-solr-4.0.0\example\webapps下的solr.war文件拷贝到Tomcat下的 Tomcat7.0\webapps目录下,然后启动Tomcat 报错不用管,solr.war会自动解压,之后打开Tomcat7.0\webapps\solr\WEB- INF\web.xml,把下面代码复制进去放到后面: - <env-entry>
- <env-entry-name>solr/home</env-entry-name>
- <env-entry-value>E:\SolrHome</env-entry-value>
- <env-entry-type>java.lang.String</env-entry-type>
- </env-entry>
复制代码 其中 E:\SolrHome 是存放solr配置文件等,修改为自己文件的位置,为了看着更清晰直观,你可以这样放: E:\Tomcat7.0 E:\apache-solr-4.0.0 E:\SolrHome 现在可以重新启动Tomcat了,没有报错,通过这个地址进入Solr4.0页面:http://localhost:8080/solr 如果进入以上界面说明成功了,没有成功的话页面会有ERROR提示。 如图:左侧core0,core1等是solr 4.0中的示例,core0和core1位于apache-solr-4.0.0\example\multicore所有文件都下拷贝 到E:\SoleHome下,core0和core1可以理解为两个库,都是独立的,用来存放索引以及生成这些索引文件所需要的配置文件,solrtest是我测试建立的目录,如图: 没添加一个库都需要在solr.xml里面进行配置,这个比较简单 - <solr persistent="false">
- <cores adminPath="/admin/cores" host="${host:}" hostPort="${jetty.port:}">
- <core name="core0" instanceDir="core0" />
- <core name="core1" instanceDir="core1" />
- <core name="collection1" instanceDir="collection1" />
- <core name="solrtest" instanceDir="solrtest"/>
- </cores>
- </solr>
复制代码 name="",是库的名字,instanceDir="",是目录 每个目录下包含两个文件夹conf和data,data下有两个文件夹index和tlog,index是存放生成的索引文件,tlog存放log, conf下是必要的配置文件schema.xml和solrconfig.xml,可以参考官方或者core里面的配置文件: - <?xml version="1.0" ?>
- <schema name="example solr test" version="1.1">
- <types>
- <fieldtype name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
- <fieldType name="long" class="solr.TrieLongField" precisionStep="0" positionIncrementGap="0"/>
- </types>
- <fields>
- <!-- general -->
- <field name="id" type="string" indexed="true" stored="true" multiValued="false" required="true"/>
- <field name="type" type="string" indexed="true" stored="true" multiValued="false" />
- <field name="name" type="string" indexed="true" stored="true" multiValued="false" />
- <field name="_version_" type="long" indexed="true" stored="true"/>
- </fields>
- <!-- field to use to determine and enforce document uniqueness. -->
- <uniqueKey>id</uniqueKey>
- <!-- field for the QueryParser to use when an explicit fieldname is absent -->
- <defaultSearchField>name</defaultSearchField>
- <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
- <solrQueryParser defaultOperator="OR"/>
- </schema>
复制代码 solrconfig.xml我还不是很懂,在这里就不讲了,但是必须配置(好像是必须配): - <?xml version="1.0" encoding="UTF-8" ?>
- <!-- 可以从core文件中copy过来 -->
- <config>
- <luceneMatchVersion>LUCENE_40</luceneMatchVersion>
-
- <directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.StandardDirectoryFactory}"/>
- <dataDir>${solr.solrtest.data.dir:}</dataDir> 这里solr.后面名字改掉
- <updateHandler class="solr.DirectUpdateHandler2">
- <updateLog>
- <str name="dir">${solr.solrtest.data.dir:}</str>
- </updateLog>
- </updateHandler>
- <requestHandler name="/get" class="solr.RealTimeGetHandler">
- <lst name="defaults">
- <str name="omitHeader">true</str>
- </lst>
- </requestHandler>
-
- <requestHandler name="/replication" class="solr.ReplicationHandler" startup="lazy" />
- <requestDispatcher handleSelect="true" >
- <requestParsers enableRemoteStreaming="false" multipartUploadLimitInKB="2048" />
- </requestDispatcher>
-
- <requestHandler name="standard" class="solr.StandardRequestHandler" default="true" />
- <requestHandler name="/analysis/field" startup="lazy" class="solr.FieldAnalysisRequestHandler" />
- <requestHandler name="/update" class="solr.UpdateRequestHandler" />
- <requestHandler name="/admin/" class="org.apache.solr.handler.admin.AdminHandlers" />
- <requestHandler name="/admin/ping" class="solr.PingRequestHandler">
- <lst name="invariants">
- <str name="q">solrpingquery</str>
- </lst>
- <lst name="defaults">
- <str name="echoParams">all</str>
- </lst>
- </requestHandler>
- <!-- config for the admin interface -->
- <admin>
- <defaultQuery>solr</defaultQuery>
- </admin>
- </config>
复制代码 之后在exampledocs目录下手动创建一个solr1.xml文件: - <?xml version="1.0" ?>
- <add>
- <doc>
- <field name="id">solr1</field>
- <field name="type">type1</field>
- <field name="name">my solr test</field>
- </doc>
- </add>
复制代码 跟schema.xml中的字段对应,好了现在可以提交数据了,这里在window命令窗口提交数据,把E:\apache-solr- 4.0.0\example\exampledocs下的post.jar复制到 E:\SolrHome\exampledocs下 打开命令窗口CD 到E:\SolrHome\exampledocs下使用命令,Tomcat不要忘了开: java -Durl=http://localhost:8080/solr/solrtest/update -Ddata=files -jar post.jar solr1.xml 如图成功的添加的索引,看下E:\SolrHome\solrtest\data\index下的文件:
进入solr页面: 点击Executu Query查询,右侧生成了地址可以打开查看,到此结束。
Debug视图 认识debug视图,红色部分框为线程堆栈视图,黄色部分框为表达式、断点、变量视图,蓝色部分为代码视图。
线程堆栈视图 分别介绍一下这几个按钮的含义: 1.表示当前实现继续运行直到下一个断点,快捷键为F8。 2.表示打断整个进程 3.表示进入当前方法,快捷键为F5。 4.表示运行下一行代码,快捷键为F6。 5.表示退出当前方法,返回到调用层,快捷键为F7。 6.表示当前线程的堆栈,从中可以看出在运行哪些代码,并且整个调用过程,以及代码行号
变量视图 1.为变量名视图,显示当前代码行中所有可以访问的实例变量和局部变量 2.显示所有的变量值 3.可以通过该窗口来改变变量值
断点视图 1.显示所有断点 2. 将当前窗口1中选中的端口失效,再次点击启用。 3.异常断点
表达式视图 表达式视图:表达式视图是Debug过程中较为常用的一个视图,可以对自己的感兴趣的一些变量进行观察,也可以增加一些自己的表达式,也可以查看一行代码的运行结果。 1.表达式 2. 点击此可以新增一个表达式
代码视图 代码视图:用来显示具体的代码。其中绿色部分是指当前将要执行的代码
场景一:小明辛苦了两天终于将自己的负责的任务完成了,第二天转测后,测试找到了小明说,小明的程序有bug,可以是小明经过仔细调试,发现本地没有任何问题,但是测试的环境上确实有问题,所以小明遇到了难题,测试的环境linux,又不能上去linux去debug,小明这个时候想要是Linux也可以debug就好了. 远程debug 远程debug:远程debug顾名思义,能够将远程操作系统上的任何java进行debug,但是有前提是本地需要有同步的代码。 1.远程debug的步骤是在远程操作系统上启动java进程时增加特殊的 -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=n 2.在Eclipse中新建一个Remote Java Application 远程debug 1.打开Debug Configurations视图 2.右击Remote Java Application, New 3.选择源码工程 4.输入远程IP和端口,端口即服务 端的$DEBUG_PORT,点击OK。
场 景一:小明写了一个任务执行者,该执行者不间断的执行一些任务,在现网上运行了一段时间后,发现有概率的出现一些故障,发现运行一段时间后,该任务者异常 退出了,退出的因为是空指针,可以小明想要在本地debug,不知道断点打在哪里,该问题是概率事件,不一定会出现,所以小明debug几遍下来后,头晕 眼花,连代码都看不清楚了,小明想要是能有个断点每当出现空指针异常的时候就停下来让他发现问题,那该多好呀。 异常断点 异常断点:在定位问题的过程中,常常会遇到断点无法打到合适的位置,以至于和问题的触发点千差万别,所以这个时候不妨试试异常断点,顾名思义,异常断点是指抛出某种异常后自动挂起的断点。 点击红色部位,增加一个异常断点
输入想要定位的异常类型,例如NullPointerException,这样系统中抛出任何NullPointerException异常后,都会挂起当前线程,给你机会去定位问题。 场景一:小明写了一个巨大的循环,在调测代码时,小明发现每当循环到第100000次的时候,就是出现问题,没有达到自己的预期,于是小明在循环里打了个断点,想看看到底怎么回事,可小明万万没有想到,想要到达100000次循环是多么的困难,小明这个时候已经开始浮想联翩,如果能有这样的断点: If 循环次数== 100000,线程停下来 条件断点 如右图,循环1000次,如果想要在循环到500 次的时候停下来,可以创建一个条件断点,右 击断点悬着Breakpoint Properties。
选中Enable Condition 在空白处,添加你自己的条件,如果条件返回true,线程会被挂起,如果为false,则忽略该异常 Hit Count为该断点经过多少次后,正式挂起线程,如果设置为500,则表达前499次,经过该断点都不会停下,当第500次,该断点会挂起当前线程。
表达式 表达式可以查看一些在当前代码中没有的命令行,方便定位问题。 场景一:小明最近遇到一个难题,在调用一个第三方插件时总是会有问题,小明怀疑是第三方插件的bug,但小明没有找到源码不能进行debug,小明该怎么办呢? Debug定位第三方插件的问题 1.使用反编译工具将代码反编译 2.将反编译后的源码进行过滤 3.修复源码编译错误 4.进行debug Debug一些经验 1.尽量减少debug,少用debug,优秀的程序员总是花80%的时间来思考如何解决问题,20%的时间来动手完成代码,而糟糕的程序员总是用20%的时间去写代码,80%的时间去调试代码,动手之前尽量想好如何去做,并且已经为你自己的思路做了充分的实验。 2.尽可能的提高debug的效率,设置合适的断点,使用快捷键。 3.debug的F6快捷键经常用到,它与金山词霸的快捷键冲突,所以在debug的时候最好将金山词霸关掉。 4.debug的表达式是可执行代码,将会对代码结果产生永久性影响,在调试时注意,经常将不用的表达式清除掉。 原文出自: http://mgoann.iteye.com/blog/1396637
1 import java.io.File; 2 3 public class FileTest { 4 5 public static void main(String[] args) throws Exception { 6 7 System.out.println(Thread.currentThread().getContextClassLoader() 8 .getResource("")); 9 10 System.out.println(FileTest.class.getClassLoader().getResource("")); 11 12 System.out.println(ClassLoader.getSystemResource("")); 13 14 System.out.println(FileTest.class.getResource("")); 15 16 System.out.println(FileTest.class.getResource("/")); 17 18 // Class文件所在路径 19 20 System.out.println(new File("/").getAbsolutePath()); 21 22 System.out.println(System.getProperty("user.dir")); 23 24 System.out.println(System.getProperty("file.encoding")); 25 26 27 } 28 29 }
当我们启动一个tomcat的服务的时候,jar包和claess文件是是以怎么样的顺序被加载进来的? 加载顺序: 1. $java_home/lib 目录下的java核心api 2. $java_home/lib/ext 目录下的java扩展jar包 3. java -classpath/-Djava.class.path所指的目录下的类与jar包 4. $CATALINA_HOME/common目录下按照文件夹的顺序从上往下依次加载 5. $CATALINA_HOME/server目录下按照文件夹的顺序从上往下依次加载 6. $CATALINA_BASE/shared目录下按照文件夹的顺序从上往下依次加载 7. 我们的项目路径/WEB-INF/classes下的class文件 8. 我们的项目路径/WEB-INF/lib下的jar文件 在同一个文件夹下,jar包是按顺序从上到下依次加载 由ClassLoader的双亲委托模式加载机制我们可以知道,假设两个包名和类名完全相同的class文件不再同一个jar包,如果一个class文件已经被加载java虚拟机里了,那么后面的相同的class文件就不会被加载了。 原文 : http://xiaomogui.iteye.com/blog/847686
多站点共享Session常见的作法有: - 使用.net自动的状态服务(Asp.net State Service);
- 使用.net的Session数据库;
- 使用Memcached。
- 使用Cookie方式实现多个站点间的共享(这种方式只限于几个站点都在同一域名的情况下);
这里我们就 演练一下 以数据库的形来存储Session,来实现多站点共享Session。 首先我们 建好一下站点,如下图: Default.aspx 其中 有二个Button ,SetSession 主要是用于给一个 Session 赋值(如:Session["ShareValue"] = “abcd” ) , GetSession 主要就是获得 一个 Session 值。 具体代码如下: 代码部分就这么多就行了… 下面就是要配置一下 Web.config了 , 其实主要就是在 <system.web> 这个节点中 增加 machineKey 及 sessionState 这两个节点, 1.增加machineKey 主要作用是: “按照MSDN的标准说法:“对密钥进行配置,以便将其用于对 Forms 身份验证 Cookie 数据和视图状态数据进行加密和解密,并将其用于对进程外会话状态标识进行验证。”也就是说Asp.Net的很多加密,都是依赖于machineKey里面 的值,例如Forms 身份验证 Cookie、ViewState的加密。默认情况下,Asp.Net的配置是自己动态生成,如果单台服务器当然没问题,但是如果多台服务器负载均 衡,machineKey还采用动态生成的方式,每台服务器上的machinekey值不一致,就导致加密出来的结果也不一致,不能共享验证和 ViewState,所以对于多台服务器负载均衡的情况,一定要在每台站点配置相同的machineKey。“ ,具体可以查一下其它资料。 2.增加 sessionState 主要是让 Session 保存在数据库中。 具体配置如下: <machineKey validationKey="86B6275BA31D3D713E41388692FCA68F7D20269411345AA1C17A7386DACC9C46E7CE5F97F556F3CF0A07159659E2706B77731779D2DA4B53BC47BFFD4FD48A54" decryptionKey="9421E53E196BB56DB11B9C25197A2AD470638EFBC604AC74CD29DBBCF79D6046" validation="SHA1" decryption="AES"/> <sessionState mode="SQLServer" sqlConnectionString="Data Source=PC-07195;Initial Catalog=AWBUISession;Persist Security Info=True;User ID=jins;Password=js@#$1234" allowCustomSqlDatabase="true" cookieless="false" timeout="100"/> 网站部分 这样就好了。。。 下面就是要配置据库了….. 数据库配置: 使用aspnet_regsql.exe工具 ASP.NET 2.0版本后微软提供了aspnet_regsql.exe工具可以方便的配置Session数据库.该工具位于 Web 服务器上的"系统根目录\Microsoft.NET\Framework\版本号"文件夹中. 使用举例:
aspnet_regsql.exe -S . -U sa -P 123456 -ssadd -sstype p -S参数: 表示数据库实例名称. 可以用"."表示本机. -U和-P参数: 表示用户名和密码. -E参数: 可以再-U –P 与 -E中选择一组. –E表示以当前系统用户通过windows身份验证登录数据库, -U -P则是使用SqlServer用户登录数据库. -ssadd / –ssremove 参数: -ssadd表示是添加Session数据库, -ssremove表示移除Session数据库. sstype 参数说明: t | 将会话数据存储到 SQL Server tempdb 数据库中。这是默认设置。如果将会话数据存储到 tempdb 数据库中,则在重新启动 SQL Server 时将丢失会话数据。 | p | 将会话数据存储到 ASPState 数据库中,而不是存储到 tempdb 数据库中。 | c | 将会话数据存储到自定义数据库中。如果指定 c 选项,则还必须使用 -d 选项包括自定义数据库的名称。 | 我的设置是:aspnet_regsql.exe -S . - E -d AWBUISession -ssadd -sstype c 好了。基本的 我们就已经搞定了。。
现在 我们分别把我们刚建的一个网站 部署 到 IIS 中。不过我们既然要负载。至少也的部署两份 我们把 其中一个 服务器中的 defaut.aspx 中 “服务器 1” 改成 “服务器 2” ,这样做的主要目地是 做一下 区别! 具体如下: 两个网站的 URL分别是: server 1:127.0.0.1:8081; server 2:127.0.0.1:8080; OK。下面我们就是 配置 Nignx了。 首先 在 nginx\conf 配置 文件中找到 nginx.conf 这个文件 ,就记事本打开, 做如上的 设置: OK。 nginx 这样配置 就算OK 了。 我们启动一下 nginx .. 在浏览器中 输入我们 在 nginx 中配置的 URL 如:127.0.0.1:8090
我们会看到 服务器 1 已经开始为我们服务了,我们再点一下 “SetSession”来设置一下一个 会话值, 我们会看到 服务器 2 开始 工作。这时我们再点一下 “GetSesion”看一下 刚才在 服务器 1 设置 的会话值,结果如下 : 出现这种情况 ,主要就是在数据库中存储 一个会话时 没有做到 服务器1 和服务2的Session 共享,主要是 在ASPStateTempSessions 这个表中的 一个SessionID,其中的SessionId包括两个部分:网站生成的24位SessionID及8位AppName对于不同的站点,其AppName不同,在能够在不同站点下使24位SessionID相同的情况下,要保证经过组合加上AppName后的SessionID相同,可以通过修改存储过程TempGetAppID,使其得到的SessionID与AppName无关,修改TempGetAppID如下: ALTER PROCEDURE [dbo].[TempGetAppID] @appName tAppName, @appId int OUTPUT AS SET @appName = 'Test' --LOWER(@appName) 修改这里,使多个站点的APPname ,为一个固定值。 SET @appId = NULL SELECT @appId = AppId FROM [AWBUISession].dbo.ASPStateTempApplications WHERE AppName = @appName IF @appId IS NULL BEGIN BEGIN TRAN SELECT @appId = AppId FROM [AWBUISession].dbo.ASPStateTempApplications WITH (TABLOCKX) WHERE AppName = @appName IF @appId IS NULL BEGIN EXEC GetHashCode @appName, @appId OUTPUT INSERT [AWBUISession].dbo.ASPStateTempApplications VALUES (@appId, @appName) IF @@ERROR = 2627 BEGIN DECLARE @dupApp tAppName SELECT @dupApp = RTRIM(AppName) FROM [AWBUISession].dbo.ASPStateTempApplications WHERE AppId = @appId RAISERROR('SQL session state fatal error: hash-code collision between applications ''%s'' and ''%s''. Please rename the 1st application to resolve the problem.', 18, 1, @appName, @dupApp) END END COMMIT END RETURN 0
经过以上修改之后,下面要实现多个站点共用同一个SessionID. 重启一下各站点。再在浏览一下网站点 “SetSession”, 再点:“GetSession” 这样 我们就看到 服务器2 给出了我们 刚才在 服务器 1 中设置 的会话值了。 我们 再点:“GetSession”,可以看到 服务器1 和服务器 2 返回的是相同的结果,达到了 “多站点共享Session” 附加一点: Session 过期删除,主要是 在 SQL server 代理中的作业完成,具体的可以,查一下其它相关资料. 原文出自: http://www.cnblogs.com/red-fox/archive/2012/11/05/2755271.html
摘要: 1.加载JDBC驱动程序 Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 import java.sql.DriverManager;  ... 阅读全文
Apache Shiro 架构 ApacheShiro的设计目标是使程序的安全变得简单直观而易于实现,shiro的核心设计参照大多数用户对安全的思考模式--如何对某人(或某事)在与程序交互的环境中的进行安全控制。 程序设计通常都以用户为基础,换句话说,你经常以用户可以(或者应该)如何与软件交互为基础来设计用户接口或者服务API,例如,你可能说,“如果当前与我程序交互的用户已经登录了,我将展示一个按钮给他,他可以点击去查看自己的账户住处,如果他们没有登录,我将显示一个注册按钮。” 这个陈述例子指出我们开发程序很大程度上是为了满足用户的需求,即使“用户(User)”是另外一个软件系统而并非一个人,你仍然要写代码对当前与你软件交互的谁(或者什么)的动作进行回应。 shiro从它的设计中表现了这种理念,为了与软件开发者的直觉相配合,Apache Shiro在几乎所有程序中保留了直观和易用的特性。 概览 在概念层,shiro架构包含三个主要的理念:Subject,SecurityManager和Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。 subject:就像我们在上一章示例中提到的那样,subject本质上是当前运行用户特定的'view',而单词“user”经常暗指一个人,subject可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它--当前和软件交互的任何事件。 subject实例都和(也需要)一个SecurityManager绑定,当你和一个subject进行交互,这些交互动作被转换成SecurityManager下subject特定的交互动作。 SecurityManager:SecurityManager是Shiro架构的核心,配合内部安全组件共同组成安全伞。然而,一旦一个程序配置好了SecurityManager和它的内部对象,SecurityManager通常独自留下来,程序开发人员几乎花费的所有时间都集中在Subjet API上。 我们将在以后详细讨论SecurityManager,但当你和一个Subject互动时了解它是很重要的。任何Subject的安全操作中SecurityManager是幕后真正的举重者,这在上面的图表中可以反映出来。 Realms:Reamls是Shiro和你的程序安全数据之间的“桥”或者“连接”,它用来实际和安全相关的数据如用户执行身份认证(登录)的帐号和授权(访问控制)进行交互,shiro从一个或多个程序配置的Realm中查找这些东西。 Realm本质上是一个特定的安全DAO:它封装与数据源连接的细节,得到shiro所需的相关的数据。在配置shiro的时候,你必须指定至少一个Realm来实现认证(authentication)和/或授权(authorization)。SecurityManager可以配置多个复杂的Realm,但是至少有一个是需要的。 Shiro提供out-of-the-box Realms来连接安全数据源(或叫地址)如LDAP、JDBC、文件配置如INI和属性文件等,如果已有的Realm不能满足你的需求你也可以开发自己的Realm实现。 和其它内部组件一样,ShiroSecurityManager管理如何使用Realms获取Subject实例所代表的安全和身份信息。 详细架构 下面的图表展示了Shiro的核心架构思想,下面有简单的解释。 Subject (org.apache.shiro.subject.Subject) 正在与软件交互的一个特定的实体“view”(用户、第三方服务、时钟守护任务等)。 SecurityManager(org.apache.shiro.mgt.SecurityManager) 如同上面提到的,SecurityManager 是Shiro的核心,它基本上就是一把“伞”用来协调它管理的组件使之平稳地一起工作,它也管理着Shiro中每一个程序用户的视图,所以它知道每个用户如何执行安全操作。 Authenticator(org.apache.shiro.authc.Authenticator) Authenticator是一个组件,负责执行和反馈用户的认证(登录),如果一个用户尝试登录,Authenticator就开始执行。Authenticator知道如何协调一个或多个保存有相关用户/帐号信息的Realm,从这些Realm中获取这些数据来验证用户的身份以确保用户确实是其表述的那个人。 Authentication Strategy(org.apache.shiro.authc.pam.AuthenticationStrategy) 如果配置了多个Realm,AuthenticationStrategy将会协调Realm确定在一个身份验证成功或失败的条件(例如,如果在一个方面验证成功了但其他失败了,这次尝试是成功的吗?是不是需要所有方面的验证都成功?还是只需要第一个?) Authorizer(org.apache.shiro.authz.Authorizer) Authorizer是负责程序中用户访问控制的组件,它是最终判断一个用户是否允许做某件事的途径,像Authenticator一样,Authorizer也知道如何通过协调多种后台数据源来访问角色和权限信息,Authorizer利用这些信息来准确判断一个用户是否可以执行给定的动作。 SessionManager(org.apache.shiro.session.mgt.SessionManager) SessionManager知道如何创建并管理用户Session生命周期而在所有环境中为用户提供一个强有力的Session体验。这在安全框架领域是独一无二--Shiro具备管理在任何环境下管理用户Session的能力,即使没有Web/Servlet或者EJB容器。默认情况下,Shiro将使用现有的session(如Servlet Container),但如果环境中没有,比如在一个独立的程序或非web环境中,它将使用它自己建立的session提供相同的作用,sessionDAO用来使用任何数据源使session持久化。 SessionDAO(org.apache.shiro.session.mgt.eis.SessionDAO) SessionDAO代表SessionManager执行Session持久(CRUD)动作,它允许任何存储的数据挂接到session管理基础上。 CacheManager(org.apache.shiro.cache.CacheManager) CacheManager创建并管理其它shiro组件的catch实例生命周期,因为shiro要访问许多后端数据源来实现认证、授权和session管理,caching已经成为提升性能的一流的框架特征,任何一个现在开源的和/或企业级的caching产品都可以插入到shiro中实现一个快速而有效的用户体验。 Cryptography (org.apache.shiro.crypto.*) Cryptography在安全框架中是一个自然的附加产物,shiro的crypto包包含了易用且易懂的加密方式,Hashes(亦即digests)和不同的编码实现。该包里所有的类都亦于理解和使用,曾经用过Java自身的加密支持的人都知道那是一个具有挑战性的工作,而shiro的加密API简化了java复杂的工作方式,将加密变得易用。 Realms (org.apache.shiro.realm.Realm) 如同上面提到的,Realm是shiro和你的应用程序安全数据之间的“桥”或“连接”,当实际要与安全相关的数据进行交互如用户执行身份认证(登录)和授权验证(访问控制)时,shiro从程序配置的一个或多个Realm中查找这些数据,你需要配置多少个Realm便可配置多少个Realm(通常一个数据源一个),shiro将会在认证和授权中协调它们。 SecurityManager 因为shiro API鼓励以Subject为中心的开发方式,大部分开发人员将很少会和SecurityManager直接交互(尽管框架开发人员也许发现它非常有用),尽管如此,知道SecurityManager如何工作,特别是当在一个程序中进行配置的时候,是非常重要的。 设计 如前所述,程序中SecurityManager执行操作并且管理所有程序用户的状态,在shiro基础的SecurityManager实现中,包含以下内容: 认证(Authentication) 授权(Authorization) 会话管理(Session Management) 缓存管理(Cache Management) Realm协调(Realm coordination) 事件传导(Event propagation ) "RememberMe" 服务("Remember Me" Services) 建立Subject(Subject creation) 退出登录(Logout) 及其它。 但这些功能都在一个单独的组件中管理,并且,当所有功能集中在一个类中实现是灵活和可定制是非常困难的。 为了实现配置的简单、灵活、可插拔,shiro在设计时实现了高模块化--尽管模块化,SecurityManager(包括它的继承类)并没有做到,相反地,SecurityManager实现更像一个轻量级的‘容器(container)’,代表几乎所有嵌套/封装组件的行为,这种‘封装(wrapper)’设计在上面的架构图表中已有反映。 当组件执行逻辑的时候,SecurityManager知道如何以及何时去协调组件做出正确的动作。 SecurityManager和JavaBean兼容,这允许你(或者配置途径)通过标准的JavaBean访问/设置方法(get*/set*)很容易地定制插件,这意味着shiro模块可以根据用户行为转化成简易的配置。 简易的配置 因为适合JavaBean,任何支持Javabean配置的组件都有非常简单的途径配置SecurityManager,如Spring、Guice、JBoss,等等。 我们将在下一节讨论配置(Configuration ) 为文档加把手 我们希望这篇文档可以帮助你使用Apache Shiro进行工作,社区一直在不断地完善和扩展文档,如果你希望帮助shiro项目,请在你认为需要的地方考虑更正、扩展或添加文档,你提供的任何点滴帮助都将扩充社区并且提升Shiro。 提供你的文档的最简单的途径是将它发送到用户论坛(http://shiro-user.582556.n2.nabble.com/)或邮件列表(http://shiro.apache.org/mailing-lists.html) 原文地址:http://shiro.apache.org/architecture.html
摘要: 1.上下文Context、面向切面编程AOP模型分析 在本人的“.NET面向上下文、AOP架构模式(概述)” 一文中,我们大概了解了上下文如何辅助对象在运行时的管理。在很多时候我们急需在运行时能把对象控制在一定的逻辑范围内,在必要的时候能让他们体现出集中 化的概念,如人群、车辆、动物等等。而Context与AOP有着密切的联系,Context表示逻辑抽象的范围而AOP描述了... 阅读全文
摘要: 前段时间一直在学习和研究.NET事务处理,慢慢的我发现可以使用事务处理来实现一种可逆的系统框架。这种框架在一些IT社区似乎还没有见过,但是在我们日常开发中确实有这个需求。所以我花了点时间深入的研究了一下事务的原理和使用,实现了以事务为纽带,以资源为操作对象的可逆框架。 这里我假设您对事务有了整体的认识,也对自定义事务管理器有过了解。[王清培版权所有,转载请给出署名] (可以参考本人的:.NET简谈... 阅读全文
1. 上下文概述 上下文:其实就是一个逻辑上的业务、功能区域。在这个逻辑区域里可以有效的进行管理,算是一种制度的约束,也可以理解为某种范围类的数据共享。 其实在很多应用框架中到处可以看见上下文的概念,包括.NET本身的设计就建立在这种思想上的。实例化的对象默认存在于系统中的默认上下文中,我们可以构建自己的上下文将对象在运行时进行合理的管理。 在ASP.NET框架中比较经典的就是HttpContext上下文对象。所有的运行时对象都会逻辑归属到HttpContext上下文中来,如:我们可以使用Request、Response等对象访问HTTP处理的生命周期数据。 在Remoting中跨AppDomin访问也是建立在上下文基础上的,请求的消息通过隧道后序列化到达调用发。王清培版权所有,转载请给出署名 在这些强大的应用框架背后总有着让人难以琢磨的设计秘方,诸多的设计原则、设计模式、丰富的实践经验都将是框架稳定运行的基石。Context算是一个比较完美的逻辑范围设计模式。[王清培版权所有,转载请给出署名] 那么就让我们来领略一下上下文的奥秘吧! 2. 上下文的一般应用 上下文的设计思想绝对的美妙,很多地方一旦进行上下文抽象就能解决很多问题。比如在Remoting中我们可以动态的在上下文中加入很 多扩展对上下文中的所有对象进行强制管理,比如:调用某一个方法我们需要进行安全检查,我们可以编写一个满足自己当前项目需求的安全认证插件动态的注入到 上下文管理器区域中,在这个地方就体现出上下文的设计优势。 在Web编程中,由于它有着与Winfrom编程很大的差异性,需要将同一组对象同时服务于N个客户端进行使用,而在Winfrom中基本上都是属于单线程的,当然可以手动的开启多线程并行操作。对于ASP.NET每当有新的请求处理时,框架会自动开启新的线程去处理当前的调用,然后这个时候就是需要一个相对于之前操作的独立上下文数据环境,而不是在同一个服务器上的所有线程都是共享的。王清培版权所有,转载请给出署名 那么我们就需要将当前的HTTP处理的相关数据纳入到一个逻辑的上下文进行管理和数据共享。 这么多的优势存在,看来我们是有必要尝试一下这中设计模式了。那么就目前系统开发框架而言我们的上下文能用在哪里呢?我想当务之急就是将分层架构中的所有单条线上的对象进行上下文管理。[王清培版权所有,转载请给出署名] 典型的三层架构: 在一般的三层架构开发过程中我们的调用关系基本都是这样的,利用上下文设计模式我们可以将本来鼓励的对象进行合理的管理。上图中User对象线将是属于User上下文的,Order对象线将是属于Order上下文的。大家互不干扰,可以在这个逻辑上下文中共享数据、设置调用安全策略、设计日志记录方式、甚至可以计算每个方法的性能。 BLL的调用代码: - View Code
- /***
- * author:深度训练
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Reflection;
-
- namespace ConsoleApplication1.BLL
- {
- [ContextModule.ContextEveningBound(IsEvening = true)]
- public class BLL_Order : ContextModule.ContextModuleBaseObject<BLL_Order>
- {
- DAL.DAL_Order dal_order = new DAL.DAL_Order();
-
- [ContextModule.ContextExceptionHandler(OperationSort = 1)]
- public Model.Model_Order InsertOrderSingle(Model.Model_Order ordermodel)
- {
- return ContextModule.ContextAction.PostMethod<DAL.DAL_Order, Model.Model_Order>(
- dal_order, dal_order.GetMethodInfo("InsertOrderSingle"), ordermodel);
- }
- [ContextModule.ContextExceptionHandler(OperationSort = 1)]
- public void SendOrder(Model.Model_Order ordermodel)
- {
- ContextModule.ContextAction.PostMethod<DAL.DAL_Order, object>(
- dal_order, dal_order.GetMethodInfo("SendOrder"), ordermodel);
- }
- }
- }
DAL的执行代码: - View Code
- /***
- * author:深度训练
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
-
- namespace ConsoleApplication1.DAL
- {
- [ContextModule.ContextEveningBound(IsEvening = true)]
- public class DAL_Order : ContextModule.ContextModuleBaseObject<DAL_Order>
- {
- [ContextModule.ContextLogHandler(OperationSort = 1)]
- [ContextModule.ContextSecurityHanlder(OperationSort = 2)]
- public Model.Model_Order InsertOrderSingle(Model.Model_Order ordermodel)
- {
- return new Model.Model_Order() { OrderGuid = Guid.NewGuid(), OrderTime = DateTime.Now };
- }
- [ContextModule.ContextLogHandler(OperationSort = 1)]
- public void SendOrder(Model.Model_Order ordermodel)
- {
- Console.WriteLine("订单发送成功!");
- }
- }
- }
上述代码是我模拟一个上下文的执行过程。 3. 上下文共享区域 在每个独立的上下文环境中应该有一片共享的数据存储区域,以备多个上下文对象访问。这种方便性多半存在于项目比较紧张的修改需求的时候或者加新业务的时候扩展方法用的。 BLL调用代码: - View Code
- [ContextModule.ContextExceptionHandler(OperationSort = 1)]
- public void UpdateOrderSingle()
- {
- Model.Model_Order ordermodel = new Model.Model_Order() { OrderGuid = Guid.NewGuid(), OrderTime = DateTime.Now };
- //放入上下文共享对象池
- ContextModule.ContextRuntime.CurrentContextRuntime.SetValue("updateorder", ordermodel);
- ContextModule.ContextAction.PostMethod<DAL.DAL_Order, object>(
- dal_order, dal_order.GetMethodInfo("UpdateOrderSingle"), null);
- }
DAL执行代码:DAL执行代码:DAL执行代码:DAL执行代码: - [ContextModule.ContextLogHandler(OperationSort = 1)]
- public void UpdateOrderSingle()
- {
- Model.Model_Order ordermodel =
- ContextModule.ContextRuntime.CurrentContextRuntime.GetValue("updateorder") as Model.Model_Order;
- }
4. 上下文运行时环境 对于上下文运行时环境的构建需要考虑到运行时是共享的上下文对象。对于纳入上下文管理的所有对象都需要共享或者说是受控于上下文运行时。 上下文构建: - /***
- * author:深度训练
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
-
- namespace ContextModule
- {
- /// <summary>
- /// 上下文运行时环境。
- /// 上下文逻辑运行时环境,环境中的功能都是可以通过附加进来的。
- /// </summary>
- public class ContextRuntime : IDisposable
- {
- #region IDisposable成员
- void IDisposable.Dispose()
- {
- _currentContextRuntime = null;
- }
- #endregion
-
- protected ContextRuntime() { }
- private DateTime _initTime = DateTime.Now;
- /// <summary>
- /// 获取运行时创建上下文的时间
- /// </summary>
- public virtual DateTime InitTime { get { return _initTime; } }
- private Dictionary<object, object> _runTimeResource = new Dictionary<object, object>();
- private ContextFilterHandlerMap _filterMap = new ContextFilterHandlerMap();
- /// <summary>
- /// 获取上下文中的方法、类过滤器映射表
- /// </summary>
- public ContextFilterHandlerMap FilterMap { get { return _filterMap; } }
- private Guid _initPrimaryKey = Guid.NewGuid();
- /// <summary>
- /// 获取运行时创建上下文的唯一标识
- /// </summary>
- public virtual Guid InitPrimaryKey { get { return _initPrimaryKey; } }
- /// <summary>
- /// 获取上下文共享区域中的数据
- /// </summary>
- /// <param name="key">数据Key</param>
- /// <returns>object数据对象</returns>
- public virtual object GetValue(object key)
- {
- return _runTimeResource[key];
- }
- /// <summary>
- /// 设置上下文共享区域中的数据
- /// </summary>
- /// <param name="key">数据Key</param>
- /// <param name="value">要设置的数据对象</param>
- public virtual void SetValue(object key, object value)
- {
- _runTimeResource[key] = value;
- }
-
- [ThreadStatic]
- private static ContextRuntime _currentContextRuntime;
- /// <summary>
- /// 获取当前上下文运行时对象.
- /// </summary>
- public static ContextRuntime CurrentContextRuntime { get { return _currentContextRuntime; } }
- /// <summary>
- /// 开始运行时上下文
- /// </summary>
- /// <returns>ContextRuntime</returns>
- public static ContextRuntime BeginContextRuntime()
- {
- //可以通过配置文件配置上下文运行时环境的参数。这里只是实现简单的模拟。
- _currentContextRuntime = new ContextRuntime();
- return _currentContextRuntime;
- }
- }
- }
对于上下文的入口构建: - //开启上下文
- using (ContextModule.ContextRuntime.BeginContextRuntime())
- {
-
- }
通过Using的方式我们开始上下文生命周期。 5. 上下文活动对象 上下文对象的绑定需要延后,不能在对象的构建时就创建上下文。 使用后期绑定动态的切入到执行的上下文中。 调用代码,上下文入口: - /***
- * author:深度训练
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Data;
- using ConsoleApplication1.BLL;
- using ConsoleApplication1.Model;
-
- namespace ConsoleApplication1
- {
- public class Program
- {
- public static void Main(string[] args)
- {
- BLL.BLL_Order order = new BLL.BLL_Order();
- using (ContextModule.ContextRuntime.BeginContextRuntime())
- {
- Model.Model_Order ordermodel = new Model_Order() { OrderGuid = Guid.NewGuid(), OrderTime = DateTime.Now };
- Model.Model_Order resultmodel = ContextModule.ContextAction.PostMethod<BLL.BLL_Order, Model.Model_Order>(order, order.GetMethodInfo("InsertOrderSingle"), ordermodel);
- ContextModule.ContextAction.PostMethod<BLL.BLL_Order, object>(order, order.GetMethodInfo("SendOrder"), ordermodel);
- }
-
- }
- }
- }
6. 上下文在分层架构中的运用 有了上下文的核心原型之后我们可以扩展到分层架构中来,对于分层架构的使用其实很有必要,一般的大型业务系统都是混合的使用模式,可能有C/S、B/S、Mobile终端等等。 对于加入Service层之后BLL、DAL将位于服务之后,对于来自客户端的调用需要经过一些列的身份验证及权限授予。有了WCF之后面向SOA的架构开发变的相对容易点,对安全、性能、负载等等都很完美,所以大部分的情况下我们很少需要控制BLL、DAL的执行运行。 那么没有使用WCF构建分布式的系统时或者是没有分布式的需求就是直接的调用,如WEB的一般开发,从UI到BLL到DAL。或者是普通的Winfrom的项目、控制台项目属于内网的使用,可能就需要控制到代码的执行。 下面我通过演示一个具体的实例来看看到底效果如何。 我以控制台的程序作为演示项目类型,也使用简单的三层架构。 这个再简单不过了吧,为了演示越简单越好,关键是突出重点。 需求: 在DAL对象里面加入一个插入Order实体对象的方法: - /***
- * author:深度训练
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
-
- namespace ConsoleApplication1.DAL
- {
- [ContextModule.ContextEveningBound(IsEvening = true)]
- public class DAL_Order : ContextModule.ContextModuleBaseObject<DAL_Order>
- {
- [ContextModule.ContextLogHandler(OperationSort = 1)]
- [ContextModule.ContextSecurityHanlder(OperationSort = 2)]
- public Model.Model_Order InsertOrderSingle(Model.Model_Order ordermodel)
- {
- return new Model.Model_Order() { OrderGuid = Guid.NewGuid(), OrderTime = DateTime.Now };
- }
- }
- }
在这个类的上面有一个特性ContextEveningBound,该是用来表示当前对象属于后期绑定到上下文的对象。同时该类也继承自一个ContextModuleBaseObject<DAL_Order>泛型类,主要作用是将对象强制的绑定到上下文进行管理。 在方法InsertOrderSingle上面有两个特性,ContextLogHandler是用来记录方法的执行日志,ContextSecurityHanlder是用来在方法执行的过程中强制要求管理员认证。 BLL对象代码: - /***
- * author:深度训练
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Reflection;
-
- namespace ConsoleApplication1.BLL
- {
- [ContextModule.ContextEveningBound(IsEvening = true)]
- public class BLL_Order : ContextModule.ContextModuleBaseObject<BLL_Order>
- {
- DAL.DAL_Order dal_order = new DAL.DAL_Order();
-
- [ContextModule.ContextExceptionHandler(OperationSort = 1)]
- public Model.Model_Order InsertOrderSingle(Model.Model_Order ordermodel)
- {
- return ContextModule.ContextAction.PostMethod<DAL.DAL_Order, Model.Model_Order>(
- dal_order, dal_order.GetMethodInfo("InsertOrderSingle"), ordermodel);
- }
- }
- }
在BLL对象里面有一个调用DAL对象方法的实例对象,为了演示简单这里没有加入层的依赖注入设计方案,通过直接调 用方式。在BLL方法体中有一个专门用来在上下文中调用方法的接口,这是约束目的是为了能让框架切入到方法的执行之前先执行。具体的设计原理我将在下一篇 文章中详细讲解。 在方法的上面有一个ContextExceptionHandler特性,目的是安全的调用DAL对象的方法,在有异常的情况下能通过上下文的方式人性化的提示错误信息。这样我们就不需要频繁的编写捕获异常的代码,看起来也不爽,我们要的是代码的整洁、美丽。 UI调用: - /***
- * author:深度训练
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Data;
- using ConsoleApplication1.BLL;
- using ConsoleApplication1.Model;
-
- namespace ConsoleApplication1
- {
- public class Program
- {
- public static void Main(string[] args)
- {
- BLL.BLL_Order order = new BLL.BLL_Order();
- //开启上下文
- using (ContextModule.ContextRuntime.BeginContextRuntime())
- {
- Model.Model_Order ordermodel = new Model_Order() { OrderGuid = Guid.NewGuid(), OrderTime = DateTime.Now };
-
- Model.Model_Order resultmodel =
- ContextModule.ContextAction.PostMethod<BLL.BLL_Order, Model.Model_Order>(
- order, order.GetMethodInfo("InsertOrderSingle"), ordermodel);
- }
-
- }
- }
- }
执行效果: 会先执行日志的记录,然后要求我们输入用户凭证才能继续执行下面的方法。 我输入YES才能继续执行插入的方法。我们可以通过很简单的实现上下文的管理接口,对方法进行控制。 总结:该篇文章只是介绍上下文的作用、原理、优势。下篇文章:“.NET 面向上下文架构模式(实现)”将详细的介绍上下文框架如何开发。[王清培版权所有,转载请给出署名]
有关SSL的原理和介绍在网上已经有不少,对于Java下使用keytool生成证书,配置SSL通信的教程也非常多。但如果我们不能够亲自动 手做一个SSL Sever和SSL Client,可能就永远也不能深入地理解Java环境下,SSL的通信是如何实现的。对SSL中的各种概念的认识也可能会仅限于可以使用的程度。本文通 过构造一个简单的SSL Server和SSL Client来讲解Java环境下SSL的通信原理。 首先我们先回顾一下常规的Java Socket编程。在Java下写一个Socket服务器和客户端的例子还是比较简单的。以下是服务端的代码: Java代码 1.package org.bluedash.tryssl; 2. 3.import java.io.BufferedReader; 4.import java.io.IOException; 5.import java.io.InputStbreamReader; 6.import java.io.PrintWriter; 7.import java.net.ServerSocket; 8.import java.net.Socket; 9. 10.public class Server extends Thread { 11. private Socket socket; 12. 13. public Server(Socket socket) { 14. this.socket = socket; 15. } 16. 17. public void run() { 18. try { 19. BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 20. PrintWriter writer = new PrintWriter(socket.getOutputStream()); 21. 22. String data = reader.readLine(); 23. writer.println(data); 24. writer.close(); 25. socket.close(); 26. } catch (IOException e) { 27. 28. } 29. } 30. 31. public static void main(String[] args) throws Exception { 32. while (true) { 33. new Server((new ServerSocket(8080)).accept()).start(); 34. } 35. } 36.} package org.bluedash.tryssl; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class Server extends Thread { private Socket socket; public Server(Socket socket) { this.socket = socket; } public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter(socket.getOutputStream()); String data = reader.readLine(); writer.println(data); writer.close(); socket.close(); } catch (IOException e) { } } public static void main(String[] args) throws Exception { while (true) { new Server((new ServerSocket(8080)).accept()).start(); } } } 服务端很简单:侦听8080端口,并把客户端发来的字符串返回去。下面是客户端的代码: Java代码 1.package org.bluedash.tryssl; 2. 3.import java.io.BufferedReader; 4.import java.io.InputStreamReader; 5.import java.io.PrintWriter; 6.import java.net.Socket; 7. 8.public class Client { 9. 10. public static void main(String[] args) throws Exception { 11. 12. Socket s = new Socket("localhost", 8080); 13. 14. PrintWriter writer = new PrintWriter(s.getOutputStream()); 15. BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream())); 16. writer.println("hello"); 17. writer.flush(); 18. System.out.println(reader.readLine()); 19. s.close(); 20. } 21. 22.} package org.bluedash.tryssl; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class Client { public static void main(String[] args) throws Exception { Socket s = new Socket("localhost", 8080); PrintWriter writer = new PrintWriter(s.getOutputStream()); BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream())); writer.println("hello"); writer.flush(); System.out.println(reader.readLine()); s.close(); } } 客户端也非常简单:向服务端发起请求,发送一个"hello"字串,然后获得服务端的返回。把服务端运行起来后,执行客户端,我们将得到"hello"的返回。 就是这样一套简单的网络通信的代码,我们来把它改造成使用SSL通信。在SSL通信协议中,我们都知道首先服务端必须有一个数字证书,当客户端连接 到服务端时,会得到这个证书,然后客户端会判断这个证书是否是可信的,如果是,则交换信道加密密钥,进行通信。如果不信任这个证书,则连接失败。 因此,我们首先要为服务端生成一个数字证书。Java环境下,数字证书是用keytool生成的,这些证书被存储在store的概念中,就是证书仓库。我们来调用keytool命令为服务端生成数字证书和保存它使用的证书仓库: Bash代码 1.keytool -genkey -v -alias bluedash-ssl-demo-server -keyalg RSA -keystore ./server_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass server -keypass 123123 keytool -genkey -v -alias bluedash-ssl-demo-server -keyalg RSA -keystore ./server_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass server -keypass 123123 这样,我们就将服务端证书bluedash-ssl-demo-server保存在了server_ksy这个store文件当中。有关keytool的用法在本文中就不再多赘述。执行上面的命令得到如下结果: Bash代码 1.Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days 2. for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn 3.[Storing ./server_ks] Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn [Storing ./server_ks] 然后,改造我们的服务端代码,让服务端使用这个证书,并提供SSL通信: Java代码 1.package org.bluedash.tryssl; 2. 3.import java.io.BufferedReader; 4.import java.io.FileInputStream; 5.import java.io.IOException; 6.import java.io.InputStreamReader; 7.import java.io.PrintWriter; 8.import java.net.ServerSocket; 9.import java.net.Socket; 10.import java.security.KeyStore; 11. 12.import javax.net.ServerSocketFactory; 13.import javax.net.ssl.KeyManagerFactory; 14.import javax.net.ssl.SSLContext; 15.import javax.net.ssl.SSLServerSocket; 16. 17.public class SSLServer extends Thread { 18. private Socket socket; 19. 20. public SSLServer(Socket socket) { 21. this.socket = socket; 22. } 23. 24. public void run() { 25. try { 26. BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 27. PrintWriter writer = new PrintWriter(socket.getOutputStream()); 28. 29. String data = reader.readLine(); 30. writer.println(data); 31. writer.close(); 32. socket.close(); 33. } catch (IOException e) { 34. 35. } 36. } 37. 38. private static String SERVER_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/server_ks"; 39. private static String SERVER_KEY_STORE_PASSWORD = "123123"; 40. 41. public static void main(String[] args) throws Exception { 42. System.setProperty("javax.net.ssl.trustStore", SERVER_KEY_STORE); 43. SSLContext context = SSLContext.getInstance("TLS"); 44. 45. KeyStore ks = KeyStore.getInstance("jceks"); 46. ks.load(new FileInputStream(SERVER_KEY_STORE), null); 47. KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509"); 48. kf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray()); 49. 50. context.init(kf.getKeyManagers(), null, null); 51. 52. ServerSocketFactory factory = context.getServerSocketFactory(); 53. ServerSocket _socket = factory.createServerSocket(8443); 54. ((SSLServerSocket) _socket).setNeedClientAuth(false); 55. 56. while (true) { 57. new SSLServer(_socket.accept()).start(); 58. } 59. } 60.} package org.bluedash.tryssl; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.security.KeyStore; import javax.net.ServerSocketFactory; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; public class SSLServer extends Thread { private Socket socket; public SSLServer(Socket socket) { this.socket = socket; } public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter(socket.getOutputStream()); String data = reader.readLine(); writer.println(data); writer.close(); socket.close(); } catch (IOException e) { } } private static String SERVER_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/server_ks"; private static String SERVER_KEY_STORE_PASSWORD = "123123"; public static void main(String[] args) throws Exception { System.setProperty("javax.net.ssl.trustStore", SERVER_KEY_STORE); SSLContext context = SSLContext.getInstance("TLS"); KeyStore ks = KeyStore.getInstance("jceks"); ks.load(new FileInputStream(SERVER_KEY_STORE), null); KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509"); kf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray()); context.init(kf.getKeyManagers(), null, null); ServerSocketFactory factory = context.getServerSocketFactory(); ServerSocket _socket = factory.createServerSocket(8443); ((SSLServerSocket) _socket).setNeedClientAuth(false); while (true) { new SSLServer(_socket.accept()).start(); } } } 可以看到,服务端的Socket准备设置工作大大增加了,增加的代码的作用主要是将证书导入并进行使用。此外,所使用的Socket变成了 SSLServerSocket,另外端口改到了8443(这个不是强制的,仅仅是为了遵守习惯)。另外,最重要的一点,服务端证书里面的CN一定和服务 端的域名统一,我们的证书服务的域名是localhost,那么我们的客户端在连接服务端时一定也要用localhost来连接,否则根据SSL协议标 准,域名与证书的CN不匹配,说明这个证书是不安全的,通信将无法正常运行。 有了服务端,我们原来的客户端就不能使用了,必须要走SSL协议。由于服务端的证书是我们自己生成的,没有任何受信任机构的签名,所以客户端是无法 验证服务端证书的有效性的,通信必然会失败。所以我们需要为客户端创建一个保存所有信任证书的仓库,然后把服务端证书导进这个仓库。这样,当客户端连接服 务端时,会发现服务端的证书在自己的信任列表中,就可以正常通信了。 因此现在我们要做的是生成一个客户端的证书仓库,因为keytool不能仅生成一个空白仓库,所以和服务端一样,我们还是生成一个证书加一个仓库(客户端证书加仓库): Bash代码 1.keytool -genkey -v -alias bluedash-ssl-demo-client -keyalg RSA -keystore ./client_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass client -keypass 456456 keytool -genkey -v -alias bluedash-ssl-demo-client -keyalg RSA -keystore ./client_ks -dname "CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn" -storepass client -keypass 456456 结果如下: Bash代码 1.Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days 2. for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn 3.[Storing ./client_ks] Generating 1,024 bit RSA key pair and self-signed certificate (SHA1withRSA) with a validity of 90 days for: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn [Storing ./client_ks] 接下来,我们要把服务端的证书导出来,并导入到客户端的仓库。第一步是导出服务端的证书: Bash代码 1.keytool -export -alias bluedash-ssl-demo-server -keystore ./server_ks -file server_key.cer keytool -export -alias bluedash-ssl-demo-server -keystore ./server_ks -file server_key.cer 执行结果如下: Bash代码 1.Enter keystore password: server 2.Certificate stored in file <server_key.cer> Enter keystore password: server Certificate stored in file <server_key.cer> 然后是把导出的证书导入到客户端证书仓库: Bash代码 1.keytool -import -trustcacerts -alias bluedash-ssl-demo-server -file ./server_key.cer -keystore ./client_ks keytool -import -trustcacerts -alias bluedash-ssl-demo-server -file ./server_key.cer -keystore ./client_ks 结果如下: Bash代码 1.Enter keystore password: client 2.Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn 3.Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn 4.Serial number: 4c57c7de 5.Valid from: Tue Aug 03 15:40:14 CST 2010 until: Mon Nov 01 15:40:14 CST 2010 6.Certificate fingerprints: 7. MD5: FC:D4:8B:36:3F:1B:30:EA:6D:63:55:4F:C7:68:3B:0C 8. SHA1: E1:54:2F:7C:1A:50:F5:74:AA:63:1E:F9:CC:B1:1C:73:AA:34:8A:C4 9. Signature algorithm name: SHA1withRSA 10. Version: 3 11.Trust this certificate? [no]: yes 12.Certificate was added to keystore Enter keystore password: client Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn Serial number: 4c57c7de Valid from: Tue Aug 03 15:40:14 CST 2010 until: Mon Nov 01 15:40:14 CST 2010 Certificate fingerprints: MD5: FC:D4:8B:36:3F:1B:30:EA:6D:63:55:4F:C7:68:3B:0C SHA1: E1:54:2F:7C:1A:50:F5:74:AA:63:1E:F9:CC:B1:1C:73:AA:34:8A:C4 Signature algorithm name: SHA1withRSA Version: 3 Trust this certificate? [no]: yes Certificate was added to keystore 好,准备工作做完了,我们来撰写客户端的代码: Java代码 1.package org.bluedash.tryssl; 2. 3.import java.io.BufferedReader; 4.import java.io.InputStreamReader; 5.import java.io.PrintWriter; 6.import java.net.Socket; 7. 8.import javax.net.SocketFactory; 9.import javax.net.ssl.SSLSocketFactory; 10. 11.public class SSLClient { 12. 13. private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks"; 14. 15. public static void main(String[] args) throws Exception { 16. // Set the key store to use for validating the server cert. 17. System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE); 18. 19. System.setProperty("javax.net.debug", "ssl,handshake"); 20. 21. SSLClient client = new SSLClient(); 22. Socket s = client.clientWithoutCert(); 23. 24. PrintWriter writer = new PrintWriter(s.getOutputStream()); 25. BufferedReader reader = new BufferedReader(new InputStreamReader(s 26. .getInputStream())); 27. writer.println("hello"); 28. writer.flush(); 29. System.out.println(reader.readLine()); 30. s.close(); 31. } 32. 33. private Socket clientWithoutCert() throws Exception { 34. SocketFactory sf = SSLSocketFactory.getDefault(); 35. Socket s = sf.createSocket("localhost", 8443); 36. return s; 37. } 38.} package org.bluedash.tryssl; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; public class SSLClient { private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks"; public static void main(String[] args) throws Exception { // Set the key store to use for validating the server cert. System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE); System.setProperty("javax.net.debug", "ssl,handshake"); SSLClient client = new SSLClient(); Socket s = client.clientWithoutCert(); PrintWriter writer = new PrintWriter(s.getOutputStream()); BufferedReader reader = new BufferedReader(new InputStreamReader(s .getInputStream())); writer.println("hello"); writer.flush(); System.out.println(reader.readLine()); s.close(); } private Socket clientWithoutCert() throws Exception { SocketFactory sf = SSLSocketFactory.getDefault(); Socket s = sf.createSocket("localhost", 8443); return s; } } 可以看到,除了把一些类变成SSL通信类以外,客户端也多出了使用信任证书仓库的代码。以上,我们便完成了SSL单向握手通信。即:客户端验证服务端的证书,服务端不认证客户端的证书。 以上便是Java环境下SSL单向握手的全过程。因为我们在客户端设置了日志输出级别为DEBUG: Java代码 1.System.setProperty("javax.net.debug", "ssl,handshake"); System.setProperty("javax.net.debug", "ssl,handshake"); 因此我们可以看到SSL通信的全过程,这些日志可以帮助我们更具体地了解通过SSL协议建立网络连接时的全过程。 结合日志,我们来看一下SSL双向认证的全过程: 第一步: 客户端发送ClientHello消息,发起SSL连接请求,告诉服务器自己支持的SSL选项(加密方式等)。 Bash代码 1.*** ClientHello, TLSv1 *** ClientHello, TLSv1 第二步: 服务器响应请求,回复ServerHello消息,和客户端确认SSL加密方式: Bash代码 1.*** ServerHello, TLSv1 *** ServerHello, TLSv1 第三步: 服务端向客户端发布自己的公钥。 第四步: 客户端与服务端的协通沟通完毕,服务端发送ServerHelloDone消息: Bash代码 1.*** ServerHelloDone *** ServerHelloDone 第五步: 客户端使用服务端给予的公钥,创建会话用密钥(SSL证书认证完成后,为了提高性能,所有的信息交互就可能会使用对称加密算法),并通过ClientKeyExchange消息发给服务器: Bash代码 1.*** ClientKeyExchange, RSA PreMasterSecret, TLSv1 *** ClientKeyExchange, RSA PreMasterSecret, TLSv1 第六步: 客户端通知服务器改变加密算法,通过ChangeCipherSpec消息发给服务端: Bash代码 1.main, WRITE: TLSv1 Change Cipher Spec, length = 1 main, WRITE: TLSv1 Change Cipher Spec, length = 1 第七步: 客户端发送Finished消息,告知服务器请检查加密算法的变更请求: Bash代码 1.*** Finished *** Finished 第八步:服务端确认算法变更,返回ChangeCipherSpec消息 Bash代码 1.main, READ: TLSv1 Change Cipher Spec, length = 1 main, READ: TLSv1 Change Cipher Spec, length = 1 第九步:服务端发送Finished消息,加密算法生效: Bash代码 1.*** Finished *** Finished 那么如何让服务端也认证客户端的身份,即双向握手呢?其实很简单,在服务端代码中,把这一行: Java代码 1.((SSLServerSocket) _socket).setNeedClientAuth(false); ((SSLServerSocket) _socket).setNeedClientAuth(false); 改成: Java代码 1.((SSLServerSocket) _socket).setNeedClientAuth(true); ((SSLServerSocket) _socket).setNeedClientAuth(true); 就可以了。但是,同样的道理,现在服务端并没有信任客户端的证书,因为客户端的证书也是自己生成的。所以,对于服务端,需要做同样的工作:把客户端的证书导出来,并导入到服务端的证书仓库: Bash代码 1.keytool -export -alias bluedash-ssl-demo-client -keystore ./client_ks -file client_key.cer 2.Enter keystore password: client 3.Certificate stored in file <client_key.cer> keytool -export -alias bluedash-ssl-demo-client -keystore ./client_ks -file client_key.cer Enter keystore password: client Certificate stored in file <client_key.cer> Bash代码 1.keytool -import -trustcacerts -alias bluedash-ssl-demo-client -file ./client_key.cer -keystore ./server_ks 2.Enter keystore password: server 3.Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn 4.Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn 5.Serial number: 4c57c80b 6.Valid from: Tue Aug 03 15:40:59 CST 2010 until: Mon Nov 01 15:40:59 CST 2010 7.Certificate fingerprints: 8. MD5: DB:91:F4:1E:65:D1:81:F2:1E:A6:A3:55:3F:E8:12:79 9. SHA1: BF:77:56:61:04:DD:95:FC:E5:84:48:5C:BE:60:AF:02:96:A2:E1:E2 10. Signature algorithm name: SHA1withRSA 11. Version: 3 12.Trust this certificate? [no]: yes 13.Certificate was added to keystore keytool -import -trustcacerts -alias bluedash-ssl-demo-client -file ./client_key.cer -keystore ./server_ks Enter keystore password: server Owner: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn Issuer: CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn Serial number: 4c57c80b Valid from: Tue Aug 03 15:40:59 CST 2010 until: Mon Nov 01 15:40:59 CST 2010 Certificate fingerprints: MD5: DB:91:F4:1E:65:D1:81:F2:1E:A6:A3:55:3F:E8:12:79 SHA1: BF:77:56:61:04:DD:95:FC:E5:84:48:5C:BE:60:AF:02:96:A2:E1:E2 Signature algorithm name: SHA1withRSA Version: 3 Trust this certificate? [no]: yes Certificate was added to keystore 完成了证书的导入,还要在客户端需要加入一段代码,用于在连接时,客户端向服务端出示自己的证书: Java代码 1.package org.bluedash.tryssl; 2. 3.import java.io.BufferedReader; 4.import java.io.FileInputStream; 5.import java.io.InputStreamReader; 6.import java.io.PrintWriter; 7.import java.net.Socket; 8.import java.security.KeyStore; 9.import javax.net.SocketFactory; 10.import javax.net.ssl.KeyManagerFactory; 11.import javax.net.ssl.SSLContext; 12.import javax.net.ssl.SSLSocketFactory; 13. 14.public class SSLClient { 15. private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks"; 16. private static String CLIENT_KEY_STORE_PASSWORD = "456456"; 17. 18. public static void main(String[] args) throws Exception { 19. // Set the key store to use for validating the server cert. 20. System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE); 21. System.setProperty("javax.net.debug", "ssl,handshake"); 22. SSLClient client = new SSLClient(); 23. Socket s = client.clientWithCert(); 24. 25. PrintWriter writer = new PrintWriter(s.getOutputStream()); 26. BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream())); 27. writer.println("hello"); 28. writer.flush(); 29. System.out.println(reader.readLine()); 30. s.close(); 31. } 32. 33. private Socket clientWithoutCert() throws Exception { 34. SocketFactory sf = SSLSocketFactory.getDefault(); 35. Socket s = sf.createSocket("localhost", 8443); 36. return s; 37. } 38. 39. private Socket clientWithCert() throws Exception { 40. SSLContext context = SSLContext.getInstance("TLS"); 41. KeyStore ks = KeyStore.getInstance("jceks"); 42. 43. ks.load(new FileInputStream(CLIENT_KEY_STORE), null); 44. KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509"); 45. kf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray()); 46. context.init(kf.getKeyManagers(), null, null); 47. 48. SocketFactory factory = context.getSocketFactory(); 49. Socket s = factory.createSocket("localhost", 8443); 50. return s; 51. } 52.} package org.bluedash.tryssl; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.security.KeyStore; import javax.net.SocketFactory; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; public class SSLClient { private static String CLIENT_KEY_STORE = "/Users/liweinan/projs/ssl/src/main/resources/META-INF/client_ks"; private static String CLIENT_KEY_STORE_PASSWORD = "456456"; public static void main(String[] args) throws Exception { // Set the key store to use for validating the server cert. System.setProperty("javax.net.ssl.trustStore", CLIENT_KEY_STORE); System.setProperty("javax.net.debug", "ssl,handshake"); SSLClient client = new SSLClient(); Socket s = client.clientWithCert(); PrintWriter writer = new PrintWriter(s.getOutputStream()); BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream())); writer.println("hello"); writer.flush(); System.out.println(reader.readLine()); s.close(); } private Socket clientWithoutCert() tbhrows Exception { SocketFactory sf = SSLSocketFactory.getDefault(); Socket s = sf.createSocket("localhost", 8443); return s; } private Socket clientWithCert() throws Exception { SSLContext context = SSLContext.getInstance("TLS"); KeyStore ks = KeyStore.getInstance("jceks"); ks.load(new FileInputStream(CLIENT_KEY_STORE), null); KeyManagerFactory kf = KeyManagerFactory.getInstance("SunX509"); kf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray()); context.init(kf.getKeyManagers(), null, null); SocketFactory factory = context.getSocketFactory(); Socket s = factory.createSocket("localhost", 8443); return s; } } 通过比对单向认证的日志输出,我们可以发现双向认证时,多出了服务端认证客户端证书的步骤: Bash代码 1.*** CertificateRequest 2.Cert Types: RSA, DSS 3.Cert Authorities: 4.<CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn> 5.<CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn> 6.*** ServerHelloDone *** CertificateRequest Cert Types: RSA, DSS Cert Authorities: <CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn> <CN=localhost, OU=cn, O=cn, L=cn, ST=cn, C=cn> *** ServerHelloDone Bash代码 1.*** CertificateVerify 2.main, WRITE: TLSv1 Handshake, length = 134 3.main, WRITE: TLSv1 Change Cipher Spec, length = 1 *** CertificateVerify main, WRITE: TLSv1 Handshake, length = 134 main, WRITE: TLSv1 Change Cipher Spec, length = 1 在 @*** ServerHelloDone@ 之前,服务端向客户端发起了需要证书的请求 @*** CertificateRequest@ 。 在客户端向服务端发出 @Change Cipher Spec@ 请求之前,多了一步客户端证书认证的过程 @*** CertificateVerify@ 。 客户端与服务端互相认证证书的情景
Java代码 - 在项目中经常遇见这样的问题:修改应用的配置文件web.xml后,无论重启应用还是重启WebSphere服务器,都不能重新加载web.xml,导致修改的内容无效。
-
- 这 个问题困扰了我好久,即使删除了${was安装目录}\IBM\WebSphere\AppServer\profiles\AppSrv01\下的 temp和wstemp两个缓存文件夹下的临时文件,重启后还是无效。几经折腾,后来终于找到了问题所在——还是由于was的缓存机制导致的。
-
- 找 到${was安装目录}\AppServer\profiles\AppSrv01\config\cells\xxxNode01Cell \applications\${应用名}.ear\deployments\目录下,有一个与应用相同名称的缓存文件夹,删除或修改该文件夹的 web.xml,重启was即可。
Java代码 - 在websphere中修改了jsp后,有时会出现修改的jsp没有起作用,特别是改变了某jsp的样式后,在页面中没看到效果,这主要就是由于websphere中缓存的缘故,这就要清除WebSphere中jsp缓存,如我的应用部署的目录为:
- E:\IBM\WebSphere\AppServer\profiles\AppSrv01\installedApps\nbxzfwNode01Cell\项目名_war.ear\项目名.war
- 在这个目录下更新了某个jsp页面,后在浏览器里面查看的时候,发现页面没有改变。基于此,我查看了一下目录,存放应用临时文件的地方:
- E:\IBM\WebSphere\AppServer\profiles\AppSrv01\temp\nbxzfwNode01\server1\项目名_war\项目名.war
- 在这目录下,可以看到有很多class文件,都是jsp编译过来的,对应我们应用目录下的jsp文件,于是找到对应jsp的class文件删除,再到浏览器中查看,发现已经改变了。。
- 还有一种办法,就是把这个jsp从项目中删除或重命名,再到浏览器里面查看那个页面,这时可能会报错,之后,再把对应的jsp添加上或名字改过来,再次到浏览器里面查看应用的时候,就发现这jsp的更新效果出来了,呵呵…
Java代码 - 前两天去客户那里给系统做升级,同时协助解决几个使用中的问题。到了现场第一件事情是把以前的应用导出做备份, 结果居然遇到返回null。查看日志发现系统报告空间不足,以前遇到这个问题是因为WAS出现oom(Out of Memory)之后,会生成javacore和dump文件供分析内存,这两个文件通常都比较大,30多M。如果多次出现oom,生成的文件就会占用大量空间。难道最近经常内存溢出?我心抽搐。
- 快马赶到Profile目录,没有发现导出文件,询问客户也没有出现系统宕机的情况,大石落地。仔细查看之后发现profile下的wstemp目录体积巨大,接近1.7G。这是个临时目录,每当管理员通过console登录之后,wstemp会生成一个新文件夹,保存管理员的所有操作记录,在管理员登出之后会删除该目录。但是wstemp下一堆的临时文件夹都没有被删掉,看来是was 5的bug又遗留到was 6了,真是搞不明白,was的这一堆补丁怎么都没解决掉这么明显的一个问题。N多RMB的was啊,越来越觉得还不如免费的jboss。
- 回公司之后查看了自己的was版本,wstemp目录2.4G,不过分区够大才没出问题。直接删之了事
转: http://dsr-22.iteye.com/blog/1258831
简介: Gang of Four 的解释器设计模式 (Interpreter design pattern) 鼓励在一个语言的基础上构建一个新的语言来实现扩展。大多数函数式语言都能够让您以多种方式(如操作符重载和模式匹配)对语言进行扩展。尽管 Java™ 不支持这些技术,下一代 JVM 语言均支持这些技术,但其具体实现细则有所不同。在本文中,Neal Ford 将探讨 Groovy、Scala 和 Clojure 如何通过以 Java 无法支持的方式来实现函数式扩展,从而实现解释器设计模式的目的。 在本期 函数式思维 的文章中,我将继续研究 Gang of Four (GoF) 设计模式(参阅 参考资料)的函数式替代解决方案。在本文中,我将研究最少人了解,但却是最强大的模式之一:解释器 (Interpreter)。 解释器的定义是: 给定一个语言,定义其语法表示,以及一个使用该表示来解释语言中的句子的解释器。 换句话说,如果您正在使用的语言不适用于解决问题,那么用它来构建一个适用的语言。关于该方法的一个很好的示例出现在 Web 框架中,如 Grails 和 Ruby on Rails(参阅 参考资料),它们扩展了自己的基础语言(分别是 Groovy 和 Ruby),使编写 Web 应用程序变得更容易。 这种模式最少人了解,因为构建一种新的语言并不常见,需要专业的技能和惯用语法。它是最强大的 设计模式,因为它鼓励您针对正在解决的问题扩展自己的编程语言。这在 Lisp(因此 Clojure 也同样)世界是一个普遍的特质,但在主流语言中不太常见。 当使用禁止对语言本身进行扩展的语言(如 Java)时,开发人员往往将自己的思维塑造成该语言的语法;这是您的惟一选择。然而,当您渐渐习惯使用允许轻松扩展的语言时,您就会开始将语言折向解决问题的方向,而不是其他折衷的方式。 Java 缺乏直观的语言扩展机制,除非您求助于面向方面的编程。然而,下一代的 JVM 语言(Groovy、Scala 和 Clojure)(参阅 参考资料)均支持以多种方式进行扩展。通过这样做,它们可以达到解释器设计模式的目的。首先,我将展示如何使用这三种语言实现操作符重载,然后演示 Groovy 和 Scala 如何让您扩展现有的类。 操作符重载(operator overloading) 操作符重载 是函数式语言的一个常见特性,能够重定义操作符(如 + 、- 或 * )配合新的类型工作,并表现出新的行为。操作符重载的缺失是 Java 形成时期的一个有意识的决定,但现在几乎每一个现代语言都具备这个特性,包括在 JVM 上 Java 的天然接班人。 Groovy Groovy 尝试更新 Java 的语法,使其跟上潮流,同时保留其自然语义。因此,Groovy 通过将操作符自动映射到方法名称实现操作符重载。例如,如果您想重载 Integer 的 + 操作符,那么您要重写 Integer 类的 plus() 方法。完整的映射列表已在线提供(参阅 参考资料);表 1 显示了列表的一部分: 表 1. Groovy 的操作符/方法映射列表的一部分 操作符 | 方法 |
---|
x + y | x.plus(y) | x * y | x.multiply(y) | x / y | x.div(y) | x ** y | x.power(y) | 作为一个操作符重载的示例,我将在 Groovy 和 Scala 中都创建一个 ComplexNumber 类。复数 是一个数学概念,由一个实数 和虚数 部分组成,一般写法是,例如 3 + 4i 。复数在许多科学领域中都很常用,包括工程学、物理学、电磁学和混沌理论。开发人员在编写这些领域的应用程序时,大大受益于能够创建反映其问题域的操作符。(有关复数的更多信息,请参阅 参考资料。) 清单 1 中显示了一个 Groovy ComplexNumber 类: 清单 1. Groovy 中的 ComplexNumber package complexnums class ComplexNumber { def real, imaginary public ComplexNumber(real, imaginary) { this.real = real this.imaginary = imaginary } def plus(rhs) { new ComplexNumber(this.real + rhs.real, this.imaginary + rhs.imaginary) } def multiply(rhs) { new ComplexNumber( real * rhs.real - imaginary * rhs.imaginary, real * rhs.imaginary + imaginary * rhs.real) } String toString() { real.toString() + ((imaginary < 0 ? "" : "+") + imaginary + "i").toString() } } | 在 清单 1 中,我创建一个类,保存实数和虚数部分,并且我创建重载的 plus() 和 multiply() 操作符。两个复数的相加是非常直观的:plus() 操作符将两个数各自的实数和虚数分别进行相加,并产生结果。两个复数的相乘需要以下公式: (x + yi)(u + vi) = (xu - yv) + (xv + yu)i | 在 清单 1 中的 multiply() 操作符复制该公式。它将两个数字的实数部分相乘,然后减去虚数部分相乘的积,再加上实数和虚数分别彼此相乘的积。 清单 2 测试复数运算符: 清单 2. 测试复数运算符 package complexnums import org.junit.Test import static org.junit.Assert.assertTrue import org.junit.Before class ComplexNumberTest { def x, y @Before void setup() { x = new ComplexNumber(3, 2) y = new ComplexNumber(1, 4) } @Test void plus_test() { def z = x + y; assertTrue 3 + 1 == z.real assertTrue 2 + 4 == z.imaginary } @Test void multiply_test() { def z = x * y assertTrue(-5 == z.real) assertTrue 14 == z.imaginary } } | 在 清单 2 中,plus_test() 和 multiply_test() 方法对重载操作符的使用(两者都以该领域专家使用的相同符号代表)与类似的内置类型用法没什么区别。 Scala(和 Clojure) Scala 通过放弃操作符和方法之间的区别来实现操作符重载:操作符仅仅是具有特殊名称的方法。因此,要使用 Scala 重写乘法运算,您要重写 * 方法。在清单 3 中,我用 Scala 创建复数。 清单 3. Scala 中的复数 class ComplexNumber(val real:Int, val imaginary:Int) { def +(operand:ComplexNumber):ComplexNumber = { new ComplexNumber(real + operand.real, imaginary + operand.imaginary) } def *(operand:ComplexNumber):ComplexNumber = { new ComplexNumber(real * operand.real - imaginary * operand.imaginary, real * operand.imaginary + imaginary * operand.real) } override def toString() = { real + (if (imaginary < 0) "" else "+") + imaginary + "i" } } | 清单 3 中的类包括熟悉的 real 和 imaginary 成员,以及 + 和 * 操作符/方法。如清单 4 所示,我可以自然地使用 ComplexNumber : 清单 4. 在 Scala 中使用复数 val c1 = new ComplexNumber(3, 2) val c2 = new ComplexNumber(1, 4) val c3 = c1 + c2 assert(c3.real == 4) assert(c3.imaginary == 6) val res = c1 + c2 * c3 printf("(%s) + (%s) * (%s) = %s\n", c1, c2, c3, res) assert(res.real == -17) assert(res.imaginary == 24) | 通过统一操作符和方法,Scala 使操作符重载变成一件小事。Clojure 使用相同的机制来重载操作符。例如,以下 Clojure 代码定义了一个重载的 ** 操作符: (defn ** [x y] (Math/pow x y)) | 回页首 扩展类 类似于操作符重载,下一代的 JVM 语言允许您扩展类(包括核心 Java 类),扩展的方式在 Java 语言本身是不可能实现的。这些设施通常用于构建领域特定的语言 (DSL)。虽然 GOF 从来没有考虑过 DSL(因为它们与当时流行的语言没有共同点),DSL 却体现了解释器设计模式的初衷。 通过将计量单位和其他修饰符添加给 Integer 等核心类,您可以(就像添加操作符一样)更紧密地对现实问题进行建模。Groovy 和 Scala 都支持这样做,但它们使用不同的机制。 Groovy 的 Expando 和类别类 Groovy 包括两种对现有类添加方法的机制:ExpandoMetaClass 和类别。(在 函数式思维:函数设计模式,第 2 部分 中,我在适配器模式的上下文中详细介绍过 ExpandoMetaClass 。) 比方说,您的公司由于离奇的遗留原因,需要以浪(furlongs,英国的计量单位)/每两周而不是以英里/每小时 (MPH) 的方法来表达速度,开发人员发现自己经常要执行这种转换。使用 Groovy 的 ExpandoMetaClass ,您可以添加一个 FF 属性给处理转换的 Integer ,如清单 5 所示: 清单 5. 使用 ExpandoMetaClass 添加一个浪/两周的计量单位给 Integer static { Integer.metaClass.getFF { -> delegate * 2688 } } @Test void test_conversion_with_expando() { assertTrue 1.FF == 2688 } | ExpandoMetaClass 的替代方法是,创建一个类别 包装器类,这是从 Objective-C 借来的概念。在清单 6 中,我添加了一个(小写) ff 属性给 Integer : 清单 6. 通过一个类别类添加计量单位 class FFCategory { static Integer getFf(Integer self) { self * 2688 } } @Test void test_conversion_with_category() { use(FFCategory) { assertTrue 1.ff == 2688 } } | 一个类别类是一个带有一组静态方法集合的普通类。每个方法接受至少一个参数;第一个参数是这种方法增强的类型。例如,在 清单 6 中, FFCategory 类拥有一个 getFf() 方法,它接受一个 Integer 参数。当这个类别类与 use 关键字一起使用时,代码块内所有相应类型都被增强。在单元测试中,我可以在代码块内引用 ff 属性(记住,Groovy 自动将 get 方法转换为属性引用),如在 清单 6 的底部所示。 有两种机制可供选择,让您可以更准确地控制增强的范围。例如,如果整个系统使用 MPH 作为速度的默认单位,但也需要频繁转换为浪/每两周,那么使用 ExpandoMetaClass 进行全局修改将是适当的。 您可能对重新开放核心 JVM 类的有效性持怀疑态度,担心会产生广泛深远的影响。类别类让您限制潜在危险性增强的范围。以下是一个来自真实世界的开源项目示例,它极好地利用了这一机制。 easyb 项目(参阅 参考资料)让您可以编写测试,以验证正接受测试的类的各个方面。请研究清单 7 所示的 easyb 测试代码片段: 清单 7. easyb 测试一个 queue 类 it "should dequeue items in same order enqueued", { [1..5].each {val -> queue.enqueue(val) } [1..5].each {val -> queue.dequeue().shouldBe(val) } } | queue 类不包括 shouldBe() 方法,这是我在测试的验证阶段所调用的方法。easyb 框架已为我添加了该方法;清单 8 中所显示的在 easyb 源代码中的 it() 方法定义,演示了该过程: 清单 8. easyb 的 it() 方法定义 def it(spec, closure) { stepStack.startStep(BehaviorStepType.IT, spec) closure.delegate = new EnsuringDelegate() try { if (beforeIt != null) { beforeIt() } listener.gotResult(new Result(Result.SUCCEEDED)) use(categories) { closure() } if (afterIt != null) { afterIt() } } catch (Throwable ex) { listener.gotResult(new Result(ex)) } finally { stepStack.stopStep() } } class BehaviorCategory { // ... static void shouldBe(Object self, value) { shouldBe(self, value, null) } //... } | 在 清单 8中,it() 方法接受了一个 spec (描述测试的一个字符串)和一个代表测试的主体的闭包块。在方法的中间,闭包会在 BehaviorCategory 块内执行,该块出现在清单的底部。BehaviorCategory 增强 Object ,允许 Java 世界中的任何 实例验证其值。 通过允许选择性增强驻留在层次结构顶层的 Object ,Groovy 的开放类机制可以轻松地实现为任何实例验证结果,但它限制了对 use 块主体的修改。 Scala 的隐式转换 Scala 使用隐式转换 来模拟现有类的增强。隐式转换不会对类添加方法,但允许语言自动将一个对象转换成拥有所需方法的相应类型。例如,我不能将 isBlank() 方法添加到 String 类中,但我可以创建一个隐式转换,将 String 自动转换为拥有这种方法的类。 作为一个示例,我想将 append() 方法添加到 Array ,这让我可以轻松地将 Person 实例添加到适当类型的数组,如清单 9 所示: 清单 9.将一个方法添加到 Array 中,以增加人员 case class Person (firstName: String, lastName: String) {} class PersonWrapper(a: Array[Person]) { def append(other: Person) = { a ++ Array(other) } def +(other: Person) = { a ++ Array(other) } } implicit def listWrapper(a: Array[Person]) = new PersonWrapper(a) | 在 清单 9中,我创建一个简单的 Person 类,它带有若干个属性。为了使 Array[Person] (在 Scala 中,一般使用 [ ] 而不是 < > 作为分隔符)Person 可知,我创建一个 PersonWrapper 类,它包括所需的 append() 方法。在清单的底部,我创建一个隐式转换,当我在数组上调用 append() 方法时,隐式转换会自动将一个 Array[Person] 转换为 PersonWrapper 。清单 10 测试该转换: 清单 10. 测试对现有类的自然扩展 val p1 = new Person("John", "Doe") var people = Array[Person]() people = people.append(p1) | 在 清单 9中,我也为 PersonWrapper 类添加了一个 + 方法。清单 11 显示了我如何使用操作符的这个漂亮直观的版本: 清单 11. 修改语言以增强可读性 people = people + new Person("Fred", "Smith") for (p <- people) printf("%s, %s\n", p.lastName, p.firstName) | Scala 实际上并未对原始的类添加一个方法,但它通过自动转换成一个合适的类型,提供了这样做的外观。使用 Groovy 等语言进行元编程所需要的相同工作在 Scala 中也需要,以避免过多使用隐式转换而产生由相互关联的类所组成的令人费解的网。但是,在正确使用时,隐式转换可以帮助您编写表达非常清晰的代码。 回页首 结束语 来自 GoF 的原始解释器设计模式建议创建一个新语言,但其基础语言并不支持我们今天所掌握的良好扩展机制。下一代 Java 语言都通过使用多种技术来支持语言级别的可扩展性。在本期文章中,我演示了操作符重载如何在 Groovy、Scala 和 Clojure 中工作,并研究了在 Groovy 和 Scala 中的类扩展。 在下期文章中,我将展示 Scala 风格的模式匹配和泛型的组合如何取代一些传统的设计模式。该讨论的中心是一个在函数式错误处理中也起着作用的概念,这一概念将是我们下期文章的主题。 参考资料 学习 获得产品和技术 讨论 关于作者 Neal Ford 是一家全球性 IT 咨询公司 ThoughtWorks 的软件架构师和 Meme Wrangler。他的工作还包括设计和开发应用程序、教材、杂志文章、课件和视频/DVD 演示,而且他是各种技术书籍的作者或编辑,包括最近的新书 The Productive Programmer 。他主要的工作重心是设计和构建大型企业应用程序。他还是全球开发人员会议上的国际知名演说家。请访问他的 Web 站点。
http://www.ibm.com/developerworks/cn/views/java/libraryview.jsp?view_by=search&sort_by=Date&sort_order=desc&view_by=Search&search_by=%E5%87%BD%E6%95%B0%E5%BC%8F%E6%80%9D%E7%BB%B4&dwsearch.x=18&dwsearch.y=11
摘要: 简介 目的 本文将描述 IBM Cognos Active Report 内可用的各种功能,以及如何使用它们创建和分发交互式报告应用程序。 本文假设您对 IBM Cognos Active Report 功能有一定的基本了解。有关入门资料,请参阅 http://publib.boulder.ibm.com/infocenter/cbi/v10r1m1/index.jsp 上 Au... 阅读全文
摘要: 在Word 给段落编号时,有时某些段落编号后会突然增大缩进1.选择更改样式-段落间距-自定义段落间距2.选择编辑选项卡,点击修改按钮3.设置相应的样式即可 阅读全文
Project 默认字体是11号宋体,显然有点太大了,但每次输入一个任务都要去格式化太麻烦 可以统一设置一个默认字体,步骤如下: 1.选择格式选项卡,点击文本样式 2.选择需要的样式,并将要修改的项选择全部,单击确定就搞定了
好的网站设计需要有好的效果技巧组合,无论你是一个自由者,设计师,要想使网站效果突出,绚丽,jquery是你一个不二的选择,利用jQuery,你可以很容易地创建自己的图像幻灯片,UI设计界面,完全与众不同的风格以达到惊人的过渡效果。有了这些功能,它可以帮助您的网上脱颖而出,提升您的品牌标识。今天的这些jquery插件,今天他被在60%的网站上使用,如果你正在创建web项目,这个是不错的选择 TN3 Gallery – jQuery Slider and Image Gallery Take a Tour 下载 Easy Slider 1.7 – Numeric Navigation jQuery Slider 演示 下载 Easy Slider 是一个滑动门插件,支持任何图片或内容,当点击时实现横向或纵向滑动。它拥有一系列丰富的参数设置,可通过CSS来进行完全的控制。所以,基本上你只需要链接这个插件文件后,设置好内容,然后样式化CSS就可以了。 RoyalSlider – Touch-Enabled jQuery Image Gallery 演示 下载 SlideDeck 演示 下载 slideViewer (a jQuery image slider built on a single unordered list) 1.2 演示 下载 利用这个jQuery插件,只需几行代码就能够将一组无序的图片列表转换成可导航控制的相册。 jqFancyTransitions – slideshow with strip effects 演示 下载 jqFancyTransitions这个jQuery插件能够以奇特的切换效果幻灯展示图片。它支持三种切换效果包括:波浪、拉链和卷帘。可以为图片设置标题,切换速度等。 AviaSlider – jQuery Slideshow 演示 下载 AviaSlider是一个独特的slidershow插件,支持8种不同的图片切换效果。支持图片预加载,图片自动播放当用户交互时停止。 Presentation Cycle – Cycle with a progressbar 演示 下载 Nivo Slider 演示 下载 Nivo Slider是一款出色的jQuery幻灯片插件,支持多种切换效果,可定制性强. jQuery Easy Slides v1.1 演示 下载 Advanced Slider – jQuery XML slider 演示 下载 Horinaja 演示 下载 Horinaja是一个可定制、易于安装、跨浏览器的Slideshow控件。当鼠标移到Slideshow区域时,内容项目停止滚动切换。当鼠标移开时,内容项目自动滚动切换。提供基于scriptaculous/prototype和jQuery两种版本 mbSlider 演示 下载 by mike182uk
Dragdealer JS 演示 下载 Smooth Div Scroll 演示 下载 Supersized jQuery Plugin 演示 下载 Pikachoose 演示 下载 PikaChoose是一个超轻量级的图片画廊jQuery插件,它是可以轻而易举地使用在您的网站。 有许多的网站使用了他,在他的主页上可以看到这些网站。 UnoSlider – Responsive Touch Enabled Slider 演示 下载 bxSlider 演示 下载 bxSlider是一个jQuery插件用于创建简单的内容滑动变换容器。可以设置是否自动滑动,滑动速度等。 slideViewerPro 1.5 演示 下载 Slides 演示 下载 Simple Controls Gallery v1.4 演示 下载 jQuery OneByOne Slider Plugin 演示 下载 s3Slider jQuery plugin 演示 下载 jQuery s3Slider 就是一个效果非常棒的产品展示用 Plugin ,它让我们单纯使用 HTML 就能达到如同 Flash 般的动画效果,而且配合适当的 CSS 时,更能在展示产品呈现一流的设计感。 JCoverflip 演示 下载 jCoverflip是一个jQuery插件用于创建类似于Coverflow的界面,可以展示图片或其它任意内容。内容既可以单击其中的某个项目进行浏览,也可以拖动Slider(jQuery UI slider)浏览。 Elastislide – A Responsive jQuery Carousel Plugin 演示 下载 Orbit: A Slick jQuery Image Slider Plugin 演示 下载 LayerSlider – The Parallax Effect Slider 演示 下载 jQuery UI Slider 演示 下载 FLEXSLIDER 演示 下载 simpleSlide 演示 下载 Sliding Image Gallery jQuery Plugin 演示 下载 Translucent – jQuery Banner Rotator / Slideshow 演示 下载 Blueberry Slider 演示 下载 Coin Slider 演示 下载 jQuery.popeye 演示 下载 Query.popeye这个插件能够将一组无序的图片列表转换成一个简单的相册。当点击图片时将以Lightbox风格放大图片。图片展示框提供 向前/ 向后控制并能够为每一张图片添加备注说明信息。jQuery.popeye能够根据图片大小自动调整展示框的高度和宽度。 Colorbox 演示 下载 这个是jquery的弹窗效果插件 jQuery Slider Evolution 演示 下载 Coda-Slider 演示 下载 AnythingSlider jQuery Plugin 演示 下载 jQuery Multimedia Portfolio 演示 下载 jQuery Multimedia Portfolio支持多种多媒体格式,包括:图片,视频(flv), 音频(mp3), 并能自动侦测每个文件的扩展名再分别调用合适的播放。 jQuery Cycle Plugin 演示 下载 jCycle 是一种新的插件jQuery致力于添加影像的平稳过渡。它支持6个不同的过渡类型而且很容易使用。自由职业者,相信您的客户会喜欢它的! Estro – jQuery Ken Burns & swipe effect slider 演示 下载 ResponsiveSlides.js 演示 下载 ResponsiveSlides.js 是一个微型的 jQuery 插件用来创建响应式的幻灯展示效果,对 <ul> 标签中的图片进行自动幻灯展示,支持几乎所有浏览器包括 IE6。也可设置 max-width 属性并对 IE6 有效。 wmuSlider 演示 下载 FSS – Full Screen Sliding Website Plugin 演示 下载 Agile Carousel 演示 下载 jQuery Slider plugin 演示 下载 jQuery Carousel Evolution 演示 下载 Skitter Slideshow 演示 下载 转自: http://www.cnblogs.com/web8cn/archive/2012/08/03/2622060.html
摘要: 1.新建插件工程,选择从已存在的Jar包创建2.选择本地的jar包3.输入工程名称,点击完成4.在Eclipse 资源管理器看到新建的插件工程5.双击MANIFEST.MF文件6.在编辑器中打开的设计界面,点击导出按钮7.选择插件依赖工程,和导出路径8.点击完成,在指定的位置看到导出的jar插件包 阅读全文
是自己编写的一个jar文件,每次添加单个jar到maven本地仓库的操作如下: 1.建立一个新的文件夹,将jar文件存放在该文件夹下。 注意文件夹下最好只存放该文件。 2.在该文件夹下建立一个pom.xml文件,在pom文件中定义其maven坐标。 3.在cmd窗口中执行以下命令: mvn install:install-file -Dfile=<path-to-file> -DgroupId=<group-id> -DartifactId=<artifact-id> -Dversion=<version> -Dpackaging=<packaging> 例如 mvn install:install-file -Dfile=/home/xx.jar -DgroupId=xx -DartifactId=xx -Dversion=1.0 -Dpackaging=jar
摘要: Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 using System; 2 using System.Collections.Generic; &n... 阅读全文
http://hb.qq.com/a/20110624/001008.htm http://blog.163.com/popfei3707@126/blog/static/12297648420107542344500/ http://www.iteye.com/problems/30192 http://hi.baidu.com/guorabbit/item/dddca00b7521eb813d42e279
修改 apache-tomcat-6.0.33\bin\catalina.bat 文件 @echo off if "%OS% " == "Windows_NT " setlocal rem --------------------------------------------------------------------------- rem Start script for the CATALINA Server rem rem $Id: startup.bat,v 1.4 2002/08/04 18:19:43 patrickl Exp $ rem --------------------------------------------------------------------------- set JAVA_HOME=c:\jdk set CATALINA_HOME=c:\apache-tomcat-6.0.33 rem Guess CATALINA_HOME if not defined if not "%CATALINA_HOME% " == " " goto gotHome JAVA_HOME=c:\jdk set CATALINA_HOME=c:\apache-tomcat-6.0.33 中 c:\jdk 是你jdk工具包的目录, c:\apache-tomcat-6.0.33 是tomcat 安装目录
jquery的功能总是那么的强大,用他可以开发任何web和移动框架,在浏览器市场,他一直是占有重要的份额,今天,就给大家分享20惊人的jQuery插件为设计师和开发人员。 比方说The-M-Project 可能就是你正在等待的一个开源的手机的 HTML5 的 JavaScript 框架,通过它可编写 HTML5/CSS3/SVG 应用,并支持多数数据平台,包括 iOS, Android, Palm webOS, 和 Blackberry OS。 jShowOff:jQuery的内容肩 - jQuery插件 ShowOff 是一个基于 jQuery 的内容幻灯插件,之所以叫内容插件而不是图片幻灯插件,是因为jShowOff不仅支持图片,同时也支持任何HTML代码。jShowOff很易于使用, 对HTML结构的要求很简单,有一个父元素包裹的元素集合就可以了,并且jShowOff提供了丰富实用的配置参数。 预览:
下载jQuery的内容肩插件 要求: jQuery的兼容性:所有主要浏览器下载:http://github.com/ekallevig/jShowOff
使用jQuery - Zoomy快速和方便的缩放 zoomy是简单的。zoomy是很容易实现和定制。Zoomy是一个快速简便的插件,使图片放大或缩小。你只需要两个副本图像和一个大的图像显示,然后缩放图像。大多数CMS系统保存或创建多个大小的图像,所以它很容易成立。只是链接上显示图片的放大图片,并告诉插件使用变焦时的链接。只需点点的脚本。Zoomy是一个很容易实现的jQuery插件,它可以帮助创建图像放大功能。 预览:
要求: jQuery的下载:http://redeyeoperations.com/plugins/zoomy/zoomy0.5.zip 使用php /jquery /image 图像裁剪插件 jQuery上传/图像上传工具的使用和作物不同类型的图像,JPG,GIF和PNG和你现在可以上传图片,并有一个随机的文件名 (此修复一些有缓存的问题)你需要“ 图片区选择 “插件。 我们需要的是一种方式来上传一个JPG图像,调整其大小,如果需要的话,那么裁剪给定的高度和宽度。 preivew:
要求:jQuery的,PHP PHP的GD
Demo: http://www.webmotionuk.co.uk/jquery/image_upload_crop.php Download: http://www.webmotionuk.co.uk/jquery/jquery_upload_crop.zip ProcessWire:PHP的CMS的框架与jQuery ProcessWire是一个开源的内容管理系统(CMS)和Web应用程序框架,针对设计师,开发人员和他们的客户的需求。ProcessWire给你更多的控制领域,模板和标记比其他平台,并提供了强大的模板系统,ProcessWire的API使您的内容轻松,愉快的工作。相比,你可以用来管理和发展在ProcessWire的网站是惊人的简单。 预览:
要求: PHP 5.2.4 +的MySQL 5.0.15 + 演示:http://processwire.com/demo/ 下载:https://github.com/ryancramerdesign/ProcessWire
jRating - 灵活的jQuery的Ajax评分插件 jRating是一个非常灵活的jQuery插件,用于快速创建一个Ajaxed星级评级制度。它是可以配置的每一个细节,从“数星星”,“如果星星可以代表小数或不”。还有一个选项,以显示大或小的恒星和图像可以很容易改变的任何其他文件。 预览:
要求: jQuery的兼容性:所有主要
The-M-Project HTML5的跨平台移动应用的JavaScript框架 The-M-Project 可能就是你正在等待的一个开源的手机的 HTML5 的 JavaScript 框架,通过它可编写 HTML5/CSS3/SVG 应用,并支持多数数据平台,包括 iOS, Android, Palm webOS, 和 Blackberry OS。The-M-Project是一个令人兴奋的HTML5的JavaScript框架建立跨平台的移动网络应用程序(IOSAndroid,Palm的webOS,黑莓)。它使用jquery的JavaScript部分,并包含所有UI +像脱机支持,国际化和功能的核心文件。 预览:
要求: Git的nodeJS 兼容性:所有主要的移动
arbor.js:使用Web和jQuery的图形可视化图书馆 arbor.js 是一个利用 Web Works 和 jQuery 创建的可视化图形库,它为图形组织和屏幕刷新处理提供了一个高效的、力导向的布局算法。 预览:
要求: jQuery的兼容性:所有现代浏览器下载:https://github.com/samizdatco/arbor
jQuery.validval插件简化定制的表单验证 jQuery.validVal是一个插件,旨在简化和定制的验证形式。它是高度可定制,功能非常丰富,可以很容易地在任何类型的HTML形式(甚至AJAX的形式),。它可用于任何一种HTML表单(包括Ajaxed)的验证,并通过在类名定义的规则。 预览:
要求: jQuery的兼容性:所有主要浏览器演示:http://validval.frebsite.nl/examples.php
FancyMoves Jquery的产品滑块 FancyMoves 是一个漂亮的产品图片滚动展播jQuery插件。支持键盘或点击图片导航浏览,当点击具体图片会弹出一个Lightbox模式对话框来显示原始图片。他有三种展现方式:使用键盘箭头,使用滑块两侧的左,右箭头,或在滑块只需点击下一个或最后一个项目。 FancyMoves,为您的网站或博客的一个新的jQuery滑块产品 预览:
要求: Demo: http://webdesignandsuch.com/posts/jquery-product-slider/jQuery-productSlider/index.html Download: http://webdesignandsuch.com/posts/jquery-product-slider/jQuery-productSlider/FancyMoves.zip
jquery 自动复制插件sheepIt SheepIt!是一个能够动态复制表单元素的jQuery插件,可以批量增加、删除某一个表单元素。在增加表单元素前/后都可以添加回调函数。 有时候,一个静态的形式是不够的,因为我们可能希望使人们有可能为用户定义灵活的地址,电话,电子邮件等 这是SheepIt!
预览:
要求: jQuery的1.4 + 的兼容性:所有主要
jquery Overlay Effect Menu Overlay Effect Menu是一款基于jquery的菜单插件,当鼠标经过菜单时候,将会产生遮罩,遮挡除了菜单的任何部分,并且展开子菜单. 预览:
要求: jQuery的兼容性:所有主要的浏览器演示:
http://tympanus.net/Tutorials/OverlayEffectMenu/ Download: http://tympanus.net/Tutorials/OverlayEffectMenu/OverlayEffectMenu.zip File Download jQuery Plugin: jDownload jDownload是一个jQuery插件,其目的是要下载有关文件的说明 一旦下载链接被点击时,它发送一个Ajax查询到一个PHP文件,它返回一个模态窗口中的文件的名称,类型和大小。 预览:
要求: jQuery的jQuery UI的兼容性:所有主要浏览器下载:http://code.google.com/p/jquery-jdownload/downloads/list
创意与jQuery自由基Web版式 - Lettering.js lettering.js,重量轻,易于使用JavaScript <SPAN>插件用来实现比较激进的网页排版。 它只是简单地划分成片<SPAN>小号的任何给定的元素包装一个自定义类的每个字母。 preivew:
要求: jQuery的兼容性:所有主要浏览器下载:http://github.com/davatron5000/Lettering.js
SlideNote:滑动通知的jQuery插件 SlideNote是一个可定制的,灵活jquery插件,可以很容易地显示在您的网站或在你的web应用滑动通知。欲了解更多信息或看到一个演示中,向下滚动。插件显示任何URL的内容,可选它可以显示该网址内的元素的内容。 预览:
要求: jQuery的兼容性:所有主要浏览器下载:http://github.com/tommcfarlin/slidenote~~V
jQuery的水平手风琴 -Easy Accordion Easy Accordion是一个高度灵活的jQuery插件用于快速创建水平可折叠手风琴(Accordion),它支持在同一个页面中创建多个实例。可折叠任意内容如:图片、列表、Flash等,外观也完全可以通过CSS自定义。 预览:
要求: jQuery的兼容性:所有主要
carouFredSel - 无限的jQuery传送带滚动水平和垂直插件 carouFredSel是一个内容无限循环播放容器jQuery,可以展示任何类型的HTML元素。支持水平和垂直两个方向。项目的高度和宽度可以不相同。可以动态从容器中删除/添加项目,可以利用向前/向后按纽播放项目或设置成自动播放。 Slick jQuery Image Slider Plugin “Orbit” Orbit是一个设计良好并且容易使用的jQuery图片滑动幻灯片插件,它除了支持图片滚动切换展示外,还支持针对内容的滚动。插件的定制性相当高,它提供了多个参数的设置,通过设置你可以将它打造成完全符合你要求的样式。 预览:
要求: jQuery的兼容性:所有主要浏览器网址:http://www.zurb.com/playground/jquery-image-slider-plugin/Orbit_Kit.zip 转自:http://www.cnblogs.com/web8cn/archive/2012/07/30/2614795.html
摘要: 今天在网上看到了一篇关于JAVA图像处理的文章,博主贴出了一个处理类:特点是高品质缩小,具体代码如下: import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import&n... 阅读全文
一、需要用到的类 java.awt.image.BufferedImage; javax.imageio.ImageIO; java.io.*; 二、为什么要将BufferedImage转为byte数组 在传输中,图片是不能直接传的,因此需要把图片变为字节数组,然后传输比较方便;只需要一般输出流的write方法即可; 而字节数组变成BufferedImage能够还原图像; 三、如何取得BufferedImage BufferedImage image = ImageIO.read(new File("1.gif")); 四、BufferedImage ---->byte[] ImageIO.write(BufferedImage image,String format,OutputStream out);方法可以很好的解决问题; 参数image表示获得的BufferedImage; 参数format表示图片的格式,比如“gif”等; 参数out表示输出流,如果要转成Byte数组,则输出流为ByteArrayOutputStream即可; 执行完后,只需要toByteArray()就能得到byte[]; 五、byte[] ------>BufferedImage ByteArrayInputStream in = new ByteArrayInputStream(byte[]b); //将b作为输入流; BufferedImage image = ImageIO.read(InputStream in); //将in作为输入流,读取图片存入image中,而这里in可以为ByteArrayInputStream(); 六、显示BufferedImage public void paint(Graphics g){ super.paint(g); g.drawImage(image); //image为BufferedImage类型 } 七、实例 要求:编写一个网络程序,通过Socket将图片从服务器端传到客户端,并存入文件系统; Server端: package org.exam3;
import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.net.ServerSocket; import java.net.Socket;
import javax.imageio.ImageIO;
public class T6Server {
public static void main(String[] args) throws Exception { ServerSocket server = new ServerSocket(8888); Socket s = server.accept(); DataOutputStream dout = new DataOutputStream(s.getOutputStream()); BufferedImage image = ImageIO.read(new File("1.gif")); ByteArrayOutputStream out = new ByteArrayOutputStream(); boolean flag = ImageIO.write(image, "gif", out); byte[] b = out.toByteArray(); dout.write(b); s.close(); }
}
Client端: package org.exam3;
import java.awt.BorderLayout; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.PrintWriter; import java.net.Socket;
import javax.imageio.ImageIO; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel;
public class T6Client extends JFrame { JButton button; MyPanel panel; public T6Client() { setSize(300, 400); button = new JButton("获取图像"); add(button,BorderLayout.NORTH); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { try { Socket s = new Socket("localhost",8888); PrintWriter out = new PrintWriter(s.getOutputStream()); out.print("a"); DataInputStream in = new DataInputStream(s.getInputStream()); byte[]b = new byte[1000000]; in.read(b); ByteArrayInputStream bin = new ByteArrayInputStream(b); BufferedImage image = ImageIO.read(bin); ImageIO.write(image, "gif", new File("2.gif")); s.close(); } catch (Exception e) { } } }); panel = new MyPanel(); add(panel);
} public static void main(String[] args) throws Exception { T6Client frame = new T6Client(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }
} 转自:
这两个概念是早些时候Martin Fowler总结出来的两种常见模型设计类型,没有说谁好谁不好,为不同的模型类别选择合适的场景是设计者的工作。没有工具本身的问题,只有工具使用者的问题。 贫血模型是指领域对象里只有get和set方法(POJO),所有的业务逻辑都不包含在内而是放在Business Logic层。
优点是系统的层次结构清楚,各层之间单向依赖,Client->(Business Facade)->Business Logic->Data Access Object。可见,领域对象几乎只作传输介质之用,不会影响到层次的划分。 该模型的缺点是不够面向对象,领域对象只是作为保存状态或者传递状态使用,它是没有生命的,只有数据没有行为的对象不是真正的对象,在Business Logic里面处理所有的业务逻辑,对于细粒度的逻辑处理,通过增加一层Facade达到门面包装的效果。 在使用Spring的时候,通常暗示着你使用了贫血模型,我们把Domain类用来单纯地存储数据,Spring管不着这些类的注入和管理,Spring关心的逻辑层(比如单例的被池化了的Business Logic层)可以被设计成singleton的bean。 假使我们这里逆天而行,硬要在Domain类中提供业务逻辑方法,那么我们在使用Spring构造这样的数据bean的时候就遇到许多麻烦,比如:bean之间的引用,可能引起大范围的bean之间的嵌套构造器的调用。 贫血模型实施的最大难度在于如何梳理好Business Logic层内部的划分关系,由于该层会比较庞大,边界不易控制,内部的各个模块之间的依赖关系不易管理,可以考虑这样这样的实现思路: (1)铺设扁平的原子业务逻辑层,即简单的CRUD操作(含批量数据操作); (2)特定业务清晰的逻辑通过Facade层来组装原子操作实现。 (3)给业务逻辑层实施模块划分,保持模块之间的松耦合的关系。 举例说明: 原子业务逻辑层(Service)提供了用户模型的条件查询方法: List<User> queryUser(Condition con) Facade层则提供了一种特定的业务场景的分子接口,满足18岁的中国公民,内部实现调用的正是上述的原子接口: List<User> queryAdultChinese() Facade、Service层纵向划分为几个大的领域包:用户、内容和产品。 充血模型层次结构和上面的差不多,不过大多业务逻辑和持久化放在 Domain Object里面,Business Logic只是简单封装部分业务逻辑以及控制事务、权限等,这样层次结构就变成Client->(Business Facade)->Business Logic->Domain Object->Data Access Object。
它的优点是面向对象,Business Logic符合单一职责,不像在贫血模型里面那样包含所有的业务逻辑太过沉重。 缺点是如何划分业务逻辑,什么样的逻辑应该放在Domain Object中,什么样的业务逻辑应该放在Business Logic中,这是很含糊的。即使划分好了业务逻辑,由于分散在Business Logic和Domain Object层中,不能更好的分模块开发。熟悉业务逻辑的开发人员需要渗透到Domain Logic中去,而在Domian Logic又包含了持久化,对于开发者来说这十分混乱。 其次,如果Business Logic要控制事务并且为上层提供一个统一的服务调用入口点,它就必须把在Domain Logic里实现的业务逻辑全部重新包装一遍,完全属于重复劳动。 使用RoR开发时, 每一个领域模型对象都可以具备自己的基础业务方法,通常满足充血模型的特征。充血模型更加适合较复杂业务逻辑的设计开发。 充血模型的层次和模块的划分是一门学问,对开发人员要求亦较高,可以考虑定义这样的一些规则: (1)事务控制不要放在领域模型的对象中实现,可以放在facade中完成。 (2)领域模型对象中只保留该模型驱动的一般方法,对于业务特征明显的特异场景方法调用放在facade中完成。 万事都不是绝对的,也有一些看起来不易解决的问题。例如,考虑到性能的需要,我需要一次查询出满足某种条件的用户和某种条件的产品,他们二者之间通过订购关系关联起来,可能发现这种情形下,上述的模型层次划分变得无解了…… 怎么办呢?包括以上种种,欢迎大家讨论。
转自: http://raychase.iteye.com/blog/1328224
简介: EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。本文充分的介绍了 EhCache 缓存系统对集群环境的支持以及使用方法。 发布日期: 2010 年 4 月 01 日 级别: 初级 访问情况 : 13809 次浏览 评论: 1 (查看 | 添加评论 - 登录) EhCache 缓存系统简介 EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。 下图是 EhCache 在应用程序中的位置: 图 1. EhCache 应用架构图 EhCache 的主要特性有: - 快速;
- 简单;
- 多种缓存策略;
- 缓存数据有两级:内存和磁盘,因此无需担心容量问题;
- 缓存数据会在虚拟机重启的过程中写入磁盘;
- 可以通过 RMI、可插入 API 等方式进行分布式缓存;
- 具有缓存和缓存管理器的侦听接口;
- 支持多缓存管理器实例,以及一个实例的多个缓存区域;
- 提供 Hibernate 的缓存实现;
- 等等 …
由于 EhCache 是进程中的缓存系统,一旦将应用部署在集群环境中,每一个节点维护各自的缓存数据,当某个节点对缓存数据进行更新,这些更新的数据无法在其它节点中共享, 这不仅会降低节点运行的效率,而且会导致数据不同步的情况发生。例如某个网站采用 A、B 两个节点作为集群部署,当 A 节点的缓存更新后,而 B 节点缓存尚未更新就可能出现用户在浏览页面的时候,一会是更新后的数据,一会是尚未更新的数据,尽管我们也可以通过 Session Sticky 技术来将用户锁定在某个节点上,但对于一些交互性比较强或者是非 Web 方式的系统来说,Session Sticky 显然不太适合。所以就需要用到 EhCache 的集群解决方案。 EhCache 从 1.7 版本开始,支持五种集群方案,分别是: - Terracotta
- RMI
- JMS
- JGroups
- EhCache Server
本文主要介绍其中的三种最为常用集群方式,分别是 RMI、JGroups 以及 EhCache Server 。 回页首 RMI 集群模式 RMI 是 Java 的一种远程方法调用技术,是一种点对点的基于 Java 对象的通讯方式。EhCache 从 1.2 版本开始就支持 RMI 方式的缓存集群。在集群环境中 EhCache 所有缓存对象的键和值都必须是可序列化的,也就是必须实现 java.io.Serializable 接口,这点在其它集群方式下也是需要遵守的。 下图是 RMI 集群模式的结构图: 图 2. RMI 集群模式结构图 采用 RMI 集群模式时,集群中的每个节点都是对等关系,并不存在主节点或者从节点的概念,因此节点间必须有一个机制能够互相认识对方,必须知道其它节点的信息,包括 主机地址、端口号等。EhCache 提供两种节点的发现方式:手工配置和自动发现。手工配置方式要求在每个节点中配置其它所有节点的连接信息,一旦集群中的节点发生变化时,需要对缓存进行重 新配置。 由于 RMI 是 Java 中内置支持的技术,因此使用 RMI 集群模式时,无需引入其它的 Jar 包,EhCache 本身就带有支持 RMI 集群的功能。使用 RMI 集群模式需要在 ehcache.xml 配置文件中定义 cacheManagerPeerProviderFactory 节点。假设集群中有两个节点,分别对应的 RMI 绑定信息是: 节点 1 | 192.168.0.11 | 4567 | /oschina_cache | 节点 2 | 192.168.0.12 | 4567 | /oschina_cache | 节点 3 | 192.168.0.13 | 4567 | /oschina_cache | 那么对应的手工配置信息如下: <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="hostName=localhost, port=4567, socketTimeoutMillis=2000, peerDiscovery=manual, rmiUrls=//192.168.0.12:4567/oschina_cache|//192.168.0.13:4567/oschina_cache" />
其它节点配置类似,只需把 rmiUrls 中的两个 IP 地址换成另外两个节点对应的 IP 地址即可。 接下来在需要进行缓存数据复制的区域(Region)上配置如下即可: <cache name="sampleCache2" maxElementsInMemory="10" eternal="false" timeToIdleSeconds="100" timeToLiveSeconds="100" overflowToDisk="false"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true "/> </cache>
具体每个参数代表的意义请参考 EhCache 的手册,此处不再详细说明。 EhCache 的 RMI 集群模式还有另外一种节点发现方式,就是通过多播( multicast )来维护集群中的所有有效节点。这也是最为简单而且灵活的方式,与手工模式不同的是,每个节点上的配置信息都相同,大大方便了节点的部署,避免人为的错漏出现。 在上述三个节点的例子中,配置如下: <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1, multicastGroupPort=4446, timeToLive=32" />
其中需要指定节点发现模式 peerDiscovery 值为 automatic 自动;同时组播地址可以指定 D 类 IP 地址空间,范围从 224.0.1.0 到 238.255.255.255 中的任何一个地址。 回页首 JGroups 集群模式 EhCache 从 1.5. 版本开始增加了 JGroups 的分布式集群模式。与 RMI 方式相比较, JGroups 提供了一个非常灵活的协议栈、可靠的单播和多播消息传输,主要的缺点是配置复杂以及一些协议栈对第三方包的依赖。 JGroups 也提供了基于 TCP 的单播 ( Unicast ) 和基于 UDP 的多播 ( Multicast ) ,对应 RMI 的手工配置和自动发现。使用单播方式需要指定其它节点的主机地址和端口,下面是两个节点,并使用了单播方式的配置: <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory" properties="connect=TCP(start_port=7800): TCPPING(initial_hosts=host1[7800],host2[7800]; port_range=10;timeout=3000; num_initial_members=3;up_thread=true;down_thread=true): VERIFY_SUSPECT(timeout=1500;down_thread=false;up_thread=false): pbcast.NAKACK(down_thread=true;up_thread=true;gc_lag=100; retransmit_timeout=3000): pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false; print_local_addr=false;down_thread=true;up_thread=true)" propertySeparator="::" />
使用多播方式配置如下: <cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory" properties="connect=UDP(mcast_addr=231.12.21.132;mcast_port=45566;):PING:MERGE2:FD_SOCK:VERIFY_SUSPECT:pbcast.NAKACK:UNICAST:pbcast.STABLE:FRAG:pbcast.GMS" propertySeparator="::" />
从上面的配置来看,JGroups 的配置要比 RMI 复杂得多,但也提供更多的微调参数,有助于提升缓存数据复制的性能。详细的 JGroups 配置参数的具体意义可参考 JGroups 的配置手册。 JGroups 方式对应缓存节点的配置信息如下: <cache name="sampleCache2" maxElementsInMemory="10" eternal="false" timeToIdleSeconds="100" timeToLiveSeconds="100" overflowToDisk="false"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true" /> </cache>
使用组播方式的注意事项 使用 JGroups 需要引入 JGroups 的 Jar 包以及 EhCache 对 JGroups 的封装包 ehcache-jgroupsreplication-xxx.jar 。 在一些启用了 IPv6 的电脑中,经常启动的时候报如下错误信息: java.lang.RuntimeException: the type of the stack (IPv6) and the user supplied addresses (IPv4) don't match: /231.12.21.132. 解决的办法是增加 JVM 参数:-Djava.net.preferIPv4Stack=true。如果是 Tomcat 服务器,可在 catalina.bat 或者 catalina.sh 中增加如下环境变量即可: SET CATALINA_OPTS=-Djava.net.preferIPv4Stack=true
经过实际测试发现,集群方式下的缓存数据都可以在 1 秒钟之内完成到其节点的复制。 回页首 EhCache Server 与前面介绍的两种集群方案不同的是, EhCache Server 是一个独立的缓存服务器,其内部使用 EhCache 做为缓存系统,可利用前面提到的两种方式进行内部集群。对外提供编程语言无关的基于 HTTP 的 RESTful 或者是 SOAP 的数据缓存操作接口。 下面是 EhCache Server 提供的对缓存数据进行操作的方法: OPTIONS /{cache}} 获取某个缓存的可用操作的信息。 HEAD /{cache}/{element} 获取缓存中某个元素的 HTTP 头信息,例如: curl --head http://localhost:8080/ehcache/rest/sampleCache2/2
EhCache Server 返回的信息如下: HTTP/1.1 200 OK X-Powered-By: Servlet/2.5 Server: GlassFish/v3 Last-Modified: Sun, 27 Jul 2008 08:08:49 GMT ETag: "1217146129490" Content-Type: text/plain; charset=iso-8859-1 Content-Length: 157 Date: Sun, 27 Jul 2008 08:17:09 GMT
GET /{cache}/{element} 读取缓存中某个数据的值。 PUT /{cache}/{element} 写缓存。 由于这些操作都是基于 HTTP 协议的,因此你可以在任何一种编程语言中使用它,例如 Perl、PHP 和 Ruby 等等。 下图是 EhCache Server 在应用中的架构: 图 3. EhCache Server 应用架构图 EhCache Server 同时也提供强大的安全机制、监控功能。在数据存储方面,最大的 Ehcache 单实例在内存中可以缓存 20GB。最大的磁盘可以缓存 100GB。通过将节点整合在一起,这样缓存数据就可以跨越节点,以此获得更大的容量。将缓存 20GB 的 50 个节点整合在一起就是 1TB 了。 回页首 总结 以上我们介绍了三种 EhCache 的集群方案,除了第三种跨编程语言的方案外,EhCache 的集群对应用程序的代码编写都是透明的,程序人员无需考虑缓存数据是如何复制到其它节点上。既保持了代码的轻量级,同时又支持庞大的数据集群。 EhCache 可谓是深入人心。 2009 年年中,Terracotta 宣布收购 EhCache 产品。Terracotta 公司的产品 Terracotta 是一个 JVM 级的开源群集框架,提供 HTTP Session 复制、分布式缓存、POJO 群集、跨越集群的 JVM 来实现分布式应用程序协调。最近 EhCache 主要的改进都集中在跟 Terracotta 框架的集成上,这是一个真正意义上的企业级缓存解决方案。 参考资料 学习 获得产品和技术 讨论 关于作者 刘柄成,开源中国社区(http://www.oschina.net)站长,DLOG4J 作者,十年的 Java 开发经验,热衷于开源软件的开发和应用。 http://www.ibm.com/developerworks/cn/java/j-lo-ehcache/
Ehcache 是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大,最初知道它,是从Hibernate的缓存开始的。网上中文的EhCache材料 以简单介绍和配置方法居多,如果你有这方面的问题,请自行google;对于API,官网上介绍已经非常清楚,请参见官网;但是很少见到特性说明和对实现 原理的分析,因此在这篇文章里面,我会详细介绍和分析EhCache的特性,加上一些自己的理解和思考,希望对缓存感兴趣的朋友有所收获。 一、特性一览,来自官网,简单翻译一下: 1、快速轻量 过去几年,诸多测试表明Ehcache是最快的Java缓存之一。 Ehcache的线程机制是为大型高并发系统设计的。 大量性能测试用例保证Ehcache在不同版本间性能表现得一致性。 很多用户都不知道他们正在用Ehcache,因为不需要什么特别的配置。 API易于使用,这就很容易部署上线和运行。 很小的jar包,Ehcache 2.2.3才668kb。 最小的依赖:唯一的依赖就是SLF4J了。
2、伸缩性 缓存在内存和磁盘存储可以伸缩到数G,Ehcache为大数据存储做过优化。 大内存的情况下,所有进程可以支持数百G的吞吐。 为高并发和大型多CPU服务器做优化。 线程安全和性能总是一对矛盾,Ehcache的线程机制设计采用了Doug Lea的想法来获得较高的性能。 单台虚拟机上支持多缓存管理器。 通过Terracotta服务器矩阵,可以伸缩到数百个节点。
3、灵活性 Ehcache 1.2具备对象API接口和可序列化API接口。 不能序列化的对象可以使用除磁盘存储外Ehcache的所有功能。 除了元素的返回方法以外,API都是统一的。只有这两个方法不一致:getObjectValue和getKeyValue。这就使得缓存对象、序列化对象来获取新的特性这个过程很简单。 支持基于Cache和基于Element的过期策略,每个Cache的存活时间都是可以设置和控制的。 提供了LRU、LFU和FIFO缓存淘汰算法,Ehcache 1.2引入了最少使用和先进先出缓存淘汰算法,构成了完整的缓存淘汰算法。 提供内存和磁盘存储,Ehcache和大多数缓存解决方案一样,提供高性能的内存和磁盘存储。 动态、运行时缓存配置,存活时间、空闲时间、内存和磁盘存放缓存的最大数目都是可以在运行时修改的。
4、标准支持 Ehcache提供了对JSR107 JCACHE API最完整的实现。因为JCACHE在发布以前,Ehcache的实现(如net.sf.jsr107cache)已经发布了。 实现JCACHE API有利于到未来其他缓存解决方案的可移植性。 Ehcache的维护者Greg Luck,正是JSR107的专家委员会委员。
5、可扩展性 监听器可以插件化。Ehcache 1.2提供了CacheManagerEventListener和CacheEventListener接口,实现可以插件化,并且可以在ehcache.xml里配置。 节点发现,冗余器和监听器都可以插件化。 分布式缓存,从Ehcache 1.2开始引入,包含了一些权衡的选项。Ehcache的团队相信没有什么是万能的配置。 实现者可以使用内建的机制或者完全自己实现,因为有完整的插件开发指南。 缓存的可扩展性可以插件化。创建你自己的缓存扩展,它可以持有一个缓存的引用,并且绑定在缓存的生命周期内。 缓存加载器可以插件化。创建你自己的缓存加载器,可以使用一些异步方法来加载数据到缓存里面。 缓存异常处理器可以插件化。创建一个异常处理器,在异常发生的时候,可以执行某些特定操作。
6、应用持久化 在VM重启后,持久化到磁盘的存储可以复原数据。 Ehcache是第一个引入缓存数据持久化存储的开源Java缓存框架。缓存的数据可以在机器重启后从磁盘上重新获得。 根据需要将缓存刷到磁盘。将缓存条目刷到磁盘的操作可以通过cache.flush()方法来执行,这大大方便了Ehcache的使用。
7、监听器 缓存管理器监听器。允许注册实现了CacheManagerEventListener接口的监听器: notifyCacheAdded() notifyCacheRemoved() 缓存事件监听器。允许注册实现了CacheEventListener接口的监听器,它提供了许多对缓存事件发生后的处理机制: notifyElementRemoved/Put/Updated/Expired
8、开启JMX Ehcache的JMX功能是默认开启的,你可以监控和管理如下的MBean: CacheManager、Cache、CacheConfiguration、CacheStatistics
9、分布式缓存 从Ehcache 1.2开始,支持高性能的分布式缓存,兼具灵活性和扩展性。 分布式缓存的选项包括: 通过Terracotta的缓存集群:设定和使用Terracotta模式的Ehcache缓存。缓存发现是自动完成的,并且有很多选项可以用来调试缓存行为和性能。 使用RMI、JGroups或者JMS来冗余缓存数据:节点可以通过多播或发现者手动配置。状态更新可以通过RMI连接来异步或者同步完成。 Custom:一个综合的插件机制,支持发现和复制的能力。 可用的缓存复制选项。支持的通过RMI、JGroups或JMS进行的异步或同步的缓存复制。 可靠的分发:使用TCP的内建分发机制。 节点发现:节点可以手动配置或者使用多播自动发现,并且可以自动添加和移除节点。对于多播阻塞的情况下,手动配置可以很好地控制。 分布式缓存可以任意时间加入或者离开集群。缓存可以配置在初始化的时候执行引导程序员。 BootstrapCacheLoaderFactory抽象工厂,实现了BootstrapCacheLoader接口(RMI实现)。 缓存服务端。Ehcache提供了一个Cache Server,一个war包,为绝大多数web容器或者是独立的服务器提供支持。 缓存服务端有两组API:面向资源的RESTful,还有就是SOAP。客户端没有实现语言的限制。 RESTful缓存服务器:Ehcached的实现严格遵循RESTful面向资源的架构风格。 SOAP缓存服务端:Ehcache RESTFul Web Services API暴露了单例的CacheManager,他能在ehcache.xml或者IoC容器里面配置。 标准服务端包含了内嵌的Glassfish web容器。它被打成了war包,可以任意部署到支持Servlet 2.5的web容器内。Glassfish V2/3、Tomcat 6和Jetty 6都已经经过了测试。
10、搜索 标准分布式搜索使用了流式查询接口的方式,请参阅文档。
11、Java EE和应用缓存 为普通缓存场景和模式提供高质量的实现。 阻塞缓存:它的机制避免了复制进程并发操作的问题。 SelfPopulatingCache在缓存一些开销昂贵操作时显得特别有用,它是一种针对读优化的缓存。它不需要调用者知道缓存元素怎样被返回,也支持在不阻塞读的情况下刷新缓存条目。 CachingFilter:一个抽象、可扩展的cache filter。 SimplePageCachingFilter:用于缓存基于request URI和Query String的页面。它可以根据HTTP request header的值来选择采用或者不采用gzip压缩方式将页面发到浏览器端。你可以用它来缓存整个Servlet页面,无论你采用的是JSP、 velocity,或者其他的页面渲染技术。 SimplePageFragmentCachingFilter:缓存页面片段,基于request URI和Query String。在JSP中使用jsp:include标签包含。 已经使用Orion和Tomcat测试过,兼容Servlet 2.3、Servlet 2.4规范。 Cacheable命令:这是一种老的命令行模式,支持异步行为、容错。 兼容Hibernate,兼容Google App Engine。 基于JTA的事务支持,支持事务资源管理,二阶段提交和回滚,以及本地事务。
12、开源协议 Apache 2.0 license 二、Ehcache的加载模块列表,他们都是独立的库,每个都为Ehcache添加新的功能,可以在此下载 : - ehcache-core:API,标准缓存引擎,RMI复制和Hibernate支持
- ehcache:分布式Ehcache,包括Ehcache的核心和Terracotta的库
- ehcache-monitor:企业级监控和管理
- ehcache-web:为Java Servlet Container提供缓存、gzip压缩支持的filters
- ehcache-jcache:JSR107 JCACHE的实现
- ehcache-jgroupsreplication:使用JGroup的复制
- ehcache-jmsreplication:使用JMS的复制
- ehcache-openjpa:OpenJPA插件
- ehcache-server:war内部署或者单独部署的RESTful cache server
- ehcache-unlockedreadsview:允许Terracotta cache的无锁读
- ehcache-debugger:记录RMI分布式调用事件
- Ehcache for Ruby:Jruby and Rails支持
Ehcache的结构设计概览: 三、核心定义: cache manager:缓存管理器,以前是只允许单例的,不过现在也可以多实例了 cache:缓存管理器内可以放置若干cache,存放数据的实质,所有cache都实现了Ehcache接口 element:单条缓存数据的组成单位 system of record(SOR):可以取到真实数据的组件,可以是真正的业务逻辑、外部接口调用、存放真实数据的数据库等等,缓存就是从SOR中读取或者写入到SOR中去的。 代码示例: Java代码 - CacheManager manager = CacheManager.newInstance("src/config/ehcache.xml");
- manager.addCache("testCache");
- Cache test = singletonManager.getCache("testCache");
- test.put(new Element("key1", "value1"));
- manager.shutdown();
当然,也支持这种类似DSL的配置方式,配置都是可以在运行时动态修改的: Java代码 - Cache testCache = new Cache(
- new CacheConfiguration("testCache", maxElements)
- .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
- .overflowToDisk(true)
- .eternal(false)
- .timeToLiveSeconds(60)
- .timeToIdleSeconds(30)
- .diskPersistent(false)
- .diskExpiryThreadIntervalSeconds(0));
事务的例子: Java代码 - Ehcache cache = cacheManager.getEhcache("xaCache");
- transactionManager.begin();
- try {
- Element e = cache.get(key);
- Object result = complexService.doStuff(element.getValue());
- cache.put(new Element(key, result));
- complexService.doMoreStuff(result);
- transactionManager.commit();
- } catch (Exception e) {
- transactionManager.rollback();
- }
四、一致性模型: 说到一致性,数据库的一致性是怎样的?不妨先来回顾一下数据库的几个隔离级别: 未提交读(Read Uncommitted):在读数据时不会检查或使用任何锁。因此,在这种隔离级别中可能读取到没有提交的数据。会出现脏读、不可重复读、幻象读。 已提交读(Read Committed):只读取提交的数据并等待其他事务释放排他锁。读数据的共享锁在读操作完成后立即释放。已提交读是数据库的默认隔离级别。会出现不可重复读、幻象读。 可重复读(Repeatable Read):像已提交读级别那样读数据,但会保持共享锁直到事务结束。会出现幻象读。 可序列化(Serializable):工作方式类似于可重复读。但它不仅会锁定受影响的数据,还会锁定这个范围,这就阻止了新数据插入查询所涉及的范围。 基于以上,再来对比思考下面的一致性模型: 1、强一致性模型:系统中的某个数据被成功更新(事务成功返回)后,后续任何对该数据的读取操作都得到更新后的值。这是传统关系数据库提供的一致性模型,也是关系数据库深受人们喜爱的原因之一。强一致性模型下的性能消耗通常是最大的。 2、弱一致性模型:系统中的某个数据被更新后,后续对该数据的读取操作得到的不一定是更新后的值,这种情况下通常有个“不一致性时间窗口”存在:即数据更新完成后在经过这个时间窗口,后续读取操作就能够得到更新后的值。 3、最终一致性模型:属于弱一致性的一种,即某个数据被更新后,如果该数据后续没有被再次更新,那么最终所有的读取操作都会返回更新后的值。 最终一致性模型包含如下几个必要属性,都比较好理解: - 读写一致:某线程A,更新某条数据以后,后续的访问全部都能取得更新后的数据。
- 会话内一致:它本质上和上面那一条是一致的,某用户更改了数据,只要会话还存在,后续他取得的所有数据都必须是更改后的数据。
- 单调读一致:如果一个进程可以看到当前的值,那么后续的访问不能返回之前的值。
- 单调写一致:对同一进程内的写行为必须是保序的,否则,写完毕的结果就是不可预期的了。
4、Bulk Load:这种模型是基于批量加载数据到缓存里面的场景而优化的,没有引入锁和常规的淘汰算法这些降低性能的东西,它和最终一致性模型很像,但是有批量、高速写和弱一致性保证的机制。 这样几个API也会影响到一致性的结果: 1、显式锁(Explicit Locking ):如果我们本身就配置为强一致性,那么自然所有的缓存操作都具备事务性质。而如果我们配置成最终一致性时,再在外部使用显式锁API,也可以达到事务的效果。当然这样的锁可以控制得更细粒度,但是依然可能存在竞争和线程阻塞。 2、无锁可读取视图(UnlockedReadsView):一个允许脏读的decorator,它只能用在强一致性的配置下,它通过申请一个特殊的写锁来比完全的强一致性配置提升性能。 举例如下,xml配置为强一致性模型: Xml代码 - <cache name="myCache"
- maxElementsInMemory="500"
- eternal="false"
- overflowToDisk="false"
- <terracotta clustered="true" consistency="strong" />
- </cache>
但是使用UnlockedReadsView: Java代码 - Cache cache = cacheManager.getEhcache("myCache");
- UnlockedReadsView unlockedReadsView = new UnlockedReadsView(cache, "myUnlockedCache");
3、原子方法(Atomic methods):方法执行是原子化 的,即CAS操作(Compare and Swap)。CAS最终也实现了强一致性的效果,但不同的是,它是采用乐观锁而不是悲观锁来实现的。在乐观锁机制下,更新的操作可能不成功,因为在这过程 中可能会有其他线程对同一条数据进行变更,那么在失败后需要重新执行更新操作。现代的CPU都支持CAS原语了。 Java代码 - cache.putIfAbsent(Element element);
- cache.replace(Element oldOne, Element newOne);
- cache.remove(Element);
五、缓存拓扑类型: 1、独立缓存(Standalone Ehcache):这样的缓存应用节点都是独立的,互相不通信。 2、分布式缓存(Distributed Ehcache):数据存储在Terracotta的服务器阵列(Terracotta Server Array,TSA)中,但是最近使用的数据,可以存储在各个应用节点中。 逻辑视角: L1缓存就在各个应用节点上,而L2缓存则放在Cache Server阵列中。 组网视角:
模型存储视角: L1级缓存是没有持久化存储的。另外,从缓存数据量上看,server端远大于应用节点。 3、复制式缓存(Replicated Ehcache):缓存数据时同时存放在多个应用节点的,数据复制和失效的事件以同步或者异步的形式在各个集群节点间传播。上述事件到来时,会阻塞写线程的操作。在这种模式下,只有弱一致性模型。 它有如下几种事件传播机制:RMI、JGroups、JMS和Cache Server。 RMI模式下,所有节点全部对等: JGroup模式:可以配置单播或者多播,协议栈和配置都非常灵活。 Xml代码 - <cacheManagerPeerProviderFactory
- class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory"
- properties="connect=UDP(mcast_addr=231.12.21.132;mcast_port=45566;):PING:
- MERGE2:FD_SOCK:VERIFY_SUSPECT:pbcast.NAKACK:UNICAST:pbcast.STABLE:FRAG:pbcast.GMS"
- propertySeparator="::"
- />
JMS模式:这种模式的核心就是一个消息队列,每个应用节点都订阅预先定义好的主题,同时,节点有元素更新时,也会发布更新元素到主题中去。JMS规范实现者上,Open MQ和Active MQ这两个,Ehcache的兼容性都已经测试过。 Cache Server模式:这种模式下存在主从节点,通信可以通过RESTful的API或者SOAP。 无论上面哪个模式,更新事件又可以分为updateViaCopy或updateViaInvalidate,后者只是发送一个过期消息,效率要高得多。 复制式缓存容易出现数据不一致的问题,如果这成为一个问题,可以考虑使用数据同步分发的机制。 即便不采用分布式缓存和复制式缓存,依然会出现一些不好的行为,比如: 缓存漂移(Cache Drift):每个应用节点只管理自己的缓存,在更新某个节点的时候,不会影响到其他的节点,这样数据之间可能就不同步了。这在web会话数据缓存中情况尤甚。 数据库瓶颈(Database Bottlenecks ):对于单实例的应用来说,缓存可以保护数据库的读风暴;但是,在集群的环境下,每一个应用节点都要定期保持数据最新,节点越多,要维持这样的情况对数据库的开销也越大。 六、存储方式: 1、堆内存储:速度快,但是容量有限。 2、堆外(OffHeapStore)存储:被称为 BigMemory,只在企业版本的Ehcache中提供,原理是利用nio的DirectByteBuffers实现,比存储到磁盘上快,而且完全不受 GC的影响,可以保证响应时间的稳定性;但是direct buffer的在分配上的开销要比heap buffer大,而且要求必须以字节数组方式存储,因此对象必须在存储过程中进行序列化,读取则进行反序列化操作,它的速度大约比堆内存储慢一个数量级。 (注:direct buffer不受GC影响,但是direct buffer归属的的JAVA对象是在堆上且能够被GC回收的,一旦它被回收,JVM将释放direct buffer的堆外空间。) 3、磁盘存储。 七、缓存使用模式: cache-aside:直接操作。先询问cache某条缓存数据是否存在,存在的话直接从cache中返回数据,绕过SOR;如果不存在,从SOR中取得数据,然后再放入cache中。 Java代码 - public V readSomeData(K key)
- {
- Element element;
- if ((element = cache.get(key)) != null) {
- return element.getValue();
- }
- if (value = readDataFromDataStore(key)) != null) {
- cache.put(new Element(key, value));
- }
- return value;
- }
cache-as-sor:结合了read-through、write-through或write-behind操作,通过给SOR增加了一层代理,对外部应用访问来说,它不用区别数据是从缓存中还是从SOR中取得的。 read-through。 write-through。 write-behind(write-back):既将写的过程变为异步的,又进一步延迟写入数据的过程。 Copy Cache的两个模式:CopyOnRead和CopyOnWrite。 CopyOnRead指的是在读缓存数据的请求到达时,如果发现数据已经过期,需要重新从源处获取,发起的copy element的操作(pull); CopyOnWrite则是发生在真实数据写入缓存时,发起的更新其他节点的copy element的操作(push)。 前者适合在不允许多个线程访问同一个element的时候使用,后者则允许你自由控制缓存更新通知的时机。 更多push和pull的变化和不同,也可参见这里。 八、多种配置方式: 包括配置文件、声明式配置、编程式配置,甚至通过指定构造器的参数来完成配置,配置设计的原则包括: 所有配置要放到一起 缓存的配置可以很容易在开发阶段、运行时修改 错误的配置能够在程序启动时发现,在运行时修改出错则需要抛出运行时异常 提供默认配置,几乎所有的配置都是可选的,都有默认值 九、自动资源控制(Automatic Resource Control,ARC): 它是提供了一种智能途径来控制缓存,调优性能。特性包括: 内存内缓存对象大小的控制,避免OOM出现 池化(cache manager级别)的缓存大小获取,避免单独计算缓存大小的消耗 灵活的独立基于层的大小计算能力,下图中可以看到,不同层的大小都是可以单独控制的 可以统计字节大小、缓存条目数和百分比 优化高命中数据的获取,以提升性能,参见下面对缓存数据在不同层之间的流转的介绍 缓存数据的流转包括了这样几种行为: Flush:缓存条目向低层次移动。 Fault:从低层拷贝一个对象到高层。在获取缓存的过程中,某一层发现自己的该缓存条目已经失效,就触发了Fault行为。 Eviction:把缓存条目除去。 Expiration:失效状态。 Pinning:强制缓存条目保持在某一层。 下面的图反映了数据在各个层之间的流转,也反映了数据的生命周期: 十、监控功能: 监控的拓扑: 每个应用节点部署一个监控探针,通过TCP协议与监控服务器联系,最终将数据提供给富文本客户端或者监控操作服务器。 十一、广域网复制: 缓存数据复制方面,Ehcache允许两个地理位置各异的节点在广域网下维持数据一致性,同时它提供了这样几种方案(注:下面的示例都只绘制了两个节点的情形,实际可以推广到N个节点): 第一种方案:Terracotta Active/Mirror Replication。 这种方案下,服务端包含一个活跃节点,一个备份节点;各个应用节点全部靠该活跃节点提供读写服务。这种方式最简单,管理容易;但是,需要寄希望于理想的网络状况,服务器之间和客户端到服务器之间都存在走WAN的情况,这样的方案其实最不稳定。 第二种方案:Transactional Cache Manager Replication。 这 种方案下,数据读取不需要经过WAN,写入数据时写入两份,分别由两个cache manager处理,一份在本地Server,一份到其他Server去。这种方案下读的吞吐量较高而且延迟较低;但是需要引入一个XA事务管理器,两个 cache manager写两份数据导致写开销较大,而且过WAN的写延迟依然可能导致系统响应的瓶颈。 第三种方案:Messaging based (AMQ) replication。 这 种方案下,引入了批量处理和队列,用以减缓WAN的瓶颈出现,同时,把处理读请求和复制逻辑从Server Array物理上就剥离开,避免了WAN情况恶化对节点读取业务的影响。这种方案要较高的吞吐量和较低的延迟,读/复制的分离保证了可以提供完备的消息分 发保证、冲突处理等特性;但是它较为复杂,而且还需要一个消息总线。 有一些Ehcache特性应用较少或者比较边缘化,没有提到,例如对于JMX的支持;还有一些则是有类似的特性和介绍了,例如对于WEB的支持,请参见我这篇关于OSCache的解读,其中的“web支持”一节有详细的原理分析。 最后,关于Ehcache的性能比对,下面这张图来自Ehcache的创始人Greg Luck的blog: put/get上Ehcache要500-1000倍快过Memcached。原因何在?他自己分析道:“In-process caching and asynchronous replication are a clear performance winner”。有关它详细的内容还是请参阅他的blog吧。
转自: http://raychase.iteye.com/blog/1545906
Windows 7,在使用 JGroups 的时候提示错误: java.lang.RuntimeException: the type of the stack (IPv6) and the user supplied addresses (IPv4) don't match: /xxx.xxx.xxx.xxx. 在操作系统中禁用 IPv6 是没用的,这个需要在 JVM 中禁用 IPv6 特性,加入以下参数即可: -Djava.net.preferIPv4Stack=true
摘要: Spring框架提供了构造Web应用程序的全能MVC模块。Spring MVC分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行制定。是一个标准的MVC框架。 那你猜一猜哪一部分应该是哪一部分? SpringMVC框架图 SpringMVC接口解释 DispatcherServlet接口: Spring提供的前端... 阅读全文
原文: http://www.cnblogs.com/web8cn/archive/2012/07/19/2598745.html
引子 “请写一个Singleton。”面试官微笑着和我说。 “这可真简单。”我心里想着,并在白板上写下了下面的Singleton实现: class Singleton { public: static Singleton& Instance() { static Singleton singleton; return singleton; } private: Singleton() { }; }; “那请你讲解一下该实现的各组成。”面试官的脸上仍然带着微笑。 “首先要说的就是Singleton的构造函数。由于Singleton限制其类型实例有且只能有一个,因此我们应通过将构造函数设置为非公有 来保证其不会被用户代码随意创建。而在类型实例访问函数中,我们通过局部静态变量达到实例仅有一个的要求。另外,通过该静态变量,我们可以将该实例的创建 延迟到实例访问函数被调用时才执行,以提高程序的启动速度。” 保护 “说得不错,而且更可贵的是你能注意到对构造函数进行保护。毕竟中间件代码需要非常严谨才能防止用户代码的误用。那么,除了构造函数以外,我们还需要对哪些组成进行保护?” “还需要保护的有拷贝构造函数,析构函数以及赋值运算符。或许,我们还需要考虑取址运算符。这是因为编译器会在需要的时候为这些成员创建一个默认的实现。” “那你能详细说一下编译器会在什么情况下创建默认实现,以及创建这些默认实现的原因吗?”面试官继续问道。 “在这些成员没有被声明的情况下,编译器将使用一系列默认行为:对实例的构造就是分配一部分内存,而不对该部分内存做任何事情;对实例的拷贝也 仅仅是将原实例中的内存按位拷贝到新实例中;而赋值运算符也是对类型实例所拥有的各信息进行拷贝。而在某些情况下,这些默认行为不再满足条件,那么编译器 将尝试根据已有信息创建这些成员的默认实现。这些影响因素可以分为几种:类型所提供的相应成员,类型中的虚函数以及类型的虚基类。” “就以构造函数为例,如果当前类型的成员或基类提供了由用户定义的构造函数,那么仅进行内存拷贝可能已经不是正确的行为。这是因为该成员的构造 函数可能包含了成员初始化,成员函数调用等众多执行逻辑。此时编译器就需要为这个类型生成一个默认构造函数,以执行对成员或基类构造函数的调用。另外,如 果一个类型声明了一个虚函数,那么编译器仍需要生成一个构造函数,以初始化指向该虚函数表的指针。如果一个类型的各个派生类中拥有一个虚基类,那么编译器 同样需要生成构造函数,以初始化该虚基类的位置。这些情况同样需要在拷贝构造函数中考虑:如果一个类型的成员变量拥有一个拷贝构造函数,或者其基类拥有一 个拷贝构造函数,位拷贝就不再满足要求了,因为拷贝构造函数内可能执行了某些并不是位拷贝的逻辑。同时如果一个类型声明了虚函数,拷贝构造函数需要根据目 标类型初始化虚函数表指针。如基类实例经过拷贝后,其虚函数表指针不应指向派生类的虚函数表。同理,如果一个类型的各个派生类中拥有一个虚派生,那么编译 器也应为其生成拷贝构造函数,以正确设置各个虚基类的偏移。” “当然,析构函数的情况则略为简单一些:只需要调用其成员的析构函数以及基类的析构函数即可,而不需要再考虑对虚基类偏移的设置及虚函数表指针的设置。” “在这些默认实现中,类型实例的各个原生类型成员并没有得到初始化的机会。但是这一般被认为是软件开发人员的责任,而不是编译器的责任。”说完这些,我长出一口气,心里也暗自庆幸曾经研究过该部分内容。 “你刚才提到需要考虑保护取址运算符,是吗?我想知道。” “好的。首先要声明的是,几乎所有的人都会认为对取址运算符的重载是邪恶的。甚至说,boost为了防止该行为所产生的错误更是提供了 addressof()函数。而另一方面,我们需要讨论用户为什么要用取址运算符。Singleton所返回的常常是一个引用,对引用进行取址将得到相应 类型的指针。而从语法上来说,引用和指针的最大区别在于是否可以被delete关键字删除以及是否可以为NULL。但是Singleton返回一个引用也 就表示其生存期由非用户代码所管理。因此使用取址运算符获得指针后又用delete关键字删除Singleton所返回的实例明显是一个用户错误。综上所 述,通过将取址运算符设置为私有没有多少意义。” 重用 “好的,现在我们换个话题。如果我现在有几个类型都需要实现为Singleton,那我应怎样使用你所编写的这段代码呢?” 刚刚还在洋洋自得的我恍然大悟:这个Singleton实现是无法重用的。没办法,只好一边想一边说:“一般来说,较为流行的重用方法一共有三 种:组合、派生以及模板。首先可以想到的是,对Singleton的重用仅仅是对Instance()函数的重用,因此通过从Singleton派生以继 承该函数的实现是一个很好的选择。而Instance()函数如果能根据实际类型更改返回类型则更好了。因此奇异递归模板(CRTP,The Curiously Recurring Template Pattern)模式则是一个非常好的选择。”于是我在白板上飞快地写下了下面的代码: 1 template <typename T> 2 class Singleton 3 { 4 public: 5 static T& Instance() 6 { 7 static T s_Instance; 8 return s_Instance; 9 } 10 11 protected: 12 Singleton(void) {} 13 ~Singleton(void) {} 14 15 private: 16 Singleton(const Singleton& rhs) {} 17 Singleton& operator = (const Singleton& rhs) {} 18 }; 同时我也在白板上写下了对该Singleton实现进行重用的方法: 1 class SingletonInstance : public Singleton<SingletonInstance>… “在需要重用该Singleton实现时,我们仅仅需要从Singleton派生并将Singleton的泛型参数设置为该类型即可。” 生存期管理 “我看你在实现中使用了静态变量,那你是否能介绍一下上面Singleton实现中有关生存期的一些特征吗?毕竟生存期管理也是编程中的一个重要话题。”面试官提出了下一个问题。 “嗯,让我想一想。我认为对Singleton的生存期特性的讨论需要分为两个方面:Singleton内使用的静态变量的生存期以及 Singleton外在用户代码中所表现的生存期。Singleton内使用的静态变量是一个局部静态变量,因此只有在Singleton的 Instance()函数被调用时其才会被创建,从而拥有了延迟初始化(Lazy)的效果,提高了程序的启动性能。同时该实例将生存至程序执行完毕。而就 Singleton的用户代码而言,其生存期贯穿于整个程序生命周期,从程序启动开始直到程序执行完毕。当然,Singleton在生存期上的一个缺陷就 是创建和析构时的不确定性。由于Singleton实例会在Instance()函数被访问时被创建,因此在某处新添加的一处对Singleton的访问 将可能导致Singleton的生存期发生变化。如果其依赖于其它组成,如另一个Singleton,那么对它们的生存期进行管理将成为一个灾难。甚至可 以说,还不如不用Singleton,而使用明确的实例生存期管理。” “很好,你能提到程序初始化及关闭时单件的构造及析构顺序的不确定可能导致致命的错误这一情况。可以说,这是通过局部静态变量实现 Singleton的一个重要缺点。而对于你所提到的多个Singleton之间相互关联所导致的生存期管理问题,你是否有解决该问题的方法呢?” 我突然间意识到自己给自己出了一个难题:“有,我们可以将Singleton的实现更改为使用全局静态变量,并将这些全局静态变量在文件中按照特定顺序排序即可。” “但是这样的话,静态变量将使用eager initialization的方式完成初始化,可能会对性能影响较大。其实,我想听你说的是,对于具有关联的两个Singleton,对它们进行使用的 代码常常局限在同一区域内。该问题的一个解决方法常常是将对它们进行使用的管理逻辑实现为Singleton,而在内部逻辑中对它们进行明确的生存期管 理。但不用担心,因为这个答案也过于经验之谈。那么下一个问题,你既然提到了全局静态变量能解决这个问题,那是否可以讲解一下全局静态变量的生命周期是怎 样的呢?” “编译器会在程序的main()函数执行之前插入一段代码,用来初始化全局变量。当然,静态变量也包含在内。该过程被称为静态初始化。” “嗯,很好。使用全局静态变量实现Singleton的确会对性能造成一定影响。但是你是否注意到它也有一定的优点呢?” 见我许久没有回答,面试官主动帮我解了围:“是线程安全性。由于在静态初始化时用户代码还没有来得及执行,因此其常常处于单线程环境下,从而保 证了Singleton真的只有一个实例。当然,这并不是一个好的解决方法。所以,我们来谈谈Singleton的多线程实现吧。” 多线程 “首先请你写一个线程安全的Singleton实现。” 我拿起笔,在白板上写下早已烂熟于心的多线程安全实现: 1 template <typename T> 2 class Singleton 3 { 4 public: 5 static T& Instance() 6 { 7 if (m_pInstance == NULL) 8 { 9 Lock lock; 10 if (m_pInstance == NULL) 11 { 12 m_pInstance = new T(); 13 atexit(Destroy); 14 } 15 return *m_pInstance; 16 } 17 return *m_pInstance; 18 } 19 20 protected: 21 Singleton(void) {} 22 ~Singleton(void) {} 23 24 private: 25 Singleton(const Singleton& rhs) {} 26 Singleton& operator = (const Singleton& rhs) {} 27 28 void Destroy() 29 { 30 if (m_pInstance != NULL) 31 delete m_pInstance; 32 m_pInstance = NULL; 33 } 34 35 static T* volatile m_pInstance; 36 }; 37 38 template <typename T> 39 T* Singleton<T>::m_pInstance = NULL; “写得很精彩。那你是否能逐行讲解一下你写的这个Singleton实现呢?” “好的。首先,我使用了一个指针记录创建的Singleton实例,而不再是局部静态变量。这是因为局部静态变量可能在多线程环境下出现问题。” “我想插一句话,为什么局部静态变量会在多线程环境下出现问题?” “这是由局部静态变量的实际实现所决定的。为了能满足局部静态变量只被初始化一次的需求,很多编译器会通过一个全局的标志位记录该静态变量是否已经被初始化的信息。那么,对静态变量进行初始化的伪码就变成下面这个样子:”。 1 bool flag = false; 2 if (!flag) 3 { 4 flag = true; 5 staticVar = initStatic(); 6 } “那么在第一个线程执行完对flag的检查并进入if分支后,第二个线程将可能被启动,从而也进入if分支。这样,两个线程都将执行对静态变量 的初始化。因此在这里,我使用了指针,并在对指针进行赋值之前使用锁保证在同一时间内只能有一个线程对指针进行初始化。同时基于性能的考虑,我们需要在每 次访问实例之前检查指针是否已经经过初始化,以避免每次对Singleton的访问都需要请求对锁的控制权。” “同时,”我咽了口口水继续说,“因为new运算符的调用分为分配内存、调用构造函数以及为指针赋值三步,就像下面的构造函数调用:” 1 SingletonInstance pInstance = new SingletonInstance(); “这行代码会转化为以下形式:” 1 SingletonInstance pHeap = __new(sizeof(SingletonInstance)); 2 pHeap->SingletonInstance::SingletonInstance(); 3 SingletonInstance pInstance = pHeap; “这样转换是因为在C++标准中规定,如果内存分配失败,或者构造函数没有成功执行, new运算符所返回的将是空。一般情况下,编译器不会轻易调整这三步的执行顺序,但是在满足特定条件时,如构造函数不会抛出异常等,编译器可能出于优化的 目的将第一步和第三步合并为同一步:” 1 SingletonInstance pInstance = __new(sizeof(SingletonInstance)); 2 pInstance->SingletonInstance::SingletonInstance(); “这样就可能导致其中一个线程在完成了内存分配后就被切换到另一线程,而另一线程对Singleton的再次访问将由于pInstance已经 赋值而越过if分支,从而返回一个不完整的对象。因此,我在这个实现中为静态成员指针添加了volatile关键字。该关键字的实际意义是由其修饰的变量 可能会被意想不到地改变,因此每次对其所修饰的变量进行操作都需要从内存中取得它的实际值。它可以用来阻止编译器对指令顺序的调整。只是由于该关键字所提 供的禁止重排代码是假定在单线程环境下的,因此并不能禁止多线程环境下的指令重排。” “最后来说说我对atexit()关键字的使用。在通过new关键字创建类型实例的时候,我们同时通过atexit()函数注册了释放该实例的 函数,从而保证了这些实例能够在程序退出前正确地析构。该函数的特性也能保证后被创建的实例首先被析构。其实,对静态类型实例进行析构的过程与前面所提到 的在main()函数执行之前插入静态初始化逻辑相对应。” 引用还是指针 “既然你在实现中使用了指针,为什么仍然在Instance()函数中返回引用呢?”面试官又抛出了新的问题。 “这是因为Singleton返回的实例的生存期是由Singleton本身所决定的,而不是用户代码。我们知道,指针和引用在语法上的最大区 别就是指针可以为NULL,并可以通过delete运算符删除指针所指的实例,而引用则不可以。由该语法区别引申出的语义区别之一就是这些实例的生存期意 义:通过引用所返回的实例,生存期由非用户代码管理,而通过指针返回的实例,其可能在某个时间点没有被创建,或是可以被删除的。但是这两条 Singleton都不满足,因此在这里,我使用指针,而不是引用。” “指针和引用除了你提到的这些之外,还有其它的区别吗?” “有的。指针和引用的区别主要存在于几个方面。从低层次向高层次上来说,分为编译器实现上的,语法上的以及语义上的区别。就编译器的实现来说, 声明一个引用并没有为引用分配内存,而仅仅是为该变量赋予了一个别名。而声明一个指针则分配了内存。这种实现上的差异就导致了语法上的众多区别:对引用进 行更改将导致其原本指向的实例被赋值,而对指针进行更改将导致其指向另一个实例;引用将永远指向一个类型实例,从而导致其不能为NULL,并由于该限制而 导致了众多语法上的区别,如dynamic_cast对引用和指针在无法成功进行转化时的行为不一致。而就语义而言,前面所提到的生存期语义是一个区别, 同时一个返回引用的函数常常保证其返回结果有效。一般来说,语义区别的根源常常是语法上的区别,因此上面的语义区别仅仅是列举了一些例子,而真正语义上的 差别常常需要考虑它们的语境。” “你在前面说到了你的多线程内部实现使用了指针,而返回类型是引用。在编写过程中,你是否考虑了实例构造不成功的情况,如new运算符运行失败?” “是的。在和其它人进行讨论的过程中,大家对于这种问题有各自的理解。首先,对一个实例的构造将可能在两处抛出异常:new运算符的执行以及构 造函数抛出的异常。对于new运算符,我想说的是几点。对于某些操作系统,例如Windows,其常常使用虚拟地址,因此其运行常常不受物理内存实际大小 的限制。而对于构造函数中抛出的异常,我们有两种策略可以选择:在构造函数内对异常进行处理,以及在构造函数之外对异常进行处理。在构造函数内对异常进行 处理可以保证类型实例处于一个有效的状态,但一般不是我们想要的实例状态。这样一个实例会导致后面对它的使用更为繁琐,例如需要更多的处理逻辑或再次导致 程序执行异常。反过来,在构造函数之外对异常进行处理常常是更好的选择,因为软件开发人员可以根据产生异常时所构造的实例的状态将一定范围内的各个变量更 改为合法的状态。举例来说,我们在一个函数中尝试创建一对相互关联的类型实例,那么在一个实例的构造函数抛出了异常时,我们不应该在构造函数里对该实例的 状态进行维护,因为前一个实例的构造是按照后一个实例会正常创建来进行的。相对来说,放弃后一个实例,并将前一个实例删除是一个比较好的选择。” 我在白板上比划了一下,继续说到:“我们知道,异常有两个非常明显的缺陷:效率,以及对代码的污染。在太小的粒度中使用异常,就会导致异常使用次数的增加,对于效率以及代码的整洁型都是伤害。同样地,对拷贝构造函数等组成常常需要使用类似的原则。” “反过来说,Singleton的使用也可以保持着这种原则。Singleton仅仅是一个包装好的全局实例,对其的创建如果一旦不成功,在较高层次上保持正常状态同样是一个较好的选择。” Anti-Patten “既然你提到了Singleton仅仅是一个包装好的全局变量,那你能说说它和全局变量的相同与不同么?” “单件可以说是全局变量的替代品。其拥有全局变量的众多特点:全局可见且贯穿应用程序的整个生命周期。除此之外,单件模式还拥有一些全局变量所 不具有的性质:同一类型的对象实例只能有一个,同时适当的实现还拥有延迟初始化(Lazy)的功能,可以避免耗时的全局变量初始化所导致的启动速度不佳等 问题。要说明的是,Singleton的最主要目的并不是作为一个全局变量使用,而是保证类型实例有且仅有一个。它所具有的全局访问特性仅仅是它的一个副 作用。但正是这个副作用使它更类似于包装好的全局变量,从而允许各部分代码对其直接进行操作。软件开发人员需要通过仔细地阅读各部分对其进行操作的代码才 能了解其真正的使用方式,而不能通过接口得到组件依赖性等信息。如果Singleton记录了程序的运行状态,那么该状态将是一个全局状态。各个组件对其 进行操作的调用时序将变得十分重要,从而使各个组件之间存在着一种隐式的依赖。” “从语法上来讲,首先Singleton模式实际上将类型功能与类型实例个数限制的代码混合在了一起,违反了SRP。其次Singleton模式在Instance()函数中将创建一个确定的类型,从而禁止了通过多态提供另一种实现的可能。” “但是从系统的角度来讲,对Singleton的使用则是无法避免的:假设一个系统拥有成百上千个服务,那么对它们的传递将会成为系统的一个灾 难。从微软所提供的众多类库上来看,其常常提供一种方式获得服务的函数,如GetService()等。另外一个可以减轻Singleton模式所带来不 良影响的方法则是为Singleton模式提供无状态或状态关联很小的实现。” “也就是说,Singleton本身并不是一个非常差的模式,对其使用的关键在于何时使用它并正确的使用它。” 面试官抬起手腕看了看时间:“好了,时间已经到了。你的C++功底已经很好了。我相信,我们会在不久的将来成为同事。” 笔者注:这本是Writing Patterns Line by Line的一篇文章,但最后想想,写模式的人太多了,我还是省省吧。。。 下一篇回归WPF,环境刚好。可能中间穿插些别的内容,比如HTML5,JS,安全等等。 头一次写小品文,不知道效果是不是好。因为这种文章的特点是知识点分散,而且隐藏在文章的每一句话中。。。好处就是写起来轻松,呵呵。。。
错误 Server Tomcat v6.0 Server at localhost was unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor. 修改 workspace\.metadata\.plugins\org.eclipse.wst.server.core\servers.xml文件。 <servers> <server hostname="localhost" id="JBoss v5.0 at localhost" name="JBoss v5.0 at localhost" runtime-id="JBoss v5.0" server-type="org.eclipse.jst.server.generic.jboss5" server-type-id="org.eclipse.jst.server.generic.jboss5" start-timeout="1000" stop- timeout="15" timestamp="0"> <map jndiPort="1099" key="generic_server_instance_properties" port="8090" serverAddress="127.0.0.1" serverConfig="default"/> </server> </servers> 把 start-timeout="45" 改为 start-timeout="1000" 或者更长 重启eclipse就可以了。 这个原因就是:启动tomcat需要的时间比45秒大了,Eclipse会判断tomcat在默认的时间是否启动了,如果在默认45秒没有启动就会报错了。
在看java performance的时候看到一些同步的名词,偏向锁、轻量级锁之类的,于是想先了解一下虚拟机中的锁机制,于是找到了这篇文章。发现是《深入理解Java虚拟机:JVM高级特性与最佳实践》一书的章节,讲得干脆好懂,差点就有要去买一本的冲动-----还是明天吧。以下是文章转载: ------------------------------------------------------------------------------------------------------------------------------------------------------------------ 高效并发是JDK1.6的一个重要主题,HotSpot虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋 (AdaptiveSpinning)、锁削除(Lock Elimination)、锁膨胀(Lock Coarsening)、轻量级锁(LightweightLocking)、偏向锁(BiasedLocking)等,这些技术都是为了在线程之间更高 效地共享数据,以及解决竞争问题,从而提高程序的执行效率。 13.3.1 自旋锁与自适应自旋 前面我们讨论互斥同步的时候,提到了互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发 性能带来了很大的压力。同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值 得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一会”,但不放弃处理器的执行时间,看 看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只须让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。 自旋锁在JDK 1.4.2中就已经引入,只不过默认是关闭的,可以使用-XX:+UseSpinning参数来开启,在JDK1.6中就已经改为默认开启了。自旋等待不 能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,所以如果锁被占用的时间很短,自旋等待的效 果就会非常好,反之如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能的浪费。因此自旋等待的时间 必须要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了。自旋次数的默认值是10次,用户可以使用参数 -XX:PreBlockSpin来更改。 在JDK1.6中引入了自适应的自旋锁。自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同 一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更 长的时间,比如100个循环。另一方面,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。有了 自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虚拟机就会变得越来越“聪明”了。 13.3.2 锁削除 锁削除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。锁削除的主要判定依据来源于逃逸分析 的数据支持(第11章已经讲解过逃逸分析技术),如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上数据对 待,认为它们是线程私有的,同步加锁自然就无须进行。 也许读者会有疑问,变量是否逃逸,对于虚拟机来说需要使用数据流分析来确定,但是程序员自己应该是很清楚的,怎么会在明知道不存在数据争用的情况下要 求同步呢?答案是有许多同步措施并不是程序员自己加入的,同步的代码在Java程序中的普遍程度也许超过了大部分读者的想象。我们来看看下面代码清单 13-6中的例子,这段非常简单的代码仅仅是输出三个字符串相加的结果,无论是源码字面上还是程序语义上都没有同步。 代码清单 13-6 一段看起来没有同步的代码 Java代码 - public String concatString(String s1, String s2, String s3) {
- return s1 + s2 + s3;
- }
- public String concatString(String s1, String s2, String s3) {
- return s1 + s2 + s3;
- }
我们也知道,由于String是一个不可变的类,对字符串的连接操作总是通过生成新的String对象来进行的,因此Javac编译器会对String 连接做自动优化。在JDK 1.5之前,会转化为StringBuffer对象的连续append()操作,在JDK1.5及以后的版本中,会转化为StringBuilder对象 的连续append()操作。即代码清单13-6中的代码可能会变成代码清单13-7的样子 。 代码清单 13-7 Javac转化后的字符串连接操作 Java代码 - public String concatString(String s1, String s2, String s3) {
- StringBuffer sb = new StringBuffer();
- sb.append(s1);
- sb.append(s2);
- sb.append(s3);
- return sb.toString();
- }
- public String concatString(String s1, String s2, String s3) {
- StringBuffer sb = new StringBuffer();
- sb.append(s1);
- sb.append(s2);
- sb.append(s3);
- return sb.toString();
- }
(注1:实事求是地说,既然谈到锁削除与逃逸分析,那虚拟机就不可能是JDK 1.5之前的版本,所以实际上会转化为非线程安全的StringBuilder来完成字符串拼接,并不会加锁。但是这也不影响笔者用这个例子证明Java对象中同步的普遍性。) 现在大家还认为这段代码没有涉及同步吗?每个StringBuffer.append()方法中都有一个同步块,锁就是sb对象。虚拟机观察变量 sb,很快就会发现它的动态作用域被限制在concatString()方法内部。也就是sb的所有引用永远不会“逃逸”到concatString() 方法之外,其他线程无法访问到它,所以这里虽然有锁,但是可以被安全地削除掉,在即时编译之后,这段代码就会忽略掉所有的同步而直接执行了。 13.3.3 锁膨胀 原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小——只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快地拿到锁。 大部分情况下,上面的原则都是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。 上面代码清单13-7中连续的append()方法就属于这类情况。如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围 扩展(膨胀)到整个操作序列的外部,以代码清单13-7为例,就是扩展到第一个append()操作之前直至最后一个append()操作之后,这样只需 要加锁一次就可以了。 13.3.4 轻量级锁 轻量级锁是JDK1.6之中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就被称为“重 量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的 性能消耗。 要理解轻量级锁,以及后面会讲到的偏向锁的原理和运作过程,必须从HotSpot虚拟机的对象(对象头部分)的内存布局开始介绍。HotSpot虚拟 机的对象头(ObjectHeader)分为两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄 (Generational GCAge)等,这部分数据的长度在32位和64位的虚拟机中分别为32个和64个Bits,官方称它为“MarkWord”,它是实现轻量级锁和偏向锁 的关键。另外一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。 对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,MarkWord被设计成一个非固定的数据结构以便在极小的空间内存 储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机中对象未被锁定的状态下,MarkWord的32个Bits 空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0,在其他 状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如表13-1所示。 表13-1 HotSpot虚拟机对象头Mark Word 存储内容 | 标志位 | 状态 | 对象哈希码、对象分代年龄 | 01 | 未锁定 | 指向锁记录的指针 | 00 | 轻量级锁定 | 指向重量级锁的指针 | 10 | 膨胀(重量级锁定) | 空,不需要记录信息 | 11 | GC标记 | 偏向线程ID、偏向时间戳、对象分代年龄 | 01 | 可偏向 | 简单地介绍完了对象的内存布局,我们把话题返回到轻量级锁的执行过程上。在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状 态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的MarkWord的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced MarkWord),这时候线程堆栈与对象头的状态如图13-3所示。 图13-3 轻量级锁CAS操作之前堆栈与对象的状态 然后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向LockRecord的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位(MarkWord的最后两个Bits)将转变为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图 13-4所示。 图13-4 轻量级锁CAS操作之后堆栈与对象的状态 ( 注2:图13-3和图13-4来源于HotSpot虚拟机的一位Senior Staff Engineer——Paul Hohensee所写的PPT《The Hotspot Java Virtual Machine》) 如果这个更新操作失败了,虚拟机首先会检查对象的MarkWord是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直 接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的 状态值变为“10”,MarkWord中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 上面描述的是轻量级锁的加锁过程,它的解锁过程也是通过CAS操作来进行的,如果对象的MarkWord仍然指向着线程的锁记录,那就用CAS操作把 对象当前的Mark Word和线程中复制的Displaced MarkWord替换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线 程。 轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用CAS 操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。 13.3.5 偏向锁 偏向锁也是JDK1.6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。 偏向锁的“偏”,就是偏心的“偏”、偏袒的“偏”。它的意思是这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。 如果读者读懂了前面轻量级锁中关于对象头MarkWord与线程之间的操作过程,那偏向锁的原理理解起来就会很简单。假设当前虚拟机启用了偏向锁(启 用参数-XX:+UseBiasedLocking,这是JDK1.6的默认值),那么,当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位 设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的MarkWord之中,如果CAS操作成功,持有偏向锁的线程以后 每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作(例如Locking、Unlocking及对Mark Word的Update等)。 当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(RevokeBias)后恢复到未锁定 (标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就如上面介绍的轻量级锁那样执行。偏向锁、轻量级锁的状态转化及对象 Mark Word的关系如图13-5所示。 图13-5 偏向锁、轻量级锁的状态转化及对象Mark Word的关系 偏向锁可以提高带有同步但无竞争的程序性能。它同样是一个带有效益权衡(TradeOff)性质的优化,也就是说它并不一定总是对程序运行有 利,如果程序中大多数的锁都总是被多个不同的线程访问,那偏向模式就是多余的。在具体问题具体分析的前提下,有时候使用参数 -XX:-UseBiasedLocking来禁止偏向锁优化反而可以提升性能。
作者:icyfenix@gmail.com 来源: 《深入理解Java虚拟机:JVM高级特性与最佳实践》
比onload更快获取图片尺寸 文章更新:2011-05-31 lightbox类效果为了让图片居中显示而使用预加载,需要等待完全加载完毕才能显示,体验不佳(如filick相册的全屏效果)。javascript无法获取img文件头数据,真的是这样吗?本文通过一个巧妙的方法让javascript获取它。 这是大部分人使用预加载获取图片大小的例子: 01 | var imgLoad = function (url, callback) { |
02 | var img = new Image(); |
06 | callback(img.width, img.height); |
08 | img.onload = function () { |
09 | callback(img.width, img.height); |
可以看到上面必须等待图片加载完毕才能获取尺寸,其速度不敢恭维,我们需要改进。 web应用程序区别于桌面应用程序,响应速度才是最好的用户体验。如果想要速度与优雅兼得,那就必须提前获得图片尺寸,如何在图片没有加载完毕就能获取图片尺寸? 十多年的上网经验告诉我:浏览器在加载图片的时候你会看到图片会先占用一块地然后才慢慢加载完毕,并且不需要预设width与height属性,因 为浏览器能够获取图片的头部数据。基于此,只需要使用javascript定时侦测图片的尺寸状态便可得知图片尺寸就绪的状态。 当然实际中会有一些兼容陷阱,如width与height检测各个浏览器的不一致,还有webkit new Image()建立的图片会受以处在加载进程中同url图片影响,经过反复测试后的最佳处理方式: 02 | // 05.27: 1、保证回调执行顺序:error > ready > load;2、回调函数this指向img本身 |
03 | // 04-02: 1、增加图片完全加载后的回调 2、提高性能 |
06 | * 图片头数据加载就绪事件 - 更快获取图片尺寸 |
10 | * @param {String} 图片路径 |
11 | * @param {Function} 尺寸就绪 |
12 | * @param {Function} 加载完毕 (可选) |
13 | * @param {Function} 加载错误 (可选) |
15 | alert('size ready: width=' + this.width + '; height=' + this.height); |
18 | var imgReady = ( function () { |
19 | var list = [], intervalId = null , |
24 | for (; i < list.length; i++) { |
25 | list[i].end ? list.splice(i--, 1) : list[i](); |
27 | !list.length && stop(); |
32 | clearInterval(intervalId); |
36 | return function (url, ready, load, error) { |
37 | var onready, width, height, newWidth, newHeight, |
45 | load && load.call(img); |
53 | img.onerror = function () { |
54 | error && error.call(img); |
56 | img = img.onload = img.onerror = null ; |
60 | onready = function () { |
62 | newHeight = img.height; |
63 | if (newWidth !== width || newHeight !== height || |
64 | // 如果图片已经在其他地方加载可使用面积检测 |
65 | newWidth * newHeight > 1024 |
74 | img.onload = function () { |
75 | // onload在定时器时间差范围内可能比onready快 |
76 | // 这里进行检查并保证onready优先执行 |
77 | !onready.end && onready(); |
79 | load && load.call(img); |
81 | // IE gif动画会循环执行onload,置空onload即可 |
82 | img = img.onload = img.onerror = null ; |
88 | // 无论何时只允许出现一个定时器,减少浏览器性能损耗 |
89 | if (intervalId === null ) intervalId = setInterval(tick, 40); |
调用例子: 2 | alert( 'size ready: width=' + this .width + '; height=' + this .height); |
是不是很简单?这样的方式获取摄影级别照片尺寸的速度往往是onload方式的几十多倍,而对于web普通(800×600内)浏览级别的图片能达到秒杀效果。看了这个再回忆一下你见过的web相册,是否绝大部分都可以重构一下呢?好了,请观赏令人愉悦的 DEMO : http://www.planeart.cn/demo/imgReady/ (通过测试的浏览器:Chrome、Firefox、Safari、Opera、IE6、IE7、IE8) planeArt.cn原创文章,原文地址:http://www.planeart.cn/?p=1121 其他文章: 1、再谈IE6之Fixed定位 2、简易的全屏透明遮罩(lightBox)解决方案
http://www.cnblogs.com/lhb25/archive/2011/03/09/1964344.html 您可能还喜欢 对于Web设计和开发人员来说,CSS是非常重要的一部分,随着越来越多的浏览器对CSS3的支持及不断完善,设计师和开发者们有了更多的选 择。如今,用纯CSS就可以实现各种各样很酷的效果,甚至是动画。今天这篇文章向大家推荐24款非常优秀的CSS3工具,为了获得更佳的效果,请在 Chrome 4+, Safari 4+, Firefox 3.6+, IE9+, Opera 10.5+版本浏览器中浏览如下在线工具。 使用CSS3 Pie可以让IE6至IE8版本实现大多数的CSS3修饰特性,如圆角、阴影、渐变等等。 非常好的CSS3效果演示,提供了示例代码。 非常帅的一款CSS3工具,可修改代码,即时预览。 一个非常不错的CSS3按钮制作工具。 非常不错的CSS3代码生成器,带预览效果。 非常不错的CSS3菜单制作工具。 一款非常棒的CSS3渐变效果演示工具。 一份不错的CSS3属性速查手册(PDF格式)。 非常不错的CSS3选择器测试工具 一款强大的CSS3旋转动画效果演示工具,即时生成代码。 CSS3特性介绍及效果预览。 一款非常不错的CSS3代码生成工具。 CSS3颜色命名对照表。 CSS3书签工具。 一款在线CSS3圆角工具,四个角输入值就能生成对应的效果和代码。 很炫的CSS3桌面。 非常详尽的浏览器对CSS支持情况,包括CSS2.1和CSS3。 让元素以键盘风格显示的样式表。 一款在线CSS3圆角和阴影效果演示及代码生成工具。 CSS3包装阴影效果。 模仿内阴影效果,可输入内容查看效果,中文也可以噢。 这工具名字太奇怪了,一款文本阴影效果工具,可即时生成代码。 在线CSS3学习工具,可即时预览效果。 最后压轴的这款工具非常强大,可在线演示渐变、阴影、旋转、动画等非常多的效果,并生成对应效果的代码,赶紧体验一下吧! 您可能还喜欢 (编译来源:梦想天空 原文来自:Ultimate Collection of CSS3 Tools For Your Next Web Development)
工具提示(Tooltip)在网站中的一个小功能,但却有很重要的作用,常用于显示一些温馨的提示信息。如果网站中的工具提示功能做得非常有创意的话能够加深用户对网站印象。因此,今天这篇文章向大家推荐的20款优秀的 jQuery Tooltip 插件就是用于帮助你制作漂亮的工具提示效果。 您你可能还喜欢
jQuery的易扩展性吸引了来自全球的开发者来共同编写jQuery插件。jQuery插件不仅能够增强网站的可用性,有效地改善用户体验,还可以大大减少开发时间。现在的jQuery插件很多,可以根据您的项目需要来选择。这里为您介绍20款非常不错的插件。 Creative Radical Web Typography Lettering.js是一个轻量经的、易于使用的jQuery插件,可创造出极具个性的网页排版,是2010年最佳jQuery插件之一。 演示 | 下载 New FancyMoves Jquery Product Slider Jquery Product Slider是一款效果很不错的产品幻灯片插件。 演示 | 下载 Jquery Space Gallery Jquery Space Gallery是一款很有空间感的图片库插件。 演示 | 下载 Fancy Thumbnail Hover Effect 这是一款非常不错的Hover效果插件。 演示 | 下载 Jquery Inline Form Validation 这是一款表单验证插件。 演示 | 下载 Site Switcher 这是一款站点切换插件。 演示 | 下载 AnythingSlider 这是一款效果很棒的幻灯片插件。 演示 | 下载 Jquery Tooltip Coda Bubble 这是一款简洁的jQuery信息提示插件。 演示 | 下载 Jquery Upload and Crop Image 这是一款图片上传和裁剪插件。 演示 | 下载 jQuery Carts 这是一款jQuery图表插件。 演示 | 下载 Twitter-like login box 这是一款类似Twitter登陆框效果的插件。 演示 | 下载 File Download 这是一款文件下载插件。 演示 | 下载 Polaroid Photo Viewer 这是一款宝丽莱效果图片浏览插件。 演示 | 下载 jquery Hover Sub Tag Cloud 这是一款子标签云显示插件。 演示 | 下载 Graph Visualization 这是一款图标可视化插件。 演示 | 下载 Show/Hide Jquery Panel 这是一款控制面板显示和隐藏的插件。 演示 | 下载 Drop Down with CSS and jQuery 这是一款下拉菜单插件。 演示 | 下载 Quick & Easy Zooming With jQuery – Zoomy 这是一款非常好用的缩放插件。 演示 | 下载 Horizontal Accordions 这是一款横向手风琴效果插件。 演示 | 下载 Flexible Rating 这是一款非常灵活的评分插件。 演示 | 下载 (编译来源:梦想天空 原文来自:20 Useful jQuery Plugins Every Developer Should Know About)
流体布局(一) jQuery插件:jQuery.Waterfall.js, js的计算方法 jQuery1.4.4,IE8.0,opera,firefox,chrome测试通过 围观请点击:http://3vke.com 下载地址:Waterfall on github 1.使用方法: ①.加载jQuery库; ②.加载jQuery.Waterfull.js , 必须在jQuery库之后; ③.调用接口: $node.waterfull({/* 此处为设置选项, 可留空 */}) , 如: $('#container').waterfall({}) 2.设置选项: { itemSelector:'.post-home', // 子元素id/class, 可留空 columnCount:4, // 列数, 纯数字, 可留空 columnWidth:300 // 列宽度, 纯数字, 可留空 isResizable: false, // 自适应浏览器宽度, 默认false isAnimated: false, // 元素动画, 默认false Duration: 500, // 动画时间 Easing: "swing", // 动画效果, 配合 jQuery Easing Plugin 使用 endFn: function(){} // 回调函数 } 3.Ajax说明: $.ajax({ url: Url, beforeSend: function() {}, success: function(date) { $('#container').append(date).waterfall({}); } }) 流体布局(二) 固定宽度的流体布局的个人思路:参考文献:@qiqiboy javascript 图片预加载 思路如下: 1.imgReady可以在图片没有加载完成之前,通过头信息获取到图片的大小(这种方法相当天才),于是我建立一个局部的数组,将图片高度储存起来:(div的高度亦可) var argg= new Array()//例如有10篇文章,就是一个length=10的数组 2.通过浏览器的宽度,来判断一行可以放几张图(假定3张),再建立一个全局数组,保存数据: var args= new Array()//初始化数据,全部设定为0 args=[0,0,0]; 3.排序,用for循环,每一次通过比较argg[i]和args的最小值,来确定下一个div放置的位置,放完之后,把args的最小值重新赋值: 新版iphoto主题,采用如上方法布局,包含ajax后只有8Kb,相当廉价,新版首页观光地址:http://icold.me/photo 1.流体布局主题iPhoto新版首页观光地址:http://icold.me/photo 2.流体布局js计算方法以及js源代码下载:流体布局(三) 流体布局(三) 本文主要写固定宽度流体布局中的处理办法(全文以iphoto主题为例) 1.先看看Html结构
<div id="container"> <div class="post-home">xxoo..</div> <div class="post-home">xxoo..</div> <div class="post-home">xxoo..</div> <div class="post-home">xxoo..</div> <div class="post-home">xxoo..</div> </div> #container宽度我设定为1050px, .post-home宽度设定为350px,具体的css就不写了(也就是3列) 2.js的算法 jQuery(document).ready(function($) { var args = [0, 0, 0]; /*储存已排列的最后3个.post-home的top值, 初始时为[0,0,0]; 没有储存left值, 因为left已经是知道的, [0,350,700]; */ sort($('#container > .post-home')); function sort(elem) { // elem是传入的DOM var r, // setTimeout相关变量 m = 0,// 初始变量 n = elem.length,// .post-home的数量 topArgs = new Array(); // 建立一个局部数组 Array.prototype.min = function() { /*返回数组中最小值的序号 0 ==>第一列(left = 0*350px) 1 ==>第二列(left = 1*350px) 2 ==>第三列(left = 2*350px) */ var e,d = 0,b = this[0],c = this.length; for (e = 1; e < c; e++) { if (this[e] < b) {b = this[e];d = e} } return d }; Array.prototype.max = function() { // 返回数组中的最大值 var d, b = this[0],c = this.length; for (d = 1; d < c; d++) { if (this[d] > b) {b = this[d]} } return b }; getHeight(); function getHeight() { if (m < n) { // 用来判断是不是获取了所有的.post-home的高度 var $this = elem.eq(m), // 第m个.post-home h = parseInt($this.height()) + 16; // 第m个.post-home高度 + 16, 16是与下一个div的距高 topArgs.push(h); // 把第m个.post-home高度, 放到数组中去 m++; // m累加 r = setTimeout(getHeight, 10) // setTimeout来循环函数, 直到m==n 获取所有的高度 } if (m >= n) { clearTimeout(r); // 清除循环 r = null; var d, e = topArgs.length; //初始化数据 for (d = 0; d < e; d++) { // for循环来给topleft赋值 var minNum = args.min(), // 获得args的最小值的序号 newTop = args[minNum],// 获得args的最小值 $that = elem.eq(d); // 第d个.post-home $that.css({ // for循环来给topleft赋值 position: "absolute", top: newTop, left: 350 * minNumber }); args[minNum] = newTop + topArgs[d]; /*改变args数组最小值的大小 这样args[minNum]就不是最小了 而是这一列下一个.post-home的高度 如此循环,下一个args[minNum],就是第二列的下一个.post-home的高度 */ } $('#container').css({ height: args.max() //给$('#container')的高度赋值 }); } } } });
3.最重要的问题 假如不能及时得到img的高度,将会错位,所以这里推荐再谈javascript图片预加载技术, 如果你嫌麻烦,可以用$(window).load(function(){})把上面的代码包括起来,$(window).load可以在图片加载完成之后执行内部的代码。 http://3vke.com/2012/03/09/%E6%B5%81%E4%BD%93%E5%B8%83%E5%B1%80%E6%8F%92%E4%BB%B6waterfall/
MySQL性能优化的21个最佳实践 今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显。关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我 们程序员需要去关注的事情。当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能。这里,我们不会讲过 多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库。希望下面的这些优化技巧对你有用。 1. 为查询缓存优化你的查询 大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。 这里最主要的问题是,对于程序员来说,这个事情是很容易被忽略的。因为,我们某些查询语句会让MySQL不使用缓存。请看下面的示例: 上面两条SQL语句的差别就是 CURDATE() ,MySQL的查询缓存对这个函数不起作用。所以,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所需要的就是用一个变量来代替MySQL的函数,从而 开启缓存。 2. EXPLAIN 你的 SELECT 查询 使用 EXPLAIN 关键字可以让你知道MySQL是如何处理你的SQL语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。 EXPLAIN 的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的……等等,等等。 挑一个你的SELECT语句(推荐挑选那个最复杂的,有多表联接的),把关键字EXPLAIN加到前面。你可以使用phpmyadmin来做这个事。然后,你会看到一张表格。下面的这个示例中,我们忘记加上了group_id索引,并且有表联接: 当我们为 group_id 字段加上索引后: 我们可以看到,前一个结果显示搜索了 7883 行,而后一个只是搜索了两个表的 9 和 16 行。查看rows列可以让我们找到潜在的性能问题。 3. 当只要一行数据时使用 LIMIT 1 当你查询表的有些时候,你已经知道结果只会有一条结果,但因为你可能需要去fetch游标,或是你也许会去检查返回的记录数。 在这种情况下,加上 LIMIT 1 可以增加性能。这样一样,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。 下面的示例,只是为了找一下是否有“中国”的用户,很明显,后面的会比前面的更有效率。(请注意,第一条中是Select *,第二条是Select 1) 4. 为搜索字段建索引 索引并不一定就是给主键或是唯一的字段。如果在你的表中,有某个字段你总要会经常用来做搜索,那么,请为其建立索引吧。 从上图你可以看到那个搜索字串 “last_name LIKE ‘a%’”,一个是建了索引,一个是没有索引,性能差了4倍左右。 另外,你应该也需要知道什么样的搜索是不能使用正常的索引的。例如,当你需要在一篇大的文章中搜索一个词时,如: “WHERE post_content LIKE ‘%apple%’”,索引可能是没有意义的。你可能需要使用MySQL全文索引 或是自己做一个索引(比如说:搜索关键词或是Tag什么的) 5. 在Join表的时候使用相当类型的例,并将其索引 如果你的应用程序有很多 JOIN 查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。 而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把 DECIMAL 字段和一个 INT 字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样) 6. 千万不要 ORDER BY RAND() 想打乱返回的数据行?随机挑一个数据?真不知道谁发明了这种用法,但很多新手很喜欢这样用。但你确不了解这样做有多么可怕的性能问题。 如果你真的想把返回的数据行打乱了,你有N种方法可以达到这个目的。这样使用只让你的数据库的性能呈指数级的下降。这里的问题是:MySQL会不得 不去执行RAND()函数(很耗CPU时间),而且这是为了每一行记录去记行,然后再对其排序。就算是你用了Limit 1也无济于事(因为要排序) 下面的示例是随机挑一条记录 7. 避免 SELECT * 从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。 所以,你应该养成一个需要什么就取什么的好的习惯。 8. 永远为每张表设置一个ID 我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的AUTO_INCREMENT标志。 就算是你 users 表有一个主键叫 “email”的字段,你也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。 而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区…… 在这里,只有一个情况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,通过若干个别的表的主键构成。我们把这个情况叫做“外键”。比 如: 有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程表,在成绩表中,学生ID和课程 ID叫“外键”其共同组成主键。 9. 使用 ENUM 而不是 VARCHAR ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。 如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。 MySQL也有一个“建议”(见第十条)告诉你怎么去重新组织你的表结构。当你有一个 VARCHAR 字段时,这个建议会告诉你把其改成 ENUM 类型。使用 PROCEDURE ANALYSE() 你可以得到相关的建议。 10. 从 PROCEDURE ANALYSE() 取得建议 PROCEDURE ANALYSE() 会让 MySQL 帮你去分析你的字段和其实际的数据,并会给你一些有用的建议。只有表中有实际的数据,这些建议才会变得有用,因为要做一些大的决定是需要有数据作为基础的。 例如,如果你创建了一个 INT 字段作为你的主键,然而并没有太多的数据,那么,PROCEDURE ANALYSE()会建议你把这个字段的类型改成 MEDIUMINT 。或是你使用了一个 VARCHAR 字段,因为数据不多,你可能会得到一个让你把它改成 ENUM 的建议。这些建议,都是可能因为数据不够多,所以决策做得就不够准。 在phpmyadmin里,你可以在查看表时,点击 “Propose table structure” 来查看这些建议 一定要注意,这些只是建议,只有当你的表里的数据越来越多时,这些建议才会变得准确。一定要记住,你才是最终做决定的人。 11. 尽可能的使用 NOT NULL 除非你有一个很特别的原因去使用 NULL 值,你应该总是让你的字段保持 NOT NULL。这看起来好像有点争议,请往下看。 首先,问问你自己“Empty”和“NULL”有多大的区别(如果是INT,那就是0和NULL)?如果你觉得它们之间没有什么区别,那么你就不要使用NULL。(你知道吗?在 Oracle 里,NULL 和 Empty 的字符串是一样的!) 不要以为 NULL 不需要空间,其需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。 当然,这里并不是说你就不能使用NULL了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL值。 12. Prepared Statements Prepared Statements很像存储过程,是一种运行在后台的SQL语句集合,我们可以从使用 prepared statements 获得很多好处,无论是性能问题还是安全问题。 Prepared Statements 可以检查一些你绑定好的变量,这样可以保护你的程序不会受到“SQL注入式”攻击。当然,你也可以手动地检查你的这些变量,然而,手动的检查容易出问题, 而且很经常会被程序员忘了。当我们使用一些framework或是ORM的时候,这样的问题会好一些。 在性能方面,当一个相同的查询被使用多次的时候,这会为你带来可观的性能优势。你可以给这些Prepared Statements定义一些参数,而MySQL只会解析一次。 虽然最新版本的MySQL在传输Prepared Statements是使用二进制形势,所以这会使得网络传输非常有效率。 当然,也有一些情况下,我们需要避免使用Prepared Statements,因为其不支持查询缓存。但据说版本5.1后支持了。 在PHP中要使用prepared statements,你可以查看其使用手册:mysqli 扩展 或是使用数据库抽象层,如: PDO. 13. 无缓冲的查询 正常的情况下,当你在当你在你的脚本中执行一个SQL语句的时候,你的程序会停在那里直到没这个SQL语句返回,然后你的程序再往下继续执行。你可以使用无缓冲查询来改变这个行为。 mysql_unbuffered_query() 发送一个SQL语句到MySQL而并不像mysql_query()一样去自动fethch和缓存结果。这会相当节约很多可观的内存,尤其是那些会产生大 量结果的查询语句,并且,你不需要等到所有的结果都返回,只需要第一行数据返回的时候,你就可以开始马上开始工作于查询结果了。 然而,这会有一些限制。因为你要么把所有行都读走,或是你要在进行下一次的查询前调用 mysql_free_result() 清除结果。而且, mysql_num_rows() 或 mysql_data_seek() 将无法使用。所以,是否使用无缓冲的查询你需要仔细考虑。 14. 把IP地址存成 UNSIGNED INT 很多程序员都会创建一个 VARCHAR(15) 字段来存放字符串形式的IP而不是整形的IP。如果你用整形来存放,只需要4个字节,并且你可以有定长的字段。而且,这会为你带来查询上的优势,尤其是当 你需要使用这样的WHERE条件:IP between ip1 and ip2。 我们必需要使用UNSIGNED INT,因为 IP地址会使用整个32位的无符号整形。 而你的查询,你可以使用 INET_ATON() 来把一个字符串IP转成一个整形,并使用 INET_NTOA() 把一个整形转成一个字符串IP。在PHP中,也有这样的函数 ip2long() 和 long2ip()。 15. 固定长度的表会更快 如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。 固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。 并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。 使用“垂直分割”技术(见下一条),你可以分割你的表成为两个一个是定长的,一个则是不定长的。 16. 垂直分割 “垂直分割”是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段的数目,从而达到优化的目的。(以前,在银行做过项目,见过一张表有100多个字段,很恐怖) 示例一:在Users表中有一个字段是家庭地址,这个字段是可选字段,相比起,而且你在数据库操作的时候除了个人信息外,你并不需要经常读取或是改 写这 个字段。那么,为什么不把他放到另外一张表中呢? 这样会让你的表有更好的性能,大家想想是不是,大量的时候,我对于用户表来说,只有用户ID,用户名,口令,用户角色等会被经常使用。小一点的表总是会有 好的性能。 示例二: 你有一个叫 “last_login” 的字段,它会在每次用户登录时被更新。但是,每次更新时会导致该表的查询缓存被清空。所以,你可以把这个字段放到另一个表中,这样就不会影响你对用户 ID,用户名,用户角色的不停地读取了,因为查询缓存会帮你增加很多性能。 另外,你需要注意的是,这些被分出去的字段所形成的表,你不会经常性地去Join他们,不然的话,这样的性能会比不分割时还要差,而且,会是极数级的下降。 17. 拆分大的 DELETE 或 INSERT 语句 如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。 Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。 如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让你泊WEB服务Crash,还可能会让你的整台服务器马上掛了。 所以,如果你有一个大的处理,你定你一定把其拆分,使用 LIMIT 条件是一个好的方法。下面是一个示例: 18. 越小的列会越快 对于大多数的数据库引擎来说,硬盘操作可能是最重大的瓶颈。所以,把你的数据变得紧凑会对这种情况非常有帮助,因为这减少了对硬盘的访问。 参看 MySQL 的文档 Storage Requirements 查看所有的数据类型。 如果一个表只会有几列罢了(比如说字典表,配置表),那么,我们就没有理由使用 INT 来做主键,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 会更经济一些。如果你不需要记录时间,使用 DATE 要比 DATETIME 好得多。 当然,你也需要留够足够的扩展空间,不然,你日后来干这个事,你会死的很难看,参看Slashdot的例子(2009年11月06日),一个简单的ALTER TABLE语句花了3个多小时,因为里面有一千六百万条数据。 19. 选择正确的存储引擎 在 MySQL 中有两个存储引擎 MyISAM 和 InnoDB,每个引擎都有利有弊。酷壳以前文章《MySQL: InnoDB 还是 MyISAM?》讨论和这个事情。 MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都 无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。 InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。 下面是MySQL的手册 target=”_blank”MyISAM Storage Engine InnoDB Storage Engine 20. 使用一个对象关系映射器(Object Relational Mapper) 使用 ORM (Object Relational Mapper),你能够获得可靠的性能增涨。一个ORM可以做的所有事情,也能被手动的编写出来。但是,这需要一个高级专家。 ORM 的最重要的是“Lazy Loading”,也就是说,只有在需要的去取值的时候才会去真正的去做。但你也需要小心这种机制的副作用,因为这很有可能会因为要去创建很多很多小的查询反而会降低性能。 ORM 还可以把你的SQL语句打包成一个事务,这会比单独执行他们快得多得多。 目前,个人最喜欢的PHP的ORM是:Doctrine。 21. 小心“永久链接” “永久链接”的目的是用来减少重新创建MySQL链接的次数。当一个链接被创建了,它会永远处在连接的状态,就算是数据库操作已经结束了。而且,自 从我 们的Apache开始重用它的子进程后——也就是说,下一次的HTTP请求会重用Apache的子进程,并重用相同的 MySQL 链接。 PHP手册:mysql_pconnect() 在理论上来说,这听起来非常的不错。但是从个人经验(也是大多数人的)上来说,这个功能制造出来的麻烦事更多。因为,你只有有限的链接数,内存问题,文件句柄数,等等。 而且,Apache 运行在极端并行的环境中,会创建很多很多的了进程。这就是为什么这种“永久链接”的机制工作地不好的原因。在你决定要使用“永久链接”之前,你需要好好地考虑一下你的整个系统的架构。 转自: http://3vke.com/2012/05/14/mysql%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E7%9A%8421%E4%B8%AA%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/
|