-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 407 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 407 KB
1
{"meta":{"title":"枪口下的砚台","subtitle":"所谓拥有,皆非束缚;所有过往,皆为序章。","description":null,"author":"syshlang","url":"https://syshlang.com","root":"/"},"pages":[{"title":"categories","date":"2019-02-20T17:25:54.000Z","updated":"2020-09-16T04:24:23.874Z","comments":false,"path":"categories/index.html","permalink":"https://syshlang.com/categories/index.html","excerpt":"","text":""},{"title":"BOOK","date":"2019-09-09T13:11:09.000Z","updated":"2020-09-04T03:40:48.217Z","comments":true,"path":"bookshelf/index.html","permalink":"https://syshlang.com/bookshelf/index.html","excerpt":"","text":"._3GhgGH8Y4Zh8H0uBP5aUMD { box-sizing: border-box; } ._3mVsbsHWKWuZwBS5zIrFO9 { background-repeat: no-repeat; background-position: center; } .oSnRgpdW2BnrDruxKh9We { margin: 0 auto; box-sizing: border-box; } .Iuu474S1L6y5p7yalKQbW { display: inline-block; vertical-align: top; } ._1EMsmdlbNCt0CnfgbG9uDi { background-image: url(/images/shelf.jpg); background-repeat: no-repeat; background-position: center; background-size: 100% 100%; padding-top: 22px; } ._1EMsmdlbNCt0CnfgbG9uDi .learnAllBooksBox { width: 90%; height: 218px; margin: 0 auto; box-sizing: border-box; overflow: hidden; } ._1EMsmdlbNCt0CnfgbG9uDi .learnAllBooksBox .bookItem { width: 40px; height: 230px; float: left; overflow: hidden; } ._1EMsmdlbNCt0CnfgbG9uDi .learnAllBooksBox .bookItem .bookItemInner { height: 195px; padding-top: 10px; box-sizing: border-box; cursor: pointer; background-image: url(/images/book.jpg); background-repeat: no-repeat; background-position: center; background-size: 100% 100%;; transition: all 0.5s ease-in-out; -ms-flex-align: center; align-items: center; -js-display: flex; display: block !important; text-align: center; } ._1EMsmdlbNCt0CnfgbG9uDi .learnAllBooksBox .bookItem .bookItemInner:hover { transform: scale(1.15); z-index: 1000; } ._1EMsmdlbNCt0CnfgbG9uDi .learnAllBooksBox .bookItem .bookItemInner p { height: 155px; color: #984517; writing-mode: tb-rl; -ms-writing-mode: tb-rl; font-size: 12px; font-weight: 400; padding-top: 10px; line-height: 14px; word-spacing: 1px; margin: 0 auto; box-sizing: border-box; display: inline-block; text-align: left; } ._3GhgGH8Y4Zh8H0uBP5aUMDAAA{ font-weight: 600; font-size: 28px; font-family: \"黑体\"; color: #8c888b; background: -webkit-linear-gradient(45deg, #70f7fe, #fbd7c6, #fdefac, #bfb5dd, #bed5f5); -moz-linear-gradient(45deg, #70f7fe, #fbd7c6, #fdefac, #bfb5dd, #bed5f5); -ms-linear-gradient(45deg, #70f7fe, #fbd7c6, #fdefac, #bfb5dd, #bed5f5); color: transparent; /*设置字体颜色透明*/ -webkit-background-clip: text; /*背景裁剪为文本形式*/ animation: ran 10s linear infinite; /*动态10s展示*/ } .fa-external-link:before { content: \"\"; } .post-description { font-size: 1.875em !important; font-style: italic; } .bookItemInner a{ font-weight: bold; } Linux 命令全集Docker从入门到实践设计原则Design Patterns"},{"title":"tags","date":"2019-02-20T17:20:15.000Z","updated":"2020-09-17T00:34:15.264Z","comments":true,"path":"tags/index.html","permalink":"https://syshlang.com/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"设计模式之结构型模式中的桥接模式(Bridge Pattern)","slug":"java-design-patterns6","date":"2020-05-30T12:56:21.000Z","updated":"2020-10-22T01:47:24.603Z","comments":true,"path":"62f7f6a4/","link":"","permalink":"https://syshlang.com/62f7f6a4/","excerpt":"","text":"一、基本介绍     桥接模式(Bridge Pattern)是一种结构型设计模式,基于类的最小设计原则,通过使用封装、聚合及继承等方式让不同的类承担不同的职责,从而把抽象(Abstraction)和实现(Implementation)分离开放在不同的层次中,使得各部分保持独立更加利于扩展。一句话来说,就是在抽象与实现之间提供一个桥梁,降低二者耦合度,达到二者可以独立变化而不互相影响的目的。 二、从“类爆炸”说起     🌰 假如,现在有这样一个业务场景,需要对不同颜色不同品牌的手机实现相应的功能,如打电话、发短信、上网等。    如果使用传统的方式,可能是这样的,类图如下:     从以上的类图中,可以看出存在的问题,首先,这种方式会带来扩展性的问题,随着功能和手机品牌的增加,需要维护的类的数量也会增加,扩展性极差😖,产生“类爆炸”;其次,从类的最小设计原则考虑,当需要手机的颜色时,同时还需增加所有品牌的手机,这种设计违反了单一职责原则。 三、桥接模式    那么,基于以上两点的考虑,借鉴单一职责原则的核心思想,尝试将对象解耦,将类的功能职责粒度分解细化,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。 3.1 采用桥接模式解决问题 定义手机品牌接口 123456789101112131415161718192021222324/** * 手机品牌接口 * * @author sunys */public interface PhoneBrand { /** * Call. * 打电话 */ void call(); /** * Send message. * 发短信 */ void sendMessage(); /** * Go online. * 上网 */ void goOnline();} 不同品牌手机接口的接口实现 1234567891011121314151617181920/** * @author sunys * HuaWei品牌接口实现 */public class HuaWeiPhoneBrandImpl implements PhoneBrand { @Override public void call() { System.out.println("Call use HuaWei phone."); } @Override public void sendMessage() { System.out.println("SendMessage use HuaWei phone."); } @Override public void goOnline() { System.out.println("GoOnline use HuaWei phone."); }} 1234567891011121314151617181920/** * @author sunys * Apple品牌接口实现 */public class ApplePhoneBrandImpl implements PhoneBrand { @Override public void call() { System.out.println("Call use Apple phone."); } @Override public void sendMessage() { System.out.println("SendMessage use Apple phone."); } @Override public void goOnline() { System.out.println("GoOnline use Apple phone."); }} 1234567891011121314151617181920/** * @author sunys * XiaoMi品牌接口实现 */public class XiaoMiPhoneBrandImpl implements PhoneBrand { @Override public void call() { System.out.println("Call use XiaoMi phone."); } @Override public void sendMessage() { System.out.println("SendMessage use XiaoMi phone."); } @Override public void goOnline() { System.out.println("GoOnline use XiaoMi phone."); }} 创建手机这个抽象类,即“桥” 123456789101112131415161718192021222324252627282930/** * @author sunys * 手机抽象类 相当于“桥” */public abstract class Phone { /** * PhoneBrand 类聚合 */ private PhoneBrand phoneBrand; public Phone(PhoneBrand phoneBrand) { super(); this.phoneBrand = phoneBrand; } protected void call() { this.phoneBrand.call(); } protected void sendMessage() { this.phoneBrand.sendMessage(); } protected void goOnline() { this.phoneBrand.goOnline(); }} 不同颜色的具体手机 1234567891011121314151617181920212223242526272829303132/** * The type Phone black. * @author sunys */public class PhoneBlack extends Phone{ /** * Instantiates a new Phone black. * * @param phoneBrand the phone brand */ public PhoneBlack(PhoneBrand phoneBrand) { super(phoneBrand); } @Override public void call() { super.call(); System.out.println("The phone of call is Black."); } @Override public void sendMessage() { super.sendMessage(); System.out.println("The phone of sendMessage is Black."); } @Override public void goOnline() { super.goOnline(); System.out.println("The phone of goOnline is Black."); }} 12345678910111213141516171819202122232425262728293031323334/** * The type Phone red. * * @author sunys */public class PhoneRed extends Phone{ /** * Instantiates a new Phone red. * * @param phoneBrand the phone brand */ public PhoneRed(PhoneBrand phoneBrand) { super(phoneBrand); } @Override public void call() { super.call(); System.out.println("The phone of call is Red."); } @Override public void sendMessage() { super.sendMessage(); System.out.println("The phone of sendMessage is Red."); } @Override public void goOnline() { super.goOnline(); System.out.println("The phone of goOnline is Red."); }} 123456789101112131415161718192021222324252627282930313233/** * The type Phone white. * * @author sunys */public class PhoneWhite extends Phone{ /** * Instantiates a new Phone white. * * @param phoneBrand the phone brand */ public PhoneWhite(PhoneBrand phoneBrand) { super(phoneBrand); } @Override public void call() { super.call(); System.out.println("The phone of call is White."); } @Override public void sendMessage() { super.sendMessage(); System.out.println("The phone of sendMessage is White."); } @Override public void goOnline() { super.goOnline(); System.out.println("The phone of goOnline is White."); }} 客户端调用 123456789101112131415161718192021222324/** * The type Client. * * @author sunys */public class Client { /** * Bridge test. */ public static void bridgeTest() { PhoneRed phoneRed = new PhoneRed(new HuaWeiPhoneBrandImpl()); phoneRed.call(); phoneRed.sendMessage(); phoneRed.goOnline(); PhoneBlack phoneBlack = new PhoneBlack(new HuaWeiPhoneBrandImpl()); phoneBlack.call(); phoneBlack.sendMessage(); phoneBlack.goOnline(); PhoneWhite phoneWhite = new PhoneWhite(new XiaoMiPhoneBrandImpl()); phoneWhite.call(); phoneWhite.sendMessage(); phoneWhite.goOnline(); }}     至此,解决问题的方案改进完毕,通过这种方式,如果要添加新品牌的手机,只需让其实现PhoneBrand类即可;如果需要增加手机的颜色,只需让其继承Phone类即可。看下类图,如下:     在这个方案中,Phone这个抽象类是所有具体手机的父类,也是PhoneBrand类聚合的对象,起到了关键的“桥”的作用。基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责,将抽象与实现分离开保证不同层次独立改变互不影响更,从而极大的提高了系统的灵活性,有利于扩展。 四、桥接模式在JDBC中的应用 首先,来看一段客户端调用JDBC连接数据库的过程1234567891011121314151617181920212223242526272829303132333435/** * The type Client. * * @author sunys */public class Client { /** * Jdbc test. */ public static void jdbcTest(){ try { // 1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 创建一个连接对象 Connection conn = DriverManager.getConnection("url","user","password"); //3. 创建一个sql语句的发送命令对象 Statement stmt = conn.createStatement(); // 4. 执行sql,拿到查询的结果集对象 ResultSet rs = stmt.executeQuery("s"); //5. 输出结果集的数据 while(rs.next()){ System.out.println(rs); } //6. 关闭连接,命令对象以及结果集。 rs.close(); stmt.close(); conn.close(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } }} 然后,看下相关类的UML类图     从UML类图可以看出,com.mysql.jdbc.Driver类是java.sql.Driver接口的实现类,注册驱动时,会执行com.mysql.jdbc.Driver类的静态块,该静态代码块中调用java.sql.DriverManager#registerDriver(java.sql.Driver)方法,将Driver对象注册到 DriverManager中,DriverManager就相当于是“桥”;调用java.sql.DriverManager#getConnection(java.lang.String, java.lang.String, java.lang.String)创建一个连接对象时,会根据驱动的类型调用不同类型数据库(mysql、oracle等)实现的 Driver 的 connect() 方法,最终获得连接对象。 附:本次演示的项目地址https://github.com/syshlang/java-design-patterns","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计模式","slug":"java/设计模式","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"patterns","slug":"patterns","permalink":"https://syshlang.com/tags/patterns/"}]},{"title":"基于TrueLicense实现web应用的License验证","slug":"java-truelicense","date":"2020-05-05T03:29:00.000Z","updated":"2020-09-24T04:17:51.033Z","comments":true,"path":"45cf44f/","link":"","permalink":"https://syshlang.com/45cf44f/","excerpt":"    近期开发的web应用交付海外客户试用时,领导要求需要限制客户的试用期限,于是考虑使用license进行授权。查阅相关资料,了解到一个开源的证书管理引擎——TrueLicense,通过TrueLicense可以实现授权验证功能,用于license的生成和有效性的验证。","text":"    近期开发的web应用交付海外客户试用时,领导要求需要限制客户的试用期限,于是考虑使用license进行授权。查阅相关资料,了解到一个开源的证书管理引擎——TrueLicense,通过TrueLicense可以实现授权验证功能,用于license的生成和有效性的验证。      一、License授权和验证的原理 首先,需要生成密钥对,生成方法有很多,本次项目使用的是JDK提供的KeyTool工具; 在服务端,也就是授权者通过私钥(授权者保留,不能泄露)对包含授权信息(如起始日期、截止日期,服务器硬件的MAC地址等)的license进行数字签名; 在客户端,也就是软件的使用方,通过公钥(一般放在验证的代码中使用)验证license是否符合使用条件。 二、实现步骤1.生成密钥对1.1.生成私钥库    安装jdk,并配置环境变量,然后使用KeyTool工具生成密钥对,命令如下: 1keytool -genkeypair -keysize 1024 -validity 3650 -alias SYSHLANG -keystore privateKeys.keystore -storepass 12345678A -keypass 12345678A -dname "CN=syshlang, OU=syshlang, O=syshlang, L=GZ, ST=GD, C=CN" 参数说明: keysize 密钥长度 validity 私钥的有效期(单位:天) alias 私钥别称 keystore 指定私钥库文件的名称(生成在当前目录) storepass 指定私钥库的密码(keystore文件存储密码) keypass 指定别名条目的密码(私钥加解密密码) dname 证书个人信息 CN为你的姓名 OU为你的组织单位名称 O为你的组织名称 L为你所在的城市名称 ST为你所在的省份名称 C为你的国家名称 1.2.导出公钥    这一步主要是把私钥库内的公匙导出到一个文件中,命令如下: 1keytool -exportcert -alias SYSHLANG -keystore privateKeys.keystore -storepass 12345678A -file certfile.cer 参数说明: alias 私钥别称 keystore 指定私钥库文件的名称(如果没有带路径,在当前目录查找) storepass 指定私钥库的密码 file 导出证书文件名称 1.3.导入证书文件    这一步主要是把上一步中导出的证书文件导入到公钥库中,命令如下: 1keytool -import -alias SYSHLANG -file certfile.cer -keystore publicCerts.keystore -storepass 12345678A 参数说明: alias 公钥别称 file 证书文件名称 keystore 公钥文件名称 storepass 指定私钥库的密码     是否信任此证书? [否]:” ,那么请输入”Y”;此步骤中如果没有storepass会提示输入输入密钥库口令,输入之前设置的即可,如果没有设置口令,则输入信任证书默认密钥“changeit”。     如果顺利通过以上的步骤,会在当前目录下生成三个文件: certfile.cer 认证证书,已经没用了,可以删掉 privateKeys.keystore 私钥,授权者保留,不能泄露 publicCerts.keystore 公钥,给客人使用(一般放在验证的代码中使用)     此时,可以查看cacerts中的证书列表 12keytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit // 查看所有keytool -list -alias SYSHLANG -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit // 查看指定别名     删除cacerts中指定名称的证书 1keytool -delete -alias SYSHLANG -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit 2.使用TrueLicense实现授权验证2.1服务端生成授权证书     TrueLicense默认只帮我们验证了时间,可以自定义加入一些需要验证的其它信息,例如,服务器硬件的MAC地址、CPU序列号等。然后调用私钥(privateKeys.keystore)生成授权证书文件(license.lic)。 123public class LicenseCreateParam implements Serializable { // 添加自定义验证的信息字段}     具体代码实现,可以点击服务端代码链接查看。 2.2客户端验证授权证书     在客户端,继承LicenseManager类,重写verify方法,校验自定义加入一些需要验证的其它信息。将公钥(publicCerts.keystore)放在项目中,将授权证书文件(license.lic)放到对应的路径即可。 123456public class ClientLicenseManager extends LicenseManager { @Override protected synchronized LicenseContent verify(LicenseNotary licenseNotary) throws LicenseContentException { // 重写verify方法,校验自定义信息 }}     具体代码实现,可以点击客户端代码链接查看。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"TrueLicense","slug":"TrueLicense","permalink":"https://syshlang.com/tags/TrueLicense/"},{"name":"License","slug":"License","permalink":"https://syshlang.com/tags/License/"}]},{"title":"设计模式之结构型模式中的适配器模式(Adapter Pattern)","slug":"java-design-patterns5","date":"2020-01-17T06:27:22.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"fbfea71e/","link":"","permalink":"https://syshlang.com/fbfea71e/","excerpt":"","text":"基本介绍     生活中,我们接触到最多的适配器就是电源适配器,不管是电脑的电源适配器、手机电源适配器,还是其他的电子产品电源适配器,其主要目的都是将家用电源220V转换为电子产品需要的电压,这些适配器的接口有两孔的也有三孔的,实质就是把一个转接头或者电压变换成另外所需要的插口或者电压。拿到编程里面来说,就是将一个类的接口转换成客户端所希望的另外一个接口,这样使得原本不能一起工作的那些类(接口不兼容,方法不匹配等)可以一起工作,主要目的就是处理兼容性。 适配器模式分类     适配器模式主要分为三类:类适配器、对象适配器、接口适配器。类适配器和对象适配器在实现上有些差别,而接口适配器则差别较大。 类适配器模式     类适配器实现的原理主要是通过继承。适配器类(Adapter)通过继承需要被适配的类(Source),实现需要得到的类(Destination)接口,从而完成Adapter到Destination的适配。 日常举例 手机充电的例子,通过手机充电器(Adapter)完成220V电源(Source)到 5V电压(Destination)的转换 12345public class Source220V { public int outVoltage220v(){ return 220; }} 1234567public interface Destination5V { /** * * @return */ int outVoltage5v();} 1234567public class VoltageAdapter extends Source220V implements Destination5V{ @Override public int outVoltage5v() { int voltage220v = outVoltage220v(); return voltage220v/44; }} 123456public class Client { public static void main(String[] args) { VoltageAdapter voltageAdapter = new VoltageAdapter(); voltageAdapter.outVoltage5v(); }} 对象适配器模式     对象配器实现的原理主要是通过组合或聚合。在《设计模式七大原则之合成复用原则(Composite Reuse Principle)》中,合成复用原则指出尽量使用合成/聚合,尽量不要使用类继承。对象适配器模式思路和类适配器模式基本相同,只不过将适配器类(Adapter)做修改,不再继承需要被适配的类(Source),而是直接持有需要被适配的类(Source),实现需要得到的类(Destination)接口,从而完成Adapter到Destination的适配。 日常举例 仍然是手机充电的例子,只需要修改适配器类(Adapter),如下: 12345678910111213141516public class VoltageAdapter implements Destination5V{ /** * 直接持有需要被适配的类(Source) */ private Source220V source220V; public VoltageAdapter(Source220V source220V) { this.source220V = source220V; } @Override public int outVoltage5v() { int voltage220v = source220V.outVoltage220v(); return voltage220v/44; }} 123456public class Client { public static void main(String[] args) { VoltageAdapter voltageAdapter = new VoltageAdapter(new Source220V()); voltageAdapter.outVoltage5v(); }} 接口适配器模式     接口配器实现的原理主要是通过抽象类来实现适配。接口适配器模式的核心思路是,当不需要全部实现接口的方法时,可以先设计一个抽象的类实现接口,并为接口中的每个方法提供一个默认的实现,对于该抽象类的子类就可以有选择的覆盖父类的某些方法,从而达到适配的目的。因此,该模式也被称为缺省适配器模式或是默认适配器模式(Default Adapter Pattern)。 日常举例 仍然是手机充电的例子,代码实现如下: 12345678910111213141516171819202122public interface Destination { /** * * @return */ int outVoltage5v(); /** * @return */ int outVoltage10v(); /** * @return */ int outVoltage36v(); /** * @return */ int outVoltage220v();} 123456789101112131415161718192021public abstract class AbstractVoltageAdapter implements Destination{ @Override public int outVoltage5v() { return 5; } @Override public int outVoltage10v() { return 10; } @Override public int outVoltage36v() { return 36; } @Override public int outVoltage220v() { return 220; }} 1234567public class VoltageAdapter extends AbstractVoltageAdapter{ @Override public int outVoltage5v() { int voltage220v = outVoltage220v(); return voltage220v/44; }} 123456public class Client { public static void main(String[] args) { VoltageAdapter voltageAdapter = new VoltageAdapter(); voltageAdapter.outVoltage5v(); }} 适配器模式在Spring框架中的应用对于SpringMVC有一个很重要的servlet,它有一个方法:org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter截取部分源码如下:源码:org.springframework.web.servlet.DispatcherServlet(片段) 123456789101112131415161718192021public class DispatcherServlet extends FrameworkServlet {... /** * Return the HandlerAdapter for this handler object. * @param handler the handler object to find an adapter for * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error. */ protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }...} 源码:org.springframework.web.servlet.HandlerAdapter 1234567891011121314151617181920212223242526272829303132333435363738394041public interface HandlerAdapter { /** * Given a handler instance, return whether or not this {@code HandlerAdapter} * can support it. Typical HandlerAdapters will base the decision on the handler * type. HandlerAdapters will usually only support one handler type each. * <p>A typical implementation: * <p>{@code * return (handler instanceof MyHandler); * } * @param handler handler object to check * @return whether or not this object can use the given handler */ boolean supports(Object handler); /** * Use the given handler to handle this request. * The workflow that is required may vary widely. * @param request current HTTP request * @param response current HTTP response * @param handler handler to use. This object must have previously been passed * to the {@code supports} method of this interface, which must have * returned {@code true}. * @throws Exception in case of errors * @return ModelAndView object with the name of the view and the required * model data, or {@code null} if the request has been handled directly */ ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; /** * Same contract as for HttpServlet's {@code getLastModified} method. * Can simply return -1 if there's no support in the handler class. * @param request current HTTP request * @param handler handler to use * @return the lastModified value for the given handler * @see javax.servlet.http.HttpServlet#getLastModified * @see org.springframework.web.servlet.mvc.LastModified#getLastModified */ long getLastModified(HttpServletRequest request, Object handler);} 再看HandlerAdapter的继承关系图: 分析: 从上面的源码片段及HandlerAdapter的继承关系图,可以看出Spring定义了一个适配接口HandlerAdapter,而其实现子类使得每一种Controller都有一种对应的适配器实现类,扩展Controller时只需增加对应的适配器实现类就可以了。 附:本次演示的项目地址https://github.com/syshlang/java-design-patterns","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计模式","slug":"java/设计模式","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"patterns","slug":"patterns","permalink":"https://syshlang.com/tags/patterns/"}]},{"title":"设计模式之创建型模式中的建造者模式(Builder Pattern)","slug":"java-design-patterns4","date":"2020-01-17T00:56:09.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"8cf99788/","link":"","permalink":"https://syshlang.com/8cf99788/","excerpt":"","text":"基本介绍     在java开发过程中,我们经常会创建大量的对象,这些对象有简单的也有复杂的,但是不管是简单对象还是复杂对象,构建这些对象的过程是相对稳定的,只不过它们的内部属性(成员属性)不同,这也就意味着构建的具体内部细节不一样。那么,基于这种情况,我们可以将复杂的对象的构建过程抽象出来,通过抽象过程的不同实现方法来实现不同对象的内部细节构建过程,从某种意义上来说,这也是产品构建过程复杂度的解耦,这就是建造者模式(Builder Pattern)。     建造者模式(Builder Pattern)又叫生成器模式,它把对象的创建步骤抽象成生成器,将一个产品的内部表象与产品的生产过程分割开来,一步一步构建一个复杂的对象,用户只用指定复杂对象的类型和内容,而无需知道内部的具体构建细节就可以构建它们。 建造者模式的四个角色 产品角色(Product):我们所要构建的产品对象; 抽象建造者(Builder):创建产品(Product)对象的接口/抽象类。它定义了创建一个产品(Product)对象所需要的各个部件的操作(抽象方法),同时包含一个获取产品(Product)对象(获取成品)的方法。 具体建造者(ConcreteBuilder):抽象建造者的具体实现,实现抽象建造者(Builder)的接口,构建和装配产品(Product)对象的各个部件,对于不同的部件或者构建步骤进行不同的详细实现,来完成不同的产品。 指导者(Director):主要用来使用Buider接口,构建一个使用Buider接口的对象,以一个相对稳定且统一的过程生产产品(Product)对象。 日常举例 建造小汽车的例子:小汽车主要部件:发动机(Engine)、车身框架(Frame)、轮胎(Wheel)等,不管是什么品牌的汽车,都有这些部件,只不过内部的构造和质量等不一样。代码实现,如下: 产品角色(Product)12345678910111213141516171819202122232425262728293031323334353637public class Car { private String engine; private String frame; private String wheel; public String getEngine() { return engine; } public void setEngine(String engine) { this.engine = engine; } public String getFrame() { return frame; } public void setFrame(String frame) { this.frame = frame; } public String getWheel() { return wheel; } public void setWheel(String wheel) { this.wheel = wheel; } @Override public String toString() { return "Car{" + "engine='" + engine + '\\'' + ", frame='" + frame + '\\'' + ", wheel='" + wheel + '\\'' + '}'; }} 抽象建造者(Builder)12345678910111213141516171819202122232425public interface CarBuilder { /** * 建造发动机 * @return */ String buildEngine(); /** * 建造车身框架 * @return */ String buildFrame(); /** * 建造轮胎 * @return */ String buildWheel(); /** * 获取小汽车成品 * @return */ Car getCar();} 具体建造者(ConcreteBuilder)1234567891011121314151617public class BaoMaCarBuilder implements CarBuilder{ Car car = new Car(); public void buildEngine() { this.car.setEngine("建造宝马发动机!"); } public void buildFrame() { this.car.setFrame("建造宝马车身框架!"); } public void buildWheel() { this.car.setWheel("建造宝马轮胎!"); } public Car getCar() { return this.car; }} 指导者(Director)123456789101112131415161718public class CarDirector { private CarBuilder carBuilder; public CarDirector(CarBuilder carBuilder) { this.carBuilder = carBuilder; } /** * 构建汽车的流程交给指导者 * @return */ public Car builderCar(){ carBuilder.buildEngine(); carBuilder.buildFrame(); carBuilder.buildWheel(); return carBuilder.getCar(); }} 客户端使用12345678public class Client { public static void main(String[] args) { BaoMaCarBuilder baoMaCarBuilder = new BaoMaCarBuilder(); CarDirector carDirector = new CarDirector(baoMaCarBuilder); Car car = carDirector.builderCar(); System.out.println(car); }} 运行结果: 建造者模式在JDK中的应用先看一张StringBuilder类图 源码:java.lang.Appendable 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071public interface Appendable { /** * Appends the specified character sequence to this <tt>Appendable</tt>. * * <p> Depending on which class implements the character sequence * <tt>csq</tt>, the entire sequence may not be appended. For * instance, if <tt>csq</tt> is a {@link java.nio.CharBuffer} then * the subsequence to append is defined by the buffer's position and limit. * * @param csq * The character sequence to append. If <tt>csq</tt> is * <tt>null</tt>, then the four characters <tt>"null"</tt> are * appended to this Appendable. * * @return A reference to this <tt>Appendable</tt> * * @throws IOException * If an I/O error occurs */ Appendable append(CharSequence csq) throws IOException; /** * Appends a subsequence of the specified character sequence to this * <tt>Appendable</tt>. * * <p> An invocation of this method of the form <tt>out.append(csq, start, * end)</tt> when <tt>csq</tt> is not <tt>null</tt>, behaves in * exactly the same way as the invocation * * <pre> * out.append(csq.subSequence(start, end)) </pre> * * @param csq * The character sequence from which a subsequence will be * appended. If <tt>csq</tt> is <tt>null</tt>, then characters * will be appended as if <tt>csq</tt> contained the four * characters <tt>"null"</tt>. * * @param start * The index of the first character in the subsequence * * @param end * The index of the character following the last character in the * subsequence * * @return A reference to this <tt>Appendable</tt> * * @throws IndexOutOfBoundsException * If <tt>start</tt> or <tt>end</tt> are negative, <tt>start</tt> * is greater than <tt>end</tt>, or <tt>end</tt> is greater than * <tt>csq.length()</tt> * * @throws IOException * If an I/O error occurs */ Appendable append(CharSequence csq, int start, int end) throws IOException; /** * Appends the specified character to this <tt>Appendable</tt>. * * @param c * The character to append * * @return A reference to this <tt>Appendable</tt> * * @throws IOException * If an I/O error occurs */ Appendable append(char c) throws IOException;} 源码:java.lang.AbstractStringBuilder(片段) 12345678910111213141516171819202122232425262728293031323334353637383940abstract class AbstractStringBuilder implements Appendable, CharSequence {... // Documentation in subclasses because of synchro difference public AbstractStringBuilder append(StringBuffer sb) { if (sb == null) return appendNull(); int len = sb.length(); ensureCapacityInternal(count + len); sb.getChars(0, len, value, count); count += len; return this; } /** * @since 1.8 */ AbstractStringBuilder append(AbstractStringBuilder asb) { if (asb == null) return appendNull(); int len = asb.length(); ensureCapacityInternal(count + len); asb.getChars(0, len, value, count); count += len; return this; } // Documentation in subclasses because of synchro difference @Override public AbstractStringBuilder append(CharSequence s) { if (s == null) return appendNull(); if (s instanceof String) return this.append((String)s); if (s instanceof AbstractStringBuilder) return this.append((AbstractStringBuilder)s); return this.append(s, 0, s.length()); }...} 源码:java.lang.StringBuilder(片段) 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{... @Override public StringBuilder append(Object obj) { return append(String.valueOf(obj)); } @Override public StringBuilder append(String str) { super.append(str); return this; } /** * Appends the specified {@code StringBuffer} to this sequence. * <p> * The characters of the {@code StringBuffer} argument are appended, * in order, to this sequence, increasing the * length of this sequence by the length of the argument. * If {@code sb} is {@code null}, then the four characters * {@code "null"} are appended to this sequence. * <p> * Let <i>n</i> be the length of this character sequence just prior to * execution of the {@code append} method. Then the character at index * <i>k</i> in the new character sequence is equal to the character at * index <i>k</i> in the old character sequence, if <i>k</i> is less than * <i>n</i>; otherwise, it is equal to the character at index <i>k-n</i> * in the argument {@code sb}. * * @param sb the {@code StringBuffer} to append. * @return a reference to this object. */ public StringBuilder append(StringBuffer sb) { super.append(sb); return this; } @Override public StringBuilder append(CharSequence s) { super.append(s); return this; }...} 分析: 接口Appendable定义了多个append抽象方法,即为抽象建造者;抽象类AbstractStringBuilder实现了接口Appendable的方法,即为具体建造者,但是不能实例化;StringBuilder继承了抽象类AbstractStringBuilder,具体的方法已经由AbstractStringBuilder实现,StringBuilder对部分方法进行了覆盖,由此可以看出,StringBuilder既充当了具体建造者,也是指导者的角色。 附:本次演示的项目地址https://github.com/syshlang/java-design-patterns","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计模式","slug":"java/设计模式","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"patterns","slug":"patterns","permalink":"https://syshlang.com/tags/patterns/"}]},{"title":"原型模式(Prototype Pattern)之浅拷贝和深拷贝","slug":"java-design-patterns3-1","date":"2020-01-15T07:06:44.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"a853c6f2/","link":"","permalink":"https://syshlang.com/a853c6f2/","excerpt":"","text":"    在《设计模式之创建型模式中的原型模式(Prototype Pattern)》中,通过克隆羊多莉的例子了解了原型模式,从客户端代码运行的结果,很容易发现:通过原型对象创建另一个新对象时,如果更改原型对象的某些类型的属性,新建的对象的属性也可能会发生变化。 浅拷贝     通过原型对象创建另一个新对象时,将原型对象的非静态成员变量复制到新的对象,对于不同类型的成员变量,拷贝规则如下: 如果成员变量的数据类型是值类型(基本数据类型),浅拷贝会直接进行值传递,也就是直接复制一份该属性给新对象; 如果成员变量的数据类型是引用类型,则浅拷贝会进行引用传递,也就是只会将该成员变量的引用值(内存地址)复制一份给新对象,而不会复制引用的对象,那么也就意味着新对象和原型对象的该成员变量都指向同一个实例。因此,一个对象中修改成员变量的值会影响到另一个对象的该成员变量的值。     上一文我们举的例子就是浅拷贝实现克隆羊,SheepPrototype类实现了Cloneable接口,使用了默认的super.clone()方法。 基本类型也称为值类型,分别是字符类型 char,布尔类型 boolean以及数值类型 byte、short、int、long、float、double。 引用类型则包括类、接口、数组、枚举等。 Java 将内存空间分为堆和栈。基本类型直接在栈中存储数值,而引用类型是将引用放在栈中,实际存储的值是放在堆中,通过栈中的引用指向堆中存放的数据。 深拷贝     通过原型对象创建另一个新对象时,将原型对象的非静态成员变量复制到新的对象,不管成员变量的数据类型是值类型还是引用类型,深拷贝都会重新复制一份给新的对象。因此,修改其中一个对象的任何成员变量的值,都不会影响到另一个对象。 深拷贝的实现一、重写clone() 方法    重写clone()方法,把要复制的对象所引用的对象都复制一遍,如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273public class DeepConcreteSheepPrototype implements Cloneable{ private String name; private int age; private String color; private SheepPrototype mother; public DeepConcreteSheepPrototype(String name, int age, String color) { this.name = name; this.age = age; this.color = color; } public DeepConcreteSheepPrototype() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public SheepPrototype getMother() { return mother; } public void setMother(SheepPrototype mother) { this.mother = mother; } /** * 克隆实例 * @return */ @Override protected DeepConcreteSheepPrototype clone() { DeepConcreteSheepPrototype deepConcreteSheepPrototype = null; try { deepConcreteSheepPrototype = (DeepConcreteSheepPrototype) super.clone(); SheepPrototype sheepPrototype = mother.clone(); deepConcreteSheepPrototype.mother = sheepPrototype; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return deepConcreteSheepPrototype; } @Override public String toString() { return "DeepConcreteSheepPrototype{" + "name='" + name + '\\'' + ", age=" + age + ", color='" + color + '\\'' + ", mother=" + mother + '}'; }} 1234567891011121314151617public class CloneDollyPrototype { static class Client{ public static void main(String[] args) { DeepConcreteSheepPrototype deepDolly = new DeepConcreteSheepPrototype("dolly",2,"gray"); deepDolly.setMother(new ConcreteSheepPrototype("dolly",5,"gray")); DeepConcreteSheepPrototype deepConcreteSheepPrototype = deepDolly.clone(); DeepConcreteSheepPrototype deepConcreteSheepPrototype1 = deepDolly.clone(); DeepConcreteSheepPrototype deepConcreteSheepPrototypeN = deepDolly.clone(); deepDolly.getMother().setColor("red"); System.out.println(deepDolly); System.out.println(deepConcreteSheepPrototype); System.out.println(deepConcreteSheepPrototype1); System.out.println(deepConcreteSheepPrototypeN); } }} 运行结果如下: 二、利用序列化 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081public class DeepConcreteSheepSerializable implements Serializable { private static final long serialVersionUID = 8738439006982997247L; private String name; private int age; private String color; private SheepPrototype mother; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public SheepPrototype getMother() { return mother; } public void setMother(SheepPrototype mother) { this.mother = mother; } public Object deepClone(){ ByteArrayOutputStream out = null; ObjectOutputStream obs = null; ByteArrayInputStream ios = null; ObjectInputStream ois = null; try { //序列化 out = new ByteArrayOutputStream(); obs = new ObjectOutputStream(out); obs.writeObject(this); obs.close(); //反序列化 ios = new ByteArrayInputStream(out.toByteArray()); ois = new ObjectInputStream(ios); //返回生成的新对象 DeepConcreteSheepSerializable deepConcreteSheepSerializable = (DeepConcreteSheepSerializable) ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); obs.close(); ios.close(); ois.close(); } catch (IOException ex) { ex.printStackTrace(); } } return null; } @Override public String toString() { return "DeepConcreteSheepSerializable{" + "name='" + name + '\\'' + ", age=" + age + ", color='" + color + '\\'' + ", mother=" + mother + '}'; }} 1234567891011121314public class CloneDollyPrototype { static class Client{ public static void main(String[] args) { DeepConcreteSheepSerializable deepDollySerializable = new DeepConcreteSheepSerializable("dolly",2,"gray"); deepDollySerializable.setMother(new ConcreteSheepPrototype("dolly",5,"gray")); DeepConcreteSheepSerializable deepConcreteSheepSerializable = deepDollySerializable.deepClone(); deepDollySerializable.getMother().setColor("red"); deepDollySerializable.setAge(6); System.out.println(deepDollySerializable); System.out.println(deepConcreteSheepSerializable); } }} 运行结果:     从上面可以看出,利用序列化实现深拷贝主要是在内存中通过字节流的拷贝来实现的。把原型对象序列化写入到字节流中,然后再从字节流中将其读出来进行反序列化,这样就可以创建一个新的对象,当然,此种方式不会存在原型对象与新对象之间引用共享的问题,推荐使用这种方式实现深拷贝。 附:本次演示的项目地址https://github.com/syshlang/java-design-patterns","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计模式","slug":"java/设计模式","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"patterns","slug":"patterns","permalink":"https://syshlang.com/tags/patterns/"}]},{"title":"设计模式之创建型模式中的原型模式(Prototype Pattern)","slug":"java-design-patterns3","date":"2020-01-03T00:58:09.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"129d022b/","link":"","permalink":"https://syshlang.com/129d022b/","excerpt":"","text":"基本介绍     原型模式(Prototype Pattern)主要用于在保证性能的情况下创建重复的对象,也就是创建当前对象的克隆,这种模式是实现了一个原型接口。在开发过程中,如果我们已经明确了所需要创建对象的种类,且创建这种类型对象的代价比较大(创建数量庞大,频繁的数据库交互对数据库等),就可以用原型实例指定所要创建对象的种类,然后通过拷贝这些原型创建新的对象。这就有点像我们生物里面学的细胞分裂。 日常举例 克隆羊的例子:有一只克隆羊叫多莉,没错就是这只羊,长的就是下面图片中的样子。。。现在按照这个样子再克隆N只多莉。。。 传统方式1234567891011121314151617181920212223242526272829303132333435363738public class Sheep { private String name; private int age; private String color; public Sheep(String name, int age, String color) { this.name = name; this.age = age; this.color = color; } public Sheep() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getColor() { return color; } public void setColor(String color) { this.color = color; }} 1234567891011121314/** * 以传统的简单方式克隆多莉羊 * @author sunys */public class CloneDollySimple { static class Client{ public static void main(String[] args) { Sheep dolly = new Sheep("dolly",2,"gray"); Sheep sheep = new Sheep(dolly.getName(),dolly.getAge(),dolly.getColor()); Sheep sheep1 = new Sheep(dolly.getName(),dolly.getAge(),dolly.getColor()); Sheep sheepN = new Sheep(dolly.getName(),dolly.getAge(),dolly.getColor()); } }} 原型模式12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970public abstract class SheepPrototype implements Cloneable{ private String name; private int age; private String color; private SheepPrototype mother; public SheepPrototype(String name, int age, String color) { this.name = name; this.age = age; this.color = color; } public SheepPrototype() { } protected abstract String eat(); public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public SheepPrototype getMother() { return mother; } public void setMother(SheepPrototype mother) { this.mother = mother; } /** * 克隆实例 * @return */ @Override protected SheepPrototype clone() { SheepPrototype sheepPrototype = null; try { sheepPrototype = (SheepPrototype) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return sheepPrototype; } @Override public String toString() { return "SheepPrototype{" + "name='" + name + '\\'' + ", age=" + age + ", color='" + color + '\\'' + ", mother=" + mother + '}'; }} 12345678910111213/** * 创建当前对象的浅表副本 * @author sunys */public class ConcreteSheepPrototype extends SheepPrototype{ public ConcreteSheepPrototype(String name, int age, String color) { super(name, age, color); } @Override protected String eat() { return "Eating green grass"; }} 1234567891011121314151617181920212223/** * * 以原型模式克隆多莉羊 * @author sunys */public class CloneDollyPrototype { static class Client{ public static void main(String[] args) { ConcreteSheepPrototype dolly = new ConcreteSheepPrototype("dolly",2,"gray"); dolly.setMother(new ConcreteSheepPrototype("dolly",5,"gray")); ConcreteSheepPrototype sheepPrototype = (ConcreteSheepPrototype) dolly.clone(); ConcreteSheepPrototype sheepPrototype1 = (ConcreteSheepPrototype) dolly.clone(); ConcreteSheepPrototype sheepPrototypeN = (ConcreteSheepPrototype) dolly.clone(); dolly.getMother().setColor("red"); dolly.setAge(6); System.out.println(dolly); System.out.println(sheepPrototype); System.out.println(sheepPrototype1); System.out.println(sheepPrototypeN); } }} 运行结果:     对比以上的两种方式很容易可以看出,传统的方式虽然易于理解,但是每次创建新的对象时,都需要重新初始化对象,并获取原始对象的属性,然后设置属性,显然,这种方式效率低;相对于传统方式,原型模式就方便快捷很多,在无需关注细节的情况下,就可以通过原型对象创建出另外的可定制对象,这种方式必须实现 Cloneable 接口。 原型模式主要包含3个角色: Prototype(抽象原型类):声明克隆方法的接口,是所有具体原型类的公共父类,它可是抽象类也可以是接口,甚至可以是具体实现类。 ConcretePrototype(具体原型类):它实现抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。 Client(客户端):在客户类中,让一个原型对象克隆自身从而创建一个新的对象。 原型模式在Spring框架中的应用1234567<bean id="pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut" scope="prototype"> <property name="patterns"> <list> <value>com.syshlang.service..*Service.*(..)</value> </list> </property></bean> 1234567@Bean@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE )public class Sheep { private String name; private int age; private String color;}     在上面的配置中可以看到一个标签属性scope=”prototype”,原型模式与名称为prototype的作用域相似。在Spring中,一个类如果被标记为”prototype”,那么将该类注入到另一个bean中或者调用容器的getBean()方法时,都会产生一个新的bean实例。其实,这个产生新的bean的过程就是利用了原型模式。 源码:org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) 1234567//---------------------------------------------------------------------// Implementation of BeanFactory interface//---------------------------------------------------------------------public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false);} 源码:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean()(片段) 123456789101112131415161718protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {...else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); }}... 附:本次演示的项目地址https://github.com/syshlang/java-design-patterns","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计模式","slug":"java/设计模式","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"patterns","slug":"patterns","permalink":"https://syshlang.com/tags/patterns/"}]},{"title":"Oracle和Mysql数据库转换","slug":"mysql-oracle-conversion","date":"2019-12-30T02:22:40.000Z","updated":"2020-09-17T00:34:15.260Z","comments":true,"path":"463fcbf0/","link":"","permalink":"https://syshlang.com/463fcbf0/","excerpt":"","text":"前言    我们的项目一直使用的是SSH框架+mysql+tomcat服务器,但是最近一个新项目对数据库的使用和web服务器提出了硬性要求,按照他们的要求,需要使用Oracle数据库,并采用Weblogic进行服务部署。本文针对本次数据库转换(Mysql转换Oracle)过程中遇到的一些问题做记录,对于部署服务器的更换(tomcat更换为Weblogic)另一文章将继续分享。 问题分析    首先,我们系统的架构情况如下: 开发环境: MAVEN:3.5 JDK:1.8 Spring: 4.3.9.RELEASE hibernate: 4.3.11.Final Shiro:1.4.0 开发工具: MySql: 5.7.x ORACLE: 11.2.0.1.0 Tomcat: 9.0 EZDML:2.06(表结构设计器)     分析了一下部分代码,发现Mysql转换至Oracle,整体框架无需大的改动,由于使用了hibernate框架做持久层,业务操作基本都是HQL,所以无需做单独处理,需要变动的主要涉及到数据库结构的转换和一些公共自定义sql的兼容性处理,明确了解决问题的要点,现在开始着手。 数据库转换数据库结构首先,使用EZDML表结构设计器进行数据库的整体转换1、打开EZDML,点击模型,选择导入数据库: 2、点击确定,选择需要转换的表名,确定之后就可以看到对应的表结构模型;3、在左边单击表明,就可以看到详细的表结构设计,选择生成,就可以看到不同类型数据库对应的建表DDL。 然后,对表结构进行微调Mysql和Oracle之间的数据类型转换 MySQL Data Type Oracle Data Type BIGINT NUMBER(19, 0) BIT RAW BLOB BLOB, RAW CHAR CHAR DATE DATE DATETIME DATE DECIMAL FLOAT (24) DOUBLE FLOAT (24) DOUBLE PRECISION FLOAT (24) ENUM VARCHAR2 FLOAT FLOAT INT NUMBER(10, 0) INTEGER NUMBER(10, 0) LONGBLOB BLOB, RAW LONGTEXT CLOB, RAW MEDIUMBLOB BLOB, RAW MEDIUMINT NUMBER(7, 0) MEDIUMTEXT CLOB, RAW NUMERIC NUMBER REAL FLOAT (24) SET VARCHAR2 SMALLINT NUMBER(5, 0) TEXT VARCHAR2, CLOB TIME DATE TIMESTAMP DATE TINYBLOB RAW TINYINT NUMBER(3, 0) TINYTEXT VARCHAR2 VARCHAR VARCHAR2, CLOB YEAR NUMBER 自增序列处理 MySQL Data Type Oracle Data Type 有自动增长的数据类型,插入记录时不用操作此字段,会自动获得数据值 ORACLE没有自动增长的数据类型,需要建立一个自动增长的序列号,插入记录时要把序列号的下一个值赋于此字段 Mysql自动增长的数据类型 12345create table table_name( ID int auto_increment -- 自增长 primary key); Oracle自动增长的数据类型 1234567891011121314create sequence table_name_pkminvalue 1maxvalue 9999999999999999999999999999start with 447increment by 1cache 20;create table table_name( ID int generated as identity constraint table_name_pk primary key)/ 长字符串处理    在ORACLE中,进行INSERT和UPDATE时,最大可操作的字符串长度必须小于等于4000个单字节。如果超出这个长度,要插入更长的字符串数据,可以考虑使用CLOB类型。在本项目转换过程中,使用到了指纹数据的16进制字符串的存取,长度超过4000,在Mysql中使用BLOB类型存储没问题,但是ORACLE下不可以。BLOB全称为二进制大型对象(Binary Large Object),它用于存储数据库中的大型二进制对象,原来在ORACLE下,对二进制对象有严格要求,所以采用CLOB类型存储。 日期字段处理     在MYSQL中,日期字段可以分DATE(包含年月日)和DATETIME(包含年月日时分秒)两种类型,而ORACLE日期字段只有DATE(包含年月日时分秒)一种类型。所以在实际业务使用过程中需要进行格式转换。 比较项 MySQL Data Type Oracle Data Type 数据类型 DATE(包含年月日)、DATETIME(包含年月日时分秒) DATE(包含年月日时分秒) 日期格式 %Y:代表4位的年份%y:代表2为的年份%m:代表月, 格式为(01……12) %c:代表月, 格式为(1……12)%d:代表月份中的天数,格式为(00……31)%e:代表月份中的天数, 格式为(0……31)%H:代表小时,格式为(00……23)%k:代表 小时,格式为(0……23)%h: 代表小时,格式为(01……12)%I: 代表小时,格式为(01……12)%l :代表小时,格式为(1……12)%i: 代表分钟, 格式为(00……59)%r:代表 时间,格式为12 小时(hh:mm:ss [AP]M)%T:代表 时间,格式为24 小时(hh:mm:ss)%S:代表 秒,格式为(00……59)%s:代表 秒,格式为(00……59) YYYY、YYY、YY 分别代表4位、3位、2位的数字年MM 数字月DD 数字日AM 表示上午或者下午HH24、HH12 代表24小时制或12小时制MI 分钟SS 秒钟 当前日期 sysdate()、current_date、current_time sysdate、current_date、current_timestamp 日期和字符互转 DATE_FORMAT例如:DATE_FORMAT(sysdate(),’%Y-%m-%d %H:%i:%s’)STR_TO_DATE例如:STR_TO_DATE(‘2019-12-30 19:25:34’,’%Y-%m-%d %H:%i:%s’) TO_CHAR例如:TO_CHAR(sysdate,’YYYY-MM-DD HH24:MI:SS’)TO_DATE例如:TO_DATE(‘2019-12-30 19:25:34’,’YYYY-MM-DD HH24:MI:SS’) 日期/时间增减 增减一小时:date_sub(createDate, interval -1 hour)date_sub(createDate, interval 1 hour)增减一天:date_sub(createDate, interval -1 day)date_sub(createDate, interval 1 day)增减一月:date_sub(createDate, interval -1 month)date_sub(createDate, interval 1 month)增减一季度:date_sub(createDate, interval -3 month)date_sub(createDate, interval 3 month)增减一年:date_sub(createDate, interval -1 year)date_sub(createDate, interval 1 year) 增减一小时:createDate+1/24 createDate-1/24增减一天:createDate+1createDate-1增减一月:add_months(createDate, 1)add_months(createDate, -1)增减一季度:add_months(createDate, 3)add_months(createDate, -3)增减一年:add_months(createDate, 12) add_months(createDate, -12) 日期/时间比较 ①直接比较②转成unix时间戳比较③转换为日期类型比较 ①直接比较②转换为日期类型比较 日期/时间比较 12345678910111213-- Mysql-- 直接比较(此种方式不走索引,一定程度上会降低性能)select sysdate() from dual where '2019-12-30 19:39:05' > '2019-12-30 17:39:05';-- 用unix_timestamp函数,将字符型的时间,转成unix时间戳select sysdate() from dual where unix_timestamp('2019-12-30 19:39:05') > unix_timestamp('2019-12-30 17:39:05');-- 将字符串转换为相同格式相同进制的日期类型select sysdate() from dual where str_to_date('2019-12-30 19:39:05','%Y-%m-%d %H:%i:%s') > str_to_date('2019-12-30 17:39:05','%Y-%m-%d %H:%i:%s');-- Oracle-- 直接比较select sysdate from dual where '2019-12-30 19:39:05' > '2019-12-30 17:39:05';-- 将字符串转换为相同格式相同进制的日期类型select sysdate from dual where to_date('2019-12-30 19:39:05','yyyy-mm-dd hh24:mi:ss') > to_date('2019-12-30 17:39:05','yyyy-mm-dd hh24:mi:ss'); 返回Map列名大小写问题    转换过程发现,使用Hibernate返回实体类型的数据我们可以不用关注列名大小写问题,但是返回Map类型的数据集合时,对Mysql和Oracle返回的Map的key大小写是不一致的,也就是返回列名大小写不统一,对于两数据库不同的系统环境列名大小写情况如下: 系统 MySQL Data Type Oracle Data Type Windows 默认都不区分大小写,可通过修改配置来区分大小写:lower_case_table_names = 0(0:区分大小写,1:不区分大小写) 1、在Oracle中,如果字段名称被双引号(””)包裹,Oracle会区分大小写;2、如果字段名称没有被双引号(””)包裹,则全部转换成大写来执行。3、如果表结构设计时,字段名称使用了数据库的保留字,SQL中的字段名称必须用双引号(””)包裹,以避免SQL语句执行出错。 Linux 1、数据库名与表名是严格区分大小写的;2、表的别名是严格区分大小写的;3、列名与列的别名在所有的情况下均是忽略大小写的;4、变量名也是严格区分大小写的; 同Windows     根据以上,可以看出,我们要解决大小写问题,需要对返回列表的字段名加引号(””),这种做法工作量过大,于是查看了一下返回Map集合的接口实现,如下: 12345public List<Map> findMapResultBySql(String sql) { SQLQuery query = this.getCurrentSession().createSQLQuery(sql); query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); return query.list();}     很容易可以看出,处理结果集返回的关键是Transformers.ALIAS_TO_ENTITY_MAP,查看源码 AliasToEntityMapResultTransformer.java123456789101112131415161718192021222324252627282930313233343536public class AliasToEntityMapResultTransformer extends AliasedTupleSubsetResultTransformer { public static final AliasToEntityMapResultTransformer INSTANCE = new AliasToEntityMapResultTransformer(); /** * Disallow instantiation of AliasToEntityMapResultTransformer. */ private AliasToEntityMapResultTransformer() { } @Override public Object transformTuple(Object[] tuple, String[] aliases) { Map result = new HashMap(tuple.length); for ( int i=0; i<tuple.length; i++ ) { String alias = aliases[i]; if ( alias!=null ) { result.put( alias, tuple[i] ); } } return result; } @Override public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) { return false; } /** * Serialization hook for ensuring singleton uniqueing. * * @return The singleton instance : {@link #INSTANCE} */ private Object readResolve() { return INSTANCE; }}     可以看出,AliasToEntityMapResultTransformer类的transformTuple方法即是解决问题的关键,于是自定义自己的返回结果集处理工具,如下: 12345678910111213141516171819202122232425262728/** * 返回map统一小写 * @author sunys */public class CustomResultTransformer extends AliasedTupleSubsetResultTransformer { public static final CustomResultTransformer INSTANCE = new CustomResultTransformer(); private CustomResultTransformer() { @Override public Object transformTuple(Object[] tuple, String[] aliases) { Map result = new HashMap(tuple.length); for ( int i=0; i<tuple.length; i++ ) { String alias = aliases[i]; if ( alias!=null ) { //将Map的key转为小写返回 result.put( alias.toLowerCase(), tuple[i] ); } } return result; } @Override public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) { return false; } private Object readResolve() { return INSTANCE; }}     然后设置SQLQuery的ResultTransformer,如下: 123456789public static final CustomResultTransformer ALIAS_TO_ENTITY_LOWERCASE_MAP = CustomResultTransformer.INSTANCE;public List<Map> findMapResultBySql(String sql) { SQLQuery query = this.getCurrentSession().createSQLQuery(sql); query.setResultTransformer(ALIAS_TO_ENTITY_LOWERCASE_MAP); return query.list();}     至此,Oracle和Mysql数据库返回Map列名统一大小写问题得以解决。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"oracle","slug":"database/oracle","permalink":"https://syshlang.com/categories/database/oracle/"}],"tags":[{"name":"Oracle","slug":"Oracle","permalink":"https://syshlang.com/tags/Oracle/"},{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"}]},{"title":"设计模式之创建型的三种工厂模式(Factory Pattern)","slug":"java-design-patterns2","date":"2019-09-18T01:18:02.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"659a32bd/","link":"","permalink":"https://syshlang.com/659a32bd/","excerpt":"    工厂模式(Factory Pattern)主要是为创建对象提供了接口,实际开发中,我们可以通过指定的工厂来生产对象,在工厂中创建对象时,不会对客户端暴露创建逻辑,并且所有的对象都是通过一个共同的工厂(接口)创建出来的,如果创建对象的业务逻辑发生改变,我们只需改动“工厂”的逻辑,这在一定程度上降低了耦合度,达到了解耦的目的。","text":"    工厂模式(Factory Pattern)主要是为创建对象提供了接口,实际开发中,我们可以通过指定的工厂来生产对象,在工厂中创建对象时,不会对客户端暴露创建逻辑,并且所有的对象都是通过一个共同的工厂(接口)创建出来的,如果创建对象的业务逻辑发生改变,我们只需改动“工厂”的逻辑,这在一定程度上降低了耦合度,达到了解耦的目的。     工厂模式(Factory Pattern)可以细分为三类:简单工厂模式(Simple Factory)、 工厂方法模式(Factory Method)、 抽象工厂模式(Abstract Factory)。 简单工厂模式(Simple Factory) 简单工厂模式(Simple Factory)也叫静态工厂模式。 12345678910111213141516171819202122232425262728293031323334353637383940/** * @author sunys */public class SimpleFactory { public static void main(String[] args) { Shape shape = ShapeSimpleFactory.getShapeByType("circle"); } private interface Shape{ /** * @return */ String getShapeType(); } private static class Circle implements Shape { public String getShapeType() { return "circle"; } } private static class Rectangle implements Shape { public String getShapeType() { return "rectangle"; } } private static class ShapeSimpleFactory{ public static Shape getShapeByType(String type){ if ("circle".equals(type)){ return new Circle(); }else if ("rectangle".equals(type)){ return new Rectangle(); }else { return null; } } }}     在上面的例子中,提供了一个ShapeSimpleFactory类,用于封装实例化对象的行为,作为生产几何图形Shape类型对象的“工厂”,该类提供了一个公共的静态方法用于根据类型获取对应的几何图形实体。可以看到这种方式简单粗暴,使用了if…else来做业务逻辑的判断生产对象,试想,那么如果项目的规模变大,Shape的类型增多。。。 工厂方法模式(Factory Method)123456789101112131415161718192021222324252627282930313233/** * @author sunys */public class FactoryMethod { public static void main(String[] args) { CircleFactory circleFactory = new CircleFactory(); Shape circle = circleFactory.createShape(); RectangleFactory rectangleFactory = new RectangleFactory(); Shape rectangle = rectangleFactory.createShape(); } private interface Shape{} private static class Circle implements Shape { } private static class Rectangle implements Shape { } private interface ShapeFactoryMethod{ /** * 生产几何图型 * @return */ Shape createShape(); } private static class CircleFactory implements ShapeFactoryMethod{ public Shape createShape() { return new Circle(); } } private static class RectangleFactory implements ShapeFactoryMethod{ public Shape createShape() { return new Rectangle(); } }}     在工厂方法模式的例子中,定义了一个创建对象的父类接口ShapeFactoryMethod,并声明了生产几何图形的方法createShape,由子类工厂实现生产对象的逻辑。乍一看,工厂方法跟简单工厂似乎不一样,但实际上是“换汤不换药”,简单工厂是采用if…else,而工厂方法模式则是将对象的实例化推迟到子类实现。显然,项目的规模变大时,需要创建更多的工厂,该种方式也不适用。     简单工厂模式可以看做是工厂方法模式的一种特例,两个归为一类,当项目需要生产的特定对象种类较少时,可以采用这种方式,简单实用。 抽象工厂模式(Abstract Factory)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748/** * @author sunys */public class AbstractFactory { public static void main(String[] args) { ShapeAbstractFactory circlecombination = ShapeAbstractFactory.getFactory("circlecombination"); Shape shape = circlecombination.getShape(); } private interface Shape{} private static class Circle implements Shape { } private static class Rectangle implements Shape { } /** * 圆组合 */ private static class CircleCombination extends ShapeAbstractFactory { @Override Shape getShape() { return new Circle(); } } /** * 矩形组合 */ private static class RectangleCombination extends ShapeAbstractFactory { @Override Shape getShape() { return new Rectangle(); } } private static abstract class ShapeAbstractFactory{ private static final CircleCombination CIRCLECOMBINATION = new CircleCombination(); private static final RectangleCombination RECTANGLECOMBINATION = new RectangleCombination(); static ShapeAbstractFactory getFactory(String Combination){ if ("circlecombination".equals(Combination)){ return CIRCLECOMBINATION; } if ("rectanglecombination".equals(Combination)){ return RECTANGLECOMBINATION; } return null; } abstract Shape getShape(); }}     看上面的代码例子,可以看到简单工厂和工厂方法的影子,实际上抽象工厂模式可以看作是将简单工厂模式和工厂方法模式进行整合,或者是升级改进版。将工厂抽象成两层,AbstractFactory(抽象工厂)和具体实现的工厂子类,那么我们在创建对象时就可以根据对象的类型特点选择对应的工厂子类进行生产,这样一来,就相当将上面提到的简单工厂模式的单个的工厂类变成了工厂簇,这种方式不仅更利于代码的维护和扩展,而且适用于大规模项目中需要生产大量对象的情况。 附:本次演示的项目地址https://github.com/syshlang/java-design-patterns","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计模式","slug":"java/设计模式","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"patterns","slug":"patterns","permalink":"https://syshlang.com/tags/patterns/"}]},{"title":"设计模式之创建型模式中的单例模式(Singleton Pattern)","slug":"java-design-patterns1","date":"2019-09-16T14:11:01.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"fc936307/","link":"","permalink":"https://syshlang.com/fc936307/","excerpt":"","text":"    单例模式(Singleton Pattern)是我们平时开发中用的比较多的一种设计模式,如上图所示,所谓类的单例设计模式,就是确保在整个的软件系统中一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,该类只提供一个取得其对象实例的静态方法。 单例设计模式八种方式饿汉式采用静态常量实现1234567891011121314151617181920212223/** * @author sunys */public class HungryStaticConstants { /** * 构造器私有化,防止new */ private HungryStaticConstants() { } /** * 类内部创建静态对象实例 */ private static final HungryStaticConstants instance = new HungryStaticConstants(); /** * 提供一个公有的取得其对象实例的静态方法 * @return */ public static HungryStaticConstants getInstance(){ return instance; }}     这种方式,在类装载时instance就被实例化,由于类装载原因的不确定性,也就像这种方式的名字所表达的意思一样,饿汉式,没有达到懒加载(lazy loading)的效果,并且还可能造成内存浪费。由于是基于classloder机制的,避免了多线程的同步问题,是多线程安全的,并且没有锁机制,因此执行效率会提高。 采用静态代码块实现12345678910111213141516171819202122232425/** * @author sunys */public class HungryStaticBlock { /** * 构造器私有化,防止new */ private HungryStaticBlock() { } private static final HungryStaticBlock instance; /** * 在静态代码块中,创建单例对象 */ static { instance = new HungryStaticBlock(); } /** * 提供一个公有的取得其对象实例的静态方法 * @return */ public static HungryStaticBlock getInstance() { return instance; }}     这种方式和上面的方式差不多,只不过是将类的实例化放在了静态代码块中进行,static块的执行发生在初始化的阶段。初始化阶段,jvm主要完成对静态变量的初始化,静态块执行等工作,也就是说instance在这个时候被实例化,其优缺点和上面的方式是一样。 懒汉式线程不安全12345678910111213141516171819202122/** * @author sunys */public class LazyThreadUnsafe { /** * 构造器私有化,防止new */ private LazyThreadUnsafe() { } private static LazyThreadUnsafe instance; /** * 提供一个静态的公有方法,当使用到该方法时,才去实例化instance * @return */ public static LazyThreadUnsafe getInstance() { if (instance == null){ instance = new LazyThreadUnsafe(); } return instance; }}     与饿汉式不一样的地方,显然这种方式达到了懒加载(lazy loading)的效果。但是由于这种方式没有加锁 synchronized,它不是多线程安全的,在多线程下会有产生多个实例的可能。 线程安全采用同步方法的实现12345678910111213141516171819202122/** * @author sunys */public class LazyThreadSafeSyncMethod { /** * 构造器私有化,防止new */ private LazyThreadSafeSyncMethod() { } private static LazyThreadSafeSyncMethod instance; /** * /提供一个静态的公有方法,加入同步锁,解决线程安全问题 * @return */ public static synchronized LazyThreadSafeSyncMethod getInstance() { if (instance == null){ instance = new LazyThreadSafeSyncMethod(); } return instance; }}     很显然,这种方式达到了懒加载(lazy loading)的效果,并且对方法加了同步锁,解决了线程安全问题,但是每个线程在获得实例执行getInstance()方法时都要进行同步,效率很低。 采用同步代码块实现123456789101112131415161718192021222324/** * @author sunys */public class LazyThreadSafeSyncBlock { /** * 构造器私有化,防止new */ private LazyThreadSafeSyncBlock() { } private static LazyThreadSafeSyncBlock instance; /** * 提供一个静态的公有方法,对代码块加入同步锁,解决线程安全问题 * @return */ public static LazyThreadSafeSyncBlock getInstance() { if (instance == null){ synchronized (LazyThreadSafeSyncBlock.class){ instance = new LazyThreadSafeSyncBlock(); } } return instance; }}     这种方式和上面的方式差不多,只不过是将同步锁从方法上移到了代码块上,实际效果也不理想。 双检锁/双重校验锁(DCL:double-checked locking)1234567891011121314151617181920212223242526/** * @author sunys */public class DoubleCheckedLock { /** * 构造器私有化,防止new */ private DoubleCheckedLock() { } private static volatile DoubleCheckedLock instance; /** * 提供一个静态的公有方法,加入双重检查代码和同步锁,解决线程安全问题,同时解决懒加载问题 * @return */ public static DoubleCheckedLock getInstance() { if (instance == null){ synchronized (DoubleCheckedLock.class){ if (instance == null){ instance = new DoubleCheckedLock(); } } } return instance; }}     这种方式可以看作是上面两种方式的结合体或者升级方式,这种方式采用了双重校验加锁的机制,既达到了懒加载(lazy loading)的效果,又保证了线程安全,而且效率较高。     volatile是一个类型修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。volatile关键字的两层语义: 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的; 禁止进行指令重排序。 登记式/静态内部类1234567891011121314151617181920212223242526/** * @author sunys */public class StaticInternalClass { /** * 构造器私有化,防止new */ private StaticInternalClass() { } /** * 提供一个静态内部类, * 该类中有一个静态属性StaticInternalClass */ private static class SingletonHolder { private static final StaticInternalClass INSTANCE = new StaticInternalClass(); } /** * 提供一个静态的公有方法,直接返回内部类的静态属性 * @return */ public static StaticInternalClass getInstance() { return SingletonHolder.INSTANCE; }}     首先,这种方式采用了类装载的机制来保证初始化实例时只有一个线程(在类进行初始化时,别的线程是无法进入的),巧妙的解决了线程安全问腿;其次,静态内部类的方式在SingletonHolder类被装载时并不会立即实例化,而是在需要实例化(调用getInstance方法)时,才会装载SingletonHolder类,从而完成StaticInternalClass的实例化,也很巧妙的达到了懒加载(lazy loading)的效果。当然,也不会存在效率低的问题。 枚举1234567891011121314151617/** * @author sunys */public class SingletonEnum { public static void main(String[] args) { Instance instance = Singleton.INSTANCE.instance; } private static class Instance{} enum Singleton{ INSTANCE; private Instance instance; Singleton() { instance = new Instance(); } }}     首先,枚举类的构造方法限制为私有的,我们访问枚举类的实例时会执行它的构造方法,并且每个枚举类的实例都是static final类型的,也就是只能被实例化一次,实际也就是通过类加载机制保证了线程安全。 单例模式在Spring框架中的应用     先上源码,org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean) DefaultSingletonBeanRegistry.java12345678910111213141516171819202122232425 /** * Return the (raw) singleton object registered under the given name. * <p>Checks already instantiated singletons and also allows for an early * reference to a currently created singleton (resolving a circular reference). * @param beanName the name of the bean to look for * @param allowEarlyReference whether early references should be created or not * @return the registered singleton object, or {@code null} if none found */protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null);}     如上,Spring依赖注入Bean实例默认是单例的,Spring的依赖注入主要是在org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)方法中,getBean中调用的 doGetBean 方法调用了getSingleton 进行bean的创建,从上面代码可以看到,spring依赖注入时,使用了双重判断加锁的单例模式。 附:本次演示的项目地址https://github.com/syshlang/java-design-patterns","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计模式","slug":"java/设计模式","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"patterns","slug":"patterns","permalink":"https://syshlang.com/tags/patterns/"}]},{"title":"设计模式七大原则之合成复用原则(Composite Reuse Principle)","slug":"java-design-principle7","date":"2019-09-02T14:50:32.000Z","updated":"2020-09-17T00:34:15.260Z","comments":true,"path":"fd78ccb3/","link":"","permalink":"https://syshlang.com/fd78ccb3/","excerpt":"    合成复用原则(CRP:Composite Reuse Principle)又称为组合/聚合复用原则(CARP:Composition/Aggregate Reuse Principle,),该原则指出尽量使用合成/聚合,尽量不要使用类继承。","text":"    合成复用原则(CRP:Composite Reuse Principle)又称为组合/聚合复用原则(CARP:Composition/Aggregate Reuse Principle,),该原则指出尽量使用合成/聚合,尽量不要使用类继承。     在面向对象设计中,其中一个很重要的目标 就是提高软件的高可复用性。而复用已有的设计和实现通常有两种方法,也就是上面提到的,通过组合/聚合关系或通过继承。但是,合成复用原则主张优先考虑使用组合/聚合的方式来达到复用的目的。    为什么要这么主张呢?    实际上在《设计模式七大原则之里氏代换原则(Liskov Substitution Principle)》一文中,我们就提到继承实际上是增强了两个类耦合性,子类在继承基类时,会将基类的实现细节暴露给子类,基类的内部细节通常对子类来说是可见的,其实这种复用又叫作“白箱”复用。这种复用容易在“不知情”的情况下破坏系统的封装性,所以遵循里氏代换原则,我们优先通过聚合,组合,依赖等方式来解决问题。    组合/聚合复用有什么好处?    通过组合或聚合达到复用目的的做法实际上是,将已有的成员对象加入到新的对象中,让其成为新对象的一部分,而不破坏原有对象的封装,这样使得成员对象的内部细节不会暴露给新对象,所需的依赖较少,相对于继承复用来说,这种方式降低了耦合性,这种复用又叫作“黑箱”复用。 合成:表示一种强的拥有关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。合成关系用实心的菱形+实线来表示,如下图:聚合:表示一种弱的拥有关系,体现的是A对象可以包含B对象,但是B对象并不是A对象的一部分。聚合关系用空心的菱形+实线来表示,如下图:     合成/聚合复用原则在设计模式中最好的体现就是桥接(Bridge)模式,后面介绍设计模式时再继续介绍。 附:本次演示的项目地址https://github.com/syshlang/java-design-principle","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计原则","slug":"java/设计原则","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"principle","slug":"principle","permalink":"https://syshlang.com/tags/principle/"}]},{"title":"设计模式七大原则之迪米特法则(Demeter Principle)","slug":"java-design-principle6","date":"2019-09-01T13:28:20.000Z","updated":"2020-09-17T00:34:15.260Z","comments":true,"path":"8a7ffc25/","link":"","permalink":"https://syshlang.com/8a7ffc25/","excerpt":"    迪米特法则(LOD:Law of Demeter/Demeter Principle),又叫最少知道原则(LKP:Least Knowledge Principle),即一个类对自己依赖的类知道的越少越好。","text":"    迪米特法则(LOD:Law of Demeter/Demeter Principle),又叫最少知道原则(LKP:Least Knowledge Principle),即一个类对自己依赖的类知道的越少越好。     其实,迪米特法则反应出来的核心思想还是编程中总的原则:低耦合,高内聚。类与类关系越密切,耦合度越大,如果实际的需求发生改变,我们需要更改软件,当一个类改动时,另一个或多个类跟这个类的关系越密切(耦合度越高),受到的影响也越大。迪米特法则还有一个更简单的定义:只与直接的朋友通信。 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。 举一个打印员工绩效的例子 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102/** * @author sunys */public class Demeterprinciple { public static void main(String[] args) { DevelopmentCenterManage.showPerformance(); } /** * 开发中心员工 */ private static class DevelopmentCenter{ private String name; private String performance; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPerformance() { return performance; } public void setPerformance(String performance) { this.performance = performance; } } /** * 开发中心的开发一部员工 */ private static class FirstDepartment{ private String name; private String performance; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPerformance() { return performance; } public void setPerformance(String performance) { this.performance = performance; } } /** * 开发中心的开发一部员工管理类 */ private static class FirstDepartmentManager{ private static List<FirstDepartment> getFirstDepartment(){ List<FirstDepartment> firstDepartmentList = new ArrayList<FirstDepartment>(); FirstDepartment firstDepartment = new FirstDepartment(); firstDepartment.setName("zhangsan"); firstDepartment.setPerformance("A"); firstDepartmentList.add(firstDepartment); return firstDepartmentList; } } /** * 开发中心管理类 */ private static class DevelopmentCenterManage{ /** * 获取开发中心所有员工绩效 * @return */ private static List<DevelopmentCenter> getDevelopmentCenter(){ List<DevelopmentCenter> developmentCenterList = new ArrayList<DevelopmentCenter>(); DevelopmentCenter developmentCenter = new DevelopmentCenter(); developmentCenter.setName("zhangsan"); developmentCenter.setPerformance("A"); developmentCenterList.add(developmentCenter); return developmentCenterList; } /** * 打印绩效 */ private static void showPerformance(){ List<DevelopmentCenter> developmentCenterList = getDevelopmentCenter(); for (DevelopmentCenter developmentCenter : developmentCenterList ){ System.out.println("name:"+ developmentCenter.getName() + " performance:"+ developmentCenter.getPerformance()); } // 开发中心的开发一部员工普遍绩效比较优秀也要打印出来 // 但此处引入了DevelopmentCenterManage的非直接朋友类FirstDepartment,违反了迪米特法则 List<FirstDepartment> firstDepartmentList = FirstDepartmentManager.getFirstDepartment(); for (FirstDepartment firstDepartment : firstDepartmentList) { System.out.println("name:"+ firstDepartment.getName() + " performance:"+ firstDepartment.getPerformance()); } } }}     在上面的例子中,我们想打印出开发中心所有员工的绩效情况,但是由于开发中心的开发一部员工普遍表现优秀,所以也想一起打印出来,但共用了DevelopmentCenterManage类的打印方法,最终导致引入了DevelopmentCenterManage的非直接朋友类FirstDepartment,违反了迪米特法则,这样显然是增加了不必要的耦合。因此,改进如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110/** * @author sunys */public class Demeterprinciple1 { public static void main(String[] args) { DevelopmentCenterManage.showPerformance(); } /** * 开发中心员工 */ private static class DevelopmentCenter{ private String name; private String performance; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPerformance() { return performance; } public void setPerformance(String performance) { this.performance = performance; } } /** * 开发中心的开发一部员工 */ private static class FirstDepartment{ private String name; private String performance; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPerformance() { return performance; } public void setPerformance(String performance) { this.performance = performance; } } /** * 开发中心的开发一部员工管理类 */ private static class FirstDepartmentManager{ private static List<FirstDepartment> getFirstDepartment(){ List<FirstDepartment> firstDepartmentList = new ArrayList<FirstDepartment>(); FirstDepartment firstDepartment = new FirstDepartment(); firstDepartment.setName("zhangsan"); firstDepartment.setPerformance("A"); firstDepartmentList.add(firstDepartment); return firstDepartmentList; } /** * 增加独立的打印方法 */ private static void showFirstDepartmentPerformance(){ List<FirstDepartment> firstDepartmentList = getFirstDepartment(); for (FirstDepartment firstDepartment : firstDepartmentList) { System.out.println("name:"+ firstDepartment.getName() + " performance:"+ firstDepartment.getPerformance()); } } } /** * 开发中心管理类 */ private static class DevelopmentCenterManage{ /** * 获取开发中心所有员工绩效 * @return */ private static List<DevelopmentCenter> getDevelopmentCenter(){ List<DevelopmentCenter> developmentCenterList = new ArrayList<DevelopmentCenter>(); DevelopmentCenter developmentCenter = new DevelopmentCenter(); developmentCenter.setName("zhangsan"); developmentCenter.setPerformance("A"); developmentCenterList.add(developmentCenter); return developmentCenterList; } /** * 打印绩效 */ private static void showPerformance(){ List<DevelopmentCenter> developmentCenterList = getDevelopmentCenter(); for (DevelopmentCenter developmentCenter : developmentCenterList ){ System.out.println("name:"+ developmentCenter.getName() + " performance:"+ developmentCenter.getPerformance()); } // 遵循迪米特法则,这里调用FirstDepartmentManager类自己封装的打印方法, // 不在引入非直接朋友类FirstDepartment FirstDepartmentManager.showFirstDepartmentPerformance(); } }}     迪米特法则的核心是降低类之间的耦合,在实际开发的过程中,对于被依赖的类不管多么复杂,我们都应尽量将逻辑封装在类的内部,对外除了提供的public方法,不对外泄露任何信息。当然,这不是要求完全没有依赖关系,使用迪米特法则时我们应当根据实际情况权衡,尽量降低类间(对象间)耦合关系。 附:本次演示的项目地址https://github.com/syshlang/java-design-principle","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计原则","slug":"java/设计原则","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"principle","slug":"principle","permalink":"https://syshlang.com/tags/principle/"}]},{"title":"设计模式七大原则之开闭原则 (Open Close Principle)","slug":"java-design-principle5","date":"2019-08-28T02:42:38.000Z","updated":"2020-09-17T00:34:15.260Z","comments":true,"path":"1376ad9f/","link":"","permalink":"https://syshlang.com/1376ad9f/","excerpt":"    开闭原则 (OCP:Open Close Principle)是编程中最基础、最重要的设计原则。它指出,一个软件中的对象(类,模块,函数等等)应该对于扩展是开放的(对提供方),但是对于修改是封闭的(对使用方)。","text":"    开闭原则 (OCP:Open Close Principle)是编程中最基础、最重要的设计原则。它指出,一个软件中的对象(类,模块,函数等等)应该对于扩展是开放的(对提供方),但是对于修改是封闭的(对使用方)。 举一个计算几何形状周长的例子 1234567891011121314151617181920212223242526/** * @author sunys */public class Openclose { public static void main(String[] args) { CalculatePerimeter calculatePerimeter = new CalculatePerimeter(); calculatePerimeter.rectanglePerimeter(8,4); } /** * 计算几何图形周长的工具类,提供了一个计算矩形周长的方法 * 这个种方式扩展性不好,如果新增一个计算圆形的周长, * 需要改动类,增加新的方法(违背开闭原则) */ private static class CalculatePerimeter{ /** * 计算矩形周长 * @param length * @param width * @return */ public double rectanglePerimeter(double length,double width){ return (length + width) * 2; } }}     在上面的例子中,CalculatePerimeter类用于计算几何图形周长,并提供了一个计算矩形周长的方法,这种设计方式违反了设计模式的ocp原则,假如我们需要计算其他图形的周长,需要改动原有的类,增加新的方法,如果业务复杂需要改动的地方会更多,扩展性不好很差。改进方式如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364/** * @author sunys */public class Openclose1 { public static void main(String[] args) { CalculatePerimeter calculatePerimeter = new CalculatePerimeter(); Rectangle rectangle = new Rectangle(8,4); calculatePerimeter.calculatePerimeter(rectangle); //如果需要增加计算圆周长,只需自行扩展圆类型 Circle circle = new Circle(4); calculatePerimeter.calculatePerimeter(circle); } /** * 计算几何图形周长的工具类 */ private static class CalculatePerimeter{ public double calculatePerimeter(Shape shape){ return shape.shapePerimeter(); } } /** * 抽象出几何图形基类 */ private static abstract class Shape{ public abstract double shapePerimeter(); } /** * 在矩形中实现计算周长的细节 */ private static class Rectangle extends Shape{ private double length; private double width; public Rectangle(double length, double width) { this.length = length; this.width = width; } @Override public double shapePerimeter() { return (length + width) * 2; } } /** * 扩展计算圆形周长 */ private static class Circle extends Shape{ private final double π = 3.14; private double radius; public Circle(double radius) { this.radius = radius; } @Override public double shapePerimeter() { return 2 * π * radius; } }}     开闭原则体现出来的思想就是用抽象构建框架,用实现扩展细节。抽象的灵活性好,适应性广,易扩展,在框架设计时,我们要设计合理的抽象基类,在抽象派生的实现类中实现细节部分,这样当软件需要变化时,可以通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现,这种方式从一定程度上保证了软件架构的稳定性。 附:本次演示的项目地址https://github.com/syshlang/java-design-principle","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计原则","slug":"java/设计原则","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"principle","slug":"principle","permalink":"https://syshlang.com/tags/principle/"}]},{"title":"设计模式七大原则之里氏代换原则(Liskov Substitution Principle)","slug":"java-design-principle4","date":"2019-08-23T08:18:16.000Z","updated":"2020-09-17T00:34:15.256Z","comments":true,"path":"64719d09/","link":"","permalink":"https://syshlang.com/64719d09/","excerpt":"    里氏代换原则(LSP:Liskov Substitution Principle)指出:如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。    换句话说,所有引用基类的地方必须能透明地使用其子类的对象。对于子类而言,可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:","text":"    里氏代换原则(LSP:Liskov Substitution Principle)指出:如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。    换句话说,所有引用基类的地方必须能透明地使用其子类的对象。对于子类而言,可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义: 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法; 子类中可以增加自己特有的方法; 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松; 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。 还是举一个交通工具的例子 123456789101112131415161718192021222324252627282930/** * @author sunys */public class Liskovsubstitution { public static void main(String[] args) { Transportation transportation = new Transportation(); transportation.run("轮船"); Transportation1 transportation1 = new Transportation1(); transportation1.run("轮船"); transportation1.runSpeed(120); } private static class Transportation{ void run(String transportation) { System.out.println(transportation + " 在水中航行...."); } } private static class Transportation1 extends Transportation{ /** * 覆盖父类的方法 * @param vehicle */ @Override void run(String vehicle) { System.out.println(vehicle + " 在公路上运行...."); } void runSpeed(int runSpeed){ System.out.println("行驶速度为:"+ runSpeed); } }}     由上面的例子我们可以看出继承的风险,Transportation类有一个打印交通工具行驶方式行驶方式的功能,且该功能只适用于水中运行的交通工具,刚开始一切正常。但是,现在多了一个新的需求,要打印交通工具的行驶速度,类Transportation1来负责实现,于是Transportation1继承Transportation完成第二个功能,但是在调用者不知道的情况下无意间覆盖(重写)了父类中的方法,导致原本正常的功能发生逻辑错误。改进方式:去掉原有的继承关系,让父类和子类都继承一个更通俗的基类,使用组合方式完成功能。如下: 123456789101112131415161718192021222324252627282930313233343536/** * @author sunys */public class Liskovsubstitution1 { public static void main(String[] args) { TransportationSpeed transportationSpeed = new TransportationSpeed("轮船",120); transportationSpeed.runSpeed(); } private static class Transportation{ public String way; public int speed; } private static class TransportationWay extends Transportation{ void run(String transportation) { System.out.println(transportation + " 在水中航行...."); } } private static class TransportationSpeed extends Transportation{ //如果需要使用TransportationWay类的方法,使用组合关系 private TransportationWay transportationWay = new TransportationWay(); public TransportationSpeed(String way,int speed) { this.way = way; this.speed = speed; } public TransportationSpeed() { } void runSpeed(){ //组合调用TransportationWay类run方法获取行驶方式 transportationWay.run(way); System.out.println("行驶速度为:"+ speed); } }}     里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在实际开发的过程中,使用继承时,我们应当遵循里氏替换原则,在子类中尽量不要重写父类的方法,适当情况下,我们可以通过聚合,组合,依赖等方式来解决问题。 附:本次演示的项目地址https://github.com/syshlang/java-design-principle","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计原则","slug":"java/设计原则","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"principle","slug":"principle","permalink":"https://syshlang.com/tags/principle/"}]},{"title":"设计模式七大原则之依赖倒转原则 (Dependence Inversion Principle)","slug":"java-design-principle3","date":"2019-08-23T03:04:25.000Z","updated":"2020-09-17T00:34:15.256Z","comments":true,"path":"fa1508aa/","link":"","permalink":"https://syshlang.com/fa1508aa/","excerpt":"    依赖倒转原则(DIP: Dependence Inversion Principle)也说依赖倒置原则,包含三层含义:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。依赖倒转(倒置)的中心思想就是面向接口编程。","text":"    依赖倒转原则(DIP: Dependence Inversion Principle)也说依赖倒置原则,包含三层含义:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。依赖倒转(倒置)的中心思想就是面向接口编程。 举个我们日常通信的例子 123456789101112131415161718192021/** * @author sunys */public class Dependenceinversion { public static void main(String[] args) { People people = new People(); people.sendMessageBySMS(new SMScommunicate()); } private static class SMScommunicate{ public void sendMessage(){ System.out.println("使用手机短信发送信息!"); } } private static class People{ public void sendMessageBySMS(SMScommunicate smScommunicate){ smScommunicate.sendMessage(); } }} 在很久之前,互联不发达时,我们通信一般采用手机发送短信或者打电话,如上面的代码,一切正常。但是,随机互联网的普及更多的通信方式出现了,例如微信、QQ等,需求场景发生变化,那么最简单的做法就是新增微信和QQ的通讯类,并且给People增加相应的方法。显然,这种方法不能满足多变性需求,且不稳定。所以,我们使用另一种思路解决,如下: 12345678910111213141516171819202122232425262728293031323334353637383940/** * @author sunys */public class Dependenceinversion1 { public static void main(String[] args) { People people = new People(); people.sendMessage(new SMScommunicate()); people.sendMessage(new QQcommunicate()); people.sendMessage(new Wechatcommunicate()); } private interface Communicate{ void sendMessage(); } private static class SMScommunicate implements Communicate{ public void sendMessage(){ System.out.println("使用手机短信发送信息!"); } } private static class QQcommunicate implements Communicate{ public void sendMessage() { System.out.println("使用QQ发送信息!"); } } private static class Wechatcommunicate implements Communicate{ public void sendMessage() { System.out.println("使用微信发送信息!"); } } private static class People{ public void sendMessage(Communicate communicate){ communicate.sendMessage(); } }} 根据依赖倒置原则,在新的解决方案中,我们引入了一个通讯工具的接口Communicate,让几种不同的通信方式去实现接口,这样修改后,无论以后怎样扩展,都不需要再修改People类了。 依赖关系传递的三种方式 接口声明依赖 在上面日常通信的例子中,我们在用的就是接口声明依赖的方式,该方法也叫做接口注入。 构造函数传递依赖对象 1234567891011121314151617/** * 构造函数传递依赖对象 * @author sunys */public class People implements Communicate{ private Communicate communicate; //构造函数注入 public People(Communicate communicate) { this.communicate = communicate; } public void sendMessage() { communicate.sendMessage(); }}interface Communicate{ void sendMessage();} Setter方法传递依赖对象 12345678910111213141516171819/** * Setter方法传递依赖对象 * @author sunys */public class People1 implements Communicate1{ private Communicate communicate; //Setter依赖注入 public void setCommunicate(Communicate communicate) { this.communicate = communicate; } public void sendMessage() { communicate.sendMessage(); }}interface Communicate1{ void sendMessage();}     在java中,抽象指的是接口或抽象类,细节就是具体的实现类,相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。在实际的开发中,低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好;变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化;使用继承时遵循 里氏代换原则(Liskov Substitution Principle)。 附:本次演示的项目地址 https://github.com/syshlang/java-design-principle","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计原则","slug":"java/设计原则","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"principle","slug":"principle","permalink":"https://syshlang.com/tags/principle/"}]},{"title":"设计模式七大原则之接口隔离原则 (Interface Segregation Principle)","slug":"java-design-principle2","date":"2019-08-18T09:06:06.000Z","updated":"2020-09-17T00:34:15.256Z","comments":true,"path":"8d12383c/","link":"","permalink":"https://syshlang.com/8d12383c/","excerpt":"    接口隔离原则 (ISP: Interface Segregation Principle)有两种定义:客户端不应该依赖它不需要的接口;类间的依赖关系应该建立在最小的接口上。","text":"    接口隔离原则 (ISP: Interface Segregation Principle)有两种定义:客户端不应该依赖它不需要的接口;类间的依赖关系应该建立在最小的接口上。 类之间通过接口依赖的例子 123456789101112131415161718192021222324252627282930 /** * @author sunys */public class Interfacesegregation { public static void main(String[] args) { B b = new B(); b.dependA(new A()); } interface InterfaceCommon{ void methodA(); void methodOther(); } private static class A implements InterfaceCommon{ public void methodA() { System.out.println("A类方法"); } public void methodOther() { System.out.println("其他类方法"); } } private static class B { public void dependA(InterfaceCommon interfaceCommon){ System.out.println("通过公共接口依赖A类调用A类方法"); } }} 在上面的例子中可以看出,我们使用B类时想通过公共接口InterfaceCommon依赖A类,调用A类实现的方法,但是A类在实现自己的方法时还必须去实现其他类的方法。因此,InterfaceCommon对于B来说不是最小的接口,违背了接口隔离原则。更改方案,将InterfaceCommon拆分为独立的接口,如下: 12345678910111213141516171819202122232425262728/** * @author sunys */public class Interfacesegregation1 { public static void main(String[] args) { B b = new B(); b.dependC(new A()); } private interface InterfaceA{ void methodA(); } private interface InterfaceOther{ void methodOther(); } private static class A implements InterfaceA{ public void methodA() { System.out.println("A类方法"); } } private static class B { public void dependC(InterfaceA interfaceA){ System.out.println("通过最小接口依赖A类调用A类方法"); } }} 采用接口隔离原则对接口进行约束时,要注意以下几点: 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。 接口隔离原则和单一职责的异同: 共同点:这两种原则都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想。 不同点: 侧重点不一样,单一职责原则注重的是类的功能职责单一;接口隔离原则注重的是对接口依赖的隔离; 约束对象不一样,单一职责原则主要约束类,它针对的是类的功能职责,程序的实现和细节;接口隔离原则主要约束接口,它针对的是接口模板功能的设计构建。 附:本次演示的项目地址https://github.com/syshlang/java-design-principle","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计原则","slug":"java/设计原则","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"principle","slug":"principle","permalink":"https://syshlang.com/tags/principle/"}]},{"title":"Linux上安装 MySQL","slug":"database-mysql-install-linux","date":"2019-08-10T01:15:41.000Z","updated":"2020-09-25T08:41:02.609Z","comments":true,"path":"728791e9/","link":"","permalink":"https://syshlang.com/728791e9/","excerpt":"","text":"前言     上一篇文章中,讲到了在Window上通过绿色版压缩包安装的方式安装MySQL,本文继续讲解在Linux系统环境下安装MySQL。Deepin下安装MySQL三种方式: 在线安装:通过命令sudo apt-get install mysql-server在线安装,安装完毕之后已经自动配置好环境变量,可以直接使用,比Window下方便很多;二进制包安装:在网上下载二进制安装包,通过sudo dpkg -i [deb包名]进行安装,安装完毕之后已经自动配置好环境变量,可以直接使用;离线安装:通过下载离线安装包进行配置安装。      在线安装和二进制包安装相对比较简单,不同的Linux发行版本安装命令也各有差异,离线安装的方式通用性相对更强,不同的Linux版本安装方式也大同小异,本文重点也是讲述离线的安装方式。 一、准备工作1、系统环境     Linux系统的发行版本较多,本此安装是在Deepin 20 Beta环境为例,具体参数如下: Linux version 5.4.50-amd64-desktop linux内核版本号 2、下载安装包     进入MySQL 下载,可以看到mysql的下载包列表。我选择的是8.0.21版本。     Deepin和Ubuntu都是基于Debian的,他们的软件包管理器相同,都用的apt/dpkg,如果是采用第二种方式安装,就下载相应的deb包进行安装,如下图所示。      本次安装我是采用通用方式,通过下载离线安装包进行配置安装,下载安装包之前之前首先要查看一下自己系统的glibc版本,然后下载对应的版本。 12sunys@syshlang:~$ getconf GNU_LIBC_VERSIONglibc 2.28 二、安装配置1、解压安装包并授权123456789101112131415## 解压安装包(方法一)sunys@syshlang:~/develop$ xz -d mysql-8.0.21-linux-glibc2.17-x86_64-minimal.tar.xzsunys@syshlang:~/develop$ tar -xvf mysql-8.0.21-linux-glibc2.17-x86_64-minimal.tar## 或解压安装包(方法二)sunys@syshlang:~/develop$ tar -Jxf mysql-8.0.21-linux-glibc2.17-x86_64-minimal.tar.xz## 将解压的文件复制或剪切到自己的软件安装目录下sunys@syshlang:~/develop$ sudo mv mysql-8.0.21-linux-glibc2.17-x86_64-minimal /usr/local/mysql## 切换至rootsunys@syshlang:/usr/local/mysql$ su## 创建mysql的数据文件夹dataroot@syshlang:/usr/local/mysql# mkdir data## 创建mysql_3306.err日志文件(可省略)root@syshlang:/usr/local/mysql# touch mysql_3306.err## 设置mysql读写权限root@syshlang:/usr/local/mysql# chmod -R 777 ./ 2、编辑my.cnf文件12## 编辑my.cnfroot@syshlang:/usr/local/mysql# vim /etc/mysql/my.cnf /etc/mysql/my.cnf1234567891011121314151617[client]port = 3306socket = /tmp/mysql.sock[mysqld]init-connect='SET NAMES utf8'# 目录basedir=/usr/local/mysqldatadir=/usr/local/mysql/datasocket=/tmp/mysql.sock# 允许最大连接数max_connections=200# 服务端使用的字符集默认为8比特编码的latin1字符集#character-set-server=utf8# 创建新表时将使用的默认存储引擎default-storage-engine=INNODBlog-error=/usr/local/mysql/mysql_3306.err 详细的配置说明见配置清单 3、创建用户和用户组并授权123456## 添加mysql用户组root@syshlang:/usr/local/mysql# groupadd mysql## 添加mysql用户root@syshlang:/usr/local/mysql# useradd -r -g mysql mysql## 授权root@syshlang:/usr/local/mysql# chown -R mysql:mysql ./ 4、初始化数据库12root@syshlang:/usr/local/mysql# cd bin/root@syshlang:/usr/local/mysql# ./mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data 不错喔!我初始化的时候没有报错,如果报错,可以去mysql_3306.err文件查看错误信息 初始化完成,mysql会自动生成一个初始化密码,可以去日志文件查看,保存这个密码一会会用的到。 5、将MySQL服务添加到系统服务1234## 将mysql的启动服务文件复制到init.d目录root@syshlang:/usr/local/mysql# cp -r support-files/mysql.server /etc/init.d/mysqld## 修改配置root@syshlang:/usr/local/mysql# vim /etc/init.d/mysqld 6、配置环境变量12## 编辑profile文件root@syshlang:/usr/local/mysql# vim /etc/profile 文件末尾追加配置export PATH=$PATH:/usr/local/mysql/bin:/usr/local/mysql/sbin 12## 刷新资源root@syshlang:/usr/local/mysql# source /etc/profile 7、修改默认密码1234## 启动MySQL服务root@syshlang:/usr/local/mysql# /etc/init.d/mysqld start## 使用临时密码登陆数据库root@syshlang:/usr/local/mysql# mysql -uroot -p 123456## 修改数据库的密码mysql> ALTER user 'root'@'localhost' IDENTIFIED BY '123456';Query OK, 0 rows affected (0.13 sec)## 刷新权限表mysql> flush privileges;Query OK, 0 rows affected (0.03 sec)      到此,MySQL的安装就结束了,针对MySQL的更改host名,配置权限等与在windows下是一样的,可以参考《Windows上安装 MySQL》 三、其他相关命令启动/停止/重启/启动状态12345678## 使用 serviceroot@syshlang:/usr/local/mysql# service mysql [start|stop|restart|status]## 使用 mysqld 脚本root@syshlang:/usr/local/mysql# /etc/init.d/mysqld [start|stop|restart|status]## 使用 safe_mysqld启动root@syshlang:/usr/local/mysql# safe_mysql&## MySQL管理工具mysqladmin停止mysqladmin shutdown 附:配置清单123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596[mysqld]user = mysqlport = 3306socket = /data/3306/mysql.sock,#这里指定了一个特别的连接basedir = /usr/local/mysqldatadir = /data/3306/data[client]port = 3306socket = /data/3306/mysql.sock,在客户端也要声明它,命令行要用到3. 查询缓存要不要开写入频繁的数据库,不要开查询缓存query_cache_size查询缓存区的最大长度(默认设置是0,不开辟查询缓存区)。首先要把Query_cache和该表相关的语句全部置为失效,然后在写入更新。那么如果Query_cache非常大,该表的查询结构又比较多,查询语句失效也慢,一个更新或是Insert就会很慢,这样看到的就是Update或是Insert怎么这么慢了。所以在数据库写入量或是更新量也比较大的系统,该参数不适合分配过大。而且在高并发,写入量大的系统,建议把该功能禁掉。query_cache_limit指定单个查询能够使用的缓冲区大小,缺省为1Mquery_cache_min_res_unit默认是4KB,设置值大对大数据查询有好处,但如果你的查询都是小数据查询,就容易造成内存碎片和浪费说明:禁掉查询缓存的方法就是直接注释掉查询缓存的配置,如#query_cache_size=1M, 这样就可以了4. 其他需要开的缓存读缓存,线程缓存,排序缓存sort_buffer_size = 2Mconnection级参数。太大将导致在连接数增高时,内存不足。max_allowed_packet = 32M网络传输中一次消息传输量的最大值。系统默认值 为1MB,最大值是1GB,必须设置1024的倍数。join_buffer_size = 2M和sort_buffer_size一样,该参数对应的分配内存也是每个连接独享tmp_table_size = 256M默认大小是 32M。GROUP BY 多不多的问题max_heap_table_size = 256Mkey_buffer_size = 2048M索引的缓冲区大小,对于内存在4GB左右的服务器来说,该参数可设置为256MB或384MB。read_buffer_size = 1Mread_rnd_buffer_size = 16M进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索bulk_insert_buffer_size = 64M批量插入数据缓存大小,可以有效提高插入效率,默认为8MInnodb缓存innodb_buffer_pool_size = 2048M只需要用Innodb的话则可以设置它高达 70-80% 的可用内存。一些应用于 key_buffer 的规则有 ——如果你的数据量不大,并且不会暴增,那么无需把innodb_buffer_pool_size 设置的太大了。innodb_additional_mem_pool_size = 16M网络传输中一次消息传输量的最大值。系统默认值为1MB,最大值是1GB,必须设置1024的倍数。innodb_log_files_in_group = 3循环方式将日志文件写到多个文件。推荐设置为3innodb_lock_wait_timeout = 120InnoDB 有其内置的死锁检测机制,能导致未完成的事务回滚。innodb_file_per_table = 0 独享表空间,关闭5. 连接数open_files_limit = 10240允许打开的文件数back_log = 600短时间内的多少个请求可以被存在堆栈中max_connections = 3000MySQL默认的最大连接数为100,MySQL服务器允许的最大连接数16384max_connect_errors = 6000设置每个主机的连接请求异常中断的最大次数,当超过该次数,MYSQL服务器将禁止host的连接请求thread_cache_size = 300重新利用保存在缓存中线程的数量thread_concurrency = 8thread_concurrency应设为总CPU核数的2倍thread_stack = 192K每个线程的堆栈大小,默认值足够大,可满足普通操作。可设置范围为128K至4GB,默认为192KB。6. 线程池有关参数线程池很少配thread_handling表示线程池模型。thread_pool_size表示线程池的group个数,一般设置为当前CPU核心数目。理想情况下,一个group一个活跃的工作线程,达到充分利用CPU的目的。thread_pool_stall_limit用于timer线程定期检查group是否“停滞”,参数表示检测的间隔。thread_pool_idle_timeout当一个worker空闲一段时间后会自动退出,保证线程池中的工作线程在满足请求的情况下,保持比较低的水平。60秒thread_pool_oversubscribe该参数用于控制CPU核心上“超频”的线程数。这个参数设置值不含listen线程计数。threadpool_high_prio_mode表示优先队列的模式。thread_pool_max_threads限制线程池最大的线程数,超过将无法再创建更多的线程,默认为100000。thread_pool_high_prio_tickets最多语序多少次被放入高优先级队列中,默认为4294967295。只有在thread_pool_high_prio_mode为transactions的时候才有效果说明:线程处理的最小单位是statement(语句)线程池实现在server端,通过创建一定数量的线程服务DB请求,相对于one-conection-per-thread的一个线程服务一个连接的方式,线程池服务的最小单位是语句,即一个线程可以对应多个活跃的连接。7. 慢查询日志slow_query_log是否开启慢查询日志,1表示开启,0表示关闭。log-slow-queries旧版(5.6以下版本)MySQL数据库慢查询日志存储路径。可以不设置该参数,系统则会默认给一个缺省的文件host_name-slow.logslow-query-log-file新版(5.6及以上版本)MySQL数据库慢查询日志存储路径。可以不设置该参数,系统则会默认给一个缺省的文件host_name-slow.loglong_query_time慢查询阈值,当查询时间多于设定的阈值时,记录日志。log_queries_not_using_indexes未使用索引的查询也被记录到慢查询日志中(可选项)。log_output日志存储方式。log_output='FILE'表示将日志存入文件,默认值是'FILE'。log_output='TABLE'表示将日志存入数据库,这样日志信息就会被写入到mysql.slow_log表中。MySQL数据库支持同时两种日志存储方式,配置的时候以逗号隔开即可,如:log_output='FILE,TABLE'。日志记录到系统的专用日志表中,要比记录到文件耗费更多的系统资源,因此对于需要启用慢查询日志,又需要能够获得更高的系统性能,那么建议优先记录到文件。log-errormysql生成的错误日志存放的路径,它是一个文本文件","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"Windows上安装 MySQL","slug":"database-mysql-install-windows","date":"2019-08-09T03:20:25.000Z","updated":"2020-09-24T01:20:06.450Z","comments":true,"path":"7d2a91d4/","link":"","permalink":"https://syshlang.com/7d2a91d4/","excerpt":"","text":"前言      Window上安装MySQL主要有两种方式:第一种,MySQL安装包安装方式;第二种,绿色版压缩包安装方式。第一种方式相对简单,就是简单的点击下一步安装,不再赘述,本文主要讲解第二种方式的安装及安装过程中出现问题的解决方式。 下载安装包     进入MySQL 下载,可以看到mysql的下载包列表。我选择的是5.7版本。 安装配置 将下载好的压缩包文件解压到指定目录,比如D:\\mysql-5.7.20-winx64; 配置下 MySQL 的配置文件,打开刚刚解压的文件夹 D:\\mysql-5.7.20-winx64 ,修改my-default.ini为my.ini,如果不存在直接创建 my.ini 配置文件,编辑 my.ini 配置以下基本信息:1234567891011121314151617181920# For advice on how to change settings please see# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html# *** DO NOT EDIT THIS FILE. It's a template which will be copied to the# *** default location during install, and will be replaced if you# *** upgrade to a newer version of MySQL.[client]port=3306default-character-set=utf8[mysqld]# 设置为自己MYSQL的安装目录basedir=D:\\mysql-5.7.20-winx64# 设置为MYSQL的数据目录datadir=D:\\mysql-5.7.20-winx64\\dataport=3306character_set_server=utf8sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES#开启查询缓存explicit_defaults_for_timestamp=true#默认的存储引擎default-storage-engine=INNODB 上面这个是5.7版本的配置,MySQL 8+配置如下:1234567891011121314151617[client]# 设置mysql客户端默认字符集default-character-set=utf8[mysqld]# 设置3306端口port = 3306# 设置mysql的安装目录basedir=C:\\\\web\\\\mysql-8.0.11# 设置 mysql数据库的数据的存放目录,MySQL 8+ 不需要以下配置,系统自己生成即可,否则有可能报错# datadir=C:\\\\web\\\\sqldata# 允许最大连接数max_connections=20# 服务端使用的字符集默认为8比特编码的latin1字符集character-set-server=utf8# 创建新表时将使用的默认存储引擎default-storage-engine=INNODB 启动 MySQL 数据库以管理员身份打开 cmd 命令行工具,切换目录:1C:\\Users\\sunys>cd D:\\mysql-5.7.20-winx64\\bin 初始化数据库:1D:\\mysql-5.7.20-winx64\\bin>mysqld --initialize --user=mysql--console 此时会给管理账户root随机生成一个临时密码。 -initialize生成随机密码 -initialize-insecure生成空密码 默认帐号root,后面的-user=mysql不更改 安装Mysql服务1D:\\mysql-5.7.20-winx64\\bin>mysqld --install MySQL 启动服务1D:\\mysql-5.7.20-winx64\\bin>net start MySQL 登录数据库当 MySQL 服务已经运行时, 我们可以通过 MySQL 自带的客户端工具登录到 MySQL 数据库中, 首先打开命令提示符, 输入以下格式的命名:1mysql -h 主机名 -u 用户名 -p -h : 指定客户端所要登录的 MySQL 主机名, 登录本机(localhost 或 127.0.0.1)该参数可以省略; -u : 登录的用户名; -p : 告诉服务器将会使用一个密码来登录, 如果所要登录的用户名密码为空, 可以忽略此选项。 我是登录本机的 MySQL 数据库,只需要输入以下命令即可:1D:\\mysql-5.7.20-winx64\\bin>mysql -u root -p 按回车确认,输入之前生成的临时密码(如果生成空密码,这里不用输入密码直接回车即可)。 成功登录后需要首先修改root账户的随机密码1mysql>alter user 'root'@'localhost' identified by '密码'; 密码修改成功后即可使用自己设定的密码登录 卸载 卸载安装版方式安装的MySQL跟卸载普通的软件方式一样,直接在控制面板的程序和功能中卸载即可;绿色版安装的MySQL卸载方式如下: 在CMD命令行模式下,删除mysql服务1C:\\Users\\sunys>sc delete mysql regedit进入注册表,删除mysql相关的文件 问题及解决方案缺少dll 安装版,遇到MSVCR120.dll文件丢失错误,如下图: 解决方案:下载 VC redist packages for x64,安装即可。 mysql无法启动 重新安装MySQL数据库之后无法启动,报错如下: 解决方案: 删除原来的mysql服务,进入mysql安装目录下的bin目录运行;1D:\\mysql-5.7.20-winx64\\bin>mysqld --remove MySQL 在mysql的根目录下, 清空data目录; 删除注册表,重启电脑(貌似执行这一步之后才会有效,否则还是不行); 重新执行安装命令并启动服务,启动成功。 Mysql连接报错:1130 数据库安装完成之后,使用localhost作为地址链接没问题,但是改为真实IP之后,Mysql连接报错:1130-host … is not allowed to connect to this MySql server。 这个问题是因为在数据库服务器中的mysql数据库中的user的表中没有权限,解决方案: 连接服务器: 1D:\\mysql-5.7.20-winx64\\bin>mysql -u root -p 看当前所有数据库: 1mysql>show databases; 进入mysql数据库: 1mysql>use mysql; 查看mysql数据库中所有的表: 1mysql>show tables; 查看user表中的数据: 1mysql>select host, user from user; 修改user表中的Host: 1mysql>update user set host='%' where user='ebm'; 最后刷新一下: 1mysql>flush privileges; 再查看user表中的数据: 1mysql>select host, user from user; 可以看到,此时ebm的host已经发生了变化,再使用真实IP链接mysql,链接成功。 host列的值: localhost 代表只可以本机连接 % 代表任何客户机都可以连接 空 值等价于’%’ 固定IP 指定的IP可以连接 通配符字符(“%”和“_”) 例如:192.168.1.% 就表示ip为192.168.1.前缀的客户端都可以连接","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"设计模式七大原则之单一职责原则 (Single Responsibility Principle)","slug":"java-design-principle1","date":"2019-05-21T12:14:58.000Z","updated":"2020-09-17T00:34:15.256Z","comments":true,"path":"934a08a4/","link":"","permalink":"https://syshlang.com/934a08a4/","excerpt":"    单一职责原则(SRP:Single responsibility principle)又称单一功能原则,对于一个类而言,不应存在多于一个导致类变更的原因,否则类应该被拆分,也就是说一个类只负责一项职责。如果一个类承担的职责项过多,就等于把这些职责耦合起来,使其难维护,复用度低,缺乏灵活性,当需求发生变化时,一项功能职责的变动可能导致整个功能无法使用,因此在实际中开发中,我们应将类的功能职责粒度分解,其核心就是控制类的粒度大小、将对象解耦、提高其内聚性。","text":"    单一职责原则(SRP:Single responsibility principle)又称单一功能原则,对于一个类而言,不应存在多于一个导致类变更的原因,否则类应该被拆分,也就是说一个类只负责一项职责。如果一个类承担的职责项过多,就等于把这些职责耦合起来,使其难维护,复用度低,缺乏灵活性,当需求发生变化时,一项功能职责的变动可能导致整个功能无法使用,因此在实际中开发中,我们应将类的功能职责粒度分解,其核心就是控制类的粒度大小、将对象解耦、提高其内聚性。 交通工具的例子 方式一 12345678910111213141516/** * @author sunys */public class SingleResponsibility1 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("摩托车"); vehicle.run("汽车"); vehicle.run("飞机"); } private static class Vehicle{ void run(String vehicle) { System.out.println(vehicle + " 在公路上运行...."); } }} 飞机本应天空中飞行,显然方式一中Vehicle的run方法中,违反了单一职责原则,根据单一职责原则改进如下: 方式二 123456789101112131415161718192021222324252627282930313233/** * @author sunys */public class SingleResponsibility2 { public static void main(String[] args) { RoadVehicle roadVehicle = new RoadVehicle(); roadVehicle.run("摩托车"); roadVehicle.run("汽车"); AirVehicle airVehicle = new AirVehicle(); airVehicle.run("飞机"); WaterVehicle waterVehicle = new WaterVehicle(); waterVehicle.run("轮船"); } private static class RoadVehicle { void run(String vehicle) { System.out.println(vehicle + "公路运行"); } } private static class AirVehicle { void run(String vehicle) { System.out.println(vehicle + "天空运行"); } } private static class WaterVehicle { void run(String vehicle) { System.out.println(vehicle + "水中运行"); } }} 改进之后,每个交通工具类都有自己的单一职责,互不影响,遵守单一职责原则,但是这种改动花销很大,除了要根据职责将类分解为多个类,同时还要修改客户端。因此,改进如下: 方式三 123456789101112131415161718192021222324/** * @author sunys */public class SingleResponsibility3 { public static void main(String[] args) { VehicleCommon vehicleCommon = new VehicleCommon(); vehicleCommon.runRoad("汽车"); vehicleCommon.runAir("飞机"); vehicleCommon.runWater("轮船"); } private static class VehicleCommon { public void runRoad(String vehicle) { System.out.println(vehicle + " 在公路上运行...."); } public void runAir(String vehicle) { System.out.println(vehicle + " 在天空上运行...."); } public void runWater(String vehicle) { System.out.println(vehicle + " 在水中行...."); } }} 可以看到,这种方式没有对类进行拆分,只是增加方法,改动不大,但最终也满足实际的需求,这种改动虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责原则。在实际开发中,我们可以根据实际情况决定在类级别上遵守单一职责原则,还是在方法级别上遵守单一职责原则; 附:本次演示的项目地址https://github.com/syshlang/java-design-principle","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计原则","slug":"java/设计原则","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"principle","slug":"principle","permalink":"https://syshlang.com/tags/principle/"}]},{"title":"JAVA开发中的设计模式","slug":"java-design-patterns","date":"2019-05-17T12:28:57.000Z","updated":"2020-09-25T07:52:39.890Z","comments":true,"path":"25810b4c/","link":"","permalink":"https://syshlang.com/25810b4c/","excerpt":"","text":"前言    在《java抽象类和模板方法设计》中,讲到了利用接口和抽象类进行模板方法设计。在我们实际开发的过程中,最多的操作可能就是curd堆业务代码,只有负责架构的才会去考虑设计模式相关的东西,然而呢,其实我们接触到与java设计模式相关的还是很多,例如,jDK源码,几乎每个web项目都会使用的企业级应用分层框架spring框架等。    在软件开发过程中,对于软件架构的设计,我们需要从耦合性、内聚性以及可维护性、可扩展性、重用性、灵活性等多方面进行考虑,设计模式的出现在这些方面为我们提供了思路和解决方案。 设计模式的七大原则     设计模式的原则,指的就是我们在软件编程的过程中,应该遵循的原则,它是设计模式的基础和依据,主要有七大原则: 单一职责原则 (Single Responsibility Principle) 接口隔离原则 (Interface Segregation Principle) 依赖倒转原则 (Dependence Inversion Principle) 里氏代换原则 (Liskov Substitution Principle) 开闭原则 (Open Close Principle) 迪米特法则(Demeter Principle) 合成复用原则(Composite Reuse Principle) 设计模式的分类总体来说设按照功能可将计模式分为三大类: 分类 功能 设计模式 创建型模式 主要用于创建对象 工厂方法模式、抽象工厂模式、单例模式、原型模式、建造者模式 结构型模式 主要用于处理类或者对象的组合 适配器模式、桥接模式、装饰器模式、代理模式、外观模式、组合模式、享元模式 行为型模式 主要用于描述对类或对象怎样交互和怎样分配职责 策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式 其实还有两类:并发型模式和线程池模式。 创建型模式(Creational patterns)     创建型模式,顾名思义就是用来创建对象的设计模式,所以这种类型的模式主要用途是就用于创建对象,关注点是对象的创建。这种类型的设计模式主要特点是将创建对象的过程进行了抽象,封装,对于对象的使用者而言只需调用,而不需要去关心对象创建的过程如何。 结构型模式(Structural patterns)     结构型设计模式,为我们如何组合类和对象以获得更大的结构提供思路,从程序的结构上解决模块之间的耦合问题。    从组合结构上来看,又可以分为两类:类结构型模式、对象结构型模式。类结构型模式主要关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系;对象结构型模式则主要关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法,更符合“合成复用原则”。 行为型模式(Behavioral patterns)     行为型模式,是设计模式中最为庞大的一类,前面两类模式已经解决了对象的创建问题及类和对象的组合结构问题,那么这第三大类型的模式自然就是用来解决类或对象相互协作的问题。这类设计模式主要用于描述程序在运行时复杂的流程控制,例如分配算法与对象间职责,协调类或对象之间相互协作等。    当然,行为型模式也可分为两类:类行为模式、对象行为模式。类行为模式采用继承机制来在类间分派行为,对象行为模式则采用组合或聚合在对象间分配行为;由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 设计模式之间的关系 给两张图 设计模式在软件中的应用graph LR A(面向对象) -->|设计模式+算法+数据结构| B[功能模块] B -->|多种设计模式| C[框架] C -->|服务器集群| D[架构] 总结     设计模式实际是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的问题解决方案,当我们在软件开发过程中面临一般问题时,不妨从这方面入手看是否能找到解决方法。     设计模式包含了面向对象的精髓,“懂了设计模式,你就懂了面向对象分析和设计(OOA/D)的精要”。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计模式","slug":"java/设计模式","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"patterns","slug":"patterns","permalink":"https://syshlang.com/tags/patterns/"}]},{"title":"java抽象类和模板方法设计","slug":"template-pattern-abstract","date":"2019-04-20T07:25:41.000Z","updated":"2020-09-17T00:34:15.264Z","comments":true,"path":"4fb5687d/","link":"","permalink":"https://syshlang.com/4fb5687d/","excerpt":"","text":"前言     最近,在做项目时,遇到一个业务场景是这样的:有一种类型的电子锁,开锁的方式有两种,这两种方式开锁的过程有不同的地方也有相同的地方,主要的开锁流程差不多一致,设计这两种方式开锁流程的时候,我想到了利用java抽象类来进行模板方法设计。 Java抽象类与接口的区别     面试的过程中,很多面试官考察java基础知识的时候,通常都会问诸如“Java抽象类与接口有什么区别?请你说说两者各自的使用场景?”这样的问题,那么两者有什么区别呢,大概总结如下: 参数 抽象类 接口 默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现 实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 构造器 抽象类可以有构造器 接口不能有构造器 与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型 成员变量 抽象类中的静态成员变量的访问类型可以任意 接口中定义的变量只能是public static final类型,并且默认即为public static final类型。 成员方法 抽象方法可以有public、protected和default这些修饰符,可以包含静态方法 接口方法默认修饰符是public,并且默认即为public abstract类型,不能包含静态方法 多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口 速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。 Java抽象类与接口的使用场景interface的应用场合 类与类之前需要特定的接口进行协调,而不在乎其如何实现; 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识; 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联; 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系。 USB.java1234public interface USB{ // 定义了USB接口 void start() ; // USB设备开始工作 void stop() ; // USB设备结束工作} Flash.java12345678public class Flash implements USB{ public void start(){ // 覆写方法 System.out.println("U盘开始工作。") ; } public void stop(){ // 覆写方法 System.out.println("U盘停止工作。") ; }} Print.java12345678public class Print implements USB{ public void start(){ // 覆写方法 System.out.println("打印机开始工作。") ; } public void stop(){ // 覆写方法 System.out.println("打印机停止工作。") ; }} abstract class的应用场合 在既需要统一的接口,又需要实例变量或缺省的方法的情况下,就可以使用它,例如:规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能。 Person.java123456789101112131415161718public abstract class Person{ private String name ; // 定义name属性 private int age ; // 定义age属性 public Person(String name,int age){ this.name = name ; this.age = age ; } public String getName(){ return this.name ; } public int getAge(){ return this.age ; } public void say(){ // 人说话是一个具体的功能 System.out.println(this.getContent()) ; // 输出内容 } public abstract String getContent() ; // 说话的内容由子类决定} Student.java123456789101112public class Student extends Person{ private float score ; public Student(String name,int age,float score){ super(name,age) ; // 调用父类中的构造方法 this.score = score ; } public String getContent(){ return "学生信息 --> 姓名:" + super.getName() + ";年龄:" + super.getAge() + ";成绩:" + this.score ; }} Worker.java123456789101112public class Worker extends Person{ private float salary ; public Worker(String name,int age,float salary){ super(name,age) ; // 调用父类中的构造方法 this.salary = salary ; } public String getContent(){ return "工人信息 --> 姓名:" + super.getName() + ";年龄:" + super.getAge() + ";工资:" + this.salary ; }}     在实际开发过程中,接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用。模板方法设计模式就是抽象类的一个典型应用,工厂模式、代理设计模式都是通过implements实现接口的设计模式,范型则是装饰设计模式。    关于java开发中的23种设计模式,在下文《JAVA开发中的设计模式》继续。。。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计模式","slug":"java/设计模式","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"java","slug":"java","permalink":"https://syshlang.com/tags/java/"},{"name":"abstract","slug":"abstract","permalink":"https://syshlang.com/tags/abstract/"}]},{"title":"web应用安全之XSS攻击","slug":"discussion-on-xss-injection-in-web-applications","date":"2019-03-21T14:25:41.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"afd61e4d/","link":"","permalink":"https://syshlang.com/afd61e4d/","excerpt":"","text":"前言     在上一篇文章《web应用安全之SQL注入》中,本人从java的角度就Java web开发过程中SQL注入的问题简单表达了下自己的观点,本文将在上一文的基础上继续讲述web应用安全的另一个问题————XSS攻击。 什么是XSS攻击     XSS攻击,全称是“跨站点脚本攻击”(Cross Site Scripting),之所以缩写为XSS,主要是为了和“层叠样式表”(Cascading Style Sheets,CSS)区别开。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。 XSS攻击原理 攻击者对含有漏洞的服务器发起XSS攻击(注入JS代码); 诱使受害者打开受到攻击的服务器URL; 受害者在Web浏览器中打开URL,恶意脚本执行。 一个简单演示代码如下:     从上面的代码可以看出,输入框被非法放入了一段js代码,当浏览器解析到这段代码时,浏览器并不知道这些代码改变了原本程序的意图,会照做弹出一个信息框。 XSS攻击的类型     常见的 XSS 攻击有三种:反射型、DOM-based 型、存储型。 其中反射型、DOM-based 型可以归类为非持久型 XSS 攻击,存储型归类为持久型 XSS 攻击。 反射型    用户在页面输入框中输入数据,通过 get 或者 post 方法向服务器端传递数据,输入的数据一般是放在 URL 的 query string 中,或者是 form 表单中,如果服务端没有对这些数据进行过滤、验证或者编码,直接将用户输入的数据呈现出来,就可能会造成反射型 XSS。     上面这个请求地址被非法注入了js代码,当name的参数值(脚本标记)被后端代码重新下发给前端时,脚本标记就会在前端被执行,从而触发反射型XSS。 DOM-based 型    DOM 是一个树形结构,攻击者可以通过写 js 代码来修改节点,对象和值。如果用户在客户端输入的数据包含了恶意的 JavaScript 脚本,而这些脚本没有经过适当的处理,那么应用程序就可能受到DOM-based XSS攻击。     本文在讲述XSS攻击原理时,演示了一个非法注入的HTML页面,如果在这个页面的基础上执行如下js,将会发生DOM-based XSS攻击。 123var content = document.getElementById("content");var board = document.getElementById("board");board.innerHTML = text.value; //发生DOM-based XSS攻击 存储型    存储型XSS攻击也可以说是持久型XSS攻击,通常是因为服务器端将用户输入的恶意脚本没有经过验证就存储在数据库中,并且通过调用数据库的方式,将数据呈现在浏览器上,当页面被用户打开的时候执行,每当用户打开浏览器,恶意脚本就会执行。持久型的 XSS 攻击相比非持久型的危害性更大,因为每当用户打开页面,恶意脚本都会执行。    假如XSS攻击原理演示中的id为content的输入框内容被提交,如果后台没有做过滤处理,服务端将内容保存到数据库,当从后台再次取出数据在前端展示时,就会执行这些恶意攻击代码,并且这种攻击每次打开都会发生。 XSS的防御措施编码     对用户输入的数据进行编码 HTML 编码     将不可信数据放入到 HTML 标签内(例如div、span等)的时候进行HTML编码 显示结果 描述 实体编号 空格 &nbsp ; < 小于 &lt ; > 大于 &gt ; & 和 &amp ; ‘’ 引号 &quot ; 123456789function encodeForHTML(str, kwargs){ return ('' + str) .replace(/&/g, '&amp;') .replace(/</g, '&lt;') // DEC=> &#60; HEX=> &#x3c; Entity=> &lt; .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#x27;') // &apos; 不推荐,因为它不在HTML规范中 .replace(/\\//g, '&#x2F;'); }; HTML Attribute 编码     将不可信数据放入 HTML 属性时(不含src、href、style 和事件处理属性),进行 HTML Attribute 编码,除了字母数字字符以外,使用 &#xHH;(或者可用的命名实体)格式来转义ASCII值小于256所有的字符 1234567891011function encodeForHTMLAttibute(str, kwargs){ let encoded = ''; for(let i = 0; i < str.length; i++) { let ch = hex = str[i]; if (!/[A-Za-z0-9]/.test(str[i]) && str.charCodeAt(i) < 256) { hex = '&#x' + ch.charCodeAt(0).toString(16) + ';'; } encoded += hex; } return encoded; }; JavaScript 编码     将不可信数据放入事件处理属性、JavaScirpt值时进行 JavaScript 编码,除字母数字字符外,使用\\xHH格式转义ASCII码小于256的所有字符 1234567891011function encodeForJavascript(str, kwargs) { let encoded = ''; for(let i = 0; i < str.length; i++) { let cc = hex = str[i]; if (!/[A-Za-z0-9]/.test(str[i]) && str.charCodeAt(i) < 256) { hex = '\\\\x' + cc.charCodeAt().toString(16); } encoded += hex; } return encoded; }; URL 编码     将不可信数据作为URL参数值时需要对参数进行encodeURIComponent编码 123function encodeForURL(str, kwargs){ return encodeURIComponent(str); }; CSS 编码     将不可信数据作为 CSS 时进行 CSS 编码,除了字母数字字符以外,使用\\XXXXXX格式来转义ASCII值小于256的所有字符 1234567891011121314function encodeForCSS (attr, str, kwargs){ let encoded = ''; for (let i = 0; i < str.length; i++) { let ch = str.charAt(i); if (!ch.match(/[a-zA-Z0-9]/)) { let hex = str.charCodeAt(i).toString(16); let pad = '000000'.substr((hex.length)); encoded += '\\\\' + pad + hex; } else { encoded += ch; } } return encoded; }; Http Only cookie     许多 XSS 攻击的目的就是为了获取用户的 cookie,将重要的 cookie 标记为 http only,这样的话当浏览器向服务端发起请求时就会带上 cookie 字段,但是在脚本中却不能访问 cookie,这样就避免了 XSS 攻击利用 js 的 document.cookie获取 cookie。 使用 XSS Filter     在上一篇文章《web应用安全之SQL注入》中,讲SQL注入的防范与处理时,提到了自定义过滤规则防范SQL注入,同样的对于XSS我们也可以自定义过滤规则防范XSS攻击,我们只需要在重写getParameter方法中调用XSS的过滤规则即可,详情不在赘述。 附:2017 年公布了十大安全漏洞列表 注入 失效的身份认证 敏感信息泄漏 XML 外部实体(XXE) 失效的访问控制 安全配置错误 跨站脚本(XSS) 不安全的反序列化 使用含有已知漏洞的组件 不足的日志记录和监控","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"safety","slug":"java/safety","permalink":"https://syshlang.com/categories/java/safety/"},{"name":"web","slug":"java/safety/web","permalink":"https://syshlang.com/categories/java/safety/web/"}],"tags":[{"name":"web","slug":"web","permalink":"https://syshlang.com/tags/web/"},{"name":"injection","slug":"injection","permalink":"https://syshlang.com/tags/injection/"},{"name":"xss","slug":"xss","permalink":"https://syshlang.com/tags/xss/"}]},{"title":"Spring整合Quartz分布式调度","slug":"spring-quartz","date":"2019-03-19T05:29:54.000Z","updated":"2020-09-17T00:34:15.260Z","comments":true,"path":"a0757df1/","link":"","permalink":"https://syshlang.com/a0757df1/","excerpt":"","text":"前言     最近在做项目时,项目中涉及到很多定时任务相关的功能,并且很多定时都是动态的。为了业务开发时定时调度更加方便,且能清晰的管理所有定时任务,我决定将调度中心这个模块做成可视化的管理界面;此外,考虑到后期项目的壮大,应用的高可用和高并发性,可能会有采用集群部署多个节点;对于定时任务,如果每个节点都执行自己的定时任务,一方面耗费了系统资源,另一方面有些任务多次执行,可能引发应用逻辑问题,所以需要一个分布式的调度系统,来协调每个节点执行定时任务,定时任务采用动态配置并持久化到数据库。 版本选择     spring对于quartz的支持,是通过org.springframework.scheduling.quartz.CronTriggerBean继承org.quartz.CronTrigger来实现的。在quartz1.x系列中org.quartz.CronTrigger是个类,而在quartz2.x系列中org.quartz.CronTrigger变成了接口,这就造成了无法用spring的方式配置quartz的触发器(trigger)。因此,在spring3.1以下的版本必须使用quartz1.x系列,3.1以上的版本才支持quartz 2.x,否则会出错。     本次采用版本:spring版本4.3.5.RELEASE,quartz版本2.3.0 Spring整合QuartzMaven依赖文件 pom.xml123456789101112131415161718192021222324252627282930313233<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency> <!--分布式调度持久化选用mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.29</version> </dependency> </dependencies> 配置job     Quartz是一个成熟的任务调度系统提供了两种方式来配置job,分别是: MethodInvokingJobDetailFactoryBean JobDetailFactoryBean MethodInvokingJobDetailFactoryBean 要调用特定bean的一个方法的时候使用,具体配置如下: applicationContext-job.xml1234<bean id="firstTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="springFrameworkQuartzJobDemo" /> <property name="targetMethod" value="springFrameworkQuartzJobDemoJob" /></bea> JobDetailFactoryBean 这种方式更加灵活,可以设置传递参数,具体配置如下: applicationContext-job.xml1234567891011121314<bean id="springFrameworkQuartzJobDemoBeanId" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="com.syshlang.quartz.core.quartz.schedulerframework.SpringFrameworkQuartzJobDemo"/> <property name="name" value="springFrameworkQuartzJobDemoJob"/> <property name="durability" value="true" /> <!-- <property name="jobDataMap"> <map> <entry key="firstService" value-ref="firstService" /> </map> </property> --></bean> jobClass定义的任务类,继承QuartzJobBean,实现executeInternal方法;可以使用jobDataMap来给job传递数据; 配置调度触发器 调度的触发器同样也提供了两种类型,分别是: SimpleTriggerFactoryBean CronTriggerFactoryBeanCronTriggerFactoryBean相对更加灵活,本例中也是采用这种类型的触发器,如下: applicationContext-job.xml1234567891011121314151617<!-- 执行定时器 --><bean id="springFrameworkQuartzJobDemoId" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <!-- 每隔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 * * ? --> <property name="cronExpression" value="*/5 * * * * ?"/> <property name="jobDetail" ref="springFrameworkQuartzJobDemoBeanId"/></bean> 配置Quartz调度器的SchedulerFactoryBean Quartz调度器的SchedulerFactoryBean同样也提供了两种方式: 内存RAMJobStore 数据库方式 RAMJobStore job的相关信息存储在内存里,每个节点存储各自的,互相隔离,配置如下: applicationContext-job.xml123456789<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false"> <property name="triggers"> <list> <ref bean="springFrameworkQuartzJobDemoId" /> <!-- 如果有多个定时任务就在这里添加 --> <!-- <ref bean="quartzTrigger2" /> --> </list> </property></bean> 数据库方式     job的相关信息存储在数据库中,所有节点共用数据库,每个节点通过数据库来通信,保证一个job同一时间只会在一个节点上执行,并且如果某个节点挂掉,job会被分配到其他节点执行,这也是集群部署时,分布式的调度系统采用的方式。 其原理如下: 具体配置如下: applicationContext-job.xml123456789101112131415161718192021222324252627282930<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false"> <property name="dataSource" ref="dataSource" /> <property name="quartzProperties"> <props> <prop key="org.quartz.scheduler.instanceName">EBMDynamicQuartz</prop> <prop key="org.quartz.scheduler.instanceId">AUTO</prop> <!-- 线程池配置 --> <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop> <prop key="org.quartz.threadPool.threadCount">20</prop> <prop key="org.quartz.threadPool.threadPriority">5</prop> <!-- JobStore 配置 --> <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop> <!-- 集群配置 --> <prop key="org.quartz.jobStore.isClustered">true</prop> <prop key="org.quartz.jobStore.clusterCheckinInterval">15000</prop> <prop key="org.quartz.jobStore.maxMisfiresToHandleAtATime">1</prop> <prop key="org.quartz.jobStore.misfireThreshold">120000</prop> <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop> </props> </property> <property name="schedulerName" value="EBMDynamicQuartz" /> <!--必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动 --> <property name="startupDelay" value="30" /> <property name="applicationContextSchedulerContextKey" value="applicationContextKey" /> <!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 --> <property name="overwriteExistingJobs" value="true" /> <!-- 设置自动启动 --> <property name="autoStartup" value="true" /> <!--<property name="configLocation" value="classpath:quartz.properties" />--></bean>     dataSource用来配置数据源,数据表相关信息,可以到quartz官网下载gz包。面提供了主流数据库的sql文件,总共11张表,本例采用mysql数据库,表结构如下: tables_mysql.sql123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169## Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar## PLEASE consider using mysql with innodb tables to avoid locking issues## In your Quartz properties file, you'll need to set# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate#DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;DROP TABLE IF EXISTS QRTZ_LOCKS;DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;DROP TABLE IF EXISTS QRTZ_TRIGGERS;DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;DROP TABLE IF EXISTS QRTZ_CALENDARS;CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE VARCHAR(1) NOT NULL, IS_NONCONCURRENT VARCHAR(1) NOT NULL, IS_UPDATE_DATA VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP));CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT(13) NULL, PREV_FIRE_TIME BIGINT(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT(13) NOT NULL, END_TIME BIGINT(13) NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT(2) NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP));CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT BIGINT(7) NOT NULL, REPEAT_INTERVAL BIGINT(12) NOT NULL, TIMES_TRIGGERED BIGINT(10) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP));CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(200) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP));CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP));CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP));CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME));CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP));CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT(13) NOT NULL, SCHED_TIME BIGINT(13) NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT VARCHAR(1) NULL, REQUESTS_RECOVERY VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID));CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT(13) NOT NULL, CHECKIN_INTERVAL BIGINT(13) NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME));CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME));commit; 附:本次项目地址https://github.com/syshlang/syshlang-spring-quartz.git     ","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"framework","slug":"java/framework","permalink":"https://syshlang.com/categories/java/framework/"},{"name":"spring","slug":"spring","permalink":"https://syshlang.com/categories/spring/"},{"name":"springquartz","slug":"spring/springquartz","permalink":"https://syshlang.com/categories/spring/springquartz/"}],"tags":[{"name":"Spring","slug":"Spring","permalink":"https://syshlang.com/tags/Spring/"},{"name":"Quartz","slug":"Quartz","permalink":"https://syshlang.com/tags/Quartz/"}]},{"title":"Linux 命令全集","slug":"Linux-command-complete-set","date":"2019-03-18T13:16:38.000Z","updated":"2020-09-09T07:31:04.858Z","comments":true,"path":"fe644125/","link":"","permalink":"https://syshlang.com/fe644125/","excerpt":"","text":"","categories":[],"tags":[{"name":"book","slug":"book","permalink":"https://syshlang.com/tags/book/"},{"name":"linux","slug":"linux","permalink":"https://syshlang.com/tags/linux/"},{"name":"shell","slug":"shell","permalink":"https://syshlang.com/tags/shell/"}]},{"title":"Docker从入门到实践","slug":"Docker-from-start-to-practice","date":"2019-03-16T16:14:26.000Z","updated":"2020-09-04T03:40:48.045Z","comments":true,"path":"c1109852/","link":"","permalink":"https://syshlang.com/c1109852/","excerpt":"","text":"","categories":[],"tags":[{"name":"docker","slug":"docker","permalink":"https://syshlang.com/tags/docker/"},{"name":"book","slug":"book","permalink":"https://syshlang.com/tags/book/"}]},{"title":"web应用安全之SQL注入","slug":"discussion-on-sql-injection-in-web-applications","date":"2019-03-16T14:25:41.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"7443f4e/","link":"","permalink":"https://syshlang.com/7443f4e/","excerpt":"","text":"前言     最近,在做一个项目,当项目完成交付时,银行客户对我们的产品安全提出了质疑,要求我们对产品系统进行安全检测,应要求我们利用IBM AppScan 安全扫描工具进行了扫描,经过扫描我们发现系统存在一些SQL注入、XSS攻击等安全漏洞。我们在开发web应用的过程中,对于项目DAO层的SQL非法注入问题是我们经常会考虑的web安全隐患之一。作为一个从业多年的Java web应用开发者,本文将从java的角度来说说开发过程中的SQL注入的问题。 什么是SQL注入     所谓SQL注入,就是攻击者恶意将SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,这样当应用程序向后台数据库进行SQL查询时,以“欺骗”服务器执行非法的SQL命令,最终致使攻击者非法数据侵入系统。 现在我们通过一个简单的项目演示攻击者利用SQL注入非法入侵系统。 SQL注入演示环境搭建     采用Mysql新建用户表,并搭建一个web项目。 SYS_USER123456789101112131415-- ------------------------------ Table structure for `SYS_USER`-- ----------------------------DROP TABLE IF EXISTS `SYS_USER`;CREATE TABLE `SYS_USER` ( `id` int(11) NOT NULL AUTO_INCREMENT, `account` varchar(50) NOT NULL COMMENT '登录名', `password` varchar(100) NOT NULL COMMENT '密码(加密)', `lastLoginIp` varchar(20) DEFAULT NULL COMMENT '最后登录IP', `lastLoginTime` datetime DEFAULT NULL COMMENT '最后登录时间', `loginCount` int(11) NOT NULL COMMENT '登录总次数', `createTime` datetime NOT NULL COMMENT '创建时间', `isEnable` int(1) NOT NULL COMMENT '是否启用', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='用户表'; 并插入数据,如下: controller层提供一个查询用户列表的接口(RESTFu风格) UserController.java12345678910111213141516171819202122@Path("/user")@Transactional@Component@Slf4jpublic class UserController { @Autowired private UserDao userDao; @Resource(name = "myDataSource") private DataSource myDataSource; @GET @Path("/list") @Produces(MediaType.APPLICATION_JSON) public Response getUserList(@QueryParam("account") String account){ //执行SQL,输出查到的数据 JdbcTemplate jdbcTemplate = new JdbcTemplate(myDataSource); String sql = "select * from sys_user where account ='"+account+"'"; List<?> resultList = jdbcTemplate.queryForList(sql); return Response.ok(resultList).build(); }} 注入演示 首先,我们在浏览器输入请求地址,发出请求,查询账户为admin的用户信息: GET>http://localhost:8080/user/list?account=admin 浏览器窗口正常返回结果: 接着,我们在发出这样一个请求,如下 GET> http://localhost:8080/user/list?account=admin' or ‘a’=’a 浏览器窗口返回结果: 此时我们发现,查出了所有的用户信息,仔细调试会发现执行了如下的sql 1select * from sys_user where account ='admin' or 'a'='a' 这是因为我们传入的参数account参数与我们接口中的查询语句进行拼接后构成了一条合法的SQL查询,这就是SQL注入。黑客往往就会通过传入精心构造的参数来进行SQL注入,非法入侵系统。 SQL注入的防范与处理     SQL注入原因就是由于传入的参数与系统的SQL拼接成了合法的SQL而导致的,而其本质还是将用户输入的数据当做了代码执行。了解了SQL注入的本质和原理,在Java web应用开发的过程中,我们如何防范和处理呢? JDBC的预处理    Java的JDBC中,有个预处理功能,这个功能提供了PreparedStatement (预处理执行语句)的方式,SQL语句在程序运行前已经进行了预编译,在程序运行时第一次操作数据库之前,SQL语句已经被数据库分析,编译和优化,对应的执行计划也会缓存下来并允许数据库以参数化的形式进行查询,当运行时,动态地把参数传给PreprareStatement时,即使参数里有敏感字符,如 or ‘a=a’,数据库会将整个参数作为一个字段的属性值来处理而不会作为一个SQL指令,这样就在一定程度上预防了绝大多数的SQL注入。    对刚才的代码做优化,采用预处理的方式,如下: UserController.java12345678910@GET @Path("/list") @Produces(MediaType.APPLICATION_JSON) public Response getUserListPreprareStatement(@QueryParam("account") String account){ //执行SQL,输出查到的数据 JdbcTemplate jdbcTemplate = new JdbcTemplate(myDataSource); String sql = "select * from sys_user where account = ?"; List resultList = jdbcTemplate.queryForList(sql,account); return Response.ok(resultList).build(); }     此时,我们再采用刚才的SQL非法注入的方式访问,发现未查询出任何数据,说明SQL注入未成功,打印JDBC预处理后的SQL,发现所有的 ‘ 都被 ' 转义掉了,从而防止了SQL注入。 Mybatis下注入防范    Mybatis框架作为一款半自动化的持久层框架,支持定制化 SQL、存储过程以及高级映射,其sql语句都要我们自己来手动编写,使用该框架时,防止SQL注入我们只需要弄清楚#{}和${}的区别以及order by注入问题。 #{}:使用的是PreparedStatement,会有类型转换,比较安全;${}:使用字符串拼接,可以SQL注入;order by语句后不能用#{},只能用${},此时会存在SQL注入危险,需要手动处理;like查询不小心会有漏动,正确写法如下: 123456--Mysql:select * from sys_user where account like concat('%', #{account}, '%')--Oracle:select * from sys_user where account like '%' || #{account} || '%'--SQLServer:select * from sys_user where account like '%' + #{account} + '%' 自定义过滤规则防范注入    由于动态SQL语句是引发SQL注入的根源。因此,开发过程中我们应尽量使用预编译语句来组装SQL查询,并且,随着ORM技术的发展,很多ORM框架在安全问题上都有进行处理,只要我们按照规范,基本上可以很大程度的消除SQL注入的风险。但是,在必要情况下,我们还需通过自定义过滤规则的方式来防范SQL注入。就Java web而言,我们可以通过在后台添加自定义的过滤器(Filter),对每个请求的参数过滤一些关键字,替换成安全的,从而解决注入问题,步骤如下 在后台添加自定义的过滤器,对每个请求进行过滤 SqlFilter.java1234567891011121314151617public class SqlFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //在Filter里面调用自定义的HttpServletRequestWrapper filterChain.doFilter(new SqlHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse); } @Override public void destroy() { }} 实现一个自定义的HttpServletRequestWrapper,然后在Filter里面调用它,重写getParameter方法 SqlHttpServletRequestWrapper.java123456789101112131415161718192021222324252627282930313233343536public class SqlHttpServletRequestWrapper extends HttpServletRequestWrapper { public SqlHttpServletRequestWrapper(HttpServletRequest request) { super(request); } /** * 重写getParameter方法 过滤一些关键字,替换成安全的 * @param s * @return */ @Override public String getParameter(String s) { String parameter = super.getParameter(s); parameter = stripSqlInject(parameter); return parameter; } /** * 对每个请求的参数过滤一些关键字,替换成安全的 * @param parameter * @return */ private String stripSqlInject(String parameter) { if(!StringUtils.isEmpty(parameter)) { //干掉or 攻击 parameter=parameter.replaceAll("(?i)\\\\w*\\\\s*((\\\\%27)|(\\\\'))\\\\s*((\\\\%6F)|o|(\\\\%4F))((\\\\%72)|r|(\\\\%52))", ""); //干掉union 攻击 parameter=parameter.replaceAll("(?i)\\\\w*\\\\s*((\\\\%27)|(\\\\'))\\\\s*union", ""); //干掉截断攻击--原理:通过注释符号来截断后面的查询条件 parameter=parameter.replaceAll("(?i)\\\\s*((\\\\%27)|(\\\\'))[\\\\s\\\\S^-]*--\\\\s*[and|exec|execute|insert|select|delete|" + "update|count|drop|truncate|information_schema.columns|table_schema|union]*", ""); } return parameter; }} 在web.xml中配置过滤器 web.xml123456789<filter> <filter-name>SqlFilter</filter-name> <filter-class>com.syshlang.framework.filter.SqlFilter</filter-class></filter><filter-mapping> <filter-name>SqlFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher></filter-mapping> 附:本次演示的项目地址https://github.com/syshlang/syshlang-injection-demo","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"safety","slug":"java/safety","permalink":"https://syshlang.com/categories/java/safety/"},{"name":"web","slug":"java/safety/web","permalink":"https://syshlang.com/categories/java/safety/web/"}],"tags":[{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"},{"name":"web","slug":"web","permalink":"https://syshlang.com/tags/web/"},{"name":"injection","slug":"injection","permalink":"https://syshlang.com/tags/injection/"}]},{"title":"JavaScript 思维导图","slug":"JavaScript-Advanced","date":"2019-01-10T14:58:43.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"aedf899f/","link":"","permalink":"https://syshlang.com/aedf899f/","excerpt":"JavaScript变量","text":"JavaScript变量 JavaScript运算符 JavaScript数组 JavaScript流程语句 JavaScript字符串函数 JavaScript函数基础 JavaScript基础DOM操作 JavaScript DOM对象 JavaScript BOM浏览器对象模型 JavaScript 正则表达式","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"javascript","slug":"javascript","permalink":"https://syshlang.com/categories/javascript/"},{"name":"mindmap","slug":"javascript/mindmap","permalink":"https://syshlang.com/categories/javascript/mindmap/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://syshlang.com/tags/JavaScript/"},{"name":"js","slug":"js","permalink":"https://syshlang.com/tags/js/"}]},{"title":"MySQL使用笔记(九)","slug":"database-mysql8","date":"2019-01-06T12:30:56.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"cc2e58d0/","link":"","permalink":"https://syshlang.com/cc2e58d0/","excerpt":"","text":"⇦ MySQL使用笔记(八) 用户和权限管理1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677-- root密码重置1. 停止MySQL服务2. [Linux] /usr/local/mysql/bin/safe_mysqld --skip-grant-tables & [Windows] mysqld --skip-grant-tables3. use mysql;4. UPDATE `user` SET PASSWORD=PASSWORD("密码") WHERE `user` = "root";5. FLUSH PRIVILEGES;用户信息表:mysql.user-- 刷新权限FLUSH PRIVILEGES;-- 增加用户CREATE USER 用户名 IDENTIFIED BY [PASSWORD] 密码(字符串) - 必须拥有mysql数据库的全局CREATE USER权限,或拥有INSERT权限。 - 只能创建用户,不能赋予权限。 - 用户名,注意引号:如 'user_name'@'192.168.1.1' - 密码也需引号,纯数字密码也要加引号 - 要在纯文本中指定密码,需忽略PASSWORD关键词。要把密码指定为由PASSWORD()函数返回的混编值,需包含关键字PASSWORD-- 重命名用户RENAME USER old_user TO new_user-- 设置密码SET PASSWORD = PASSWORD('密码') -- 为当前用户设置密码SET PASSWORD FOR 用户名 = PASSWORD('密码') -- 为指定用户设置密码-- 删除用户DROP USER 用户名-- 分配权限/添加用户GRANT 权限列表 ON 表名 TO 用户名 [IDENTIFIED BY [PASSWORD] 'password'] - all privileges 表示所有权限 - *.* 表示所有库的所有表 - 库名.表名 表示某库下面的某表 GRANT ALL PRIVILEGES ON `pms`.* TO 'pms'@'%' IDENTIFIED BY 'pms0817';-- 查看权限SHOW GRANTS FOR 用户名 -- 查看当前用户权限 SHOW GRANTS; 或 SHOW GRANTS FOR CURRENT_USER; 或 SHOW GRANTS FOR CURRENT_USER();-- 撤消权限REVOKE 权限列表 ON 表名 FROM 用户名REVOKE ALL PRIVILEGES, GRANT OPTION FROM 用户名 -- 撤销所有权限-- 权限层级-- 要使用GRANT或REVOKE,您必须拥有GRANT OPTION权限,并且您必须用于您正在授予或撤销的权限。全局层级:全局权限适用于一个给定服务器中的所有数据库,mysql.user GRANT ALL ON *.*和 REVOKE ALL ON *.*只授予和撤销全局权限。数据库层级:数据库权限适用于一个给定数据库中的所有目标,mysql.db, mysql.host GRANT ALL ON db_name.*和REVOKE ALL ON db_name.*只授予和撤销数据库权限。表层级:表权限适用于一个给定表中的所有列,mysql.talbes_priv GRANT ALL ON db_name.tbl_name和REVOKE ALL ON db_name.tbl_name只授予和撤销表权限。列层级:列权限适用于一个给定表中的单一列,mysql.columns_priv 当使用REVOKE时,您必须指定与被授权列相同的列。-- 权限列表ALL [PRIVILEGES] -- 设置除GRANT OPTION之外的所有简单权限ALTER -- 允许使用ALTER TABLEALTER ROUTINE -- 更改或取消已存储的子程序CREATE -- 允许使用CREATE TABLECREATE ROUTINE -- 创建已存储的子程序CREATE TEMPORARY TABLES -- 允许使用CREATE TEMPORARY TABLECREATE USER -- 允许使用CREATE USER, DROP USER, RENAME USER和REVOKE ALL PRIVILEGES。CREATE VIEW -- 允许使用CREATE VIEWDELETE -- 允许使用DELETEDROP -- 允许使用DROP TABLEEXECUTE -- 允许用户运行已存储的子程序FILE -- 允许使用SELECT...INTO OUTFILE和LOAD DATA INFILEINDEX -- 允许使用CREATE INDEX和DROP INDEXINSERT -- 允许使用INSERTLOCK TABLES -- 允许对您拥有SELECT权限的表使用LOCK TABLESPROCESS -- 允许使用SHOW FULL PROCESSLISTREFERENCES -- 未被实施RELOAD -- 允许使用FLUSHREPLICATION CLIENT -- 允许用户询问从属服务器或主服务器的地址REPLICATION SLAVE -- 用于复制型从属服务器(从主服务器中读取二进制日志事件)SELECT -- 允许使用SELECTSHOW DATABASES -- 显示所有数据库SHOW VIEW -- 允许使用SHOW CREATE VIEWSHUTDOWN -- 允许使用mysqladmin shutdownSUPER -- 允许使用CHANGE MASTER, KILL, PURGE MASTER LOGS和SET GLOBAL语句,mysqladmin debug命令;允许您连接(一次),即使已达到max_connections。UPDATE -- 允许使用UPDATEUSAGE -- “无权限”的同义词GRANT OPTION -- 允许授予权限 表维护12345678-- 分析和存储表的关键字分布ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE 表名 ...-- 检查一个或多个表是否有错误CHECK TABLE tbl_name [, tbl_name] ... [option] ...option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}-- 整理数据文件的碎片OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... 杂项 可用反引号(`)为标识符(库名、表名、字段名、索引、别名)包裹,以避免与关键字重名!中文也可以作为标识符! 每个库目录存在一个保存当前数据库的选项文件db.opt。 注释: 单行注释 # 注释内容 多行注释 /* 注释内容 */ 单行注释 - - 注释内容 (标准SQL注释风格,要求双破折号后加一空格符(空格、TAB、换行等)) 模式通配符: _ 任意单个字符 % 任意多个字符,甚至包括零字符 单引号需要进行转义 ' CMD命令行内的语句结束符可以为 “;”, “\\G”, “\\g”,仅影响显示结果。其他地方还是用分号结束。delimiter 可修改当前对话的语句结束符。 SQL对大小写不敏感 清除已有语句:\\c","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"MySQL使用笔记(八)","slug":"database-mysql7","date":"2019-01-06T09:22:32.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"d0ffb234/","link":"","permalink":"https://syshlang.com/d0ffb234/","excerpt":"","text":"⇦ MySQL使用笔记(七) 内置函数1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253-- 数值函数abs(x) -- 绝对值 abs(-10.9) = 10format(x, d) -- 格式化千分位数值 format(1234567.456, 2) = 1,234,567.46ceil(x) -- 向上取整 ceil(10.1) = 11floor(x) -- 向下取整 floor (10.1) = 10round(x) -- 四舍五入去整mod(m, n) -- m%n m mod n 求余 10%3=1pi() -- 获得圆周率pow(m, n) -- m^nsqrt(x) -- 算术平方根rand() -- 随机数truncate(x, d) -- 截取d位小数-- 时间日期函数now(), current_timestamp(); -- 当前日期时间current_date(); -- 当前日期current_time(); -- 当前时间date('yyyy-mm-dd hh:ii:ss'); -- 获取日期部分time('yyyy-mm-dd hh:ii:ss'); -- 获取时间部分date_format('yyyy-mm-dd hh:ii:ss', '%d %y %a %d %m %b %j'); -- 格式化时间unix_timestamp(); -- 获得unix时间戳from_unixtime(); -- 从时间戳获得时间-- 字符串函数length(string) -- string长度,字节char_length(string) -- string的字符个数substring(str, position [,length]) -- 从str的position开始,取length个字符replace(str ,search_str ,replace_str) -- 在str中用replace_str替换search_strinstr(string ,substring) -- 返回substring首次在string中出现的位置concat(string [,...]) -- 连接字串charset(str) -- 返回字串字符集lcase(string) -- 转换成小写left(string, length) -- 从string2中的左边起取length个字符load_file(file_name) -- 从文件读取内容locate(substring, string [,start_position]) -- 同instr,但可指定开始位置lpad(string, length, pad) -- 重复用pad加在string开头,直到字串长度为lengthltrim(string) -- 去除前端空格repeat(string, count) -- 重复count次rpad(string, length, pad) --在str后用pad补充,直到长度为lengthrtrim(string) -- 去除后端空格strcmp(string1 ,string2) -- 逐字符比较两字串大小-- 流程函数case when [condition] then result [when [condition] then result ...] [else result] end 多分支if(expr1,expr2,expr3) 双分支。-- 聚合函数count()sum();max();min();avg();group_concat()-- 其他常用函数md5();default(); 自定义函数123456789101112131415161718--// 存储函数,自定义函数 ------------ 新建 CREATE FUNCTION function_name (参数列表) RETURNS 返回值类型 函数体 - 函数名,应该合法的标识符,并且不应该与已有的关键字冲突。 - 一个函数应该属于某个数据库,可以使用db_name.funciton_name的形式执行当前函数所属数据库,否则为当前数据库。 - 参数部分,由"参数名"和"参数类型"组成。多个参数用逗号隔开。 - 函数体由多条可用的mysql语句,流程控制,变量声明等语句构成。 - 多条语句应该使用 begin...end 语句块包含。 - 一定要有 return 返回值语句。-- 删除 DROP FUNCTION [IF EXISTS] function_name;-- 查看 SHOW FUNCTION STATUS LIKE 'partten' SHOW CREATE FUNCTION function_name;-- 修改 ALTER FUNCTION function_name 函数选项 自定义功能1234567891011121314151617181920212223242526272829303132--// 存储过程,自定义功能 ------------ 定义存储存储过程 是一段代码(过程),存储在数据库中的sql组成。一个存储过程通常用于完成一段业务逻辑,例如报名,交班费,订单入库等。而一个函数通常专注与某个功能,视为其他程序服务的,需要在其他语句中调用函数才可以,而存储过程不能被其他调用,是自己执行 通过call执行。-- 创建CREATE PROCEDURE sp_name (参数列表) 过程体参数列表:不同于函数的参数列表,需要指明参数类型IN,表示输入型OUT,表示输出型INOUT,表示混合型注意,没有返回值。/* 存储过程 */ ------------------存储过程是一段可执行性代码的集合。相比函数,更偏向于业务逻辑。调用:CALL 过程名-- 注意- 没有返回值。- 只能单独调用,不可夹杂在其他语句中-- 参数IN|OUT|INOUT 参数名 数据类型IN 输入:在调用过程中,将数据输入到过程体内部的参数OUT 输出:在调用过程中,将过程体处理完的结果返回到客户端INOUT 输入输出:既可输入,也可输出-- 语法CREATE PROCEDURE 过程名 (参数列表)BEGIN 过程体END ⇨ MySQL使用笔记(九)","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"MySQL使用笔记(七)","slug":"database-mysql6","date":"2019-01-06T07:29:04.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"19ad0d38/","link":"","permalink":"https://syshlang.com/19ad0d38/","excerpt":"","text":"⇦ MySQL使用笔记(六) 字符连接函数123concat(str1,str2,...])concat_ws(separator,str1,str2,...) sql语句12345678910111213141516171819202122232425262728-- 分支语句if 条件 then 执行语句elseif 条件 then 执行语句else 执行语句end if;-- 修改最外层语句结束符delimiter 自定义结束符号 SQL语句自定义结束符号delimiter ; -- 修改回原来的分号-- 语句块包裹begin 语句块end-- 特殊的执行1. 只要添加记录,就会触发程序。2. Insert into on duplicate key update 语法会触发: 如果没有重复记录,会触发 before insert, after insert; 如果有重复记录并更新,会触发 before insert, before update, after update; 如果有重复记录但是没有发生更新,则触发 before insert, before update3. Replace 语法 如果有记录,则执行 before insert, before delete, after delete, after insert SQL编程12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152--// 局部变量 ------------ 变量声明 declare var_name[,...] type [default value] 这个语句被用来声明局部变量。要给变量提供一个默认值,请包含一个default子句。值可以被指定为一个表达式,不需要为一个常数。如果没有default子句,初始值为null。-- 赋值 使用 set 和 select into 语句为变量赋值。 - 注意:在函数内是可以使用全局变量(用户自定义的变量)--// 全局变量 ------------ 定义、赋值set 语句可以定义并为变量赋值。set @var = value;也可以使用select into语句为变量初始化并赋值。这样要求select语句只能返回一行,但是可以是多个字段,就意味着同时为多个变量进行赋值,变量的数量需要与查询的列数一致。还可以把赋值语句看作一个表达式,通过select执行完成。此时为了避免=被当作关系运算符看待,使用:=代替。(set语句可以使用= 和 :=)。select @var:=20;select @v1:=id, @v2=name from t1 limit 1;select * from tbl_name where @var:=30;select into 可以将表中查询获得的数据赋给变量。 -| select max(height) into @max_height from tb;-- 自定义变量名为了避免select语句中,用户自定义的变量与系统标识符(通常是字段名)冲突,用户自定义变量在变量名前使用@作为开始符号。@var=10; - 变量被定义后,在整个会话周期都有效(登录到退出)--// 控制结构 ------------ if语句if search_condition then statement_list[elseif search_condition then statement_list]...[else statement_list]end if;-- case语句CASE value WHEN [compare-value] THEN result[WHEN [compare-value] THEN result ...][ELSE result]END-- while循环[begin_label:] while search_condition do statement_listend while [end_label];- 如果需要在循环内提前终止 while循环,则需要使用标签;标签需要成对出现。 -- 退出循环 退出整个循环 leave 退出当前循环 iterate 通过退出的标签决定退出哪个循环 ⇨ MySQL使用笔记(八)","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"MySQL使用笔记(六)","slug":"database-mysql5","date":"2019-01-05T10:45:04.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"86a515b2/","link":"","permalink":"https://syshlang.com/86a515b2/","excerpt":"","text":"⇦ MySQL使用笔记(五) 视图 什么是视图: 视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。 视图具有表结构文件,但不存在数据文件。 对其中所引用的基础表来说,视图的作用类似于筛选。定义视图的筛选可以来自当前或其它数据库的一个或多个表,或者其它视图。通过视图进行查询没有任何限制,通过它们进行数据修改时的限制也很少。 视图是存储在数据库中的查询的sql语句,它主要出于两种原因:安全原因,视图可以隐藏一些数据,如:社会保险基金表,可以用视图只显示姓名,地址,而不显示社会保险号和工资数等,另一原因是可使复杂的查询易于理解和使用。 123456789101112131415161718192021222324-- 创建视图CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name [(column_list)] AS select_statement - 视图名必须唯一,同时不能与表重名。 - 视图可以使用select语句查询到的列名,也可以自己指定相应的列名。 - 可以指定视图执行的算法,通过ALGORITHM指定。 - column_list如果存在,则数目必须等于SELECT语句检索的列数-- 查看结构 SHOW CREATE VIEW view_name-- 删除视图 - 删除视图后,数据依然存在。 - 可同时删除多个视图。 DROP VIEW [IF EXISTS] view_name ...-- 修改视图结构 - 一般不修改视图,因为不是所有的更新视图都会映射到表上。 ALTER VIEW view_name [(column_list)] AS select_statement-- 视图作用 1. 简化业务逻辑 2. 对客户端隐藏真实的表结构-- 视图算法(ALGORITHM) MERGE 合并 将视图的查询语句,与外部查询需要先合并再执行! TEMPTABLE 临时表 将视图执行完毕后,形成临时表,再做外层查询! UNDEFINED 未定义(默认),指的是MySQL自主去选择相应的算法。 事务(transaction)12345678910111213141516171819202122232425262728293031323334353637383940414243444546事务是指逻辑上的一组操作,组成这组操作的各个单元,要不全成功要不全失败。 - 支持连续SQL的集体成功或集体撤销。 - 事务是数据库在数据晚自习方面的一个功能。 - 需要利用 InnoDB 或 BDB 存储引擎,对自动提交的特性支持完成。 - InnoDB被称为事务安全型引擎。-- 事务开启 START TRANSACTION; 或者 BEGIN; 开启事务后,所有被执行的SQL语句均被认作当前事务内的SQL语句。-- 事务提交 COMMIT;-- 事务回滚 ROLLBACK; 如果部分操作发生问题,映射到事务开启前。-- 事务的特性 1. 原子性(Atomicity) 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 2. 一致性(Consistency) 事务前后数据的完整性必须保持一致。 - 事务开始和结束时,外部数据一致 - 在整个事务过程中,操作是连续的 3. 隔离性(Isolation) 多个用户并发访问数据库时,一个用户的事务不能被其它用户的事物所干扰,多个并发事务之间的数据要相互隔离。 4. 持久性(Durability) 一个事务一旦被提交,它对数据库中的数据改变就是永久性的。-- 事务的实现 1. 要求是事务支持的表类型 2. 执行一组相关的操作前开启事务 3. 整组操作完成后,都成功,则提交;如果存在失败,选择回滚,则会回到事务开始的备份点。-- 事务的原理 利用InnoDB的自动提交(autocommit)特性完成。 普通的MySQL执行语句后,当前的数据提交操作均可被其他客户端可见。 而事务是暂时关闭“自动提交”机制,需要commit提交持久化数据操作。-- 注意 1. 数据定义语言(DDL)语句不能被回滚,比如创建或取消数据库的语句,和创建、取消或更改表或存储的子程序的语句。 2. 事务不能被嵌套-- 保存点 SAVEPOINT 保存点名称 -- 设置一个事务保存点 ROLLBACK TO SAVEPOINT 保存点名称 -- 回滚到保存点 RELEASE SAVEPOINT 保存点名称 -- 删除保存点-- InnoDB自动提交特性设置 SET autocommit = 0|1; 0表示关闭自动提交,1表示开启自动提交。 - 如果关闭了,那普通操作的结果对其他客户端也不可见,需要commit提交后才能持久化数据操作。 - 也可以关闭自动提交来开启事务。但与START TRANSACTION不同的是, SET autocommit是永久改变服务器的设置,直到下次再次修改该设置。(针对当前连接) 而START TRANSACTION记录开启前的状态,而一旦事务提交或回滚后就需要再次开启事务。(针对当前事务) 锁表1234567表锁定只用于防止其它客户端进行不正当地读取和写入MyISAM 支持表锁,InnoDB 支持行锁-- 锁定 LOCK TABLES tbl_name [AS alias]-- 解锁 UNLOCK TABLES 触发器123456789101112131415161718192021 触发程序是与表有关的命名数据库对象,当该表出现特定事件时,将激活该对象 监听:记录的增加、修改、删除。-- 创建触发器CREATE TRIGGER trigger_name trigger_time trigger_event ON tbl_name FOR EACH ROW trigger_stmt 参数: trigger_time是触发程序的动作时间。它可以是 before 或 after,以指明触发程序是在激活它的语句之前或之后触发。 trigger_event指明了激活触发程序的语句的类型 INSERT:将新行插入表时激活触发程序 UPDATE:更改某一行时激活触发程序 DELETE:从表中删除某一行时激活触发程序 tbl_name:监听的表,必须是永久性的表,不能将触发程序与TEMPORARY表或视图关联起来。 trigger_stmt:当触发程序激活时执行的语句。执行多个语句,可使用BEGIN...END复合语句结构-- 删除DROP TRIGGER [schema_name.]trigger_name可以使用old和new代替旧的和新的数据 更新操作,更新前是old,更新后是new. 删除操作,只有old. 增加操作,只有new.-- 注意 1. 对于具有相同触发程序动作时间和事件的给定表,不能有两个触发程序。 ⇨ MySQL使用笔记(七)","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"MySQL使用笔记(五)","slug":"database-mysql4","date":"2019-01-05T04:52:06.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"1faa5dc0/","link":"","permalink":"https://syshlang.com/1faa5dc0/","excerpt":"","text":"⇦ MySQL使用笔记(四) 连接查询(join)12345678910111213141516171819202122232425将多个表的字段进行连接,可以指定连接条件。-- 内连接(inner join) - 默认就是内连接,可省略inner。 - 只有数据存在时才能发送连接。即连接结果不能出现空行。 on 表示连接条件。其条件表达式与where类似。也可以省略条件(表示条件永远为真) 也可用where表示连接条件。 还有 using, 但需字段名相同。 using(字段名) -- 交叉连接 cross join 即,没有条件的内连接。 select * from tb1 cross join tb2;-- 外连接(outer join) - 如果数据不存在,也会出现在连接结果中。 -- 左外连接 left join 如果数据不存在,左表记录会出现,而右表为null填充 -- 右外连接 right join 如果数据不存在,右表记录会出现,而左表为null填充-- 自然连接(natural join) 自动判断连接条件完成连接。 相当于省略了using,会自动查找相同字段名。 natural join natural left join natural right joinselect info.id, info.name, info.stu_num, extra_info.hobby, extra_info.sex from info, extra_info where info.stu_num = extra_info.stu_id; 导出123456789101112131415161718192021select * into outfile 文件地址 [控制格式] from 表名; -- 导出表数据load data [local] infile 文件地址 [replace|ignore] into table 表名 [控制格式]; -- 导入数据 生成的数据默认的分隔符是制表符 local未指定,则数据文件必须在服务器上 replace 和 ignore 关键词控制对现有的唯一键记录的重复的处理-- 控制格式fields 控制字段格式默认:fields terminated by '\\t' enclosed by '' escaped by '\\\\' terminated by 'string' -- 终止 enclosed by 'char' -- 包裹 escaped by 'char' -- 转义 -- 示例: SELECT a,b,a+b INTO OUTFILE '/tmp/result.text' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\\n' FROM test_table;lines 控制行格式默认:lines terminated by '\\n' terminated by 'string' -- 终止 INSERT123456789101112131415select语句获得的数据可以用insert插入。可以省略对列的指定,要求 values () 括号内,提供给了按照列顺序出现的所有字段的值。 或者使用set语法。 INSERT INTO tbl_name SET field=value,...;可以一次性使用多个值,采用(), (), ();的形式。 INSERT INTO tbl_name VALUES (), (), ();可以在列值指定时,使用表达式。 INSERT INTO tbl_name VALUES (field_value, 10+10, now());可以使用一个特殊值 DEFAULT,表示该列使用默认值。 INSERT INTO tbl_name VALUES (field_value, DEFAULT);可以通过一个查询的结果,作为需要插入的值。 INSERT INTO tbl_name SELECT ...;可以指定在插入的值出现主键(或唯一索引)冲突时,更新其他非主键列的信息。 INSERT INTO tbl_name VALUES/SET/SELECT ON DUPLICATE KEY UPDATE 字段=值, …; DELETE1234567DELETE FROM tbl_name [WHERE where_definition] [ORDER BY ...] [LIMIT row_count]按照条件删除。where指定删除的最多记录数。limit可以通过排序条件删除。order by + limit支持多表删除,使用类似连接语法。delete from 需要删除数据多表1,表2 using 表连接操作 条件。 TRUNCATE123456789TRUNCATE [TABLE] tbl_name清空数据删除重建表区别:1,truncate 是删除表再创建,delete 是逐条删除2,truncate 重置auto_increment的值。而delete不会3,truncate 不知道删除了几条,而delete知道。4,当被用于带分区的表时,truncate 会保留分区 备份与还原123456789101112131415161718192021备份,将数据的结构与表内数据保存起来。利用 mysqldump 指令完成。-- 导出mysqldump [options] db_name [tables]mysqldump [options] ---database DB1 [DB2 DB3...]mysqldump [options] --all--database1. 导出一张表 mysqldump -u用户名 -p密码 库名 表名 > 文件名(D:/a.sql)2. 导出多张表 mysqldump -u用户名 -p密码 库名 表1 表2 表3 > 文件名(D:/a.sql)3. 导出所有表 mysqldump -u用户名 -p密码 库名 > 文件名(D:/a.sql)4. 导出一个库 mysqldump -u用户名 -p密码 --lock-all-tables --database 库名 > 文件名(D:/a.sql)可以-w携带WHERE条件-- 导入1. 在登录mysql的情况下: source 备份文件2. 在不登录的情况下 mysql -u用户名 -p密码 库名 < 备份文件 ⇨ MySQL使用笔记(六)","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"MySQL使用笔记(四)","slug":"database-mysql3","date":"2019-01-05T02:24:04.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"14a2e4fb/","link":"","permalink":"https://syshlang.com/14a2e4fb/","excerpt":"","text":"⇦ MySQL使用笔记(三) 建表规范 Normal Format, NF 每个表保存一个实体信息 每个具有一个ID字段作为主键 ID主键 + 原子表 1NF, 第一范式 字段不能再分,就满足第一范式。 2NF, 第二范式 满足第一范式的前提下,不能出现部分依赖。 消除符合主键就可以避免部分依赖。增加单列关键字。 3NF, 第三范式 满足第二范式的前提下,不能出现传递依赖。 某个字段依赖于主键,而有其他字段依赖于该字段。这就是传递依赖。 将一个实体信息的数据放在一个表内实现。 SELECT123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960SELECT [ALL|DISTINCT] select_expr FROM -> WHERE -> GROUP BY [合计函数] -> HAVING -> ORDER BY -> LIMITa. select_expr -- 可以用 * 表示所有字段。 select * from tb; -- 可以使用表达式(计算公式、函数调用、字段也是个表达式) select stu, 29+25, now() from tb; -- 可以为每个列使用别名。适用于简化列标识,避免多个列标识符重复。 - 使用 as 关键字,也可省略 as. select stu+10 as add10 from tb;b. FROM 子句 用于标识查询来源。 -- 可以为表起别名。使用as关键字。 SELECT * FROM tb1 AS tt, tb2 AS bb; -- from子句后,可以同时出现多个表。 -- 多个表会横向叠加到一起,而数据会形成一个笛卡尔积。 SELECT * FROM tb1, tb2; -- 向优化符提示如何选择索引 USE INDEX、IGNORE INDEX、FORCE INDEX SELECT * FROM table1 USE INDEX (key1,key2) WHERE key1=1 AND key2=2 AND key3=3; SELECT * FROM table1 IGNORE INDEX (key3) WHERE key1=1 AND key2=2 AND key3=3;c. WHERE 子句 -- 从from获得的数据源中进行筛选。 -- 整型1表示真,0表示假。 -- 表达式由运算符和运算数组成。 -- 运算数:变量(字段)、值、函数返回值 -- 运算符: =, <=>, <>, !=, <=, <, >=, >, !, &&, ||, in (not) null, (not) like, (not) in, (not) between and, is (not), and, or, not, xor is/is not 加上ture/false/unknown,检验某个值的真假 <=>与<>功能相同,<=>可用于null比较d. GROUP BY 子句, 分组子句 GROUP BY 字段/别名 [排序方式] 分组后会进行排序。升序:ASC,降序:DESC 以下[合计函数]需配合 GROUP BY 使用: count 返回不同的非NULL值数目 count(*)、count(字段) sum 求和 max 求最大值 min 求最小值 avg 求平均值 group_concat 返回带有来自一个组的连接的非NULL值的字符串结果。组内字符串连接。e. HAVING 子句,条件子句 与 where 功能、用法相同,执行时机不同。 where 在开始时执行检测数据,对原数据进行过滤。 having 对筛选出的结果再次进行过滤。 having 字段必须是查询出来的,where 字段必须是数据表存在的。 where 不可以使用字段的别名,having 可以。因为执行WHERE代码时,可能尚未确定列值。 where 不可以使用合计函数。一般需用合计函数才会用 having SQL标准要求HAVING必须引用GROUP BY子句中的列或用于合计函数中的列。f. ORDER BY 子句,排序子句 order by 排序字段/别名 排序方式 [,排序字段/别名 排序方式]... 升序:ASC,降序:DESC 支持多个字段的排序。g. LIMIT 子句,限制结果数量子句 仅对处理好的结果进行数量限制。将处理好的结果的看作是一个集合,按照记录出现的顺序,索引从0开始。 limit 起始位置, 获取条数 省略第一个参数,表示从索引0开始。limit 获取条数h. DISTINCT, ALL 选项 distinct 去除重复记录 默认为 all, 全部记录 UNION12345678将多个select查询的结果组合成一个结果集合。 SELECT ... UNION [ALL|DISTINCT] SELECT ... 默认 DISTINCT 方式,即所有返回的行都是唯一的 建议,对每个SELECT查询加上小括号包裹。 ORDER BY 排序时,需加上 LIMIT 进行结合。 需要各select查询的字段数量一样。 每个select查询的字段列表(数量、类型)应一致,因为结果中的字段名以第一条select语句为准。 子查询1234567891011121314151617181920212223242526272829 - 子查询需用括号包裹。-- from型 from后要求是一个表,必须给子查询结果取个别名。 - 简化每个查询内的条件。 - from型需将结果生成一个临时表格,可用以原表的锁定的释放。 - 子查询返回一个表,表型子查询。 select * from (select * from tb where id>0) as subfrom where id>1;-- where型 - 子查询返回一个值,标量子查询。 - 不需要给子查询取别名。 - where子查询内的表,不能直接用以更新。 select * from tb where money = (select max(money) from tb); -- 列子查询 如果子查询结果返回的是一列。 使用 in 或 not in 完成查询 exists 和 not exists 条件 如果子查询返回数据,则返回1或0。常用于判断条件。 select column1 from t1 where exists (select * from t2); -- 行子查询 查询条件是一个行。 select * from t1 where (id, gender) in (select id, gender from t2); 行构造符:(col1, col2, ...) 或 ROW(col1, col2, ...) 行构造符通常用于与对能返回两个或两个以上列的子查询进行比较。 -- 特殊运算符 != all() 相当于 not in = some() 相当于 in。any 是 some 的别名 != some() 不等同于 not in,不等于其中某一个。 all, some 可以配合其他运算符一起使用。 ⇨ MySQL使用笔记(五)","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"MySQL使用笔记(三)","slug":"database-mysql2","date":"2019-01-04T13:20:08.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"63a5d46d/","link":"","permalink":"https://syshlang.com/63a5d46d/","excerpt":"","text":"⇦ MySQL使用笔记(二) 选择类型PHP角度 功能满足 存储空间尽量小,处理效率更高 考虑兼容问题 IP存储 只需存储,可用字符串 如果需计算,查找等,可存储为4个字节的无符号int,即unsigned PHP函数转换ip2long可转换为整型,但会出现携带符号问题。需格式化为无符号的整型。利用sprintf函数格式化字符串sprintf(“%u”, ip2long(‘192.168.3.134’));然后用long2ip将整型转回IP字符串 MySQL函数转换(无符号整型,UNSIGNED)INET_ATON(‘127.0.0.1’) 将IP转为整型INET_NTOA(2130706433) 将整型转为IP 列属性(列约束)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748491. PRIMARY 主键 - 能唯一标识记录的字段,可以作为主键。 - 一个表只能有一个主键。 - 主键具有唯一性。 - 声明字段时,用 primary key 标识。 也可以在字段列表之后声明 例:create table tab ( id int, stu varchar(10), primary key (id)); - 主键字段的值不能为null。 - 主键可以由多个字段共同组成。此时需要在字段列表后声明的方法。 例:create table tab ( id int, stu varchar(10), age int, primary key (stu, age));2. UNIQUE 唯一索引(唯一约束) 使得某字段的值也不能重复。3. NULL 约束 null不是数据类型,是列的一个属性。 表示当前列是否可以为null,表示什么都没有。 null, 允许为空。默认。 not null, 不允许为空。 insert into tab values (null, 'val'); -- 此时表示将第一个字段的值设为null, 取决于该字段是否允许为null4. DEFAULT 默认值属性 当前字段的默认值。 insert into tab values (default, 'val'); -- 此时表示强制使用默认值。 create table tab ( add_time timestamp default current_timestamp ); -- 表示将当前时间的时间戳设为默认值。 current_date, current_time5. AUTO_INCREMENT 自动增长约束 自动增长必须为索引(主键或unique) 只能存在一个字段为自动增长。 默认为1开始自动增长。可以通过表属性 auto_increment = x进行设置,或 alter table tbl auto_increment = x;6. COMMENT 注释 例:create table tab ( id int ) comment '注释内容';7. FOREIGN KEY 外键约束 用于限制主表与从表数据完整性。 alter table t1 add constraint `t1_t2_fk` foreign key (t1_id) references t2(id); -- 将表t1的t1_id外键关联到表t2的id字段。 -- 每个外键都有一个名字,可以通过 constraint 指定 存在外键的表,称之为从表(子表),外键指向的表,称之为主表(父表)。 作用:保持数据一致性,完整性,主要目的是控制存储在外键表(从表)中的数据。 MySQL中,可以对InnoDB引擎使用外键约束: 语法: foreign key (外键字段) references 主表名 (关联字段) [主表记录删除时的动作] [主表记录更新时的动作] 此时需要检测一个从表的外键需要约束为主表的已存在的值。外键在没有关联的情况下,可以设置为null.前提是该外键列,没有not null。 可以不指定主表记录更改或更新时的动作,那么此时主表的操作被拒绝。 如果指定了 on update 或 on delete:在删除或更新时,有如下几个操作可以选择: 1. cascade,级联操作。主表数据被更新(主键值更新),从表也被更新(外键值更新)。主表记录被删除,从表相关记录也被删除。 2. set null,设置为null。主表数据被更新(主键值更新),从表的外键被设置为null。主表记录被删除,从表相关记录外键被设置成null。但注意,要求该外键列,没有not null属性约束。 3. restrict,拒绝父表删除和更新。 注意,外键只被InnoDB存储引擎所支持。其他引擎是不支持的。 ⇨ MySQL使用笔记(四)","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"MySQL使用笔记(二)","slug":"database-mysql1","date":"2019-01-04T12:29:04.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"faac85d7/","link":"","permalink":"https://syshlang.com/faac85d7/","excerpt":"","text":"⇦ MySQL使用笔记(一) 数据操作1234567891011121314151617-- 增 INSERT [INTO] 表名 [(字段列表)] VALUES (值列表)[, (值列表), ...] -- 如果要插入的值列表包含所有字段并且顺序一致,则可以省略字段列表。 -- 可同时插入多条数据记录! REPLACE 与 INSERT 完全一样,可互换。 INSERT [INTO] 表名 SET 字段名=值[, 字段名=值, ...]-- 查 SELECT 字段列表 FROM 表名[ 其他子句] -- 可来自多个表的多个字段 -- 其他子句可以不使用 -- 字段列表可以用*代替,表示所有字段-- 删 DELETE FROM 表名[ 删除条件子句] 没有条件子句,则会删除全部-- 改 UPDATE 表名 SET 字段名=新值[, 字段名=新值] [更新条件] 字符集编码123456789101112131415161718-- MySQL、数据库、表、字段均可设置编码-- 数据编码与客户端编码不需一致SHOW VARIABLES LIKE 'character_set_%' -- 查看所有字符集编码项 character_set_client 客户端向服务器发送数据时使用的编码 character_set_results 服务器端将结果返回给客户端所使用的编码 character_set_connection 连接层编码SET 变量名 = 变量值 SET character_set_client = gbk; SET character_set_results = gbk; SET character_set_connection = gbk;SET NAMES GBK; -- 相当于完成以上三个设置-- 校对集 校对集用以排序 SHOW CHARACTER SET [LIKE 'pattern']/SHOW CHARSET [LIKE 'pattern'] 查看所有字符集 SHOW COLLATION [LIKE 'pattern'] 查看所有校对集 CHARSET 字符集编码 设置字符集编码 COLLATE 校对集编码 设置校对集编码 数据类型(列类型)数值类型a. 整型 类型 字节 范围(有符号位) tinyint 1字节 -128 ~ 127 无符号位:0 ~ 255 smallint 2字节 -32768 ~ 32767 mediumint 3字节 -8388608 ~ 8388607 int 4字节 bigint 8字节 int(M) M表示总位数 默认存在符号位,unsigned 属性修改 显示宽度,如果某个数不够定义字段时设置的位数,则前面以0补填,zerofill 属性修改例:int(5) 插入一个数’123’,补填后为’00123’ 在满足要求的情况下,越小越好。 1表示bool值真,0表示bool值假。MySQL没有布尔类型,通过整型0和1表示。常用 tinyint(1)表示布尔型。 b. 浮点型 类型 字节 范围 float(单精度) 4字节 double(双精度) 8字节 浮点型既支持符号位 unsigned 属性,也支持显示宽度 zerofill 属性。不同于整型,前后均会补填0.定义浮点型时,需指定总位数和小数位数。float(M, D) double(M, D)M表示总位数,D表示小数位数。M和D的大小会决定浮点数的范围。不同于整型的固定范围。M既表示总位数(不包括小数点和正负号),也表示显示宽度(所有显示符号均包括)。支持科学计数法表示。浮点数表示近似值。 c. 定点数 decimal – 可变长度decimal(M, D) M也表示总位数,D表示小数位数。保存一个精确的数值,不会发生数据的改变,不同于浮点数的四舍五入。将浮点数转换为字符串来保存,每9位数字保存为4个字节。 字符串类型 a. char, varchar char 定长字符串,速度快,但浪费空间 varchar 变长字符串,速度慢,但节省空间 M表示能存储的最大长度,此长度是字符数,非字节数。 不同的编码,所占用的空间不同。 char,最多255个字符,与编码无关。 varchar,最多65535字符,与编码有关。 一条有效记录最大不能超过65535个字节。 utf8 最大为21844个字符,gbk 最大为32766个字符,latin1 最大为65532个字符 varchar 是变长的,需要利用存储空间保存 varchar 的长度,如果数据小于255个字节,则采用一个字节来保存长度,反之需要两个字节来保存。 varchar 的最大有效长度由最大行大小和使用的字符集确定。 最大有效长度是65532字节,因为在varchar存字符串时,第一个字节是空的,不存在任何数据,然后还需两个字节来存放字符串的长度,所以有效长度是64432-1-2=65532字节。 例:若一个表定义为 CREATE TABLE tb(c1 int, c2 char(30), c3 varchar(N)) charset=utf8; 问N的最大值是多少? 答:(65535-1-2-4-30*3)/3 b. blob, text blob 二进制字符串(字节字符串) tinyblob, blob, mediumblob, longblob text 非二进制字符串(字符字符串) tinytext, text, mediumtext, longtext text 在定义时,不需要定义长度,也不会计算总长度。 text 类型在定义时,不可给default值 c. binary, varbinary 类似于char和varchar,用于保存二进制字符串,也就是保存字节字符串而非字符字符串。 char, varchar, text 对应 binary, varbinary, blob. 日期时间类型 一般用整型保存时间戳,可以很方便的将时间戳进行格式化。 |类型|字节|日期/时间|范围||-|-|-||datetime|8字节|日期及时间| 1000-01-01 00:00:00 到 9999-12-31 23:59:59||date|3字节|日期| 1000-01-01 到 9999-12-31||timestamp|4字节|时间戳|19700101000000 到 2038-01-19 03:14:07||time|3字节|时间|-838:59:59 到 838:59:59||year|1字节|年份|1901 - 2155||datetime|YYYY-MM-DD hh:mm:ss||||timestamp|YY-MM-DD hh:mm:ss / YYYYMMDDhhmmss / YYMMDDhhmmss / YYYYMMDDhhmmss / YYMMDDhhmmss||||date|YYYY-MM-DD / YY-MM-DD / YYYYMMDD / YYMMDD / YYYYMMDD / YYMMDD||||time|hh:mm:ss / hhmmss / hhmmss||||year|YYYY / YY / YYYY / YY|||| 枚举和集合 枚举(enum) enum(val1, val2, val3...) 在已知的值中进行单选。最大数量为65535. 枚举值在保存时,以2个字节的整型(smallint)保存。每个枚举值,按保存的位置顺序,从1开始逐一递增。 表现为字符串类型,存储却是整型。 NULL值的索引是NULL。 空字符串错误值的索引值是0。 集合(set) 123set(val1, val2, val3...)create table tab ( gender set('男', '女', '无') );insert into tab values ('男, 女'); 最多可以有64个不同的成员。以bigint存储,共8个字节。采取位运算的形式。 当创建表时,SET成员值的尾部空格将自动被删除。 ⇨ MySQL使用笔记(三)","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"MySQL使用笔记(一)","slug":"database-mysql","date":"2019-01-03T14:29:04.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"77d59a46/","link":"","permalink":"https://syshlang.com/77d59a46/","excerpt":"","text":"Windows服务12345-- 启动MySQL net start mysql-- 创建Windows服务 sc create mysql binPath= mysqld_bin_path(注意:等号与值之间有空格) 连接与断开服务器12345mysql -h 地址 -P 端口 -u 用户名 -p 密码SHOW PROCESSLIST -- 显示哪些线程正在运行SHOW VARIABLES -- 显示系统变量信息 数据库操作12345678910111213141516171819-- 查看当前数据库 SELECT DATABASE();-- 显示当前时间、用户名、数据库版本 SELECT now(), user(), version();-- 创建库 CREATE DATABASE[ IF NOT EXISTS] 数据库名 数据库选项 数据库选项: CHARACTER SET charset_name COLLATE collation_name-- 查看已有库 SHOW DATABASES[ LIKE 'PATTERN']-- 查看当前库信息 SHOW CREATE DATABASE 数据库名-- 修改库的选项信息 ALTER DATABASE 库名 选项信息-- 删除库 DROP DATABASE[ IF EXISTS] 数据库名 同时删除该数据库相关的目录及其目录内容 表的操作1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677-- 创建表 CREATE [TEMPORARY] TABLE[ IF NOT EXISTS] [库名.]表名 ( 表的结构定义 )[ 表选项] 每个字段必须有数据类型 最后一个字段后不能有逗号 TEMPORARY 临时表,会话结束时表自动消失 对于字段的定义: 字段名 数据类型 [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT] [UNIQUE [KEY] | [PRIMARY] KEY] [COMMENT 'string']-- 表选项 -- 字符集 CHARSET = charset_name 如果表没有设定,则使用数据库字符集 -- 存储引擎 ENGINE = engine_name 表在管理数据时采用的不同的数据结构,结构不同会导致处理方式、提供的特性操作等不同 常见的引擎:InnoDB MyISAM Memory/Heap BDB Merge Example CSV MaxDB Archive 不同的引擎在保存表的结构和数据时采用不同的方式 MyISAM表文件含义:.frm表定义,.MYD表数据,.MYI表索引 InnoDB表文件含义:.frm表定义,表空间数据和日志文件 SHOW ENGINES -- 显示存储引擎的状态信息 SHOW ENGINE 引擎名 {LOGS|STATUS} -- 显示存储引擎的日志或状态信息 -- 自增起始数 AUTO_INCREMENT = 行数 -- 数据文件目录 DATA DIRECTORY = '目录' -- 索引文件目录 INDEX DIRECTORY = '目录' -- 表注释 COMMENT = 'string' -- 分区选项 PARTITION BY ... (详细见手册)-- 查看所有表 SHOW TABLES[ LIKE 'pattern'] SHOW TABLES FROM 表名-- 查看表机构 SHOW CREATE TABLE 表名 (信息更详细) DESC 表名 / DESCRIBE 表名 / EXPLAIN 表名 / SHOW COLUMNS FROM 表名 [LIKE 'PATTERN'] SHOW TABLE STATUS [FROM db_name] [LIKE 'pattern']-- 修改表 -- 修改表本身的选项 ALTER TABLE 表名 表的选项 eg: ALTER TABLE 表名 ENGINE=MYISAM; -- 对表进行重命名 RENAME TABLE 原表名 TO 新表名 RENAME TABLE 原表名 TO 库名.表名 (可将表移动到另一个数据库) -- RENAME可以交换两个表名 -- 修改表的字段机构(13.1.2. ALTER TABLE语法) ALTER TABLE 表名 操作名 -- 操作名 ADD[ COLUMN] 字段定义 -- 增加字段 AFTER 字段名 -- 表示增加在该字段名后面 FIRST -- 表示增加在第一个 ADD PRIMARY KEY(字段名) -- 创建主键 ADD UNIQUE [索引名] (字段名)-- 创建唯一索引 ADD INDEX [索引名] (字段名) -- 创建普通索引 DROP[ COLUMN] 字段名 -- 删除字段 MODIFY[ COLUMN] 字段名 字段属性 -- 支持对字段属性进行修改,不能修改字段名(所有原有属性也需写上) CHANGE[ COLUMN] 原字段名 新字段名 字段属性 -- 支持对字段名修改 DROP PRIMARY KEY -- 删除主键(删除主键前需删除其AUTO_INCREMENT属性) DROP INDEX 索引名 -- 删除索引 DROP FOREIGN KEY 外键 -- 删除外键-- 删除表 DROP TABLE[ IF EXISTS] 表名 ...-- 清空表数据 TRUNCATE [TABLE] 表名-- 复制表结构 CREATE TABLE 表名 LIKE 要复制的表名-- 复制表结构和数据 CREATE TABLE 表名 [AS] SELECT * FROM 要复制的表名-- 检查表是否有错误 CHECK TABLE tbl_name [, tbl_name] ... [option] ...-- 优化表 OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...-- 修复表 REPAIR [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... [QUICK] [EXTENDED] [USE_FRM]-- 分析表 ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... ⇨ MySQL使用笔记(二)","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"}],"tags":[{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"java 进阶","slug":"java-Advanced","date":"2018-12-26T13:54:55.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"88f4aef3/","link":"","permalink":"https://syshlang.com/88f4aef3/","excerpt":"","text":"","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"mindmap","slug":"java/mindmap","permalink":"https://syshlang.com/categories/java/mindmap/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"}]},{"title":"Oracle使用笔记","slug":"database-oracle","date":"2018-11-29T03:40:19.000Z","updated":"2020-09-17T00:34:15.252Z","comments":true,"path":"92d20082/","link":"","permalink":"https://syshlang.com/92d20082/","excerpt":"Linux系统下启动Oracle数据库步骤 第一步:打开Oracle监听 1[root@sunys ~]# lsnrctl start 第二步:进入sqlplus 1[root@sunys ~]# sqlplus /nolog 第三步:使用sysdab角色登录sqlplus 1SQL> conn /as sysdba 第四步:启动数据库 1SQL> startup      经过上面的四个步骤,oracle数据库就可以启动了;关闭数据库用 shutdown 是要等待事物结束才关闭,强制关闭用 shutdown abort。 修改Oracle数据库表空间自增长1234select * from dba_data_files; --查询表空间地址ALTER DATABASE DATAFILE '/oradata/twcwfntts.dbf' AUTOEXTEND ON; --打开自动增长ALTER DATABASE DATAFILE '/oradata/twcwfntts.dbf' AUTOEXTEND ON NEXT 200M ; --每次自动增长200mALTER DATABASE DATAFILE '/oradata/twcwfntts.dbf' AUTOEXTEND ON NEXT 200M MAXSIZE 2024M; --每次自动增长200m,数据表最大不超过1G 使用listagg()和WITHIN GROUP ()将多行合并成一行 1234567select a.id, a.name, (select listagg(b.name, ',') within group(order by b.id) from t_sm_line b where a.exc_line like b.id||',%' or a.exc_line like '%,'||b.id||',%' or a.exc_line like '%,'||b.id or a.exc_line = cast(a.id as varchar(10)) or instr(b.id,a.exc_line) >0) exc_linefrom t_sm_line a where a.exc_line is not null","text":"Linux系统下启动Oracle数据库步骤 第一步:打开Oracle监听 1[root@sunys ~]# lsnrctl start 第二步:进入sqlplus 1[root@sunys ~]# sqlplus /nolog 第三步:使用sysdab角色登录sqlplus 1SQL> conn /as sysdba 第四步:启动数据库 1SQL> startup      经过上面的四个步骤,oracle数据库就可以启动了;关闭数据库用 shutdown 是要等待事物结束才关闭,强制关闭用 shutdown abort。 修改Oracle数据库表空间自增长1234select * from dba_data_files; --查询表空间地址ALTER DATABASE DATAFILE '/oradata/twcwfntts.dbf' AUTOEXTEND ON; --打开自动增长ALTER DATABASE DATAFILE '/oradata/twcwfntts.dbf' AUTOEXTEND ON NEXT 200M ; --每次自动增长200mALTER DATABASE DATAFILE '/oradata/twcwfntts.dbf' AUTOEXTEND ON NEXT 200M MAXSIZE 2024M; --每次自动增长200m,数据表最大不超过1G 使用listagg()和WITHIN GROUP ()将多行合并成一行 1234567select a.id, a.name, (select listagg(b.name, ',') within group(order by b.id) from t_sm_line b where a.exc_line like b.id||',%' or a.exc_line like '%,'||b.id||',%' or a.exc_line like '%,'||b.id or a.exc_line = cast(a.id as varchar(10)) or instr(b.id,a.exc_line) >0) exc_linefrom t_sm_line a where a.exc_line is not null","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"oracle","slug":"database/oracle","permalink":"https://syshlang.com/categories/database/oracle/"}],"tags":[{"name":"Oracle","slug":"Oracle","permalink":"https://syshlang.com/tags/Oracle/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"}]},{"title":"Apache Shiro框架之授权","slug":"Apache-Shiro-5","date":"2018-08-08T14:12:58.000Z","updated":"2020-09-17T00:34:15.244Z","comments":true,"path":"d533dab/","link":"","permalink":"https://syshlang.com/d533dab/","excerpt":"前言     在应用系统中,对于不同角色的用户而言,在系统中的操作权限,例如,对于超级管理员角色可以操作所有功能,普通用户只能访问部分功能,这就是所谓的授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作等)。","text":"前言     在应用系统中,对于不同角色的用户而言,在系统中的操作权限,例如,对于超级管理员角色可以操作所有功能,普通用户只能访问部分功能,这就是所谓的授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作等)。 一、shiro中授权的核心     在shiro授权中,最核心的是:主体/用户(Subject)、权限(Permission)、角色(Role)、资源(Resource),通过这几个关键对象,Shiro可以支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)。 主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。 资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。 权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许。 角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。 二、Shiro三种授权方式1.编程式 通过写if/else 授权代码块完成 1234567 Subject currentUser = SecurityUtils.getSubject();// 测试是否有某一个角色. 调用 Subject 的 hasRole 方法.if (currentUser.hasRole("admin")) { //有权限} else { //无权限} 2.注解式 通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相应的异常 12345678910/** * @author sunys */public class ShiroService { @RequiresRoles({"admin"}) public void testMethod(){ //有权限 }} 权限注解 @RequiresAuthentication:表示当前Subject已经通过 login进行了身份验证;即 Subject. isAuthenticated()返回 true; @RequiresUser:表示当前 Subject已经身份验证或者通过记 住我登录的; @RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份; @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示当前 Subject 需要角色 admin 和user; @RequiresPermissions(value={“user:a”, “user:b”}, logical= Logical.OR):表示当前 Subject需要权限 user:a或 user:b。 2.JSP/GSP 标签 在JSP/GSP页面通过相应的标签完成 12345<%--Jsp页面引入shiro标签库--%><%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %><shiro:hasRole name="admin"><!— 有权限 —></shiro:hasRole> Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制 标签名称及标签条件(均是显示标签内容) 含义 举例 <shiro:authenticated> 用户已经身份验证通过,即 Subject.login登录成功,不是记住我登录的 <shiro:notAuthenticated> 用户未进行身份验证,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证 <shiro:guest> 用户没有身份验证时显示相应信息,即游客访问信息 <shiro:user> 用户已经经过认证/记住我登录后显示相应的信息 <shiro:hasAnyRoles name=”abc,123”> 在有abc或者123角色时 <shiro:hasRole name=”abc”> 拥有角色abc <shiro:lacksRole name=”abc”> 没有角色abc <shiro:hasPermission name=”abc”> 拥有权限资源abc <shiro:lacksPermission name=”abc”> 没有abc权限资源 <shiro:principal> 显示用户身份名称 <shiro:principal property=”username”/> 显示用户身份信息,默认调用 Subject.getPrincipal()获取,即 Primary Principal。 三、Shiro 授权流程 1.授权流程 首先调用 Subject.isPermitted*/hasRole* 接口,其会委托给SecurityManager,而 SecurityManager接着会委托给 Authorizer; Authorizer是真正的授权者,如果调用如 isPermitted(“user:view”),其首先会通过 PermissionResolver把字符串转换成相应的 Permission实例; 在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限; Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果 有多个Realm,会委托给ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted*/hasRole* 会返回true,否则返回false表示授权失败。 1.1.Permissions Shiro 的 Permissions 规则:    资源标识符:操作:对象实例 ID 即对哪个资源的哪个实例可以进行什么操作. 其默认支持通配符权限字符串; : 表示资源/操作/实例的分割; , 表示操作的分割, * 表示任意资源/操作/实例。 多层次管理:    例如:user:query、user:edit 冒号是一个特殊字符,它用来分隔权限字符串的下一部件:第一部分 是权限被操作的领域(打印机),第二部分是被执行的操作; 多个值:每个部件能够保护多个值。因此,除了授予用户 user:query 和 user:edit 权限外,也可以简单地授予他们一个:user:query, edit; 还可以用 * 号代替所有的值,如:user:* , 也可以写:*:query,表示某个用户在所有的领域都有 query 的权限。 实例级访问控制: 这种情况通常会使用三个部件:域、操作、被付诸实施的实例。如:user:edit:manager; 也可以使用通配符来定义,如:user:edit:*、user:*:*、user:*:manager; 部分省略通配符:缺少的部件意味着用户可以访问所有与之匹配的值,比如:user:edit 等价于 user:edit :*、 user 等价于 user:*:*; 注意:通配符只能从字符串的结尾处省略部件,也就是说 user:edit 并不等价于 user:*:edit 1.2.ModularRealmAuthorizer ModularRealmAuthorizer 进行多 Realm 匹配流程: 首先检查相应的 Realm 是否实现了实现了Authorizer; 如果实现了 Authorizer,那么接着调用其相应的 isPermitted*/hasRole* 接口进行匹配; 如果有一个Realm匹配那么将返回 true,否则返回 false。 附:授权相关的的拦截器及其他","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"shiro","slug":"java/shiro","permalink":"https://syshlang.com/categories/java/shiro/"}],"tags":[{"name":"Apache","slug":"Apache","permalink":"https://syshlang.com/tags/Apache/"},{"name":"Shiro","slug":"Shiro","permalink":"https://syshlang.com/tags/Shiro/"},{"name":"Authorization","slug":"Authorization","permalink":"https://syshlang.com/tags/Authorization/"}]},{"title":"Apache Shiro框架之认证","slug":"Apache-Shiro-4","date":"2018-08-07T14:36:58.000Z","updated":"2020-09-17T00:34:15.244Z","comments":true,"path":"159c7b0e/","link":"","permalink":"https://syshlang.com/159c7b0e/","excerpt":"前言     在应用系统中,对于用户而言,登录系统时,一般需要提供如身份 ID等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明,这就是所谓的身份验证;对于服务端,需要先收集用户(对应Shiro中的Subject)提供的 principals(身份)和 credentials(证明)并进行进行身份确认,这就是所谓的认证。","text":"前言     在应用系统中,对于用户而言,登录系统时,一般需要提供如身份 ID等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明,这就是所谓的身份验证;对于服务端,需要先收集用户(对应Shiro中的Subject)提供的 principals(身份)和 credentials(证明)并进行进行身份确认,这就是所谓的认证。 一、身份验证    在 shiro 中,用户需要提供 principals (身份)和 credentials(证 明)给 shiro,从而应用能验证用户身份: principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个Primary principals,一般是用户名/邮箱/手机号。 credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。 最常见的 principals 和 credentials 组合就是用户名/密码了。 1.身份验证基本流程 收集用户身份/凭证,即如用户名/密码; 调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException异常,根据异常提示用户 错误信息;否则登录成功; 创建自定义的 Realm类,继承 org.apache.shiro.realm.AuthorizingRealm类,实现 doGetAuthenticationInfo()方法; 如果身份验证失败请捕获 AuthenticationException或其子类。 示例:ShiroHandler.java ShiroHandler.java123456789101112131415161718192021222324252627282930/** * @author sunys */@Controller@RequestMapping("/shiro")public class ShiroHandler { @RequestMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password) { Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { // 把用户名和密码封装为 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken(username, password); // rememberme token.setRememberMe(true); try { // 执行登录. currentUser.login(token); } // ... catch more exceptions here (maybe custom ones specific to your application? // 所有认证时异常的父类. catch (AuthenticationException ae) { //unexpected condition? error? System.out.println("登录失败: " + ae.getMessage()); } } return "redirect:/list.jsp"; }} ShiroRealm.java ShiroRealm.java1234567891011121314151617181920212223242526272829303132333435363738394041424344/** * @author sunys */public class ShiroRealm extends AuthorizingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //1. 把 AuthenticationToken 转换为 UsernamePasswordToken UsernamePasswordToken upToken = (UsernamePasswordToken) token; //2. 从 UsernamePasswordToken 中来获取 username String username = upToken.getUsername(); //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录 System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息."); //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常 if("unknown".equals(username)){ throw new UnknownAccountException("用户不存在!"); } //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常. if("monster".equals(username)){ throw new LockedAccountException("用户被锁定"); } //6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo //以下信息是从数据库中获取的. //1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. Object principal = username; //2). credentials: 密码. Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4"; if("admin".equals(username)){ credentials = "038bdaf98f2037b31f1e75b5b4c9b26e"; }else if("user".equals(username)){ credentials = "098d2c478e9c11555ce2823231e02ec1"; } //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可 String realmName = getName(); //4). 盐值. ByteSource credentialsSalt = ByteSource.Util.bytes(username); SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName); info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); return info; }} 二、身份认证 1.身份认证流程 首先调用 Subject.login(token)进行登录,其会自动委托给 SecurityManager; SecurityManager负责真正的身份验证逻辑;它会委托给 Authenticator进行身份验证; Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现; Authenticator可能会委托给相应的 AuthenticationStrategy进行多 Realm身份验证,默认 ModularRealmAuthenticator会调用AuthenticationStrategy进行多 Realm身份验证; Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序及策略进行访问。 1.1.Authenticator     Authenticator的职责是验证用户帐号,是 Shiro API中身份验证核心的入口点:如果验证成功,将返回AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException异常。    SecurityManager接口继承了 Authenticator,另外还有一个 ModularRealmAuthenticator实现,其委托给多个Realm进行验证,验证规则通过 AuthenticationStrategy接口指定。 1.2.Realm     Shiro从 Realm获取安全数据(如用户、角色、 权限),即 SecurityManager要验证用户身份,那么它需要从 Realm获取相应的用户进行比较以确定用户身份是否 合法;也需要从 Realm得到用户相应的角色/权限进行验证用户是否能进行操作,可以有一个或多个 Realm,将按照相应的顺序及策略进行访问。 1234567891011121314151617181920212223242526272829<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realms"> <list> <!-- 配置多个Realm ,采用不同的加密方式,将按照list相应的顺序--> <ref bean="firstRealm"/> <ref bean="secondRealm"/> </list> </property></bean><bean id="firstRealm" class="com.syshlang.shiro.realms.ShiroRealm"> <!-- 配置凭证算法匹配器 --> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!-- 采用MD5加密--> <property name="hashAlgorithmName" value="MD5"></property> <property name="hashIterations" value="1024"></property> </bean> </property></bean><bean id="secondRealm" class="com.syshlang.shiro.realms.SecondRealm"> <!-- 配置凭证算法匹配器 --> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!-- 采用SHA1加密--> <property name="hashAlgorithmName" value="SHA1"></property> <property name="hashIterations" value="1024"></property> </bean> </property></bean> Realm接口如下: String getName();//返回一个唯一的Realm名字 boolean supports(AuthenticationToken token);//判断此Realm是否支持Token AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;//根据Token获取认证信息     实际开发中,一般继承 AuthorizingRealm(授权)即可;其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。 Realm 的继承关系: 1.3.AuthenticationStrategy AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有 Realm身份验证成功的认证信息,如果有一个失败就失败了; AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和 FirstSuccessfulStrategy不同,将返回所有 Realm身份验证成功的认证信 息; FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm身份验证成功的认证信息,其他的忽略。 ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy策略: 1234//ModularRealmAuthenticator默认构造器 public ModularRealmAuthenticator() { this.authenticationStrategy = new AtLeastOneSuccessfulStrategy(); } 自定义配置ModularRealmAuthenticator,更改默认策略: 12345<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"></bean> </property></bean> 附:身份验证相关的拦截器","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"shiro","slug":"java/shiro","permalink":"https://syshlang.com/categories/java/shiro/"}],"tags":[{"name":"Apache","slug":"Apache","permalink":"https://syshlang.com/tags/Apache/"},{"name":"Shiro","slug":"Shiro","permalink":"https://syshlang.com/tags/Shiro/"},{"name":"Authenticator","slug":"Authenticator","permalink":"https://syshlang.com/tags/Authenticator/"},{"name":"Realm","slug":"Realm","permalink":"https://syshlang.com/tags/Realm/"},{"name":"AuthenticationStrategy","slug":"AuthenticationStrategy","permalink":"https://syshlang.com/tags/AuthenticationStrategy/"}]},{"title":"Apache Shiro框架与Web集成","slug":"Apache-Shiro-3","date":"2018-07-29T11:57:08.000Z","updated":"2020-09-17T00:34:15.240Z","comments":true,"path":"54c6b908/","link":"","permalink":"https://syshlang.com/54c6b908/","excerpt":"前言     在《Apache Shiro框架》中提到,Apache Shiro是 Java 的一个强大易用的安全(权限)框架,提供了认证、授权、加密、会话管理、与Web 集成、缓存等功能,本文主要记录本人在学习使用Apache Shiro框架与Web集成过程中一些总结及感悟,学习过程中通过新建项目边学边练来加深理解。","text":"前言     在《Apache Shiro框架》中提到,Apache Shiro是 Java 的一个强大易用的安全(权限)框架,提供了认证、授权、加密、会话管理、与Web 集成、缓存等功能,本文主要记录本人在学习使用Apache Shiro框架与Web集成过程中一些总结及感悟,学习过程中通过新建项目边学边练来加深理解。 与Web的集成    Shiro 提供了与Web集成的支持,其通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制。ShiroFilter类似于如 Strut2/SpringMVC这种web框架的前端控制器,是安全控制的入口点,其负责读取配置(如ini 配置文件),然后判断URL 是否需要登录/权限等工作。《Apache Shiro框架默认过滤器及URL匹配》对于Shiro框架默认过滤器及URL匹配做了详细的讲解。 Shiro与Web的集成的配置详解1.配置Spring 及 SpringMVC    Spring及SpringMVC配置此处略,详见《Apache SpringMvc配置详解及源码分析》。 2.配置Shiro的SecurityManager    securityManager安全管理器,它相当于SpringMVC 中的 DispatcherServlet;是Shiro的心脏所有具体的交互都通过SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证、授权、会话及缓存的管理,其配置如下: 1234567891011<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="authenticator" ref="authenticator"/> <property name="realms"> <list> <ref bean="jdbcRealm"/> <ref bean="secondRealm"/> </list> </property> <property name="rememberMeManager.cookie.maxAge" value="10"></property></bean> 3.配置CacheManager及authenticator    CacheManager缓存控制器,用来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能;Authenticator负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了。 12345678<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/></bean><bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean> </property></bean> 4.配置 Realm    Shiro从Realm 获取安全数据(如用户、角色、权限),可以有一个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的 Realm。 1234567891011121314151617<!-- 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean --><bean id="jdbcRealm" class="com.syshlang.shiro.realms.ShiroRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5"></property> <property name="hashIterations" value="1024"></property> </bean> </property></bean><bean id="secondRealm" class="com.syshlang.shiro.realms.SecondRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="SHA1"></property> <property name="hashIterations" value="1024"></property> </bean> </property></bean> 5.配置LifecycleBeanPostProcessor    LifecycleBeanPostProcessor将Initializable和Destroyable的实现类统一在其内部自动分别调用了Initializable.init()和Destroyable.destroy()方法,从而达到管理shiro bean生命周期的目的, 可以自定的来调用配置在Spring IOC容器中shiro bean的生命周期方法。 1<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> 6.启用IOC容器中使用shiro的注解    启用IOC 容器中使用shiro的注解,但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用。 12345<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/><bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/></bean> 7.配置Shiro的shiroFilter     Shiro的DefaultFilter在整个Shiro架构中的作用便是用来拦截所有请求。在 Shiro DefaultFilter 中我们配置了 filterChainDefinitions 属性。filterChainDefinitions 的作用便是对所有被Shiro 拦截的请求做声明,下面是一个标准的DefaultFilter和 filterChainDefinitions 的配置。 12345678910111213141516171819202122232425262728<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/list.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property> <!-- 配置哪些页面需要受保护. 以及访问这些页面需要的权限. 1). anon 可以被匿名访问 2). authc 必须认证(即登录)后才可能访问的页面. 3). logout 登出. 4). roles 角色过滤器 --> <property name="filterChainDefinitions"> <value> /login.jsp = anon /shiro/login = anon /shiro/logout = logout /user.jsp = roles[user] /admin.jsp = roles[admin] # everything else requires authentication: /** = authc </value> </property></bean>     从以上的配置我们可以预见一个问题,那就是倘若filterChainDefinitions的声明过多的话会导致该配置文件冗余臃肿。在Shiro的源代码中filterChainDefinition本身是一个linkedHashMap,所以我们可以通过工厂设计模式来创建一个filterChainDefinitionMap。 1234567891011121314<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/list.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property></bean><!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 --><bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean><bean id="filterChainDefinitionMapBuilder" class="com.syshlang.shiro.factory.FilterChainDefinitionMapBuilder"></bean> FilterChainDefinitionMapBuilder.java12345678910111213141516/** * @author sunys */public class FilterChainDefinitionMapBuilder { public LinkedHashMap<String, String> buildFilterChainDefinitionMap() { LinkedHashMap<String, String> map = new LinkedHashMap<>(); map.put("/login.jsp", "anon"); map.put("/shiro/login", "anon"); map.put("/shiro/logout", "logout"); map.put("/user.jsp", "authc,roles[user]"); map.put("/admin.jsp", "authc,roles[admin]"); map.put("/list.jsp", "user"); map.put("/**", "authc"); return map; }}     在web.xml文件中配置Shiro的shiroFilter过滤器。 web.xml12345678910111213<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param></filter><filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping>     DelegatingFilterProxy 实际上是 Filter 的一个代理对象,DelegatingFilterProxy 作用是自动到Spring IOC容器中查找名字为shiroFilter(filter-name)的bean并把所有Filter的操作委托给它,也可以通过targetBeanName 的初始化参数来配置filter bean 的id。    因为Shiro会来IOC容器中查找和名字对应的 filter bean,所以配置Shiro的filter bean的id必须和 web.xml文件中配置的 DelegatingFilterProxy的 一致,若不一致, 则会抛出: NoSuchBeanDefinitionException。    shiro框架在Java Web应用中使用时,本质上是通过filter方式集成的。也就是说,它是遵循过滤器链规则的:filter的执行顺序与在web.xml中定义的顺序一致     以上所涉及到的配置项在《Apache Shiro框架》中都有详细的讲解。 附:配置清单 applicationContext.xml12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="authenticator" ref="authenticator"/> <property name="realms"> <list> <ref bean="jdbcRealm"/> <ref bean="secondRealm"/> </list> </property> <property name="rememberMeManager.cookie.maxAge" value="10"></property> </bean> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean> <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean> </property> </bean> <bean id="jdbcRealm" class="com.syshlang.shiro.realms.ShiroRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5"></property> <property name="hashIterations" value="1024"></property> </bean> </property> </bean> <bean id="secondRealm" class="com.syshlang.shiro.realms.SecondRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="SHA1"></property> <property name="hashIterations" value="1024"></property> </bean> </property> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/list.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property> <!-- 配置哪些页面需要受保护. 以及访问这些页面需要的权限. 1). anon 可以被匿名访问 2). authc 必须认证(即登录)后才可能访问的页面. 3). logout 登出. 4). roles 角色过滤器 --> <!-- <property name="filterChainDefinitions"> <value> /login.jsp = anon /shiro/login = anon /shiro/logout = logout /user.jsp = roles[user] /admin.jsp = roles[admin] # everything else requires authentication: /** = authc </value> </property> --> </bean> <!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 --> <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean> <bean id="filterChainDefinitionMapBuilder" class="com.syshlang.shiro.factory.FilterChainDefinitionMapBuilder"></bean> <bean id="shiroService" class="com.syshlang.shiro.services.ShiroService"></bean></beans>","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"shiro","slug":"java/shiro","permalink":"https://syshlang.com/categories/java/shiro/"}],"tags":[{"name":"Apache","slug":"Apache","permalink":"https://syshlang.com/tags/Apache/"},{"name":"Shiro","slug":"Shiro","permalink":"https://syshlang.com/tags/Shiro/"},{"name":"frame","slug":"frame","permalink":"https://syshlang.com/tags/frame/"}]},{"title":"Apache Shiro框架默认过滤器及URL匹配","slug":"Apache-Shiro-2","date":"2018-07-28T15:01:26.000Z","updated":"2020-09-17T00:34:15.240Z","comments":true,"path":"96db3174/","link":"","permalink":"https://syshlang.com/96db3174/","excerpt":"前言     shiro框架在Java Web应用中使用时,本质上是通过filter方式集成的,也就是说,它是遵循过滤器链规则,其通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制,本文将对Shiro框架默认过滤器及URL匹配做讲解。","text":"前言     shiro框架在Java Web应用中使用时,本质上是通过filter方式集成的,也就是说,它是遵循过滤器链规则,其通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制,本文将对Shiro框架默认过滤器及URL匹配做讲解。 1.Shiro框架默认过滤器1.1.ShiroFilter的工作原理     filterChainDefinitions是ShiroFilter的属性,在filterChainDefinitions中可以对Url进行配置,看是否可以登录或安全权限的访问,详见后文。    由上图可以看出:浏览器过来的任何请求都会先经过shiro先过滤,先到达AbstractShiroFilter.executeChain()方法,去根据request解析出来的url找到对应的过滤链,然后执行过滤器链,直到成功才会执行javaweb本身的过滤器。 1.2.Shiro框架默认过滤器    ShiroFilter拦截需要安全控制的URL,然后进行相应的控制,如果当前请求的url匹配 [urls]部分的某个url模式,将会执行其配置的拦截器,例如:anon(anonymous)拦截器表示匿名访问(即不需要登 录即可访问),authc (authentication)拦截器表示需要身份认证通过后才能访问。Shiro 内置了很多默认的拦截器,比如身份验证、授权等相关的。shiro框架内置一系列的过滤器供使用,如下:org.apache.shiro.web.filter.mgt.DefaultFilter.java shiro中默认的过滤器:     这些过滤器分为两组,一组是认证过滤器,一组是授权过滤器,后面将会继续讲到。这些过滤器的API可以点击查看。实际开发中,我们可以自定义拦截器来扩展功能以满足项目需求,例如:动态url-角色/权限访问控制的实现、根据 Subject 身份信息获取用户信息绑定到 Request(即设置通用数据)、验证码验证、在线用户信息的保存等。 2.Shiro框架URL匹配    由于ShiroFilter通过拦截需要安全控制的URL进行相应的控制,urls可能是shiro中处理web项目比较核心的部分。 2.1.RL配置格式 [urls]部分的配置,其格式是:”url=拦截器[参数],拦截器[参数]“ 2.2.URL匹配模式 url 模式使用 Ant 风格模式; Ant 路径通配符支持 ?、*、**,注意通配符匹配不包括目录分隔符“/”:     –?:匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/;     –*:匹配零个或多个字符串,如 /admin 将匹配 /admin、/admin123,但不匹配 /admin/1;     –**:匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a 或 /admin/a/b。 2.3.URL 匹配顺序 URL权限采取第一次匹配优先的方式,即从头开始使用第一个匹配的 url 模式对应的拦截器链。 如:    /bb/**=filter1    /bb/aa=filter2    /**=filter3    如果请求的url是“/bb/aa”,因为按照声明顺序进行匹配,那么将使用 filter1 进行拦截。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"shiro","slug":"java/shiro","permalink":"https://syshlang.com/categories/java/shiro/"}],"tags":[{"name":"Apache","slug":"Apache","permalink":"https://syshlang.com/tags/Apache/"},{"name":"Shiro","slug":"Shiro","permalink":"https://syshlang.com/tags/Shiro/"},{"name":"frame","slug":"frame","permalink":"https://syshlang.com/tags/frame/"}]},{"title":"Apache Shiro框架","slug":"Apache-Shiro-1","date":"2018-07-21T10:07:17.000Z","updated":"2020-09-17T00:34:15.240Z","comments":true,"path":"43930f47/","link":"","permalink":"https://syshlang.com/43930f47/","excerpt":"一、概述    Apache Shiro是 Java 的一个强大易用的安全(权限)框架,提供了认证、授权、加密、会话管理、与Web 集成、缓存等功能,可为任何应用提供安全保障,能非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在 JavaEE 环境。Shiro为解决下列问题提供了保护应用的API: 认证 - 用户身份识别,常被称为用户“登录”; 授权 - 访问控制; 密码加密 - 保护或隐藏数据防止被偷窥; 会话管理 - 每用户相关的时间敏感的状态。","text":"一、概述    Apache Shiro是 Java 的一个强大易用的安全(权限)框架,提供了认证、授权、加密、会话管理、与Web 集成、缓存等功能,可为任何应用提供安全保障,能非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在 JavaEE 环境。Shiro为解决下列问题提供了保护应用的API: 认证 - 用户身份识别,常被称为用户“登录”; 授权 - 访问控制; 密码加密 - 保护或隐藏数据防止被偷窥; 会话管理 - 每用户相关的时间敏感的状态。 二、功能简介    Apache Shiro基本功能点如图所示: Authentication : 身份认证/登录,验证用户是不是拥有相应的身份; Authorization : 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用\\户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户\\对某个资源是否具有某个权限; Session Manager : 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中,会话可以是普通 JavaSE 环境,也可以是 Web 环境的; Cryptography : 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; Web Support : Web 支持,可以非常容易的集成到Web 环境; Caching : 缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率; Concurrency : 多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去; Testing : 提供测试支持; Run As : 允许一个用户假装为另一个用户(如果他们允许)的身份进行访问; Remember Me : 记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了 三、Shiro 架构3.1.Shiro外部来看    从外部来看Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成工作: Subject :应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外API 核心就是 Subject。Subject 代表了当前“用户”, 这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;与 Subject 的所有交互都会委托给 SecurityManager;Subject 其实是一个门面,SecurityManager 才是实际的执行者; SecurityManager : 安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色; Realm :Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource。 3.2.Shiro内部来看 Subject :任何可以与应用交互的“用户”; SecurityManager :相当于SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证、授权、会话及缓存的管理。 Authenticator :负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了; Authorizer :授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能; Realm :可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的 Realm SessionManager : 管理 Session 生命周期的组件;而 Shiro并不仅仅可以用在 Web环境,也可以用在如普通的 JavaSE 环境; CacheManager : 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能; Cryptography : 密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"shiro","slug":"java/shiro","permalink":"https://syshlang.com/categories/java/shiro/"}],"tags":[{"name":"Apache","slug":"Apache","permalink":"https://syshlang.com/tags/Apache/"},{"name":"Shiro","slug":"Shiro","permalink":"https://syshlang.com/tags/Shiro/"},{"name":"frame","slug":"frame","permalink":"https://syshlang.com/tags/frame/"}]},{"title":"SpringData组件之自定义Repository方法","slug":"study-SpringData-customize-Method","date":"2018-06-17T03:54:57.000Z","updated":"2020-09-17T00:34:15.264Z","comments":true,"path":"8f305ba7/","link":"","permalink":"https://syshlang.com/8f305ba7/","excerpt":"为某一个 Repository 上添加自定义方法 1、定义一个接口: 声明要添加的, 并自实现的方法; PersonDao.java PersonDao.java12345public interface PersonDao { //自定义方法 void test();}","text":"为某一个 Repository 上添加自定义方法 1、定义一个接口: 声明要添加的, 并自实现的方法; PersonDao.java PersonDao.java12345public interface PersonDao { //自定义方法 void test();} 2、提供该接口的实现类: 类名需在要声明的 Repository 后添加 Impl, 并实现方法; PersonRepsotoryImpl.java PersonRepsotoryImpl.java1234567891011//规约:命名必须符合声明使用的 Repsotory 接口名 + Implpublic class PersonRepsotoryImpl implements PersonDao { @PersistenceContext private EntityManager entityManager; @Override public void test() { Person person = entityManager.find(Person.class, 11); System.out.println("-->" + person); }} 3、声明 Repository 接口, 并继承自定义的接口; PersonRepsotory.java PersonRepsotory.java123public interface PersonRepsotory extends JpaRepository<Person, Integer>,PersonDao {} 注意:默认情况下, Spring Data 会在 base-package 中查找 “接口名Impl” 作为实现类,也可以通过 repository-impl-postfix 声明后缀,如果有两个以上类名为PersonRepsotoryImpl的类,Spring Data Jpa会抛出错误。 为所有的 Repository 都添加自实现的方法 1、声明一个接口, 在该接口中声明需要自定义的方法, 且该接口需要继承 Spring Data 的 Repository; CommonMethod.java CommonMethod.java123456@NoRepositoryBeanpublic interface CommonMethod<T, ID extends Serializable> extends JpaRepository<T, ID> { void method();} 2、提供所声明的接口的实现类. 且继承 SimpleJpaRepository, 并提供方法的实现; CommonMethodImpl.java CommonMethodImpl.java1234567891011121314@NoRepositoryBean CommonMethodImpl.javapublic class CommonMethodImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements CommonMethod<T, ID> { public CommonMethodImpl(Class<T> domainClass, EntityManager em) { super(domainClass, em); } @Override public void method() { System.out.println("...METHOD TEST..."); }} 3、定义 JpaRepositoryFactoryBean 的实现类, 使其生成 1) 定义的接口实现类的对象; CommonJpaRepositoryFactoryBean.java CommonJpaRepositoryFactoryBean.java12345678910111213141516171819202122232425262728public class CommonJpaRepositoryFactoryBean <T extends Repository<S, ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID> { protected RepositoryFactorySupport createRepositoryFactory( EntityManager entityManager) { return new CommonRepositoryFactory(entityManager); } private static class CommonRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory { private EntityManager entityManager; public CommonRepositoryFactory(EntityManager entityManager) { super(entityManager); this.entityManager = entityManager; } protected Object getTargetRepository(RepositoryMetadata metadata) { return new CommonMethodImpl<T, I>( (Class<T>) metadata.getDomainType(), entityManager); } protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { return CommonMethod.class; } }} 4、修改applicationContext.xml文件 <jpa:repositories/> 节点的 factory-class 属性指向CommonJpaRepositoryFactoryBean.java 的全类名; applicationContext.xml applicationContext.xml123456789... <!-- base-package: 扫描 Repository Bean 所在的 package --> <jpa:repositories base-package="com.syshlang" entity-manager-factory-ref="entityManagerFactory" factory-class="com.syshlang.repository.common.impl.CommonJpaRepositoryFactoryBean"> </jpa:repositories>... 注意:全局的扩展实现类不要用 Imp 作为后缀名, 或为全局扩展接口添加 @NoRepositoryBean 注解告知 Spring Data: Spring Data 不把其作为 Repository。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"framework","slug":"java/framework","permalink":"https://syshlang.com/categories/java/framework/"},{"name":"spring","slug":"spring","permalink":"https://syshlang.com/categories/spring/"},{"name":"springdata","slug":"spring/springdata","permalink":"https://syshlang.com/categories/spring/springdata/"}],"tags":[{"name":"Spring","slug":"Spring","permalink":"https://syshlang.com/tags/Spring/"},{"name":"SpringData","slug":"SpringData","permalink":"https://syshlang.com/tags/SpringData/"},{"name":"Repository","slug":"Repository","permalink":"https://syshlang.com/tags/Repository/"}]},{"title":"SpringData组件之Repository接口方法定义规范","slug":"study-SpringData-Method-definition-specification","date":"2018-06-05T02:06:57.000Z","updated":"2020-09-17T00:34:15.264Z","comments":true,"path":"70fbc0d8/","link":"","permalink":"https://syshlang.com/70fbc0d8/","excerpt":"简单条件查询      简单条件查询,查询某一个实体类或者集合,按照 Spring Data 的规范,查询方法以 find | read | get 开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写。      例如:定义一个 Entity 实体类: 1234class User{ private String firstName; private String lastName;}       使用And条件连接时,应这样写: 12//条件的属性名称与个数要与参数的位置与个数一一对应findByLastNameAndFirstName(String lastName,String firstName);","text":"简单条件查询      简单条件查询,查询某一个实体类或者集合,按照 Spring Data 的规范,查询方法以 find | read | get 开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写。      例如:定义一个 Entity 实体类: 1234class User{ private String firstName; private String lastName;}       使用And条件连接时,应这样写: 12//条件的属性名称与个数要与参数的位置与个数一一对应findByLastNameAndFirstName(String lastName,String firstName); 支持的关键字     直接在接口中定义查询方法,如果是符合规范的,可以不用写实现,目前支持的关键字写法如下: 查询方法解析流程     假如创建查询:findByUserDepUuid(),假设查询实体为Doc,框架在解析该方法时,步骤如下: 首先剔除 findBy,然后对剩下的属性进行解析; 先判断 userDepUuid (根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续下一步; 从右往左截取第一个大写字母开头的字符串(此处为Uuid),然后检查剩下的字符串是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复步骤,继续从右往左截取;最后假设 user 为查询实体的一个属性; 接着处理剩下部分(DepUuid),先判断 user 所对应的类型是否有depUuid属性,如果有,则表示该方法最终是根据 “ Doc.user.depUuid” 的取值进行查询;否则继续按照上一步骤的规则从右往左截取,最终表示根据 “Doc.user.dep.uuid” 的值进行查询; 可能会存在一种特殊情况,比如 Doc包含一个 user 的属性,也有一个 userDep 属性,此时会存在混淆。可以明确在属性之间加上 “_” 以显式表达意图,比如 “findByUser_DepUuid()” 或者 “findByUserDep_uuid()”。即支持属性的级联查询, 若当前类有符合条件的属性, 则优先使用, 而不使用级联属性;若需要使用级联属性, 则属性之间使用 _ 进行连接。 使用 @Query 注解使用@Query自定义查询     这种查询可以声明在 Repository 方法中,摆脱像命名查询那样的约束,将查询直接在相应的接口方法中声明,结构更为清晰,这是 Spring data 的特有实现。例如: 1234//查询 id 值最大的那个 Person//使用 @Query 注解可以自定义 JPQL 语句以实现更灵活的查询@Query("SELECT p FROM Person p WHERE p.id = (SELECT max(p2.id) FROM Person p2)")Person getMaxIdPerson(); 索引参数与命名参数 索引参数如下所示,索引值从1开始,查询中 ”?X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致。 123//为 @Query 注解传递参数的方式1: 使用占位符.@Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")List<Person> testQueryAnnotationParams1(String lastName, String email); 命名参数(推荐使用这种方式):可以定义好参数名,赋值时采用@Param(“参数名”),而不用管顺序。 123//为 @Query 注解传递参数的方式1: 命名参数的方式.@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")List<Person> testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName); 如果是 @Query 中有 LIKE 关键字,后面的参数需要前面或者后面加 %,这样在传递参数值的时候就可以不加 % 1234567//SpringData 允许在占位符上添加 %%.@Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")List<Person> testQueryAnnotationLikeParam(String lastName, String email);//SpringData 允许在占位符上添加 %%.@Query("SELECT p FROM Person p WHERE p.lastName LIKE %:lastName% OR p.email LIKE %:email%")List<Person> testQueryAnnotationLikeParam2(@Param("email") String email, @Param("lastName") String lastName); 还可以使用@Query来指定本地查询,只要设置nativeQuery为true,比如: 123456//设置 nativeQuery=true 即可以使用原生的 SQL 查询@Query(value="SELECT count(id) FROM jpa_persons", nativeQuery=true)long getTotalCount();@Query(value="select * from tbl_user where name like %?1" ,nativeQuery=true)public List<UserModel> findByUuidOrAge(String name); @Modifying 注解和事务@Query 与 @Modifying 执行更新操作 @Query 与 @Modifying 这两个 annotation一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最为常用,示例如下: 123456789101112/**** 可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT* 在 @Query 注解中编写 JPQL 语句, 但必须使用 @Modifying 进行修饰. 以通知 SpringData, 这是一个* UPDATE 或 DELETE 操作* UPDATE 或 DELETE 操作需要使用事务, 此时需要定义 Service 层. 在 Service 层的方法上添加事务操作.* 默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作!*/@Modifying@Query("UPDATE Person p SET p.email = :email WHERE id = :id")void updatePersonEmail(@Param("id") Integer id, @Param("email") String email); 注意:方法的返回值应该是 int,表示更新语句所影响的行数;在调用的地方必须加事务,没有事务不能正常执行 事务 Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务。 对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上注解 @Transactional 声明 。 进行多个 Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要在 Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。 PersonRepsotory.java PersonRepsotory.java123456789public interface PersonRepsotory extends JpaRepository<Person, Integer>, JpaSpecificationExecutor<Person>, PersonDao{ @Modifying @Query("UPDATE Person p SET p.email = :email WHERE id = :id") void updatePersonEmail(@Param("id") Integer id, @Param("email") String email);} PersonService.java PersonService.java1234567891011121314151617@Servicepublic class PersonService { @Autowired private PersonRepsotory personRepsotory; @Transactional public void savePersons(List<Person> persons){ personRepsotory.save(persons); } @Transactional public void updatePersonEmail(String email, Integer id){ personRepsotory.updatePersonEmail(id, email); }}","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"framework","slug":"java/framework","permalink":"https://syshlang.com/categories/java/framework/"},{"name":"spring","slug":"spring","permalink":"https://syshlang.com/categories/spring/"},{"name":"springdata","slug":"spring/springdata","permalink":"https://syshlang.com/categories/spring/springdata/"}],"tags":[{"name":"Spring","slug":"Spring","permalink":"https://syshlang.com/tags/Spring/"},{"name":"SpringData","slug":"SpringData","permalink":"https://syshlang.com/tags/SpringData/"},{"name":"Repository","slug":"Repository","permalink":"https://syshlang.com/tags/Repository/"}]},{"title":"SpringData组件之Repository接口概述","slug":"study-SpringData-Repository","date":"2018-06-04T12:54:57.000Z","updated":"2020-09-17T00:34:15.264Z","comments":true,"path":"a6a04339/","link":"","permalink":"https://syshlang.com/a6a04339/","excerpt":"前言    在《Spring框架组件之Spring Data》中,讲述Spring框架组件SpringData使用时提到了到Spring Data组件一个很重要的接口Repository接口,本篇文章在《Spring框架组件之Spring Data》的基础上着重讲述一下Repository接口。","text":"前言    在《Spring框架组件之Spring Data》中,讲述Spring框架组件SpringData使用时提到了到Spring Data组件一个很重要的接口Repository接口,本篇文章在《Spring框架组件之Spring Data》的基础上着重讲述一下Repository接口。 Repository 接口概述     Repository 接口是 Spring Data的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法; 1public interface Repository<T, ID extends Serializable> { }     Spring Data可以让我们自定义接口,只要遵循 Spring Data的规范,就无需写实现类,例如: 12public interface PersonRepsotory extends Repository<Person,Integer>{}     与继承 Repository 等价的一种方式,就是在持久层接口上使用 @RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性。如下两种方式是完全等价的,如下图 Repository 的子接口     基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下: -Repository:仅仅是一个标识,表明任何继承它的均为仓库接口类 -CrudRepository<:继承 Repository,实现了一组 CRUD 相关的方法 -PagingAndSortingRepository:继承 CrudRepository,实现了一组分页排序相关的方法 -JpaRepository:继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法 -自定义的 XxxxRepository:需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力 -JpaSpecificationExecutor:不属于Repository体系,实现一组 JPA Criteria 查询相关的方法。 CrudRepository 接口     CrudRepository 接口提供了最基本的对实体类的添删改查操作 T save(T entity); //保存单个实体 Iterable save(Iterable<? extends T> entities);//保存集合 T findOne(ID id);//根据id查找实体 boolean exists(ID id);//根据id判断实体是否 Iterable findAll();//查询所有实体,不用或慎用! long count();//查询实体数量 void delete(ID id);//根据Id删除实体 void delete(T entity);//删除一个实体 void delete(Iterable<? extends T> entities);//删除一个实体的集合 void deleteAll();//删除所有实体,不用或慎用! PagingAndSortingRepository接口     该接口提供了分页与排序功能 Iterable findAll(Sort sort); //排序 Page findAll(Pageable pageable); //分页查询(含排序功能) JpaRepository 接口     该接口提供了JPA的相关功能 List findAll(); //查找所有实体 List findAll(Sort sort); //排序、查找所有实体 List save(Iterable<? extends T> entities);//保存集合 void flush();//执行缓存与数据库同步 T saveAndFlush(T entity);//强制执行持久化 void deleteInBatch(Iterable entities);//删除一个实体集合 JpaSpecificationExecutor接口     该接口不属于Repository体系,它实现一组 JPA Criteria 查询相关的方法 -Specification:封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象 自定义 Repository 方法    为某一个 Repository 上添加自定义方法,详见《SpringData组件之自定义Repository方法》。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"framework","slug":"java/framework","permalink":"https://syshlang.com/categories/java/framework/"},{"name":"spring","slug":"spring","permalink":"https://syshlang.com/categories/spring/"},{"name":"springdata","slug":"spring/springdata","permalink":"https://syshlang.com/categories/spring/springdata/"}],"tags":[{"name":"Spring","slug":"Spring","permalink":"https://syshlang.com/tags/Spring/"},{"name":"SpringData","slug":"SpringData","permalink":"https://syshlang.com/tags/SpringData/"},{"name":"Repository","slug":"Repository","permalink":"https://syshlang.com/tags/Repository/"}]},{"title":"Spring框架组件之Spring Data","slug":"study-SpringData","date":"2018-06-03T02:54:57.000Z","updated":"2020-09-17T00:34:15.264Z","comments":true,"path":"8b6d97/","link":"","permalink":"https://syshlang.com/8b6d97/","excerpt":"前言     本篇文章主要记录本人在学习使用Spring框架组件Spring Data的过程中一些总结及感悟,学习过程中通过新建项目边学边练来加深理解","text":"前言     本篇文章主要记录本人在学习使用Spring框架组件Spring Data的过程中一些总结及感悟,学习过程中通过新建项目边学边练来加深理解 Spring Data 概述 Spring Data是Spring 的一个子项目。用于简化数据库访问,支持NoSQL和关系数据存储 。其主要目标是使数据库的访问变得方便快捷。 Spring Data 项目所支持NoSQL存储:    -MongoDB (文档数据库)    -Neo4j(图形数据库)    -Redis(键/值存储)    -Hbase(列族数据库) Spring Data 项目所支持的关系数据存储技术:    -JDBC    -JPA(本次采用) JPA Spring Data致力于减少数据访问层 (DAO) 的开发量. 开发者唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成! 框架怎么可能代替开发者实现业务逻辑呢?比如:当有一个 UserDao.findUserById() 这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User 对象。Spring Data JPA 做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。 搭建环境     练习项目采用Maven+Spring Data,具体jar包及环境配置此处略,可参照 Spring Data 、JPA进行持久层开发步骤     使用 Spring Data JPA 进行持久层开发需要的四个步骤: 配置 Spring 整合 JPA123456789101112131415161718192021222324252627282930313233343536373839404142434445464748<!-- 1. 配置数据源 --> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <!-- 配置其他属性 --> </bean> <!-- 2. 配置 JPA 的 EntityManagerFactory --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"></property> <!--jpa是是实现产品的适配器--> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean> </property> <!--扫描加注解的包--> <property name="packagesToScan" value="com.syshlang"></property> <property name="jpaProperties"> <props> <!-- 二级缓存相关 --> <!-- <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop> <prop key="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop> --> <!-- 生成的数据表的列的映射策略 --> <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> <!-- hibernate 基本属性 --> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> </bean> <!-- 3. 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"></property> </bean> <!-- 4. 配置支持注解的事务 --> <tx:annotation-driven transaction-manager="transactionManager"/> 在Spring 配置文件中配置 Spring Data    在Spring 配置文件中配置 Spring Data,让Spring为声明的接口创建代理对象。配置了 <jpa:repositories>后,Spring 初始化容器时将会扫描 base-package指定的包目录及其子目录,为继承 Repository 或其子接口的接口创建代理对象,并将代理对象注册为Spring Bean,业务层便可以通过Spring自动封装的特性来直接使用该对象。 123456<!-- 5. 配置 SpringData --> <!-- 加入 jpa 的命名空间 --> <!-- base-package: 扫描 Repository Bean 所在的 package --> <jpa:repositories base-package="com.syshlang" entity-manager-factory-ref="entityManagerFactory"> </jpa:repositories> 声明持久层的接口    声明持久层的接口,该接口继承Repository,Repository 是一个标记型接口,它不包含任何方法,如必要,Spring Data 可实现 Repository其他子接口,其中定义了一些常用的增删改查,以及分页相关的方法。 持久层的接口: 1234567891011121314/* * Copyright (c) 2018. GRGBanking * @File: PersonRepsotory.java * @Description: * @Author: sunys * @Date: 18-6-7 下午9:49 * @since: */package com.syshlang.repository;import com.syshlang.entity.Person;import org.springframework.data.repository.Repository;public interface PersonRepsotory extends Repository<Person,Integer>{} 实体类: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465/* * Copyright (c) 2018. GRGBanking * @File: Person.java * @Description: * @Author: sunys * @Date: 18-6-7 下午9:54 * @since: */package com.syshlang.entity;import javax.persistence.*;import java.util.Date;@Table(name="JPA_PERSONS")@Entitypublic class Person { private Integer id; private String lastName; private String email; private Date birth; @GeneratedValue @Id public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } @Override public String toString() { return "Person [id=" + id + ", lastName=" + lastName + ", email=" + email + ", brith=" + birth + "]"; }} Repository 是一个空接口. 即是一个标记接口; 若我们定义的接口继承了 Repository, 则该接口会被 IOC 容器识别为一个 Repository Bean.纳入到 IOC 容器中. 进而可以在该接口中定义满足一定规范的方法; 实际上, 也可以通过 @RepositoryDefinition 注解来替代继承 Repository 接口。 关于Repository接口相关概述参照《SpringData组件之Repository接口概述》。 在接口中声明需要的方法    在接口中声明需要的方法,Spring Data 将根据给定的策略来为其生成实现代码。 123456789101112131415161718/* * Copyright (c) 2018. GRGBanking * @File: PersonRepsotory.java * @Description: * @Author: sunys * @Date: 18-6-7 下午9:49 * @since: */package com.syshlang.repository;import com.syshlang.entity.Person;import org.springframework.data.repository.Repository;public interface PersonRepsotory extends Repository<Person,Integer>{ //根据 lastName 来获取对应的 Person Person getByLastName(String lastName);} 测试: 1234567 @Test public void testSpringData(){ PersonRepsotory personRepsotory = ctx.getBean(PersonRepsotory.class); Person person = personRepsotory.getByLastName("AA"); System.out.println(person);} 测试结果: 1234567891011Hibernate: select person0_.id as id1, person0_.birth as birth2, person0_.email as email3, person0_.last_name as last_nam4 from jpa_persons person0_ where person0_.last_name=? Person [id=1, lastName=AA, email=syshlang@163.com, brith=2018-06-07 13:42:02.0] 在 Repository 子接口中声明方法: 不是随便声明的. 而需要符合一定的规范; 查询方法以 find | read | get 开头; 涉及条件查询时,条件的属性用条件关键字连接; 要注意的是:条件属性以首字母大写; 支持属性的级联查询. 若当前类有符合条件的属性, 则优先使用, 而不使用级联属性; 若需要使用级联属性, 则属性之间使用 _ 进行连接。 关于Repository子接口中声明方法参照《SpringData组件之Repository接口方法定义规范》。 附:配置清单 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置自动扫描的包 --> <context:component-scan base-package="com.syshlang"></context:component-scan> <!-- 1. 配置数据源 --> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <!-- 配置其他属性 --> </bean> <!-- 2. 配置 JPA 的 EntityManagerFactory --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"></property> <!--jpa是是实现产品的适配器--> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean> </property> <!--扫描加注解的包--> <property name="packagesToScan" value="com.syshlang"></property> <property name="jpaProperties"> <props> <!-- 二级缓存相关 --> <!-- <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop> <prop key="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop> --> <!-- 生成的数据表的列的映射策略 --> <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> <!-- hibernate 基本属性 --> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> </bean> <!-- 3. 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"></property> </bean> <!-- 4. 配置支持注解的事务 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 5. 配置 SpringData --> <!-- 加入 jpa 的命名空间 --> <!-- base-package: 扫描 Repository Bean 所在的 package --> <jpa:repositories base-package="com.syshlang" entity-manager-factory-ref="entityManagerFactory"> </jpa:repositories></beans>","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"framework","slug":"java/framework","permalink":"https://syshlang.com/categories/java/framework/"},{"name":"spring","slug":"spring","permalink":"https://syshlang.com/categories/spring/"},{"name":"springdata","slug":"spring/springdata","permalink":"https://syshlang.com/categories/spring/springdata/"}],"tags":[{"name":"Spring","slug":"Spring","permalink":"https://syshlang.com/tags/Spring/"},{"name":"SpringData","slug":"SpringData","permalink":"https://syshlang.com/tags/SpringData/"}]},{"title":"使用HttpServletRequestWrapper重写Request请求参数","slug":"Rewriting-HttpServletRequestWrapper","date":"2018-05-04T00:36:36.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"79c4401c/","link":"","permalink":"https://syshlang.com/79c4401c/","excerpt":"前言     在上一篇文章中讲述到,在做一个PHP语言开发的系统集成JAVA语言开发的系统功能时遇到获取传参的问题,最终是使用**request.getInputStream()**方式使问题得以解决,但是同时又带来了新的问题,request.getInputStream()本身获取到的是request请求内容的字节流,然后使用IOUtils.toString(IOUtils.toString)转为字符串,这种方式无法满足我的Controller层(springMVC)参数与实例对象的映射,增加了业务层处理参数的复杂度。","text":"前言     在上一篇文章中讲述到,在做一个PHP语言开发的系统集成JAVA语言开发的系统功能时遇到获取传参的问题,最终是使用**request.getInputStream()**方式使问题得以解决,但是同时又带来了新的问题,request.getInputStream()本身获取到的是request请求内容的字节流,然后使用IOUtils.toString(IOUtils.toString)转为字符串,这种方式无法满足我的Controller层(springMVC)参数与实例对象的映射,增加了业务层处理参数的复杂度。 问题分析    既然request.getInputStream()获取参数无法满足Controller层需求,request.getParameter()方式可以满足但又无法获取参数,那何不先用request.getInputStream()获取到参数之后再重新设置request的参数以满足Controller层需求。但是,对于HttpServletRequest而言,貌似只有使用setAttribute(String name, Object o) 这个方法可以设置参数,经过尝试之后发现:使用 setAttribute(String name, Object o) 方法来重新设置参数不可行,因为在Controller中获取参数本质上还是调用的ServletRequest的public String getParameter(String name) 或者 public String[] getParameterValues(String name) 方法,了解一番之后,才明白需要使用装饰模式来复写这些方法才可行。因此,决定使用HttpServletRequestWrapper重写Request请求参数。 代码实现    Filter能在request到达servlet的服务方法之前拦截HttpServletRequest对象,而在服务方法转移控制后又能拦截HttpServletResponse对象,因此,可以使用filter来实现特定的任务————重写Request请求参数。 使用HttpServletRequestWrapper重写12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576/* * Copyright (c) 2017 GRGBanking * @File: ChangeRequestWrapper.java * @Description: * @Author: sunys * @Date: 18-4-24 下午3:31 * @since: * */package com.grgbanking.framework.core.common;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import java.util.Enumeration;import java.util.Map;import java.util.Vector;/** * Created by sunys on 2018/4/24. */public class ChangeRequestWrapper extends HttpServletRequestWrapper { private Map<String, String[]> parameterMap; // 所有参数的Map集合 public ChangeRequestWrapper(HttpServletRequest request) { super(request); parameterMap = request.getParameterMap(); } // 重写几个HttpServletRequestWrapper中的方法 /** * 获取所有参数名 * * @return 返回所有参数名 */ @Override public Enumeration<String> getParameterNames() { Vector<String> vector = new Vector<String>(parameterMap.keySet()); return vector.elements(); } /** * 获取指定参数名的值,如果有重复的参数名,则返回第一个的值 接收一般变量 ,如text类型 * * @param name 指定参数名 * @return 指定参数名的值 */ @Override public String getParameter(String name) { String[] results = parameterMap.get(name); if (results != null){ return results[0]; } return null; } /** * 获取指定参数名的所有值的数组,如:checkbox的所有数据 * 接收数组变量 ,如checkobx类型 */ @Override public String[] getParameterValues(String name) { return parameterMap.get(name); } @Override public Map<String, String[]> getParameterMap() { return parameterMap; } public void setParameterMap(Map<String, String[]> parameterMap) { this.parameterMap = parameterMap; }} web.xml增加一个过滤器处理web.xml12345678910...<filter> <filter-name>securityAccessFilter</filter-name> <filter-class>com.grgbanking.framework.core.common.SecurityAccessFilter</filter-class> </filter> <filter-mapping> <filter-name>securityAccessFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>...     所有的请求,都会验证此filter。 新增Filter实现特定的任务SecurityAccessFilter.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172/* * Copyright (c) 2017 GRGBanking * @File: SecurityAccessFilter.java * @Description: * @Author: sunys * @Date: 18-5-4 上午9:33 * @since: * */package com.grgbanking.framework.core.common;import com.grgbanking.framework.utils.AjaxUtils;import net.sf.json.JSONObject;import org.apache.commons.io.IOUtils;import org.apache.log4j.Logger;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.HashMap;import java.util.Map;/** * 过滤器,在web.xml中配置,指定对哪些路径进行过滤。 */public class SecurityAccessFilter implements Filter { private Logger logger = Logger.getLogger(SecurityAccessFilter.class); public void destroy() { } public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) arg0; HttpServletResponse response = (HttpServletResponse) arg1; String uri = request.getRequestURI(); if (uri.endsWith(".php")){ //首先获取参数字符串 String json = new String(IOUtils.toString(request.getInputStream())); ServletRequest arg_tem = changeParam(json,request,response); if(arg_tem != null){ //使用复写后的wrapper arg0 = arg_tem; } } arg2.doFilter(arg0, arg1); } private ServletRequest changeParam(String json, HttpServletRequest request, HttpServletResponse response) { try { System.out.println(json); // 调用ChangeRequestWrapper 改变参数 ChangeRequestWrapper changeRequestWrapper = new ChangeRequestWrapper(request); Map<String, String[]> parameterMap = new HashMap<>(changeRequestWrapper.getParameterMap()); JSONObject jsonObject = JSONObject.fromObject(json); String param = jsonObject.get("param").toString(); parameterMap.put("param",new String[]{param}); changeRequestWrapper.setParameterMap(parameterMap); return changeRequestWrapper; } catch (Exception e) { e.printStackTrace(); AjaxUtils.renderFailureOther("解析请求参数时发生异常!", request,response); return null; } } public void init(FilterConfig arg0) throws ServletException { }}","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"Request","slug":"Request","permalink":"https://syshlang.com/tags/Request/"},{"name":"HttpServletRequestWrapper","slug":"HttpServletRequestWrapper","permalink":"https://syshlang.com/tags/HttpServletRequestWrapper/"}]},{"title":"PHP、JAVA关于post请求数据接收的处理","slug":"PHP-JAVA-on-the-processing-of-post-request-data-reception","date":"2018-05-03T08:08:51.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"9a4c71ab/","link":"","permalink":"https://syshlang.com/9a4c71ab/","excerpt":"背景      最近在做项目时遇到这样一个问题,一个客户公司的智能柜管理系统想集成我们公司开发的电子锁系统,我负责相关接口的对接及开发,在一切准备工作就绪之后,进行接口联调时遇到传参问题的困扰。对方系统是采用PHP语言开发,我方系统是采用JAVA语言开发,接口约定采用post请求方式,以json格式传输数据,但是在实际接口联调中,PHP端使用curl请求JAVA接口时的传参JAVA端接口始终无法获取到,对方经过检查json数据格式没问题,但是我采用ajax请求JAVA端接口时可以获取到参数,于是推断是不同语言传输和接受参数的方式问题!","text":"背景      最近在做项目时遇到这样一个问题,一个客户公司的智能柜管理系统想集成我们公司开发的电子锁系统,我负责相关接口的对接及开发,在一切准备工作就绪之后,进行接口联调时遇到传参问题的困扰。对方系统是采用PHP语言开发,我方系统是采用JAVA语言开发,接口约定采用post请求方式,以json格式传输数据,但是在实际接口联调中,PHP端使用curl请求JAVA接口时的传参JAVA端接口始终无法获取到,对方经过检查json数据格式没问题,但是我采用ajax请求JAVA端接口时可以获取到参数,于是推断是不同语言传输和接受参数的方式问题! 分析JAVA接受post请求数据方式     >Java的servlet中接收Post请求数据主要采用两种方式: (1) request.getParameter();(2) request.getInputStream();      Content-Type仅在取值为application/x-www-data-urlencoded和multipart/form-data两种情况下,request.getParameter()才能获取到值,否则返回空。Content-Type为其他类型时,一般采用request.getInputStream()方式获取,如下: 12String json = org.apache.commons.io.IOUtils.toString(request.getInputStream());System.out.println(json);      于是改用request.getInputStream()方式,问题得到解决,该方式返回request请求内容的字节流,转为字符串之后对取到的值进行处理,如果需要改变请求参数的值或者改变获取参数的方式满足Controller层的需求,可以通过使用HttpServletRequestWrapper重写Request请求参数来达到目的。 request的Content-Type小结     application/x- www-form-urlencoded是Post请求默认的请求体内容类型,也是form表单默认的类型。Servlet API规范中对该类型的请求内容提供了request.getParameter()方法来获取请求参数值。但当请求内容不是该类型时,需要调用request.getInputStream()或request.getReader()方法来获取请求内容值。     当请求体内容(注意:get请求没有请求体)类型是application/x- www-form-urlencoded时也可以直接调用request.getInputStream()或request.getReader()方法获取到请求内容再解析出具体都参数,但前提是还没调用request.getParameter()方法。此时当request.getInputStream()或request.getReader()获取到请求内容后,无法再调request.getParameter()获取请求内容。即对该类型的请求,三个方法互斥,只能调其中一个。今天遇到一个Controller请求经过Spring MVC 的RequestMapping处理后,只能通过request.getParameter()获取到参数、无法通过request.getInputStream()和request.getReader()读取内容很可能就是因为在请求经过Spring MVC时已调用过request.getParameter()方法的原因。 注意:在一个请求链中,请求对象被前面对象方法中调用request.getInputStream()或request.getReader()获取过内容后,后面的对象方法里再调用这两个方法也无法获取到客户端请求的内容,但是调用request.getParameter()方法获取过内容后,后面的对象方法里依然可以调用它获取到参数的内容。      当请求体内容是其它类型时,比如 multipart/form-data或application/json时,无法通过request.getParameter()获取到请求内容,此时只能通过request.getInputStream()和request.getReader()方法获取请求内容,此时调用request.getParameter()也不会影响第一次调用request.getInputStream()或request.getReader()获取到请求内容。request.getInputStream()返回请求内容字节流,多用于文件上传,request.getReader()是对前者返回内容的封装,可以让调用者更方便字符内容的处理(不用自己先获取字节流再做字符流的转换操作)。 普及PHP知识PHP接收post请求数据方式     PHP接收post请求数据主要采用两种方式: (1) $_POST[index]方式(2) $data = file_get_contents(“php://input”);      Content-Type仅在取值为application/x-www-data-urlencoded和multipart/form-data两种情况下,PHP才会将http请求数据包中相应的数据填入全局变量$_POST。因此,如果使用$_POST来获取post过来的数据时,需要注意Content-Type类型,如果不是application/x-www-data-urlencoded和multipart/form-data,则采用file_get_contents(“php://input”);进行获取。      php://input 可以读取http entity body中指定长度的值,由Content-Length指定长度,不管是POST方式或者GET方法提交过来的数据。但是,一般GET方法提交数据 时,http request entity body部分都为空。例如在传递json串的时候,通过file_get_contents(“php://input”);获取原始串,然后通过json_decode()进行解析。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"}],"tags":[{"name":"PHP","slug":"PHP","permalink":"https://syshlang.com/tags/PHP/"},{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"post","slug":"post","permalink":"https://syshlang.com/tags/post/"}]},{"title":"Git使用问题总结及解决方案","slug":"Question-summary-for-Git","date":"2018-04-17T02:38:31.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"1523b56c/","link":"","permalink":"https://syshlang.com/1523b56c/","excerpt":"前言      Git(读音为/gɪt/。)是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理。 [1] Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,分布式相比于集中式的最大区别在于开发者可以提交到本地,每个开发者通过克隆(git clone,在本地机器上拷贝一个完整的Git仓库。不必服务器端软件支持(wingeddevil注:这得分是用什么样的服务端,使用http协议或者git协议等不太一样。并且在push和pull的时候和服务器端还是有交互的。),使源代码的发布和交流极其方便。      本篇文章专门用来记录本人在使用git过程中遇到的一些问题及解决方案,长期更新。","text":"前言      Git(读音为/gɪt/。)是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理。 [1] Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,分布式相比于集中式的最大区别在于开发者可以提交到本地,每个开发者通过克隆(git clone,在本地机器上拷贝一个完整的Git仓库。不必服务器端软件支持(wingeddevil注:这得分是用什么样的服务端,使用http协议或者git协议等不太一样。并且在push和pull的时候和服务器端还是有交互的。),使源代码的发布和交流极其方便。      本篇文章专门用来记录本人在使用git过程中遇到的一些问题及解决方案,长期更新。 FQA关键字:error: RPC failed问题      最近从Github上获取一份代码,在使用终端clone的时候,屡次出现如下错误: 1234error: RPC failed; curl 18 transfer closed with outstanding read data remainingfatal: The remote end hung up unexpectedlyfatal: early EOFfatal: index-pack failed 原因说明 我们的项目由于时代久远,所以导致整个项目比较复杂庞大。出现这种错误,就是因为curl的postBuffer默认值太小原因,重新在终端配置一下这个值就可以了。 解决方法 重新在终端配置postBuffer的值。 12git config –global http.postBuffer 524288000 #524288000代表B,524288000B也就是500MB。这个值得大小,可以根据项目酌情设置。git config –list #查看是否配置成功 关键字:回滚 恢复问题 git reset –hard 回滚到之前的版本以后,怎么撤销这一步回滚操作? 解决方法 可以通过reflog来进行恢复,前提是丢失的分支或commit信息没有被git gc清除,一般情况下,gc对那些无用的object会保留很长时间后才清除的,可以使用git reflog show或git log -g命令来看到所有的操作日志,恢复的过程很简单: 通过git log -g命令来找到需要恢复的信息对应的commitid,可以通过提交的时间和日期来辨别,找到执行reset –hard之前的那个commit对应的commitid; 通过git branch recover_branch commitid 来建立一个新的分支。 这样,就把到commitid为止的代码、各种提交记录等信息都恢复到了recover_branch分支上了。执行下面几个命令即可,详见git reset。 123git refloggit checkoutgit branch 关键字:命令 Git最详细命令 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970git help <command> # 显示command的helpgit show # 显示某次提交的内容 git show $idgit co -- <file> # 抛弃工作区修改git co . # 抛弃工作区修改git add <file> # 将工作文件修改提交到本地暂存区git add . # 将所有修改过的工作文件提交暂存区git rm <file> # 从版本库中删除文件git rm <file> --cached # 从版本库中删除文件,但不删除文件git reset <file> # 从暂存区恢复到工作文件git reset -- . # 从暂存区恢复到工作文件git reset --hard # 恢复最近一次提交过的状态,即放弃上次提交后的所有本次修改git ci <file> git ci . git ci -a # 将git add, git rm和git ci等操作都合并在一起做git ci -am "some comments"git ci --amend # 修改最后一次提交记录git revert <$id> # 恢复某次提交的状态,恢复动作本身也创建次提交对象git revert HEAD # 恢复最后一次提交的状态git diff <file> # 比较当前文件和暂存区文件差异 git diffgit diff <id1><id1><id2> # 比较两次提交之间的差异git diff <branch1>..<branch2> # 在两个分支之间比较git diff --staged # 比较暂存区和版本库差异git diff --stat # 仅仅比较统计信息git log git log <file> # 查看该文件每次提交记录git log -p <file> # 查看每次详细修改内容的diffgit log -p -2 # 查看最近两次详细修改内容的diffgit log --stat #查看提交统计信息git br -r # 查看远程分支git br <new_branch> # 创建新的分支git br -v # 查看各个分支最后提交信息git br --merged # 查看已经被合并到当前分支的分支git br --no-merged # 查看尚未被合并到当前分支的分支git co <branch> # 切换到某个分支git co -b <new_branch> # 创建新的分支,并且切换过去git co -b <new_branch> <branch> # 基于branch创建新的new_branchgit co $id # 把某次历史提交记录checkout出来,但无分支信息,切换到其他分支会自动删除git co $id -b <new_branch> # 把某次历史提交记录checkout出来,创建成一个分支git br -d <branch> # 删除某个分支git br -D <branch> # 强制删除某个分支 (未被合并的分支被删除的时候需要强制)git merge <branch> # 将branch分支合并到当前分支git merge origin/master --no-ff # 不要Fast-Foward合并,这样可以生成merge提交git diff > ../sync.patch # 生成补丁git apply ../sync.patch # 打补丁git apply --check ../sync.patch #测试补丁能否成功git stash # 暂存git stash list # 列出所有stashgit stash apply # 恢复暂存的内容git stash drop # 删除暂存区git pull # 抓取远程仓库所有分支更新并合并到本地git pull --no-ff # 抓取远程仓库所有分支更新并合并到本地,不要快进合并git fetch origin # 抓取远程仓库更新git merge origin/master # 将远程主分支合并到本地当前分支git co --track origin/branch # 跟踪某个远程分支创建相应的本地分支git co -b <local_branch> origin/<remote_branch> # 基于远程分支创建本地分支,功能同上git push # push所有分支git push origin master # 将本地主分支推到远程主分支git push -u origin master # 将本地主分支推到远程(如无远程主分支则创建,用于初始化远程仓库)git push origin <local_branch> # 创建远程分支, origin是远程仓库名git push origin <local_branch>:<remote_branch> # 创建远程分支git push origin :<remote_branch> #先删除本地分支(git br -d <branch>),然后再push删除远程分支git remote -v # 查看远程服务器地址和仓库名称git remote show origin # 查看远程服务器仓库状态git remote add origin git@ github:robbin/robbin_site.git # 添加远程仓库地址git remote set-url origin git@ github.com:robbin/robbin_site.git # 设置远程仓库地址(用于修改远程仓库地址)git remote rm <repository> # 删除远程仓库git clone --bare robbin_site robbin_site.git # 用带版本的项目创建纯版本仓库scp -r my_project.git git@ git.csdn.net:~ # 将纯仓库上传到服务器上mkdir robbin_site.git && cd robbin_site.git && git --bare init # 在服务器创建纯仓库git remote add origin git@ github.com:robbin/robbin_site.git # 设置远程仓库地址git push -u origin master # 客户端首次提交git push -u origin develop # 首次将本地develop分支提交到远程develop分支,并且trackgit remote set-head origin master # 设置远程仓库的HEAD指向master分支 关键字: 分支 合并 主干问题 如何使用git将分支branch合并到主干master上? 解决方法 假设现在有2个分支: master和 hexo-next 1.使用如下命令从当前分支切换到主干master上 1git checkout master 2.合并某个分支到主干master 1git merge <branchName> 3.最重要的一步 push代码 1git push origin","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"git","slug":"git","permalink":"https://syshlang.com/categories/git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"https://syshlang.com/tags/Git/"},{"name":"FQA","slug":"FQA","permalink":"https://syshlang.com/tags/FQA/"}]},{"title":"Linux系统下配置网关","slug":"Gateway_Configuration_on_Linux_Systems","date":"2018-03-13T05:56:40.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"9d41370/","link":"","permalink":"https://syshlang.com/9d41370/","excerpt":"讲解Linux系统下配置网关的基本操作,以CentOS 7 64位系统为例","text":"讲解Linux系统下配置网关的基本操作,以CentOS 7 64位系统为例 检查虚拟网卡这个硬件设备 ①:查看虚拟网卡这个硬件设备具体型号 1[root@localhost]# lspci |grep Ethernet 可以查看到该主机网卡为Intel 82545EM 千兆以太网卡 ②:查看目前网卡驱动程序版本信息 12[root@localhost]# ethtool -i ethx[root@localhost]# ifconfig eth0 提示:eth0: error fetching interface information: Device not found判断应该是没有安装网卡驱动 安装网卡驱动 ①:前往Intel官网下载Intel 82545EM 千兆以太网卡驱动程序,http://www.intel.cn ②:下载完成后,依次执行命令解压安装驱动包 12[root@localhost]# tar zxf e1000-8.0.35.tar.gz[root@localhost]# cd e1000-8.0.35/src/ && make install 注:此驱动程序为源代码的程序包,安装前确认系统kernel-headers、kernel-firmware、kernel-devel三个软件包已经安装,且版本一定要相同,以及编译工具gcc、gcc-c++已经安装,如果没有安装,请配置正确的yum源后使用yum命令来安装 配置网络①:查看路由表 1[root@localhost]# route -n ②:发现里面并没有我想要的网关,所以就要添加一个 1[root@localhost]# route add 10.10.88.203 dev eth0 ③:重启network 1[root@localhost]# service network restart","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"OS","slug":"OS","permalink":"https://syshlang.com/categories/OS/"},{"name":"linux","slug":"OS/linux","permalink":"https://syshlang.com/categories/OS/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"https://syshlang.com/tags/linux/"},{"name":"System","slug":"System","permalink":"https://syshlang.com/tags/System/"},{"name":"network","slug":"network","permalink":"https://syshlang.com/tags/network/"},{"name":"Gateway","slug":"Gateway","permalink":"https://syshlang.com/tags/Gateway/"}]},{"title":"RFID射频识别技术预研","slug":"RFID_Radio_Frequency_Identification_Technology","date":"2018-01-11T08:24:22.000Z","updated":"2020-09-04T03:40:48.169Z","comments":true,"path":"35059db1/","link":"","permalink":"https://syshlang.com/35059db1/","excerpt":"预研背景     公司预计将采用RFID技术来管理电动车。 何谓RFID?     无线射频识别技术(RFID,Radio Frequency Identification)是一种非接触的自动识别技术,其基本原理是利用射频信号和空间耦合(电感或者电磁耦合)传输特性,实现对被识别物体的自动识别。我们称无线射频识别技术也为电子标签系统。","text":"预研背景     公司预计将采用RFID技术来管理电动车。 何谓RFID?     无线射频识别技术(RFID,Radio Frequency Identification)是一种非接触的自动识别技术,其基本原理是利用射频信号和空间耦合(电感或者电磁耦合)传输特性,实现对被识别物体的自动识别。我们称无线射频识别技术也为电子标签系统。 RFID系统组成    RFID系统组成如下图  主要有三部分: 天线(Antenna , or coil) 读取器(Reader , transceiver) 感应标签(Transponder, or RF Tag, 或硅芯片)     RFID 读取流程如下图","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"RFID","slug":"technology/RFID","permalink":"https://syshlang.com/categories/technology/RFID/"}],"tags":[{"name":"RFID","slug":"RFID","permalink":"https://syshlang.com/tags/RFID/"},{"name":"电动车","slug":"电动车","permalink":"https://syshlang.com/tags/%E7%94%B5%E5%8A%A8%E8%BD%A6/"}]},{"title":"应用系统中的异常处理","slug":"Application_system_exception_handling","date":"2018-01-03T02:19:34.000Z","updated":"2020-09-17T00:34:15.244Z","comments":true,"path":"1fe23ddd/","link":"","permalink":"https://syshlang.com/1fe23ddd/","excerpt":"背景 某次安防项目的晨会上。 质管小A:“安防项目今天突然不能播放视频了,点击播放按钮,提示失败,是不是底层C端出问题了!” 底层C端研发负责人小B:“没有啊,怎么可能是我们C端的问题,昨天还是好好的,是不是业务系统出问题了!” 业务系统负责人小C:“。。。。。。小明,小强你们两个排查下问题!” 小明,小强经过一阵排查,发现播放视频的配置参数被修改了,没有填写端口号。","text":"背景 某次安防项目的晨会上。 质管小A:“安防项目今天突然不能播放视频了,点击播放按钮,提示失败,是不是底层C端出问题了!” 底层C端研发负责人小B:“没有啊,怎么可能是我们C端的问题,昨天还是好好的,是不是业务系统出问题了!” 业务系统负责人小C:“。。。。。。小明,小强你们两个排查下问题!” 小明,小强经过一阵排查,发现播放视频的配置参数被修改了,没有填写端口号。 分析     从以上的事件中可以看出,问题的关键在于系统对于异常情况的处理不到位,并且对于出现问题的提示信息过于片面化,导致问题出现时,无法迅速定位问题所在。我们开发的业务系统,或者是产品,常常面临着这样的问题: 系统运行出错,但是完全不知道错误发生的位置; 我们找到了错误的位置,但是完全不知道是因为什么; 系统明明出了错误,但是就是看不到错误堆栈信息。     由此,J2EE Web应用系统中的异常处理显得尤为重要,优雅的以正确的方式处理异常,不仅可以提高系统健壮性的,并且还可以有效的帮助我们速度的定位排查问题。 异常的分类从异常的分类结构看     由异常的分类结构图可以看出:Throwable类有两个直接子类java.lang.Error和java.lang.Exception,Error是无法处理的异常,一般发生这种异常,JVM会选择终止程序,编写程序时不需要关心;Exception是常见异常情况,这些异常是我们可以处理的异常,是所有异常类的父类。 从异常的受检查情况来看     从异常的受检查情况来看,可捕获的异常又可以分为两类:check异常(受查异常(checked exception))和runtime异常(非受查异常(unchecked exception))。对于非受查异常,派生自RuntimeException的异常类,对于此类异常java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定;对于受查异常,直接派生自Exception的异常类,java编译器强制程序员必须进行捕获处理,否则编译都不通过。 从系统的应用角度来看 (1) 系统级异常————与应用业务逻辑无关,需要有JVM系统来处理的异常; (2) 应用级异常————由于用户违背了商业业务逻辑而导致的错误,这种错误一般不是致命的,需要由应用系统程序本身处理。 Web应用中的异常处理机制和实现技术异常的处理机制 Web应用中的异常处理 (1) 不要让用户看到原始的Java异常信息,也是就说禁止将异常的堆栈信息抛出到页面上;(2) 可以将原始的Java异常信息记录到日志文件中或者输出到控制台,这样有助于程序调试中的错误定位;(3) 在分层系统实现中的异常处理规则:    ① 在分层系统实现中,下层系统向上层系统报告异常错误时,通常采用抛出自定义异常的方式实现,这样以便统一系统中不同的异常类型,例如,在项目中定义一个 AppException,然后向上层系统抛出这个异常: 12345public class AppException extends RuntimeException{ public AppException(String message){ super(message); }}     ② 设计自定义异常时,一般让自定义异常直接继承RuntimeException(非受查异常),从而使得上层的系统代码不必进行与异常相关的处理,这样也就使得上层程序代码不必依赖于下层的程序代码实现,这在一定程度上降低了代码的耦合度;    ③ 在控制层组件中捕获用户自定义的异常,在表示层组件中处理未捕获的异常,例如,定义错误页面等;    ④ 根据不同的业务场景或层级定义不同的异常类,例如,我们定义ServiceException异常类,用来表示业务逻辑受理失败,它仅表示我们处理业务的时候发现无法继续执行下去; 12345public class ServiceException extends AppException{ public ServiceException(String message){ super(message); }} (4)利用框架的相关机制处理异常,例如,spring为我们提供了ControllerAdvice机制,进行全局的 Controller 层异常处理: 1234567891011121314/*** @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理,* 不用在 Controller 层进行 try-catch**/@ControllerAdvicepublic class GlobalExceptionHandler { @ExceptionHandler({ ServiceException.class }) @ResponseBody public AppResponse handleException(Exception e) { }} (5) 异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多,如果考虑效率的话,可以重写Throwable的fillStackTrace方法,fillStackTrace是一个native方法,会填充异常类内部的运行轨迹;(6) 不要用异常进行业务逻辑处理,我们提倡在业务处理的时候,如果发现无法处理直接抛出异常即可。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"system","slug":"system","permalink":"https://syshlang.com/categories/system/"},{"name":"framework","slug":"system/framework","permalink":"https://syshlang.com/categories/system/framework/"}],"tags":[{"name":"Exception","slug":"Exception","permalink":"https://syshlang.com/tags/Exception/"},{"name":"系统","slug":"系统","permalink":"https://syshlang.com/tags/%E7%B3%BB%E7%BB%9F/"}]},{"title":"系统架构演进","slug":"System_architecture_evolution","date":"2017-10-16T03:44:48.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"f502a05/","link":"","permalink":"https://syshlang.com/f502a05/","excerpt":"系统架构演进首先,给个图。。。","text":"系统架构演进首先,给个图。。。     从以上的演进图中,我们可以很清晰的看出:平台随着业务的发展从 All in One 环境就可以满足业务需求(以Java来说,可能只是一两个war包就解决了);发展到需要拆分多个应用,并且采用MVC的方式分离前后端,加快开发效率;在发展到服务越来越多,不得不将一些核心或共用的服务拆分出来,提供实时流动监控计算等,其实发展到此阶段,如果服务拆分的足够精细,并且独立运行,这个时候至少可以理解为SOA架构了。特点概述: 单一应用架构 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键。 垂直应用架构 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的 Web框架(MVC) 是关键。 分布式服务架构 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键 流动计算架构 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的 资源调度和治理中心(SOA) 是关键。","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"system","slug":"system","permalink":"https://syshlang.com/categories/system/"},{"name":"framework","slug":"system/framework","permalink":"https://syshlang.com/categories/system/framework/"}],"tags":[{"name":"框架","slug":"框架","permalink":"https://syshlang.com/tags/%E6%A1%86%E6%9E%B6/"},{"name":"Dubbox","slug":"Dubbox","permalink":"https://syshlang.com/tags/Dubbox/"}]},{"title":"SpringMVC和mybatis的整合","slug":"Springmvc_and_mybatis_integration","date":"2017-07-12T15:42:46.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"820c489b/","link":"","permalink":"https://syshlang.com/820c489b/","excerpt":"SpringMVC+mybaits的系统架构","text":"SpringMVC+mybaits的系统架构 SpringMVC+mybaits整合步骤第一步:整合dao层     在doDispatch()方法中调用了DispatcherServlet类的getHandler方法。映射器根据request当中的URL,找到了Handler,最终返回一个执行器的链(HandlerExecutionChain)。这个链里面有Handler。配置mybatis的全局配置文件:sqlMapConfig.xml sqlMapConfig.xml123456789101112131415161718192021222324252627282930<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!--mybatis的全局配置文件--><!-- 全局setting配置,根据需要添加 --><!-- 配置别名 --><typeAliases> <!-- 批量扫描别名 --> <package name="com.syshlang.smm.pojo"/></typeAliases><!-- 配置mapper由于使用spring和mybatis的整合包进行mapper扫描,这里不需要配置了。必须遵循:mapper.xml和mapper.java文件同名且在一个目录 --><!--<mappers> 通过mapper元素的resource属性可以指定一个相对于类路径的Mapper.xml文件 <mapper resource="com/syshlang/smm/mapping/ItemsMapper.xml"/> 通过mapper元素的url属性可以指定一个通过URL请求道的Mapper.xml文件 <mapper url="file:///E:\\Workspaces\\Intellij\\SpringMM\\src\\main\\resources\\com\\syshlang\\smm\\mapping\\ItemsMapper.xml"/> 通过mapper元素的class属性可以指定一个Mapper接口进行注册 <mapper class="com.syshlang.smm.mapper.ItemsMapper"/> 通过package元素将会把指定包下面的所有Mapper接口进行注册 <package name="com.syshlang.smm.mapper"/></mappers>--></configuration> 配置spring和mybatis整合的配置文件:applicationContext-dao.xml主要包括数据源、SqlSessionFactory、mapper扫描器等 applicationContext-dao.xml12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- 加载db.properties文件中的内容,db.properties文件中key命名要有一定的特殊规则 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 配置数据源 ,dbcp --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="30" /> <property name="maxIdle" value="5" /> </bean> <!-- sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 数据库连接池 --> <property name="dataSource" ref="dataSource" /> <!-- 加载mybatis的全局配置文件 --> <property name="configLocation" value="classpath:config/mybatis/sqlMapConfig.xml" /> <property name="mapperLocations" value="classpath:com/syshlang/smm/mapping/*Mapper.xml"/> <!--分页插件--> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <value> dialect=mysql reasonable=true </value> </property> </bean> </array> </property> </bean> <!-- mapper扫描器 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 扫描包路径,如果需要扫描多个包,中间使用半角逗号隔开 --> <property name="basePackage" value="com.syshlang.smm.mapper"></property> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean></beans> 第二步:整合service层     通过spring管理 service接口,使用配置方式将service接口配置在spring配置文件中,实现事务控制。配置service:applicationContext-service.xml applicationContext-service.xml12345678910111213141516<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <bean id="itemsserviceimpl" class="com.syshlang.smm.service.impl.ItemsServiceImpl" /></beans> 使用spring声明事务控制方法,配置事务控制:applicationContext-transaction.xml applicationContext-transaction.xml12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- 事务管理器 对mybatis操作数据库事务控制,spring使用jdbc的事务控制类 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 数据源 dataSource在applicationContext-dao.xml中配置了 --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 传播行为 --> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"/> <tx:method name="get*" propagation="SUPPORTS" read-only="true"/> <tx:method name="select*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> <!-- aop --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.syshlang.smm.service.impl.*.*(..))"/> </aop:config> <!-- <aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* com.syshlang.smm.service.impl.*.*Dao.*(..))" /> <aop:advisor pointcut-ref="transactionPointcut" advice-ref="txAdvice" /> </aop:config>--></beans> 第三步:整合SpringMVC     由于SpringMVC是spring的模块,不需要整合,只需配置spring-mvc.xml文件,包括处理器映射器、适配器、视图解析器等。配置:spring-mvc.xml spring-mvc.xml12345678910111213141516171819202122232425262728293031323334353637<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- 自动扫描包,实现支持注解的IOC --> <context:component-scan base-package="com.syshlang.smm" /> <!-- Spring MVC不处理静态资源 --> <mvc:default-servlet-handler /> <!--注解映射器 支持mvc注解驱动--> <mvc:annotation-driven /> <!-- 扫描指定controller的包--> <!--<context:component-scan base-package="com.syshlang.smm.controller"></context:component-scan>--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 配置jsp路径的前缀 <property name="prefix" value="/WEB-INF/jsp/" /> 配置jsp路径的后缀 <property name="suffix" value=".jsp" /> --> </bean></beans>","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"framework","slug":"java/framework","permalink":"https://syshlang.com/categories/java/framework/"},{"name":"spring","slug":"spring","permalink":"https://syshlang.com/categories/spring/"},{"name":"springmvc","slug":"spring/springmvc","permalink":"https://syshlang.com/categories/spring/springmvc/"}],"tags":[{"name":"Spring","slug":"Spring","permalink":"https://syshlang.com/tags/Spring/"},{"name":"SpringMVC","slug":"SpringMVC","permalink":"https://syshlang.com/tags/SpringMVC/"},{"name":"mybatis","slug":"mybatis","permalink":"https://syshlang.com/tags/mybatis/"}]},{"title":"SpringMvc配置详解及源码分析","slug":"Detailed_SpringMVC_configuration","date":"2017-07-10T14:54:07.000Z","updated":"2020-09-17T00:34:15.244Z","comments":true,"path":"375e3f9d/","link":"","permalink":"https://syshlang.com/375e3f9d/","excerpt":"SpringMVC配置详解配置前端控制器DispatcherServlet      SpringMVC是一个基于DispatcherServlet的MVC框架,每一个请求最先访问的都是DispatcherServlet,DispatcherServlet是继承自HttpServlet的,DispatcherServlet负责转发每一个Request请求给相应的Handler,Handler处理以后再返回相应的视图(View)和模型(Model),返回的视图和模型都可以不指定,即可以只返回Model或只返回View或都不返回。","text":"SpringMVC配置详解配置前端控制器DispatcherServlet      SpringMVC是一个基于DispatcherServlet的MVC框架,每一个请求最先访问的都是DispatcherServlet,DispatcherServlet是继承自HttpServlet的,DispatcherServlet负责转发每一个Request请求给相应的Handler,Handler处理以后再返回相应的视图(View)和模型(Model),返回的视图和模型都可以不指定,即可以只返回Model或只返回View或都不返回。      首先,在web.xml文件中声明DispatcherServlet: web.xml1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!--<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/applicationContext.xml, classpath:springmvc.xml, </param-value> </context-param>--> <!--<context-param> <param-name>log4jConfigLocation</param-name> <param-value>log4j.properties</param-value> </context-param>--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--SpringMVC前端控制器--> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- contextConfigLocation 配置SpringMVC加载的配置文件(配置处理器映射器、适配器等) 如果不配置,默认加载的是/WEB_INF/servlet名称-servlet.xml(dispatcher-servlet.xml) --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <!-- 1)load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其 init()方法)。 2)它的值必须是一个整数,表示servlet应该被载入的顺序 2)当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet; 3)当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。 4)正数的值越小,该servlet的优先级越高,应用启动时就越先加载 5)当值相同时,容器就会自己选择顺序来加载。 所以,<load-on-startup>x</load-on-startup>, 中x的取值1,2,3,4,5代表的是优先级,而非启动延迟时间。 --> <load-on-startup>1</load-on-startup> <!-- <async-supported>子标签,该标签的默认取值为false, 要启用异步处理支持,则将其设为true即可 --> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <!-- ① *.action,访问以.action结尾的由DispatcherServlet进行解析; ② /,所有/的地址都由DispatcherServlet进行解析, 对于静态的文件的解析需要配置不让DispatcherServlet进行解析, 使用此种风格可以实现RESTFull风格的url解析; ③ /*,这样配置错误,使用这种配置时,最终要转发到jsp页面时, 仍然会由DsipatcherServlet解析jsp地址,不能根据jsp页面找到handler,会报错 --> <url-pattern>*.action</url-pattern> </servlet-mapping> <!--welcome pages--> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list></web-app> 配置处理器适配器、映射器、视图解析器等     然后,在classpath下的spring-mvc.xml中配置处理器适配器: spring-mvc.xml123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 说明:前端控制器加载处理映射器、适配器、视图解析器等组件,如果不在spring-mvc.xml中配置,则会自动使用spring-webmvc-4.3.1.RELEASE.jar包中org\\springframework.web.servlet中DispatcherSerlvet.properties配置文件中的默认配置。 --> <!--启用spring的一些annotation --> <!--<context:annotation-config/>--> <!-- ****************配置Handler 开始*********************--> <bean id="queryController" name="/query.action" class="com.syshlang.smm.controller.QueryController"></bean> <bean id="queryHttpController" name="/httpquery.action" class="com.syshlang.smm.controller.QueryHttpController"> </bean> <!-- 对于注解的Handler可以单个配置,实际开发中建议使用组件扫描 可以扫描controller、service、...这里让扫描controller,指定controller的包 --> <!--<bean class="com.syshlang.smm.controller.QueryAnnotationController" />--> <context:component-scan base-package="com.syshlang.smm.controller"> </context:component-scan> <!-- ****************配置Handler 结束*********************--> <!-- *********************配置处理器映射器 开始*************************************** --> <!-- 所有的适配器都实现了HandlerMapping接口 --> <!--第一种方式:最简单的,一个class对应一个handler(非注解) 将bean的name当作url来查找,需要在配置Handler时配置bean的name(就是url),如上 --> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"> </bean> <!--第二种方式 集中映射配置 (非注解) SimpleUrlHandlerMapping是BeanNameUrlHandlerMapping的增强版本, 它可以将url和处理器的bean的id进行统一的映射配置 --> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/query0.action">queryController</prop> <prop key="/query1.action">queryController</prop> <prop key="/httpquery0.action">queryHttpController</prop> <prop key="/httpquery1.action">queryHttpController</prop> <!--<prop key="url地址">Controller的bean的id</prop>--> </props> </property> </bean> <!-- 第三种方式 注解映射器 在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping注解映射器。在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping注解映射器 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean> <!-- 结论:① 多个映射器可以并存; ② 一个bean可以对应多个url; ③HandlerMapping 无需配置,springmvc可以默认启动; ④使用注解的映射器和注解的适配器。(注解的映射器和注解的适配器必须配对使用)。 --> <!-- ********************************配置处理器映射器 结束************************** --> <!-- *********************配置处理器适配器 开始*************************************** --> <!-- 所有的处理器适配器都实现HandlerAdapter接口 --> <!--①配置处理器适配器 (非注解) public boolean supports(Object handler) { return handler instanceof Controller; } SimpleControllerHandlerAdapter中的support方法可以看出: 编写的handler需要实现Controller接口 public interface Controller { ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception; } 由此,开发Handler时需要实现Controller接口才能由org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter来执行 --> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"> </bean> <!-- ②HttpRequestHandlerAdapter 是http请求处理适配器, (非注解) 所有实现了HttpRequestHandler接口的bean通过此适配器进行适配、执行 public boolean supports(Object handler) { return handler instanceof HttpRequestHandler; } 由HttpRequestHandlerAdapter中的support方法可以看出: 编写的handler需要实现HttpRequestHandler接口 public interface HttpRequestHandler { void handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException;} --> <bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean> <!--③注解适配器 在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter注解适配器。在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter注解适配器。 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean> <!-- *********************配置处理器适配器 结束*************************************** --> <!-- 使用 mvc:annotation-driven代替上边注解映射器和注解适配器配置 mvc:annotation-driven默认加载很多的参数绑定方法, 比如json转换解析器就默认加载了,如果使用mvc:annotation-driven不用配置上边的RequestMappingHandlerMapping和RequestMappingHandlerAdapter 实际开发时使用mvc:annotation-driven --> <!-- <mvc:annotation-driven></mvc:annotation-driven> --> <!-- *********************配置视图解析器 开始*************************************** --> <!--视图解析器 解析jsp文件,默认使用jstl的标签 classpatch下要有jstl的jar包 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"></bean> <!-- *********************配置视图解析器 结束*************************************** --> <bean id="log4jInitialization" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetClass" value="org.springframework.util.Log4jConfigurer" /> <property name="targetMethod" value="initLogging" /> <property name="arguments"> <list> <value>classpath:log4j.properties</value> </list> </property> </bean></beans> 开发Handler     在此处只举一例,如下:     通过实现 controller接口,由org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter适配器执行的情况。 12345678910111213141516171819202122232425262728293031323334353637383940414243package com.syshlang.smm.controller;import com.syshlang.smm.pojo.QueryPojo;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.mvc.Controller;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.util.ArrayList;import java.util.List;/** * Created by sunys on 2017/7/1 2:44. * Description: 使用SimpleControllerHandlerAdapter处理器适配器实现Controller接口的处理器 */public class QueryController implements Controller{ //添加一个日志器 private static final Logger logger = LoggerFactory.getLogger(QueryController.class); @Override public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { //调用service层,查询数据库 List<QueryPojo> list = new ArrayList<QueryPojo>(); QueryPojo pojo = new QueryPojo(); pojo.setId("1"); pojo.setName("aaaaaa"); list.add(pojo); //返回ModelAndView ModelAndView modelAndView = new ModelAndView(); //相当于request的setAttribut,在jsp页面通过list取值 modelAndView.addObject("listpojo",list); modelAndView.addObject("des","使用SimpleControllerHandlerAdapter处理器适配器实现Controller接口的处理器"); //指定视图View modelAndView.setViewName("/view/query.jsp"); //输出日志文件 logger.info("the first jsp pages"); return modelAndView; }} 源码分析源码分析     根据配置过程,通过前端控制器源码分析SpringMVC的执行过程。 第一步:前端控制器接收请求     在org.springframework.web.servlet.DispatcherServlet中可以看到doDispatch方法。前端控制器接收请求,.action类型的URL通过过滤器进入DispatcherServlet类,doDispatch()方法 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Object dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { this.noHandlerFound(processedRequest, response); return; } HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (this.logger.isDebugEnabled()) { this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } } 第二步:前端控制器调用处理器映射器查找 Handler     在doDispatch()方法中调用了DispatcherServlet类的getHandler方法。映射器根据request当中的URL,找到了Handler,最终返回一个执行器的链(HandlerExecutionChain)。这个链里面有Handler。 12345678910111213141516171819protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Iterator var2 = this.handlerMappings.iterator(); HandlerExecutionChain handler; do { if (!var2.hasNext()) { return null; } HandlerMapping hm = (HandlerMapping)var2.next(); if (this.logger.isTraceEnabled()) { this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'"); } handler = hm.getHandler(request); } while(handler == null); return handler; } 第三步:调用处理器适配器执行Handler,得到执行结果ModelAndView123...mv = ha.handle(processedRequest, response, mappedHandler.getHandler());... 第四步:视图渲染,将model数据填充到request域。     视图解析,得到view:在doDispatch()方法中 1this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); 1234567891011121314151617181920212223242526272829private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { this.logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException)exception).getModelAndView(); } else { Object handler = mappedHandler != null ? mappedHandler.getHandler() : null; mv = this.processHandlerException(request, response, handler, exception); errorView = mv != null; } } if (mv != null && !mv.wasCleared()) { this.render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else if (this.logger.isDebugEnabled()) { this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling"); } if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, (Exception)null); } } } 在processDispatchResulth()方法中 1this.render(mv, request, response); 12345678910111213141516171819202122232425262728293031323334protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { Locale locale = this.localeResolver.resolveLocale(request); response.setLocale(locale); View view; if (mv.isReference()) { view = this.resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'"); } } else { view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + this.getServletName() + "'"); } } if (this.logger.isDebugEnabled()) { this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'"); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } view.render(mv.getModelInternal(), request, response); } catch (Exception var7) { if (this.logger.isDebugEnabled()) { this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var7); } throw var7; } } 在render()方法中 1view = this.resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); 调用view的渲染方法,将model数据填充到request域 1view.render(mv.getModelInternal(), request, response); 根据InternalResourceView的继承关系:org.springframework.web.servlet.view.AbstractView ,org.springframework.web.servlet.view.AbstractUrlBasedView,org.springframework.web.servlet.view.InternalResourceView 最终找到render方法在AbstractView中,如下代码所示: 123456789public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (this.logger.isTraceEnabled()) { this.logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + " and static attributes " + this.staticAttributes); } Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response); this.prepareResponse(request, response); this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response); } 123456789101112131415161718192021protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception { Iterator var3 = model.entrySet().iterator(); while(var3.hasNext()) { Entry<String, Object> entry = (Entry)var3.next(); String modelName = (String)entry.getKey(); Object modelValue = entry.getValue(); if (modelValue != null) { request.setAttribute(modelName, modelValue); if (this.logger.isDebugEnabled()) { this.logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() + "] to request in view with name '" + this.getBeanName() + "'"); } } else { request.removeAttribute(modelName); if (this.logger.isDebugEnabled()) { this.logger.debug("Removed model object '" + modelName + "' from request in view with name '" + this.getBeanName() + "'"); } } } }","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"framework","slug":"java/framework","permalink":"https://syshlang.com/categories/java/framework/"},{"name":"spring","slug":"spring","permalink":"https://syshlang.com/categories/spring/"},{"name":"springmvc","slug":"spring/springmvc","permalink":"https://syshlang.com/categories/spring/springmvc/"}],"tags":[{"name":"Spring","slug":"Spring","permalink":"https://syshlang.com/tags/Spring/"},{"name":"SpringMVC","slug":"SpringMVC","permalink":"https://syshlang.com/tags/SpringMVC/"}]},{"title":"Spring框架组件学习之SpringMVC","slug":"Springmvc_framework","date":"2017-07-02T09:57:10.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"3b06d7c3/","link":"","permalink":"https://syshlang.com/3b06d7c3/","excerpt":"SpringMVC框架SpringMVC框架是什么","text":"SpringMVC框架SpringMVC框架是什么     从以上的框架图中,我们可以很清晰的看出:Springmvc是一个基于mvc的web框架,并且它是spring框架的一个模块。由此,我们在开发的过程中,SpringMVC和spring无需通过中间整合层进行整合。 mvc在b/s系统下的应用     mvc是一个设计模式,mvc在b/s系统下的应用,如下图: SpringMVC框架     SpringMVC框架,用户发出一个请求时,处理流程如下图:      根据以上处理流程图,总结如下: 发起请求到前端控制器(DispatcherServlet); 前端控制器请求HandlerMapping查找Handler可以根据xml配置、注解进行查找(DispatcherServlet); 处理器映射器HandlerMapping向前端控制器返回Handler; 前端控制器调用处理器适配器去执行Handler; 处理器适配器去执行Handler; Handler执行完成给适配器返回ModelAndView; 处理器适配器向前端控制器返回ModelAndView,ModelAndView是SpringMVC框架的一个底层对象,包括Model和view; 前端控制器请求视图解析器去进行视图解析根据逻辑视图名解析成真正的视图(jsp); 视图解析器向前端控制器返回View; 前端控制器进行视图渲染视图渲染将模型数据(在ModelAndView对象中)填充到request域; 前端控制器向用户响应结果。      在这个流程中所涉及到组件: 前端控制器DispatcherServlet(不需要程序员开发)作用:接收请求,响应结果,相当于转发器,中央处理器,有了DispatcherServlet减少了其它组件之间的耦合度。 处理器映射器HandlerMapping(不需要程序员开发)作用:根据请求的url查找Handler 处理器适配器HandlerAdapter作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler 处理器Handler(需要程序员开发)注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler 视图解析器View resolver(不需要程序员开发)作用:进行视图解析,根据逻辑视图名解析成真正的视图(view) 视图View(需要程序员开发jsp)View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"framework","slug":"java/framework","permalink":"https://syshlang.com/categories/java/framework/"},{"name":"spring","slug":"spring","permalink":"https://syshlang.com/categories/spring/"},{"name":"springmvc","slug":"spring/springmvc","permalink":"https://syshlang.com/categories/spring/springmvc/"}],"tags":[{"name":"Spring","slug":"Spring","permalink":"https://syshlang.com/tags/Spring/"},{"name":"SpringMVC","slug":"SpringMVC","permalink":"https://syshlang.com/tags/SpringMVC/"}]},{"title":"Linux系统下操作集锦","slug":"Operation-highlights-on-a-Linux-system","date":"2017-04-23T07:24:08.000Z","updated":"2020-09-17T00:34:15.248Z","comments":true,"path":"b529e05a/","link":"","permalink":"https://syshlang.com/b529e05a/","excerpt":"前言      本篇文章专门用来记录本人在使用Linux系统过程一些操作技巧和遇到的一些问题及解决方案,长期更新。","text":"前言      本篇文章专门用来记录本人在使用Linux系统过程一些操作技巧和遇到的一些问题及解决方案,长期更新。 SOLUTIONLinux系统下配置网关 详见《Linux系统下配置网关》。 Linux系统下关闭/开启防火墙Centos 7 firewall 命令 查看已经开放的端口 1[root@sunys ~]# firewall-cmd --list-ports 开启端口 1[root@sunys ~]# firewall-cmd --zone=public --add-port=80/tcp --permanent – zone #作用域– add-port=80/tcp #添加端口,格式为:端口/通讯协议– permanent #永久生效,没有此参数重启后失效 重启/停止/停止/查看防火墙 1234[root@sunys ~]# firewall-cmd --reload #重启firewall[root@sunys ~]# systemctl stop firewalld.service #停止firewall[root@sunys ~]# systemctl disable firewalld.service #禁止firewall开机启动[root@sunys ~]# firewall-cmd --state #查看默认防火墙状态(关闭后显示notrunning,开启后显示running) CentOS 7 以下版本 iptables 命令 查看防火墙状态 12[root@sunys ~]# service iptables statusiptables: Firewall is not running. 查看打开的端口 1[root@sunys ~]# /etc/init.d/iptables status 开启端口 ①使用命令行: 12[root@sunys ~]# /sbin/iptables -I INPUT -p tcp --dport 80 -j ACCEPT[root@sunys ~]# /etc/rc.d/init.d/iptables save #保存 ②或者修改/etc/sysconfig/iptables 文件,添加以下内容: 12-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT 重启/停止/停止/查看防火墙 ①永久关闭,重启后生效开启: chkconfig iptables on关闭: chkconfig iptables off②即时生效,重启后失效开启: service iptables start关闭: service iptables stop 查看 SELinux状态 & 关闭 SELinux查看SELinux状态 ① getenforce 命令是单词get(获取)和enforce(执行)连写,可查看selinux状态,与setenforce命令相反。 12[root@sunys ~]# getenforceEnforcing ② /usr/sbin/sestatus 查看详细参数 12345678910[root@sunys ~]# /usr/sbin/sestatusSELinux status: enabledSELinuxfs mount: /sys/fs/selinuxSELinux root directory: /etc/selinuxLoaded policy name: targetedCurrent mode: enforcingMode from config file: enforcingPolicy MLS status: enabledPolicy deny_unknown status: allowedMax kernel policy version: 28 SELinux status:selinux防火墙的状态,enabled表示启用selinux防火墙 Current mode: selinux防火墙当前的安全策略,enforcing 表示强 关闭SELinux ①临时关闭 setenforce 命令则是单词set(设置)和enforce(执行)连写,用于设置selinux防火墙状态,如: setenforce 0用于关闭selinux防火墙,但重启后失效 1234567891011[root@sunys ~]# setenforce 0[root@sunys ~]# /usr/sbin/sestatusSELinux status: enabledSELinuxfs mount: /sys/fs/selinuxSELinux root directory: /etc/selinuxLoaded policy name: targetedCurrent mode: permissiveMode from config file: enforcingPolicy MLS status: enabledPolicy deny_unknown status: allowedMax kernel policy version: 28 ②永久关闭 修改selinux的配置文件,重启后生效。 打开 selinux 配置文件,将SELINUX=enforcing改为SELINUX=disabled。 123456789101112[root@sunys ~]# vim /etc/selinux/config# This file controls the state of SELinux on the system.# SELINUX= can take one of these three values:# enforcing - SELinux security policy is enforced.# permissive - SELinux prints warnings instead of enforcing.# disabled - No SELinux policy is loaded.SELINUX=enforcing# SELINUXTYPE= can take one of three two values:# targeted - Targeted processes are protected,# minimum - Modification of targeted policy. Only selected processes are protected.# mls - Multi Level Security protection.SELINUXTYPE=targeted 重启 1[root@sunys ~]# reboot 验证 1234[root@sunys ~]# /usr/sbin/sestatusSELinux status: disabled[root@sunys ~]# getenforceDisabled CentOS下开启SSH Server服务 查看SSH是否安装 1[root@sunys ~]# rpm -qa | grep ssh 若没安装SSH则可输入一下命令安装 1[root@sunys ~]# yum install openssh-server 启动SSH服务 1[root@sunys ~]# systemctl start sshd 重启SSH服务 1[root@sunys ~]# systemctl restart sshd 停止SSH服务 1[root@sunys ~]# systemctl stop sshd 启动后可输入一下命令查看是否启动22端口(可略) 1[root@sunys ~]# netstat -antp | grep sshd 设置SSH服务为开机启动 1[root@sunys ~]# systemctl enable sshd 设置禁止SSH开机启动 1[root@sunys ~]# systemctl disable sshd linux下java读取串口相关问题权限相关问题 No permission to create lock fileissue: CentOS下部署短信猫时报错:check_group_uucp(): error testing lock file creation Error details:权限不够check_lock_status: No permission to create lock file.please see: How can I use Lock Files with rxtx? in INSTALL cause: 这个问题源于试图在/ var / lock中创建锁文件的rxtx包。 可以通过更改访问权限(以root身份)来解决此问题 解决办法一 查找/etc下的groups文件,但是没有找到。只有group、group- 这2个文件,其中group文件就是我们要找的。打开此文件,将如下2段修改:uucp❌14:uucp 修改为 uucp❌14:uucp,ebmlock❌54: 修改为 lock❌54:ebm修改这个文件后,必须重启系统,才能使之起效 解决办法二 执行如下命令 12[root@sunys ~]# chgrp uucp /var/lock/[root@sunys ~]# chmod g+w /var/lock/ 文件已存在 File existsissue: RXTX fhs_lock() Error: creating lock file: /var/lock/LCK..ttyS0: 文件已存在 cause: 有多个进程同时使用串口,端口被占用 解决办法执行如下命令 1[root@sunys ~]# rm /var/lock/LCK..ttyS0","categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"OS","slug":"OS","permalink":"https://syshlang.com/categories/OS/"},{"name":"linux","slug":"OS/linux","permalink":"https://syshlang.com/categories/OS/linux/"}],"tags":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/tags/technology/"},{"name":"OS,linux","slug":"OS-linux","permalink":"https://syshlang.com/tags/OS-linux/"},{"name":"Oracle","slug":"Oracle","permalink":"https://syshlang.com/tags/Oracle/"}]}],"categories":[{"name":"technology","slug":"technology","permalink":"https://syshlang.com/categories/technology/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/categories/java/"},{"name":"设计模式","slug":"java/设计模式","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/categories/database/"},{"name":"oracle","slug":"database/oracle","permalink":"https://syshlang.com/categories/database/oracle/"},{"name":"设计原则","slug":"java/设计原则","permalink":"https://syshlang.com/categories/java/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"},{"name":"mysql","slug":"database/mysql","permalink":"https://syshlang.com/categories/database/mysql/"},{"name":"safety","slug":"java/safety","permalink":"https://syshlang.com/categories/java/safety/"},{"name":"web","slug":"java/safety/web","permalink":"https://syshlang.com/categories/java/safety/web/"},{"name":"framework","slug":"java/framework","permalink":"https://syshlang.com/categories/java/framework/"},{"name":"spring","slug":"spring","permalink":"https://syshlang.com/categories/spring/"},{"name":"springquartz","slug":"spring/springquartz","permalink":"https://syshlang.com/categories/spring/springquartz/"},{"name":"javascript","slug":"javascript","permalink":"https://syshlang.com/categories/javascript/"},{"name":"mindmap","slug":"javascript/mindmap","permalink":"https://syshlang.com/categories/javascript/mindmap/"},{"name":"mindmap","slug":"java/mindmap","permalink":"https://syshlang.com/categories/java/mindmap/"},{"name":"shiro","slug":"java/shiro","permalink":"https://syshlang.com/categories/java/shiro/"},{"name":"springdata","slug":"spring/springdata","permalink":"https://syshlang.com/categories/spring/springdata/"},{"name":"git","slug":"git","permalink":"https://syshlang.com/categories/git/"},{"name":"OS","slug":"OS","permalink":"https://syshlang.com/categories/OS/"},{"name":"linux","slug":"OS/linux","permalink":"https://syshlang.com/categories/OS/linux/"},{"name":"RFID","slug":"technology/RFID","permalink":"https://syshlang.com/categories/technology/RFID/"},{"name":"system","slug":"system","permalink":"https://syshlang.com/categories/system/"},{"name":"framework","slug":"system/framework","permalink":"https://syshlang.com/categories/system/framework/"},{"name":"springmvc","slug":"spring/springmvc","permalink":"https://syshlang.com/categories/spring/springmvc/"}],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://syshlang.com/tags/JAVA/"},{"name":"design","slug":"design","permalink":"https://syshlang.com/tags/design/"},{"name":"patterns","slug":"patterns","permalink":"https://syshlang.com/tags/patterns/"},{"name":"TrueLicense","slug":"TrueLicense","permalink":"https://syshlang.com/tags/TrueLicense/"},{"name":"License","slug":"License","permalink":"https://syshlang.com/tags/License/"},{"name":"Oracle","slug":"Oracle","permalink":"https://syshlang.com/tags/Oracle/"},{"name":"Mysql","slug":"Mysql","permalink":"https://syshlang.com/tags/Mysql/"},{"name":"principle","slug":"principle","permalink":"https://syshlang.com/tags/principle/"},{"name":"database","slug":"database","permalink":"https://syshlang.com/tags/database/"},{"name":"sql","slug":"sql","permalink":"https://syshlang.com/tags/sql/"},{"name":"java","slug":"java","permalink":"https://syshlang.com/tags/java/"},{"name":"abstract","slug":"abstract","permalink":"https://syshlang.com/tags/abstract/"},{"name":"web","slug":"web","permalink":"https://syshlang.com/tags/web/"},{"name":"injection","slug":"injection","permalink":"https://syshlang.com/tags/injection/"},{"name":"xss","slug":"xss","permalink":"https://syshlang.com/tags/xss/"},{"name":"Spring","slug":"Spring","permalink":"https://syshlang.com/tags/Spring/"},{"name":"Quartz","slug":"Quartz","permalink":"https://syshlang.com/tags/Quartz/"},{"name":"book","slug":"book","permalink":"https://syshlang.com/tags/book/"},{"name":"linux","slug":"linux","permalink":"https://syshlang.com/tags/linux/"},{"name":"shell","slug":"shell","permalink":"https://syshlang.com/tags/shell/"},{"name":"docker","slug":"docker","permalink":"https://syshlang.com/tags/docker/"},{"name":"JavaScript","slug":"JavaScript","permalink":"https://syshlang.com/tags/JavaScript/"},{"name":"js","slug":"js","permalink":"https://syshlang.com/tags/js/"},{"name":"Apache","slug":"Apache","permalink":"https://syshlang.com/tags/Apache/"},{"name":"Shiro","slug":"Shiro","permalink":"https://syshlang.com/tags/Shiro/"},{"name":"Authorization","slug":"Authorization","permalink":"https://syshlang.com/tags/Authorization/"},{"name":"Authenticator","slug":"Authenticator","permalink":"https://syshlang.com/tags/Authenticator/"},{"name":"Realm","slug":"Realm","permalink":"https://syshlang.com/tags/Realm/"},{"name":"AuthenticationStrategy","slug":"AuthenticationStrategy","permalink":"https://syshlang.com/tags/AuthenticationStrategy/"},{"name":"frame","slug":"frame","permalink":"https://syshlang.com/tags/frame/"},{"name":"SpringData","slug":"SpringData","permalink":"https://syshlang.com/tags/SpringData/"},{"name":"Repository","slug":"Repository","permalink":"https://syshlang.com/tags/Repository/"},{"name":"Request","slug":"Request","permalink":"https://syshlang.com/tags/Request/"},{"name":"HttpServletRequestWrapper","slug":"HttpServletRequestWrapper","permalink":"https://syshlang.com/tags/HttpServletRequestWrapper/"},{"name":"PHP","slug":"PHP","permalink":"https://syshlang.com/tags/PHP/"},{"name":"post","slug":"post","permalink":"https://syshlang.com/tags/post/"},{"name":"Git","slug":"Git","permalink":"https://syshlang.com/tags/Git/"},{"name":"FQA","slug":"FQA","permalink":"https://syshlang.com/tags/FQA/"},{"name":"System","slug":"System","permalink":"https://syshlang.com/tags/System/"},{"name":"network","slug":"network","permalink":"https://syshlang.com/tags/network/"},{"name":"Gateway","slug":"Gateway","permalink":"https://syshlang.com/tags/Gateway/"},{"name":"RFID","slug":"RFID","permalink":"https://syshlang.com/tags/RFID/"},{"name":"电动车","slug":"电动车","permalink":"https://syshlang.com/tags/%E7%94%B5%E5%8A%A8%E8%BD%A6/"},{"name":"Exception","slug":"Exception","permalink":"https://syshlang.com/tags/Exception/"},{"name":"系统","slug":"系统","permalink":"https://syshlang.com/tags/%E7%B3%BB%E7%BB%9F/"},{"name":"框架","slug":"框架","permalink":"https://syshlang.com/tags/%E6%A1%86%E6%9E%B6/"},{"name":"Dubbox","slug":"Dubbox","permalink":"https://syshlang.com/tags/Dubbox/"},{"name":"SpringMVC","slug":"SpringMVC","permalink":"https://syshlang.com/tags/SpringMVC/"},{"name":"mybatis","slug":"mybatis","permalink":"https://syshlang.com/tags/mybatis/"},{"name":"technology","slug":"technology","permalink":"https://syshlang.com/tags/technology/"},{"name":"OS,linux","slug":"OS-linux","permalink":"https://syshlang.com/tags/OS-linux/"}]}