-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 897 KB
/
content.json
1
{"meta":{"title":"code dog","subtitle":"不停奔跑,不停思考~","description":"什么鬼...","author":"xxyxpy","url":"http://blog.xxyxpy.pub"},"pages":[{"title":"","date":"2017-04-10T09:31:36.921Z","updated":"2017-04-10T09:31:36.921Z","comments":true,"path":"404.html","permalink":"http://blog.xxyxpy.pub/404.html","excerpt":"","text":""},{"title":"About","date":"2017-12-22T09:10:13.380Z","updated":"2017-12-22T09:10:13.379Z","comments":true,"path":"about/index.html","permalink":"http://blog.xxyxpy.pub/about/index.html","excerpt":"","text":"呵呵哒"},{"title":"Categories","date":"2017-11-14T08:21:04.777Z","updated":"2017-11-14T08:21:04.777Z","comments":true,"path":"categories/index.html","permalink":"http://blog.xxyxpy.pub/categories/index.html","excerpt":"","text":""},{"title":"Tags","date":"2017-12-26T06:17:40.426Z","updated":"2017-11-14T08:21:04.777Z","comments":true,"path":"tags/index.html","permalink":"http://blog.xxyxpy.pub/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"spring实战-03高级装配","slug":"java/spring/spring实战-03高级装配","date":"2018-12-29T07:01:16.000Z","updated":"2019-01-02T09:37:01.466Z","comments":true,"path":"2018/12/29/java/spring/spring实战-03高级装配/","link":"","permalink":"http://blog.xxyxpy.pub/2018/12/29/java/spring/spring实战-03高级装配/","excerpt":"环境与profile解决不同的环境存在不同的配置问题 @Profile注解 应用在整个config上面 这个配置类中的bean只有在dev profile激活时才会创建,若没有激活那么带有@Bean注解的方法都会被忽略掉。 12345678@Configuration@Profile(\"dev\")public class CDPlayerConfig { @Bean(\"myAAA\") public Logger getLogger() { return new Logger(); }} 应用在config中的单个方法上 不同的方法设置不同的profile,一个config中可以兼容多个不同环境中的bean 1234567891011121314@Configurationpublic class CDPlayerConfig { @Bean(\"myAAA\") @Profile(\"dev\") public Logger getLogger() { return new Logger(); } @Bean @Profile(\"prod\") public CdPlayer cdPlayer(Logger logger) { return new CdPlayer(logger); }}","text":"环境与profile解决不同的环境存在不同的配置问题 @Profile注解 应用在整个config上面 这个配置类中的bean只有在dev profile激活时才会创建,若没有激活那么带有@Bean注解的方法都会被忽略掉。 12345678@Configuration@Profile(\"dev\")public class CDPlayerConfig { @Bean(\"myAAA\") public Logger getLogger() { return new Logger(); }} 应用在config中的单个方法上 不同的方法设置不同的profile,一个config中可以兼容多个不同环境中的bean 1234567891011121314@Configurationpublic class CDPlayerConfig { @Bean(\"myAAA\") @Profile(\"dev\") public Logger getLogger() { return new Logger(); } @Bean @Profile(\"prod\") public CdPlayer cdPlayer(Logger logger) { return new CdPlayer(logger); }} 激活profileSpring通过两个独立的属性spring.profiles.active和spring.profiles.default来确定哪个(或哪几个,支持同时激活多个)profile处于激活状态。优先查找spring.profiles.active属性。有以下多种方式可以设置这两个属性: 作为DispatcherServlet的初始化参数; 作为Web应用的上下文参数; 作为JNDI条目; 作为环境变量; 作为JVM的系统属性; 在集成测试类上,使用@ActiveProfiles注解设置 123456789@RunWith(SpringRunner.class)@SpringBootTest@ActiveProfiles({\"qa\", \"stage\"})public class DemoApplicationTests { @Test public void contextLoads() throws InterruptedException { }} 条件化的bean如果希望一个bean只有在应用的类路径下包含特定的库时才创建,或者希望某个bean只有当另外某个特定的bean也声明之后才会创建。还可能要求只有某个特定的环境变量设置之后,才会创建某个bean. 例:MagicBean的类只有在设置了magic环境属性的时候,Spring才实例化这个类。没有就被忽略。 @Conditional注解使用@Conditional注解进行条件化配置,此注解要求传入Condition接口的实现类,当传入多个时必须同时满足条件 12345678910@Configurationpublic class ConditionConfig { @Bean @Conditional(MagicExistsCondition.class) public MagicBean magicBean() { return new MagicBean(); } } Condition接口此接口只有一个方法matches()需要实现,用于判断是否满足条件。返回true时通过。方法的参数为ConditionContext和AnnotatedTypeMetadata。前者可用于获取系统配置或类加载等信息,后者可以用来获取注解及注解的属性。可参考@Profile注解的实现。 1234567public class MagicExistsCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { Environment environment = conditionContext.getEnvironment(); return environment.containsProperty(\"magic\"); }} 处理自动装配的歧义性当自动装配的bean不唯一时,Spring无法做出选择,会拋出NoUniqueBeanDefinitionException异常。有两种方式可以用来处理这类异常。 将其中的一个可选bean设置为primary的 使用限定符qualifier来帮助Spring将可选的bean的范围缩小到只有一个bean 使用@Primary标识首选的bean1234567@Component@Primarypublic class IceCream implements Dessert { @Override public void eat() { }} 但是如果同时有两个首选的bean,同样会有歧义性的问题。此时就需要使用更高级的技巧 1234567@Component@Primarypublic class Cake implements Dessert { @Override public void eat() { }} 使用@Qualifier限定自动装配的bean@Qualifier一般与@Autowired同时使用,在注入时指定想要注入进去的是哪个bean 1234567private Dessert dessert;@Autowired@Qualifier(\"iceCream\")public void setDessert(Dessert dessert) { this.dessert = dessert;} 为bean设置自己的限定符解除类名与@Qualifier注解中参数的耦合 1234567@Component@Qualifier(\"cold\")public class IceCream implements Dessert { @Override public void eat() { }} 此时cold限定符分配给了IceCream bean,因为没有耦合类名,所以可以随意重构IceCream的类名,而不必担心会破坏自动装配。只需要在注入的地方引用cold限定符就可以了。 1234567private Dessert dessert;@Autowired@Qualifier(\"cold\")public void setDessert(Dessert dessert) { this.dessert = dessert;} 使用自定义的限定符注解当多个bean具有相同的限定符名称的话仍然会有歧义问题。一种简单的办法是继续添加@Qualifier注解。 此时一个bean上面同时定义了两个@Qualifier注解,但这是不被Java允许的。Java不允许在同一个条目上重复出现相同类型的多个注解。但是我们可以创建自定义的限定符注解,在自定义的限定符注解中组合@Qualifier注解。 在类的定义: 12345678910111213141516171819@Component@Cold@Creamypublic class IceCream implements Dessert { @Override public void eat() { }}@Component@Cold@Fruitypublic class Popsicle implements Dessert { @Override public void eat() { }} 注入时使用类上面定义的限定符 12345678private Dessert dessert;@Autowired@Cold@Creamypublic void setDessert(Dessert dessert) { this.dessert = dessert;} bean的作用域默认情况下,Spring应用上下文中所有的bean都是单例(singleton)形式创建的。Spring同时支持多种作用域: 单例(Singleton):整个应用中,只创建bean的一个实例; 原型(Prototype):每次注入或者通过Spring应用上下文获取时都会创建一个新的bean实例; 会话(Session):在Web应用中,为每个会话创建一个bean实例; 请求(Request):在Web应用中,为每个请求创建一个bean实例。 例:设置原型 此处使用了常量,也可以使用字符@Scope("prototype") 1234@Component@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public class Notepad {} 使用会话和请求作用域考虑购物车的业务场景,一个用户在登录之后会有自己的购物车。这种场景下购物车bean就是会话作用域。 123456@Bean@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.INTERFACES)public ShoppingCart cart() { return new ShoppingCart();} 注意@Scope中的proxyMode属性,它被设置成了ScopedProxyMode.INTERFACES。该属性用于解决将会话或请求作用域的bean注入到单例bean中所遇到的问题。具体的业务场景是当某个单例bean需要ShoppingCart,而由于ShoppingCart是会话相关的被需要时还没有创建完成要如何处理。 假如此时StoreService bean需要ShoppingCart 12345678910@Componentpublic class StoreService { private ShoppingCart shoppingCart; @Autowired public void setShoppingCart(ShoppingCart shoppingCart) { this.shoppingCart = shoppingCart; } } 由于StoreService bean是单例的,当它创建时ShoppingCart bean还不存在。另外当多个用户访问时系统中会有多个ShoppingCart实例。我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例刚好是当前会话所对应的那一个。 Spring并不会将实际的ShoppingCart bean注入到StoreService,会注入一个到ShoppingCart bean的代理。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。 当proxyMode = ScopedProxyMode.INTERFACES,表明代理要实现ShoppingCart接口,并将调用委托给实现bean。具体实现是用的JDK动态代理; 当proxyMode = ScopedProxyMode.TARGET_CLASS,表明ShoppingCart是一个类,表明要以生成子类的方式创建代理。具体实现方式是使用的CGLib。 1234com._03.ShoppingCart@45c3f3ac // chromecom._03.ShoppingCart@45c3f3ac // chromecom._03.ShoppingCart@45c3f3ac // chromecom._03.ShoppingCart@8883797 // edge 运行时值注入注入外部的值@PropertySource注解需要先将配置文件注入后通过@Value注解取值 app.properties 12disc.title=disctitledisc.artist=discartist 通过@PropertySource注解声明,使用单独配置类或声明在spring boot的启动类上 12345678910111213@Configuration@PropertySource(\"classpath:app.properties\")public class ExpressiveConfig { @Value(\"${disc.title}\") private String title; @Value(\"${disc.artist}\") private String artist; @Bean public BlankDisc disc() { return new BlankDisc(title, artist); }} 如果依赖于组件扫描和自动装配来创建和初始化应用组件的话,可以在构造函数中使用@Value注解自动注入。不过配置文件app.properties需要在其他地方先加载完成 12345public BlankDisc(@Value(\"${disc.title}\") String title , @Value(\"${disc.artist}\") String artist) { this.title = title; this.artist = artist;} Environment当加载app.properties之后就可以通过Environment进行取值。 1234567891011@Configuration@PropertySource(\"classpath:app.properties\")public class ExpressiveConfig { @Autowired private Environment env; @Bean public BlankDisc disc() { return new BlankDisc(env.getProperty(\"disc.title\"), env.getProperty(\"disc.artist\")); }} 共有以下四个重载 String getProperty(String key); 如上面的使用方法 String getProperty(String key, String defaultValue); 当属性不存在时给定一个默认值 \\ T getProperty(String key, Class\\ targetType); 获取定义的属性值,并将其转换为指定的类型 \\ T getProperty(String key, Class\\ targetType, T defaultValue); 获取定义的属性值,并将其转换为指定的类型。不存在时设置一个默认值 其他的方法 String getRequiredProperty(String key, Class\\ targetType) 如果要求某个属性必须要定义而不是获取到null,不存在时会拋出异常 boolean containsProperty(String key) 检查某个属性是否存在 检查profile的激活状态 String[] getActiveProfiles() 返回激活的profile名称的数组 String[] getDefaultProfiles() 返回默认的profile名称的数组 boolean acceptsProfiles(String… profiles) 判断给定的profile是否是激活状态","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"springf","slug":"springf","permalink":"http://blog.xxyxpy.pub/tags/springf/"}]},{"title":"spring实战-02装配Bean","slug":"java/spring/spring实战-02装配Bean","date":"2018-12-28T07:01:16.000Z","updated":"2018-12-29T06:36:11.522Z","comments":true,"path":"2018/12/28/java/spring/spring实战-02装配Bean/","link":"","permalink":"http://blog.xxyxpy.pub/2018/12/28/java/spring/spring实战-02装配Bean/","excerpt":"使用上下文容纳Bean支持使用多种类型的应用上下文。 AnnotationConfigApplicationContext:从一个或多个基于Java配置类中加载Spring应用上下文; AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。 ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。 FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义。 XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。 123ApplicationContext context = new FileSystemXmlApplicationContext(\"c:/a.xml\");ApplicationContext context1 = new ClassPathXmlApplicationContext(\"classpath:a.xml\");ApplicationContext context2 = new AnnotationConfigApplicationContext(WebLogAspect.class);","text":"使用上下文容纳Bean支持使用多种类型的应用上下文。 AnnotationConfigApplicationContext:从一个或多个基于Java配置类中加载Spring应用上下文; AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。 ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。 FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义。 XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。 123ApplicationContext context = new FileSystemXmlApplicationContext(\"c:/a.xml\");ApplicationContext context1 = new ClassPathXmlApplicationContext(\"classpath:a.xml\");ApplicationContext context2 = new AnnotationConfigApplicationContext(WebLogAspect.class); 三种主要的装配机制 在XML中显示配置 在Java中进行配置 隐式的Bean发现机制和自动装配 自动化装配Bean 组件扫描@ComponentScan 自动装配@Autowired 创建可被发现的类在类上添加@Component注解,标明该类是组件类,并告之Spring要为这个类创建bean。默认不设置beanID的话名称会被指定为小写开头的类名,如:sgtPeppers.如果要自定义可直接指定@Component("myOwerId") 123@Componentpublic class SgtPeppers {} 组件扫描默认是不启动的,需要显示的配置,从而命令它去寻找带有@Component注解的类,并为其创建bean 1234@Configuration@ComponentScanpublic class CDPlayerConfig {} @ComponentScan默认会扫描与配置类相同的包以及其子包,查找带有@Component注解的类。 设置扫描的基础包@ComponentScan(basePackages = {“packageA”, “packageB”}) 设置扫描的类或接口@ComponentScan(basePackageClasses = {Logger.class, B.class}) 为bean添加注解实现自动装配 使用在构造器上 1234@Autowiredpublic CDPlayer(CompactDisc cd) { this.cd = cd;} 使用在属性的Setter方法或类的任何方法上 123456789@Autowiredpublic void setCompactDisc(CompactDisc cd) { thid.cd = cd;}@Autowiredpublic void insertDisc(CompactDisc cd) { thid.cd = cd;} 避免没有匹配bean时的异常,将required属性设置为false 1234@Autowired(required=false)public CDPlayer(CompactDisc cd) { this.cd = cd;} 通过Java代码装配bean假如想要将第三方库中的组件装配到应用中,这下无法进行自动装配。 需要先创建配置类,然后在配置类中声明bean,同样bean的名字可以自定义(如果不设置ID就是方法名) 1234567@Configurationpublic class CDPlayerConfig { @Bean(\"myAAA\") public Logger getLogger() { return new Logger(); }} 使用JavaConfig自动注入看起来Logger实例是通过getLogger()方法得到的,实际上并非如此。因为getLogger()上面添加了@Bean注解,Spring将拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。 1234@Beanpublic CdPlayer cdPlayer() { return new CdPlayer(getLogger());} 更为简单的自动注入此种方式的注入不像调用方法一样要求Logger对应的bean在JavaConfig中定义,它可以通过组件扫描功能自动发现或者通过XML来进行配置。Spring不论要注入的bean是采用什么方式创建的。 1234@Beanpublic CdPlayer cdPlayer(Logger logger) { return new CdPlayer(logger);} 通过XML装配bean基本淘汰掉了 导入和混合配置ConfigA组合ConfigB需要将A配置导入到B配置中 12345678@Configuration@Import(ConfigB.class)public class ConfigA {}@Configurationpublic class ConfigB {} 定义更高级别的Config1234@Configuration@Import({ConfigA.class, ConfigB.class})public class ConfigAll {} JavaConfig中载入XML配置12345@Configuration@Import({ConfigA.class, ConfigB.class})@ImportResource(\"classpath:cd-config.xml\")public class ConfigAll {} XML配置间组合1<imprt resource=\"cdplayer-config.xml\" /> XML中组合JavaConfig1<bean class=\"packageName.ConfigA\" />","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"springf","slug":"springf","permalink":"http://blog.xxyxpy.pub/tags/springf/"}]},{"title":"Jdk命令行和工具","slug":"java/jdk/Jdk命令行和工具","date":"2018-12-22T07:01:16.000Z","updated":"2018-12-22T09:23:24.716Z","comments":true,"path":"2018/12/22/java/jdk/Jdk命令行和工具/","link":"","permalink":"http://blog.xxyxpy.pub/2018/12/22/java/jdk/Jdk命令行和工具/","excerpt":"jps类似于Linux下面的ps命令,用于列出Java的进程。通过添加参数可以查看Java进程的启动类、传入参数和JVM参数等信息 jstat用于观察Java应用程序运行时信息的工具。通过它,可以查看堆信息的详细情况。 jinfo查看正在运行的Java应用程序的扩展参数,甚至支持在运行时修改部分参数。","text":"jps类似于Linux下面的ps命令,用于列出Java的进程。通过添加参数可以查看Java进程的启动类、传入参数和JVM参数等信息 jstat用于观察Java应用程序运行时信息的工具。通过它,可以查看堆信息的详细情况。 jinfo查看正在运行的Java应用程序的扩展参数,甚至支持在运行时修改部分参数。 jmap生成Java应用程序的堆快照和对象统计信息。 统计信息,显示内存的实例数量和合计 jmap -histo 202764 >c:\\s.txt 堆快照 jmap -dump:format=b,file=d:\\heap.hprof 202764 jhat分析Java应用程序的堆快照。分析完成之后可以使用Http服务器展示分析结果,在浏览器中访问http://127.0.0.1:7000在结果的底部还支持跳转到新页面使用OQL语句对堆快照进行查询 jhat d:\\heap.hprof jstack用于导出Java应用程序的堆栈。可以用来排查死锁问题 jstack -l 202764 >d:\\deadlock.txt jstatd一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机。 hrpof一个Java agent工具,它可用于监控Java应用程序在运行时的CPU信息和堆信息。 JConsoleJDK自带的图形化性能监控工具。查看Java应用程序的堆信息、永久区使用情况、类加载情况等。 内存监控 线程监控-还可以用于自动检测死锁情况 类加载情况 虚拟机信息 MBean管理 加载插件 jvisualvmJDK自带的管理工具(Visual VM),使用它可以代替jstat、jmap、jstack甚至是JConsole 连接应用程序(包括远程连接) 监控应用程序概况-进程id,main class,启动参数等 线程Dump导出和分析 性能分析 快照-堆,线程和应用程序快照","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"JDK","slug":"JDK","permalink":"http://blog.xxyxpy.pub/tags/JDK/"}]},{"title":"并行程序设计模式","slug":"java/并发编程/并行程序设计模式","date":"2018-12-15T07:01:16.000Z","updated":"2018-12-15T07:14:46.642Z","comments":true,"path":"2018/12/15/java/并发编程/并行程序设计模式/","link":"","permalink":"http://blog.xxyxpy.pub/2018/12/15/java/并发编程/并行程序设计模式/","excerpt":"Future模式此种模式类似于做饭时可以先把电饭锅插上开始做米饭,中间不需要等着饭做好,可以先去炒菜。等菜炒完之后可以再来看看米饭做完了没有。此时有两种可能,一种是米饭做好了可以吃了,另外一种是还没有做好需要等着做好再开吃。 12345678910111213141516171819202122232425262728293031323334353637383940414243import java.util.concurrent.*;public class FuturePattern { public static void main(String[] args) throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); FutureTask<String> future = new FutureTask<>(new RealData(\"abc\")); // 此处不可以直接调用get,必须要放到异步线程中 //future.get(); // 可以使用线程池执行也可以单独创建线程执行 // 单独创建线程执行 //Thread thread = new Thread(future); //thread.start(); // 线程池执行 ExecutorService executorService = Executors.newFixedThreadPool(1); executorService.submit(future); // main线程等待 Thread.sleep(2000); // 获取结果 System.out.println(\"data=\" + future.get()); System.out.println(\"spent:\" + (System.currentTimeMillis() - start)); } public static class RealData implements Callable<String> { private String para; public RealData(String para) { this.para = para; } @Override public String call() throws Exception { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 15; i++) { sb.append(para); Thread.sleep(100); } return sb.toString(); } } }","text":"Future模式此种模式类似于做饭时可以先把电饭锅插上开始做米饭,中间不需要等着饭做好,可以先去炒菜。等菜炒完之后可以再来看看米饭做完了没有。此时有两种可能,一种是米饭做好了可以吃了,另外一种是还没有做好需要等着做好再开吃。 12345678910111213141516171819202122232425262728293031323334353637383940414243import java.util.concurrent.*;public class FuturePattern { public static void main(String[] args) throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); FutureTask<String> future = new FutureTask<>(new RealData(\"abc\")); // 此处不可以直接调用get,必须要放到异步线程中 //future.get(); // 可以使用线程池执行也可以单独创建线程执行 // 单独创建线程执行 //Thread thread = new Thread(future); //thread.start(); // 线程池执行 ExecutorService executorService = Executors.newFixedThreadPool(1); executorService.submit(future); // main线程等待 Thread.sleep(2000); // 获取结果 System.out.println(\"data=\" + future.get()); System.out.println(\"spent:\" + (System.currentTimeMillis() - start)); } public static class RealData implements Callable<String> { private String para; public RealData(String para) { this.para = para; } @Override public String call() throws Exception { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 15; i++) { sb.append(para); Thread.sleep(100); } return sb.toString(); } } } Master-Worker模式系统由两类进程协作工作:Master进程和Worker进程。Master进程负责接收和分配任务,Worker进程负责处理子会务。当子任务处理完成之后将结果返回给Master进程,由其来负责归纳和汇总。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119public class MasterWorkerPattern { public static void main(String[] args) { // 实现求100以内的立方之和 Master m = new Master(new PlusWorker(), 5); // 5个Worker // 提交100个子任务 for (int i = 0; i <= 100; i++) { m.submit(i); } m.execute(); // 开始计算 Map<String, Object> resultMap = m.getResultMap(); // 由于是异步计算,此时可以开始使用计算结果 int result = 0; while (resultMap.size() > 0 || !m.isComplete()) { Set<String> keys = resultMap.keySet(); String key = null; for (String k : keys) { key = k; break; } Integer tmp = null; if (Objects.nonNull(key)) { tmp = (Integer) resultMap.get(key); } if (Objects.nonNull(tmp)) { result += tmp; } if (Objects.nonNull(key)) { resultMap.remove(key); } } System.out.println(\"result:\" + result); } public static class PlusWorker extends Worker { public Object handle(Object input) { Integer i = (Integer) input; if (i == null) { return 0; } return i * i * i; } } public static class Master { // 任务队列 protected Queue<Object> workQueue = new LinkedBlockingQueue<>(); // Worker进程队列 protected Map<String, Thread> threadMap = new HashMap<>(); // 子任务处理结果集 protected Map<String, Object> resultMap = new ConcurrentHashMap<>(); public Master(Worker worker, int countWorker) { worker.setResultMap(resultMap); worker.setWorkQueue(workQueue); for (int i = 0; i < countWorker; i++) { threadMap.put(String.valueOf(i), new Thread(worker, String.valueOf(i))); } } public void submit(Object object) { workQueue.add(object); } public void execute() { for (Map.Entry<String, Thread> threadEntry : threadMap.entrySet()) { threadEntry.getValue().start(); } } public Map<String, Object> getResultMap() { return resultMap; } // 是否所有的子任务都结束 public boolean isComplete() { for (Map.Entry<String, Thread> threadEntry : threadMap.entrySet()) { if (threadEntry.getValue().getState() != Thread.State.TERMINATED) { return false; } } return true; } } public static class Worker implements Runnable { // 任务队列 private Queue<Object> workQueue; // 子任务处理结果集 private Map<String, Object> resultMap; @Override public void run() { // 循环获取任务 while (true) { Object input = workQueue.poll(); if (input == null) { break; } Object result = handle(input); resultMap.put(String.valueOf(input.hashCode()), result); } } // 子任务处理逻辑,根据需要在子类中实现 public Object handle(Object input) { return input; } public void setWorkQueue(Queue<Object> workQueue) { this.workQueue = workQueue; } public void setResultMap(Map<String, Object> resultMap) { this.resultMap = resultMap; } }}","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"}]},{"title":"Java虚拟机内存模型","slug":"java/jvm/Java虚拟机内存模型","date":"2018-12-15T07:01:16.000Z","updated":"2018-12-22T08:22:20.856Z","comments":true,"path":"2018/12/15/java/jvm/Java虚拟机内存模型/","link":"","permalink":"http://blog.xxyxpy.pub/2018/12/15/java/jvm/Java虚拟机内存模型/","excerpt":"内存数据主要分为程序计数器、虚拟机栈、本地方法栈、Java堆和方法区 程序计数器一块很小的内存空间。每一个线程一个独立的计数器用于记录下一条要运行的指令。如果当前线程正在执行一个Java方法则记录正在执行的Java字节码地址,如果是一个Native方法则计数器为空。 Java虚拟机栈线程私有的内存空间,和线程同时创建,保存方法的局部变量、部分结果,并参与方法的调用和返回。 Hot Spot虚拟机中使用-Xss参数来设置栈的大小。栈的大小决定了函数调用的可达深度 虚拟机栈在运行时使用栈帧的数据结构保存上下文数据。栈帧中存放了方法的局部变量表、操作数栈、动态连接方法和返回地址等信息。 本地方法栈用于管理本地方法的调用。Hot Spot虚拟机中,不区分本地方法栈和虚拟机栈。","text":"内存数据主要分为程序计数器、虚拟机栈、本地方法栈、Java堆和方法区 程序计数器一块很小的内存空间。每一个线程一个独立的计数器用于记录下一条要运行的指令。如果当前线程正在执行一个Java方法则记录正在执行的Java字节码地址,如果是一个Native方法则计数器为空。 Java虚拟机栈线程私有的内存空间,和线程同时创建,保存方法的局部变量、部分结果,并参与方法的调用和返回。 Hot Spot虚拟机中使用-Xss参数来设置栈的大小。栈的大小决定了函数调用的可达深度 虚拟机栈在运行时使用栈帧的数据结构保存上下文数据。栈帧中存放了方法的局部变量表、操作数栈、动态连接方法和返回地址等信息。 本地方法栈用于管理本地方法的调用。Hot Spot虚拟机中,不区分本地方法栈和虚拟机栈。 Java堆几乎所有的对象和数组都是在堆中分配空间的。Java堆分为新生代和老年代两个部分,新生代用于存放刚刚产生的对象和年轻的对象,如果对象一直没有被回收,生存的足够长,老年对象就会被移入老年代。 新生代又可进一步细分为eden、survivor space0(s0或者from space)和survivor space1(s1或者to space)。eden意义为伊甸园,即对象的出生地,大部分对象刚刚建立时就存放在这里。s0和s1为survivor空间,直译为幸存者,也就是说存放的对象至少经过一次垃圾回收,得以幸存。如果幸存区的对象到了指定年龄仍未被回收,则有机会进入老年代。 方法区与堆空间类似,方法区也是被JVM中所有的线程共享的。主要保存的信息是类的元数据。 元数据包括类的类型信息、常量池、域信息、方法信息。 类型信息包括类的完整名称、类型修改符和类型的直接接口类型表; 常量池包括这个类方法、域等信息所引用的常量信息; 域信息包括域名称、域类型和域修饰符; 方法信息包括方法名称、返回类型、方法参数、方法修饰符、方法字节码、操作数栈和方法帧栈的局部变量区大小以及异常表。 综上,方法区中保存的信息大部分来自于class文件。 Hot Spot虚拟机同样会对方法区进行回收操作,通常涉及常量池回收和类元数据回收。 对于常量池,只要常量池中的常量没有被任何地方引用,就可以被回收; 该类的所有实例已经被回收,并且加载该类的ClassLoader已经被回收,GC就有可能回收该类型 内存分配参数显示GC详情 -XX:+PrintGCDetails 设置最大堆内存 -Xmx5M 设置最小堆内存 此值越小回收的GC操作越频繁 -Xms3M 设置新生代 较大的新生代会减小老年代的大小,新生代的大小一般设置为整个堆空间的1/4~1/3 -Xmn2M 新生代大小 -XX:NewSize 新生代初始大小 -XX:MaxNewSize 新生代最大值 设置持久代 -XX:MaxPermSize 设置持久代的最大值 -XX:PermSize 设置持久代的初始值 设置线程栈 -Xss1M 堆的比例分配 eden空间和from空间的比例 -XX:SurvivorRatio 设置新生代中eden空间和from空间的比例关系。from空间=to空间 -XX:SurvivorRatio=8,eden:from:to = 8:1:1 -Xmn10M -XX:SurvivorRatio=2,eden 5M from 2.5M to 2.5M 新生代和老年代比例 -XX:NewRatio=老年代/新生代 垃圾回收垃圾回收算法引用计数法每个对象分配计数器,有引用就+1,引用失效就-1.当引用计数器的值为0时清除。 无法处理循环引用的问题,Java中的GC没有使用这种算法 标记-清除算法标记阶段通过根节点标记所有从根节点开始的可达对象。未被标记的对像就是未被引用的垃圾对象,在清除阶段清除掉未被标记的对象。 此算法最大的问题是会产生较多的空间碎片,回收后空间不是连续的。 复制算法将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,然后清除正在使用内存块中的所有对象,交换两个内存的角色完成回收。 当垃圾对象多存活对象少(适用于新生代)时复制算法的效率高,并且不会产生内存碎片。缺点是需要的内存空间大,资源多。 新生代的from和to空间就使用了复制算法,两者地位相同。 标记-压缩算法适用于老年代的回收算法。先从根节点开始做可达标记,然后将标记对象压缩到内存的一端。之后,清理边界之外所有的空间。 这样既避免了碎片的产生,又不需要两块相同的内存空间。 增量算法大部分回收算法在执行垃圾回收时需要Stop the World,此时所有的线程都需要挂起等待回收完成。如果回收时间长则会影响系统运行。 增量算法的思想是将垃圾收集线程和应用程序线程交替执行。每次垃圾收集线程只回收一小片区域的内存空间,接着切换到应用程序线程。这样可以减少系统的停顿。但是因为线程切换和上下文转换的消耗,会使得回收的总体成本上升造成吞吐量下降 分代将内存区域分为不同的代以根据不同代的特点来使用不同的垃圾回收算法。 如Hot Spot虚拟机,将新建对象放入新生代,大约90%的新建对象会被很快回收,因此新生代使用效率高的复制算法。当一个对象经过几次回收后仍然存活,就会被移动到老年代。老年代对象存活周期长,可能经过N次回收仍然存活,如果使用复制算法则需要复制大量对象,成本比较高。所以此时可以使用标记-压缩算法来提高垃圾回收效率。 垃圾收集器类型 按线程数:串行和并行垃圾回收器 按工作模式:并发和独占垃圾回收器 碎片处理:压缩和非压缩垃圾回收器 分代:新生代和老年代垃圾处理器 新生代串行收集器使用单线程进行回收,独占式的垃圾收集机制,使用复制算法 Hot Spot中使用-XX:UseSerialGC指定新生代串行收集器和老年代串行收集器 老年代串行收集器使用单线程进行回收,独占式的垃圾收集机制,使用标记-压缩算法 并行收集器工作在新生代的收集器,将串行回收器多线程化。同时并行收集器也是独占式的。 新生代并行回收收集器多线程,使用复制算法的收集器 老年代并行回收收集器多线程,使用标记-压缩算法的收集器 CMS收集器并发标记清除,使用标记-清除算法,同时又是一个使用多线程并行回收的垃圾收集器。 工作步骤:初始标记、并发标记、重新标记、并发清除和并发重置。初始标记和重新标记是独占系统资源的,而并发标签、并发清除和并发重置是可以和用户线程一起执行的。 G1收集器基于标记-压缩算法。不会产生空间碎片,也没有必要在收集完成后,进行一次独占式的碎片整理工作。可以进行非常精确的停顿控制。可指定在长度为M的时间段中,垃圾回收时间不超N。 常用调优案例和方法 将新对象预留在新生代 增加新生代的大小 大对象进入老年代 新生代分配较多的大对象会导致新生代内存不足。通 -XX:PretenureSizeThreshold参数来设置大对象直接进入老年代的阈值。当对象有大小超过这个值时,将直接在老年代分配。 -XX:PretenureSizeThreshold=1000000超过此值的对象直接进入老年代 设置对象进入老年代的年龄 虚拟机为每个对象都维护一个年龄。在eden区,经过一次GC后还存活就会被移动到survivior区,对象年龄+1.以后每次GC如果对象都存活年龄再+1.当年龄达到阈值时就会移入老年代,成为老年对象。 阈值可以通过参数-XX:MaxTenuringThreshold来设置,默认值是15.虽然此值可以是15或更大,但这并不表示新对象非要达到这个年龄才能进入老年代。实际上进入老年代的年龄是虚拟机根据内存使用情况动态计算的。最终进入老年代的年龄是动态计算算得年龄与-XX:MaxTenuringThreshold设置中较小的那个 稳定与震荡的堆大小 稳定的堆大小对垃圾回收有利。使-Xms和-Xmx大小一致即可得到稳定堆大小 稳定的堆大小虽然可以减少GC的次数,但是增加了GC的执行时间。使用震荡大小的堆可以加快单次GC的速度 实用Jvm参数 JIT编译参数 堆快照 -XX:+HeapDumpOnOutOfMemoryError参数在OOM时导出应用程序的当前堆快照 -XX:HeapDumpPath指定堆快照保存的位置 取得GC信息 -XX:PrintGC -XX:PrintGCDetail 如果需要在GC时打印详细的堆信息,可以打开-XX:+PrintHeapAtGC开关。一旦打开它每次GC时都将打印堆的使用情况 类和对象跟踪 -XX:+TraceClassLoading 跟踪类加载情况 -XX:TraceClassUnloading 跟踪类卸载情况 控制GC -XX:+DisableExplicitGC 禁止手动的System.gc()","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"JVM","slug":"JVM","permalink":"http://blog.xxyxpy.pub/tags/JVM/"}]},{"title":"丧心病狂的库和插件","slug":"java/tool/丧心病狂的库和插件","date":"2018-10-15T16:00:00.000Z","updated":"2018-10-16T06:58:36.055Z","comments":true,"path":"2018/10/16/java/tool/丧心病狂的库和插件/","link":"","permalink":"http://blog.xxyxpy.pub/2018/10/16/java/tool/丧心病狂的库和插件/","excerpt":"Lombok 消除模板代码(如get、set方法),提高代码可读性。 使用时IDEA中需要安装插件lombok,否则代码会编译错误。依赖如下: 12345<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version></dependency> 更多教程: Lombok看这篇就够了","text":"Lombok 消除模板代码(如get、set方法),提高代码可读性。 使用时IDEA中需要安装插件lombok,否则代码会编译错误。依赖如下: 12345<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version></dependency> 更多教程: Lombok看这篇就够了","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"插件","slug":"插件","permalink":"http://blog.xxyxpy.pub/tags/插件/"}]},{"title":"并发编程实战-09性能与可伸缩性","slug":"java/并发编程/09性能与可伸缩性","date":"2018-10-07T16:00:00.000Z","updated":"2018-10-09T09:09:39.966Z","comments":true,"path":"2018/10/08/java/并发编程/09性能与可伸缩性/","link":"","permalink":"http://blog.xxyxpy.pub/2018/10/08/java/并发编程/09性能与可伸缩性/","excerpt":"线程的最主要目的是提高程序的运行性能。 对性能的思考尽管使用多个线程的目标是提升整体性能,但与单线程的方法相比,使用多个线程总传统引入一些额外的性能开销。造成这些开销的操作包括:线程之间的协调(比如加锁、触发信号以及内存同步等),增加的上下文切换,线程的创建和销毁,以及线程的调度等。如果过度的使用线程,那么这些开销甚至超过由于提高吞吐量、响应性或者计算能力所带来的性能提升。另一方面,一个并发设计很糟糕的应用程序,其性能甚至比实现相同功能的串行程序的性能还要差。","text":"线程的最主要目的是提高程序的运行性能。 对性能的思考尽管使用多个线程的目标是提升整体性能,但与单线程的方法相比,使用多个线程总传统引入一些额外的性能开销。造成这些开销的操作包括:线程之间的协调(比如加锁、触发信号以及内存同步等),增加的上下文切换,线程的创建和销毁,以及线程的调度等。如果过度的使用线程,那么这些开销甚至超过由于提高吞吐量、响应性或者计算能力所带来的性能提升。另一方面,一个并发设计很糟糕的应用程序,其性能甚至比实现相同功能的串行程序的性能还要差。 性能与可伸缩性 可伸缩性指的是:当增加计算资源时(CPU、内存、存储容量或io带宽),程序的吞吐量或者处理能力能相应地增加。 评估各种性能权衡因素 避免不成熟的优化,首先使程序正确,然后再提高运行速度——如果它还运行得不够快。 线程引入的开销上下文切换如果可运行的线程数大于CPU数量,那么操作系统最终会将某个正在运行的线程调度出来,从而使其他线程能够使用CPU。这将导致一次上下文切换,在这个过程中将保存当前运行线程的执行上下文,并将新调试进来的线程的执行上下文设置为当前上下文。 内存同步同步操作的性能开销包括多个方面。在synchronized和volatile提供的可见性保证中可能会使用一些特殊指令,即内存栅栏。内存栅栏可以刷新缓存,使缓存无效,刷新硬件的写缓冲,以及停止执行检查。内存栅栏可能同样会对性能带来间接的影响,因为它们将抑制一些编译器优化操作。在内存栅栏中,大多数操作都是不能被重排序的。 阻塞当在锁上发生竞争时,竞争失败的线程肯定会阻塞。Jvm在实现阻塞行为时,可以采用自旋等待(指通过循环不断尝试获取锁,直到成功)或者通过操作系统挂起被阻塞的线程。这两种方式的效率高低,要取决于上下文切换的开销以及在成功获取锁之前需要等待的时间。如果等待时间短,则适合采用自旋等待方式,而如果等待时间较长,则适合采用线程挂起方式。大多数Jvm在等待锁时都只是将线程挂起。 当线程无法获取某个锁或者由于在某个条件等待或在I/O操作上阻塞时,需要被挂起,在这个过程中将包含两次额外的上下文切换,以及所有必要的操作系统操作和缓存操作:被阻塞的线程在其执行时间片还未用完之前就被交换出去,而在随后当要获取的锁或者其他资源可用时,又再次被切换回来。(由于锁竞争而导致阻塞时,线程在持有锁时将存在一定的开销:当它释放锁时,必须告诉操作系统恢复运行阻塞的线程。) 减少锁的竞争如果在锁上持续发生竞争,那么将限制代码的可伸缩性。 在并发程序中,对可伸缩性的最主要威胁就是独占方式的资源锁。 有两个因素将影响在锁上发生竞争的可能性:锁的请求频度以及每次持有锁的时间。如果二者的乘积很小,那么大多数获取锁的操作都不会发生竞争,因此在该锁上的竞争不会对可伸缩性造成严重影响。 有3种方式可以降低锁的竞争程度: 减少锁的持有时间 降低锁的请求频率 用带有协调机制的独占锁,这些机制允许更高的并发性。 缩小锁的范围(快进快出)降低发生竞争可能性的一种有效方式就是尽可能缩短锁的持有时间。例如,可以将一些与锁无关的代码移出同步代码块,尤其是那些开销较大的操作,以及可能被阻塞的操作,例如I/O操作。 如果将一个高度竞争的锁持有过长的时间,那么会限制可伸缩性,如果某个操作持有锁的时间超过2ms并且所有操作都需要这个锁,那么无论拥有多少个空闲处理器,吞吐量也不会超过每秒500个操作。如果将这个锁的持有时间降为1ms,那么能够将这个锁对应的吞吐量提高到每秒1000个操作。 尽管缩小同步代码块能提高可伸缩性,但同步代码块也不能过小——一些需要采用原子方式执行的操作(例如对某个不变性条件中的多个变量进行更新)必须包含在一个同步块中。此外,同步需要一定的开销,当把一个同步代码块分解为多个同步代码块时(在确保正确性的情况下),反而会对性能提升产生负面影响 。在分解同步代码块时,理想的平衡点将与平台相关,但在实际情况中,仅当可以将一些大量的计算或阻塞操作从同步代码块中移出时才应该考虑同步代码块的大小。 减小锁的粒度另一种减小锁的持有时间的方式是降低线程请求锁的频率(从而减小发生竞争的可能性)。这可以通过锁分解和锁分段等技术来实现,在这些技术中将采用多个相互独立的锁来保护独立的状态变量,从而改变这些变量在之前由单个锁来保护的情况。这些技术能减小锁操作的粒度,并能实现更高的可伸缩性,然而,使用的锁越多,那么发生死锁的风险也就越高。 如果一个锁需要保护多个相互独立的状态变量,那么可以将这个锁分解为多个锁,并且每个锁只保护一个变量,从而提高可伸缩性,并最终降低每个锁被请求的频率。 锁分段在某些情况下可以将锁分解技术进一步扩展为对一组独立对象上的锁进行分解,这种情况被称为锁分段。例如在ConcurrentHashMap的实现中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16。 锁分段的一个劣势在于:与采用单个锁来实现独占访问相比,要获取多个锁来实现独占访问将更加困难并且开销更高。通常,在执行一个操作时最多只需要获取一个锁,但在某些情况下需要加锁整个容器,例如当ConcurrentHashMap需要扩展映射范围,以及重新计算键值的散列值要分布到更大的桶集合中时,就需要获取分段锁集合中所有的锁。","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"},{"name":"并发编程实战","slug":"并发编程实战","permalink":"http://blog.xxyxpy.pub/tags/并发编程实战/"}]},{"title":"并发编程实战-08避免活跃性危险","slug":"java/并发编程/08避免活跃性危险","date":"2018-09-27T16:00:00.000Z","updated":"2018-10-08T03:27:02.753Z","comments":true,"path":"2018/09/28/java/并发编程/08避免活跃性危险/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/28/java/并发编程/08避免活跃性危险/","excerpt":"死锁当一个线程永远地持有一个锁,并且其他线程都尝试获得这个锁时,那么它们将永远被阻塞。在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么这两个线程将永远地等待下去。 在数据库系统中,对于死锁会自动的选择一个牺牲者并放弃这个事务,牺牲者将放弃它持有的资源,从而使事务执行完成,应用程序可以重新执行被强行中止的事务,现在这个事务可以成功完成。","text":"死锁当一个线程永远地持有一个锁,并且其他线程都尝试获得这个锁时,那么它们将永远被阻塞。在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么这两个线程将永远地等待下去。 在数据库系统中,对于死锁会自动的选择一个牺牲者并放弃这个事务,牺牲者将放弃它持有的资源,从而使事务执行完成,应用程序可以重新执行被强行中止的事务,现在这个事务可以成功完成。 锁顺序死锁两个线程试图以不同的顺序来获得相同的锁。 如果所有线程都以固定的顺序来获得锁,那么在程序中就不会出现锁顺序死锁问题 简单的锁顺序死锁 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950public class LeftRightDeadlock { private final Object left = new Object(); private final Object right = new Object(); public void leftRight () throws InterruptedException { synchronized (left) { Thread.sleep(100); synchronized (right) { System.out.println(\"leftRight...\"); } } } public void rightLeft () throws InterruptedException { synchronized (right) { //Thread.sleep(100); synchronized (left) { System.out.println(\"rightLeft...\"); } } } public static void main(String[] args) { LeftRightDeadlock deadlock = new LeftRightDeadlock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { try { deadlock.leftRight(); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { deadlock.rightLeft(); } catch (InterruptedException e) { e.printStackTrace(); } } }); t1.start(); t2.start(); }} 动态的锁顺序死锁考虑如下的代码,将资金从一个帐户转移到另一个帐户。 动态的锁顺序死锁(容易发生死锁) 123456789101112131415161718192021222324252627282930313233343536373839public class AccountDeadlock { public void transferMoney(Account fromAccount, Account toAccount, int amount) throws InsufficientResourcesException { synchronized (fromAccount) { if (fromAccount.getBalance() < amount) { throw new InsufficientResourcesException(); } else { synchronized (toAccount) { fromAccount.debit(amount); toAccount.credit(amount); } } } }}class Account { private int balance; Account(int balance) { this.balance = balance; } public void debit(int amount) { balance -= amount; } public void credit(int amount) { balance += amount; } public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; }} transferMoney方法中看似不太可能发生死锁。但实际上由于参数传入的顺序依赖外部的输入。如果有两个线程同时调用此方法,并且交易双方为X转帐给Y,Y转帐给X,那么就会发生死锁。 Thread A: transferMoney(XAccount, YAccount, 10) Thread B: transferMoney(YAccount, XAccount, 20) 此时发生死锁的原因和上面讲到的锁顺序死锁相同。要想解决这个问题必须定义锁的顺序,使两个线程以相同的顺序来获得锁。 12345678910111213141516171819202122232425262728293031323334353637383940public class AccountDeadlock2 { private static final Object tieLock = new Object(); public void transferMoney(Account fromAccount, Account toAccount, int amount) throws InsufficientResourcesException { class Helper { public void transfer() throws InsufficientResourcesException { if (fromAccount.getBalance() < amount) { throw new InsufficientResourcesException(); } else { fromAccount.debit(amount); toAccount.credit(amount); } } } int fromHash = System.identityHashCode(fromAccount); int toHash = System.identityHashCode(toAccount); if (fromHash < toHash) { synchronized (fromAccount) { synchronized (toAccount) { new Helper().transfer(); } } } else if (fromHash > toHash) { synchronized (toAccount) { synchronized (fromAccount) { new Helper().transfer(); } } } else { synchronized (tieLock) { synchronized (fromAccount) { synchronized (toAccount) { new Helper().transfer(); } } } } }} 在协作对象之间发生的死锁下面的程序中有两个相互协作的类,Taxi代表一个出租车对象,包含位置和目的地两个属性,Dispatcher代表一个出租车车队。 在相互协作对象之间的锁顺序死锁 123456789101112131415161718192021222324252627282930313233343536373839404142public class Taxi { @GuardedBy(\"this\") private Point location, destination; private final Dispatcher dispatcher; public Taxi(Dispatcher dispatcher) { this.dispatcher = dispatcher; } public synchronized Point getLocation() { return location; } public synchronized void setLocation(Point location) { this.location = location; if (location.equals(destination)) { dispatcher.notifyAvailable(this); } }}class Dispatcher { private final Set<Taxi> taxis; private final Set<Taxi> availableTaxis; Dispatcher() { taxis = new HashSet<>(); availableTaxis = new HashSet<>(); } public synchronized void notifyAvailable(Taxi taxi) { availableTaxis.add(taxi); } public synchronized Image getImage() { Image image = new BufferedImage(100, 100, ColorSpace.TYPE_2CLR); for (Taxi taxi : taxis) { //image.drawMarker(taxi.getLocation()); // 将taxi绘制在image上 } return image; }} 尽管没有任何方法显示地获取两个锁,但setLocation和getImage等方法的调用者都会获得两个锁。两个方法以先不同的顺序分别获得两个锁,当两线程同时调用两个方法时可能会产生死锁。 在LeftRightDeadlock或transferMoney中要查找到死锁是比较简单的,只需要找出那些需要获取两个锁的方法。然而要在上面的代码清单中查找死锁则比较困难:如果在持有锁的情况下调用某个外部方法,那么就需要警惕死锁。 如果持有锁时调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。 开放调用如果在调用个方法时不需要持有锁,那么这种调用被称为开放调用。依赖于开用手类通常能表现出更好的行为,并且与那些在调用方法时需要持有锁的类相比,也更易于编写。 通过开放调用来避免在相互协作的对象之间产生死锁 123456789101112131415161718192021public void setLocation2(Point location) { boolean reachedDestination; synchronized (this) { this.location = location; reachedDestination = location.equals(destination); } if (reachedDestination) { dispatcher.notifyAvailable(this); }}public Image getImage2() { Set<Taxi> copy; synchronized (this) { copy = new HashSet<>(taxis); } Image image = new BufferedImage(100, 100, ColorSpace.TYPE_2CLR); for (Taxi taxi : copy) { //image.drawMarker(taxi.getLocation()); // 将taxi绘制在image上 } return image;} 死锁的避免与诊断在使用细粒度锁的程序中,可以通过使用一种两阶段策略来检查代码中的死锁:首先,找出在什么地方将获取多个锁,然后对所有这些实例进行全局分析,从而确保它们在整个过程中获取锁的顺序都保持一致。尽可能地使用开放调用,这能极大地简化分析过程。 支持定时的锁显式的使用Lock类中的tryLock功能来代替内置锁机制。显式锁相比内置锁可以指定一个超时时限,在等待超过这个时间后tryLock会返回一个失败信息。 通过线程转储信息来分析死锁其他活跃性危险除了死锁外的其他活跃性危险,包括:饥饿、丢失信号和活锁等。 饥饿当线程由于无法访问它所需要的资源而不能继续执行时,就发生了饥饿。如果在Java中对线程的优先级使用不当,或者在持有锁时执行一些无法结束的结构(无限循环,或无限等待),那么也可能导致饥饿,因为其他需要这个锁的线程将无法得到它。 要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程度中,都可以使用默认的线程优先级。 糟糕的响应性不良的锁管理也可能导致糟糕的响应性,如果某个线程长时间占有一个锁,而其他想要访问这个容器的线程就必须等待很长时间。 活锁活锁是另一种形式的活跃性问题,该问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败。比如在处理事务时,由于异常处理失败发生回滚,并将它重新放到队列的开头。如果再次失败又回滚再放入队列重新处理。 当多个相互协作的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程都无法继续执行时,就发生了活锁。","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"},{"name":"并发编程实战","slug":"并发编程实战","permalink":"http://blog.xxyxpy.pub/tags/并发编程实战/"}]},{"title":"Http,Tcp/Ip,Socket,Udp","slug":"java/网络编程/Http,Tcp.Ip,Socket,Udp","date":"2018-09-26T16:00:00.000Z","updated":"2018-09-27T05:40:25.893Z","comments":true,"path":"2018/09/27/java/网络编程/Http,Tcp.Ip,Socket,Udp/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/27/java/网络编程/Http,Tcp.Ip,Socket,Udp/","excerpt":"七层网络模型 从下往上:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。 Ip 网络层协议 Tcp和Udp使用Ip协议从一个网络传送数据包到另一个网络。 Tcp 传输控制协议 传输层协议,主要解决数据如何在网络中传输 Http 应用层协议 应用层协议,主要解决如何包装数据 Socket 对Tcp/Ip协议的封装和应用,Socket本身不是协议,而是一个调用接口(API) 通过Socket我们才能使用Tcp/Ip协议,其在接口设计时,就希望也能适应其他的网络协议。所以说Socket的出现只是使得程序员更方便地使用Tcp/Ip协议栈","text":"七层网络模型 从下往上:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。 Ip 网络层协议 Tcp和Udp使用Ip协议从一个网络传送数据包到另一个网络。 Tcp 传输控制协议 传输层协议,主要解决数据如何在网络中传输 Http 应用层协议 应用层协议,主要解决如何包装数据 Socket 对Tcp/Ip协议的封装和应用,Socket本身不是协议,而是一个调用接口(API) 通过Socket我们才能使用Tcp/Ip协议,其在接口设计时,就希望也能适应其他的网络协议。所以说Socket的出现只是使得程序员更方便地使用Tcp/Ip协议栈 Udp 用户数据报协议 Http是基于Tcp和Udp的,在他们的上层。 Http是利用Tcp在两台电脑(通常是web服务器和客户端)之间传输信息的协议,使用ip协议来连接网络。客户端使用web浏览器发起Http请求给web服务器。 一、什么是TCP连接的三次握手 第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认; 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。 理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。 断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开) 二、利用Socket建立网络连接的步骤 建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。 套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。 1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。 2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。 为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。 3、连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。 而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。 三、HTTP链接的特点 HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。 HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。 四、TCP和UDP的区别 1、TCP是面向链接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但TCP的三次握手在最低限度上(实际上也很大程度上保证了)保证了连接的可靠性; 而UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说UDP是无连接的、不可靠的一种数据传输协议。 2、也正由于1所说的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。 知道了TCP和UDP的区别,就不难理解为何采用TCP传输协议的MSN比采用UDP的QQ传输文件慢了,但并不能说QQ的通信是不安全的, 因为程序员可以手动对UDP的数据收发进行验证,比如发送方对每个数据包进行编号然后由接收方进行验证啊什么的, 即使是这样,UDP因为在底层协议的封装上没有采用类似TCP的“三次握手”而实现了TCP所无法达到的传输效率。 参考: http 、 Tcp/Ip 、Socket 、Udp 区别 TCP、UDP、HTTP、SOCKET之间的区别","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"网络编程","slug":"网络编程","permalink":"http://blog.xxyxpy.pub/tags/网络编程/"}]},{"title":"并发编程实战-07线程池的使用","slug":"java/并发编程/07线程池的使用","date":"2018-09-24T16:00:00.000Z","updated":"2018-09-26T09:18:19.220Z","comments":true,"path":"2018/09/25/java/并发编程/07线程池的使用/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/25/java/并发编程/07线程池的使用/","excerpt":"在任务执行和策略之间的隐性耦合只有当任务是同类型并且相互独立时,线程池的性能才能达到最佳。如果将运行时间较长的与运行时间较短的任务混合在一起,那么除非线程池很大,否则可能造成“拥堵”。如果提交的任务依赖于其他任务,那么除非线程池无限大,否则将可能造成死锁。 线程饥饿死锁在线程池中,如果任务依赖于其他任务,那么可能产生死锁。在单线程的Executor中,如果一个任务将另一个任务提交到同一个Executor,并且等待这个被提交任务的结果,那么通常会引发死锁。第二个任务停留在工作者队列中,并等待第一个任务完成,而第一个任务又无法完成,因为它在等待第二个任务的完成。在更大的线程池中,如果所有正在执行任务的线程都由于等待其他仍处于工作队列中的任务而阻塞,那么会发生同样的问题。这种现象被称为线程饥饿死锁,只要线程池中的任务需要无限期地等待一些必须由池中的其他任务才能提供的资源或条件,例如某个任务等待另一个任务的返回值或执行结果,那么除非线程池足够大,否则将发生线程饥饿死锁。","text":"在任务执行和策略之间的隐性耦合只有当任务是同类型并且相互独立时,线程池的性能才能达到最佳。如果将运行时间较长的与运行时间较短的任务混合在一起,那么除非线程池很大,否则可能造成“拥堵”。如果提交的任务依赖于其他任务,那么除非线程池无限大,否则将可能造成死锁。 线程饥饿死锁在线程池中,如果任务依赖于其他任务,那么可能产生死锁。在单线程的Executor中,如果一个任务将另一个任务提交到同一个Executor,并且等待这个被提交任务的结果,那么通常会引发死锁。第二个任务停留在工作者队列中,并等待第一个任务完成,而第一个任务又无法完成,因为它在等待第二个任务的完成。在更大的线程池中,如果所有正在执行任务的线程都由于等待其他仍处于工作队列中的任务而阻塞,那么会发生同样的问题。这种现象被称为线程饥饿死锁,只要线程池中的任务需要无限期地等待一些必须由池中的其他任务才能提供的资源或条件,例如某个任务等待另一个任务的返回值或执行结果,那么除非线程池足够大,否则将发生线程饥饿死锁。 运行时间较长的任务如果任务阻塞时间过长,那么即使不出现死锁,线程池的响应性也会变得糟糕。执行时间较长的任务不仅会造成线程池堵塞,甚至还会增加执行时间较短任务的服务时间。如果线程池中线程的数量远小于稳定状态下执行时间较长任务的数量,那么到最后可能所有的线程都会运行这些执行时间较长的任务,从而影响整体的响应性。 有一项技术可以缓解执行时间较长任务造成的影响,即限定任务等待资源的时间,而不要无限制地等待。在平台类库的大多数可阻塞方法中,都同时定义了限时版本和无限时版本,例如Thread.join、BlockingQueue.put、CountDownLatch.await以及Selector.select等。如果等待超时,那么可以把任务标识为失败,然后中止任务或者将任务重新放回队列以便随后执行。这样,无论任务是否成功,这种方法都能确保任务总能继续执行下去,并将线程释放同来以执行一些能更快完成的任务。如果在线程池中总是充满了被阻塞的任务,那么也可能表明线程池的规模过小。 设置线程池的大小通过Runtime.availableProcessors来获取CPU数量,对于计算密集型的线程池大小可以设置为CPU数量+1,额外的1个线程可以使当线程由于页缺失故障或其它原因而暂时时,确保CPU的时钟周期不会被浪费。 配置ThreadPoolExecutorThreadPoolExecutor为一些Executor提供了基本的实现,这些实现是由工厂方法返回的。ThreadPoolExecutor是一个灵活的、稳定的线程池,允许进行各种定制。 ThreadPoolExecutor的通用构造函数 1234567public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {...} 线程的创建与销毁线程池的基本大小、最大大小以及存活时间等因素共同负责线程的创建与销毁。基本大小就是线程池的目标大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。最大大小表示可同时活动的线程数量的上限。如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过了基本大小时这个线程将被终止。 newFixedThreadPool工厂方法将线程池的基本大小和最大大小设置为参数中指定的值,而且创建的线程池不会超时 newCachedThreadPool工厂方法将线程池的最大大小设置为Integer.MAX_VALUE,而将基本大小设置为0,并且将超时时间设置为1分钟,这种方法创建出来的线程池可以被无限扩展,并且当需求降低时会自动收缩 其他形式的线程池可以通过显示的ThreadPoolExecutor构造函数来构造 管理队列任务在有限的线程池中会限制可并发执行的任务数量。如果无限制地创建线程,那么将导致不稳定性,此时可以通过采用固定大小的线程池来解决这个问题。然而,这个方案并不完整。在高负载情况下,应用程序仍可能耗尽资源,只是出现问题概率较小。(如果客户提交给服务器请求的速率超过了服务器处理速率,仍可能会耗尽资源) ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务。基本的任务排队方法有3种:无界队列、有界队列和同步移交。队列的选择与其他的配置参数有关,例如线程池大小等。 newFixedThreadPool和newSingleThreadExecutor在默认情况下将使用一个无界的LinkedBlockingQueue.为了防止队列无限制带来的内存暴涨,更稳妥的资源管理策略是使用有界队列,例如ArrayBlockingQueue、有界的LinkedBlockingQueue、PriorityQueue。有界队列虽然避免的资源耗尽的问题,但面临当队列填满后,新的任务将如何处理的问题?此时线程池的大小和队列的大小需要一起作出调整。如果线程池较小而队列较大,那么有助于减少内存使用量,降低CPU使用率,同时还可以减少上下文切换,但付出的代价是可能会限制吞吐量。 饱和策略通过setRejectedExecutionHandler来设置饱和策略,仅当有限队列被填满后饱和策略才会生效。JDK提供了如下几种饱和策略: 中止(Abort)策略,这是默认的策略,该策略会拋出未检查的RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码; 抛弃策略(Discard)会抛弃无法提交的任务; 抛弃最旧的(Discard-Oldest)策略会抛弃下一个将被执行的任务,然后尝试提交新的任务。所以当这个策略作用于优先队列时,优先级越高越容易被丢弃; 调用者运行(Caller-Runs)策略实现了一种调节机制,该策略既不会抛弃任务也不会抛出异常,而是将某些任务退回到调用者,从而降低新任务的流量。一般情况下此时主线程将用于处理这些任务,由于执行任务需要一定的时间,因此主线程至少一段时间不能提交任何任务。所以此时外部的请求会被保存在TCP层的队列中而不是在应用程序的队列中。如果持续过载,TCP层会发现它的队列被填满,同样开始抛弃任务。 在创建Executor时可以指定策略。 创建一个固定大小的线程池,并采用有界队列以及“调用者运行”饱和策略 123ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 100, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(300));executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 当工作队列被填满后没有预定义的饱和策略来阻塞execute。然而,通过使用Semaphore(信号量)来限制任务的到达率,就可以实现这个功能。 下面的Executor使用了一个无界队列,并设置信号量上限为线程池的大小,以此来控制队列占满时任务的提交。 123456789101112131415161718192021222324252627public class BounderExecutor { private final Executor exec; private final Semaphore semaphore; public BounderExecutor(Executor exec, int bound) { this.exec = exec; this.semaphore = new Semaphore(bound); } public void submitTask(final Runnable command) throws InterruptedException { semaphore.acquire(); try { exec.execute(new Runnable() { @Override public void run() { try { command.run(); } finally { semaphore.release(); } } }); } catch (RejectedExecutionException e) { semaphore.release(); } }} 线程工厂每当线程池需要创建一个线程时,都是通过线程工厂方法来完成的。默认的线程工厂方法将创建一个新的、非守护的线程,并且不包含特殊的配置信息。通过指定一个线程工厂方法,可以定制线程池的配置信息。 123public interface ThreadFactory { Thread newThread(Runnable r);} 首先需要先定制一个Thread基类。此类实现的功能包括:为线程指定名字,设置自定义UncaughtExceptionHandler向Logger中写入信息,维护一些统计信息(包括有多少线程被创建和销毁),以及在线程被创建或者终止时把调试消息写入日志。 123456789101112131415161718192021222324252627282930313233343536373839404142public class MyAppThread extends Thread { public static final String DEFAULT_NAME = \"MyAppThread\"; private static volatile boolean debugLifecycle = false; private static final AtomicInteger created = new AtomicInteger(); private static final AtomicInteger alive = new AtomicInteger(); private static Logger log = Logger.getAnonymousLogger(); public MyAppThread(Runnable r) { this(r, DEFAULT_NAME); } public MyAppThread(Runnable runnable, String name) { super(runnable, name + \"-\" + created.incrementAndGet()); setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { log.log(Level.SEVERE, \"UNCAUGHT in thread \" + t.getName(), e); } }); } public void run() { boolean debug = debugLifecycle; if (debug) { log.log(Level.FINE, \"Created \" + getName()); } try { alive.incrementAndGet(); super.run(); } finally { alive.decrementAndGet(); if (debug) { log.log(Level.FINE, \"Exiting \" + getName()); } } } public static int getThreadsCreated() { return created.get(); } public static int getThreadsAlive() { return alive.get(); } public static boolean getDebug() { return debugLifecycle; } public static void setDebug(boolean b) { debugLifecycle = b; }} 自定义线程工厂 123456789101112public class MyThreadFactory implements ThreadFactory { private final String poolName; public MyThreadFactory(String poolName) { this.poolName = poolName; } @Override public Thread newThread(Runnable r) { return new MyAppThread(r, poolName); }} 在调用构造函数后再定制ThreadPoolExecutor在调用完ThreadPoolExecutor构造函数之后还可以通过设置函数(Setter)来修改大部分传递给构造函数的参数(线程池的基本大小、最大大小、存活时间、线程工厂以及拒绝执行处理器)。如果Executor是通过Executors中的某个(newSingleThreadExecutor除外)工厂方法创建的,那么可以将结果的类型转换为ThreadPoolExecutor以访问设置器。 123456ExecutorService exec =Executors.newCachedThreadPool();if (exec instanceof ThreadPoolExecutor) { ((ThreadPoolExecutor) exec).setCorePoolSize(10);} else { throw new AssertionError(\"Oops, bad assumption\");} 扩展ThreadPoolExecutorThreadPoolExecutor是可扩展的,它提供了几个可以在子类化中改写的方法:beforeExecute、afterExecute和terminated,这些方法可以用于扩展ThreadPoolExecutor的行为。 在执行任务的线程中将调用beforeExecute和afterExecute等方法,在这些方法中还可以添加日志、计时、监视或统计信息收集的功能。无论任务是从run中正常返回还是拋出异常而返回,afterExecute都会被调用。如果beforeExecute拋出一个RuntimeException,那么任务将不被执行,并且afterExecute也不会被调用。 在线程池完成关闭操作时调用terminated,也就是在所有任务都已经完成并且所有工作者线程也已经关闭后。terminated可以用来释放Executor在其生命周期里分配的各种资源,此外还可以执行发送通知、记录日志或者收集finalize统计信息等操作。","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"},{"name":"并发编程实战","slug":"并发编程实战","permalink":"http://blog.xxyxpy.pub/tags/并发编程实战/"}]},{"title":"一致性Hash算法","slug":"java/tool/一致性Hash算法","date":"2018-09-24T16:00:00.000Z","updated":"2018-09-25T07:36:37.462Z","comments":true,"path":"2018/09/25/java/tool/一致性Hash算法/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/25/java/tool/一致性Hash算法/","excerpt":"业务场景假设有从数据库中读取数据,为了缓解数据库的压力,使用分布式缓存来对数据进行缓存操作。当下次读取时发现缓存中有直接从缓存中输出,从而降低数据库的压力。 普通Hash算法为了让实现缓存的负载均衡最常见的方法是根据机器的数量进行取模(Mod)实现平均分配。 hash(对象) mode n = 存放的机器m 在正常情况下此方式不会存在问题,但考虑如果原来是3台机器,业务量上升需要增加机器或者其中一台宕机减少机器。使用取模算法最大的问题是缓存命中的概率会急剧的降低,此时产生的问题类似于HashMap的扩容,需要重排。那对于db的压力也可想而知,db穿透的后果简单的是业务响应延迟,严重的可能是应用宕机。","text":"业务场景假设有从数据库中读取数据,为了缓解数据库的压力,使用分布式缓存来对数据进行缓存操作。当下次读取时发现缓存中有直接从缓存中输出,从而降低数据库的压力。 普通Hash算法为了让实现缓存的负载均衡最常见的方法是根据机器的数量进行取模(Mod)实现平均分配。 hash(对象) mode n = 存放的机器m 在正常情况下此方式不会存在问题,但考虑如果原来是3台机器,业务量上升需要增加机器或者其中一台宕机减少机器。使用取模算法最大的问题是缓存命中的概率会急剧的降低,此时产生的问题类似于HashMap的扩容,需要重排。那对于db的压力也可想而知,db穿透的后果简单的是业务响应延迟,严重的可能是应用宕机。 一致性Hash算法一致性Hash算法是为了解决上面问题的方法,它可以保证机器增加或减少时,对缓存访问命中的概率影响减至最小。 一致性Hash环这个环的起点是0,终点是2^32-1,起点和终点连接,环的中间整数按逆时针分布。 放置对象将对象通过hash算法得到它的hash值(范围为0~2^32-1),根据hash后的结果放置到环上相应的点。 放置机器同样使用hash算法得到机器的hash值(可以hash机器的ip地址,范围为0~2^32-1),根据hash后的结果放置到环上相应的点。 为对象选择机器将对象和机器都放置到hash环上,沿顺时针方向查找距离某个对象hash值最近的机器,即是这个对象的所属机器。 机器增减情况假如此时有机器的增减,只需要重新分配部分对象到新的机器上。若有C1,C2,C3三台机器: 增加C4到C2和C3之间,只有C2和C4之间的对象需要重新分配到机器C4,其它对象仍在原来机器上; 减少C2节点,只需要将原来C1到C2之间的对象重新分配到C3。 虚拟节点一致性hash在使用过程中有可能会存在hash值不合理,负载分配不均匀的问题。为此可以引入虚拟节点来解决负载不均衡的问题。将每台物理机器虚拟为一组虚拟机器,将虚拟机器放置到hash环上,如果需要确定对象的机器需要先确定对象的虚拟机器,再由虚拟机器确定物理机器。 参考: 一致 Hash 算法分析 一致性Hash原理解剖","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"小知识","slug":"小知识","permalink":"http://blog.xxyxpy.pub/tags/小知识/"}]},{"title":"并发编程实战-06取消与关闭","slug":"java/并发编程/06取消与关闭","date":"2018-09-19T16:00:00.000Z","updated":"2018-09-26T09:18:12.252Z","comments":true,"path":"2018/09/20/java/并发编程/06取消与关闭/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/20/java/并发编程/06取消与关闭/","excerpt":"要使任务和线程能安全、快速、可靠地停止下来,并不是一件容易的事。Java没有任何机制来安全地终止线程。但它提供了中断,它能够一个线程终止另一个线程的工作。 任务取消如果外部代码能在某个操作正常完成之前将其转入“完成”状态,那么这个操作就可以称为可取消的。 在Java中没有一种安全的抢占式方法来停止线程,因此也就没有安全的抢占式方法来停止任务。只有一些协作式的机制,使请求取消的任务和代码都遵循一种协商好的协议。其中一种协作机制能设置某个“已请求取消”标志,而任务将定期查看该标志。如果设置了此标志,任务将提前结束。","text":"要使任务和线程能安全、快速、可靠地停止下来,并不是一件容易的事。Java没有任何机制来安全地终止线程。但它提供了中断,它能够一个线程终止另一个线程的工作。 任务取消如果外部代码能在某个操作正常完成之前将其转入“完成”状态,那么这个操作就可以称为可取消的。 在Java中没有一种安全的抢占式方法来停止线程,因此也就没有安全的抢占式方法来停止任务。只有一些协作式的机制,使请求取消的任务和代码都遵循一种协商好的协议。其中一种协作机制能设置某个“已请求取消”标志,而任务将定期查看该标志。如果设置了此标志,任务将提前结束。 质数生成器-使用volatile类型的域来保存取消状态 12345678910111213141516171819202122232425public class PrimeGenerator implements Runnable { private final List<BigInteger> primes = new ArrayList<>(); private volatile boolean cancelled; @Override public void run() { BigInteger p = BigInteger.ONE; while (!cancelled) { p = p.nextProbablePrime(); synchronized (this) { primes.add(p); } } } public void cancel() { cancelled = true; } public synchronized List<BigInteger> get() { return new ArrayList<>(primes); }} 一个仅运行一秒钟的质数生成器 12345678910List<BigInteger> aSecondPrimes() throws InterruptedException { PrimeGenerator generator = new PrimeGenerator(); new Thread(generator).start(); try { SECONDS.sleep(1); } finally { generator.cancel(); } return generator.get();} 中断PrimeGenerator中采用取消标志的取消机制。但如果使用这种方法的任务调用了一个阻塞方法,例如BlockingQueue.put,那么可能会产生严重的问题——任务可能永远没有机会检查取消标志,因此永远不会结束。 下面的BrokenPrimeProducer代码说明了这个问题。生产者线程生产质数用于消费者线程消费。如果生产者生产的速度超过消费速度那么队列被填满时生产者的put方法将会阻塞。如此消费者此时取消了消费任务,并且取消生产者任务,那么将会发生由于put方法阻塞无法判断取消标志的情况,一直无法结束。 不可取消的操作把生产者置于阻塞的操作中 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970public class BrokenPrimeProducer extends Thread { private final BlockingQueue<BigInteger> queue; private volatile boolean cancelled; public BrokenPrimeProducer(BlockingQueue<BigInteger> queue) { this.queue = queue; } @Override public void run() { try { BigInteger p = BigInteger.ONE; while (!cancelled) { p = p.nextProbablePrime(); synchronized (this) { queue.put(p); } } } catch (InterruptedException e) { e.printStackTrace(); } } public void cancel() { cancelled = true; }}class BrokenPrimeConsumer { private volatile boolean flag = true; void consumerPrimes() { BlockingQueue<BigInteger> primes = new ArrayBlockingQueue<BigInteger>(100); BrokenPrimeProducer producer = new BrokenPrimeProducer(primes); producer.start(); try { while (flag) { consume(primes.take()); } // 当停止消费了,触发生产者取消任务 producer.cancel(); } catch (InterruptedException e) { e.printStackTrace(); } } private void consume(BigInteger bigInteger) throws InterruptedException { Thread.sleep(100); System.out.println(bigInteger); } void cancel() { flag = false; }}public class UseBrokenPrime { public static void main(String[] args) throws InterruptedException { BrokenPrimeConsumer primeConsumer = new BrokenPrimeConsumer(); new Thread(new Runnable() { @Override public void run() { primeConsumer.consumerPrimes(); } }).start(); Thread.sleep(5000); primeConsumer.cancel(); // 生产者线程无法被取消,因为put操作阻塞了 }} 每个线程都有一个boolean类型的中断状态。当中断线程时,这个中断状态将被设置为true。 Thread中的中断方法 12345public class Thread { public void interrupt() { } // 中断目标线程 public boolean isInterrupted() { } // 返回线程的中断状态 public static boolean interrupted() { } // 清除线程的中断状态并返回它之前的值} 阻塞库方法,如Thread.sleep和Object.wait等,都会检查线程何时中断,并且在发现中断时提前返回。它们在响应中断时执行的操作包括:清除中断状态,拋出InterruptedException,表示阻塞操作由于中断而提前结束。 当线程在非阻塞状态下中断时,它的中断状态将被设置。然后根据将被取消的操作来检查中断状态以判断发生了中断。通过这样的方法,中断操作将变得“有黏性”——如果不触发InterruptedException,那么中断状态将一直保持,直到明确地清除中断状态。 调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。 对中断操作的正确理解是:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己(这些时刻被称为取消点)。有些方法,如wait、sleep和join等,将严格地处理这种请求,当它们收到中断请求或者在开始执行时发现某个已被设置好的中断状态时,将拋出一个异常。 使用静态的interrupted时要小心,它会清除当前线程的中断状态。如果调用时返回了true,那么除非你想屏蔽这个中断,否则必须对它进行处理——可以拋出InterruptedException或者通过再次调用interrupt来恢复中断状态。 通过中断来取消 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051public class BrokenPrimeProducer extends Thread { private final BlockingQueue<BigInteger> queue; public BrokenPrimeProducer(BlockingQueue<BigInteger> queue) { this.queue = queue; } @Override public void run() { try { BigInteger p = BigInteger.ONE; //while (!cancelled) { while (!Thread.currentThread().isInterrupted()) { p = p.nextProbablePrime(); synchronized (this) { queue.put(p); } } } catch (InterruptedException e) { e.printStackTrace(); } }}class BrokenPrimeConsumer { private volatile boolean flag = true; void consumerPrimes() { BlockingQueue<BigInteger> primes = new ArrayBlockingQueue<BigInteger>(100); BrokenPrimeProducer producer = new BrokenPrimeProducer(primes); producer.start(); try { while (flag) { consume(primes.take()); } // 当停止消费了,触发生产者取消任务 //producer.cancel(); producer.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } private void consume(BigInteger bigInteger) throws InterruptedException { Thread.sleep(100); System.out.println(bigInteger); } void cancel() { flag = false; }} 中断策略中断策略规定线程如何解释某个中断请求——当发现中断请求时,应该做哪些工作(如果需要的话),哪些工作单元对于中断来说是原子操作,以及以多快的速度来响应中断。 响应中断当调用可中断的阻塞函数时,有两种策略可以处理 传递异常(可能在执行某个特定于任务的清除操作之后),从而使你的方法也成为可中断的阻塞方法; 恢复中断状态,从而使调用栈中的上层代码能够对其进行处理。 将InterruptedException传递给调用者 12345BlockingQueue<Task> queue;...public Task getNextTask() throws InterruptedException { return queue.take();} 如果不想或无法传递InterruptedException,需要寻找另一种方式来保存中断请求。一种标准的方法是通过再次调用interrupt来恢复中断状态。不能直接屏蔽InterruptedException,例如在catch捕获到异常却不做任何处理,除非你在代码中实现了线程的中断策略。 只有实现了线程中断策略的代码才可以屏蔽中断请求。在常规的任务和库代码中都不应该屏蔽中断请求 对于一些不支持取消但仍可以调用可中断阻塞方法的操作,它们必须在循环中调用这些方法,并在发现中断后重新尝试。在这种情况下,它们应该在本地保存中断状态,并在返回前恢复状态而不是捕获InterruptedException时恢复状态。 不可取消的任务在退出前恢复状态 123456789101112131415161718public Task getNextTask(BlockingQueue<Task> queue) { boolean interrupted = false; try { while (true) { try { return queue.take(); } catch (InterruptedException e) { interrupted = true; // 重新尝试 e.printStackTrace(); } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } }} 通过Future来实现取消取消正在运行的任务 ExecutorService.submit将返回一个Future来描述任务,Future的cancel方法接收一个boolean类型参数,此方法可以用来(尝试)终止一个任务 如果任务运行之前调用了该方法,那么任务就不会被运行; 如果任务已经完成或者已经被取消,那么该方法方法不起作用; 如果任务正在运行,并且 cancel 传入参数为 true,那么便会去终止与 Future 关联的任务。 cancel(false) 与 cancel(true)的区别在于,cancel(false) 只 取消已经提交但还没有被运行的任务(即任务就不会被安排运行);而 cancel(true) 会取消所有已经提交的任务,包括 正在等待的 和 正在运行的 任务。 停止基于线程的服务日志服务 不支持关闭的生产者-消费者日志服务 12345678910111213141516171819202122232425262728293031323334353637public class LogWriter { private final BlockingQueue<String> queue; private final LoggerThread logger; public LogWriter(PrintWriter writer) { this.queue = new LinkedBlockingDeque<>(100); this.logger = new LoggerThread(writer); } public void start() { logger.start(); } public void log(String msg) throws InterruptedException { queue.put(msg); } private class LoggerThread extends Thread { private final PrintWriter writer; private LoggerThread(PrintWriter writer) { this.writer = writer; } public void run() { try { while (true) { writer.println(queue.take()); } } catch (InterruptedException e) { e.printStackTrace(); } finally { writer.close(); } } }} 当取消一个生产者-消费者操作时,需要同时取消生产者和消费者。 添加可靠的取消操作 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566public class LogService { private final BlockingQueue<String> queue; private final LoggerThread logger; @GuardedBy(\"this\") private boolean isShutDown; @GuardedBy(\"this\") private int reservations; public void stop() { synchronized (this) { isShutDown = true; } logger.interrupt(); } public LogService(PrintWriter writer) { this.queue = new LinkedBlockingDeque<>(100); this.logger = new LoggerThread(writer); } public void start() { logger.start(); } public void log(String msg) throws InterruptedException { synchronized (this) { // stop后不再写入 if (isShutDown) { throw new IllegalStateException(\"已关闭\"); } ++reservations; } queue.put(msg); } private class LoggerThread extends Thread { private final PrintWriter writer; private LoggerThread(PrintWriter writer) { this.writer = writer; } public void run() { try { while (true) { synchronized (LogService.this) { // 即使stop必须消费完才退出 if (isShutDown && reservations == 0) { break; } } writer.println(queue.take()); synchronized (LogService.this) { // 消费一个计数器-1 --reservations; } } } catch (InterruptedException e) { // retry e.printStackTrace(); } finally { writer.close(); } } }} 关闭ExecutorService可以将管理线程的工作委托给一个ExecutorService,而不是由其自行管理。当执行shutdown方法时会先停止接收新的任务,将正在执行和队列中的待执行任务执行完成之后关闭线程池。 1234567891011121314151617181920212223242526public class LogService2 { private final PrintWriter writer; private final ExecutorService exec = Executors.newSingleThreadExecutor(); public void stop() throws InterruptedException { try { exec.shutdown(); exec.awaitTermination(60, TimeUnit.SECONDS); } finally { } } public LogService2(PrintWriter writer) { this.writer = writer; } public void log(String msg) { exec.execute(new Runnable() { @Override public void run() { writer.println(msg); } }); }} 毒丸对象毒丸是指一个放在队列上的对象,其含义是:当得到空上对象时,立即停止。在FIFO队列中,毒丸对象将确保消费者在关闭之前首先完成队列中的所有工作,在提交毒丸对象之前提交的所有工作都会被处理,而生产者在提交了毒丸对象之后,将不会再提交任何工作。 此方法可以扩展到多个生产者,当各个生产者都放入一个毒丸对象,一个消费者接收到N个毒丸对象时才停止。同时可以扩展到多个消费的情况,当多个消费者总共接收到N个毒丸对象时才停止。只有在无界队列中,毒丸对象才能可靠的工作。 只执行一次的服务某个方法需要处理一批任务,当所有的任务都处理完成之后才返回。 123456789101112131415161718192021222324boolean checkMail(Set<String> hosts, long timeout, TimeUnit unit) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); final AtomicBoolean hasNewMail = new AtomicBoolean(false); try { for (String host : hosts) { exec.execute(new Runnable() { @Override public void run() { if (checkMail(host)) { hasNewMail.set(true); } } }); } } finally { exec.shutdown(); exec.awaitTermination(timeout, unit); } return hasNewMail.get();}private boolean checkMail(String host) { return true;} shutdownNow的局限性当通过shutdownNow来强行关闭ExecutorService时,它会尝试取消正在执行的任务,并返回已提交但未开始的任务,从而将这些任务写入日志或者保存起来以便之后进行处理。 可以通过在任务执行过程中监控线程的中断状态来判断任务是否是在关闭后取消的。 1234567try { runnable.run();} finally { if(isShutdown() && Thread.currentThread.isInterrupted()) { taskCancelledAtShutdown.add(runnable); // 添加到取消列表 }} 需要注意误报的问题:一些被认为已取消的任务实际上已经执行完成。这个问题的原因在于,在任务执行最后一条指令以及线程池将任务记录为结束的两个时刻之间,线程池可能已经被关闭。如果任务是幂等的,那么不存在问题。否则需要注意这种风险。 JVM关闭关闭钩子关闭钩子是指通过Runtime.addShutdownHook注册的但尚未开始的线程。这些钩子可以用于实现服务或者应用程序的清理工作,例如删除临时文件,或者清除无法由操作系统自动清除的资源。 JVM既可以正常关闭,也可以强行关闭。正常关闭的触发方式有多种,包括:当最后一个“正常(非守护)”线程结束时,或者当调用了System.exit时,或者通过其他特定于平台的方法关闭时(例如发送了SIGINT信号或者键入Ctrl-C)。 在正常关闭中,JVM首先调用所有已注册的关闭钩子。JVM并不能保证关闭钩子的调用顺序。在关闭应用程序线程时,如果有(守护或者非守护)线程仍然在执行,那么这些线程接下来将与关闭进程并发执行。当所有的关闭钩子都执行结束时,如果runFinalizersOnExit为true【通过Runtime.runFinalizersOnExit(true)设置】,那么JVM将运行这些Finalizer(对象重写的finalize方法),然后再停止。JVM不会停止或中断任何在关闭时仍然运行的应用程序线程。当JVM最终结束时,这些线程将被强行结束。如果关闭钩子或者Finalizer没有执行完成,那么正常关闭进程“挂起”并且JVM必须被强行关闭。当JVM被强行关闭时,只是关闭JVM,并不会运行关闭钩子。 在编写关闭钩子时,需要注意以下几点: 关闭钩子应该是线程安全的:它们在访问共享数据时,必须使用同步机制,并且小心地避免发生死锁,这与其他并发代码的要求相同。 关闭钩子不应该对应用程序的状态(例如,其他服务是否已经关闭,或者所有的正常线程是否已经执行完成)或者JVM的关闭原因做出任何假设,因此在编写关闭钩子的代码时,必须考虑周全。 关闭钩子必须尽快退出,因为 他们会延迟JVM的结束时间,而用户可能希望JVM尽快 终止 通过注册一个关闭钩子来停止日志服务 123456789101112public void start() { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { LogService.this.stop(); } catch (InterruptedException e) { e.printStackTrace(); } } });}","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"},{"name":"并发编程实战","slug":"并发编程实战","permalink":"http://blog.xxyxpy.pub/tags/并发编程实战/"}]},{"title":"并发编程实战-05任务执行","slug":"java/并发编程/05任务执行","date":"2018-09-17T16:00:00.000Z","updated":"2018-09-26T09:18:28.113Z","comments":true,"path":"2018/09/18/java/并发编程/05任务执行/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/18/java/并发编程/05任务执行/","excerpt":"在线程中执行任务串行地执行任务 串行的web服务器 123456789101112public class SingleThreadWebServer { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { Socket connection = socket.accept(); handleRequest(connection); } } private static void handleRequest(Socket connection) { }} 每次只能处理一个请求,第二个请求过来时被阻塞。执行效率低。","text":"在线程中执行任务串行地执行任务 串行的web服务器 123456789101112public class SingleThreadWebServer { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { Socket connection = socket.accept(); handleRequest(connection); } } private static void handleRequest(Socket connection) { }} 每次只能处理一个请求,第二个请求过来时被阻塞。执行效率低。 显式地为任务创建线程 在Web服务器中为每个请求启动一个新的线程 123456789101112131415161718public class ThreadPerTaskWebServer { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { Socket connection = socket.accept(); Runnable runnable = new Runnable() { @Override public void run() { handleRequest(connection); } }; new Thread(runnable).start(); } } private static void handleRequest(Socket connection) { }} 对于每一个连接请求都会创建一个新线程来处理请求,而不是在主循环中进行处理。 无限制创建线程的不足在生产环境中,为每个任务分配一个线程这种做法存在一些缺陷,尤其是当需要创建大量的线程时: 线程生命周期的开销非常高。线程的创建和销毁并不是没有代价的。 资源消耗。活跃的线程会消耗系统资源,尤其是内存。大量的线程在竞争cpu资源时还将产生其他的性能开销。 稳定性。不同的平台对可创建线程的总数量会有不同的限制,如果破坏了这些限制(Jvm启动参数、Thread构造函数中请求的栈大小、底层操作系统),那么很可能拋出OOM异常。 Executor框架线程池简化了线程的管理工作,并且java.util.concurrent提供了一种灵活的线程池实现作为Excecutor框架的一部分。在java类库中任务执行的主要抽象不是Thread而是Executor。 Executor接口 123public interface Executor { void execute(Runnable command);} 基于Executor的Web服务器 基于线程池的Web服务器 123456789101112131415161718192021public class TaskExecutionWebServer { private static final int NTHREADS = 100; private static final Executor exec = Executors.newFixedThreadPool(NTHREADS); public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { Socket connection = socket.accept(); Runnable runnable = new Runnable() { @Override public void run() { handleRequest(connection); } }; exec.execute(runnable); } } private static void handleRequest(Socket connection) { }} 线程池线程池通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。另外一个额外的好处是,当请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性。 通过适当调整线程池的大小,创建足够多的线程使处理器保持忙碌状态,同时还可以防止过多线程相互竞争资源而使应用程序内存耗尽或失败。 Executors中的静态工厂方法可用于创建多种不同的线程池: newFixedThreadPool。创建一个固定长度的线程池。每当提交一个会务就创建一个线程,直到达到线程池的最大数量,这时线程池的规模不再变化,如果某个线程由于异常结束,线程池会补充一个新的线程。 newCachedThreadPool。创建一个可缓存的线程池。如果线程池的当前规模超过了处理需求时,那么将回收空闲线程,当需求增加时,则可以添加新的线程,线程池的规模不受任何限制。 newSingleThreadPool。创建一个单线程的Executor。它创建单个工作者线程来执行任务,如果这个线程异常结束,会创建另外一个线程来替代。它能确保依照任务在队列中的顺序来串行执行。 newScheduledThreadPool。创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。 Executor的生命周期 ExecutorService中生命周期管理方法 12345678910111213public interface ExecutorService extends Executor { // 平滑关闭,任务缓慢结束 void shutdown(); // 粗暴关闭,任务立即结束 List<Runnable> shutdownNow(); // 是否已关闭 boolean isShutdown(); // 任务是否全部执行完成 boolean isTerminated(); // 阻塞等待,任务执行完或者超时后解除阻塞 boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;} ExecutorService的生命周期有3种状态:运行、关闭和已终止。 shutdown方法将执行平缓的关闭过程:不再接受新的任务,同时等待已经提交的任务执行完成(包括还没有开始执行的); shutdownNow方法将执行粗暴的关闭:尝试取消所有运行中的任务,并且不再启动队列中尚未开始执行的任务; awaitTermination等待到达终止状态; isTerminated是否已经终止。 在ExecutorService关闭后提交的任务将由“拒绝执行处理器”来处理,它会拋弃任务,或者使用execute方法拋出一个未检查的RejectedExecutionException。 找出可利用的并行任务串行的页面渲染器在对Html文档进行渲染处理时,会遇到文本和图像标签,串行的处理方式是当遇到文本标签时绘制文本元素,当遇到图像标签时预留出矩形的点位空间,在处理完文本之后再下载图像,下载完成之后将其绘制在相应的占位空间中。 这是一个妥妥的串行处理程序。图像下载过程的大部分时间都是在等待I/O操作执行完成,在这期间CPU几乎不做任何工作。因此,这种串行执行方法没有充分地利用CPU,使用户在看到最终页面之前要等待过长时间。通过将问题分解为多个独立的任务并发执行,能够获得更高的CPU利用率和响应灵敏度。 携带结果的任务Callable与FutureExecutor框架使用Runnable作为基本的任务表现形式,但它有一个局限性是不能返回一个值或拋出一个受检查的异常。而使用Callable可以弥补这个缺陷,它主为主入口点(即call)将返回一个值,并可能拋出一个异常。 Runnable和Callable描述的都是抽象的计算任务。这些任务通常是有范围的,即都有一个明确的起始点,并且最终会结束。Executor执行的任务有4个生命周期阶段:创建、提交、开始和完成。由于有些任务可能要执行很长时间,因此通常希望能够取消这些任务。在Executor框架中,已提交但尚未开始的任务可以取消,但对于那些已经开始执行的任务,只有当它们能响应中断时,才能取消。 Future表示一个异步的计算任务,它提供了相应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等。 Callable和Future接口 123456789101112public interface Callable<V> { V call() throws Exception;}public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;} 可以通过许多种方法创建一个Future来描述任务,ExecutorService中的所有submit方法都将返回一个Future,从而将一个Runnable或Callable提交给Executor,并得到一个Future用来获得任务的执行结果或者取消任务。还可以显式地为某个指定的Runnable或Callable实例化一个FutureTask。由于FutureTask实现了Runnable,因此可以将它提交给Executor来执行,或者直接调用它的run方法。 使用Future实现页面渲染器将上面的串行执行修改为异步,在绘制文本节点之前先异步开始进行图像下载。文本绘制完成之后通过Future来get到图像下载的结果,然后渲染图像。 使用Future异步等待图像下载 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152public class FutureRenderer { private final ExecutorService executor = Executors.newFixedThreadPool(100); void renderPage(CharSequence source) { final List<ImageInfo> imageInfos = scanForImageInfo(source); Callable<List<ImageData>> task = new Callable<List<ImageData>>() { @Override public List<ImageData> call() throws Exception { List<ImageData> result = new ArrayList<>(); for (ImageInfo imageInfo : imageInfos) { result.add(imageInfo.downloadImage()); } return result; } }; Future<List<ImageData>> future = executor.submit(task); // 提交任务 renderText(source); // 渲染文本 try { List<ImageData> imageData = future.get(); // get下载过的图像 for (ImageData data : imageData) { renderImage(data); // 渲染图像 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 重新设置线程的中断状态 e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } private void renderImage(ImageData data) { } private void renderText(CharSequence source) { } private List<ImageInfo> scanForImageInfo(CharSequence source) { return new ArrayList<>(); }}class ImageInfo { ImageData downloadImage() { return new ImageData(); }}class ImageData { } 在异构任务并行化中存在的局限FutureRenderer使用了两个任务,其中一个负责渲染文本,另一个负责下载图像。如果渲染文本的速度远远高于下载图像的速度,那么程序的最终性能与串行执行时的性能差别不大。因此,虽然做了许多工作来并发执行异构任务以提高并发度,但从中获得的并发性却是十分有限的。只有当大量相互独立且同构的任务可以并发进行处理时,才能体现出将程序的工作负载分配到多个任务中带来的真正性能提升。 CompletionService:Executor与BlockingQueueCompletionService将Executor和BlockingQueue的功能融合在一起。你可以将Callable任务提交给它来执行,然后使用类似于队列操作的take和poll等方法来获得已完成的结果,而这些结果会在完成时被封装为Futrue。ExecutorCompletionService实现了CompletionService,并将计算部分委托给一个Executor。 ExecutorCompletionService是CompletionService的唯一实现类。它在构造函数中创建一个BlockingQueue来保存计算完成的结果。当计算完成时,调用FutureTask中的done方法。当提交某个任务时,该任务将首先包装为一个QueueingFuture,FutureTask的一个子类(ExecutorCompletionService的内部类),然后再改写了类的done方法(将结果放入BlockingQueue中,这样来完成计算结果的存储)。 ExecutorCompletionService的内部类QueueingFuture 12345678private class QueueingFuture extends FutureTask<Void> { QueueingFuture(RunnableFuture<V> task) { super(task, null); this.task = task; } protected void done() { completionQueue.add(task); } private final Future<V> task;} 使用CompletionService实现页面渲染器上面为了提高页面渲染的速度,将图像下载和文本绘制异步为两个任务执行。为了提高响应的速度,当图像较多可以考虑为每个图像创建一个独立的任务,从而将串行的下载任务转换为并行的任务。CompletionService可用来获取一组任务的结果。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748public class Renderer { private final ExecutorService executor; public Renderer(ExecutorService executor) { this.executor = executor; } void renderPage(CharSequence source) { final List<ImageInfo> imageInfos = scanForImageInfo(source); CompletionService<ImageData> completionService = new ExecutorCompletionService(executor); for (ImageInfo imageInfo : imageInfos) { completionService.submit(new Callable<ImageData>() { @Override public ImageData call() throws Exception { return imageInfo.downloadImage(); } }); } renderText(source); // 渲染文本 // 取图像信息 try { for (int i = 0; i < imageInfos.size(); i++) { Future<ImageData> future = completionService.take(); ImageData imageData = future.get(); renderImage(imageData); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } private void renderImage(ImageData data) { } private void renderText(CharSequence source) { } private List<ImageInfo> scanForImageInfo(CharSequence source) { return new ArrayList<>(); }} 为任务设置时限Future的get方法有一个重载可以设置等待的时间,在超时时会拋出TimeoutException异常。此时可以catch该异常并通过cancle方法来取消任务 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; 获取TimeoutException异常后取消任务 12345try { aa = f.get(5, NANOSECONDS);} catch (TimeoutException e) { f.cancel(true);} 另外在ExecutorService中可以通过invokeAll方法为一组任务批量的指定超时时间,当所有任务执行完毕,或调用线程被中断,或超时时invokeAll将返回。当超时后任何还未完成的任务都取消,所以当invokeAll返回后任务要么正常完成,要么被取消。 接收一组Callable任务,支持指定超时时间。任务执行结果以Future列表形式返回。 List","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"},{"name":"并发编程实战","slug":"并发编程实战","permalink":"http://blog.xxyxpy.pub/tags/并发编程实战/"}]},{"title":"Queue队列接口","slug":"java/并发编程/Queue队列接口","date":"2018-09-12T16:00:00.000Z","updated":"2018-09-14T01:47:37.369Z","comments":true,"path":"2018/09/13/java/并发编程/Queue队列接口/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/13/java/并发编程/Queue队列接口/","excerpt":"在并发队列中JDK提供了两套实现,一个是以ConcurrentLinkedQueue(类)为代表的高性能队列,一个是以BlockingQueue(接口)为代表的阻塞队列。 ConcurrentLinkedQueue一个适用于高并发场景下的队列,通过无锁的方式实现了高并发状态下的高性能,通常它的性能好于BlockingQueue。它是一个基于链表的无界线程安全队列。该列队的元素遵循先进先出的原因,并且不允许添加null元素。 add和offer都是加入元素 poll和peek都是取头元素节点,不同的是poll方法会删除元素 12345678910ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<>();q.offer(\"a\");q.offer(\"b\");q.offer(\"c\");q.offer(\"d\");q.add(\"e\");System.out.println(q.poll()); // a 并删除了这个元素System.out.println(q.size()); // 4System.out.println(q.peek()); // bSystem.out.println(q.size()); // 4","text":"在并发队列中JDK提供了两套实现,一个是以ConcurrentLinkedQueue(类)为代表的高性能队列,一个是以BlockingQueue(接口)为代表的阻塞队列。 ConcurrentLinkedQueue一个适用于高并发场景下的队列,通过无锁的方式实现了高并发状态下的高性能,通常它的性能好于BlockingQueue。它是一个基于链表的无界线程安全队列。该列队的元素遵循先进先出的原因,并且不允许添加null元素。 add和offer都是加入元素 poll和peek都是取头元素节点,不同的是poll方法会删除元素 12345678910ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<>();q.offer(\"a\");q.offer(\"b\");q.offer(\"c\");q.offer(\"d\");q.add(\"e\");System.out.println(q.poll()); // a 并删除了这个元素System.out.println(q.size()); // 4System.out.println(q.peek()); // bSystem.out.println(q.size()); // 4 PriorityQueue基于数组实现的无界优先队列和PriorityBlockingQueue的实现基本相同,不同的是它不是一个阻塞队列,也不是线程安全的队列 BlockingQueue此接口有多个不同的实现类 ArrayBlockingQueue基于数组的阻塞队列,在其内部维护着一个定长的数组,以便缓存队列中的数据对象,所以也叫有界队列。其内部没有实现读写分离,也就意味着生产和消费不能完全并行,可以指定先进先出或者先进后出 LinkedBlockingQueue基于链表的阻塞队列,与上面的ArrayBlockingQueue类似,内部维护着一个数据缓冲队列(链表实现)。内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者操作的完全并行运行,它是一个无界队列 PriorityBlockingQueue基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定,也就是说传入队列的对象必须实现Comparable接口),在实现PriorityBlockingQueue时内部控制线程同步的锁采用的是公平锁,它也是一个无界队列。 DelayQueue带有延迟时间的Queue,其中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue中的元素必须实现Delayed接口,DelayQueue也是一个无界队列,具有比较多的应用场景,比如对缓存超时的数据进行移除、任务超时处理、空闲连接的关闭等 SynchronousQueue一种没有缓冲的队列,生产者生产的数据直接交给消费者消费 参考: Queue队列简单应用","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"}]},{"title":"并发编程实战-04基础构建模块","slug":"java/并发编程/04基础构建模块","date":"2018-09-09T16:00:00.000Z","updated":"2018-09-26T09:18:34.196Z","comments":true,"path":"2018/09/10/java/并发编程/04基础构建模块/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/10/java/并发编程/04基础构建模块/","excerpt":"同步容器类同步容器类包括Vector和Hashtable,另外还包括在JDK1.2中添加的一些功能相似的类,这些同步的封装器是由Collections.synchronizedXxx等工厂方法创建的。这些类实现线程安全的方式是:将它们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。 同步容器类的问题同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作。容器上常见的复合操作包括:迭代、跳转以及条件运算。在同步容器中,这些复合操作在没有客户端加锁的情况下仍然是线程安全的,但当其他线程并发地修改容器时,它们可能会表现出意料之外的行为。","text":"同步容器类同步容器类包括Vector和Hashtable,另外还包括在JDK1.2中添加的一些功能相似的类,这些同步的封装器是由Collections.synchronizedXxx等工厂方法创建的。这些类实现线程安全的方式是:将它们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。 同步容器类的问题同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作。容器上常见的复合操作包括:迭代、跳转以及条件运算。在同步容器中,这些复合操作在没有客户端加锁的情况下仍然是线程安全的,但当其他线程并发地修改容器时,它们可能会表现出意料之外的行为。 Vector上可能导致混乱结果的复合操作 123456789public static Object getLast(Vector list) { int lastIndex = list.size() - 1; return list.get(lastIndex);}public static void deleteLast(Vector list) { int lastIndex = list.size() - 1; list.remove(lastIndex);} 当AB两个线程同时操作Vector上的最后一个元素时可能会造成索引越界(一个线程删除一个线程访问) 通过使用互斥锁可以解决此问题 在使用客户端加锁的Vector上的复合操作 12345678910111213public static Object getLast(Vector list) { synchronized(list) { int lastIndex = list.size() - 1; return list.get(lastIndex); }}public static void deleteLast(Vector list) { synchronized(list) { int lastIndex = list.size() - 1; list.remove(lastIndex); }} 并发容器Java5.0提供共了多种并发容器类来改进同步容器的性能。同步容器将所有对容器状态的访问都串行化,以实现它们的线程安全性。这种方法的代价是严重降低并发性,当多个线程竞争容器的锁时,吞吐量将严重减低。 并发容器是针对多个线程并发访问设计的。在Java5.0中增加了ConcurrentHashMap用来替代同步且基于散列的Map,以及CopyOnWriteArrayList,用于在遍历操作为主要操作的情况下代替同步的List。 通过并发容器来代替同步容器,可以极大地提高伸缩性并降低风险。 增加的另外两种新的容器类型:Queue和BlockingQueue。Queue用来临时保存一组等待处理的元素。它提供了几种实现,包括:ConcurrentLinkedQueue,这是一个传统的先进先出队列,以及PriorityQueue,这是一个(非并发的)优先队列。Queue上的操作不会阻塞,如果队列为空,那么获取元素的操作将返回空值。 BlockingQueue扩展了Queue,增加了可阻塞的插入和获取等操作。如果队列为空,那么获取元素的操作将一直阻塞,直到队列中出现一个可用的元素。如果队列已满,那么插入元素的操作将一直阻塞,直到队列中出现可用的空间。阻塞队列常用于“生产者-消费者”模式中。 Java6中引入了ConcurrentSkipListMap和ConcurrentSkipListSet,分别作为同步的SortedMap和SortedSet的并发替代品(例如用synchronizedMap包装的TreeMap或TreeSet) ConcurrentHashMap使用分段锁技术,在这种机制下任意数量的读取线程可以并发地访问Map。它让在并发环境下实现更高的吞吐量。 CopyOnWriteArrayListCopyOnWriteArrayList用于替代同步List,在某些情况下它提供了更好的并发性能。并且在迭代期间不需要对容器进行加锁或复制。(类似地CopyOnWriteArraySet的作用是替代同步Set) “写入时复制(Copy-On-Write)”容器的线程安全性在于,只要正确地发布一个事实不可变对象,那么在访问该对象时就不再需要进一步的同步。在每次修改时,都会创建并重新发布一个新的容器副本,从而实现可变性。 阻塞队列和生产者-消费者模式阻塞队列提供了可阻塞的put和take方法,以及支持定时的offer和pull方法。如果队列已经满了,那么put方法将阻塞直到有空间可用;如果队列为空,那么take方法将会阻塞直到有元素可用。队列可以是有界的也可以是无界的,无界队列永远都不会满,因此无界队列的put方法也永远不会阻塞。 双端队列与工作密取Java6增加了两种容器类型,Deque和BlockingDeque,它们分别对Queue和BlockingQueue进行了扩展。Deque是一个双端队列,实现了在队列首和队列尾的高效插入和移除。具体实现包括ArrayDeque和LinkedBlockingDeque。 正如阻塞队列适用于生产者-消费者模式,双端队列同样适用于另一种相关模式,即工作密取。在生产者-消费者模式中,所有消费者有一个共享的工作队列,而在工作密取中,每个消费者都有各自的双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么它可以从其他消费者双端队列末尾秘密地获取工作。密取工作模式比传统的生产者-消费者模式具有更高的可伸缩性,这是因为工作者线程不会在半日个共享的任务队列上发生竞争。在大多数时候,它们都只是访问自己的双端队列,从而极大地减少了竞争。当工作者线程需要访问另一个队列时,它会从队列的尾部而不是问部获取工作,因此进一步降低了队列上的竞争程度。 阻塞方法与中断方法线程可能会阻塞或暂停执行,原因有多种:等待IO操作结束,等待获得一个锁,等待从Thread.Sleep方法中醒来,或是等待另一个线程的计算结果。当某方法抛出InterruptedException时,表示该方法是一个阻塞方法,如果这个方法被中断,那么它将努力提前结束阻塞状态。 Thread提供了interrupt方法,用于中断线程或者查询线程是否已经被中断。每个线程都有一个布尔类型的属性,表示线程的中断状态,当中断线程时将设置这个状态。 处理中断的方式: 传递InterruptedException。把此异常传递给方法的调用者; 恢复中断。捕获InterruptedException异常,并通过调用当前线程上的interrupt方法恢复中断状态。 同步工具类同步工具类可以是任何一个对象,只要它根据其自身的状态来协调线程的控制流。阻塞队列可以作为同步工具类,其他类型的同步工具类还包括信号量(Semaphore)、栅栏(Barrier)以及闭锁(Latch)。 所有的同步工具类都包含一些特定的结构化属性:它们封装了一些状态,这些状态将决定执行同步工具类的线程是继续执行还是等待,此外还提供了一些方法对状态进行操作,以及另一些方法用于高效地等待同步工具类进入到预期状态。 闭锁闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。 使用场景如下: 确保某个计算在其需要的所有资源都被初始化之后才继续执行; 确保某个服务在其依赖的所有其他服务都已经启动之后才启动; 直到某个操作的所有参与者(例如,多个玩家的游戏中的所有玩家)都就绪再继续执行。 CountDownLatch是一种灵活的闭锁实现,可以在上述各种情况中使用,它可以使一个或多个线程等待一组事件发生。闭锁状态包括一个计数器,该计数器被初台化为一个正数,表示需要等待的事件数量。coutDown方法递减计数器,表示有一个事件已经发生了,而await方法等待计算器到达0,这表示所有事件都已经发生。 在计时测试中使用CountDownLatch来启动和停止线程 1234567891011121314151617181920212223242526272829public class TestHarness { public long timeTasks(int nThreads, final Runnable task) throws InterruptedException { final CountDownLatch startGate = new CountDownLatch(1); final CountDownLatch endGate = new CountDownLatch(nThreads); for (int i = 0; i < nThreads; i++) { Thread t = new Thread() { @Override public void run() { try { startGate.await(); try { task.run(); } finally { endGate.countDown(); } } catch (InterruptedException ignored) { } } }; t.start(); } long start = System.nanoTime(); startGate.countDown(); endGate.await(); long end = System.nanoTime(); return end - start; }} FutureTaskFutureTask也可以用做闭锁。其表示的计算是通过Callable来实现的,相当于一种可生成结果的Runnable,并且可以处于以下3种状态:等待运行,正在运行和运行完成。执行完成表示计算的所有可能结束方式,包括正常结束、由于取消而结束和由于异常而结束等。当FutureTask进入结束状态之后,它会永远停止在这个状态上。 Future.get 如果任务完成,由get会立即返回结果,否则会阻塞直到任务进入完成状态,返回结果或异常; FutureTask在Executor框架中表示异步任务,此外还可以用来表示一些时间较长的计算,这些计算可以在使用计算结果之前启动。 使用FutureTask来提前加载稍后需要的数据 12345678910111213141516171819202122public class Preloader { private final FutureTask<ProductInfo> future = new FutureTask<ProductInfo>(new Callable<ProductInfo>() { @Override public ProductInfo call() throws Exception { return new ProductInfo(); } }); private final Thread thread = new Thread(future); public void start() { thread.start(); } public ProductInfo get() throws ExecutionException, InterruptedException { try { return future.get(); } catch (ExecutionException e) { throw e; } }} 信号量计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。Semaphore中管理一组虚拟的许可,具有如下特点: 在执行操作前需要使用acquire来获取许可,如果没有许可可用此方法会阻塞直到有许可(或被中断或超时); 获取许可后执行完成,通过release方法来释放许可; 没有强制要求在做release时必须先acquire,每次release信号量数量都会+1,所以做release操作时要谨慎; 可将信号量的值初始化为1来实现互斥锁的功能,谁获取这个许可谁就拥有了互斥锁 使用Semaphore为容器设置边界 123456789101112131415161718192021222324252627282930313233public class BoundedHashSet<T> { private final Set<T> set; private final Semaphore sem; public BoundedHashSet(int bound) { this.set = Collections.synchronizedSet(new HashSet<>()); this.sem = new Semaphore(bound); } public boolean add(T o) throws InterruptedException { sem.acquire(); boolean wasAdded = false; try { wasAdded = set.add(o); return wasAdded; } finally { if (!wasAdded) { sem.release(); } } } public boolean remove(Object o) { boolean wasRemoved = set.remove(o); if (wasRemoved) { sem.release(); } return wasRemoved; }} 栅栏栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。栅栏用于实现一些协议,例如几个家庭决定在某个地方集合,到了之后要等其他人,之后再讨化下一步要做的事情。 CyclicBarrier可以使一定数量的参与方反复地在栅栏位置汇集,它在并行迭代算法中非常有用:这种算法通常将一个问题拆分成一系列相互独立的子问题。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏打开,此时所有线程都被释放,而栅栏位置将被重置以便下次使用。如果对await调用超时,或者await阻塞的线程被中断,那到栅栏就被认为是打破了,所有阻塞的await调用都将终止并抛出BrokenBarrierException。如果成功地通过栅栏,那么await将为每个线程返回一个唯一的到达索引号,可以利用这些索引来选举产生一个领导线程,并在下一次迭代中由该领导线程执行一些特殊的工作。 CyclicBarrier还可以使用将一个栅栏操作传递给构造函数,这是一个Runnable,当成功通过栅栏时会(在一个子任务线程中)执行它,但在阻塞线程被释放之前是不能执行的。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public class CyclicBarrierTest { public static void main(String[] args) { CyclicBarrier cb = new CyclicBarrier(5, new MainTask()); new SubTask(\"A\", cb).start(); new SubTask(\"B\", cb).start(); new SubTask(\"C\", cb).start(); new SubTask(\"D\", cb).start(); new SubTask(\"E\", cb).start(); } }class MainTask implements Runnable { @Override public void run() { System.out.println(\"....终于要执行最后的任务了....\"); }}class SubTask extends Thread { private String name; private CyclicBarrier cb; SubTask(String name, CyclicBarrier cb) { this.name = name; this.cb = cb; } public void run() { System.out.println(\"并发任务:[ \" + name + \" ]开始执行\"); for (int i = 0; i < 3; i++) { // 模拟耗时任务 try { Thread.sleep((long) (Math.random() * 2000)); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(\"并发任务:[ \" + name + \" ]执行完成,通知障碍器\"); try { // 每执行完一项任务就通知一次 cb.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } 构建高效且可伸缩的结果缓存 基于FutureTask的封装器 1234567891011121314151617181920212223242526272829303132333435363738public interface Computable<A, V> { V compute(A arg) throws InterruptedException;}public class Memoizer4<A, V> implements Computable<A, V> { private final Map<A, Future<V>> cache = new ConcurrentHashMap<>(); private final Computable<A, V> c; public Memoizer4(Computable<A, V> c) { this.c = c; } @Override public V compute(A arg) throws InterruptedException { Future<V> f = cache.get(arg); if (f == null) { Callable<V> eval = new Callable<V>() { @Override public V call() throws Exception { return c.compute(arg); } }; FutureTask<V> ft = new FutureTask<V>(eval); f = cache.putIfAbsent(arg, ft); if (f == null) { f = ft; ft.run(); } } try { return f.get(); } catch (ExecutionException e) { e.printStackTrace(); } return null; }}","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"},{"name":"并发编程实战","slug":"并发编程实战","permalink":"http://blog.xxyxpy.pub/tags/并发编程实战/"}]},{"title":"并发编程实战-03对象的组合","slug":"java/并发编程/03对象的组合","date":"2018-09-06T16:00:00.000Z","updated":"2018-09-26T09:18:41.244Z","comments":true,"path":"2018/09/07/java/并发编程/03对象的组合/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/07/java/并发编程/03对象的组合/","excerpt":"设计线程安全的类在设计线程安全类的过程中,需要包含以下三个基本要素: 找出构成对象状态的所有变量 找出约束状态变量的不变性条件 建立对象状态并发访问管理策略 要分析对象的状态,首先从对象的域开始。对象的域构成对象的全部状态。 使用java监视器模式的线程安全计数器","text":"设计线程安全的类在设计线程安全类的过程中,需要包含以下三个基本要素: 找出构成对象状态的所有变量 找出约束状态变量的不变性条件 建立对象状态并发访问管理策略 要分析对象的状态,首先从对象的域开始。对象的域构成对象的全部状态。 使用java监视器模式的线程安全计数器 123456789101112@ThreadSafepublic final class Counter { @GuardedBy(\"this\") private long value = 0; public synchronized long getValue() { return value; } public synchronized long increment() { if (value == Long.MAX_VALUE) { throw new IllegalStateException(\"counter overflow\"); } return ++value; }} 收集同步需求要确保类的线程安全性,就需要确保它的不变性条件不会在并发访问的情况下被破坏,这就需要对其状态进行推断。对于包装多个变量的不变性条件将带来原子性需求:这些相关的变量必须在单个原子操作中进行读取或更新。不能首先更新一个变量,然后释放锁并再次获得锁,然后再更新其他的变量。因为释放锁后,可能会使对象处于无效状态。如果在一个不变性条件中包含多个变量,那么在执行任何访问相关变量的操作时,都必须持有保护这些变量的锁。 依赖状态的操作在单线程程序中,如果某个操作无法满足先验条件,那么就只能失败。但在并发程序中,先验条件可能会由于其他线程执行的操作而变成真。在并发程序中要一直等到先验条件为真,然后再执行操作。 实例封闭如果某对象不是线程安全的,那么可以通过多种技术使其在多线程程序中安全地使用。你可以确保该对象只能由单个线程访问(线程封闭),或者通过一个锁来保护对该对象的所有访问。 封装简化了线程安全类的实现过程,它提供共了一种实例封闭机制。当一个对象被封装到另外一个对象中时,能够访问被封装对象的所有代码路径都是已知的。与对象可以由整个程序访问的情况相比,更易于对代码进行分析。通过将封闭机制与合适的加锁策略结合起来,可以确保以线程安全的方式来使用非线程安全的对象。 将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。 被封闭对象一定不能超出它们既定的作用域。对象可以封闭在类的一个实例(例如作为类的一个私有成员)中,或者封闭在某个作用域内(例如作为一个局部变量),再或者封闭在线程内(例如在某个线程中将对象从一个方法传递到另一个方法,而不是在多个线程之间共享该对象)。 实例封闭是构建线程安全类的一个最简单方式,它还使得在锁策略的选择上拥有了更新的灵活性。在Java平台的类库中还有很多线程封闭的示例,其中有一些类的唯一用途就是将非线程安全的类转化为线程安全的类。一些基本的容器类并非线程安全的,但类库提供了包装器工厂方法(例如Collections.synchronizedList及其类似方法),使得这些非线程安全类可以在多线程环境中安全地使用。这些工厂方法通过“装饰器”模式将容器类封装在一个同步的包装器对象上。只要包装器对象拥有对底层容器对象的唯一引用(即把底层容器对象封闭在包装器中),那么它就是线程安全的。 Java监视器模式从线程封闭原由及其逻辑推论可以得出Java监视器模式。遵循Java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护。 1234567891011public class PrivateLock { private final Object myLock = new Object(); @GuardeBy(\"myLock\") Widget widget; void someMethod() { synchronized(myLock) { // ... } }} 使用私有的锁对象而不是内置锁,有许多优点。私有的锁对象可以将锁封装起来,使客户代码无法得到锁,但客户代码可以通过公有方法来访问锁,以便(正确或不正确地)参与到它的同步策略中。如果客户代码错误地获得了另一个对象的锁,那么可能会产生活跃性问题。此外,要想验证某个公有访问的锁在程序中是否被正确地使用,则需要检查整个程序,而不是单个的类。 线程安全性的委托基于委托的车辆追踪器 使用不变的Point类 12345678@Immutablepublic class Point { public final int x,y; public Point(int x, int y) { this.x = x; this.y = y; }} 由于Point类是不可变的,因而它是线程安全的。不可变的值可以被自由地共享与发布。 将线程安全委托给ConcurrentHashMap 123456789101112131415161718192021222324@ThreadSafepublic class DelegatingVehicleTracker { private final ConcurrentMap<String, Point> locations; private final Map<String, Point> unmodifiableMap; public DelegatingVehicleTracker(Map<String, Point> points) { locations = new ConcureentHashMap<String, Point>(points); unmodifiableMap = Collections.unmodifiableMap(locations); } public Map<String, Point> getLocations() { return unmodifiableMap; } public Point getLocation(String id) { return locations.get(id); } pulic void setLocations(String id, int x, int y) { if (locations.replace(id, new Point(x, y)) == null) { throw new IllegalArgumentException(\"invalid vehicle name: \" + id); } }} 在现有的线程安全类中添加功能 扩展Vector并增加一个“若没有则添加”方法 12345678910@ThreadSafepublic class BetterVector<E> extends Vector<E> { public synchronized boolean putIfAbsent(E x) { boolean absent = !contains(x); if (absent){ add(x); } return absent; }} 扩展Vector很简单,但并非所有的类都像Vector那样将状态向子类公开,因此也就不适合采用这种方法。 客户端加锁机制 通过客户端加锁来实现 若没有则添加 1234567891011121314@ThreadSafepublic class ListHelper<E> { public List<E> list = Collections.synchronizedList(new ArrayList<E>()); ... public boolean putIfAbsent(E x) { synchronized (list) { boolean absent = !list.contains(x); if (absent) { list.add(x); } return absent; } }} 组合 通过组合实现 若没有则添加 12345678910111213141516171819202122@ThreadSafepublic class ImprovedList<E> implements List<E> { private final List<E> list; public ImprovedList(List<E> list) { this.list = list; } public synchronized boolean putIfAbsent(E x) { boolean contains = list.contains(x); if (contains) { list.add(x); } return contains; } public synchronized void clear() { list.clear(); } // ...其他同步方法}","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"},{"name":"并发编程实战","slug":"并发编程实战","permalink":"http://blog.xxyxpy.pub/tags/并发编程实战/"}]},{"title":"并发编程实战-02对象的共享","slug":"java/并发编程/02对象的共享","date":"2018-09-05T16:00:00.000Z","updated":"2018-09-26T09:18:47.802Z","comments":true,"path":"2018/09/06/java/并发编程/02对象的共享/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/06/java/并发编程/02对象的共享/","excerpt":"我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且希望确保当一个线程修改了对象状态之后,其他线程能够看到发生的状态变化。如果没有同步,那么这种情况就无法实现。要构建线程安全的类,除了要保证原子性还需要考虑可见性。 可见性在单线程环境中,如果向某个变量写入值,然后在没有其他写入操作的情况下读取这个值,那么总能得到相同的值。但当读操作和写操作在不同的线程中执行时,情况确并非如此。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。 以下代码由于没有使用足够的同步机制,因此无法保证主线程写入的ready值和number值对于读线程来说是可见的。","text":"我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且希望确保当一个线程修改了对象状态之后,其他线程能够看到发生的状态变化。如果没有同步,那么这种情况就无法实现。要构建线程安全的类,除了要保证原子性还需要考虑可见性。 可见性在单线程环境中,如果向某个变量写入值,然后在没有其他写入操作的情况下读取这个值,那么总能得到相同的值。但当读操作和写操作在不同的线程中执行时,情况确并非如此。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。 以下代码由于没有使用足够的同步机制,因此无法保证主线程写入的ready值和number值对于读线程来说是可见的。 在没有同步的情况下共享变量 1234567891011121314151617181920public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { @Override public void run() { while (!ready) { Thread.yield(); } System.out.println(number); } } public static void main(String[] args) { new ReaderThread().start(); number = 42; ready = true; }} NoVisibility可能会持续循环下去,因为读线程可能永远都看不到ready的值。一种更奇怪的现象是,NoVisibility可能会输出0,因为读线程可能看到了先写入ready的值,但却没有看到之后写入number的值,这种现象被称为“重排序”。当主线程首先写入number,然后在没有同步的情况下写入ready,那么读线程看到的顺序可能与写入的顺序完全相反。 在没有同步的情况下,编译器、处理器以及运行时等都可能操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得出正确的结论。 失效数据NoVisibility展示了在缺乏同步的程序中可能产生错误结果的一种情况:失效数据。当读线程查看ready变量时,可能会得到一个已经失效的值。更糟糕的是,失效值可能不会同时出现:一个线程可能获得某个变量的最新值,而获得另一个变量的失效值。 以下程序中的MutableInteger不是线程安全的,因为get和set都是在没有同步的情况下访问value的。如果某个线程调用了set,那么另一个正在调用get的线程可能会看到更新之后的value值,也可能看不到。 非线程安全的可变整数类 123456789101112@NotThreadSafepublic class MutableInteger { private int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; }} 通过对set和get等方法进行同步,可以使MutableInteger成为一个线程安全的类。仅对set方法进行同步是不够的,调用get的线程仍然会看见失效值。 线程安全的可变整数类 1234567891011121314@ThreadSafepublic class SyhchronizedInteger { @GuardedBy(\"this\") private int value; public synchronized int getValue() { return value; } public synchronized void setValue(int value) { this.value = value; }} 非原子的64位操作对非volatile类型的long和double变量,JVM允许将64位的读操作或写操作分解为两个32位的操作。当读取一个非volatile类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的高32位和另一个值的低32位。因此即使不考虑失效数据问题,在多线程中使用共享且可变的long和double等类型的变量也是不安全的,除非用关键字volatile来声明它们,或者用锁保护起来。 加锁与可见性内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果。线程A B执行到某个同步代码块时,当A执行完后B开始执行,此时B可以看到A之前在同一个同步代码块中的所有操作结果。这也是为什么在访问某个共享且可变变量时要求所有线程在同一个锁上同步,就是为了确保某个线程写入该变的值对于其他线程来说都是可见的。否则,一个线程在未持有正确锁的情况下读取某个变量,那么读到的可能是一个失效值。 加锁的含义不仅仅于互斥行为,还包括内存可见性,为了确保所有线程都能看到共享变量的最新值,所有执行读写操作的线程都必须在同一个锁上同步 Volatile变量volatile变量用来确保将变量的更新操作通知到其他线程。被声明为volatile类型的变量有如下特点: 1.防止指令重排序。编译器和运行时会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序; 2.保证内存可见性。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。 volatile变量是一种比synchronized更轻量级的同步机制。不会执行加锁操作,因此也不会使执行线程阻塞。当线程A写入了一个volatile变量并且线程B随后读取该变量时,在写入volatile变量之间对A可见的所有变量的值,在B读取了volatile变量之后对B也是可见的。因此从内存可见性角度来看,写入volatile变量相当于退出同步代码块,而读取volatile变量就相当于进入同步代码块。 仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它们。如果在验证正确性时需要对可见性进行复杂的判断,那么就不要使用volatile变量。 虽然volatile变量很方便,但也存在一些局限性。volatile变量通常用做某个操作完成、发生中断或者状态的标志(布尔类型,true or false)。尽管它可用于表示其他的状态信息,但使用时要非常小心。例如,volatile的语义不足以确保递增操作(count++)的原子性,除非你能确保只有一个线程对变量执行写操作。 加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。 当且仅当满足以下所有条件时,才应该使用volatile变量: 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值; 该变量不会与其他状态变量一起纳入不变性条件中; 在访问变量时不需要加锁。 发布与逸出发布一个对象的意思是指使对象能够在当前作用域之外的代码中使用。当某个不应该发布的对象被发布时就称为逸出。发布对象的最简单方法是将对象的引用保存到一个公有的静态变量中,以便任何类和线程都能看见该对象。 发布一个对象 1234public static Set<Secret> knownSecrets;public void initialize() { knownSecrets = new HashSet<Secret>();} 当发布一个对象时,可能会间接地发布其他对象。如此将一个Secret对象添加到集合knownSecrets中,那么同样会发布这个对象,因为任何代码都可以遍历这个集合,并获得新Secret对象的引用。 同样,如果从非私有方法中返回一个引用,那么同样会发布返回的对象。 使内部的可变状态逸出 123456class UnsafeStates { private String[] states = new String[] { \"aa\", \"bb\" ... }; public String[] getStates() { return states; }} 最后一种发布对象或其内部状态的机制是发布一个内部的类实例,如下代码当ThisEscape发布EventListener时,也隐含地发布了ThisEscape实例本身,因为这个内部类的实例中包含了对ThisEscape实例的隐含引用。 12345678910public class ThisEscape { public ThisEscape(EventSource source) { source.registerListener( new EventListener() { public void onEvent(Event e) { doSomething(e); } }); }} this引用在构造函数中逸出。当内部的EventListener实例发布时,在外部封装的ThisEscape实例也逸出了。当且仅当对象的构造函数返回时,对象才处于可预测的和一致的状态。所以不要在构造函数中发布对象,因为发布的对象是一个尚未构造完成的对象。可以使用私有的构造函数和一个公共的工厂方法来避免不正确的构造过程。 使用工厂方法来防止this引用在构造过程中逸出 12345678910111213141516public class SafeListener { private final EventListener listener; private SafeListener() { listener = new EventListener() { public void onEvent(Event e) { doSomething(e); } } } public static SafeListener new Instance(EventSource source) { SafeListener safe = new SafeListener(); source.registerListener(safe.listener); return safe; }} 线程封闭当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。线程封闭最常见的用法是方法中声明的局部变量。 栈封闭在栈封闭中,只能通过局部变量才能访问对象。局部变量位置执行线程的栈中,其他线程无法访问这个栈。 如下代码中loadTheArk方法的numPairs,无论如何都不会破坏栈封闭性。 基本类型的局部变量与引用变量的线程封闭性 123456789101112131415161718public int loadTheArk(Collection<Animal> candidates) { SortedSet<Animal> animals; int numPairs = 0; Animal candidate = null; animals = new TreeSet<Animal>(new SpeciesGenderComparator()); animals.addAll(candidates); for (Animal a : animals) { if (candidate == null || !candidate.isPotentialMate(a)) { candidate = a; } else { ark.load(new AnimalPair(candidate, a)); ++numPairs; candidate = null; } } return numPairs;} 如果在线程内部上下文中使用非线程安全的对象,那么该对象仍然是线程安全的。 ThreadLocal类维护线程封闭性的一种更规范方法是使用ThreadLocal,这个类能使线程中的某个值与保存值的对象关联起来。它提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。 使用ThreadLocal来维持线程封闭性 12345678910private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {public Connection initialValue() { return DriverManager.getConnection(DB_URL); }}public static Connection getConnection() { return connectionHolder.get();} 不变性如果某个对象在被创建后其状态就不能被修改,那么这个对象就称为不可变对象。不可变对象一定是线程安全的。 当满足以下条件时,对象才是不可变的: 对象创建以后其状态就不能修改; 对象的所有域都是final类型; 对象是正确创建的(在对象的创建期间,this引用没有逸出) Final域在Java内存模型中final域能确保初始化过程中的安全性,从而可以不受限制地访问不可变对象,并在共享这些对象时无须同步。 使用volatile类型来发布不可变对象 对数值及其因数分解结果进行缓存的不可变容器类 123456789101112131415161718@Immutableclass OneValueCache { private final BigInteger lastNumber; private final BigInteger[] lastFactors; public OneValueCache(BigInteger i, BigInteger[] factors) { lastNumber = i; lastFactors = Arrays.copyOf(factors, factors.length); } public BigInteger[] getFactors(BigInteger i) { if (lastNumber == null || !lastNumber.equals(i)) { return null; } else { return Arrays.conpyOf(lastFactors, lastFactors.length); } }} 对于在访问和更新多个相关变量时出现的竞争条件问题,可以通过将这些变量全部保存在一个不可变对象中来消除。 使用指向不可变容器对象的volatile类型引用以缓存最新的结果 1234567891011121314@ThreadSafepublic class VolatileCachedFactorizer implements Servlet { private volatile OneValueCache cache = new OneValueCache(null, null); public void service (ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = cache.getFactors(i); if (factors == null) { factors = factor(i); cache = new OneValueCache(i, factors); } encodeIntoResponse(resp, factors); }} 安全发布在安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布: 在静态初始化函数中初始化一个对象引用; 将对象的引用保存到volatile类型的域或者AtomicReferance对象中; 将对象的引用保存到某个正确的构造对象的final类型域中 在并发程序中使用和共享对象时,可以使用一些实用的策略,包括: 线程封闭。线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改; 只读共享。在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问。但任何线程都不能修改它; 线程安全共享。线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步; 保护对象。被保护的对象只能通过持有特定的锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁来保护的对象。","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"},{"name":"并发编程实战","slug":"并发编程实战","permalink":"http://blog.xxyxpy.pub/tags/并发编程实战/"}]},{"title":"并发编程实战-01线程安全性","slug":"java/并发编程/01线程安全性","date":"2018-09-03T16:00:00.000Z","updated":"2018-09-26T09:18:53.556Z","comments":true,"path":"2018/09/04/java/并发编程/01线程安全性/","link":"","permalink":"http://blog.xxyxpy.pub/2018/09/04/java/并发编程/01线程安全性/","excerpt":"性能问题 在设计良好的并发应用程序中,线程能提升性能,但无论如何总会带来运行时的开销。在多线程程序中,当线程调度器临时挂起活跃线程并转而运行另一个线程时就会频繁的出现上下文切换操作(Context Switch),这种操作将带来极大的开销:保存和恢复执行下下文,丢失局部性,并且cpu时间将更多地花在线程调度而不是线程运行上。当线程共享数据时,必须使用同步机制,而这些机制往往会抑制某些编译器优化,使内存缓存区中的数据无效,以及增加共享内存总线的同步流量。所有这些因素都将带来额外的性能开销。","text":"性能问题 在设计良好的并发应用程序中,线程能提升性能,但无论如何总会带来运行时的开销。在多线程程序中,当线程调度器临时挂起活跃线程并转而运行另一个线程时就会频繁的出现上下文切换操作(Context Switch),这种操作将带来极大的开销:保存和恢复执行下下文,丢失局部性,并且cpu时间将更多地花在线程调度而不是线程运行上。当线程共享数据时,必须使用同步机制,而这些机制往往会抑制某些编译器优化,使内存缓存区中的数据无效,以及增加共享内存总线的同步流量。所有这些因素都将带来额外的性能开销。 线程安全性在构建稳健的并发程序时,必须正确地使用线程和锁。要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问。共享意味着变量可以由多个线程同时访问,而可变则意味着变量的值在其生命周期内可以发生变化。 一个对象是否需要是线程安全的,取决于它是否被多个线程访问。要使得对象是线程安全的,需要采用同步机制来协同对对象可变状态的访问。如果无法实现协同,那么可能会导致数据破坏以及其它不该出现的结果。 在编写并发应用程序时,一种正确的编程方法就是:首先使代码正确运行,然后再提高代码的速度。即便如此,最好也只是当性能测试结果和应用需求告诉你必须提高性能,以及测量结果表明这种优化在实际环境中确实能带来性能提升时,才进行优化。 什么是线程安全性当多个线程访问某个类时,这个类始终都能表现现正确的行为,那么就称这个类是线程安全的。 原子性一个非线程安全的Servlet 1234567891011@NotThreadSafepublic class UnsafeCountingFactorizer implements Servlet { private long count = 0; public long getCount() {return count;} public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); ++count; encodeIntoResponse(resp, factors); }} 虽然递增操作++count是一种紧凑的语法,使其看上去是一个操作,但这个操作并非原子的,因而它并不会作为一个不可分割的操作来执行。实际上,它包含了三个独立的操作:读取count的值,将值加1,然后将计算结果写入count。这是一个“读取-修改-写入”的操作序列,并且其结果状态依赖于之前的状态。 竞态条件在UnsafeCountingFactorizer中存在多个竞态条件,从而使得结果变得不可靠。当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。也就是正确的结果需要靠运气。最常见的竞态条件类型就是 先检查后执行 操作,即通过一个可能失效的观测结果来决定下一步的动作。 延时初始化中的竞态条件 123456789@NotThreadSafepublic class LazyInitRace { private ExpensiveObject instance = null; public ExpensiveObject getInstance() { if (instance == null) instance = new ExpensiveObject(); return instance; }} 以上示例中有一个竞态条件,可能有两个线程A和B都在执行instance的判断,同时进入实例的初始化。造成返回的两个实例结果不一样。 复合操作UnsafeCountingFactorizer和LazyInitRace都包含一组需要以原子方式执行(或者说不可分割)的操作。要避免竟然条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用该变量,从而确保其他线程只能在修改操作完成之前或之后读取和修改状态,而不是在修改状态的过程中。 我们将“先检查后执行”以及“读取-修改-写入”等操作统称为复合操作:包含了一组必须以原子方式执行的操作以确保线程安全性。 使用AtomicLong类型的变量来完成正确的统计 1234567891011@ThreadSafepublic class UnsafeCountingFactorizer implements Servlet { private final AtomicLong count = new AtomicLong(0); public long getCount() {return count;} public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); count.incrementAndGet(); encodeIntoResponse(resp, factors); }} 加锁机制如果在Servlet中有多个状态变量,尝试使用多个线程安全状态变量。假设缓存最新的计算结果,如果两次连续的请求相同则返回上次的计算结果。要完成此功能需要保存两个状态:最近执行因数分解的数值以及分解结果。 使用AtomicReference来缓存结果-不推荐 12345678910111213141516@NotThreadSafepublic class UnsafeCachingFactorizer implements Servlet { private final AtomicReference<BigInteger> lastNumber = new AtomicReference<>(); private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<>(); public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber.get())) { encodeIntoResponse(resp, lastFactors.get()); } else { BigInteger[] factors = factor(i); lastNumber.set(i); lastFactors.set(factors); encodeIntoResponse(resp, factors); } }} 以上并不能实现该类的线程安全,尽管对lastNumber和lastFactors的操作是线程安全的。 对于上面的代码实现线程安全的条件是:lastFactors的因数之积应该等于lastNumber中缓存的值。只有确保了这个不变性条件不被破坏上面的Servlet才是正确的。当在不变性条件中涉及多个变量时,各个变量之间并不是彼此独立的,因此必须要保证这两个变量的修改操作是原子性的。而显然该程序保证不了,有可能发生线程A在修改lastFactors时线程B修改了lastNumber。此时线程C请求时会得到错误的结果。 内置锁通过内置的锁机制(同步代码块)来实现线程安全。同步代码块包含两个部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。静态的synchronized方法以Class对象作为锁。 123synchronized (lock) { // 锁代码...} 每一个java对象都可以用做一个实现同步的锁。同一时刻只能有一个线程持有这把锁,所以内置锁又称为互斥锁。当线程A尝试获取一个由线程B持有的锁时,必须等待或者阻塞,直到线程B释放这把锁。因此,由这把锁保护的同步代码块会以原子方式执行,多个线程在执行该代码块时互不干扰。 通过内置锁可解决上面缓存计算结果时的线程安全问题,不过造成的结果是并发性能比较差。 12345678910111213141516@ThreadSafepublic class SynchronizedFactorizer implements Servlet { private BigInteger lastNumber; private BigInteger[] lastFactors; public synchronized void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber)) { encodeIntoResponse(resp, lastFactors); } else { BigInteger[] factors = factor(i); lastNumber.set(i); lastFactors.set(factors); encodeIntoResponse(resp, factors); } }} 重入由于内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。重入意味着获取锁的操作粒度是线程而不是调用。重入的一种实现方式是,为每一个锁关联一个获取计数值和所有者线程。当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值为1.如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应地递减。当计数值为0时,这个锁将被释放。 以下代码中,由于Widget和LoggingWidget中doSomething方法都是synchronized方法,如果锁不可重入,那么在调用super.doSomething时将无法获取Widget上的锁,因为这个锁已经被持有。重入避免了这种死锁情况的发生。 123456789101112public class Widget { public synchronized void doSomething() { ... }}public class LoggingWidget extends Widget { public synchronized void doSomething() { System.out.pringln(toString() + \": calling doSomething\"); super.doSomething(); }} 用锁来保护状态所谓状态指的是不同的实例值可能不同,如Person类不同的实例的name域不同,那么此类即是有状态的。由于锁能使其保护的代码路径以串行形式来访问,因此可以通过锁来构造一些协议以实现对共享状态的独占有访问。 要想确实状态的一致性,访问状态变量的所有复合操作必须是原子的,而不仅仅是在写入时。对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁保护的。所以比较安全的做法是保持此状态变量仅可在对象内部访问(试想下定义为public的变量如何保证一致性),通过内置锁来控制多个线程对此变量的访问。许多线程安全类中都使用这种模式,例如Vector和其他的同步集合类。 当某个变量由锁来保护时,意味着在每次访问这个变量时都需要首先获得锁,这样就确保在同一时刻只有一个线程可以访问这个变量。当类的不变性条件涉及多个状态变量时,那么不变性条件中的每个变量都必须由同一个锁来保护。 活跃性与性能活跃性主要指的是死锁,饥饿和活锁。在SynchronizedFactorizer中通过为service方法添加同步的方式来实现线程安全,此程方法操作简单、效果明显,但代价是代码的执行性能非常糟糕。 由于service是一个synchronized方法,因此每次只有一个线程可以执行。而Servlet框架面对的是并发请求,所以在高负载下用户的等待时间会变长。尤其是当某个大数值进行因数分解需要很长执行时间时,其他的客户端必须一直等待,直到阻塞的线程处理完成才能开始处理另一个新的请求。 要解决此问题可以通过缩小同步代码块的作用范围来既确保Servlet的并发性,又维护线程的安全性。要确保同步代码块不要过小,并且不要将本应是原子的操作从同步代码块中分离出去,从而在这些操作的执行过程中,其他线程可以访问共享状态。 1234567891011121314151617181920212223242526272829303132@ThreadSafepublic class CachedFactorizer implements Servlet { private BigInteger lastNumber; private BigInteger[] lastFactors; private long hits; private long cacheHits; public synchronized long getHits() { return hits;} public synchronized double getCacheHitRatio() { return (double) cacheHits / (double) hits; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = null; synchronized (this) { ++hits; if (i.equals(lastNumber)) { ++cacheHits; factors = lastFactors.clone(); } } if (factors == null) { factors = factor(i); synchronized (this) { lastNumber = i; lastFactors = factors.clone(); } } encodeIntoResponse(resp, factors); }} 重构后的CachedFactorizer实现了在简单性(对整个方法进行同步)与并发性(对尽可能短的代码路径进行同步)之间的平衡。在获取与释放锁等操作上都需要一定的开销,因此不能将同步代码块分解得过细。当访问状态变量或者在复合操作的执行期间持有锁,在执行较长时间的因数分解运算之前释放锁。这样既确保了线程安全也不会过多地影响并发性,而且在每个同步代码块中的代码路径都“足够短”。","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"},{"name":"并发编程实战","slug":"并发编程实战","permalink":"http://blog.xxyxpy.pub/tags/并发编程实战/"}]},{"title":"Collections工具类","slug":"java/tool/Collections工具类","date":"2018-07-08T01:43:51.000Z","updated":"2018-09-12T06:02:04.230Z","comments":true,"path":"2018/07/08/java/tool/Collections工具类/","link":"","permalink":"http://blog.xxyxpy.pub/2018/07/08/java/tool/Collections工具类/","excerpt":"### 生成空的List、Set、Map123Collections.EMPTY_LISTCollections.EMPTY_MAPCollections.EMPTY_SET 批量添加addAll public static boolean addAll(Collection<? super T> c, T… elements) 1234List<String> list = new ArrayList<>();list.add(\"a\");list.add(\"b\");Collections.addAll(list, \"c\", \"d\", \"e\");","text":"### 生成空的List、Set、Map123Collections.EMPTY_LISTCollections.EMPTY_MAPCollections.EMPTY_SET 批量添加addAll public static boolean addAll(Collection<? super T> c, T… elements) 1234List<String> list = new ArrayList<>();list.add(\"a\");list.add(\"b\");Collections.addAll(list, \"c\", \"d\", \"e\"); 排序sort public static","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"tool","slug":"java/tool","permalink":"http://blog.xxyxpy.pub/categories/java/tool/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://blog.xxyxpy.pub/tags/Java/"}]},{"title":"spring boot整合swagger","slug":"java/spring boot/spring boot整合swagger","date":"2018-07-01T16:00:00.000Z","updated":"2018-07-11T06:03:51.070Z","comments":true,"path":"2018/07/02/java/spring boot/spring boot整合swagger/","link":"","permalink":"http://blog.xxyxpy.pub/2018/07/02/java/spring boot/spring boot整合swagger/","excerpt":"依赖12345678910<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version></dependency><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version></dependency> 添加配置类也可以定义多个组,添加不同的Docket即可","text":"依赖12345678910<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version></dependency><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version></dependency> 添加配置类也可以定义多个组,添加不同的Docket即可 1234567891011121314151617181920212223242526272829303132@Configuration@EnableSwagger2@Profile({\"test\", \"stage\", \"qa\", \"uat\"}) // 设置在哪些环境中可用public class SwaggerConfig { @Bean public Docket commonApi() { return new Docket(DocumentationType.SWAGGER_2) .groupName(\"commonapi\")// 定义组 .genericModelSubstitutes(DeferredResult.class) .useDefaultResponseMessages(false) .forCodeGeneration(false) .pathMapping(\"/\") .select() // 选择那些路径和api会生成document .apis(RequestHandlerSelectors.basePackage(\"com.my.controller\")) // 拦截的包路径 .paths(PathSelectors.any())// 拦截的接口路径 .build() // 创建 .apiInfo(apiInfo()); // 配置说明 } private ApiInfo apiInfo() { return new ApiInfoBuilder()// .title(\"公共接口\")// 标题 .description(\"公共接口\")// 描述 //.termsOfServiceUrl(\"http://www.my.com\")// .contact(new Contact(\"tom\", \"http://www.my.com\", \"zcl13993@ly.com\"))// 联系 //.license(\"Apache License Version 2.0\")// 开源协议 //.licenseUrl(\"https://github.com/springfox/springfox/blob/master/LICENSE\")// 地址 .version(\"1.0\")// 版本 .build(); }} 注解 @ApiIgnore 忽略注解标注的类或者方法,不添加到API文档中 @ApiOperation 展示每个API基本信息 value api名称 notes 备注说明 @ApiImplicitParam 用于规定接收参数类型、名称、是否必须等信息 name 对应方法中接收参数名称 value 备注说明 required 是否必须 boolean paramType 参数类型 body、path、query、header、form中的一种 body 使用@RequestBody接收数据 POST有效 path 在url中配置{}的参数 query 普通查询参数 例如 ?query=q ,jquery ajax中data设置的值也可以,例如 {query:”q”},springMVC中不需要添加注解接收 header 使用@RequestHeader接收数据 form 笔者未使用,请查看官方API文档 dataType 数据类型,如果类型名称相同,请指定全路径,例如 dataType = “java.util.Date”,springfox会自动根据类型生成模型 @ApiImplicitParams 包含多个@ApiImplicitParam @ApiModelProperty 对模型中属性添加说明,例如 上面的PageInfoBeen、BlogArticleBeen这两个类中使用,只能使用在类中。 value 参数名称 required 是否必须 boolean hidden 是否隐藏 boolean 其他信息和上面同名属性作用相同,hidden属性对于集合不能隐藏,目前不知道原因 @ApiParam 对单独某个参数进行说明,使用在类中或者controller方法中都可以。注解中的属性和上面列出的同名属性作用相同 查看打开http://ip:port/swagger-ui.html即可查看效果 端点保护发现可以直接打开,无任何的安全性。此时可以添加security进行端点保护,添加依赖 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency> 配置类有一个小坑,需要关闭csrf否则swagger中调试时post请求无效。 1234567891011121314151617@Configuration@EnableWebSecuritypublic class MySecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // 关闭csrf否则post请求报403 .authorizeRequests() .antMatchers( \"/swagger-ui.html\").authenticated() .anyRequest().permitAll() .and() .formLogin() .permitAll() .and() .logout() .permitAll(); }} 用户名和密码设置在application.properties中添加如下设置: 123# spring security 用户名和密码配置spring.security.user.name=rootspring.security.user.password=123123 此时再次打开时会提示需要登录,用上面的用户名密码即可以完成登录。至于login页面其实是spring boot内部自带的,所以有点丑。 参考:SpringBoot中使用springfox+swagger2书写API文档 Boot集成Swagger2 Swagger-UI与Spring Cloud整合与安全设置 官方文档 Spring Boot集成Spring Security 修改api的端点","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"swagger","slug":"swagger","permalink":"http://blog.xxyxpy.pub/tags/swagger/"}]},{"title":"使用Redis实现分布式锁","slug":"redis/使用Redis实现分布式锁","date":"2018-06-25T01:33:41.000Z","updated":"2018-06-25T02:01:27.767Z","comments":true,"path":"2018/06/25/redis/使用Redis实现分布式锁/","link":"","permalink":"http://blog.xxyxpy.pub/2018/06/25/redis/使用Redis实现分布式锁/","excerpt":"在很多业务场景中都需要对锁进行控制,比如抢购拼团等活动中的商品数量是有限的需要控制不能进行超卖。在单体架构中处理起来比较简单,分布式环境中可以考虑使用Redis来进行控制,当然也有其它的方案比如引用第三个的消息队列等等。 为什么使用Redis可以实现分布式的锁?在分布式环境下synchronized和lock不会影响到其它的负载,只会有当前的服务器起作用。由于Redis的设计就是单例的,同时时刻只可以处理一个请求。所以可以用它来变现的实现分布式的锁。","text":"在很多业务场景中都需要对锁进行控制,比如抢购拼团等活动中的商品数量是有限的需要控制不能进行超卖。在单体架构中处理起来比较简单,分布式环境中可以考虑使用Redis来进行控制,当然也有其它的方案比如引用第三个的消息队列等等。 为什么使用Redis可以实现分布式的锁?在分布式环境下synchronized和lock不会影响到其它的负载,只会有当前的服务器起作用。由于Redis的设计就是单例的,同时时刻只可以处理一个请求。所以可以用它来变现的实现分布式的锁。 使用SETNX实现SETNX是『SET if Not eXists』(如果不存在,则 SET)的简写。实现思路: 请求进来时进入while循环,循环检测是否存在特定的key(key可以用成团订单号来设置)。为了控制cpu使用率不要过高,设置每50ms循环一次; 如果检测到此key了则说明其它的服务器正在操作,继续等待。这里最后设置最大的循环次数,防止其它服务器异常不删除key或者设置过期时间不成功造成死循环; 如果检测不到此key,SETNX命令会自动的设置key的值。此时进入业务处理逻辑; 第一时间设置此key的过期时间防止业务逻辑处理完成之后删除key操作失败; 进行业务逻辑处理; 删除key值。 使用SETEX实现此命令在实现key设置时同时设置过期时间,两个操作具有原子性,避免了上面设置过期时间失败的情况。需要注意的是如果key已经存在执行此操作会覆盖原来的值。所以在进行判断时需要循环判断是否存在此key,如果不存在先使用SETEX完成设置,再进入业务处理逻辑。同样的处理完成之后进行删除操作。 注意点使用以上两种方法时有一个注意点,要保证过期时间肯定要大于业务处理时间。否则会出现业务处理中时key被自动删除,分布式锁失效的情况。","categories":[{"name":"redis","slug":"redis","permalink":"http://blog.xxyxpy.pub/categories/redis/"}],"tags":[{"name":"分布式","slug":"分布式","permalink":"http://blog.xxyxpy.pub/tags/分布式/"}]},{"title":"mybatis-generator插件","slug":"java/mybatis/mybatis-generator插件","date":"2018-06-19T02:07:33.000Z","updated":"2018-06-19T02:38:00.087Z","comments":true,"path":"2018/06/19/java/mybatis/mybatis-generator插件/","link":"","permalink":"http://blog.xxyxpy.pub/2018/06/19/java/mybatis/mybatis-generator插件/","excerpt":"添加mybatis-generator插件需要注意的是: 设置mysql连接的jar; 设置<phase>deploy</phase>,否则maven package打包时会自动执行插件导致Mapper映射文件被追加,这样项目启动会失败。","text":"添加mybatis-generator插件需要注意的是: 设置mysql连接的jar; 设置<phase>deploy</phase>,否则maven package打包时会自动执行插件导致Mapper映射文件被追加,这样项目启动会失败。 12345678910111213141516171819202122<!-- MyBatis Generator 插件 --><plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.6</version> <executions> <execution> <id>Generate MyBatis Artifacts</id> <phase>deploy</phase> <goals> <goal>generate</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> </dependencies></plugin> 添加generatorConfig.xml映射文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE generatorConfiguration PUBLIC \"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN\" \"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd\"><generatorConfiguration> <!-- 可以用于加载配置项或者配置文件,在整个配置文件中就可以使用${propertyKey}的方式来引用配置项 resource:配置资源加载地址,使用resource,MBG从classpath开始找,比如com/myproject/generatorConfig.properties url:配置资源加载地址,使用URL的方式,比如file:///C:/myfolder/generatorConfig.properties 注意,两个属性只能选址一个 另外,如果使用了mybatis-generator-maven-plugin,那么在pom.xml中定义的properties都可以直接在generatorConfig.xml中使用 <properties resource=\"\" url=\"\" /> --> <!--<properties resource=\"db.properties\"/>--> <!-- 在MBG工作的时候,需要额外加载的依赖包location属性指明加载jar/zip包的全路径,如果在plugin中已经添加了此处就不需要指定了 --> <!--<classPathEntry--> <!--location=\"D:\\MyConfiguration\\.m2\\repository\\mysql\\mysql-connector-java\\5.1.38\\mysql-connector-java-5.1.38.jar\"/>--> <!-- context:生成一组对象的环境 id:必选,上下文id,用于在生成错误时提示 defaultModelType:指定生成对象的样式 1. conditional:类似hierarchical 2. flat:所有内容(主键,blob)等全部生成在一个对象中 3. hierarchical:主键生成一个XXKey对象(key class),Blob等单独生成一个对象,其他简单属性在一个对象中(record class) targetRuntime: 1. MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample 2. MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample introspectedColumnImpl:类全限定名,用于扩展MBG --> <context id=\"MyBatis\" targetRuntime=\"MyBatis3\"> <!-- 自动识别数据库关键字,默认false,如果设置为true,根据SqlReservedWords中定义的关键字列表 一般保留默认值,遇到数据库关键字(Java关键字),使用columnOverride覆盖 --> <property name=\"autoDelimitKeywords\" value=\"false\"/> <!-- 生成的Java文件的编码 --> <property name=\"javaFileEncoding\" value=\"UTF-8\"/> <!-- 格式化Java代码 --> <property name=\"javaFormatter\" value=\"org.mybatis.generator.api.dom.DefaultJavaFormatter\"/> <!-- 格式化XML代码 --> <property name=\"xmlFormatter\" value=\"org.mybatis.generator.api.dom.DefaultXmlFormatter\"/> <!-- 不生成注释 --> <commentGenerator> <property name=\"suppressAllComments\" value=\"true\"/> <property name=\"suppressDate\" value=\"true\"/> </commentGenerator> <!-- JDBC连接配置 --> <jdbcConnection driverClass=\"com.mysql.jdbc.Driver\" connectionURL=\"jdbc:mysql://192.168.1.1:3002/Platform\" userId=\"Platform_local\" password=\"abcd\"> </jdbcConnection> <!-- Java类型处理器 用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl 注意一点,默认会先尝试使用Integer,Long,Short等来对应DECIMAL和NUMERIC数据类型 --> <javaTypeResolver> <!-- true:使用BigDecimal对应DECIMAL和 NUMERIC数据类型 false:默认 scale>0;length>18:使用BigDecimal scale=0;length[10,18]:使用Long scale=0;length[5,9]:使用Integer scale=0;length<5:使用Short --> <property name=\"forceBigDecimals\" value=\"false\"/> </javaTypeResolver> <!-- Java模型创建器,是必须要的元素 负责: 1. key类(见context的defaultModelType) 2. java类 3. 查询类 targetPackage:生成的类要放的包,真实的包受enableSubPackages属性控制 targetProject:目标项目,指定一个存在的目录下,生成的内容会放到指定目录中,如果目录不存在,MBG不会自动建目录 --> <javaModelGenerator targetPackage=\"pub.xxyxpy.bean\" targetProject=\"D:\\code_0914\\boot-new\\src\\main\\java\"> <!-- 自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field,而不是使用setter --> <!--<property name=\"constructorBased\" value=\"false\"/>--> <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false --> <property name=\"enableSubPackages\" value=\"false\"/> <!-- 是否创建一个不可变的类,如果为true 那么MBG会创建一个没有setter方法的类,取而代之的是类似constructorBased的类 --> <!--<property name=\"immutable\" value=\"false\"/>--> <!-- 设置一个根对象 如果设置了这个根对象,那么生成的keyClass或者recordClass会继承这个类,在Table的rootClass属性中可以覆盖该选项 注意: 如果在key class或者record class中有root class相同的属性,MBG就不会重新生成这些属性了,包括: 1. 属性名相同,类型相同,有相同的getter/setter方法 --> <!--<property name=\"rootClass\" value=\"com._520it.mybatis.domain.BaseDomain\"/>--> <!-- 设置是否在getter方法中,对String类型字段调用trim()方法 --> <property name=\"trimStrings\" value=\"true\"/> </javaModelGenerator> <!-- 生成SQL map的XML文件生成器 注意,在Mybatis3之后,我们可以使用mapper.xml文件+Mapper接口(或者不用mapper接口) 或者只使用Mapper接口+Annotation,所以,如果javaClientGenerator配置中配置了需要生成XML的话,这个元素就必须配置 targetPackage/targetProject:同javaModelGenerator --> <sqlMapGenerator targetPackage=\"mybatis\\mapper2\" targetProject=\"D:\\code_0914\\boot-new\\src\\main\\resources\"> <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false --> <property name=\"enableSubPackages\" value=\"false\"/> </sqlMapGenerator> <!-- 对于mybatis来说,即生成Mapper接口 注意,如果没有配置该元素,那么默认不会生成Mapper接口 targetPackage/targetProject:同javaModelGenerator type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下) 1. ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中)不会生成对应的XML 2. MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中 3. XMLMAPPER:会生成Mapper接口,接口完全依赖XML 注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER --> <javaClientGenerator type=\"XMLMAPPER\" targetPackage=\"pub.xxyxpy.mapper2\" targetProject=\"D:\\code_0914\\boot-new\\src\\main\\java\"> <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false --> <property name=\"enableSubPackages\" value=\"false\"/> <!-- 可以为所有生成的接口添加一个父接口,但是MBG只负责生成,不负责检查 <property name=\"rootInterface\" value=\"\"/> --> </javaClientGenerator> <!-- 选择一个table来生成相关文件,可以有一个或多个table,必须要有table元素 选择的table会生成一下文件: 1. SQL map文件 2. 生成一个主键类 3. 除了BLOB和主键的其他字段的类 4. 包含BLOB的类 5. 一个用户生成动态查询的条件类(selectByExample, deleteByExample)可选 6. Mapper接口(可选) tableName(必要):要生成对象的表名,%表示通配,即为该数据库下的所有表生成xml文件 注意:大小写敏感问题.正常情况下,MBG会自动的去识别数据库标识符的大小写敏感度,在一般情况下 MBG会根据设置的schema,catalog或tablename去查询数据表,按照下面的流程: 1. 如果schema,catalog或tablename中有空格,那么设置的是什么格式,就精确的使用指定的大小写格式去查询 2. 否则,如果数据库的标识符使用大写的,那么MBG自动把表名变成大写再查找 3. 否则,如果数据库的标识符使用小写的,那么MBG自动把表名变成小写再查找 4. 否则,使用指定的大小写格式查询 另外,如果在创建表的时候,使用的\"\"把数据库对象规定大小写,就算数据库标识符是使用的大写,在这种情况下也会使用给定的大小写来创建表名 这个时候,请设置delimitIdentifiers=\"true\"即可保留大小写格式 可选: 1. schema:数据库的schema 2. catalog:数据库的catalog 3. alias:为数据表设置的别名,如果设置了alias,那么生成的所有的SELECT SQL语句中,列名会变成:alias_actualColumnName 4. domainObjectName:生成的domain类的名字,如果不设置,直接使用表名作为domain类的名字;可以设置为somepck.domainName,那么会自动把domainName类再放到somepck包里面 5. enableInsert(默认true):指定是否生成insert语句 6. enableSelectByPrimaryKey(默认true):指定是否生成按照主键查询对象的语句(就是getById或get) 7. enableSelectByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询语句 8. enableUpdateByPrimaryKey(默认true):指定是否生成按照主键修改对象的语句(即update) 9. enableDeleteByPrimaryKey(默认true):指定是否生成按照主键删除对象的语句(即delete) 10. enableDeleteByExample(默认true):MyBatis3Simple为false,指定是否生成动态删除语句 11. enableCountByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询总条数语句(用于分页的总条数查询) 12. enableUpdateByExample(默认true)MyBatis3Simple为false,指定是否生成动态修改语句(只修改对象中不为空的属性) 13. modelType:参考context元素的defaultModelType,相当于覆盖 14. delimitIdentifiers:参考tableName的解释,注意,默认的delimitIdentifiers是双引号,如果类似MYSQL这样的数据库,使用的是`(反引号,那么还需要设置context的beginningDelimiter和endingDelimiter属性) 15. delimitAllColumns:设置是否所有生成的SQL中的列名都使用标识符引起来.默认为false,delimitIdentifiers参考context的属性 注意,table里面很多参数都是对javaModelGenerator,context等元素的默认属性的一个复写 --> <!-- 指定数据库表 --> <table tableName=\"GBOrderDetail\"></table> <!--<table tableName=\"%\"--> <!--enableCountByExample=\"false\"--> <!--enableUpdateByExample=\"false\"--> <!--enableDeleteByExample=\"false\"--> <!--enableSelectByExample=\"false\"--> <!--selectByExampleQueryId=\"false\">--> <!-- 参考javaModelGenerator的constructorBased属性 --> <!--<property name=\"constructorBased\" value=\"false\"/>--> <!-- 默认为false,如果设置为true,在生成的SQL中,table名字不会加上catalog或schema --> <!--<property name=\"ignoreQualifiersAtRuntime\" value=\"false\"/>--> <!-- 参考javaModelGenerator的immutable属性 --> <!--<property name=\"immutable\" value=\"false\"/>--> <!-- 指定是否只生成domain类,如果设置为true,只生成domain类,如果还配置了sqlMapGenerator,那么在mapper XML文件中,只生成resultMap元素 --> <!--<property name=\"modelOnly\" value=\"false\"/>--> <!-- 参考javaModelGenerator的rootClass属性 --> <!--<property name=\"rootClass\" value=\"\"/>--> <!-- 参考javaClientGenerator的rootInterface属性 --> <!--<property name=\"rootInterface\" value=\"\"/>--> <!-- 如果设置了runtimeCatalog,那么在生成的SQL中,使用该指定的catalog,而不是table元素上的catalog --> <!--<property name=\"runtimeCatalog\" value=\"\"/>--> <!-- 如果设置了runtimeSchema,那么在生成的SQL中,使用该指定的schema,而不是table元素上的schema --> <!--<property name=\"runtimeSchema\" value=\"\"/>--> <!-- 如果设置了runtimeTableName,那么在生成的SQL中,使用该指定的tablename,而不是table元素上的tablename --> <!--<property name=\"runtimeTableName\" value=\"\"/>--> <!-- 注意,该属性只针对MyBatis3Simple有用 如果选择的runtime是MyBatis3Simple,那么会生成一个SelectAll方法,如果指定了selectAllOrderByClause,那么会在该SQL中添加指定的这个order条件 --> <!--<property name=\"selectAllOrderByClause\" value=\"age desc,username asc\"/>--> <!-- 如果设置为true,生成的model类会直接使用column本身的名字,而不会再使用驼峰命名方法,比如BORN_DATE,生成的属性名字就是BORN_DATE,而不会是bornDate --> <!--<property name=\"useActualColumnNames\" value=\"false\"/>--> <!-- generatedKey用于生成生成主键的方法 如果设置了该元素,MBG会在生成的<insert>元素中生成一条正确的<selectKey>元素,该元素可选 column:主键的列名 sqlStatement:要生成的selectKey语句,有以下可选项: Cloudscape:相当于selectKey的SQL为:VALUES IDENTITY_VAL_LOCAL() DB2 :相当于selectKey的SQL为:VALUES IDENTITY_VAL_LOCAL() DB2_MF :相当于selectKey的SQL为:SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1 Derby :相当于selectKey的SQL为:VALUES IDENTITY_VAL_LOCAL() HSQLDB :相当于selectKey的SQL为:CALL IDENTITY() Informix :相当于selectKey的SQL为:select dbinfo('sqlca.sqlerrd1') from systables where tabid=1 MySql :相当于selectKey的SQL为:SELECT LAST_INSERT_ID() SqlServer :相当于selectKey的SQL为:SELECT SCOPE_IDENTITY() SYBASE :相当于selectKey的SQL为:SELECT @@IDENTITY JDBC :相当于在生成的insert元素上添加useGeneratedKeys=\"true\"和keyProperty属性 --> <!--<generatedKey column=\"\" sqlStatement=\"\"/>--> <!-- 该元素会在根据表中列名计算对象属性名之前先重命名列名,非常适合用于表中的列都有公用的前缀字符串的时候 比如列名为:CUST_ID,CUST_NAME,CUST_EMAIL,CUST_ADDRESS等 那么就可以设置searchString为\"^CUST_\",并使用空白替换,那么生成的Customer对象中的属性名称就不是 custId,custName等,而是先被替换为ID,NAME,EMAIL,然后变成属性:id,name,email 注意,MBG是使用java.util.regex.Matcher.replaceAll来替换searchString和replaceString的 如果使用了columnOverride元素,该属性无效 --> <!--<columnRenamingRule searchString=\"\" replaceString=\"\"/>--> <!-- 用来修改表中某个列的属性,MBG会使用修改后的列来生成domain的属性 column:要重新设置的列名 注意,一个table元素中可以有多个columnOverride元素 --> <!--<columnOverride column=\"username\">--> <!-- 使用property属性来指定列要生成的属性名称 --> <!--<property name=\"property\" value=\"userName\"/>--> <!-- javaType用于指定生成的domain的属性类型,使用类型的全限定名 --> <!--<property name=\"javaType\" value=\"\"/>--> <!-- jdbcType用于指定该列的JDBC类型 --> <!--<property name=\"jdbcType\" value=\"\"/>--> <!-- typeHandler:用于指定该列使用到的TypeHandler,如果要指定,配置类型处理器的全限定名 注意,mybatis中,不会生成到mybatis-config.xml中的typeHandler 只会生成类似:where id = #{id,jdbcType=BIGINT,typeHandler=com._520it.mybatis.MyTypeHandler}的参数描述 --> <!--<property name=\"jdbcType\" value=\"\"/>--> <!-- 参考table元素的delimitAllColumns配置,默认为false --> <!--<property name=\"delimitedColumnName\" value=\"\"/>--> <!-- ignoreColumn设置一个MGB忽略的列,如果设置了改列,那么在生成的domain中,生成的SQL中,都不会有该列出现 column:指定要忽略的列的名字 delimitedColumnName:参考table元素的delimitAllColumns配置,默认为false 注意,一个table元素中可以有多个ignoreColumn元素 --> <!--<ignoreColumn column=\"deptId\" delimitedColumnName=\"\"/>--> <!--</table>--> </context></generatorConfiguration> 生成找到Plugins下面的mybatis-generator,展开后双击mybatis-generator:generate即可生成bean、mapper接口、mapper配置文件以及example类。","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"mybatis","slug":"java/mybatis","permalink":"http://blog.xxyxpy.pub/categories/java/mybatis/"}],"tags":[{"name":"mybatis-generator","slug":"mybatis-generator","permalink":"http://blog.xxyxpy.pub/tags/mybatis-generator/"}]},{"title":"新手上路","slug":"java/tool/新手上路","date":"2018-06-03T16:00:00.000Z","updated":"2018-11-29T15:04:34.761Z","comments":true,"path":"2018/06/04/java/tool/新手上路/","link":"","permalink":"http://blog.xxyxpy.pub/2018/06/04/java/tool/新手上路/","excerpt":"时间处理Java8中新增了LocalDate、LocalTime和LocalDateTime用于对日期时间进行处理 12345678910// 字符串转日期DateTimeFormatter formatter = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\");LocalDate date = LocalDate.parse(\"2018-01-01 12:12:00\", formatter);LocalDateTime time = LocalDateTime.parse(\"2018-01-01 12:12:00\", formatter);// 当前时间LocalDateTime now = LocalDateTime.now();// 判断时间的先后if (now.isAfter(time)) { ...}","text":"时间处理Java8中新增了LocalDate、LocalTime和LocalDateTime用于对日期时间进行处理 12345678910// 字符串转日期DateTimeFormatter formatter = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\");LocalDate date = LocalDate.parse(\"2018-01-01 12:12:00\", formatter);LocalDateTime time = LocalDateTime.parse(\"2018-01-01 12:12:00\", formatter);// 当前时间LocalDateTime now = LocalDateTime.now();// 判断时间的先后if (now.isAfter(time)) { ...} 小数格式化1234567891011// 小数点最多保留两位,有小数时才会保留小数否则显示整数DecimalFormat df = new DecimalFormat(\"#.##\");System.out.println(df.format(10.01)); // 10.01System.out.println(df.format(10.00)); // 10System.out.println(df.format(10.11)); // 10.11// 保留两位小数,不足位数时用0填充DecimalFormat df = new DecimalFormat(\"#.00\");System.out.println(df.format(10.1)); // 10.10// 百分比,千分位DecimalFormat df1 = new DecimalFormat(\"##.00%\");DecimalFormat df1 = new DecimalFormat(\"###,##0.00%\"); 有条件的延迟执行控制语句被混杂在业务逻辑代码之中。典型的情况包括进行安全性检查以及日志输出。 123if (logger.isLoggable(Log.FINER)) { logger.finer(\"Problem: \" + generateDiagnostic());} 较好的方案是将判断逻辑放到log方法中 1logger.log(Level.FINER, \"Problem: \" + generateDiagnostic()); 更优的方式是使用延迟消息构造。方法定义和调用: 1234567public void log(Level level, Supplier<String> msgSupplier) { if(Logger.isLoggable(level)) { log(level, msgSupplier.get()); // 调用重载的log方法 }}// calllogger.log(Level.FINER, () -> \"Problem: \" + generateDiagnostic()) git错误openssl ssl_connect: ssl_error_syscall in connection to github.com:443 执行命令:git config –global –unset http.proxy","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"小知识","slug":"小知识","permalink":"http://blog.xxyxpy.pub/tags/小知识/"}]},{"title":"起步测试","slug":"datastruct/begin","date":"2018-05-03T16:00:00.000Z","updated":"2018-08-08T07:53:57.539Z","comments":true,"path":"2018/05/04/datastruct/begin/","link":"","permalink":"http://blog.xxyxpy.pub/2018/05/04/datastruct/begin/","excerpt":"起步能力测试 打印沙漏 所谓“沙漏形状”,是指每行输出奇数个符号;各行符号中心对齐;相邻两行符号数差2;符号数先从大到小顺序递减到1,再从小到大顺序递增;首尾符号数相等。 给定任意N个符号,不一定能正好组成一个沙漏。要求打印出的沙漏能用掉尽可能多的符号。 12> 19 *> > 1234567> *****> ***> *> ***> *****> 2>","text":"起步能力测试 打印沙漏 所谓“沙漏形状”,是指每行输出奇数个符号;各行符号中心对齐;相邻两行符号数差2;符号数先从大到小顺序递减到1,再从小到大顺序递增;首尾符号数相等。 给定任意N个符号,不一定能正好组成一个沙漏。要求打印出的沙漏能用掉尽可能多的符号。 12> 19 *> > 1234567> *****> ***> *> ***> *****> 2> 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253import java.util.Scanner;import java.util.Arrays;public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in);//生成Scanner对象 while (sc.hasNextInt()) { int number = sc.nextInt(); String s = sc.next(); char c = s.charAt(0); int row = 0; int sum = 0; while (true) { row++; int temp = sum; if (row == 1) { sum = 1; } else { sum = sum + 2 * (2 * row - 1); } if (sum > number) { row--; sum = temp; break; } } for (int i = row; i > 0; i--) { char[] chars = new char[2 * i - 1]; Arrays.fill(chars, c); String result = new String(chars); if (i != row) { char[] chars2 = new char[row - i]; Arrays.fill(chars2, ' '); System.out.print(chars2); } System.out.println(result); } for (int i = 2; i <= row; i++) { char[] chars = new char[2 * i - 1]; Arrays.fill(chars, c); String result = new String(chars); if (i != row) { char[] chars2 = new char[row - i]; Arrays.fill(chars2, ' '); System.out.print(chars2); } System.out.println(result); } System.out.println(number - sum); } }} 素数对猜想 给定一个数,求1~这个数范围内的素数对的对数。如20,其中的素数对(两者差为2)为4:3:5,5:7,11:13,17:19 123456789101112131415161718192021222324252627282930313233public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int number = sc.nextInt(); List<Integer> primeList = new ArrayList<>(); int lastPrime = 2; int pair = 0; for (int i = 3; i <= number; i++) { if (isPrime(i)) { if (i - lastPrime == 2) { //System.out.println(String.format(\"%s-%s\", lastPrime, i)); pair++; } lastPrime = i; } } System.out.println(pair); } private static boolean isPrime(int num) { boolean res = true; if (num < 2) { return !res; } for (int i = 2; i <= Math.sqrt(num); i++) { if (num % i == 0) { res = false; break; } } return res; } } 数组元素循环右移问题 给定数组元素个数(M)和右移的数量(N),达到右移右边N个数到数组左边的效果 输入 126 21 2 3 4 5 6 输出 15 6 1 2 3 4 12345678910111213141516171819202122232425262728293031323334353637import java.util.Scanner;public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String str1 = sc.nextLine(); String str2 = sc.nextLine(); String[] tempArr = str1.split(\" \"); String[] strArr = str2.split(\" \"); int number = Integer.valueOf(tempArr[0]); int count = Integer.valueOf(tempArr[1]); if (count > number) { if (count % number == 0) { for (int i = 0; i < strArr.length; i++) { if (i == strArr.length - 1) { System.out.print(strArr[i]); } else { System.out.print(strArr[i] + \" \"); } } return; } else { count = count % number; } } for (int i = (number - count + 1); i <= number; i++) { System.out.print( strArr[i - 1] + \" \"); } for (int i = 1; i <= (number - count); i++) { if (i == (number - count)) { System.out.print(strArr[i - 1]); } else { System.out.print( strArr[i - 1] + \" \"); } } }} Have Fun with Numbers 给定一个小于20位的数值,翻倍后的值中包含原值中的所有数字。如果是输出Yes,否则输出No。最后换行输出翻倍后的数字 输入 11234567899 输出 12Yes2469135798 1234567891011121314151617181920212223242526272829303132333435363738394041import java.math.BigInteger;import java.util.Scanner;public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String numStr = sc.next(); BigInteger origiBig = new BigInteger(numStr); int[] arr = new int[10]; int[] arr2 = new int[10]; for (int i = 0; i < numStr.length(); i++) { Integer num = Integer.valueOf(numStr.substring(i, i + 1)); arr[num] = 1; } BigInteger doubOrigiBig = origiBig.multiply(BigInteger.valueOf(2)); String newNumStr = doubOrigiBig.toString(); if (numStr.length() != newNumStr.length()) { System.out.println(\"No\"); System.out.print(newNumStr); return; } for (int i = 0; i < newNumStr.length(); i++) { Integer num = Integer.valueOf(newNumStr.substring(i, i + 1)); arr2[num] = 1; } // 比较两个数组 boolean flag = true; for (int i = 1; i < 10; i++) { if (arr[i] != arr2[i]) { flag = false; break; } } if (flag) { System.out.println(\"Yes\"); } else { System.out.println(\"No\"); } System.out.print(newNumStr); }} 洗牌 54张的初始顺序如下: 12345S1, S2, ..., S13, H1, H2, ..., H13, C1, C2, ..., C13, D1, D2, ..., D13, J1, J2 给定[1,54]中不同整数的排列 和翻转的次数。输出翻转之后的结果 例如,假设我们只有5张牌:S3,H5,C1,D13和J2。给定一个混合顺序{4,2,5,3,1},结果将是:J2,H5,D13,S3,C1。如果我们要再次重复洗牌,结果将是:C1,H5,S3,J2,D13。 输入 12236 52 37 38 3 39 40 53 54 41 11 12 13 42 43 44 2 4 23 24 25 26 27 6 7 8 48 49 50 51 9 10 14 15 16 5 17 18 19 1 20 21 22 28 29 30 31 32 33 34 35 45 46 47 输出 1S7 C11 C10 C12 S1 H7 H8 H9 D8 D9 S11 S12 S13 D10 D11 D12 S3 S4 S6 S10 H1 H2 C13 D2 D3 D4 H6 H3 D13 J1 J2 C1 C2 C3 C4 D1 S5 H5 H11 H12 C6 C7 C8 C9 S2 S8 S9 H10 D5 D6 D7 H4 H13 C5 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354import java.util.Scanner;public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String str1 = sc.nextLine(); String str2 = sc.nextLine(); int times = Integer.valueOf(str1); String[] strArr = str2.split(\" \"); int[] toArray = new int[strArr.length]; for (int i = 0; i < toArray.length; i++) { toArray[i] = Integer.valueOf(strArr[i]); } String[] arr = init(); for (int i = 0; i < times; i++) { arr = change(arr, toArray); } for (int i = 1; i < arr.length; i++) { if (i == arr.length - 1) { System.out.print(arr[i]); } else { System.out.print(arr[i] + \" \"); } } } private static String[] change(String[] origiArray, int[] toArray) { String[] result = new String[origiArray.length]; for (int i = 0; i < toArray.length; i++) { int temp = toArray[i]; result[temp] = origiArray[i + 1]; } return result; } private static String[] init() { String[] arr = new String[55]; for (int i = 1; i < 55; i++) { int j = i % 13 == 0 ? 13 : i % 13; if (i < 14) { arr[i] = \"S\" + i; } else if (i < 27) { arr[i] = \"H\" + j; } else if (i < 40) { arr[i] = \"C\" + j; } else if (i < 53) { arr[i] = \"D\" + j; } else { arr[i] = \"J\" + j; } } return arr; }}","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"数据结构","slug":"数据结构","permalink":"http://blog.xxyxpy.pub/tags/数据结构/"}]},{"title":"应用性能优化","slug":"java/tool/性能优化","date":"2018-05-03T16:00:00.000Z","updated":"2018-08-08T07:54:53.617Z","comments":true,"path":"2018/05/04/java/tool/性能优化/","link":"","permalink":"http://blog.xxyxpy.pub/2018/05/04/java/tool/性能优化/","excerpt":"动静分离对于静态资源使用CDN进行分发 容器的并发参数设置通过性能压测确定tomcat容器的并发参数,默认是maxThreads=500","text":"动静分离对于静态资源使用CDN进行分发 容器的并发参数设置通过性能压测确定tomcat容器的并发参数,默认是maxThreads=500 应用层削峰请求被容器拦截后没有任何提示信息,如果需要友好的提示可以在代码中进行控制。通过信号量的方式超过并发数时快速失败。伪代码: 123456789semaphore=new Semaphore(350);if (!semaphore.tryAcquire()) { return \"error\";}try { execute();} finally { semaphore.release();} 缓存对于查询类的请求尽量建立缓存进少db的io,缓存可以走redis或者本地缓存如ehcache 尽量减少锁的使用由于锁的排他性,大范围的使用锁会导致其它的线程处于等待状态。容易导致请求积压,尽而可能会造成服务宕机。 消息队列对于不需要即时响应的通过消息队列进行处理。比如用户注册完成之后发短信、邮件或推送。原应用只负责完成注册,其它操作由后端另外的服务来完成。 对于写服务,如果并发多同时写入数据库,数据库压力剧增,由于数据库响应时间变长各个请求的响应时间肯定会增加。可以先快速成功,然后由消息的消费者接收消息并进行数据库的写操作,起到削峰的作用。 参考: 从限流削峰到性能优化,谈1号店抽奖系统架构实践 消息队列深入解析","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"性能优化","slug":"性能优化","permalink":"http://blog.xxyxpy.pub/tags/性能优化/"}]},{"title":"Spring Boot配置文件","slug":"java/spring boot/Spring Boot配置文件","date":"2018-04-28T16:00:00.000Z","updated":"2018-07-11T10:09:51.028Z","comments":true,"path":"2018/04/29/java/spring boot/Spring Boot配置文件/","link":"","permalink":"http://blog.xxyxpy.pub/2018/04/29/java/spring boot/Spring Boot配置文件/","excerpt":"全局配置文件Spring Boot使用全局配置文件application.properties,在启动时会加载此配置文件中的内容。它的作用是对一些默认配置的配置值进行修改和设置一些自定义的配置。 配置文件的优先级application.properties按下如优先级进行加载 当前目录下的一个/config子目录 当前目录 一个classpath下的/config包 classpath根路径 多环境配置使用application-{profile}.properties用此方式时{profile}对应环境标识。如: application-dev.properties:开发环境 application-prod.properties:生产环境","text":"全局配置文件Spring Boot使用全局配置文件application.properties,在启动时会加载此配置文件中的内容。它的作用是对一些默认配置的配置值进行修改和设置一些自定义的配置。 配置文件的优先级application.properties按下如优先级进行加载 当前目录下的一个/config子目录 当前目录 一个classpath下的/config包 classpath根路径 多环境配置使用application-{profile}.properties用此方式时{profile}对应环境标识。如: application-dev.properties:开发环境 application-prod.properties:生产环境 application.properties和application-{profile}.properties的关系为如果两者有相同属性则指定环境的会进行覆盖。 有多种方式可以启用相应的环境: 在application.properties中添加spring.profiles.active=prod 在启却时指定 java -jar xxx.jar --spring.profiles.active=dev 使用pom文件pom文件中profiles和resources配置为如下内容。profiles指定有哪些环境,resources设置哪些配置需要被打包到jar中。 1234567891011121314151617181920212223242526272829303132333435363738394041<profiles> <profile> <id>test</id> <properties> <deploy.type>test</deploy.type> <appid>123</tianwang.appid> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>product</id> <properties> <deploy.type>product</deploy.type> <appid>111</tianwang.appid> </properties> </profile></profiles><build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <excludes> <exclude>generatorConfig.xml</exclude> </excludes> </resource> <resource> <directory>src/main/resources.${deploy.type}</directory> <filtering>true</filtering> <includes> <include>application-${deploy.type}.properties</include> <include>*.properties</include> <include>*.xml</include> </includes> </resource> </resources> <plugins> </plugins></build> 在application.properties中添加如下代码完成环境隔离配置 123database.dal.environment=@deploy.type@myenv=@deploy.type@appId=@appid@ 在打包时使用不同的环境即可以将不同的配置文件分开打包,这样在运行时即实现加载不同的配置文件。打包命名为: 1mvn clean package -Dmaven.test.skip=true -P test 读取配置配置间引用12my.config.name=赵四my.config.hello=hello ${my.config.name} $Value()12@Value(\"${my.config.hello}\")private String hello; Environment123456@Autowiredprivate Environment environment;@Testpublic void show() { System.out.println(environment.getProperty(\"my.config.hello\"));} @ConfigurationProperties此注解可以支持设置前缀,然后通过定义属性通过set和get方法来完成设置和读取。使用时首先需要添加依赖 12345<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional></dependency> application.properties中有如下配置 12book.author=zsbook.name=spring boot 配置类 12345678910111213141516171819@Configuration@ConfigurationProperties(prefix = \"book\")public class BookSettings { private String author; private String name; public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getName() { return name; } public void setName(String name) { this.name = name; }} 使用时直接注入后调用get方法即可获取到配置信息 1234567891011@RunWith(SpringRunner.class)@SpringBootTestpublic class ConfigurationPropertiesTest { @Autowired private BookSettings bookSettings; @Test public void test() { System.out.println(bookSettings.getAuthor()); System.out.println(bookSettings.getName()); }} 加载其它配置文件如果配置没有放在application.properties中,可以通过@PropertySource注解完成加载。如果加载的配置文件中有重名的配置则根据加载顺序后加载的会覆盖先加载的。 12345678910111213@Configuration@PropertySource(value = { \"classpath:interface_common.properties\", \"classpath:interface_differ.properties\" }, ignoreResourceNotFound = true)@ConfigurationProperties(prefix = \"interface\")public class InterfaceConfig { private String commonapi_test; public String getCommonapi_test() { return commonapi_test; } public void setCommonapi_test(String commonapi_test) { this.commonapi_test = commonapi_test; }} 参考: Spring Boot干货系列:(二)配置文件解析 项目中动态修改加载环境配置的几种方式","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"spring boot","slug":"spring-boot","permalink":"http://blog.xxyxpy.pub/tags/spring-boot/"},{"name":"property","slug":"property","permalink":"http://blog.xxyxpy.pub/tags/property/"}]},{"title":"使用RestTemplate请求","slug":"java/spring boot/使用RestTemplat请求","date":"2018-04-22T16:00:00.000Z","updated":"2018-07-11T09:15:08.961Z","comments":true,"path":"2018/04/23/java/spring boot/使用RestTemplat请求/","link":"","permalink":"http://blog.xxyxpy.pub/2018/04/23/java/spring boot/使用RestTemplat请求/","excerpt":"前言RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。 调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。 ClientHttpRequestFactory接口主要提供了两种实现方式: 一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接。 第二种方式是使用HttpComponentsClientHttpRequestFactory方式,底层使用HttpClient访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息。 RestTemplate默认是使用SimpleClientHttpRequestFactory,内部是调用jdk的HttpConnection,默认超时为-1。","text":"前言RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。 调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。 ClientHttpRequestFactory接口主要提供了两种实现方式: 一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接。 第二种方式是使用HttpComponentsClientHttpRequestFactory方式,底层使用HttpClient访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息。 RestTemplate默认是使用SimpleClientHttpRequestFactory,内部是调用jdk的HttpConnection,默认超时为-1。 Get请求Spring Boot框架中并没有将RestTemplate作为Bean进行管理,因为每个请求可能有不同的超时间设置。所以使用前需要实例化。RestTemplate提供了很多种不同的get和post方法,具体可以查看api。 123456@GetMapping(\"object\")public String requestObject() { RestTemplate rest = new RestTemplate(); String str = rest.getForObject(\"http://localhost:9999/commonapi/test\", String.class); return str;} 使用RestTemplateBuilder构造使用RestTemplateBuilder可以很方便的进行实例化并设置超时时间以及basic认证等。以下代码同时演示了如何传输get参数。 12345678910111213141516@Autowiredprivate RestTemplateBuilder restTemplateBuilder;@GetMapping(\"entity\")public User requestEntity(int age) { // 自定义超时时间 RestTemplate rest = restTemplateBuilder .basicAuthorization(\"user\", \"password\") .setConnectTimeout(3000) .setReadTimeout(3000).build(); MultiValueMap<String,Object> dataMap = new LinkedMultiValueMap<String, Object>(); Map<String, Object> newMap = new HashMap<>(); newMap.put(\"name\", \"tom\"); newMap.put(\"age\", age); User user = rest.getForObject(\"http://localhost:9999/commonapi/test/user3?name={name}&age={age}\", User.class, newMap); return user;} Post请求支持使用Post请求发送Json数据,并且可以在header中添加信息。使用xxxForEntity时还可以获取除了响应之外的header等信息 1234567891011121314151617181920@Autowiredprivate RestTemplateBuilder restTemplateBuilder;@GetMapping(\"postentity\")public User postEntity() { RestTemplate rest = restTemplateBuilder.build(); Map<String, Object> map = new HashMap<>(); map.put(\"name\", \"jack\"); map.put(\"age\", 18); String reqStr = JacksonUtil.obj2json(map); HttpHeaders headers = new HttpHeaders(); headers.add(\"token\", \"37348eb8ff934a8ca495f09e96712924\"); headers.add(\"Content-Type\", \"application/json\"); HttpEntity<String> entity = new HttpEntity<>(reqStr, headers); User user = rest.postForObject(\"http://localhost:9999/commonapi/test/user\", entity , User.class); ResponseEntity<User> responseEntity = rest.postForEntity(\"http://localhost:9999/commonapi/test/user\", entity , User.class); System.out.println(responseEntity.getHeaders().get(\"token\")); return user;} 使用HttpClient池化RestTemplate在实例化时可以选择传入ClientHttpRequestFactory接口的实现类。此处选择前言中所说的第二种方式HttpComponentsClientHttpRequestFactory,需要使用到HttpClient。首先要添加依赖,现在最常用的版本为4.5.3 12345<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version></dependency> 可以配置不同的环境设置不同的超时时间,配置类如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798@Configurationpublic class RestClientConfig { @Value(\"${httpclient.connectionrequesttimeout}\") private int connectionRequestTimeout; @Value(\"${httpclient.connecttimeout}\") private int connectTimeout; @Value(\"${httpclient.sockettimeout}\") private int socketTimeout; @Bean public RestTemplate restTemplate() { return new RestTemplate(httpRequestFactory()); } @Bean public ClientHttpRequestFactory httpRequestFactory() { return new HttpComponentsClientHttpRequestFactory(httpClient()); } @Bean public HttpClient httpClient() { // 注册访问协议相关的Socket工厂 Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create() .register(\"http\", PlainConnectionSocketFactory.getSocketFactory()) .register(\"https\", SSLConnectionSocketFactory.getSocketFactory()) .build(); // HttpConnection工厂:配置写请求/解析响应处理器 HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory = new ManagedHttpClientConnectionFactory( DefaultHttpRequestWriterFactory.INSTANCE , DefaultHttpResponseParserFactory.INSTANCE); // Dns解析器 DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE; // 创建池化连接管理器 PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager( registry, connFactory, dnsResolver); // 默认为Socket配置 SocketConfig defaultSocketConfig = SocketConfig.custom().setTcpNoDelay(true).build(); manager.setDefaultSocketConfig(defaultSocketConfig); manager.setMaxTotal(300); // 设置整个连接池的最大连接数 // DefaultMaxPerRoute控制每个路由最大连接数,MaxTotal控制整个池子最大数,设置过小无法支持大并发。会报 // org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool // 路由是对MaxTotal的细分 manager.setDefaultMaxPerRoute(200); // 设置每个路由最大连接数 // 在从连接池获取连接时,连接不活跃多长时间后需要进行一次验证,默认为2s manager.setValidateAfterInactivity(5 * 1000); // 默认请求配置 RequestConfig requestConfig = RequestConfig.custom() // 从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool .setConnectionRequestTimeout(connectionRequestTimeout) //连接上服务器(握手成功)的时间,超出该时间抛出connect timeout .setConnectTimeout(connectTimeout) //服务器返回数据(response)的时间,超过该时间抛出read timeout .setSocketTimeout(socketTimeout) .build(); // 创建HttpClient CloseableHttpClient httpClient = HttpClientBuilder.create() .setConnectionManager(manager) .setConnectionManagerShared(false)// 连接池不是共享模式 .evictIdleConnections(60, TimeUnit.SECONDS)// 定期回收空闲连接 .evictExpiredConnections() // 定期回收过期连接 .setConnectionTimeToLive(60, TimeUnit.SECONDS)// 连接存活时间,若不设置,根据长连接信息决定 .setDefaultRequestConfig(requestConfig)// 默认请求配置 .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE) // 连接重用策略,即是否能keepAlive .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE) // 长连接配置,即获取长连接多长时间 // 设置重试次数,默认是3次。此处暂时关闭,可根据需要开启 .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)) .build(); // JVM停止或重启时,关闭连接池释放掉连接 Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); LogFactory.build() .withMarker(\"HttpClient\", \"\", \"\") .withMessage(\"HttpClient关闭失败\") .withException(e) .error(); } } }); return httpClient; }} 使用时只需要用@Autowired注入即可 12@Autowiredprivate RestTemplate restTemplate; 由于已经单例化,超时设置都相同。如果有不同的需求实例化新的RestTemplate进行设置和操作。 参考: spring-boot RestTemplate 连接池 RestTemplate 中设置 header 以及使用 HTTP 基本认证的方法 ring Boot 入门 - 进阶篇(4)- REST访问(RestTemplate) Using RestTemplate 使用httpclient必须知道的参数设置及代码写法、存在的风险 estTemplate添加超时处理ClientHttpRequestFactory的选择。 HttpClient使用详解 Spring restTemplat 常用基本用法总结","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"spring boot","slug":"spring-boot","permalink":"http://blog.xxyxpy.pub/tags/spring-boot/"},{"name":"resttemplate","slug":"resttemplate","permalink":"http://blog.xxyxpy.pub/tags/resttemplate/"}]},{"title":"Spring Boot中使用validation","slug":"java/spring boot/Spring Boot中使用validation","date":"2018-04-14T16:00:00.000Z","updated":"2018-07-11T08:12:12.181Z","comments":true,"path":"2018/04/15/java/spring boot/Spring Boot中使用validation/","link":"","permalink":"http://blog.xxyxpy.pub/2018/04/15/java/spring boot/Spring Boot中使用validation/","excerpt":"前言一般情况下的restful接口都是无状态的,在接口请求后都需要进行数据校验工作。比如判断某个字段是否为空,是不是大于0等等。对于简单的校验逻辑耦合在业务代码中是比较丑陋的。Spring Boot自带了Hibernate Validator可以帮我们完成很多前置的校验工作,校验通过后请求才会进入业务逻辑中进行处理。 依赖只需要在pom文件中添加如下依赖即完成了校验组件的引入,而此依赖是restful接口开发所必须的。所以可以理解为天然支持数据校验 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency> 查看其依赖列表会发现其子依赖项中已经包含了hibernate-validator和jackson-databind","text":"前言一般情况下的restful接口都是无状态的,在接口请求后都需要进行数据校验工作。比如判断某个字段是否为空,是不是大于0等等。对于简单的校验逻辑耦合在业务代码中是比较丑陋的。Spring Boot自带了Hibernate Validator可以帮我们完成很多前置的校验工作,校验通过后请求才会进入业务逻辑中进行处理。 依赖只需要在pom文件中添加如下依赖即完成了校验组件的引入,而此依赖是restful接口开发所必须的。所以可以理解为天然支持数据校验 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency> 查看其依赖列表会发现其子依赖项中已经包含了hibernate-validator和jackson-databind 简单校验Spring MVC中提供了简单的参数验证支持,仅可以控制参数不能为空 12345678@GetMapping(\"simple/{name}\")public String simple(@PathVariable(value = \"name\", required = true) String name) { return \"hello \" + name;}@GetMapping(\"simple2\")public String simple2(@RequestParam(value = \"name\", required = false) String name) { return \"hello2 \" + name;} 实体校验在实体中使用校验框架的注解完成自动校验。实体定义: 12345678public class User { @NotBlank @NotNull(message = \"不能为空\") private String name; @Min(value = 18, message = \"年龄不能小于18\") private Integer age; // 省略get和set方法} 从上面代码中可以看到是支持多个注解组合校验的,多个时必须同时满足才算校验通过。其实大部分注解还支持列表形式的校验,说起来比较抽象,先看下注解中的定义 123456789101112131415161718192021@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })@Retention(RUNTIME)@Repeatable(List.class)@Documented@Constraint(validatedBy = { })public @interface Min { String message() default \"{javax.validation.constraints.Min.message}\"; ... /** * Defines several {@link Min} annotations on the same element. * * @see Min */ @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Documented @interface List { Min[] value(); }} 其中List属性即用于定义多个相同的注解,当然如果定义多个时需要同时满足,不过此用法还是比较少的。用法如下: 12345@Min.List({ @Min(value = 8, message = \"年龄不能小于8\"), @Min(value = 18, message = \"年龄不能小于18\")})private Integer age; 实体在使用时需要添加@Valid或@Validated校验才会生效。这两个注解的区别在于后者可以添加分组,至于什么是分组下面会讲到。 1234@PostMapping(\"middle\")public User middle(@Valid @RequestBody User user) { return user;} 框架提供的注解 JSR提供的校验注解 @Null 被注释的元素必须为 null @NotNull 被注释的元素必须不为 null @AssertTrue 被注释的元素必须为 true @AssertFalse 被注释的元素必须为 false @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @Size(max=, min=) 被注释的元素的大小必须在指定的范围内 @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内 @Past 被注释的元素必须是一个过去的日期 @Future 被注释的元素必须是一个将来的日期 @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式 Hibernate Validator提供的校验注解 @NotBlank(message =) 验证字符串非null,且长度必须大于0 @Email 被注释的元素必须是电子邮箱地址 @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内 @NotEmpty 被注释的字符串的必须非空 @Range(min=,max=,message=) 被注释的元素必须在合适的范围内 分组校验设想这样的场景,同一个实体在新建时id可以为null,而在更新时id必须要大于0。显然直接使用上面讲到的注解是无法实现的。现在为实体添加校验分组实现: 1234567891011public class User2 { @NotBlank(groups = { Name.class }) private String name; @Min(value = 18, groups = Age18.class, message = \"年龄不能小于18\") @Min(value = 3, groups = Age3.class, message = \"年龄不能小于3\") private Integer age; // 省略get set方法 public interface Age3{} public interface Age18{} public interface Name{}} 使用时只需要指定不同的组即可 12345678@PostMapping(\"middle2\")public User2 middle2(@Validated({User2.Age3.class}) @RequestBody User2 user) { return user;}@PostMapping(\"middle3\")public User2 middle3(@Validated({User2.Name.class, User2.Age18.class}) @RequestBody User2 user) { return user;} 自定义验证虽然框架为我们提供了一些注解,但是遇到复杂校验时无法满足条件。此时需要自己实现自定义的注解。参考自带的注解实现,两步即可完成自定义校验注解的开发。假如现在需要实现手机号校验的功能。 定义校验的注解@Constraint用于指定用于校验的具体的类,校验的规则在此类中进行实现 1234567891011@Documented// 指定真正实现校验规则的类@Constraint(validatedBy = PhoneValidator.class)@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })@Retention(RetentionPolicy.RUNTIME)public @interface PhoneValidation { String message() default \"不是正确的手机号\"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; // 是否添加list根据需要} 定义校验的类此类需要实现ConstraintValidator泛型接口,需要传入上面的注解和要校验的类型,比如校验String类型就传String,校验User类型就传User。 最主要的实现在isValid()方法中,此方法返回校验是否成功。 12345678910111213141516public class PhoneValidator implements ConstraintValidator<PhoneValidation, String>{ private static final Pattern PHONE_PATTERN = Pattern.compile( \"^(13[0-9]|14[14-9]|15[0-35-9]|166|17[0-9]|18[0-9]|19[89])\\\\d{8}$\" ); @Override public void initialize(PhoneValidation constraintAnnotation) { } @Override public boolean isValid(String value, ConstraintValidatorContext context) { if ( value == null || value.length() == 0 ) { return false; } Matcher m = PHONE_PATTERN.matcher(value); return m.matches(); }} 使用12@PhoneValidationprivate String phone; 使用JavaScript自定义注解hibernate-validator支持使用@ScriptAssert来定义自定义的注解实现,此方式不需要额外的类,只需要在使用类中定义静态方法,成本相对比较低。 1234567891011121314151617181920212223242526@ScriptAssert(lang = \"javascript\" , script = \"com.ly.model.User3.checkParams(_this)\" ,groups = User3.Check.class ,message = \"参数错误\")public class User3 { @NotBlank(groups = { Name.class }) private String name; @Min(value = 18, groups = Age18.class, message = \"年龄不能小于18\") @Min(value = 3, groups = Age3.class, message = \"年龄不能小于3\") private Integer age; @PhoneValidation private String phone; // 省略get set方法 public static boolean checkParams(User3 u) { if (u.name != null && u.age == 10) { return true; } else { return false; } } public interface Age3{} public interface Age18{} public interface Name{} public interface Check{}} 需要特别注意的是: 校验方法必须是静态的; @ScriptAssert注解中的script属性定义的是全路径; 上下文变量默认值是_this,指的是当前实例。可以通过alias属性进行更改。 异常捕捉当校验不通过时我们需要给调用方一些友好的提示,此时最佳的方式就是把校验的错误信息进行返回。而不是直接响应大段的Exception。 当校验不通过时会返回MethodArgumentNotValidException异常,此时结合Spring Boot的全局异常处理对此异常进行捕获,进而提取出它的ErrorMessage。 1234567891011121314151617181920212223@ControllerAdvicepublic class GlobalExceptionHandler { @ExceptionHandler(value = { MethodArgumentNotValidException.class }) @ResponseBody public ResponseEntity validateExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException e) throws Exception { String errorMsg = \"\"; try { errorMsg = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); } catch (Exception ex) { ex.printStackTrace(); } if (StringUtils.isEmpty(errorMsg)) { errorMsg = e.getBindingResult().getFieldErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .findFirst() .orElse(e.getMessage()); } // 记下log // ... // 生成返回结果 return new ResponseEntity(ResponseType.EXCEPTION, ResponseCode.C_1000, errorMsg); }} 参考: 使用Spring Validation完成数据后端校验 hibernate-validator Spring Boot REST – request validation Spring Boot系列十四 Spring boot使用spring validation实现对Restful请求的数据进行校验","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"spring boot","slug":"spring-boot","permalink":"http://blog.xxyxpy.pub/tags/spring-boot/"},{"name":"validation","slug":"validation","permalink":"http://blog.xxyxpy.pub/tags/validation/"}]},{"title":"线程基础","slug":"java/并发编程/线程基础","date":"2018-04-08T07:01:16.000Z","updated":"2018-09-11T10:59:02.100Z","comments":true,"path":"2018/04/08/java/并发编程/线程基础/","link":"","permalink":"http://blog.xxyxpy.pub/2018/04/08/java/并发编程/线程基础/","excerpt":"创建线程在java中创建线程有两种方式 :继承Thread类,重写它的run方法和实现Runnable接口 线程安全多个线程同时操作同一个资源时行为往往会超出预期。所谓线程安全是指当多个线程访问某一个类(对象或方法)时,这个对象始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。","text":"创建线程在java中创建线程有两种方式 :继承Thread类,重写它的run方法和实现Runnable接口 线程安全多个线程同时操作同一个资源时行为往往会超出预期。所谓线程安全是指当多个线程访问某一个类(对象或方法)时,这个对象始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。 创建多线程123456789101112131415161718192021222324public class MyThread extends Thread { private int count = 5; public void run() { count--; System.out.println(this.currentThread().getName() + \"count=\" + count); } public static void main(String[] args) { MyThread myThread = new MyThread(); Thread t1 = new Thread(myThread, \"t1\"); Thread t2 = new Thread(myThread, \"t2\"); Thread t3 = new Thread(myThread, \"t3\"); Thread t4 = new Thread(myThread, \"t4\"); Thread t5 = new Thread(myThread, \"t5\"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); }} count值预期的输出结果应该为4到0,但是结果确并非如此。原因就是多线程同时操作count时没有对并发进行控制。 synchronized关键字如果要解决上面的问题只需要在run方法上添加synchronized,此关键字可以在任意对象及方法上加锁,而加锁的这段代码被称为互斥区或临界区。 一个线程想要执行synchronized修改的方法或对象时首先尝试获得锁,如果拿到就执行代码体内容,拿不到锁就会不断尝试获得这把锁,直到拿到为止。如果有多个线程同时去竞争这把锁就产生了锁竞争问题。 使用synchronized可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果。通谷地讲当线程A执行某个同步代码块时,线程B随后进入由同一个锁保护的同步代码块,当A释放锁时它操作的变量在B获得同一个锁后都是可见的。 使用synchronized作为互斥锁的几点说明: 1.如果同一个方法内同时有两个或更多线程,则每个线程有自己的局部变量拷贝; 2.类的每个实例都对自己的对象级别锁。当一个线程访问实例对象中的 synchronized 同步代码块或同步方法时,该线程便获取了该实例的对象级别锁,其他线程这时如果要访问 synchronized 同步代码块或同步方法,便需要阻塞等待,直到前面的线程从同步代码块或方法中退出,释放掉了该对象级别锁; 3.访问同一个类的不同实例对象中的同步代码块,不存在阻塞等待获取对象锁的问题,因为它们获取的是各自实例的对象级别锁,相互之间没有影响; 4.持有一个对象级别锁不会阻止该线程被交换出来,也不会阻塞其他线程访问同一示例对象中的非 synchronized 代码。当一个线程 A 持有一个对象级别锁(即进入了 synchronized 修饰的代码块或方法中)时,线程也有可能被交换出去,此时线程 B 有可能获取执行该对象中代码的时间,但它只能执行非同步代码(没有用 synchronized 修饰),当执行到同步代码时,便会被阻塞,此时可能线程规划器又让 A 线程运行,A 线程继续持有对象级别锁,当 A 线程退出同步代码时(即释放了对象级别锁),如果 B 线程此时再运行,便会获得该对象级别锁,从而执行 synchronized 中的代码; 5.持有对象级别锁的线程会让其他线程阻塞在所有的 synchronized 代码外。例如,在一个类中有三个synchronized 方法 a,b,c,当线程 A 正在执行一个实例对象 M 中的方法 a 时,它便获得了该对象级别锁,那么其他的线程在执行同一实例对象(即对象 M)中的代码时,便会在所有的 synchronized 方法处阻塞,即在方法 a,b,c 处都要被阻塞,等线程 A 释放掉对象级别锁时,其他的线程才可以去执行方法 a,b 或者 c 中的代码,从而获得该对象级别锁; 6.使用 synchronized(obj)同步语句块,可以获取指定对象上的对象级别锁。obj 为对象的引用,如果获取了 obj 对象上的对象级别锁,在并发访问 obj 对象时时,便会在其 synchronized 代码处阻塞等待,直到获取到该 obj对象的对象级别锁。当 obj 为 this 时,便是获取当前对象的对象级别锁; 7.类级别锁被特定类的所有示例共享,它用于控制对 static 成员变量以及 static 方法的并发访问。具体用法与对象级别锁相似; 8.互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。synchronized 关键字经过编译后,会在同步块的前后分别形成 monitorenter 和 monitorexit 这两个字节码指令。根据虚拟机规范的要求,在执行 monitorenter 指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加 1,相应地,在执行 monitorexit 指令时会将锁计数器减 1,当计数器为 0 时,锁便被释放了。由于 synchronized 同步块对同一个线程是可重入的,因此一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁。 对象锁还是代码锁 需要注意的是synchronized获取的锁是对象锁,而不是把一段代码当作锁。同一个类的不同实例具有不同的锁。 在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类) 查看下例中,tag a 和 tag b先后打印说明锁的并不是printNum内的代码块。由于m1和m2为不同的对象所以两者的锁并不是同一个。 1234567891011121314151617181920212223242526272829303132333435363738public class MultiThread { private int num = 0; public synchronized void printNum(String tag) { try { if (tag.equals(\"a\")) { num = 100; System.out.println(\"tag a, set num over\"); Thread.sleep(1000); } else { num = 200; System.out.println(\"tag b, set num over\"); } System.out.println(\"tag \" + tag + \", num = \" + num); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { MultiThread m1 = new MultiThread(); MultiThread m2 = new MultiThread(); Thread t1 = new Thread(new Runnable() { @Override public void run() { m1.printNum(\"a\"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { m2.printNum(\"b\"); } }); t1.start(); t2.start(); }} 如果此时把t2中执行的m2改为m1就会先执行完tag a再执行tag b,因为t1和t2操作的是同一把锁。结果为: 对象锁的同步和异步如果一个类既有被synchronized修饰的方法又有一般方法,同时调用时会如何执行呢? 123456789101112131415161718192021222324252627282930313233public class MyObject { public synchronized void method1() { try { System.out.println(Thread.currentThread().getName()); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } public void method2() { System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) { MyObject mo = new MyObject(); Thread t1 = new Thread(new Runnable() { @Override public void run() { mo.method1(); } }, \"t1\"); Thread t2 = new Thread(new Runnable() { @Override public void run() { mo.method2(); } }, \"t2\"); t1.start(); t2.start(); }} method1被synchronized修饰而method2为一般方法。两个线程操作同一个对象mo执行时t1,t2同时被打印出来。说明t1线程先持有mo对象的Lock锁,t2线程可以以异步的方式调用对象中的非synchronized修饰的方法。 此时再向method2方法上面添加synchronized关键字,再次执行时发现t2在3s后才打印出来。这是因为同一个对象只有一把锁,同一时间只能被一个线程所持有。t1线程先持有object对象的Lock锁,t2线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步 volatile关键字在java的内存模型下,为了提高执行效率线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就造成一个线程在主存中修改了变量的值,而另外一个线程还在继续使用被修改之前的值的拷贝,造成数据不一致。此时可以通过将变量声明为volatile,被声明为volatile类型的变量有如下特点: 1.防止指令重排序。编译器和运行时会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序; 2.保证内存可见性。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值; 3.不能保证操作的原子性。 volatile是一种稍弱的同步机制,在为访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。由于使用 volatile 屏蔽掉了 JVM 中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。 当线程A写入了一个volatile变量并且线程B随后读取该变量时,在写入volatile变量之间对A可见的所有变量的值,在B读取了volatile变量之后对B也是可见的。因此从内存可见性角度来看,写入volatile变量相当于退出同步代码块,而读取volatile变量就相当于进入同步代码块。","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"}],"tags":[{"name":"并发","slug":"并发","permalink":"http://blog.xxyxpy.pub/tags/并发/"}]},{"title":"统计连续N天记录","slug":"database/统计连续N天记录","date":"2018-04-07T07:01:16.000Z","updated":"2018-05-09T08:00:24.333Z","comments":true,"path":"2018/04/07/database/统计连续N天记录/","link":"","permalink":"http://blog.xxyxpy.pub/2018/04/07/database/统计连续N天记录/","excerpt":"在一般的论坛网站中一般会有显示连续N天登录用户的功能。解决方案一般有两种:一种是通过Job去跑截止前一天所有的用户的登录信息,另一个是直接用sql去统计数据。这里主要讨论用sql去查询的方式。 需求:统计所有用户连续登录的最大次数 思路:大概的思路是先将记录按用户id进行排序,然后用日期-排序后的行号得到的结果相等的记录就是连续登录,然后统计结果相同的行数,最后取得统计的行数的最大值。","text":"在一般的论坛网站中一般会有显示连续N天登录用户的功能。解决方案一般有两种:一种是通过Job去跑截止前一天所有的用户的登录信息,另一个是直接用sql去统计数据。这里主要讨论用sql去查询的方式。 需求:统计所有用户连续登录的最大次数 思路:大概的思路是先将记录按用户id进行排序,然后用日期-排序后的行号得到的结果相等的记录就是连续登录,然后统计结果相同的行数,最后取得统计的行数的最大值。 1. 分组排序取得行号在oracle中直接就有RowNum机制,无奈SqlServer中没有,需要用分区函数来实现 1234-- clocktotalid 为用户唯一标识-- time 为登录时间select ROW_NUMBER() OVER(PARTITION BY clocktotalid ORDER BY time) ROW, clocktotalid, time dd from [Clock] with(nolock) where clockstatus = 1 2. 时间减去行号得到连续值天相同的记录这里面的套路为比如登录记录为 4.1 4.2 4.4 4.5 4.6 4.7,排序号为1~6.那么相减之后的结果为: 登录日期 排序号 结果 2018-04-01 1 2018-03-31 2018-04-02 2 2018-03-31 2018-04-04 3 2018-04-01 2018-04-05 4 2018-04-01 2018-04-06 5 2018-04-01 2018-04-07 6 2018-04-01 1234-- 相减后的值 d 相同的话就是连续的select u.*,CONVERT(VARCHAR(100), DATEADD(day,-row,u.dd), 112) d from(select ROW_NUMBER() OVER(PARTITION BY clocktotalid ORDER BY time) ROW, clocktotalid, time dd from [Clock] with(nolock) where clockstatus = 1) u 3. 统计每个用户连续登录的天数如上面的表格,此用户统计的连续登录天数为2天和4天 12345select p.clocktotalid,p.d, count(1) c from (select u.*,CONVERT(VARCHAR(100), DATEADD(day,-row,u.dd), 112) d from(select ROW_NUMBER() OVER(PARTITION BY clocktotalid ORDER BY time) ROW, clocktotalid, time dd from [Clock] with(nolock) where clockstatus = 1) u) pgroup by p.clocktotalid,p.d 4. 再查每个用户连续登录天数的最大值这个就简单了,直接用max函数即可 123456789select clocktotalid,max(c) cc from(select p.clocktotalid,p.d, count(1) c from (select u.*,CONVERT(VARCHAR(100), DATEADD(day,-row,u.dd), 112) d from(select ROW_NUMBER() OVER(PARTITION BY clocktotalid ORDER BY time) ROW, clocktotalid, time dd from [Clock] with(nolock) where clockstatus = 1) u) pgroup by p.clocktotalid,p.d) qgroup by clocktotalidorder by cc desc 效率问题目前来看在记录为8W条左右时查询一次耗时约200ms,如果数据量过大的话估计会不乐观。从需求上来看对实时性的要求并不是很高,所以效率低下时可以考虑缓存查询结果,一段时间刷新一次","categories":[{"name":"database","slug":"database","permalink":"http://blog.xxyxpy.pub/categories/database/"}],"tags":[{"name":"sqlserver","slug":"sqlserver","permalink":"http://blog.xxyxpy.pub/tags/sqlserver/"},{"name":"套路","slug":"套路","permalink":"http://blog.xxyxpy.pub/tags/套路/"}]},{"title":"web项目中文件上传","slug":"csharp/web项目中文件上传","date":"2018-03-15T08:49:05.000Z","updated":"2018-03-15T09:02:53.260Z","comments":true,"path":"2018/03/15/csharp/web项目中文件上传/","link":"","permalink":"http://blog.xxyxpy.pub/2018/03/15/csharp/web项目中文件上传/","excerpt":"文件上传在web项目上经常会有文件上传的需求,本质上是由前端页面发起的post请求,其中 content-type 必须为 multipart/form-data。浏览器将文件信息以流的形式提交到后端服务器。 在.NET中上传的文件被包装到了request的Files属性中,本质上是一个HttpFileCollection。从名称上可以看出来是一个文件的集合,所以同时上传多个图片时都会存在这个集合中。集合中的元素是HttpPostedFile类型,可根据它来获取文件名及对应的流。","text":"文件上传在web项目上经常会有文件上传的需求,本质上是由前端页面发起的post请求,其中 content-type 必须为 multipart/form-data。浏览器将文件信息以流的形式提交到后端服务器。 在.NET中上传的文件被包装到了request的Files属性中,本质上是一个HttpFileCollection。从名称上可以看出来是一个文件的集合,所以同时上传多个图片时都会存在这个集合中。集合中的元素是HttpPostedFile类型,可根据它来获取文件名及对应的流。 简单的ashx中的文件处理 123456789101112131415161718192021222324252627282930313233343536373839public class Handler1 : IHttpHandler{ public void ProcessRequest(HttpContext context) { String uid = context.Request["uid"]; // file HttpFileCollection files = context.Request.Files; if (files.Count > 0) { // first HttpPostedFile file = files[0]; String fileName = file.FileName; //file.SaveAs(context.Server.MapPath(".") + "/" + fileName); using (FileStream newFile = File.Create(context.Server.MapPath(".") + "/" + fileName)) { byte[] buffer = new byte[1024]; int len; while ((len = file.InputStream.Read(buffer, 0, buffer.Length)) > 0) { newFile.Write(buffer, 0, len); } } } HttpContext.Current.Response.ContentType = "text/plain"; HttpContext.Current.Response.ContentEncoding = Encoding.UTF8; HttpContext.Current.Response.Clear(); HttpContext.Current.Response.Write("hello world"); HttpContext.Current.ApplicationInstance.CompleteRequest(); } public bool IsReusable { get { return false; } }}","categories":[{"name":"c#","slug":"c","permalink":"http://blog.xxyxpy.pub/categories/c/"}],"tags":[{"name":"web","slug":"web","permalink":"http://blog.xxyxpy.pub/tags/web/"}]},{"title":"CSS基础","slug":"前端/CSS/CSS基础","date":"2018-01-22T16:00:00.000Z","updated":"2018-02-09T07:38:48.021Z","comments":true,"path":"2018/01/23/前端/CSS/CSS基础/","link":"","permalink":"http://blog.xxyxpy.pub/2018/01/23/前端/CSS/CSS基础/","excerpt":"CSS概念css 层叠样式表,用来美化html标签,相当于页面化妆。 选择器写法选择器{属性:值;},选择标签的过程 属性 解释 width:20px; 宽 height:20px; 高 background-color:red; 背景颜色 font-size:24px; 文字大小 text-align:left/center/right; 内容水平对齐方式 text-indent:2em; 内容的首行缩进,1em=2个字符 color:red; 字体颜色","text":"CSS概念css 层叠样式表,用来美化html标签,相当于页面化妆。 选择器写法选择器{属性:值;},选择标签的过程 属性 解释 width:20px; 宽 height:20px; 高 background-color:red; 背景颜色 font-size:24px; 文字大小 text-align:left/center/right; 内容水平对齐方式 text-indent:2em; 内容的首行缩进,1em=2个字符 color:red; 字体颜色 分类基础选择器标签选择器 标签{属性:值;},选择相应的标签 123456789101112131415161718192021222324<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Document</title> <style type=\"text/css\"> /*标签选择器*/ div{ font-size: 50px; color: green; background-color: yellow; width: 300px; height: 200px; } </style></head><body> <div>a</div> <div>b</div> <p>c</p> <p>d</p></body></html> 颜色的显示方式 直接写颜色的名称 16进制。#ff0000,前两位红色,中间两位绿色,最后两位蓝色。越接近0颜色越深 rgb。rgb(255, 0, 0) rgba.alpha 代表不透明度,值在0-1之间,1透明度100%,0完全不透明.rgba(255, 0, 0, 0.5) 类选择器 .自定义类名{属性:值;} 特点: 谁调用谁生效 一个标签可以设置多个类选择器 多个标签可以调用同一个类选择器 命名规则: 不能用数字开头来定义名称 不能使用特殊符号,可以使用下划线 不建议使用汉字来定义 不建议使用属性作为类名 1234567891011121314151617<style type=\"text/css\"> /*类选择器*/ .box{ font-size: 50px; color: #ff0000; background-color: #999; width: 300px; height: 200px; } .miss{ text-indent: 2em; /*text-align: right;*/ }</style><div class=\"box miss\">a</div> ID选择器 #id名称{属性:值} 特点: 一个id选择器在一个页面只能使用一次,否则不符合w3c规则 一个标签只能使用一个id选择器 可以同时使用id和类选择器 1234567#hi{font-size: 50px;color: rgb(255, 0, 0);background-color: yellow;}<p id=\"hi\">d</p> 通配符选择器 *{属性:值} 不推荐使用,增加浏览器解析负担 特点: 对所有的符合条件的标签都起作用 只写*表示所有标签,*div表示对所有的div标签 123* div{ font-size: 200px;} 复合选择器 概念:两个或两个以上的选择器通过不同的方式连接在一起 交集选择器 标签+类/id选择器{属性:值;} 12345678.a{font-size: 50px;}/*div标签中使用a类的*/div.a{color: red;} 后代选择器 选择器+空格+选择器…{属性:值;} 特点: 无限制隔代 只要能代表标签。标签、类选择器、id选择器可以自由组合 1234567891011div p span{font-size: 55px;color: yellow;}.abc span{background-color: blue;}<div> <p class=\"abc\"><span>后代选择器</span></p></div> 子代选择器 选择器>选择器{属性:值;} 特点: 选择直接下一代,不可以隔代 123456789div>span {background-color: black;color: white;}<div> <p><span>子代选择器</span></p> <span>子代选择器2</span></div> 并集选择器 某些标签的样式全部相同或部分相同可以通过,进行连接 选择器,选择器…{属性:值;} 选择器可以是 标签/类/id/通配等 123456div,span,h1,.box{font-size: 88px;color: #666;}<h1>并集选择器</h1> 属性选择器1234567891011121314151617input[type=text] { height: 100px; background: red;}input[type=text][class=\"pwd\"] { height: 100px; width: 100px;}input[type=text][id] { height: 100px; width: 100px; background: red;}<input type=\"text\" id=\"username\"/><input type=\"text\"/><input type=\"text\" class=\"pwd\"/> 文本元素属性 font-size:16px; 文字大小 font-weight:700;文字粗细,值从100-900之间从700开始加粗,bold不推荐使用 font-family: 微软雅黑;字体 font-style: italic;italic斜体;normal默认值,正常 line-height: 100px;行高 12345678910111213div p{font-size: 200px;font-weight: 700px;font-family: 微软雅黑;font-style: italic;line-height: 100px;}<div class=\"box\"> <div class=\".div1\"> <p>中国人</p> </div></div> 文本属性连写 需要按照顺序写,文字大小和字体必需要有 font:font-style font-weight font-size/line-weight font-family; font:italic 700 16px/40px 微软雅黑; 斜体 700粗细 16px字体大小 40px行高 字体为微软雅黑 123div p{font: 700 italic 200px/100px 微软雅黑;} 文字的表达方式 中文名称。如微软雅黑 font-family: 微软雅黑; 英文名称。font-family: microsoft yahei; unicode编码。增加浏览器的解析速度。\\5b8b\\4f53代表宋体,通过在控制台中使用 escape("宋体") 可以查看相应的unicode编码 样式表书写位置内嵌式写法 在head标签中写 只作用于当前文件,没有真正实现结构与表现分离 12345<head> <style type=\"text/css\"> ... </style></head> 外链式写法 指定样式表的地址 作用范围是当前站点,范围广。真正实现结构与表现分离 12<link rel=\"stylesheet\" type=\"text/css\" href=\"http://abc.com/def.css\"><link rel=\"stylesheet\" type=\"text/css\" href=\"css/1.css\"> 行内式写法 在标签内写样式 只作用于当前的标签,结构与表现完全不分离 1<p style=\"font-size: 30px;color: red;\">c</p> 标签分类块元素 典型代表div,h1-h6,p,ul,li… 特点: 独占一行 可以设置宽高 嵌套(包含)条件下,子块元素的宽度(没有定义)和父块元素宽度默认一致 行内元素 典型代表:span,a,strong,em,del,ins… 特点: 在一行内显示 不能直接设置宽高 行内块元素(内联元素) 典型代表:input,image 特点: 在一行内显示(一行可以有多个行内块元素) 可以设置宽高 块元素与行内元素块元素转行内元素display:inline;,转成行内元素之后无法再设置宽高,因为已经转变成为行内元素 123456.a{ display: inline;}<div class=\"a\">abcedefg</div><p class=\"a\">afsaflasfl;saf;a</p> 行内元素转块元素display:block;,拥有块元素的特性,可以设置宽高和居中 12345678910span{ display: block; width: 200px; height: 200px; background-color: #999; text-align: center;}<span>hahahfadf</span><span>asdfasfasf</span> 转行内块元素display:inline-block;拥有行业块元素的所有特性 123456789101112div, a, span, strong{ display: inline-block; width: 200px; height: 150px; background-color: yellow; text-align: center;}<div>中国人</div><a href=\"#\">哈哈</a><span>sdfasdf</span><strong>adfasfasfasdfsd</strong> CSS三大特性层叠性多种样式作用到同一个标签上面时,样式会发生重叠。总是应用后加载的样式,这是由于浏览器解析是由上到下进行的。 1234567891011.box{ background-color: blue; font-size: 20px;}.box1{ background-color: yellow; font-size: 50px;}<p class=\"box box1\">中国人</p> 继承性继承性发生的前提是包含(嵌套)关系。文字的所有属性都可以继承。包括: 文字颜色 文字大小 字体 字体粗细 文字风格 文字行高 注意: h1标题系列不能继承文字大小 a标签不能继承文字颜色 1234567.father{ font: 50px microsoft yahei;}<div class=\"father\"> <p>微软雅黑</p></div> 优先级默认样式(0) < 标签选择器(1) < 类选择器(10) < id选择器(100) < 行业样式(1000 )< !important(>1000) !important可用于强制使用此种样式 优先级特点: 继承的权重为0(自己定义样式的显示自己的) 权重会叠加(p.son=(1+10)<.father.son(10+10)) 12345678910111213141516171819202122<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Document</title> <style type=\"text/css\"> #mydiv{ color: pink; } .box{ color: black !important; font-size: 50px; } div{ color: red; } </style></head><body> <div class=\"box\" id=\"mydiv\" style=\"color: yellow;\">abcdefg</div></body></html> 链接伪类 a:link{属性:值;} a{属性:值} 两者效果相同 a:link{属性:值;} 链接默认状态 a:visited{属性:值;} 链接访问之后的状态 a:hover{属性:值;} 鼠标放到链接上显示的状态 a:active{属性:值;} 链接激活的状态 :focus{属性:值;} 获取焦点 文本修饰符去除下划线 text-decoration:none | underline | line-through 12345678910111213141516171819202122232425262728293031<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Document</title> <style type=\"text/css\"> /*默认*/ a:link{ color: red; text-decoration: none; font-size: 100px; } /*访问之后*/ a:visited{ color: black; } /*经过*/ a:hover{ color: yellow; text-decoration: underline; } /*按住不动*/ a:active{ color: pink; } </style></head><body> <a href=\"#\">abcdefg</a></body></html> 背景属性背景色 background-color 背景图片 background-image background-image: url(“1.png”) 背景平铺 background-repeat repeat(默认) | no-repeat | repeat-x | repeat-y 背景定位 background-position left | right | top | center | bottom,可以写具体的像素值 方位值只写一个时另外一个值默认居中 写两个方向时顺序没有要求,默认前面是水平方向 background-position: center right; background-position: 20px; 背景滚动 background-attachment scroll(滚动,默认) | fixed(固定) 使用fixed时background-position指相对于浏览器,而不是父盒子 属性连写 background: red url(“1.png”) no-repeat bottom scroll; 图片不能省略,其它可以省略 顺序没有要求 行高浏览器默认文字大小是16px 行高:基线与基线之间的距离。 行高=文字高度+上下边距 一行文字行高和父元素高度一致的时候,垂直居中显示 行高单位 行高单位 文字大小 值 px 20px 20px(20px) em 20px 40px(2em) % 20px 30px(150%) 2 20px 40px(2) 单位除了像素以外,行高都是与文字大小乘积 行高单位 父元素文字大小 子元素文字大小 行高 40px 20px 30px 40px 2em 20px 30px 40px 150% 20px 30px 30px 2 20px 30px 60px 不带单位时,行高是和子元素文字大小相乘。em和%的行高是和父元素文字大小相乘。行高以像素为单位就是定义的行高值 盒子模型边框 线型一般有,solid dashed dottedborder-top-style: solid;/边框颜色/border-top-color: red;/边框宽度/border-top-width: 5px; 属性连写,线型为必写项 border-top: 5px solid red; border: 5px solid red; 取消表单边框 border: 0 none //去掉边框 outline-style: none //去掉轮廓线 边框合并 border-collapse: collapse label for=”id”用于友好性 实现细线表格 12345678910table { width: 300px; height: 500px; border: 1px solid red; border-collapse: collapse;}td { border: 1px solid red;} 内边距 padding-left|top|right|bottom padding: 20px; 所有都相同 padding: 20px 30px; 第一个代表上下,第二个代表左右 padding: 20px 30px 40px;代表 上 左右 下 padding: 20px 30px 40px 50px; 上 右 下 左 盒子宽度 盒子宽度 = 定义的宽度 + 边框宽度 + 左右内边距 影响盒子宽度因素 内边距会撑大盒子,如果要保持盒子大小需要用盒子宽高减去padding的大小 边框也会撑大盒子 继承的盒子如果子盒子没有定义宽度,设置的左右内边距只要不大于父盒子的宽度就不会被撑大 外边距 margin-left|top|right|bottom 可以简写,形式和内边距相同 垂直方向外边距的合并,如果都设置了外边距会取大的那个值,并不是两者相加 行内元素可以定义左右的内外距,上下的会被忽略掉 外边距塌陷。嵌套的盒子,直接给子盒子设置垂直方向外边距的时候会发生外边距塌陷,父盒子跟着子盒子下移 给父盒子设置边框(不推荐) 给父盒子设置overflow: hidden; 触发bfc 123456789101112131415161718192021222324252627282930313233<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title> <style type=\"text/css\"> .father { background: gray; height: 400px; width: 500px; /*外边距踏陷*/ overflow: hidden; } .son { background: #cccccc; height: 200px; width: 300px; margin-top: 20px; } </style></head><body><table cellspacing=\"0\"> <div class=\"father\"> <div class=\"son\"></div> </div></table></body></html> 浮动 float:left | right 特点: 浮动之后不占据原来的位置,脱离文档流 浮动的盒子会在一行显示 行内元素浮动之后转换为行内块元素(不推荐使用,尽量用display:inline-block;) 浮动的作用 文本绕图,图片浮动之后文字会靠着图片显示,文字不参与浮动 制作导航 页面布局 清除浮动 clear: left|right|both 使用最多的是both 不是不用浮动,是清除浮动产生的不利影响。 清除浮动的方法 额外标签法 在最后一个浮动元素后添加标签,并设置其浮动属性如clear:both; 父级元素使用overflow:hidden;,触发bfc 如果有内容出了盒子不能使用此方法,会被隐藏掉 伪元素清除浮动 给元素设置clearfix类 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title> <style type=\"text/css\"> body { margin: 0; padding: 0; } .header,.main,.footer{ width: 500px; } .header { height: 100px; background-color: black; } .main { margin: 10px 0; background: #eee; /*height: 300px;*/ /* 设置overflow */ /*overflow: hidden;*/ } .content { width: 200px; height: 300px; background: #cccccc; float: left; } .sidebar { width: 290px; height: 300px; background: #ff0000; float: right; margin-left: 10px; } .footer { height: 100px; background-color: green; float: left; } .clearfix:after { content: \".\"; display: block; height: 0; line-height: 0; visibility: hidden; clear: both; } /*兼容ie*/ .clearfix { zoom: 1; } </style></head><body> <div class=\"header\"> </div> <div class=\"main clearfix\"> <div class=\"content\"></div> <div class=\"sidebar\"></div> <!-- 额外标签法 --> <!--<div style=\"clear: both\"></div>--> </div> <div class=\"footer\"></div></body></html> overflow规则当内容溢出元素框时发生的事情 overflow:visible 默认值,内容不会被修剪,会呈现在元素框之外 overflow:hidden 内容会被修剪,并且其余内容不可见 overflow:scroll 内容会被修剪,但是浏览器会显示滚动条以便查看内容 overflow:auto 如果会被修剪,但是浏览器会显示滚动条以便查看内容 定位定位分为四种:静态定位、绝对定位、相对定位和固定定位 定位方向:left right top bottom 静态定位position:static;,默认值,就是文档流 绝对定位position:absolute; 绝对定位时可以设置top、bottom、left、right。相对于body或position:relative父级元素的。 特点: 元素不占据原来的位置,脱标 元素使用绝对定位,位置是从浏览器出发。 嵌套的盒子,父盒子没有使用定位,子盒子绝对定位,子盒子从浏览器出发 嵌套的盒子,父盒子使用定位,子盒子绝对定位,子盒子从父盒子出发 给行内元素使用绝对定位之后,转换为行内块。(不推荐使用,还是使用display:inline-block) 相对定位position:relative; 相对定位时可以设置margin-系列。相对于相邻元素的。 特点: 使用相对定位,位置从自身出发。 还占据原来的位置,不脱标。 子绝父相使用最多。父元素相对定位,子元素绝对定位 行内元素设置相对定位,不能转行内块 固定定位position:fixed; 特点: 固定定位之后,不占据原来的位置(脱标) 元素固定定位之后,位置从浏览器出发 给行内元素使用固定定位之后,转换为行内块。(不推荐使用,还是使用display:inline-block) 定位盒子居中显示 margin:0 auto;只能让标准流中的盒子居中对齐 父盒子相对定位,子盒子绝对定位 设置子盒子left:50% 设置子盒子margin-left:-子盒子宽度/2(负的值,向左走) 1234567891011121314151617181920212223242526body, div { margin: 0; padding: 0;}.box { position: relative; height: 400px; width: 960px; background-color: red;}.nav { position: absolute; height: 40px; width: 600px; background-color: green; bottom: 0; left: 50%; margin-left: -300px;}<div class=\"box\"> <div class=\"nav\"></div></div> 标签包含规范 div可以包含所有标签 p标签不可以包含div h1等块元素 h1中可以包含p div等 行内元素尽量包含行内元素,不要包含块元素 规避脱标流 margin-left:auto; 左边充满,向右对齐 margin-right:auto; 右边充满,向左对齐 尽量使用标准流 标准流解决不了的使用浮动 浮动解决不了的使用定位 图片和文字垂直居中对齐 vertical-align 对 inline-block 最敏感。默认属性是 vertical-align:baseline; 1234img { /* 让文字和图片垂直对齐 */ vertical-align: middle;} CSS可见性 overflow:hidden; 溢出隐藏 visibility:hidden; 可见性隐藏元素,隐藏之后占据原来的位置 display:none;隐藏元素,隐藏之后位置不占据原来的位置 display:block;元素可见 display:none和display:block常配合js使用 css之内容移出 使用text-indent,给它一个负值 div中设置值内容,但是给它设置高度为0并且overflow:hidden; 1234567891011121314a { display: inline-block; text-indent: -5000px;}.box { width: 300px; height: 0; overflow: hidden;}<div class=\"box\"> afasfsaf</div> 精灵图所有的小图标放在一张大图中,显示时从大图中进行截取显示。利用background进行背景定位 12345.box { width: 24px; height: 22px; background: url(\"tbbg.png\") -700px -110px;/* 前面是x轴 后面是y轴 */}","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"Css","slug":"Css","permalink":"http://blog.xxyxpy.pub/tags/Css/"}]},{"title":"软件使用","slug":"common/软件使用","date":"2018-01-08T02:43:56.000Z","updated":"2018-01-11T03:13:58.734Z","comments":true,"path":"2018/01/08/common/软件使用/","link":"","permalink":"http://blog.xxyxpy.pub/2018/01/08/common/软件使用/","excerpt":"sublime使用快捷键html:xt 快速新建html内容div 补全标签代码ctrl+shift+d 快速复制一行ctrl+shift+k 快速删除一行连续ctrl+单击 集体输入ctrl+h 查找和替换ctrl+f 查找ctrl+/ 注释ctrl+k+b 打开/隐藏侧边栏ctrl+n 新建ctrl+l 选中一行ctrl+w 关闭当前页面ctrl+shift+↑/↓ 光标定位行,快速整体移动","text":"sublime使用快捷键html:xt 快速新建html内容div 补全标签代码ctrl+shift+d 快速复制一行ctrl+shift+k 快速删除一行连续ctrl+单击 集体输入ctrl+h 查找和替换ctrl+f 查找ctrl+/ 注释ctrl+k+b 打开/隐藏侧边栏ctrl+n 新建ctrl+l 选中一行ctrl+w 关闭当前页面ctrl+shift+↑/↓ 光标定位行,快速整体移动 安装Package Control使用快捷键 ctrl+` 或者在菜单中点击 View > Show Console,3版本输入以下命令(可能需要翻墙): 1import urllib.request,os,hashlib; h = '2915d1851351e5ee549c20394736b442' + '8bc59f460fa1548d1514676163dafc88'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by) 安装Emmet在 Sublime Text 中按 Ctrl+Shift+p 快捷键或在菜单-工具中打开“命令面板”( Tools > Command Palette... ),输入:Install Package (安装扩展)后回车,弹出新的窗口,再输入 Emmet 查找 Emmet 确定安装,等到自动打开一个文档,说明安装成功。 新建一个html文档,输入!+tab看下效果。","categories":[{"name":"工具","slug":"工具","permalink":"http://blog.xxyxpy.pub/categories/工具/"}],"tags":[]},{"title":"Html标签","slug":"前端/HTML/Html标签","date":"2018-01-08T02:43:56.000Z","updated":"2018-01-19T02:16:05.374Z","comments":true,"path":"2018/01/08/前端/HTML/Html标签/","link":"","permalink":"http://blog.xxyxpy.pub/2018/01/08/前端/HTML/Html标签/","excerpt":"语义标签加粗12<strong>加粗</strong> <!-- 推荐 --><b>加粗</b> 斜体12<em>斜体</em> <!-- 推荐 --><i>斜体</i> 删除线12<del>删除线</del> <!-- 推荐 --><s>删除线</s> 下划线12<ins>下划线</ins> <!-- 推荐 --><u>下划线</u>","text":"语义标签加粗12<strong>加粗</strong> <!-- 推荐 --><b>加粗</b> 斜体12<em>斜体</em> <!-- 推荐 --><i>斜体</i> 删除线12<del>删除线</del> <!-- 推荐 --><s>删除线</s> 下划线12<ins>下划线</ins> <!-- 推荐 --><u>下划线</u> 图片标签1<img src=\"image source\" alt=\"图片错误时的替换文本\" title=\"鼠标放在上面时显示的信息\" width=\"宽度\" height=\"高度\"/> 超链接 空链:href=”#”压缩文件下载:href=”123.rar”跳转优化:在页面的head标签中添加<base target="_blank" />,当前页面的所有超链接都跳转到新页面 1<a href=\"跳转路径/打开的文件\" title=\"提示文本\" target=\"控制页面打开方式_black:新页面;_self:当前页,默认\">文字</a> 锚点 1.在标签上定义id,如myid2.a标签的href属性中定义值为#myid 特殊符号 描述 显示 转义 空格 &nbsp; 小于号 < &lt; 大于号 > &gt; 和号 & &amp; 引号 “ &quot; 人民币/日元 ¥ &yen; 版权 © &copy; 注册商标 ® &reg; 乘号 × &times; 除号 ÷ &divide; 正负号 ± &plusmn; 列表无序列表12345<ul type=\"列表前标识square:方块;disc:小圆点;circle:空心圆\"> <li></li> <li></li> <li></li></ul> 有序列表12345<ol type=\"修改顺序表现方式1:阿拉伯数字,a:小写字母,A:大写字母,i:罗马字符\" start=\"只能数字. 表示开始的顺序\"> <li></li> <li></li> <li></li></ol> 自定义列表123456<dl> <dt>标题</dt> <dd></dd> <dd></dd> <dd></dd></dl> 音乐标签 hidden 隐藏掉丑陋的外观显示 1<embed src=\"1.mp3\" hidden=\"true/> 滚动1<marquee behavior=\"slide/alternate/scroll\" direction=\"down/loop\">文字/图片</marguee> 上下标122<sup>3</sup>h<sub>2</sub>o head标签meta标签编码1<meta charset编码 一般用 utf-8/> 关键字1<meta name=\"keywords\" content=\"java培训\" /> 内容描述1<meta name=\"description\" content=\"java培训\" /> 网页重定向 间隔一段时间之后跳转到其它的页面 1<meta http-equiv=\"refresh\" content=\"5;http://www.baidu.com\"> link 标签 引入其它内容,如css样式表等 123<link rel=\"stylesheet\" href=\"/wp-content/themes/runoob/style.css?v=1.141\" type=\"text/css\" media=\"all\"><link rel=\"dns-prefetch\" href=\"//s.w.org\"><link rel=\"canonical\" href=\"http://www.runoob.com/html/html-entities.html\"> 表格一般用法展示数据,是对网页的一种补充 cellspacing 单元格间距,默认是2 cellpadding 内容到边框的距离 align(left, right, center) 在td上面,设置内容居中;在table上面设置表格居中;在tr上面设置整行居中 bgcolor 设置背景颜色 1234567891011<table border=\"1\" withd=\"150\" height=\"300\" cellspacing=\"20\" cellpadding=\"10\" align=\"center\" bgcolor=\"yellow\"> <tr align=\"center\"> <td align=\"center\">张三</td> <td>18</td> </tr> <tr> <td>李四</td> <td>20</td> </tr></table> 标准结构完整的结构更有利于seo优化,如果变更thead和tfoot的宽高需要在tr标签上设置width和height 1234567891011121314151617181920<table border=\"1\" withd=\"150\" height=\"300\"> <thead> <tr> <td></td> <td></td> </tr> </thead> <tbody> <tr> <td></td> <td></td> </tr> </tbody> <tfoot> <tr> <td></td> <td></td> </tr> </tfoot></table> 表头和表格合并 caption 标签可以设置表格的名称,跟在表格顶部 colspan 合并横向单元格, 合并列 rowspan 合并纵向单, 合并行 123456789101112131415161718<table border=\"1\" width=\"500\" height=\"300\" align=\"center\"> <caption>表头</caption> <tr> <td colspan=\"2\">张三 22</td> <!-- <td>20</td> --> <td rowspan=\"3\">学生</td> </tr> <tr> <td>张三</td> <td>20</td> <!-- <td rowspan=\"2\">学生</td> --> </tr> <tr> <td>张三</td> <td>20</td> <!-- <td>学生</td> --> </tr></table> 标题、边框颜色和垂直对齐 th标签设置表格的标题,自动的加粗和居中 bordercolor设置边框的颜色 valign 设置垂直居中,默认是middle,top上,bottom 底 1234567891011121314151617<table border=\"1\" bordercolor=\"red\" width=\"500\" height=\"300\" cellspacing=\"0\"> <tr> <th>张三</th> <th>20</th> <th>学生</th> </tr> <tr> <td valign=\"top\">张三</td> <td>20</td> <td>学生</td> </tr> <tr> <td>张三</td> <td>20</td> <td>学生</td> </tr></table> 细线表格将table的背景设置为绿色,td的背景设置成白色,td之间的距离设置为1 table背景色 bgcolor为green td背景色,在每个td上或者在tr上设置bgcolor为white cellspacing单元格之间的距离 123456789101112131415161718192021222324252627282930313233343536373839404142434445<table width=\"350\" height=\"250\" bgcolor=\"green\" cellspacing=\"1\" align=\"center\"> <caption>课程表</caption> <tr bgcolor=\"white\"> <th colspan=\"2\"></th> <th>星期一</th> <th>星期二</th> <th>星期三</th> <th>星期四</th> <th>星期五</th> </tr> <tr bgcolor=\"white\"> <td rowspan=\"2\">上午</td> <td>1</td> <td>语文</td> <td>数学</td> <td>物理</td> <td>化学</td> <td>生物</td> </tr> <tr bgcolor=\"white\"> <td>2</td> <td>体育</td> <td>音乐</td> <td>几何</td> <td>画画</td> <td>舞蹈</td> </tr> <tr bgcolor=\"white\"> <td rowspan=\"2\">下午</td> <td>1</td> <td>体育</td> <td>画画</td> <td>音乐</td> <td>语文</td> <td>音乐</td> </tr> <tr bgcolor=\"white\"> <td>2</td> <td>英语</td> <td>舞蹈</td> <td>体育</td> <td>唱歌</td> <td>体育</td> </tr></table> 表单用于搜集用户的信息 基本使用 action 用于处理表单中的信息,通常由服务端来进行处理 method 表单提交的方式,一般有get、post input-text 文本输入框,maxlength控制输入的最大长度,readonly控制只读,disabled未激活状态 name 属性,控件名称。提交时传递到服务器的名称 value 属性,提交时传递到服务器的值 input-password 密码输入框 input-radio 单选框,通过name进行分组,默认选中需要设置checked="checked" select 下拉选择框,option为选项标签,默认选中需要设置selected="selected",设置可以多选需要在select上设置multiple。可以通过设置optgroup进行下拉列表的分组 input-checkbox 多选框,通过name进行分组,默认选中需要设置checked="checked" textarea 多行文本框.cols控制可见的列数,rows控制可见的行数 input-file 文件上传 input-button 普通按钮不能实现提交的功能,显示的值通过value来设置 input-image 图片按钮。可以实现提交的功能 input-reset 重置按钮。将信息恢复到默认状态,已经输入的信息将会丢失 fieldset 对表单信息进行分组。通过legend子标签设置分组名称 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758<form action=\"1.php\" method=\"get\"> <!-- 文本框 --> 用户名:<input type=\"text\" name=\"username\" maxlength=\"6\" readonly=\"readonly\" disabled=\"disabled\" value=\"王者荣耀\"><br> <!-- 密码框 --> 密&nbsp;&nbsp;码:<input type=\"password\" name=\"pwd\"><br> <!-- 单选框 --> 性别:<input type=\"radio\" name=\"gender\" checked=\"checked\">男 <input type=\"radio\" name=\"gender\">女 <br> <!-- 下拉列表 --> 省(市):<select name=\"fav\"> <option>河北</option> <option>山东</option> <option>山西</option> <option selected=\"selected\">北京</option> </select> <br> 省(市):<select name=\"fav2\" multiple=\"multiple\"> <option>河北</option> <option>山东</option> <option>山西</option> <option selected=\"selected\">北京</option> </select> <br> <!-- 对下拉列表的信息进行分组 --> 市(区):<select name=\"fav3\"> <optgroup label=\"北京市\"> <option>昌平区</option> <option>海淀区</option> <option>朝阳区</option> <option>大兴区</option> </optgroup> <optgroup label=\"广州市\"> <option>昌平区</option> <option>海淀区</option> <option>朝阳区</option> <option>大兴区</option> </optgroup> </select> <br> <!-- 多选框 --> 爱好:<input type=\"checkbox\" name=\"fav4\" value=\"football\">足球 <input type=\"checkbox\" checked=\"checked\" name=\"fav4\" value=\"basketball\">篮球 <input type=\"checkbox\" name=\"fav4\" value=\"pingpong\">乒乓 <br> <!-- 多行文本框 --> <textarea cols=\"30\" rows=\"10\"></textarea><br> <!-- 文件上传 --> <input type=\"file\"><br> <!-- 表单进行分组 --> <fieldset> <legend>按钮分组</legend> <!-- 普通按钮 --> <input type=\"button\" value=\"普通按钮\"><br> <!-- 图片按钮 --> <input type=\"image\" src=\"button.jpg\"><br> <!-- 重置按钮 --> <input type=\"reset\"> <br> </fieldset> <input type=\"submit\"></form> H5表单控件 input-url 网址控件 input-date 日期控件 input-time 时间控件 input-email 邮件控件 input-number 数字控件,step属性设置步长默认是1。max,min等 input-range 滑块控件,同样可以通过step设置步长 12345678910111213141516<form action=\"1.php\" method=\"post\"> <!-- 网址控件 --> <input type=\"url\"> <br> <!-- 日期控件 --> <input type=\"date\"> <br> <!-- 时间控件 --> <input type=\"time\"> <br> <!-- 邮件控件 --> <input type=\"email\"> <br> <!-- 数字控件 --> <input type=\"number\" step=\"3\"> <br> <!-- 滑块控件 --> <input type=\"range\" step=\"20\"> <input type=\"submit\"></form> 标签语义化语义化是指去掉css样式表之后格式依然很清析。实现语义化的注意事项: 尽可能少的使用无语义的标签div和span 语义不明显时尽量使用p标签。 不要使用纯样式标签,如:b、font、u等,改用css设置 需要强调的文本可以包含在strong(加粗)或者em(斜体)标签中","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"HTML","slug":"HTML","permalink":"http://blog.xxyxpy.pub/tags/HTML/"}]},{"title":"Http请求利器","slug":"java/tool/Http请求利器","date":"2018-01-08T01:43:51.000Z","updated":"2018-01-08T05:40:58.296Z","comments":true,"path":"2018/01/08/java/tool/Http请求利器/","link":"","permalink":"http://blog.xxyxpy.pub/2018/01/08/java/tool/Http请求利器/","excerpt":"Http Request简介Http Request是一个轻量级的Http请求封装工具,不依赖于其它的第三方组件。使用起来简单快捷。 Maven12345<dependency> <groupId>com.github.kevinsawicki</groupId> <artifactId>http-request</artifactId> <version>6.0</version></dependency>","text":"Http Request简介Http Request是一个轻量级的Http请求封装工具,不依赖于其它的第三方组件。使用起来简单快捷。 Maven12345<dependency> <groupId>com.github.kevinsawicki</groupId> <artifactId>http-request</artifactId> <version>6.0</version></dependency> Get请求 获取响应的状态码 1int response = HttpRequest.get(\"http://google.com\").code(); 获取响应的内容 12String response = HttpRequest.get(\"http://google.com\").body();System.out.println(\"Response was: \" + response); Get请求中添加参数 12HttpRequest request = HttpRequest.get(\"http://google.com\", true, 'q', \"baseball gloves\", \"size\", 100);System.out.println(request.toString()); // GET http://google.com?q=baseball%20gloves&size=100 处理请求和响应的header 12345678910111213String contentType = HttpRequest.get(\"http://google.com\") .accept(\"application/json\") //Sets request header .contentType(); //Gets response headerSystem.out.println(\"Response content type was \" + contentType);// 拼装好map传入Map<String, String> map = new HashMap<>();map.put(\"Connection\", \"keep-alive\");map.put(\"Accept-Encoding\", \"gzip,deflate\");map.put(\"Accept-Language\", \"zh-CN,en-US;q=0.8\");HttpRequest request = new HttpRequest(\"http://google.com\", \"GET\") .headers(map);String response = request.body(\"UTF-8\"); 使用代理 12345HttpRequest request = new HttpRequest(\"http://google.com\", \"GET\") .useProxy(\"122.224.227.202\", 3128) // 中国代理 .proxyBasic(\"username\", \"p4ssw0rd\"); // 代理的用户名和密码,可选项 .contentType(\"text/html\", \"UTF-8\");String response = request.body(\"UTF-8\"); 需要身份认证的请求 1int response = HttpRequest.get(\"http://google.com\").basic(\"username\", \"p4ssw0rd\").code(); 在请求Https网址时忽略安全校验 12345HttpRequest request = HttpRequest.get(\"https://google.com\");//Accept all certificatesrequest.trustAllCerts();//Accept all hostnamesrequest.trustAllHosts(); 跟进重定向 1int code = HttpRequest.get(\"http://google.com\").followRedirects(true).code(); post请求 获取响应状态码 1int response = HttpRequest.post(\"http://google.com\").send(\"name=kevin\").code(); 提交组装的map数据 12345Map<String, String> data = new HashMap<String, String>();data.put(\"user\", \"A User\");data.put(\"state\", \"CA\");if (HttpRequest.post(\"http://google.com\").form(data).created()) System.out.println(\"User was created\"); 参考: 使用说明","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"tool","slug":"java/tool","permalink":"http://blog.xxyxpy.pub/categories/java/tool/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://blog.xxyxpy.pub/tags/Java/"}]},{"title":"JavaScript(9)事件","slug":"前端/JS/JavaScript 高级程序设计/JavaScript(9)事件","date":"2018-01-07T07:01:16.000Z","updated":"2018-03-28T05:52:08.505Z","comments":true,"path":"2018/01/07/前端/JS/JavaScript 高级程序设计/JavaScript(9)事件/","link":"","permalink":"http://blog.xxyxpy.pub/2018/01/07/前端/JS/JavaScript 高级程序设计/JavaScript(9)事件/","excerpt":"事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代码。在传统的软件工程中被称为观察者模式,支持页面的行为(JavaScript代码)与页面的外观(Html和Css代码)之间的松散耦合。 事件流事件流描述的是从页面中接收事件的顺序。目前有两种不同的事件流定义。IE的事件流是事件冒泡流,从触发事件的元素开始向上传递。NetScape的事件流是事件捕获流,从document开始向下传递直到触发事件的元素。 事件冒泡IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点。 12345678910<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><div id=\"myDiv\">Click Me</div></body></html> 如果单击了页面中的div元素,那么click事件会按 div,body,html,document这要的顺序传播。也就是说click事件会首先在div元素上发生,而这个元素就是我们单击的元素。click事件沿DOM树向上传播,在每一级节点上都会发生,直至传播到document对象。 事件捕获事件捕获的思路是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。以前面的页面为例,单击div元素时click事件会按document,html,body,div这样的顺序向下传播。事件捕获一般不推荐使用,尽量使用事件冒泡 DOM事件流事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收不骄不躁伯。最后一个阶段是冒泡,可以在这个阶段对事件作出响应。","text":"事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代码。在传统的软件工程中被称为观察者模式,支持页面的行为(JavaScript代码)与页面的外观(Html和Css代码)之间的松散耦合。 事件流事件流描述的是从页面中接收事件的顺序。目前有两种不同的事件流定义。IE的事件流是事件冒泡流,从触发事件的元素开始向上传递。NetScape的事件流是事件捕获流,从document开始向下传递直到触发事件的元素。 事件冒泡IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点。 12345678910<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><div id=\"myDiv\">Click Me</div></body></html> 如果单击了页面中的div元素,那么click事件会按 div,body,html,document这要的顺序传播。也就是说click事件会首先在div元素上发生,而这个元素就是我们单击的元素。click事件沿DOM树向上传播,在每一级节点上都会发生,直至传播到document对象。 事件捕获事件捕获的思路是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。以前面的页面为例,单击div元素时click事件会按document,html,body,div这样的顺序向下传播。事件捕获一般不推荐使用,尽量使用事件冒泡 DOM事件流事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收不骄不躁伯。最后一个阶段是冒泡,可以在这个阶段对事件作出响应。 事件处理程序事件就是用户或浏览器自身执行的某种动作。诸如click、load和mouseover都是事件的名字。而响应某个事件的函数就叫做事件处理程序。事件处理程序的名字以on开头,因此click事件的事件处理程序就是onclick,load事件的事件处理程序就是onload。有多种方式为事件指定处理程序。 HTML事件处理程序某个元素支持的每种事件都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是能够执行的JavaScript代码。如: 1<input type='button' value='click me' onclick=\"alert('Clicked')\"/> 通过指定onclick特性并将一些JavaScript代码作为它的值来定义。由于这个值是JavaScript,因此不能在其中命名用未经转义的HTML语法字符,例如和号、双引号、小于号或大于号。 同样也可以在页面其他地方定义脚本,然后设置为事件处理程序。 123456<script> function showMessage(){ alert('hello world!'); }</script><input type='button' value='click me' onclick=\"showMessage()\"/> 事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。这样指定事件处理程序具有一些独到之处。首先,这样会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象。 12<!-- 输出click --><input type='button' value='click me' onclick=\"alert('Clicked')\"/> 通过event变量,可以直接访问事件对象,不用自己来定义它,也可用从函数的参数列表中读取。在这个函数内部this值等于事件的目标元素。例如: 12<!-- 输出click me --><input type='button' value='click me' onclick=\"alert(this.value)\"/> 关于这个动态创建的函数,另一个有意思的地方是它扩展作用域珠方式。在这个函数内部,可以像访问局部变量一样访问document及该元素本身的成员。这个函数使用with像下面这样扩展作用域: 1234567function(){ with(document){ with(this){ // 元素属性值 } }} 如此一来,事件处理程序要访问自己的属性就简单多了。下面这行代码与前面的例子效果相同: 12<!-- 输出click me --><input type='button' value='click me' onclick=\"alert(value)\"/> 如果当前元素是一个表单输入元素,则作用域事还会包含访问表单元素(父元素)的入口,这个函数就变成了如下所示: 123456789function(){ with(document){ with(this.form){ with(this){ // 元素属性值 } } }} 实际上,这样扩展作用域的方式,无非就是想让事件处理程序无需引用表单元素就能访问其他表单字段。如: 1234<form action=\"\"> <input type=\"text\" name=\"username\" value=\"abc\"> <input type=\"button\" value=\"Click Me5\" onclick='console.log(username.value)'> <br></form> 这样定义虽然方便,但有一个很大的弊端是JavaScript代码与html代码的紧耦合。 DOM0级事件处理程序通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种方式定义的事件处理程序是在元素的作用域中运行,所以程序中的this引用当前元素。如: 1234567var btn = document.getElementById('myBtn');btn.onclick = function () { console.log('clicked'); console.log(this.id); // 删除 btn.onclick = null;} DOM2级事件处理程序通过addEventListener()和removeEventListener()来指定和删除事件处理程序。所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false则表示在冒泡阶段调用事件处理程序。 添加事件处理程序如下: 12345var btn = document.getElementById('myBtn');// 第3个参数,false表示捕获阶段处理,true表示冒泡阶段处理btn.addEventListener('click', function () { console.log(this.id);}, false); 使用此种方式的好处是可以为一个事件添加多个处理程序。当存在多个事件处理程序时,触发的顺序为添加的顺序。 12345// 可以添加多个处理程序var handler = function () { console.log('abc');};btn.addEventListener('click', handler, false); 通过addEventListener()添加的处理程序只能使用removeEventListener()来移除,移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()添加的匿名函数无法移除。 12// removebtn.removeEventListener('click', handler, false); 跨浏览器事件处理程序由于IE中添加和移除处理程序的方式和DOM2中不同。所以跨浏览器使用时需要兼容的代码。 IE中使用attachEvent()和detachEvent()两个方法来添加和移除处理程序。都接受两个参数:事件处理程序名称和事件处理程序函数。名称中的字符串需要带on。 1234567891011121314151617181920var EventUtil = { addHandler: function (element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { // ie中 element.attachEvent('on' + type, handler); } else { element['on' + type] = handler; } }, removeHandler: function (element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { // ie中 element.detachEvent('on' + type, handler); } else { element['on' + type] = null; } }}; 使用 123456var btn = document.getElementById('myBtn');var handler = function () { console.log('abc');};EventUtil.addHandler(btn, 'click', handler);EventUtil.removeHandler(btn, 'click', handler); 事件对象DOM中的事件对象兼容DOM的浏览器会将一个event对象传入到事件处理程序中。无论事件处理程序是通过何种方式添加的。 123456var btn = document.getElementById('myBtn');btn.onclick = function (ev) { console.log(ev.type); console.log(ev.currentTarget === this); console.log(ev.target === this);}; event对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有的事件都会有下表列出的成员。 属性/方法 类型 说明 bubbles Boolean 表明事件是否冒泡 cancelable Boolean 表明是否可以取消事件的默认行为 currentTarget Element 事件处理程序当前正在处理事件的那个元素 defaultPrevented Boolean 为true表示已经调用了preventDefalut() detail Integer 与事件相关的细节信息 eventPhase Integer 调用事件处理程序的阶段:1捕获阶段 2处于目标 3冒泡阶段 preventDefalut() Function 取消事件的默认行为。如果cancelable是true则可以使用此方法 stopImmediatePropagation() Function 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用 stopPropagation() Function 取消事件的进一步捕获或冒泡,如果bubbles为true则可以使用这个方法 target Element 事件的目标 trusted Boolean 为true表示事件是浏览器生成的。为false表示事件是由开发人员通过JavaScript创建的 type String 被触发的事件的类型 view AbstractView 与事件关联的抽象视图。等同于发生事件的window对象。 在事件处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标。 如果直接将事件处理程序指定给了目标元素,则this、currentTarget和target包含相同的值。 123456var btn = document.getElementById('myBtn');btn.onclick = function (ev) { console.log(ev.type); console.log(ev.currentTarget === this); // true console.log(ev.target === this); // true}; 在需要通过一个函数处理多个事件时,可以使用type属性。如: 1234567891011121314151617var btn2 = document.getElementById('myBtn2');var handler = function (ev) { switch (event.type) { case 'click': console.log('clicked'); break; case 'mouseover': event.target.style.backgroundColor = 'red'; break; case 'mouseout': event.target.style.backgroundColor = ''; break; }};btn2.onclick = handler;btn2.onmouseover = handler;btn2.onmouseout = handler; 要阻止特定事件的默认行为,可以使用preventDefalut()方法。如阻止a标签中链接的跳转。如果要中止事件的传播,即取消进一步的事件捕获或冒泡。可以使用stopPropagation()方法 1234567891011121314<a href=\"http://www.baidu.com\" id=\"myLink\">baidu</a> <br><script> // 阻止特定事件的默认行为 preventDefault() var link = document.getElementById('myLink'); link.onclick = function (ev) { ev.preventDefault(); // 阻止捕获或冒泡,body上面的click事件不会执行 ev.stopPropagation(); }; document.body.onclick = function (ev) { console.log('body click'); };</script> 事件对象的eventPhase属性可以用来确定事件当前正位于事件流的哪个阶段。 123456789101112// eventPhase属性 1 捕获阶段调用的事件处理程序;2 事件处理程序处于目标对象上;3 冒泡阶段调用的事件处理程序var btn3 = document.getElementById('myBtn3');btn3.onclick = function (ev) { console.log(ev.eventPhase); // 2};document.body.addEventListener('click', function (ev) { console.log(ev.eventPhase); // 1 }, true);document.body.onclick = function (ev) { console.log(ev.eventPhase); // 3}; 跨浏览器事件对象IE中通过window.event来访问事件对象,所以在跨浏览器调用时需要兼容。 12345678910111213141516171819202122232425262728293031323334353637383940var EventUtil = { addHandler: function (element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { // ie中 element.attachEvent('on' + type, handler); } else { element['on' + type] = handler; } }, removeHandler: function (element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { // ie中 element.detachEvent('on' + type, handler); } else { element['on' + type] = null; } }, getEvent: function (event) { return event ? event : window.event; }, getTarget: function (event) { return event.target || event.srcElement; }, preventDefault: function (event) { if (event.preventDefault) { event.preventDefault; } else { event.returnValue = false; } }, stopPropagation: function (event) { if (event.stopPropagation) { event.stopPropagation; } else { event.cancelBubble = true; } }}; 事件类型DOM3级事件规定了以下几类事件: UI事件,当用户与页面上的元素交互时触发; 焦点事件,当用户获得或失去焦点时触发; 鼠标事件,当用户通过鼠标在页面上执行操作时触发; 滚轮事件,当使用鼠标滚轮时触发; 文本事件,当在澡输入文本时触发; 键盘事件,当用户通过键盘在页面上执行操作时触发; 合成事件,当为IME(输入法编辑器)输入字符时触发; 变动事件,当底层DOM结构发生变化时触发; 变动名称事件,当元素或属性名变动时触发。已经废弃。 UI事件主要有以下事件: load:当页面完全加载后在window上面触发,当所有的框架加载完之后在框架集上面触发,当图像加载完之后在img元素上触发; unload:当页面完全卸载后在window上触发,当甩的框架都卸载后在框架集上触发,或当嵌入的内容卸载完后在object元素上面触发; abort:在用户停止下载过程时如果嵌入的内容没有加载完,则在object元素上触发; error:当发生JavaScript错误时在window上面触发,当无法加载图像时在img元素上触发,当无法加载入的内容时,则在object元素上触发,当有一个或多个框架无法加载时在框架集上触发; select:当用户选择文本框(input或textarea)中的一个或多个字符时触发; resize:当窗口或框架的大小变化时在window或框架上面触发; scroll:当用户滚动带滚动条的元素中的内容时在该元素上触发。body元素中包含所加载页面的滚动条。 load事件JavaScript中最常用的一个事件。当页面加载完全后(包括所有的图像、JavaScript文件、Css文件等外部资源),就会触发window上面的load事件。 1234// onloadwindow.onload = function (ev) { console.log('loaded');}; unload事件这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload事件。而利用这个事件最多的情况是清除引用,以避免内存泄漏。 1234// unloadwindow.onunload = function () { console.log('unloaded');}; resize事件当浏览器窗口被调整到一个新的高度或宽度时触发。 1234// resizeEventUtil.addHandler(window, 'resize', function (ev) { console.log('resized');}); scroll事件当页面滚动时触发 123456789// scrollEventUtil.addHandler(window, 'scroll', function (ev) { // 输出页面的垂直滚动位置 if (document.compatMode == 'CSS1Compat') { console.log(document.documentElement.scrollTop); } else { console.log(document.body.scrollTop); }}); 焦点事件焦点事件会在页面获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及document.activeElement属性可以使,可以知晓用户在页面上的行踪。有以下6个焦点事件: blur,在元素失去焦点时触发。这个事件不冒泡; DOMFocusIn,在元素获得焦点时触发,只有opera支持该事件; DOMFoucsOut,在元素失去焦点时触发,只有opera支持该事件; focus,在元素获得焦点时触发。这个事件不冒泡; focusin,在元素获得焦点时触发。冒泡; focusout,在元素失去焦点时触发。冒泡。 当焦点从页面中的一个元素移动到另一个元素上时会依次触发下列事件: (1)focusout在失去焦点的元素上触发;(2)focusin在获得焦点的元素上触发;(3)blur在失去焦点的元素上触发;(4)DOMFocusOut在失去焦点的元素上触发;(5)focus在获得焦点的元素上触发;(6)DOMFocusIn在获得焦点的元素上触发。 鼠标与滚轮事件主要有9个鼠标事件: click,单击鼠标按钮时触发; dbclick,双击鼠标按钮时触发; mousedown,按下任意鼠标按钮时触发; mouseenter,鼠标光标从元素外部首次移动到元素范围之内时触发。不冒泡,且移动到后代元素上不触发; mouseleave,在位于元素上方的鼠标光标移动到元素范围之外时触发。不冒泡; mousemove,当鼠标指针在元素内部移动时重复地触发; mouseout,在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发; mouseover,在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发; mouseup,在用户释放鼠标按钮时触发。 客户区坐标位置指从浏览器窗口上面和左边的距离 12345var myDiv = document.getElementById('myDiv');// 客户区坐标位置EventUtil.addHandler(myDiv, 'click', function (event) { console.log('client:' + event.clientX + \"-\" + event.clientY);}); 页面坐标位置和客户区坐标位置不同的是会加上滚动的距离 1234// 页面坐标位置EventUtil.addHandler(myDiv, 'click', function (event) { console.log('page:' + event.pageX + \"-\" + event.pageY);}); 屏幕坐标位置相对于整个电脑屏幕的位置 1234// 屏幕坐标位置EventUtil.addHandler(myDiv, 'click', function (event) { console.log('screen:' + event.screenX + \"-\" + event.screenY);}); 修改键用来判断用户按下的键 1234567891011121314151617// 修改键EventUtil.addHandler(myDiv, 'click', function (event) { var keys = new Array(); if (event.shiftKey) { keys.push('shift'); } if (event.ctrlKey) { keys.push('ctrl') } if (event.altKey) { keys.push('alt') } if (event.metaKey) { keys.push('meta') } console.log(keys.join(','));}); 相关元素在发生mouseover和mouseover事件时,还会涉及到更多的元素。这两个事件都会涉及把鼠标指针从一个元素的边界之内移动到另一个元素的边界之内。对mouseover事件而言,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素。类似地,对mouseout事件而言,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素。 鼠标按钮只有在主鼠标按钮被单击(或键盘回车键被按下)时才会触发click事件。对于mousedown和mouseover事件来说在其event对象存在一个button属性,表示按下或释放的按钮。DOM的button属性可能有如下3个值:0 主鼠标按钮(左键),1 中间的滚轮按钮, 2 次鼠标按钮(右键) 123456// 相关元素EventUtil.addHandler(myDiv, 'mouseout', function (event) { console.log(event.relatedTarget); // 鼠标按钮值 console.log(event.button);}); 鼠标滚轮事件通过mousewheel事件可以来监听鼠标的滚轮变化。其中向前时wheelDelta是120的倍数,向后时是-120的倍数。 1234// 滚轴EventUtil.addHandler(document, 'mousewheel', function (event) { console.log(event.wheelDelta);}); 键盘与文本事件有3个键盘事件: keydown:当用户按下键盘上的任意键时触发,如果按下不放则会重复触发; keypress:当用户按下键盘上的字符键时触发,如果按下不放则会重复触发; keyup:当用户释放键盘上的键时触发。 虽然所有元素都支持以上3个事件,但是只有在用户通过文本框输入文本时才最常用到。 文本事件只有一个textInput。这个事件是对keypress的补充,用意是在将文本显示给用户之前更容易挂载文本。在文本插入文本框之前会触发textInput事件。 在用户按下了键盘上的字符键时首先会触发keydown事件,然后是keypress事件,最后会触发keyup事件。其中前两个事件是在文本框发生变化之前触发的,keyup事件是在变化之后被触发的。如果按下不放会重复触发keydown和keypress事件,直到用户松开该键为止。如果用户按下的是非字符键,则会一直重复触发keydown事件,直到用户松开这个键,此时会触发keyup事件。 键码在发生keydown和keyup事件时,event对象的keyCode属性中会包含一个代码(ASCII码),与键盘上一个 特定的键对应。 charCode属性只有在发生keypress事件时才包含值,而且这个值是按下的那个键所代码字符的ASCII编码。此时的keyCode通常等于0或者也可能等于所按键的键码。 12345var myText = document.getElementById('myText');EventUtil.addHandler(myText, 'keyup', function (event) { console.log(event.keyCode); console.log(event.charCode);}); textInput事件用于替代keypress事件。两者的区别为: 任何可以获得焦点的元素都可以触发keypress事件,但只有可编辑区域才能触发textInput事件; textInput事件只会在用户按下能够输入实际字符的键时才会被触发,而keypress事件则在按下那些能够影响文本显示的键时也会触发(例如退格键)。 此事件的event对象中包含data属性,用于输出用户输入的字符。 123EventUtil.addHandler(myText, 'textInput', function (event) { console.log(event.data);}); 内存和性能在javascript中添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而卖到的DOM访问次数会延迟整个页面的交互就绪时间。 事件委托对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。查看如下代码: 12345<ul id=\"myLinks\"> <li id=\"goSomewhere\">Go somewhere</li> <li id=\"doSomething\">Do something</li> <li id=\"sayHi\">Say hi</li></ul> 此时可以这样去为三个li元素指定click事件 12345678910111213141516var list = document.getElementById('myLinks');EnventUtil.addHandler(list, 'click', function(event){ event = EventUtils.getEvent(event); var target = EventUtils.getTarget(event); switch(target.id){ case 'doSomething': console.log('doSomething'); break; case 'goSomewhere': console.log('goSomewhere'); break; case 'sayHi': console.log('sayHi'); break; }}); 移除处理程序当执行innerHTML替换页面中某一部分的时候。如果带有事件处理程序的元素被删除了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收。所以在执行innerHTML之前需要先移除相应元素的处理程序。","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://blog.xxyxpy.pub/tags/JavaScript/"},{"name":"JavaScript高级程序设计","slug":"JavaScript高级程序设计","permalink":"http://blog.xxyxpy.pub/tags/JavaScript高级程序设计/"}]},{"title":"Java抓取网页数据","slug":"java/tool/Java抓取网页数据","date":"2018-01-05T01:24:49.000Z","updated":"2018-01-05T02:10:40.726Z","comments":true,"path":"2018/01/05/java/tool/Java抓取网页数据/","link":"","permalink":"http://blog.xxyxpy.pub/2018/01/05/java/tool/Java抓取网页数据/","excerpt":"Jsoup简介jsoup是一个java版本的html网页解析组件,可以通过类似于JQuery选择器的方式去选择想要的DOM节点。所以学习成本非常的低,只要会使用JQuery就能快速的上手。 官方网址","text":"Jsoup简介jsoup是一个java版本的html网页解析组件,可以通过类似于JQuery选择器的方式去选择想要的DOM节点。所以学习成本非常的低,只要会使用JQuery就能快速的上手。 官方网址 加载html内容 类似于C#的html-agility-pack解析器也支持文件,字符串或web请求的方式来加载hmtl内容。 1234567891011121314151617// 从字符串String html = \"<html><head><title>haha</title></head><body><p>content</p></body></html>\";Document doc = Jsoup.parse(html);// 从url中,即通过web请求Document doc = Jsoup.connect(\"http://www.baidu.com/\") .proxy(\"127.0.0.1\", \"1234\") // 使用代理 .data(\"query\", \"Java\") // 请求参数 .userAgent(\"I ’ m jsoup\") // 设置 User-Agent .cookie(\"auth\", \"token\") // 设置 cookie .timeout(3000) // 设置连接超时时间 .post(); // 使用 POST 方法访问 URL // 从文件中加载File input = new File(\"D:/test.html\"); // 第三个参数指的是基础的url,当input中使用的是相对路径时就可以拼接上去Document doc = Jsoup.parse(input,\"UTF-8\",\"http://www.oschina.net/\"); Maven仓库12345<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>LATEST</version></dependency> 获取某团的所有城市假如现在要抓取链接中的所有城市信息,代码如下 1234567891011121314151617181920212223242526272829303132333435public void getAllCity() throws Exception { Document doc = Jsoup.connect(\"https://i.meituan.com/index/changecity?cevent=imt%2Fhd%2FcityBottom\") .get(); // get请求 // 所有的城市以拼音分组,被放在class为abc的div标签中 // 所以先把所有的组选择出来,得到一个列表 Elements elements = doc.select(\".abc\"); List<City> list = new ArrayList<>(); for (Element element : elements) { // 获取到分组的值 ABCD... String firstPy = element.select(\"h4\").text(); // 选取a标签得到此分组下面的所有的城市列表 Elements a = element.select(\"a\"); for (Element aElement : a) { // 如果是 ‘更多’ 节点则丢弃掉 if (aElement.hasAttr(\"data-more\")) { continue; } City city = new City(); city.setFirstPy(firstPy); city.setCityId(0); city.setProvince(\"\"); // 得到 href 属性,此为城市链接 city.setHref(aElement.attr(\"href\")); // 得到 data-citypinyin 属性,此为城市拼音 city.setPinyin(aElement.attr(\"data-citypinyin\")); // 得到 a 标签的内容,此为城市名称 city.setCityName(aElement.text()); list.add(city); System.out.println(city); } System.out.println(\"=========================\"); } // 保存到数据库 cityDao.save(list);} 处理结果: 参考: 使用 jsoup 对 HTML 文档进行解析和操作 【Jsoup】HTML解析器,轻松获取网页内容","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"tool","slug":"java/tool","permalink":"http://blog.xxyxpy.pub/categories/java/tool/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://blog.xxyxpy.pub/tags/Java/"}]},{"title":"服务器开启Https","slug":"linux/服务器开启Https","date":"2018-01-04T01:34:28.000Z","updated":"2018-01-04T06:18:00.173Z","comments":true,"path":"2018/01/04/linux/服务器开启Https/","link":"","permalink":"http://blog.xxyxpy.pub/2018/01/04/linux/服务器开启Https/","excerpt":"申请免费的SSL证书目前可以申请免费个人站点证书的网站比较多,个人推荐使用腾讯云,好处是申请速度快,半小时之内就可以通过审核。提交的资料也简单,只需要实名认证即可。试过七牛云还需要提供公司等很多信息,比较麻烦。当然还有很多其它的可以通过这个页面去选择。 注意选择的证书类型为域名型免费版,期限为一年 仅支持单域名的申请,如abc.mysite.com.不支持通配符如*.mysite.com,这种要收钱的 仅支持免费20个域名,如果不够的话可以考虑再结合其它家的一起,如阿里云","text":"申请免费的SSL证书目前可以申请免费个人站点证书的网站比较多,个人推荐使用腾讯云,好处是申请速度快,半小时之内就可以通过审核。提交的资料也简单,只需要实名认证即可。试过七牛云还需要提供公司等很多信息,比较麻烦。当然还有很多其它的可以通过这个页面去选择。 注意选择的证书类型为域名型免费版,期限为一年 仅支持单域名的申请,如abc.mysite.com.不支持通配符如*.mysite.com,这种要收钱的 仅支持免费20个域名,如果不够的话可以考虑再结合其它家的一起,如阿里云 下载证书申请成功之后可以通过下载按钮下载证书 下载解压后会发现腾讯云已经把所有的常用的服务器部署方式需要的证书按格式都整理好了。这里我用的是nginx,直接把nginx目录下面的.crt和.key两个文件复制到要开启https的服务器上 服务器操作 将.crt和.key文件放到/var/cert目录下,如果目录不存在则先创建目录。这两个文件路径在下面的nginx配置时需要用到,假设为a.crt和a.key。实际上腾讯云先成的文件名为你申请的域名 为了确保更强的安全性,我们可以采取迪菲-赫尔曼密钥交换。执行此操作时需要等待约一分钟: 123> cd /var/cert/> openssl dhparam -out a.pem 2048> nginx配置修改nginx中要开启的服务的.conf文件,默认的nginx安装路径为/etc/nginx/.由于主的配置文件(nginx.conf)中有include /etc/nginx/conf.d/*.conf;配置,所以只需要在conf.d目录下添加后缀名为conf的配置文件即可以自动的添加到配置文件中 在conf.d目录下添加文件c.conf 1touch c.conf 配置80端口301转发 意思是如果是http请求过来的跳转为https。在c.conf中添加如下内容: 1234567891011server { listen 80; server_name 你的域名如c.yourdomain.com; return 301 https://c.yourdomain.com$request_uri; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://你的ip地址:端口号; index index.html index.htm; }} 配置SSL 继续在c.conf中添加如下内容。配置完成之后保存退出 123456789101112131415161718192021server { listen 443 ssl; server_name 你的域名如a.yourdomain.com; # ssl配置,使用到了/var/cert/目录下面的三个文件 ssl on; ssl_certificate /var/cert/c.crt; ssl_dhparam /var/cert/c.pem; ssl_certificate_key /var/cert/c.key; ssl_session_timeout 5m; ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; ssl_prefer_server_ciphers on; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://你的ip地址:端口号; index index.html index.htm; }} 重新加载配置 使用nginx -t检测配置是否正常 检测正常后nginx -s reload重新加载配置使之生效 最后的设置需要注意的一点是linux对于端口的控制比较严格,上面的配置使用到了80和443端口。80端口访问权限一般都会开通。而访问80端口时会自动转发到443端口发起请求,所以443端口必须要设置访问权限。否则还是访问不了https 123456# 适用于centos7 其它操作系统的自行设置firewall-cmd --add-port=443/tcp --permanent# 查询是否开启443端口,发现没开启,原因是需要重启一发firewall-cmd --query-port=443/tcp# 重启reboot 做完这一步再次访问https//c.yourdomain.com就可以了,访问http开头的地址也会跳转到https 参考: 为Nginx配置 SSL 证书 + 搭建 HTTPS网站","categories":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/tags/linux/"},{"name":"https","slug":"https","permalink":"http://blog.xxyxpy.pub/tags/https/"}]},{"title":"JavaScript(8)DOM扩展","slug":"前端/JS/JavaScript 高级程序设计/JavaScript(8)DOM扩展","date":"2017-12-27T07:01:16.000Z","updated":"2018-03-13T02:06:10.600Z","comments":true,"path":"2017/12/27/前端/JS/JavaScript 高级程序设计/JavaScript(8)DOM扩展/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/27/前端/JS/JavaScript 高级程序设计/JavaScript(8)DOM扩展/","excerpt":"对DOM的扩展主要有Selectors API和HTML5以及元素遍历规范。还有innerHtml、children以及innerText 选择符API根据CSS选择符选择与某个模式匹配的DOM元素。 querySelector()接收一个css选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null。 123456789101112131415161718192021<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><div id=\"myDiv\"></div><div class=\"selected\"></div><script> // body var body = document.querySelector('body'); // 取得id为myDiv的元素 var myDiv = document.querySelector('#myDiv'); // 取得类为selected的第一个元素 var selected = document.querySelector('.selected'); // 取得类为button的第一个图像元素 var img = document.body.querySelector('img.button');</script></body></html> querySelectorAll()接收的参数与querySelector()方法一样,但返回的是所有匹配的元素而不仅仅是第一个元素。此方法返回的是带有所有属性和方法的NodeList,而其底层实现则类似于一组元素的快照,而非不断对文档进行搜索的动态查询。这样实现可以避免使用NodeList对象通常会引起的大多数性能问题。 只要传给querySelectorAll()方法的css选择符有效,该方法都会返回一个NodeList对象,而不管找到多少匹配的元素。如果没有找到匹配的元素,NodeList就是空的。","text":"对DOM的扩展主要有Selectors API和HTML5以及元素遍历规范。还有innerHtml、children以及innerText 选择符API根据CSS选择符选择与某个模式匹配的DOM元素。 querySelector()接收一个css选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null。 123456789101112131415161718192021<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><div id=\"myDiv\"></div><div class=\"selected\"></div><script> // body var body = document.querySelector('body'); // 取得id为myDiv的元素 var myDiv = document.querySelector('#myDiv'); // 取得类为selected的第一个元素 var selected = document.querySelector('.selected'); // 取得类为button的第一个图像元素 var img = document.body.querySelector('img.button');</script></body></html> querySelectorAll()接收的参数与querySelector()方法一样,但返回的是所有匹配的元素而不仅仅是第一个元素。此方法返回的是带有所有属性和方法的NodeList,而其底层实现则类似于一组元素的快照,而非不断对文档进行搜索的动态查询。这样实现可以避免使用NodeList对象通常会引起的大多数性能问题。 只要传给querySelectorAll()方法的css选择符有效,该方法都会返回一个NodeList对象,而不管找到多少匹配的元素。如果没有找到匹配的元素,NodeList就是空的。 123456789101112131415161718192021222324<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><div id=\"myDiv\"></div><div class=\"selected\"></div><script> // 取得某个div中的甩的em元素,类似于getElementByTagName('em') var ems = document.getElementById('myDiv').querySelectorAll('em'); // 取得类为selected的所有元素 var selecteds = document.querySelectorAll('.selected'); // 取得p元素中的所有strong元素 var strongs = document.querySelector('p strong'); // 遍历 var i, len, strong; for (i=0,len=strongs.lenght; i<len; i++) { console.log(strongs[i]); // strongs.item(i) }</script></body></html> matchesSelector()接收一个参数,即css选择符。如果调用元素与该选择符匹配则返回true,否则返回false。 不同的浏览器支持的方法名不同。IE:msMatchesSelector();FireFox:mozMatchesSelector();Safari,Chrome:webkitMatchesSelector() 1234// 如果调用的元素与该选择符匹配,则返回true,否则返回falseif(document.body.webkitMatchesSelector('body')){ console.log(\"ok\");} 元素遍历对于元素间的空格,有的浏览器会返回为文本节点,有的不返回。这样导致在使用时的firstChild等属性行为不一致。为了弥补这一差异,而同时又保持DOM规范不变,重新定义了一组新的属性。 childElementCount:返回子元素的个数,不包含文本节点和注释; firstElementChild:指向第一个子元素,firstChild的元素版; lastElementChild:指向最后一个子元素,lastChild的元素版; previousElementSibling:指向前一个同辈元素,previousSibling的元素版; nextElementSibling:指向后一个同辈元素,nextSibling的元素版。 123456789101112131415161718192021222324252627282930<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><ul> <li>a</li> <li>b</li> <li id=\"haha\">c</li> <li>d</li> <li>e</li></ul><script> var haha = document.getElementById('haha'); // 子元素个数 console.log(haha.parentNode.childElementCount); // 第一个子元素 console.log(haha.parentNode.firstElementChild); // 最后一个子元素 console.log(haha.parentNode.lastElementChild); // 前一个同辈元素 console.log(haha.previousElementSibling); // 后一个同辈元素 console.log(haha.nextElementSibling);</script></body></html> HTML5与类相关的扩充getElementsByClassName()方法接受一个参数,即一个包含一个或多个类名的字符串,返回带有指定类的所有元素的NodeList。传入多个类名时不区分类名的先后顺序。 classList属性在操作类名时,需要通过className属性添加、删除和替换类名。因为className是一个字符串,所以只修改字符串的一部分也必须每次都设置整个字符串的值。如此操作比较繁琐。HTML5中新增了一个操作类名的方法,为所有的元素添加了classList属性。这个属性是新集合类型DOMTokenList的实例。此类型定义如下方法: add(value):将给定的字符串值添加到列表中,如果值已经存在就不添加; contains(value):判断列表中是否存在给定的值,如果存在返回true,否则返回false; remove(value):从列表中删除给定的字符串; toggle(value):如果列表中已经存在则删除它,否则添加它。 12345678910111213141516171819202122232425262728293031323334<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><div class=\"bd user disabled\" id=\"myDiv\"></div><script> var hehe = document.getElementsByClassName('hehe'); console.log(hehe); // 操作类名 var div = document.getElementById('myDiv'); // 删除disabled类 div.classList.remove('disabled'); // 添加 current类 div.classList.add('current'); // 切换 user类 div.classList.toggle('user'); // 确定元素中是否包含指定的类名 if(div.classList.contains('bd') && !div.classList.contains('disabled')){ //... } // 迭代类名 for (var i=0,len=div.classList.length; i<len; i++) { console.log(div.classList[i]); }</script></body></html> 焦点管理HTML5添加了辅助管理DOM焦点的功能。 document.activeElement属性这个属性始终会引用DOM中当前获得了焦点的元素。元素获得焦点的方式有页面加载、用户输入(通常是通过按Tab键)和在代码中调用focus()方法。默认情况下文档则加载完时document.activeElement中保存的是document.body的引用。文档加载期间document.activeElement的值为null。 document.hasFocus()这个方法用于确定元素是否获得了焦点 1234567891011121314151617<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><button id=\"myButton\">aa</button><script> var button = document.getElementById('myButton'); button.focus(); console.log(document.activeElement == button); // has focus console.log(document.hasFocus()); // true</script></body></html> HTMLDocument的变化document.readyState属性指示文档是否已经加载完成。loading:正在加载;complete:加载完成。 123456789101112// readyState属性 loading completeif(document.readyState == 'complete'){ console.log('ok');} else { console.log('no');}window.onload = function (ev) { if(document.readyState == 'complete'){ console.log('ok'); }}; 兼容模式区分渲染页面的模式是标准的还是混杂的。在标准模式下document.compatMode的值等于CSS1Compat,混杂模式下值等于BackCompat 123456// 兼容模式,标准 CSS1Compat or 混杂 BackCompatif (document.compatMode == 'CSS1Compat'){ console.log('Standards Mode');} else { console.log('Quirks Mode');} document.head属性引用文档的head元素,与document.getElementsByTagName('head')[0]等效 12// head属性console.log(document.head); 字符集属性使用document.charset可以访问和设置字符集。但是部分浏览器设置时不生效。 另外可以使用document.defaultCharset查看默认的字符集。同样部分浏览器不支持此属性 1234console.log(document.charset);document.charset = 'UTF-8';console.log(document.charset);console.log(document.defaultCharset); 自定义数据属性HTML5规定可以为元素添加非标准的属性,但要添加前缀data-,目的是为元素提供与渲染无关的信息,或者提供语义信息。这些属性可以任意添加、随便命名,只要以data-开头即可。 添加完自定义属性之后,可以通过元素的dataset属性来访问自定义属性的值。dataset属性的值是DOMStringMap的一个实例,是一个键值对的映射。在这个映射中name值为属性名去掉data-前缀。 1234567891011121314151617181920<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><div id=\"myDiv\" data-appid=\"12345\" data-myname=\"Nicholas\"></div></body></html><script> var div = document.getElementById('myDiv'); // appid var appid = div.dataset.appid; var myname = div.dataset.myname; console.log(appid + \": \" + myname); appid = '123'; myname = 'abc'; console.log(appid + \": \" + myname);</script> 插入标记innerHTML属性在读取时此属性返回与调用元素的所有子节点(包括元素、注释以及文本节点)对应的html标记。在设置时会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点。 outerHTML属性在读取时返回调用它的元素及所有子节点的HTML标签。在设置时会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素。 12345678910111213141516171819202122<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><div id=\"myDiv\" data-appid=\"12345\" data-myname=\"Nicholas\"> <p>123</p></div></body></html><script> var div = document.getElementById('myDiv'); console.log(div.innerHTML); div.innerHTML = '<br>abc<br>'; // outerHTML console.log(div.outerHTML); div.outerHTML = '<p>bcd</p>'; // insertAdjacentHTML // 参数1: beforebegin afterbegin beforeend afterend</script> scrollIntoView()可以在所有的HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出来在视窗中。如果给此方法传入true或不传入任何元素,那么会让调用元素的顶部与视窗顶部尽可能的平齐。如果传入false,调用元素会尽可能的全部出现在视窗中,(可能的话,调用元素的底部会与视窗顶部平齐)不过顶部不一定平齐。 专有扩展children属性同包含元素中同样还是元素的子节点,不包含空白符。当元素只包含元素子节点时与childNodes值相同。 contains()判断某个节点是不是另外一个节点的后代。 compareDocumentPosition()确定两个节点之间的关系。返回一个表示该关系的位掩码。 1 无关;2 居前;4 居后;8 包含;16 被包含。 123456789101112131415161718192021222324252627<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><ul> <li>a</li> <li>b</li> <li>c</li> <li>d</li> <li>e</li></ul></body></html><script> // children 同包含元素节点,去除空白符 var ele = document.getElementsByTagName('ul')[0]; var firstChild = ele.children[0]; console.log(ele.children.length); console.log(firstChild); // contains 某个节点是否是另一个节点的后代 console.log(ele.contains(firstChild)); // true // compareDocumentPosition 1 无关 2 居前 4 居后 8 包含 16 被包含 console.log(ele.compareDocumentPosition(firstChild)); // 20 = 4 + 16</script> 插入文本innerText属性读取时获取元素下面的所有的文本内容,包括子文档树中的文本。设置值时会删除元素的所有子节点,插入包含相应文本值的文本节点。 outerText属性与innerText没有多大区别,唯一的区别在于作用范围扩大到了包含调用它的节点。在设置值时会替换整个元素(包含子节点) 12345678910111213141516171819<!DOCTYPE html></head><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title><body><div id=\"myDiv\"><p>abc</p></div></body></html><script> // innerText var myDiv = document.getElementById('myDiv'); console.log(myDiv.innerText); myDiv.innerText = '<p>\\\"hello !\\\"</p>' // outerText console.log(myDiv.outerText); myDiv.outerText = 'aaa';</script>","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://blog.xxyxpy.pub/tags/JavaScript/"},{"name":"JavaScript高级程序设计","slug":"JavaScript高级程序设计","permalink":"http://blog.xxyxpy.pub/tags/JavaScript高级程序设计/"}]},{"title":"c#抓取网页数据","slug":"csharp/csharp抓取网页数据","date":"2017-12-26T09:04:10.000Z","updated":"2017-12-26T09:28:27.675Z","comments":true,"path":"2017/12/26/csharp/csharp抓取网页数据/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/26/csharp/csharp抓取网页数据/","excerpt":"html-agility-packhtml-agility-pack是一个非常轻量级的网页抓取工具库,不需要依赖其它的库,大小只有100多k.可以在nuget上直接进行下载。 加载html字符串 支持通过文件、字符串或web请求的方式来加载html字符串包装成HtmlDocument对象 由于库中包装了http请求,所以web请求非常的简单,具体可以看下面的例子。 节点解析 支持通过xPath和Linq方式进行节点和内容获取","text":"html-agility-packhtml-agility-pack是一个非常轻量级的网页抓取工具库,不需要依赖其它的库,大小只有100多k.可以在nuget上直接进行下载。 加载html字符串 支持通过文件、字符串或web请求的方式来加载html字符串包装成HtmlDocument对象 由于库中包装了http请求,所以web请求非常的简单,具体可以看下面的例子。 节点解析 支持通过xPath和Linq方式进行节点和内容获取 获取网页内容现在要抓取横店m站上面的通知公告标题及内容,代码如下: 123456789101112131415161718192021222324252627void Main(){ String html = @"http://m.hengdianworld.com/"; HtmlWeb web = new HtmlWeb(); var htmlDoc = web.Load(html); //Console.WriteLine(htmlDoc); var nodes = htmlDoc.DocumentNode.SelectNodes("//div[@class='content']/ul//a"); //Console.WriteLine(nodes.Count); foreach (HtmlNode htmlNode in nodes) { Console.WriteLine(htmlNode.OuterHtml); String url = htmlNode.Attributes["href"].Value; Console.WriteLine(String.Format("title:{0},url:{1}", htmlNode.InnerText, url)); //Console.WriteLine(htmlNode.OuterHtml); // 子页面 var innerHtmlDoc = web.Load(url); // 时间 HtmlNode dateNode = innerHtmlDoc.GetElementbyId("ctl00_ydContent2_Labeltime"); // 内容 HtmlNode contentNode = innerHtmlDoc.GetElementbyId("ctl00_ydContent2_Labelcont"); Console.WriteLine("----->date:{0}, content:{1}", dateNode.InnerText, contentNode.InnerHtml); Console.WriteLine("======="); }} html-agility-pack官网 使用案例 xPath语法","categories":[{"name":"c#","slug":"c","permalink":"http://blog.xxyxpy.pub/categories/c/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"http://blog.xxyxpy.pub/tags/爬虫/"}]},{"title":"linux","slug":"linux/linux","date":"2017-12-25T09:04:10.000Z","updated":"2018-01-04T06:17:29.770Z","comments":true,"path":"2017/12/25/linux/linux/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/linux/linux/","excerpt":"关闭防火墙12345678910111213# 查看防火墙状态service iptables status# 查看已经开启的端口/etc/init.d/iptables status # 开启chkconfig iptables on# 关闭chkconfig iptables off# centos 7防火墙关闭,默认使用的是firewall作为防火墙,使用iptables必须重新设置一下systemctl stop firewalld.service #停止firewallsystemctl disable firewalld.service #禁止firewall开机启动yum -y install iptables-services #设置 iptables service 目录操作","text":"关闭防火墙12345678910111213# 查看防火墙状态service iptables status# 查看已经开启的端口/etc/init.d/iptables status # 开启chkconfig iptables on# 关闭chkconfig iptables off# centos 7防火墙关闭,默认使用的是firewall作为防火墙,使用iptables必须重新设置一下systemctl stop firewalld.service #停止firewallsystemctl disable firewalld.service #禁止firewall开机启动yum -y install iptables-services #设置 iptables service 目录操作 12345678# 创建目录mkdir a# -p参数,递归创建mkdir -p a/b/c# 删除目录,要求目录必须为空rmdir -p a/b/c# 删除带有数据的目录,r删除目录 f强制删除rm -rf c 拷贝123cp test1.txt test2.txt# 拷贝目录需要添加 -rcp -r a/ b 移动文件或目录可用来重命名文件或目录。不需要加-r 1mv apache-tomcat-8.0.44/ /usr/local/application/solr/tomcat-solr 生成和写入文件 123456# 有此文件时更新时间,没有时生成touch 1.txt# 写入数据到指定的文件echo "abc" > 2.txt# 读取文件内容显示cat 2.txt 权限操作权限分为x运行,w写入和r读取值分别为4,2,1。 chmod 语法: chmod [-R] xyz 文件名(这里的xyz,表示数字) -R选项只作用于目录,作用是级联更改,即不仅更改当前目录,连目录里的目录或者文件全部更改 如果你创建了一个目录,而该目录不想让其他人看到内容,则只需设置成 rwxr—– (740) 即可。 12345678910111213# 如果想设置全部的权限即rwx(4+2+1),设置权限值为777# b目录以及其所有的子目录和文件权限全部为rwxrwxrwxchmod -R 777 b# 可以使用r w x方式单独设置权限,使用前需要清楚以下内容# 分类user使用u表示,代表用户(前三位);group使用g表示代表所属组(中间三位);# other使用o表示代表其它组(最后三位);all使用a表示以上三个(9位)# + 加入权限 - 减少权限 = 设置等于此权限# 例1:1.txt文件og添加w的权限chmod og+w 1.txt# 例2:b目录所有去掉r权限,使用a或为空表示所有chmod -r b# 例3:设置多个使用,隔开chmod u=rwx,og=rx 2.txt 文件查找 find1234# -name filename 直接查找该文件名的文件find -name temp# -type type 通过文件类型查找,文件类型有f(文件),d(目录),b,c,l(连接档类似于windows下的快捷方式),sfind -type d 切换用户 su [-] username 123su -# 打印当前登录的用户echo $LOGNAME 修改密码1passwd [username] 磁盘管理1234# 查看磁盘使用情况df -h# 查看占用空间大小 du [-abckmsh] [文件或者目录名] a全部列出 c最后加总 h自动调节单位du -h nginx-1.8.0.tar.gz 开启关闭指定端口123456789# 打开/sbin/iptables -I INPUT -p tcp --dport 3306 -j ACCEPT# 关闭/sbin/iptables -I INPUT -p tcp --dport 80 -j DROP/etc/rc.d/init.d/iptables save# 重启服务/etc/rc.d/init.d/iptables restart# 查看端口是否已开放/etc/init.d/iptables status centos7开启指定端口12345678# 开机启用防火墙systemctl enable firewalld # 开启防火墙systemctl start firewalld# 查询是否开启80端口firewall-cmd --query-port=12345/tcp# 开启80端口 --permanent 表示永久有效firewall-cmd --add-port=80/tcp --permanent 查看tomcat的启动信息1tail -f logs/catalina.out 拷贝目录。可以同时指定新目录名 12# 拷贝solr目录下的所有内容到solrhome下cp -r solr/ /usr/local/application/solr/solrhome 编辑工具vim使用一般模式:默认打开文件时。可以进行上下移动光标;删除某个字符;删除某行;复制、粘贴一行或者多行。此模式下可以使用 编辑模式:可以用i(在当前字符前插入字符)I(在当前行首插入字符)a(在当前字符后插入字符)A(在当前行末插入字符)o(在当前行下插入新的一行)O(在当前行上插入新的一行)。进入编辑模式时,会在屏幕的最下一行出现“INSERT或REPLACE”的字样。从编辑模式回到一般模式只需要按一下键盘左上方的ESC键即可 命令模式:在一般模式下,输入”:”或者”/”即可进入命令模式。在该模式下,你可以搜索某个字符或者字符串,也可以保存、替换、退出、显示行号等等 :w 将编辑过的文档保存。 :w! 若文本属性为只读时强制保存 :q 退出vim :q! 不管编辑或未编辑都不保存退出 :wq 保存并退出 :set nu 在行首显示行号 :set nonu 取消行号 翻前页:ctrl+f或pageup 翻后页:ctrl+b或pagedown 查找:/word 查找到光标之后的word字符串,n可以继续查找下一个,N可以查找上一个。?word可以查找到光标之前的word字符串 压缩与打包12345678910111213141516171819202122232425262728# 压缩文件gzip [-d#] filename 其中#为1-9的数字。不可以压缩目录# -d :解压缩时使用# -# :压缩等级,1压缩最差,9压缩最好,6为默认gzip schema.xml# 解压gzip -d schema.xml.gz########### tar ############## tar [-zjxcvfpP] filename# -z :是否同时用gzip压缩# -j :是否同时用bzip2压缩# -x :解包或者解压缩# -t :查看tar包里面的文件# -c :建立一个tar包或者压缩文件包# -v :可视化# -f :后面跟文件名,压缩时跟-f文件名,意思是压缩后的文件名为filename,解压时跟-f文件名,# 意思是解压filename。请注意,如果是多个参数组合的情况下带有-f,请把f写到最后面。# -p :使用原文件的属性,压缩前什么属性压缩后还什么属性。(不常用)# -P :可以使用绝对路径。(不常用)# --exclude filename :在打包或者压缩时,不要将filename文件包括在内。(不常用)# 压缩 b目录->b.tartar -cvf b.tar b# 解压tar -xvf b.tar # 打包同时使用gzip压缩tar -zcvf b.tar.gz b # 只查看压缩包内的内容,不解压tar -tf b.tar.gz yum配置CentOS则你可以从/etc/yum.repos.d/CentOS-Base.repo这个文件下看到相关的配置信息 123456# 搜索包yum search vimyum list |grep vim# 安装一个rpm包 yum install [-y] [rpm包名] -y选项,会以与用户交互的方式安装# 卸载一个rpm包 “yum remove [-y] [rpm包名]”# 升级一个rpm包 “yum update [-y] [rpm包]” 安装源码包三个步骤 ./config 在这一步可以定制功能,加上相应的选项即可,具有有什么选项可以通过”./config –help ”命令来查看。在这一步会自动检测你的linux系统与相关的套件是否有编译该源码包时需要的库,因为一旦缺少某个库就不能完成编译。只有检测通过后才会生成一个Makefile文件。 make 使用这个命令会根据Makefile文件(必须要有此文件)中预设的参数进行编译,这一步其实就是gcc在工作了。 make install 安装步骤,生成相关的软件存放目录和配置文件的过程 12345678910# 下载安装tomcat# 1 下载源码包wget http://apache.mirrors.ionfish.org/tomcat/tomcat-8/v8.0.46/bin/apache-tomcat-8.0.46.tar.gz# 2 解压源码包# 3 配置相关选项,生成makefile# 4 编译# 5 安装 shell12345678910111213141516171819202122232425262728293031323334353637# 1 历史命令。!!执行上一条 !n 执行命令历史中第n条指令 !字符串(字符串大于等于1),例如!ta,表示执行命令 # 历史中最近一次以ta为开头的指令# 2 指令和文件名补全 按tab键,它可以帮你补全一个指令,也可以帮你补 # 全一个路径或者一个文件名。连续按两次tab键,系统则会把所有的指令或者文件名都列出来# 3 别名。alias显示所有的别名# 4 通配符,使用*来匹配零个或多个字符,而用?匹配一个字符ls -d test*# 5 输入输出重定向。输出重定向>,追加重定向>># 6 管道|,就是把前面的命令运行的结果丢给后面的命令# 7 作业控制。当运行一个进程时,你可以使它暂停(按Ctrl+z),然后使用fg命令恢复它,利用bg命令使他到后台运 # 行,你也可以使它终止(按Ctrl+c)# 查看环境变量PATH等信息echo $PATHecho $PWDecho $HOMEecho $LOGNAME# 查看所有的预设环境变量envset# 用户自定义变量,只在当前的shell中生效。使用bash命令即可再打开一个shellmyname=Amingecho $myname# 1 如果想系统内所有用户登录后都能使用该变量。需要在/etc/profile文件最末行加入 “export myname=Aming” # 然后运行”source /etc/profile”就可以生效了。此时你再运行bash命令或者直接su - test账户看看# 2 只想让当前用户使用该变量。需要在用户主目录下的.bashrc文件最后一行加入“export myname=Aming”然后运行”source .bashrc”就可以生效了。这时候再登录test账户,myname变量则不会生效了。上面用的source命令的作用是,讲目前设定的配置刷新,即不用注销再登录也能生效 # pstree 把linux系统中所有进程通过树形结构打印出来pstree |grep bash# export 指令父shell中设定一个变量后,进入子shell后该变量是不会生效的, # 如果想让这个变量在子shell中生效则要用到export指令。export其实就是声明一下这个变量的意思, # 让该shell的子shell也知道变量abc的值是123.如果export后面不加任何变量名,则它会声明所有的变量。# 取消一个设置的变量abc=123echo $abcunset abc 系统环境变量与个人环境变量的配置文件系统级别: /etc/profile:这个文件预设了几个重要的变量,例如PATH, USER, LOGNAME, MAIL… /etc/bashrc :这个文件主要预设umask以及PS1。这个PS1就是我们在敲命令时,前面那串字符了 用户级别(在用户目录下): .bash_profile:定义了用户的个人化路径与环境变量的文件名称。每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次 .bashrc:该文件包含专用于你的shell的bash信息,当登录时以及每次打开新的shell时,该该文件被读取。例如你可以将用户自定义的alias或者自定义变量写到这个文件中。 .bash_history:记录命令历史用的。 .bash_logout :当退出shell时,会执行该文件。可以把一些清理的工作放到这个文件中。 shell中的特殊符号123456789101112# 1 * 代表0个或多个字符或数字ls -d test*# 2 ? 代表一个任意字符touch testa testb testcls -d test?# 3 # 代表注释的意思# 4 \\ 转入字符,将特殊符号进行转入ls test\\*# 5 | 管道符。用于将特号前面执行的结果丢给符号后面的命令# 6 $ 一种方式是作为变量前的标识符,另外和!结合起来使用 !$ 表示上条命令中最后一个变量ls testa # show testals !$ # show ls testa testa shell中管道命令1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556# 1 grep 过滤一个或多个字符cat /etc/passwd |grep root# 2 cut:截取某一个字段 语法:cut -d “分隔字符” [-cf] n 这里的n是数字 # -d :后面跟分隔字符,分隔字符要用双引号括起来 # -c :后面接的是第几个字符 # -f :后面接的是第几个区块cat /etc/passwd |cut -d ":" -f 1# 3 sort 用做排序 语法:sort [-t 分隔符] [-kn1,n2] [-nru] 这里的n1 < n2 # -t 分隔符:作用跟cut的-d一个意思 # 第 12 章 学习 shell 脚本之前的基础知识 | 151 # -n :使用纯数字排序 # -r :反向排序 # -u :去重复 # -kn1,n2 :由n1区间排序到n2区间,可以只写-kn1,即对n1字段排序head -n5 /etc/passwd |sort -t: -k3n# 4 wc:统计文档的行数、字符数、词数,常用的选项为: # -l :统计行数 # -m :统计字符数 # -w :统计词数cat 1.txt |wc -lcat 1.txt |wc -mcat 1.txt |wc -w# 5 uniq:去重复的行 在进行uniq之前,需要先用sort排序然后才能uniq,否则你将得不到你想要的 # -c :统计重复的行数,并把行数写在前面cat 1.txt |uniqcat 1.txt |uniq -c# 6 tee :后跟文件名,类似与重定向”>”,但是比重定向多了一个功能,在把文件写入后面所跟的文件中的同 # 时,还显示在屏幕上。echo "haha" |tee 1.txt# 7 tr 替换字符-d :删除某个字符,-d 后面跟要删除的字符 -s :把重复的字符去掉# 小写转大写cat 1.txt |tr '[a-z]' '[A-Z]'# 单个字符替换cat 1.txt |grep a |tr 'd' 'D'# 8 split :切割文档,常用选项: # -b :依据大小来分割文档,单位为byte # -l :依据行数来分割文档split -b 500 3.txt mysplit -l 1 1.txt your# 9 分号:平时我们都是在一行中敲一个命令,然后回车就运行了,那么想在一行中运行两个或两个以上的命 # 令如何呢?则需要在命令之间加一个”;”了ls -d test*;touch test5;ls -d test* # 10 & :如果想把一条命令放到后台执行的话,则需要加上这个符号。通常用于命令运行时间非常长的情况sleep 100 &# 查看后台任务jobs可以查看当前shell中后台执行的任务。用fg可以调到前台执行。# 这里的sleep命令就是休眠的意思,后面跟数字,单位为秒,常用语循环的shell脚本中# 此时你按一下CTRL +z 使之暂停,然后再输入bg可以再次进入后台执行# 如果是多任务情况下,想要把任务调到前台执行的话,fg后面跟任务号,任务号可以使用jobs命令得到jobs# 11 >, >>, 2>, 2>>:前面讲过重定向符号> 以及>> 分别表示取代和追加的意思,# 然后还有两个符号就是这里的2> 和 2>> 分别表示错误重定向和错误追加重定向,当我们运行一个命令报错时,# 报错信息会输出到当前的屏幕,如果想重定向到一个文本里,则要用2>或者2>># 12 [ ]:中括号,中间为字符组合,代表中间字符中的任意一个ll test[a-c]# 12 && || 两条命令时类似于java中的判断ls testa && touch test11 正则表达式grep 语法: grep [-cinvABC] ‘word’ filename -c :打印符合要求的行数-i :忽略大小写-n :在输出符合要求的行的同时连同行号一起输出-v :打印不符合要求的行-A :后跟一个数字(有无空格都可以),例如 –A2则表示打印符合要求的行以及下面两行-B :后跟一个数字,例如 –B2 则表示打印符合要求的行以及上面两行-C :后跟一个数字,例如 –C2 则表示打印符合要求的行以及上下各两行 1234567891011121314# 过滤出带有某个关键词的行并输出行号grep -n 'root' /etc/passwd# 过滤不带有某个关键词的行,并输出行号grep -vn "nologin" /etc/passwd# 过滤出所有包含数字的行 如果要过滤出数字以及大小写字母则要这样写[0-9a-zA-Z]grep [0-9] 123.txt# 以某个字段开头或以某个字段结尾的行。^ 开头 $ 结尾 grep -v ‘^$’ filename表示非空行grep '^a' 1.txtgrep 'a$' 1.txt# 过滤任意一个字符与重复字符 . 表示任意一个字符,* 表示0个或多个字符grep 'a..d' 1.txtgrep 'a*' 1.txt# 指定要过滤字符出现的次数grep 'c\\{1\\}' 1.txt 查看程序的端口占用情况1234567891011121314151617181920# 查看tomcat端口占用ps -aux | grep tomcat# 查看所有的进程和端口使用情况 其中最后一栏是PID/Program name netstat –apn# 根据结果中的PID查看是哪个程序在占用。发现8080端口被PID为9658的Java进程占用。# 进一步使用命令:ps -aux | grep java,或者直接:ps -aux | grep pid 查看# 找到后使用KILL命令结束掉ps -aux | grep javaps -aux | grep pid # 方法二:直接使用 netstat -anp | grep portno# 即:netstat –apn | grep 8080# centos中# 比如查看80端口占用情况lsof -i tcp:80# 列出所有端口netstat -ntlp# centos 7 3306端口占用情况ss -lnp|grep 3306 shell脚本凡是自定义的脚本建议放到/usr/local/sbin/目录下 test.sh中第一行一定是 “#! /bin/bash”它代表的意思是,该文件使用的是bash语法 默认我们用vim编辑的文档是不带有执行权限的,所以需要加一个执行权限 使用sh命令去执行一个shell脚本的时候是可以加-x选项来查看这个脚本执行过程的 sh -x test.sh 1234# date 格式化日期 W表示星期date "+%Y%m%d %H:%M:%S" # 天数加减date -d "-1 day" "+%Y%m%d" 在shell中使用变量 反引号表示执行里面的内容。变量的引用要用$开头后面跟上变量名 123456789#! /bin/bashd=`date +%H:%M:%S`echo "the script begin at $d"echo "now we will sleep 2 seconds."sleep 2d1=`date +%H:%M:%S`echo "the script end at $d1 数学计算要用[]括起来并且以$开头 1234567#! /bin/basha=1b=2sum=$[$a+$b]echo "sum is $sum" 和用户交互,提供输入功能.read表示需要用户通过键盘进行输入 12345678#! /bin/bashecho "Please input a number:"read xecho "Please input another number:"read ysum=$[$x+$y]echo "The sum of tow number is:$sum." 另外一种更为简单的方式。read -p选项类似于echo的作用 123456#! /bin/bashread -p "Please input a number:" xread -p "Please input another number:" ysum=$[$x+$y]echo "The sum of tow number is:$sum." shell脚本的预设变量,例如/etc/init.d/iptables restart 以下脚本执行 sh -x test6.sh 1 2 12345#! /bin/bashsum=$[$1+$2]echo $sumecho $0 $1 $2 shell脚本中的逻辑判断 不带else. ((a<60))这样的形式,这是shell脚本中特有的格式,用一个小括号或者不用都会报错 123456#! /bin/bashread -p "Please input your score:" aif ((a<60)); then echo "You didn't pass the exam."fi 带else 12345678#! /bin/bashread -p "Please input your score:" aif ((a<60)); then echo "You didn't pass the exam."else echo "Good! You pass the exam."fi 带elif的多重判断 && 表示“并且”的意思,当然你也可以使用 || 表示“或者” 12345678910#! /bin/bashread -p "Please input your score:" aif ((a<60)); then echo "You didn't pass the exam."elif ((a>60)) && ((a<85)); then echo "Good! You pass the exam."else echo "Very Good! Your Score is very high."fi 在判断数值大小除了可以用”(( ))”的形式外,还可以使用”[ ]”。但是就不能使用>, < , = 这样的符号了,要使用 -lt (小于),-gt (大于),-le (小于等于),-ge (大于等于),-eq (等于),-ne (不等于) 12a=10;if [ $a -lt 5 ]; then echo ok;fia=10;if [ $a -gt 5 ]; then echo ok;fi shell 脚本中if还经常判断关于档案属性,比如判断是普通文件还是目录,判断文件是否有读写执行权限等 12345678-e :判断文件或目录是否存在-d :判断是不是目录,并是否存在-f :判断是否是普通文件,并存在-r :判断文档是否有读权限-w :判断是否有写权限-x :判断是否可执行if [ -d /home ] ; then echo ok; fiif [ -f /home ] ; then echo ok; fi shell脚本中使用case 1234567891011121314151617181920212223242526case 变量 invalue1)command;;value2)command;;value3)command;;*)command;;esac#! /bin/bashread -p "Please input a number:" na=$[$n%2]case $a in 1) echo "The number is odd" ;;0) echo "The number is even"esac shell脚本中使用for while循环 seq 1 5 表示从1到5的一个序列 1234567891011121314151617181920212223242526272829for 变量名 in 循环的条件; docommanddone#! /bin/bashfor i in `seq 1 5`; do echo $idone# 循环的条件那一部分也可以写成这样的形式,中间用空格隔开即可for i in 1 2 3 4 5; do echo $i; donewhile 条件; docommanddone#! /bin/basha=10while [ $a -ge 1 ]; do echo "$a" a=$[$a-1]done# 忽略掉while中的条件 while :; docommanddone shell脚本中的函数 shell脚本中,函数一定要写在最前面,不能出现在中间或者最后 function 函数名() {command} 1234567#! /bin/bashfunction sum(){ sum=$[$1+$2] echo $sum}sum $1 $2 日常管理1234567891011121314151617181920212223242526272829w 查看当前系统的负载vmstat 监控系统的状态top 显示进程所占系统资源sar -n DEV 查看网卡流量 sar -q 查看历史负载 free 查看内存使用状况ps aux 查看系统进程 进程id可以用 kill pid杀掉。杀不掉的可以使用 kill -9 pid 可以用ps aux |grep redis进行筛选netstat 查看网络状况 netstat -lnp(打印当前系统启动哪些端口)以及netstat -an (打印网络连接状况)tcpdump 抓包工具 yum install -y tcpdump 命令去安装一下ifconfig 查看网卡ip 设置IP非常简单修改配置文件/etc/sysconfig/network-scripts/ifcfg-eth0了,如果是eth1那么配置文件是/etc/sysconfig/network-scripts/ifcfg-eth1.如果你的linux是通过dhcp服务器自动获得的IP,BOOTPROTO那里会是’dhcp’,如果你要配置成静 态IP的话,这里就需要写成’none’当修改完IP后需要重启网络服务新IP才能生效,重 启命令为’ service network restart’mii-tool 查看网卡连接状态,虚拟网卡不支持hostname 显示主机名。hostname abc 即修改主机名为abc 不过这样修改只是保存在内存中, 下次重启还会变成未改之前的主机名, 所以需要你还要去更改相关的配置文件’/etc/sysconfig/network’vim /etc/resolv.conf 设置DNS 文件/etc/hosts也能解析域名, 不过是需要我们手动在里面添加IP 域名这些内容,它的作用是临时解析某个域名防火墙 将selinux关闭, vim /etc/selinux/config 将 SELINUX 修改为 disabled iptableswho -b 查看最后一次系统启动的时间。who -r 查看当前系统运行时间last reboot 系统历史启动的时间df -l -m 查看硬盘使用情况 gitlab相关1234567891011121314151617181920212223242526272829启动所有gitlab组件:gitlab-ctl start停止所有gitlab组件:gitlab-ctl stop重启所有gitlab组件:gitlab-ctl restart备份GitLab repositories and GitLab metadata加入计划任务:0 2 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create恢复a.进入备份gitlab的目录[root@puppet backups]# pwd/var/opt/gitlab/backups[root@puppet backups]# ls1406691018_gitlab_backup.tarb.停止unicorn和sidekiq,保证数据库没有新的连接,不会有写数据情况[root@puppet backups]# gitlab-ctl stop unicornok: down: unicorn: 0s, normally upYou have new mail in /var/spool/mail/root[root@puppet backups]# gitlab-ctl stop sidekiqok: down: sidekiq: 0s, normally upc.恢复数据,1406691018为备份文件的时间戳[root@puppet backups]# gitlab-rake gitlab:backup:restore BACKUP=1406691018 生成git需要的ssh key1ssh-keygen -t rsa -C "youremail@example.com" idea授权服务器自启动12# 修改/etc/rc.local,添加如下内容nohup ./usr/local/ideaServer/IntelliJIDEALicenseServer_linux_amd64 -l vps的ip -p 1017 服务器每天定时重启1234567891011# 安装 crontabyum install vixie-cron crontabschkconfig crond onservice crond start# 编辑定时脚本crontab -e# 每天5点定时重启0 5 * * * /sbin/reboot# 保存退出重启生效/etc/rc.d/init.d/crond stop/etc/rc.d/init.d/crond start 查看SS的网络连接情况1netstat -anp |grep 'ESTABLISHED' |grep '1213' yum 操作12# 查询mysql的源列表yum list|grep mysql","categories":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/tags/linux/"}]},{"title":"web-06Listener,Filter","slug":"java/web/web-06Listener,Filter","date":"2017-12-25T07:54:44.644Z","updated":"2017-12-25T07:54:45.044Z","comments":true,"path":"2017/12/25/java/web/web-06Listener,Filter/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/web/web-06Listener,Filter/","excerpt":"Listener一般自定义的Listener使用的比较少。 作用监听web服务器中域对象ServletContext、ServletRequest、HttpSession。比如spring的监听器ContextLoaderListener可以在web应用程序启动时自动的实例化配置的bean对象。 监听内容 监听三个对象的创建和销毁; 监听三个对象属性的变化; 监听session中的JavaBean的状态。 常用的接口监听三个对象的创建和销毁 ServletContextListener ServletRequestListener HttpSessionListener","text":"Listener一般自定义的Listener使用的比较少。 作用监听web服务器中域对象ServletContext、ServletRequest、HttpSession。比如spring的监听器ContextLoaderListener可以在web应用程序启动时自动的实例化配置的bean对象。 监听内容 监听三个对象的创建和销毁; 监听三个对象属性的变化; 监听session中的JavaBean的状态。 常用的接口监听三个对象的创建和销毁 ServletContextListener ServletRequestListener HttpSessionListener 监听三个对象属性的变化 ServletContextAttributeListener ServletRequestAttributeListener HttpSessionAttributeListener 监听session中的JavaBean的状态 HttpSessionActivationListener(钝化和活化) HttpSessionBindingListener(绑定和解绑) 使用步骤 编写一个类实现要相应的接口 实现接口中的方法 在web.xml中编写配置文件 监听ServletContext123456789101112131415/** * 服务器启动时为每个项目创建(init) * 在正常关闭时调用destroy */public class MyServletContextLis implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { System.out.println(\"servletContext init...\"); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { System.out.println(\"servletContext destroyed\"); }} 在web.xml中进行配置 123<listener> <listener-class>com.listener.MyServletContextLis</listener-class></listener> Filterfilter实现对请求和响应的修改。比如实现对编码的处理,对是登录的校验等。需要在web.xml中配置定义的filter类以及相应要拦截的请求规则。 作用 在HttpServletRequest到达Servlet之前,拦截HttpServletRequest 对HttpServletRequest进行处理,可以修改其头部或请求数据 在HttpServletResponse到达浏览器之前拦截HttpServletResponse 对HttpServletResponse进行处理,可以修改其问部或响应数据 分类一般有如下分类的filter 权限验证filter:比如有些页面必须要登录才可以查看 日志filter:记录某些特殊的用户请求 编码filter:解决请求和响应的乱码问题,典型的如spring中的CharacterEncodingFilter 能改变xml内容的XSLT Filter等 可以进行filter链过滤,即一个请求可以经过多个filter。执行的顺序根据web.xml中配置的顺序从上到下执行 接口方法 init:初始化操作 doFilter:处理业务逻辑 destroy:销毁操作 生命周期filter和servlet一样是单实例多线程。在服务器启动时进行创建,调用init方法进行初始化。当请求过来时创建一个线程,根据路径调用相应filter的doFilter方法执行业务处理。当filter被移除或服务器正常关闭时调用destroy方法执行销毁操作。 使用步骤 编写一个类实现filter接口 在web.xml中进行配置并绑定路径 默认是对浏览器过来的请求进行过滤,如果需要更改通过dispatcher指定相应的值。REQUEST(浏览器过来的请求);FORWARD(转发过来的请求); ERROR(因服务器错误而发送过来的请求); INCLUDE(包含过来的请求)。 1234567<filter-mapping> <filter-name>DispatcherFilter</filter-name> <url-pattern>/e/*</url-pattern> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> <dispatcher>ERROR</dispatcher></filter-mapping> 解决请求参数中文乱码EncodingFilter实现,对获取参数值的方法进行了加强 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.util.Map;/** * 统一编码 * @author Administrator * */public class EncodingFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { //1.强转 HttpServletRequest request=(HttpServletRequest) req; HttpServletResponse response=(HttpServletResponse) resp; //2.放行 chain.doFilter(new MyRequest2(request), response); } @Override public void destroy() { }}class MyRequest2 extends HttpServletRequestWrapper{ private HttpServletRequest request; private boolean flag=true; public MyRequest2(HttpServletRequest request) { super(request); this.request=request; } @Override public String getParameter(String name) { if(name==null || name.trim().length()==0){ return null; } String[] values = getParameterValues(name); if(values==null || values.length==0){ return null; } return values[0]; } @Override /** * hobby=[eat,drink] */ public String[] getParameterValues(String name) { if(name==null || name.trim().length()==0){ return null; } Map<String, String[]> map = getParameterMap(); if(map==null || map.size()==0){ return null; } return map.get(name); } @Override /** * map{ username=[tom],password=[123],hobby=[eat,drink]} */ public Map<String,String[]> getParameterMap() { /** * 首先判断请求方式 * 若为post request.setchar...(utf-8) * 若为get 将map中的值遍历编码就可以了 */ String method = request.getMethod(); if(\"post\".equalsIgnoreCase(method)){ try { request.setCharacterEncoding(\"utf-8\"); return request.getParameterMap(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } }else if(\"get\".equalsIgnoreCase(method)){ Map<String,String[]> map = request.getParameterMap(); if(flag){ for (String key:map.keySet()) { String[] arr = map.get(key); //继续遍历数组 for(int i=0;i<arr.length;i++){ //编码 try { arr[i]=new String(arr[i].getBytes(\"iso8859-1\"),\"utf-8\"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } flag=false; } //需要遍历map 修改value的每一个数据的编码 return map; } return super.getParameterMap(); }} web.xml配置 12345678<filter> <filter-name>EncodingFilter</filter-name> <filter-class>com.filter.EncodingFilter</filter-class></filter><filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping> 参考: servlet/filter/listener/interceptor区别与联系","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"web","slug":"java/web","permalink":"http://blog.xxyxpy.pub/categories/java/web/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"web","slug":"web","permalink":"http://blog.xxyxpy.pub/tags/web/"},{"name":"listener","slug":"listener","permalink":"http://blog.xxyxpy.pub/tags/listener/"},{"name":"filter","slug":"filter","permalink":"http://blog.xxyxpy.pub/tags/filter/"}]},{"title":"web-05事务","slug":"java/web/web-05事务","date":"2017-12-25T07:54:35.875Z","updated":"2017-12-25T07:54:35.900Z","comments":true,"path":"2017/12/25/java/web/web-05事务/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/web/web-05事务/","excerpt":"事务什么是事务完成一件事情需要分多步执行,只要其中有一步出错执行过的所有操作全部回滚。要么全部成功,要么全部失败。 事务的特性ACID 原子性:事务里面的操作单元不可切割,要么全部成功,要么全部失败 一致性:事务执行前后,业务状态和其他业务状态保持一致 隔离性:一个事务执行的时候最好不要受到其他事务的影响 持久性:一旦事务提交或者回滚.这个状态都要持久化到数据库中","text":"事务什么是事务完成一件事情需要分多步执行,只要其中有一步出错执行过的所有操作全部回滚。要么全部成功,要么全部失败。 事务的特性ACID 原子性:事务里面的操作单元不可切割,要么全部成功,要么全部失败 一致性:事务执行前后,业务状态和其他业务状态保持一致 隔离性:一个事务执行的时候最好不要受到其他事务的影响 持久性:一旦事务提交或者回滚.这个状态都要持久化到数据库中 如果不保证一致性会存在如下的问题: 脏读:在一个事务中读取到另一个事务没有提交的数据 事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的 不可重复读:在一个事务中,两次查询的结果不一致。由于查询时其它事务进行了修改(update)操作引起的。 比如事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果 虚读(幻读):在一个事务中,两次查询的结果不一致。由于查询时其它事务进行了插入操作。 例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样 经典案例:转帐A转帐给B,先发生A帐户金额的扣减,再发生B帐户金额的增加。一旦扣减成功而增加失败需要把扣减的金额再加回去。在java中实现此功能,需要手动管理事务提交,一般数据库都是自动提交事务的。 注意:一旦使用手动事务,调用方法的时候都需要手动传入connection,并且需要手动关闭连接。下面这个实现是通过将connection绑定到线路上解决connection一致的问题 DataSourceUtils类,此类用到了c3p0工具包。可以查看前面的jdbc内容 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566import com.mchange.v2.c3p0.ComboPooledDataSource;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;public class DataSourceUtils { private static ComboPooledDataSource dataSource = new ComboPooledDataSource(); private static ThreadLocal<Connection> tl = new ThreadLocal<>(); public static DataSource getDataSource(){ return dataSource; } public static Connection getConnection() throws SQLException { Connection con = tl.get(); if (null == con){ con = dataSource.getConnection(); tl.set(con); } return con; } public static void beginTransaction() throws SQLException { //获取连接并关闭自动提交 getConnection().setAutoCommit(false); } public static void commitTransaction(){ try { Connection con = getConnection(); con.commit(); con.close(); tl.remove(); } catch (SQLException e) { e.printStackTrace(); } } public static void rollBackTransaction(){ try { Connection con = getConnection(); con.rollback(); con.close(); tl.remove(); } catch (SQLException e) { e.printStackTrace(); } } /** * 关闭连接,Statement或ResultSet * @param closeables */ public static void close(AutoCloseable... closeables){ for (AutoCloseable a : closeables) { if (null == a){ continue; } try { a.close(); } catch (Exception e) { e.printStackTrace(); } } }} AccountService类,三层架构中的service层 123456789101112131415public class AccountService { private final AccountDao dao = new AccountDao(); public void account(String from, String to, String money) throws SQLException { try { DataSourceUtils.beginTransaction(); dao.accountOut(from, money); //int i = 1/0; dao.accountIn(to, money); DataSourceUtils.commitTransaction(); }catch (Exception e){ DataSourceUtils.rollBackTransaction(); throw e; } }} AccountDao类,三层架构中的dao层。这里用到了dbutils工具包 1234567891011121314public class AccountDao { public void accountIn(String to, String money) throws SQLException { QueryRunner qr = new QueryRunner(); String sql = \"update account set money=money+? where name=?\"; qr.update(DataSourceUtils.getConnection(), sql, money, to); } public void accountOut(String from, String money) throws SQLException { QueryRunner qr = new QueryRunner(); String sql = \"update account set money=money-? where name=?\"; qr.update(DataSourceUtils.getConnection(), sql, money, from); }} 事务的隔离级别五分钟搞清楚MySQL事务隔离级别 深入分析事务的隔离级别 美团点评-Innodb中的事务隔离级别和锁的关系 读未提交(Read Uncommitted):最低的隔离级别。一个事务可以读取到另外一个事务未提交的数据 读已提交(Read Committed):在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。但是不能解决不可重复读的问题。比如事务一第一读读取时发生在事务二还未提交,读取结果为A。事务二提交后再次读取结果是B。存在AB两次结果不一致的问题。 可重复读(Repeatable reads):由于提交读隔离级别会产生不可重复读的读现象。所以,比提交读更高一个级别的隔离级别就可以解决不可重复读的问题。但是它解决不了幻读(虚读)的问题,因为可重复读时共享锁是加在已经存在的表上面的。如查此时再进行插入操作,再次查询的结果就会不同而产生幻读。 可序列化(Serializable):是最高的隔离级别,前面提到的所有的隔离级别都无法解决的幻读,在可序列化的隔离级别中可以解决。 事务在读取数据时,必须先对其加 表级共享锁 ,直到事务结束才释放;事务1正在读取A表中的记录时,则事务2也能读取A表,但不能对A表做更新、新增、删除,直到事务1结束。(因为事务一对表增加了表级共享锁,其他事务只能增加共享锁读取数据,不能进行其他任何操作) 事务在更新数据时,必须先对其加 表级排他锁 ,直到事务结束才释放。事务1正在更新A表中的记录时,则事务2不能读取A表的任意记录,更不可能对A表做更新、新增、删除,直到事务1结束。(事务一对表增加了表级排他锁,其他事务不能对表增加共享锁或排他锁,也就无法进行任何操作) 虽然可序列化解决了脏读、不可重复读、幻读等读现象。但是序列化事务会产生以下效果: 1.无法读取其它事务已修改但未提交的记录。 2.在当前事务完成之前,其它事务不能修改目前事务已读取的记录。 3.在当前事务完成之前,其它事务所插入的新记录,其索引键值不能在当前事务的任何语句所读取的索引键范围中。 四种事务隔离级别从隔离程度上越来越高,但同时在并发性上也就越来越低。之所以有这么几种隔离级别,就是为了方便开发人员在开发过程中根据业务需要选择最合适的隔离级别。","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"web","slug":"java/web","permalink":"http://blog.xxyxpy.pub/categories/java/web/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"web","slug":"web","permalink":"http://blog.xxyxpy.pub/tags/web/"},{"name":"事务","slug":"事务","permalink":"http://blog.xxyxpy.pub/tags/事务/"}]},{"title":"web-04Jsp,El,Jstl","slug":"java/web/web-04Jsp,El,Jstl","date":"2017-12-25T07:54:29.429Z","updated":"2017-12-25T07:54:29.660Z","comments":true,"path":"2017/12/25/java/web/web-04Jsp,El,Jstl/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/web/web-04Jsp,El,Jstl/","excerpt":"jsp本质上jsp就是一个servlet,在html代码中嵌套java代码,运行在服务器端,处理请求,生成动态的内容。对应的java和class文件在tomcat目录下的work目录。 三种脚本编写方式。上一篇讲过了,简单提一下 <%..%> java代码片段 <%=..%> 输出表达式 相当于out.print() <%!…%> 声明成员","text":"jsp本质上jsp就是一个servlet,在html代码中嵌套java代码,运行在服务器端,处理请求,生成动态的内容。对应的java和class文件在tomcat目录下的work目录。 三种脚本编写方式。上一篇讲过了,简单提一下 <%..%> java代码片段 <%=..%> 输出表达式 相当于out.print() <%!…%> 声明成员 jsp指令一个页面可以出现多个指令,指令可以放在任意位置,一般都放在jsp页面的顶部。 作用:声明 jsp页面的一些属性和动作 格式:<%@指令名称 属性=”值” 属性=”值”%> 指令的分类 page:主要声明jsp页面的一些属性 include:静态包含 taglib:导入标签库 page 属性 作用 pageEncoding 设置页面的编码 contentType 设置响应流的编码,及通知浏览器用什么编码打开.设置文件的mimetype import 导入所需要的包 language 当前jsp页面里面可以嵌套的语言 buffer 设置jsp页面的流的缓冲区的大小 autoFlush 是否自动刷新 extends 声明当前jsp的页面继承于那个类.必须继承的是httpservlet 及其子类 session 设置jsp页面是否可以使用session内置对象 isELIgnored 是否忽略el表达式 errorPage 当前jsp页面出现异常的时候要跳转到的jsp页面 isErrorPage 当前jsp页面是否是一个错误页面若值为true,可以使用jsp页面的一个内置对象 exception contentType和pageEncoding的关系: 两个都设置时使用各自的编码 只设置一个两个都使用出现的这个编码 两个都不设置使用tomcat默认的编码,tomcat8开始默认是utf-8,之前默认是ios8859-1 1234567891011121314// 一般页面<%@ page contentType=\"text/html;charset=UTF-8\" pageEncoding=\"utf-8\" language=\"java\" errorPage=\"error.jsp\" %>// 错误页面<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" isErrorPage=\"true\" %><html><head> <title>error page</title></head><body> <h3>error page</h3><br> <%=exception.getMessage() %></body></html> include静态包含,就是将其他页面或者servlet的内容包含进来,一起编译运行。生成一个java文件。可以理解为这两个页面的代码写在了同一个页面里。 格式:<%@include file="相对路径或者是内部路径" 1234567891011<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>include</title></head><body> <%@include file=\"/jsp/include/i1.jsp\"%> <hr> <%@include file=\"/jsp/include/i2.jsp\"%></body></html> taglib导入标签库。导入之后可以使用标签库中的一些功能 格式:<%@taglib prefix="前缀名" uri="名称空间" %> 1234<%@taglib prefix=\"fn\" uri=\"http://java.sun.com/jsp/jstl/functions\" %><%@taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\" %>// 使用${fn:toUpperCase(\"hello\")} jsp内置对象在jsp页面上可以直接使用的对象 内置对象 类型 out JspWriter request HttpServletRequest response HttpServletResponse session HttpSession exception Throwable page Servlet(this) config ServletConfig application ServletContext pageContext PageContext pageContext域对象 pageContext是servlet中第四个域对象,作用的范围是当前的jsp页面。 通过getXXX方法可以获取request,response,session等其他对象pageContext.getRequest() 通过set/get/reomverAtrribute方法可以操作自己和其它的域对象。操作其他域对象时通过指定scope枚举实现。如:pageContext.setAttribute("rkey", "rvalue", PageContext.REQUEST_SCOPE); 通过findAttribute方法实现遍历查找。依次从pageContext,request,session,application四个域中查找相应的属性。如果查找到返回对应的属性值,如果查找不到返回null。一旦查找到立即返回。 123456789101112131415161718<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>Title</title></head><body><% pageContext.setAttribute(\"pkey\", \"pvalue\"); // 操作request域对象 pageContext.setAttribute(\"rkey\", \"rvalue\", PageContext.REQUEST_SCOPE); session.setAttribute(\"rkey\", \"svalue\");%><%=pageContext.getAttribute(\"pkey\")%> <br> <!--便捷查找--><%=pageContext.findAttribute(\"rkey\")%></body></html> servlet四大域对象总结 域对象 作用范围 生命周期 PageContext 整个jsp页面,作用域最小的一个 当对jsp请求时开始,响应结束时销毁 ServletRequest 整个请求链,包括请求转发 请求到达服务器,service方法调用前由服务器创建。请求结束时销毁 Session 一次会话 第一次调用request.getSession方法时。session到期、手动关闭或服务器非正常关闭时 ServletContext 整个web应用 web程序启动时生成,当服务器关闭或者web应用被移除时销毁 jsp动作标签 请求转发 相当于java中 request.getRequestDispatcher(..).forward(..);使用<jsp:forward page="内部路径"></jsp:forward> 123456789<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>Title</title></head><body><jsp:forward page=\"for1.jsp\"></jsp:forward></body></html> 动态包含 将被包含页面或servlet的运行结果包含到当前页面中 12345678910111213<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>include</title></head><body> <%--<%@include file=\"/jsp/include/i1.jsp\"%>--%> <jsp:include page=\"i1.jsp\"></jsp:include> <hr> <%--<%@include file=\"/jsp/include/i2.jsp\"%>--%> <jsp:include page=\"i2.jsp\"></jsp:include></body></html> 静态包含VS动态包含简单的理解静态包含就是两个页面代码合并在同一个页面中。查看html源只有一个html节点。而动态包含中当向这个页面发出请求后会转发到include的那个页面去执行。执行完之后接着执行本页面下面的代码。查看html源有多个html节点,动态包含一次多一个。更详情的解释如下: 1) 静态包含在转换成为java文件的时候将要包含的文件包含进来,作为一个整体编译。动态包含是各个包含文件分别转换,分别编译。2) 静态包含在两个文件中不能有相同的变量,动态包含允许3) 静态包含只能包含文件,动态包含还可以包含servlet输出的结果4) 静态包含不能使用变量作为文件名,动态包含可以使用变量作为文件名5) 动态包含文件发生变化,包含文件会感知变化。 elEL(Expression Language) 是为了使JSP写起来更加简单。表达式语言的灵感来自于 ECMAScript 和 XPath 表达式语言,它提供了在 JSP 中简化表达式的方法,让Jsp的代码更加简化。用于替代<%=…%> 作用 获取域中的数据 执行运算 获取常见的web对象 调用java的方法 获取域中数据 简单数据获取 数据获取时可以不使用xxScope直接使用${属性} 1234567891011121314151617181920212223242526272829303132<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>el语言</title></head><body><% pageContext.setAttribute(\"pkey\", \"pvalue\"); request.setAttribute(\"rkey\", \"rvalue\"); session.setAttribute(\"skey\", \"svalue\"); application.setAttribute(\"akey\", \"avalue\");%>get pageContext value <br><%=pageContext.getAttribute(\"pkey\")%> <br>${pageScope.pkey}<hr>get request value <br><%=request.getAttribute(\"rkey\")%> <br>${requestScope.rkey}<hr>get session value <br><%=session.getAttribute(\"skey\")%> <br>${sessionScope.skey}<hr>get application value <br><%=application.getAttribute(\"akey\")%> <br>${applicationScope.akey}<hr>便捷获取${pkey},${rkey},${skey},${akey}</body></html> 复杂数据获取 若属性名中出现了”.”|”+”|”-“等特殊符号,需要使用scope获取。例如:${requestScope["arr.age"] } 123456789101112131415161718192021222324252627282930313233343536373839404142<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>el处理复杂数据</title></head><body><% //array request.setAttribute(\"arr\", new String[]{\"a\", \"b\", \"c\"}); //list List<String> list = new ArrayList<>(); list.add(\"aa\"); list.add(\"bb\"); list.add(\"cc\"); request.setAttribute(\"list\", list); //map Map m = new HashMap(); m.put(\"name\", \"tom\"); m.put(\"age\", 18); request.setAttribute(\"map\", m); //带特殊符号的数据 request.setAttribute(\"arr.age\", 18);%>get array <br>old:<%=((String[])request.getAttribute(\"arr\"))[0]%> <br>new:${arr[0]}<hr>get list <br>old:<%=((List<String>)request.getAttribute(\"list\")).get(1)%> <br>new:${list[1]}<hr> get map <br>old:<%=((Map)request.getAttribute(\"map\")).get(\"name\")%> <br>new:${map.get(\"name\")}<hr>获取特殊名称数据 <br>${requestScope[\"arr.age\"]}</body></html> 获取javaBean数据 直接使用${javaBean名称.bean属性} 12345678910111213141516<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>Title</title></head><body><% User user = new User(); user.setId(\"001\"); user.setName(\"jack\"); user.setPassword(\"abc\"); request.setAttribute(\"u\", user);%>${u.name}</body></html> 执行运算 +:只能进行加法运算,字符串形式数字可以进行加法运算 empty:判断一个容器的长度是否为0(array set list map),还可以判断一个对象是否为空${empty 域中的对象名称} 支持三元运算符 123456789101112131415161718<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>Title</title></head><body><% request.setAttribute(\"i\", 3); request.setAttribute(\"j\", 4); List<String> list = null; request.setAttribute(\"list\", list);%>${i+j} <br>${empty list} <br>${i > j ? \"yes\" : \"no\"} <br>${i == 3}</body></html> el内置对象总人有11个内置对象,除了pageContext其余对象获取的全是map集合。 pageScope、requestScope、sessionScope、applicationScope、param、paramValues、header、haederValues、initParam、cookie★、pageContext★ 参数相关:param、paramValues 12345${param} <br>${param.password} <br>${paramValues} <br>${paramValues.get(\"hobby\")[1]} <br>${paramValues.hobby[1]} <br> 请求头相关:header、haederValues 12345${header} <br>${headerValues} <br><hr>accept:${header.accept}<br>user-agent:${headerValues[\"user-agent\"][0]} 全局初始化参数相关:initParam 1${initParam} Cookie相关:cookie 12345678910111213141516171819202122${cookie} 获取map{key=Cookie}例如:创建cookieCookie c=new Cookie(\"username\",\"tom\");通过${cookie}获取相当于 {username=new Cookie(\"username\",\"tom\")}相当于map的key是cookie的键map的value是当前cookie若想获取名称username的cookie的value值(获取tom)${cookie.username.value}--javabean导航注意:java中Cookie的apigetName():获取cookie的名称getValue():获取cookie的value值我们称name和value是cookie的bean属性使用cookie内置对象:${cookie.给cookie起名字.value}例如:获取jsession的值${cookie.JSESSIONID.value} jstljsp标准的标签库语言,主要用来替代java脚本。如果要使用标签库必须要导入jstl.jar和standard.jar两个jar包。 更多关于标签库的内容可以查看文档 分类: core:核心类库 fmt:格式化,国际化 xml:已过时 sql:已过时 fn:函数库,比较少使用 core的使用主要有if判断,forEach循环,choose类似switch等 使用<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>引入core标签库 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><%@taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\" %><html><head> <title>Title</title></head><body><% //list List<String> list = new ArrayList<>(); list.add(\"a\"); list.add(\"b\"); list.add(\"c\"); request.setAttribute(\"list\", list); //set Set<String> set = new HashSet<>(); set.add(\"aa\"); set.add(\"bb\"); set.add(\"cc\"); request.setAttribute(\"set\", set);//map Map<String, String> map = new HashMap<>(); map.put(\"ak\", \"av\"); map.put(\"bk\", \"bv\"); map.put(\"ck\", \"cv\"); request.setAttribute(\"map\", map); request.setAttribute(\"aa\", \"bb\");%> <c:if test='${aa==\"bb\"}'> true </c:if><hr/> <c:if test=\"${aa=='cc'}\"> true </c:if> <c:forEach begin=\"1\" end=\"10\" step=\"1\" var=\"i\">${i}</c:forEach><hr><c:forEach begin=\"1\" end=\"20\" step=\"2\" var=\"i\" varStatus=\"vs\"> ${i}--${vs.count}--${vs.first}--${vs.last} <br></c:forEach> <c:forEach items=\"${list}\" var=\"l\"> ${l}</c:forEach><hr><c:forEach items=\"${set}\" var=\"s\" varStatus=\"ss\"> ${s}--${ss.count}</c:forEach><hr><c:forEach items=\"${map}\" var=\"m\"> ${m.key}--${m.value} <br></c:forEach> <c:set var=\"day\" value=\"3\"/><c:choose> <c:when test=\"${day==1 }\"> 周1 </c:when> <c:when test=\"${day==2}\"> 周2 </c:when> <c:when test=\"${day==3 }\"> 周3 </c:when> <c:otherwise> 其他 </c:otherwise></c:choose></body></html> fn标签库的使用12345678910<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><%@taglib prefix=\"fn\" uri=\"http://java.sun.com/jsp/jstl/functions\" %><html><head> <title>Title</title></head><body>${fn:toUpperCase(\"hello\")}</body></html>","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"web","slug":"java/web","permalink":"http://blog.xxyxpy.pub/categories/java/web/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"web","slug":"web","permalink":"http://blog.xxyxpy.pub/tags/web/"},{"name":"jsp","slug":"jsp","permalink":"http://blog.xxyxpy.pub/tags/jsp/"},{"name":"el","slug":"el","permalink":"http://blog.xxyxpy.pub/tags/el/"},{"name":"jstl","slug":"jstl","permalink":"http://blog.xxyxpy.pub/tags/jstl/"}]},{"title":"web-03Cookie,Session","slug":"java/web/web-03Cookie,Session","date":"2017-12-25T07:54:14.922Z","updated":"2017-12-25T07:54:18.097Z","comments":true,"path":"2017/12/25/java/web/web-03Cookie,Session/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/web/web-03Cookie,Session/","excerpt":"jsp本质上jsp就是一个servlet,在html代码中嵌套java代码,运行在服务器端,处理请求,生成动态的内容。对应的java和class文件在tomcat目录下的work目录。 执行流程 浏览器发送请求,访问jsp页面 服务器接收到请求,jspServlet查询对应的jsp文件 服务器将jsp页面翻译成java文件 jvm会java文件编译成.class文件 服务器运行.class文件,生成动态的内容 将内容发送给服务器 服务器组成响应信息,发送给浏览器 浏览器接收数据,解析展示","text":"jsp本质上jsp就是一个servlet,在html代码中嵌套java代码,运行在服务器端,处理请求,生成动态的内容。对应的java和class文件在tomcat目录下的work目录。 执行流程 浏览器发送请求,访问jsp页面 服务器接收到请求,jspServlet查询对应的jsp文件 服务器将jsp页面翻译成java文件 jvm会java文件编译成.class文件 服务器运行.class文件,生成动态的内容 将内容发送给服务器 服务器组成响应信息,发送给浏览器 浏览器接收数据,解析展示 脚本 程序片断 直接输出 成员声明 123456789101112131415161718192021222324<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>Title</title></head><body> <% int i = 4; System.out.println(i); out.print(i); out.print(j); // 因为本质是servlet所以有getServletContext方法 ServletContext sc = getServletContext(); //获取运行路径 \"/\"表示应用在tomcat中的根路径 out.write(\"<h4>运行路径:\" + sc.getRealPath(\"/\") + \"<h4>\"); %> <%--直接输出--%> <%=11%> <%--全局常量--%> <%! int j = 8; %> <%=j%></body></html> cookie由服务器生成,通过response将cookie写回浏览器(set-cookie),保留在浏览器上,下一次访问时浏览器根据一定的规则携带不同的cookie(通过request的头 cookie),服务器就可以接受cookie cookie的使用123456789101112131415// 添加cookieCookie c = new Cookie(key, \"value\" + i);// 设置过期时间c.setMaxAge(3600);// 设置路径,类似于域名的概念。将主站或各个子站的cookie分开管理c.setPath(req.getContextPath() + \"/\");resp.addCookie(c);// 获取cookie,通过request拿到的是cookie的数组集合// 所以要查询cookie时只需要遍历这个集合就可以了Cookie[] cookies = req.getCookies();for (Cookie cookie : cookies) { if(cookie.getName().equals(\"abc\")) { System.out.println(cookie.getValue()); }} 以下是封装的cookie操作相关的工具类,实现获取所有cookie、通过key查询cookie等功能 12345678910111213141516171819202122232425262728293031public class CookieUtil { //获取所有的key,返回List<String> public static List<String> getKeys(Cookie[] cookies){ List<String> keys = new ArrayList<>(); if (cookies == null) return keys; for (Cookie cookie : cookies) { keys.add(cookie.getName()); } return keys; } //通过key返回一个cookie public static Cookie getCookieByKey(String key, Cookie[] cookies){ Cookie c = null; if (cookies == null) return c; for (Cookie cookie : cookies) { if (cookie.getName().equals(key)){ c = cookie; break; } } return c; } //判断此key是否在cookie数组中 public static boolean isExistKey(String key, Cookie[] cookies){ List<String> keys = getKeys(cookies); return keys.contains(key); }} 注意点不能跨浏览器,不支持中文 session使用12345//自动添加sessionHttpSession session = req.getSession();session.setAttribute(\"cart\", \"abc\");//清空此sessionreq.getSession().invalidate(); 失效时间有三种方式可以设置session的失效时间。优先级分别是java代码>项目中配置>tomcat中配置 web容器中设置 tomcat的conf目录下的web.xml文件中,时间单位为分钟 1234567<!-- ==================== Default Session Configuration ================= --><!-- You can set the default session timeout (in minutes) for all newly --><!-- created sessions by modifying the value below. --><session-config> <session-timeout>30</session-timeout></session-config> 项目的web.xml中配置 1234<!--设置session的过期时间,单位为分钟--><session-config> <session-timeout>30</session-timeout></session-config> java代码设置,时间单位为秒 12HttpSession session = req.getSession();session.setMaxInactiveInterval(30 * 60); 钝化和活化session的活化与钝化就是当用户访问时网站异常,不能丢掉session,钝化时采用文件的方式保存session,恢复时再通过文件活化到内存中。如果要实现此目标首先需要保存的对象可以被序列化,另外还要有类去进读取和写入。所以被钝化的对象需要实现两个接口:Serializable和HttpSessionActivationListener Serializable为序列化接口,没有什么好说的。说一说HttpSessionActivationListener接口,实现此接口的JavaBean,可以感知自己被活化(从硬盘到内存)和钝化(从内存到硬盘)的过程,包含两个方法分别可以在钝化和活化时做另外的处理。以下为实现: 要钝化的JavaBean必须实现两个接口 123456789101112131415161718192021222324252627282930313233343536373839public class Person2 implements HttpSessionActivationListener,Serializable { private int id; private String name; public Person2() { } public Person2(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void sessionWillPassivate(HttpSessionEvent httpSessionEvent) { System.out.println(\"钝化,写入磁盘\"); //web 下面新建 Meta-inf 目录 ,新建Context.xml文件 } @Override public void sessionDidActivate(HttpSessionEvent httpSessionEvent) { System.out.println(\"活化,写入内存\"); }} 文件配置 web目录下创建META-INF目录并添加context.xml配置文件。 12345678910<?xml version=\"1.0\" encoding=\"UTF-8\"?><Context> <!-- maxIdleSwap :1分钟 如果session不使用就会序列化到硬盘. directory :d:\\a 序列化到硬盘的文件存放的位置. --> <!--saveOnRestart -当服务器关闭时是否要将所有的session持久化--> <Manager className=\"org.apache.catalina.session.PersistentManager\" maxIdleSwap=\"1\" saveOnRestart=\"true\"> <Store className=\"org.apache.catalina.session.FileStore\" directory=\"d:\\a\" /> </Manager></Context> 域对象前面陆续介绍了javaweb的两个域对象:ServletContext和Request。Session也是四大域对象之一。 此域对象的作用范围是一次会话。生命周期是在第一次调用request.getSession()方法时,服务器会检查是否已经有对应的session,如果没有就在内存中创建一个session并返回。当一段时间内session没有被使用(默认为30分钟),则服务器会销毁该session。如果服务器非正常关闭(强行关闭),没有到期的session也会跟着销毁。如果调用session提供的invalidate() ,可以立即销毁session。 注意:服务器正常关闭,再启动,Session对象会进行钝化和活化操作。同时如果服务器钝化的时间在session 默认销毁时间之内,则活化后session还是存在的。否则Session不存在。 如果JavaBean 数据在session钝化时,没有实现Serializable 则当Session活化时,会消失","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"web","slug":"java/web","permalink":"http://blog.xxyxpy.pub/categories/java/web/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"web","slug":"web","permalink":"http://blog.xxyxpy.pub/tags/web/"},{"name":"cookie","slug":"cookie","permalink":"http://blog.xxyxpy.pub/tags/cookie/"},{"name":"session","slug":"session","permalink":"http://blog.xxyxpy.pub/tags/session/"}]},{"title":"web-02Request,Response","slug":"java/web/web-02Request,Response","date":"2017-12-25T07:54:10.250Z","updated":"2017-12-25T07:54:10.271Z","comments":true,"path":"2017/12/25/java/web/web-02Request,Response/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/web/web-02Request,Response/","excerpt":"request作用获取浏览器发送过来的数据 内容组成请求行,请求头,请求体 操作请求行","text":"request作用获取浏览器发送过来的数据 内容组成请求行,请求头,请求体 操作请求行 1234567891011121314// 获取请求方式String method = req.getMethod();// 获取ip地址String ip = req.getRemoteAddr();// 在java中获取项目名称String projectName = req.getContextPath();// 获取的是 从项目名到参数之前的内容 /servlet_test/registString uri = req.getRequestURI();// getRequestURL 获取的带协议的完整路径 http://localhost/servlet_test/registString url = req.getRequestURL().toString();// get请求的所有参数 username=tom&password=123String queryString = req.getQueryString();// 获取协议和版本 如:HTTP/1.1String protocol = req.getProtocol(); 操作请求头12345678910111213141516// String getHeader(String key):通过key获取指定的value (一个)// user-agent:浏览器内核 msie firefox chrome// referer:页面从那里来 防盗链String agent = req.getHeader(\"user-agent\");String referer = req.getHeader(\"referer\");String temp = \"\";if (referer == null){ temp = \"地址栏\";} else if (referer.contains(\"localhost\")){ System.out.println(\"自己\");}else if(referer.contains(\"192.168.\")){ // 假设内网地址是192.168开始,当然也有10.开始 System.out.println(\"内网\");}else{ System.out.println(\"盗链\");} 操作请求参数假设请求参数为:username=tom&password=123&hobby=drink&hobby=sleep 123456// 获取单个值String username = req.getParameter(\"username\");// 通过一个key获取多个值,一般用于多选的控件String[] hobbies = req.getParameterValues(\"hobby\");// 获取所有的参数和值,放在一个map集合中Map<String, String[]> parameterMap = req.getParameterMap(); 请求转发指的是服务器内部的转发行为,发生在不同的servlet之间的。一般需要传递原servlet的request和response对象。 转发时指定的path不需要包含项目名称,直接用web.xml中配置的路径即可 1234resp.setContentType(\"text/html;charset=utf-8\");req.setAttribute(\"newName\", \"heihei\");// 设置的newName的值会经过请求转发发送到另外一个servlet中req.getRequestDispatcher(\"reqTrans2\").forward(req, resp); 请求转发和重定向的区别 重定向发送两次请求,请求转发一次请求 重定向是从浏览器发送,请求转发是服务器内部 重定向原来的request域对象销毁,请求转发可以使用原来的request域对象 重定向是response的方法,请求转发是request的方法 重定向可以请求站外资源,请求转发不可以 域对象上一次讲到了servlet的第一个域对象ServletContext。Request是第二个域对象。 作用范围是整个请求链(请求转发也存在);生命周期是在service方法调用前由服务器创建,传入service方法。 整个请求结束,request生命结束。 12req.setAttribute(\"newName\", \"heihei\");String newName = req.getAttribute(\"newName\").toString(); response作用给浏览器返回响应信息 内容组成响应行,响应头,响应体 响应的状态码123456// 设置一般响应状态resp.setStatus(int);// 设置错误响应,一般用于error类的,比如4XX和5XXresp.sendError(int);// 重定向,传入重定向的地址resp.sendRedirect(\"/servlet_test/show\"); 常见的状态码: 12345678910111xx:已发送请求2xx:已完成响应200:正常响应3xx:还需浏览器进一步操作302:重定向 配合响应头:location304:读缓存4xx:用户操作错误404:用户操作错误.405:访问的方法不存在5xx:服务器错误500:内部异常 操作响应头常用方法 setHeader(String key,String value):设置字符串形式的响应头 setIntHeader(String key,int value):设值整形的响应头 setDateHeader(String key,long value):设值时间的响应头 addHeader(String key,String value):添加置字符串形式的响应头 之前设置过则追加,若没有设置过则设置 addIntHeader(String key,int value):添加整形的响应头 addDateHeader(String key,long value):添加时间的响应头 常用的响应头 location:重定向 refresh:定时刷新 content-type:设置文件的mime类型,以及响应流的编码 content-disposition:文件下载 1234// 响应头设置自动刷新resp.setHeader(\"Refresh\", \"2;url=/servlet_test/show\");// 文件下载响应头resp.setHeader(\"content-disposition\", \"attachment;filename=\"+_filename); 操作响应体页面上要展示的内容。常用两种流进行输出,字符流和字节流。字节流一般用于文件下载。 1234// 字符流resp.getWriter();// 字节流resp.getOutputStream(); 文件下载功能实现通过响应字节流来实现文件下载的功能。 html文件dowload.html 1234567891011<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>文件下载</title></head><body> <a href=\"/servlet_test/download?name=2.txt\">2.txt</a><br> <a href=\"/servlet_test/download?name=我是.txt\">我是.txt</a><br></body></html> DownServlet.java。这里使用commons.io工具来进行流的拷贝,返回输出的是字节流 12345678910111213141516171819202122232425262728293031public class DownServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取下载的文件名 String fileName = req.getParameter(\"name\"); //注意中文乱码: //fileName = new String(fileName.getBytes(\"iso8859-1\"), \"utf-8\"); //设置MIME类型 ServletContext servletContext = getServletContext(); String mimeType = servletContext.getMimeType(fileName); resp.setContentType(mimeType); // 通过工具类编码 String _filename= DownLoadUtils.getName(req.getHeader(\"user-agent\"), fileName); resp.setHeader(\"content-disposition\", \"attachment;filename=\"+_filename); //流拷贝 //1)生写 ServletOutputStream os = resp.getOutputStream(); InputStream is = servletContext.getResourceAsStream(\"/download/\" + fileName); /*byte[] b = new byte[1024]; int len = -1; while ((len = is.read(b)) != -1) { os.write(b, 0, len); } os.flush();*/ //2)使用第三方工具类 IOUtils.copy(is, os); is.close(); os.close(); }} DownLoadUtils.java 编码转换,防止文件名中文乱码 1234567891011121314151617public class DownLoadUtils { public static String getName(String agent, String filename) throws UnsupportedEncodingException { if (agent.contains(\"MSIE\")) { // IE浏览器 filename = URLEncoder.encode(filename, \"utf-8\"); filename = filename.replace(\"+\", \" \"); } else if (agent.contains(\"Firefox\")) { // 火狐浏览器 BASE64Encoder base64Encoder = new BASE64Encoder(); filename = \"=?utf-8?B?\" + base64Encoder.encode(filename.getBytes(\"utf-8\")) + \"?=\"; } else { // 其它浏览器 filename = URLEncoder.encode(filename, \"utf-8\"); } return filename; }} web.xml中的配置 12345678<servlet> <servlet-name>DownServlet</servlet-name> <servlet-class>com.servlet.rr.DownServlet</servlet-class></servlet><servlet-mapping> <servlet-name>DownServlet</servlet-name> <url-pattern>/download</url-pattern></servlet-mapping> 验证码功能实现实现点击后验证码自动重新生成 jsp文件。code.jsp 123456789101112131415<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html> <head> <title>$Title$</title> </head> <body> <div><img alt=\"验证码\" src=\"code\" title=\"看不清除,换一张\" onclick=\"changeImg(this)\"></div> <script> function changeImg(obj) { // 通过随机数src的方式才能实现刷新功能 obj.src = \"${pageContext.request.contextPath}/code?i=\" + Math.random(); } </script> </body></html> 图片流生成的类CodeServlet.java 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364public class CodeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 使用java图形界面技术绘制一张图片 int charNum = 4; int width = 30 * charNum; int height = 30; // 1. 创建一张内存图片 BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 2.获得绘图对象 Graphics graphics = bufferedImage.getGraphics(); // 3、绘制背景颜色 graphics.setColor(Color.YELLOW); graphics.fillRect(0, 0, width, height); // 4、绘制图片边框 graphics.setColor(Color.BLUE); graphics.drawRect(0, 0, width - 1, height - 1); // 5、输出验证码内容 graphics.setColor(Color.RED); graphics.setFont(new Font(\"宋体\", Font.BOLD, 20)); // 随机输出4个字符 Graphics2D graphics2d = (Graphics2D) graphics; // 去掉容易混淆的数字及字母 String s = \"ABCDEFGHGKLMNPQRSTUVWXYZ23456789\"; Random random = new Random(); // session中要用到,要校验是否正确的话需要保存在session中 String msg = \"\"; int x = 5; for (int i = 0; i < charNum; i++) { int index = random.nextInt(32); String content = String.valueOf(s.charAt(index)); msg += content; double theta = random.nextInt(45) * Math.PI / 180; // 让字体扭曲 graphics2d.rotate(theta, x, 18); graphics2d.drawString(content, x, 18); graphics2d.rotate(-theta, x, 18); x += 30; } // 6、绘制干扰线 graphics.setColor(Color.GRAY); for (int i = 0; i < 5; i++) { int x1 = random.nextInt(width); int x2 = random.nextInt(width); int y1 = random.nextInt(height); int y2 = random.nextInt(height); graphics.drawLine(x1, y1, x2, y2); } // 释放资源 graphics.dispose(); // 图片输出 ImageIO ImageIO.write(bufferedImage, \"jpg\", resp.getOutputStream()); }} web.xml配置 12345678<servlet> <servlet-name>CodeServlet</servlet-name> <servlet-class>com.servlet.rr.CodeServlet</servlet-class></servlet><servlet-mapping> <servlet-name>CodeServlet</servlet-name> <url-pattern>/code</url-pattern></servlet-mapping>","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"web","slug":"java/web","permalink":"http://blog.xxyxpy.pub/categories/java/web/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"web","slug":"web","permalink":"http://blog.xxyxpy.pub/tags/web/"},{"name":"request","slug":"request","permalink":"http://blog.xxyxpy.pub/tags/request/"},{"name":"response","slug":"response","permalink":"http://blog.xxyxpy.pub/tags/response/"}]},{"title":"web-01Servlet","slug":"java/web/web-01Servlet","date":"2017-12-25T07:54:05.302Z","updated":"2017-12-25T07:54:05.533Z","comments":true,"path":"2017/12/25/java/web/web-01Servlet/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/web/web-01Servlet/","excerpt":"什么是servlet?Servlet(Server Applet)是Java Servlet的简称,是为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。 编写一个servlet 新建类继承HttpServlet.重写doPost或doGet方法.分别对应post和get请求","text":"什么是servlet?Servlet(Server Applet)是Java Servlet的简称,是为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。 编写一个servlet 新建类继承HttpServlet.重写doPost或doGet方法.分别对应post和get请求 1234567891011121314151617public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求参数 String userName = req.getParameter(\"userName\"); String passWord = req.getParameter(\"password\"); // 设置响应类型和字符编码 resp.setContentType(\"text/html; charset=utf-8\"); // 响应 resp.getWriter().print(\"userName:\" + userName + \"; passWord:\" + passWord); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); }} 编写配置文件web.xml(web-inf目录下).配置servlet和servlet-mapping 12345678910<servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.servlet.use.HelloServlet</servlet-class> <!--表示加载顺序--> <load-on-startup>2</load-on-startup></servlet><servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/hello</url-pattern></servlet-mapping> 浏览器中请求此servlet路径 部署项目名为:servlet http://localhost:8080/servlet/hello?userName=张三&password=123456 接收和回写 接收处理参数 这里先简单的说一下如何去接收请求参数,具体的更详情内容放在request中描述。 参数的传递是通过key-value的方式进行的,所以在获取值的时候用getParameter(key)即可,需要说明的是所有的参数都是以字符串进行传递的 回写内容 通过Response对象中的PrinterWriter对象进行回写操作,设置返回的内容格式和编码。具体内容放在response中描述。 乱码问题 无论是请求还是响应都会有乱码问题,响应时指定编码格式为utf-8即可解决响应中文的编码,如上面的resp.setContentType("text/html; charset=utf-8")。 对于请求的乱码根据请求方式的不同处理方式也不一样: get请求:new String(参数.getBytes(“iso-8859-1”),”utf-8”),但此方法并不适用所有的环境,本地;java8+tomcat8不添加也不产生乱码。 post请求:request.setCharacterEncoding(“utf-8”) servlet体系结构 类层次结构 Servlet接口→GenericServlet抽象类→HttpServlet抽象类→自定义Servlet 常用方法 Servlet接口 123456789// 初始化void init(ServletConfig config);// 服务 处理业务逻辑,最重要的方法。请求过来时service方法最先被调用// 具体可参考http://java-admin.iteye.com/blog/194455void service(ServletRequest request,ServletResponse response);// 销毁void destroy();// 获取当前servlet的配置对象ServletConfig getServletConfig(); GenericServlet抽象类 实现除service方法外的所有接口方法; init和destroy方法都是空实现,有相应业务场景时重写即可。 HttpServlet抽象类 对service方法做了实现,实际上请求过来时是调用的service方法。自定义servlet时只需要重写doXX方法即可。程序根据请求类型来判断走哪一个do方法。 servlet生命周期serlvet是单实例多线程默认第一次访问的时候,服务器创建servlet,并调用init实现初始化操作.并调用一次service方法每当请求来的时候,服务器创建一个线程,调用service方法执行自己的业务逻辑当serlvet被移除的时候服务器正常关闭的时候,服务器调用servlet的destroy方法实现销毁操作. void init(ServletConfig config):初始化 只执行一次,默认在第一次访问时执行 void service(ServletRequest request,ServletResponse response):服务 处理业务逻辑 请求过来的时候请求一次执行一次 void destroy():销毁 只执行一次,在servelet被移除或服务器正常关闭时 url路径匹配优先级 完全匹配>目录匹配>后缀名匹配 完全匹配 必须以”/“开始。例如:/hello /a/b/c 目录匹配 必须以”/“开始,以”*“结束。例如:/a/* /* 后缀名匹配 以”*“开始,以字符结尾。例如:*.jsp *.do *.action ServletConfigservlet的配置对象,在创建servlet的同时进行创建,通过getServletConfig()方法获取。主要有以下作用: 获取当前servlet的名称 获取当前servlet的初始化参数。放在了Enumeration中,需要遍历 获取全局管理者ServletContext web.xml配置: 12345678910111213141516<servlet> <servlet-name>ShowServlet</servlet-name> <servlet-class>com.servlet.use.ShowServlet</servlet-class> <init-param> <param-name>user</param-name> <param-value>root</param-value> </init-param> <init-param> <param-name>password</param-name> <param-value>1234</param-value> </init-param></servlet><servlet-mapping> <servlet-name>ShowServlet</servlet-name> <url-pattern>/show</url-pattern></servlet-mapping> Servlet中使用ServletConfig 1234567891011//获取ServletConfigServletConfig config = getServletConfig();String servletName = config.getServletName();String user = config.getInitParameter(\"user\");Enumeration<String> initParameterNames = config.getInitParameterNames();writer.write(\"<h4>ServletName:\" + servletName + \"<h4>\");writer.write(\"<h4>user:\" + user + \"<h4>\");while (initParameterNames.hasMoreElements()){ String p = initParameterNames.nextElement(); writer.write(\"<h4>\" + p + \":\" + config.getInitParameter(p) + \"<h4>\");} 输出: ServletName:ShowServletuser:rootpassword:1234user:root ServletContext一个项目的引用,代表了当前项目。使用get 当项目启动时,服务器为每一个web项目创建一个ServletContext对象。 当项目移除或服务器关闭的时候销毁。主要作用: 获取全局初始化参数 在根标签下使用context-param子标签 用来存放初始化参数 1234<context-param> <param-name>encoding</param-name> <param-value>utf-8</param-value></context-param> 获取共享资源数据 作为域对象,设置和获取的是全局的属性。内部是LinkedHashMap。 123ServletContext sc = getServletContext();int count = Integer.valueOf(sc.getAttribute(\"count\").toString());sc.setAttribute(\"count\", ++count); 获取文件资源 123456789//ServletContext使用//获取运行路径 \"/\"表示应用在tomcat中的根路径writer.write(\"<h4>运行路径:\" + sc.getRealPath(\"/\") + \"<h4>\");writer.write(\"<h4>运行路径:\" + sc.getRealPath(\"/WEB-INF/web.xml\") + \"<h4>\");//以流的形式返回一个文件InputStream inputStream = sc.getResourceAsStream(\"/2.txt\");System.out.println(inputStream);//获取文件的mime类型,此类型的集合配置在tomcat的web.xml中writer.write(\"<h4>2.txt的类型:\" + sc.getMimeType(\"2.txt\") + \"<h4>\");","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"web","slug":"java/web","permalink":"http://blog.xxyxpy.pub/categories/java/web/"},{"name":"servlet","slug":"java/web/servlet","permalink":"http://blog.xxyxpy.pub/categories/java/web/servlet/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"web","slug":"web","permalink":"http://blog.xxyxpy.pub/tags/web/"}]},{"title":"获取泛型参数的Class对象","slug":"java/tool/获取泛型参数的Class对象","date":"2017-12-25T07:53:57.781Z","updated":"2017-12-25T07:53:57.806Z","comments":true,"path":"2017/12/25/java/tool/获取泛型参数的Class对象/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/tool/获取泛型参数的Class对象/","excerpt":"问题在三层架构中的Dao层对于CRUD的处理方法基本相同.如会有save,add,update,delete,查询列表等.基于此可以封装一个Dao层的基类BaseDao,泛型结构的.T为实际要操作的JavaBean. 对于save,add等方法的提取非常的简单,参数直接用泛型怼上去就行.如: 1234public save(T t){ // 使用orm进行保存.比如hibernate this.getHibernateTemplate().save(t);} 但是对于其它的如查询所有的T对应的记录在hibernate中就需要用到T的类名.","text":"问题在三层架构中的Dao层对于CRUD的处理方法基本相同.如会有save,add,update,delete,查询列表等.基于此可以封装一个Dao层的基类BaseDao,泛型结构的.T为实际要操作的JavaBean. 对于save,add等方法的提取非常的简单,参数直接用泛型怼上去就行.如: 1234public save(T t){ // 使用orm进行保存.比如hibernate this.getHibernateTemplate().save(t);} 但是对于其它的如查询所有的T对应的记录在hibernate中就需要用到T的类名. 处理BaseDao类,Dao层基类,泛型类 12345678910111213141516171819202122232425262728293031323334353637383940414243import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;/** * 假设为dao层的基类 * Created on 2017/7/21. */public class BaseDao<T> { /** * 泛型T的Class对象 * 在构造函数中获取该值 */ private Class clazz; public BaseDao() { // 1.获取子类的Class对象,此处的this指向子类 Class c = this.getClass(); // 2.获取带有泛型的父类 // Type是 Java 编程语言中所有类型的公共高级接口。 // 它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。 Type type = c.getGenericSuperclass(); System.out.println(\"泛型父类:\" + type); // 3.获取参数化类型,即泛型 if (type instanceof ParameterizedType) { ParameterizedType p = (ParameterizedType) type; // 获取参数化类型的数组,泛型可能有多个 Type[] arguments = p.getActualTypeArguments(); clazz = (Class) arguments[0]; System.out.println(\"泛型T全限定名:\" + clazz.getName() + \";类名:\" + clazz.getSimpleName()); } } /** * 假如此时执行一个Hql查询,需要用到泛型T的类名 * 查询所有数据 */ public void findAll() { System.out.println(\"from \" + clazz.getSimpleName()); }} 子类user 1234567/** * 用户类 * Created on 2017/7/21. */public class User extends BaseDao<User> { } 测试 12345678public class Demo1 { @Test public void test1() { // 调用findAll方法查询所有记录 User u = new User(); u.findAll(); }} 泛型父类:com.superclass.BaseDao 泛型T全限定名:com.superclass.User;类名:User from User","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"tool","slug":"java/tool","permalink":"http://blog.xxyxpy.pub/categories/java/tool/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"泛型","slug":"泛型","permalink":"http://blog.xxyxpy.pub/tags/泛型/"}]},{"title":"生成二维码和条形码","slug":"java/tool/生成二维码和条形码","date":"2017-12-25T07:53:50.429Z","updated":"2017-12-25T07:53:50.440Z","comments":true,"path":"2017/12/25/java/tool/生成二维码和条形码/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/tool/生成二维码和条形码/","excerpt":"使用google提供的zxing组件来生成二维码和条形码, pom文件中的依赖为: 12345678910<dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.0.0</version></dependency><dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.0.0</version></dependency>","text":"使用google提供的zxing组件来生成二维码和条形码, pom文件中的依赖为: 12345678910<dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.0.0</version></dependency><dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.0.0</version></dependency> BarCodeUtils类,使用到的流转换类为之前写过的ConvertIOUtil 默认生成了一个输出流,可以输出或者生成一个图片地址输出图片地址 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153import com.google.zxing.BarcodeFormat;import com.google.zxing.EncodeHintType;import com.google.zxing.MultiFormatWriter;import com.google.zxing.WriterException;import com.google.zxing.client.j2se.MatrixToImageWriter;import com.google.zxing.common.BitMatrix;import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;import com.ly.util.ConvertIOUtil;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.OutputStream;import java.nio.file.Path;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;/** * 二维码,条形码生成 * Created on 2017/7/10. */public class BarCodeUtils { /** * 默认后缀名 */ private static final String default_stuffix = \"png\"; /** * 默认编码格式 */ private static final String default_charset = \"UTF-8\"; /** * 生成条形码 * @param contents 内容 * @param width 宽度 * @param height 高度 * @return 条形码图片地址 * @throws Exception */ public static String createBarCode(String contents, int width, int height) throws Exception { int codeWidth = 3 + // start guard (7 * 6) + // left bars 5 + // middle guard (7 * 6) + // right bars 3; // end guard codeWidth = Math.max(codeWidth, width); BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.CODE_128, codeWidth, height, null); OutputStream os = new ByteArrayOutputStream(); MatrixToImageWriter.writeToStream(bitMatrix, \"png\", os); String image = UpYunUtil.UploadImage(ConvertIOUtil.parse(os)); return image; } /** * 生成二维码到指定文件路径 * * @param codeData 二维码内容 * @param filePath 文件,格式为/fatherpath/childpath/filename.stuffix * @param charset 编码默认为uft-8 * @param correctionLevel 错误修正级别1,2,3,4 * @param height 高度 * @param width 宽度 * @return */ private static boolean createQRCode2File(String codeData, String filePath, String charset, int correctionLevel, int height, int width) { try { Path path = Paths.get(filePath); String suffix = filePath.substring(filePath.lastIndexOf('.') + 1); if (suffix == null || \"\".equals(suffix)) { suffix = default_stuffix; } if (charset == null || \"\".equals(charset)) { charset = default_charset; } Map<EncodeHintType, Object> hintMap = createHintMap(correctionLevel); BarcodeFormat type = BarcodeFormat.QR_CODE; BitMatrix matrix = new MultiFormatWriter().encode(new String(codeData.getBytes(charset), charset), type, width, height, hintMap); MatrixToImageWriter.writeToPath(matrix, suffix, path); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 输出二维码到输出流 * * @param codeData 二维码内容 * @param height 高度 * @param width 宽度 * @param charset 编码格式,默认utf-8 * @param suffix 生成的文件后缀名 * @param correctionLevel 错误修正级别1,2,3,4,级别越高越好识别,特别是在二维码中加入了logo的时候 * @return * @throws WriterException * @throws IOException */ private static OutputStream createQRCode2Stream(String codeData, int height, int width, String charset, String suffix, int correctionLevel) throws WriterException, IOException { if (suffix == null || suffix.equals(\"\")) { suffix = default_stuffix; } if (charset == null || charset.equals(\"\")) { charset = default_charset; } Map<EncodeHintType, Object> hintMap = createHintMap(correctionLevel); BarcodeFormat type = BarcodeFormat.QR_CODE; BitMatrix matrix = new MultiFormatWriter().encode(new String(codeData.getBytes(charset), charset), type, width, height, hintMap); OutputStream os = new ByteArrayOutputStream(); MatrixToImageWriter.writeToStream(matrix, suffix, os); return os; } /** * 得到二维码图片路径 * @param codeData 内容 * @param height 高度 * @param width 宽度 * @return 生成的图片路径 * @throws Exception */ public static String createQRCode(String codeData, int height, int width) throws Exception { OutputStream os = createQRCode2Stream(codeData, height, width, \"UTF-8\", \"\", 4); String image = UpYunUtil.UploadImage(ConvertIOUtil.parse(os)); return image; } /** * 参数处理,错误修正级别 * * @param correctionLevel * @return */ private static Map<EncodeHintType, Object> createHintMap(int correctionLevel) { Map<EncodeHintType, Object> hintMap = new HashMap<EncodeHintType, Object>(); hintMap.put(EncodeHintType.MARGIN, 1);//空白填充 if (correctionLevel == 2) { hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); } else if (correctionLevel == 3) { hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.Q); } else if (correctionLevel == 4) { hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); } else { hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); } return hintMap; }}","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"tool","slug":"java/tool","permalink":"http://blog.xxyxpy.pub/categories/java/tool/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"}]},{"title":"流之间的互相转换","slug":"java/tool/流之间的互相转换","date":"2017-12-25T07:53:33.824Z","updated":"2017-12-25T07:53:37.524Z","comments":true,"path":"2017/12/25/java/tool/流之间的互相转换/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/tool/流之间的互相转换/","excerpt":"ConvertIOUtil类","text":"ConvertIOUtil类 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.InputStream;import java.io.OutputStream;/** * io流之间的互相转换 * Created on 2017/7/10. */public class ConvertIOUtil { //inputStream转outputStream public static ByteArrayOutputStream parse(InputStream in) throws Exception { ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); int ch; while ((ch = in.read()) != -1) { swapStream.write(ch); } return swapStream; } //outputStream转inputStream public static ByteArrayInputStream parse(OutputStream out) throws Exception { ByteArrayOutputStream baos=new ByteArrayOutputStream(); baos=(ByteArrayOutputStream) out; ByteArrayInputStream swapStream = new ByteArrayInputStream(baos.toByteArray()); return swapStream; } //inputStream转String public static String parse_String(InputStream in) throws Exception { ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); int ch; while ((ch = in.read()) != -1) { swapStream.write(ch); } return swapStream.toString(); } //OutputStream 转String public static String parse_String(OutputStream out)throws Exception { ByteArrayOutputStream baos=new ByteArrayOutputStream(); baos=(ByteArrayOutputStream) out; ByteArrayInputStream swapStream = new ByteArrayInputStream(baos.toByteArray()); return swapStream.toString(); } //String转inputStream public static ByteArrayInputStream parse_inputStream(String in)throws Exception { ByteArrayInputStream input=new ByteArrayInputStream(in.getBytes()); return input; } //String 转outputStream public static ByteArrayOutputStream parse_outputStream(String in)throws Exception { return parse(parse_inputStream(in)); }}","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"tool","slug":"java/tool","permalink":"http://blog.xxyxpy.pub/categories/java/tool/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"io","slug":"io","permalink":"http://blog.xxyxpy.pub/tags/io/"}]},{"title":"Java邮件发送","slug":"java/tool/Java邮件发送","date":"2017-12-25T07:51:27.745Z","updated":"2017-12-25T07:51:27.760Z","comments":true,"path":"2017/12/25/java/tool/Java邮件发送/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/tool/Java邮件发送/","excerpt":"添加引用 mail.jar activation.jar 对于使用maven的项目添加如下的配置 12345678910<dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.7</version></dependency><dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version></dependency>","text":"添加引用 mail.jar activation.jar 对于使用maven的项目添加如下的配置 12345678910<dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.7</version></dependency><dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version></dependency> 功能实现MailUtil类 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115import javax.mail.Authenticator;import javax.mail.Message;import javax.mail.Transport;import javax.mail.internet.InternetAddress;import javax.mail.internet.MimeMessage;import java.util.Properties;import java.util.ResourceBundle;/** * 发送邮件工具类 * Created on 2017/7/14. */public class MailUtil { private static final String SMTP_MAIL_HOST; private static final String EMAIL_USERNAME; private static final String EMAIL_PASSWORD; private static final String EMAIL_FROM; private static String EMAIL_TO; static { ResourceBundle rb = ResourceBundle.getBundle(\"other_config\"); SMTP_MAIL_HOST = rb.getString(\"mail.host\"); EMAIL_USERNAME = rb.getString(\"mail.user\"); EMAIL_PASSWORD = rb.getString(\"mail.password\"); EMAIL_FROM = rb.getString(\"mail.from\"); EMAIL_TO = rb.getString(\"mail.to\"); } /** * 发送文件邮件 * @param subject 主题 * @param body 内容 * @param receiver 收件人,多个时逗号隔开 * @return 返回是否成功 */ public static boolean sendTextMail(String subject, String body, String receiver) { return sendMail(subject, body, receiver, 1); } /** * 发送html邮件 * @param subject 主题 * @param body 内容 * @param receiver 收件人,多个时逗号隔开 * @return 返回是否成功 */ public static boolean sendHtmlMail(String subject, String body, String receiver) { return sendMail(subject, body, receiver, 2); } /** * 发送邮件 * @param subject 主题 * @param body 内容 * @param receiver 收件人,多个时逗号隔开 * @param type 类型.1:文本;2:html * @return */ private static boolean sendMail(String subject, String body, String receiver, int type) { /* 服务器信息 */ Properties props = new Properties(); props.put(\"mail.smtp.host\", SMTP_MAIL_HOST); props.put(\"mail.smtp.auth\", \"true\"); boolean result = false; try { /* 创建Session */ javax.mail.Session session = javax.mail.Session.getDefaultInstance(props, new Authenticator() { @Override protected javax.mail.PasswordAuthentication getPasswordAuthentication() { return new javax.mail.PasswordAuthentication(EMAIL_USERNAME, EMAIL_PASSWORD); } }); /* 邮件信息 */ MimeMessage message = new MimeMessage(session); // 发件人 message.setFrom(new InternetAddress(EMAIL_FROM)); // 主题 message.setSubject(subject); // 添加收件人,和默认收件人组合 String res = EMAIL_TO; if (receiver != null && !receiver.isEmpty()) { res += \",\" + receiver; } if(null != EMAIL_TO && !EMAIL_TO.isEmpty()){ InternetAddress[] internetAddressTo = new InternetAddress().parse(res); message.setRecipients(Message.RecipientType.TO, internetAddressTo); } // 正文 if (2 == type) { // html信息 message.setContent(body, \"text/html;charset=UTF-8\" ); } else { // 文本信息 message.setText(body); } // 附件部分 //messageBodyPart = new MimeBodyPart(); //String filename = \"file.txt\"; //DataSource source = new FileDataSource(filename); //messageBodyPart.setDataHandler(new DataHandler(source)); //messageBodyPart.setFileName(filename); //multipart.addBodyPart(messageBodyPart); // 发送 Transport.send(message); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } } 配置文件other_config.properties, 放在src目录下面 12345678# 邮件发送相关配置mail.host=mail.163.com# 根据邮箱服务器配置,见使用说明mail.user=usermail.password=passwordmail.from=user@163.com# 默认收件人mail.to=to@163.com 使用说明 实现了发送文本邮件和html邮件两种功能 发送附件功能没有使用场景所以未经过测试 可以在方法调用时指定添加收件人,默认的收件人是必须要有的 有一个user的坑需要注意.有的邮箱服务器是用的user@服务器作为用户名(如user@163.com),有的是直接用的user.配置不正确会报5XX错误 使用1234// 文本邮件MailUtil.sendTextMail(\"A text mail\", \"haha\", \"\");// html邮件MailUtil.sendHtmlMail(\"A html mail\", \"<h1>haha</h1>\", \"\");","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"tool","slug":"java/tool","permalink":"http://blog.xxyxpy.pub/categories/java/tool/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"mail","slug":"mail","permalink":"http://blog.xxyxpy.pub/tags/mail/"}]},{"title":"spring-注解","slug":"java/spring/spring-注解","date":"2017-12-25T07:51:09.287Z","updated":"2018-11-29T06:25:56.789Z","comments":true,"path":"2017/12/25/java/spring/spring-注解/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/spring/spring-注解/","excerpt":"@Profile指定环境名称,用于实现不同的环境的切换。比如数据库连接 @ActiveProfiles声明活动的Profile 1234567891011121314151617181920212223242526272829303132333435363738public class TestBean { private String content; public TestBean(String content) { this.content = content; } public String getContent() { return content; } public void setContent(String content) { this.content = content; }}// 配置@Configurationpublic class TestConfig { @Bean @Profile(\"dev\") public TestBean devTestBean() { return new TestBean(\"dev profile\"); } @Bean @Profile(\"prod\") public TestBean prodTestBean() { return new TestBean(\"prod profile\"); }}// 测试@RunWith(SpringRunner.class)@SpringBootTest@ActiveProfiles(\"dev\")public class ProfileTest { @Autowired private TestBean testBean; @Test public void show() { System.out.println(testBean.getContent());// 打印 dev profile }} @EnableWebMvc开启SpringMvc的支持","text":"@Profile指定环境名称,用于实现不同的环境的切换。比如数据库连接 @ActiveProfiles声明活动的Profile 1234567891011121314151617181920212223242526272829303132333435363738public class TestBean { private String content; public TestBean(String content) { this.content = content; } public String getContent() { return content; } public void setContent(String content) { this.content = content; }}// 配置@Configurationpublic class TestConfig { @Bean @Profile(\"dev\") public TestBean devTestBean() { return new TestBean(\"dev profile\"); } @Bean @Profile(\"prod\") public TestBean prodTestBean() { return new TestBean(\"prod profile\"); }}// 测试@RunWith(SpringRunner.class)@SpringBootTest@ActiveProfiles(\"dev\")public class ProfileTest { @Autowired private TestBean testBean; @Test public void show() { System.out.println(testBean.getContent());// 打印 dev profile }} @EnableWebMvc开启SpringMvc的支持 @ControllerAdvice将对于控制器的全局配置放置在同一个位置,组合了@Component注解 @ExceptionHandler用于全局处理控制器里的异常 @InitBinder用来设置WeDataBinder,WeDataBinder用来自动绑定前台请求参数到Model中 @ModelAttribute绑定键值对到Model里 配置@Value加载单个配置项 @PropertySource加载配置文件。不支持加载yaml文件,必须为property @ImportResource导入外部的xml配置 @ConfigurationProperties将配置和一个Bean的属性进行关联,从而实现类型安全的配置 条件注解@ConditionalOnBean当容器中有指定的Bean的条件下 @ConditionalOnClass当类路径下有指定的类的条件下 @ConditionalOnExpression基于SpEL表达式作为判断条件 @ConditionalOnJava基于jvm版本作为判断条件 @ConditionalOnJndi在Jndi存在的条件下查找指定的位置 @ConditionalOnMissingBean当容器没有指定Bean的情况下 @ConditionalOnMissingClass当类路径下没有指定类的条件下 @ConditionalOnNotWebApplication当前项目不是web项目的条件下 @ConditionalOnProperty指定的属性是否有指定的值 @ConditionalOnResource类路径是否有指定的值 @ConditionalOnSingleCandidate当指定Bean在容器中只有一个,或者虽然有多个但是指定首选的Bean @ConditionalOnWebApplication当前项目是web项目的条件下 请求和响应@GetMapping 实际上是@RequestMapping(method = RequestMethod.GET) @PostMapping 实际上是@RequestMapping(method = RequestMethod.POST) @RequestParam从request中获取请求参数。有如下四个参数: defaultValue 如果请求中没有这个参数或者参数为空就会使用指定的默认值 name 绑定本参数的名称,与request上面的一致 required 表示此参数为必须的 value 和name一样的作用,是name属性的一个别名 @PathVariable获取rest风格接口中的请求参数 12@RequestMapping(\"/hello/{id}\")public String getDetails(@PathVariable(value=\"id\") String id) { ...... } @ResponseBody指定以json格式进行返回 @RequestBody一般是在post请求时为方法的参数添加此注解,以json格式传入,使用pojo去接收时会自动的进行反序列化 验证JSR提供的校验注解@Null 被注释的元素必须为 null @NotNull 被注释的元素必须不为 null @AssertTrue 被注释的元素必须为 true @AssertFalse 被注释的元素必须为 false @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @Size(max=, min=) 被注释的元素的大小必须在指定的范围内 @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内 @Past 被注释的元素必须是一个过去的日期 @Future 被注释的元素必须是一个将来的日期 @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式 Hibernate Validator提供的校验注解@NotBlank(message =) 验证字符串非null,且长度必须大于0 @Email 被注释的元素必须是电子邮箱地址 @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内 @NotEmpty 被注释的字符串的必须非空 @Range(min=,max=,message=) 被注释的元素必须在合适的范围内 事务@Transactional @Transactional 加于private方法, 无效 @Transactional 加于未加入接口的public方法, 再通过普通接口方法调用, 无效 @Transactional 加于接口方法, 无论下面调用的是private或public方法, 都有效 @Transactional 加于接口方法后, 被本类普通接口方法直接调用, 无效 @Transactional 加于接口方法后, 被本类普通接口方法通过接口调用, 有效 @Transactional 加于接口方法后, 被它类的接口方法调用, 有效 @Transactional 加于接口方法后, 被它类的私有方法调用后, 有效 总结: Transactional是否生效, 仅取决于是否加载于接口方法, 并且是否通过接口方法调用(而不是本类调用)","categories":[{"name":"spring","slug":"spring","permalink":"http://blog.xxyxpy.pub/categories/spring/"}],"tags":[{"name":"annotaion","slug":"annotaion","permalink":"http://blog.xxyxpy.pub/tags/annotaion/"}]},{"title":"spring-03事务管理","slug":"java/spring/spring-03事务管理","date":"2017-12-25T07:51:02.697Z","updated":"2017-12-25T07:51:02.721Z","comments":true,"path":"2017/12/25/java/spring/spring-03事务管理/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/spring/spring-03事务管理/","excerpt":"什么是事务关于事务的内容可以查看 web05事务 Spring框架中事务相关的类和API接口和API PlatformTransactionManager接口 平台事务管理器,真正管理事务的类。该接口有具体的实现类,根据不同的持久层框架需要选择不同的实现类 TransactionDefinition接口 事务定义信息,包括事务的隔离级别、传播行为、超时以及是否只读等 TransactionStatus接口 事务的状态 平台事务管理器是真正管理事务的对象,根据事务定义的信息进行事务管理。在管理事务中产生一些状态并将状态记录到TransactionStatus中","text":"什么是事务关于事务的内容可以查看 web05事务 Spring框架中事务相关的类和API接口和API PlatformTransactionManager接口 平台事务管理器,真正管理事务的类。该接口有具体的实现类,根据不同的持久层框架需要选择不同的实现类 TransactionDefinition接口 事务定义信息,包括事务的隔离级别、传播行为、超时以及是否只读等 TransactionStatus接口 事务的状态 平台事务管理器是真正管理事务的对象,根据事务定义的信息进行事务管理。在管理事务中产生一些状态并将状态记录到TransactionStatus中 PlatformTransactionManager接口中实现类和常用的方法实现类 如果使用的是Spring的JDBC模版或MyBatis框架需要选择DataSourceTransactionManager实现类 如果使用的是Hibernate框架需要选择HibernateTransactionManager实现类 常用方法(了解即可,一般不需要手动调用) void commit(TransactionStatus status) TransactionStatus getTransaction(TransactionDefinition definition) void rollback(TransactionStatus status) TransactionDefinition接口事务的隔离级别常量 static int ISOLATION_DEFAULT – 采用数据库的默认隔离级别 static int ISOLATION_READ_UNCOMMITTED static int ISOLATION_READ_COMMITTED static int ISOLATION_REPEATABLE_READ static int ISOLATION_SERIALIZABLE 事务的传播行为常量(一般不需要设置,使用默认值即可)事务的传播行为解决的是业务层之间的互相调用,确定不同业务类之间事务开启的方式 PROPAGATION_REQUIRED(默认值):A中有事务使用A中的事务,如果没有在B中就会开启一个新的事务将A包含进来以保存AB两个在同一个事务中 PROPAGATION_SUPPORTS:A中有事务使用A中的事务,如果没有那么B也不使用事务 PROPAGATION_MANDATORY:A中有事务使用A中的事务,如果没有那么拋出异常 PROPAGATION_REQUIRES_NEW:A中有事务将A中的事务挂起,B创建一个新的事务以保证AB两个不在同一个事务 PROPAGATION_NOT_SUPPORTED:A中有事务,将A中的事务挂起 PROPAGATION_NEVER:A中有事务,拋出异常 PROPAGATION_NESTED:嵌套事务,当A执行之后就会在这个位置设置一个保存点。如果B没有问题执行通过,如果B出现异常根据需求进行回滚到保存点或是初始状态 基于AspectJ的XML方式的事务管理Dao层接口和实现类 12345678910111213141516public interface AccountDao { public void outMoney(String out, double money); public void inMoney(String in, double money);}public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public void outMoney(String out, double money) { getJdbcTemplate().update(\"update t_account set money = money - ? where name = ?\", money, out); } @Override public void inMoney(String in, double money) { // int i = 10/0; getJdbcTemplate().update(\"update t_account set money = money + ? where name = ?\", money, in); }} Service层接口和实现类 123456789101112131415public interface AccountService { void pay(String out, String in, double money);}public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void pay(String out, String in, double money) { accountDao.outMoney(out, money); accountDao.inMoney(in, money); }} applicationContext.xml 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--suppress SpringFacetInspection --><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:p=\"http://www.springframework.org/schema/p\" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd\"> <!-- c3p0连接池 --> <bean class=\"com.mchange.v2.c3p0.ComboPooledDataSource\" id=\"dataSource\"> <property name=\"driverClass\" value=\"com.mysql.jdbc.Driver\"/> <property name=\"jdbcUrl\" value=\"jdbc:mysql://localhost:3306/mytest\"/> <property name=\"user\" value=\"root\"/> <property name=\"password\" value=\"root\"/> </bean> <!-- 配置事务管理器 --> <bean class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\" id=\"transactionManager\"> <!-- 注入连接池 --> <property name=\"dataSource\" ref=\"dataSource\"/> </bean> <!-- 配置事务增强 --> <tx:advice id=\"txAdvice\" transaction-manager=\"transactionManager\"> <tx:attributes> <!--propagation=REQUIRED为默认值,可以不写--> <tx:method name=\"pay\" propagation=\"REQUIRED\"/> </tx:attributes> </tx:advice> <!-- 配置aop切面 --> <aop:config> <aop:advisor advice-ref=\"txAdvice\" pointcut=\"execution(* day3.demo1.AccountServiceImpl.pay(..))\"/> </aop:config> <!-- 配置dao --> <bean class=\"day3.demo1.AccountDaoImpl\" id=\"accountDao\"> <property name=\"dataSource\" ref=\"dataSource\"/> </bean> <!-- 配置service --> <bean class=\"day3.demo1.AccountServiceImpl\" id=\"accountService\"> <property name=\"accountDao\" ref=\"accountDao\"/> </bean></beans> 测试 123456789101112131415@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(\"classpath:applicationContext5.xml\")public class MyTest { @Resource(name = \"accountService\") AccountService accountService; /** * spring框架的 * 声明式事务(采用XML配置文件的方式) */ @Test public void test1() { accountService.pay(\"王五\", \"赵六\", 100); }} 基于AspectJ的注解方式的事务管理Dao层不作修改 Servce层对impl实现类添加注解@Transactional 123456789101112131415@Transactionalpublic class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void pay(String out, String in, double money) { accountDao.outMoney(out, money); accountDao.inMoney(in, money); }} 配置文件修改为 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--suppress SpringFacetInspection --><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:p=\"http://www.springframework.org/schema/p\" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd\"> <!-- c3p0连接池 --> <bean class=\"com.mchange.v2.c3p0.ComboPooledDataSource\" id=\"dataSource\"> <property name=\"driverClass\" value=\"com.mysql.jdbc.Driver\"/> <property name=\"jdbcUrl\" value=\"jdbc:mysql://localhost:3306/mytest\"/> <property name=\"user\" value=\"root\"/> <property name=\"password\" value=\"root\"/> </bean> <!-- 配置事务管理器 --> <bean class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\" id=\"transactionManager\"> <!-- 注入连接池 --> <property name=\"dataSource\" ref=\"dataSource\"/> </bean> <!-- 配置事务增强 --> <tx:advice id=\"txAdvice\" transaction-manager=\"transactionManager\"> <tx:attributes> <!--propagation=REQUIRED为默认值,可以不写--> <tx:method name=\"pay\" propagation=\"REQUIRED\"/> </tx:attributes> </tx:advice> <!-- 配置aop切面 --> <aop:config> <aop:advisor advice-ref=\"txAdvice\" pointcut=\"execution(* day3.demo1.AccountServiceImpl.pay(..))\"/> </aop:config> <!-- 配置dao --> <bean class=\"day3.demo1.AccountDaoImpl\" id=\"accountDao\"> <property name=\"dataSource\" ref=\"dataSource\"/> </bean> <!-- 配置service --> <bean class=\"day3.demo1.AccountServiceImpl\" id=\"accountService\"> <property name=\"accountDao\" ref=\"accountDao\"/> </bean></beans> 测试类不变,同样达到事务管理的效果","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"spring","slug":"java/ssh/spring","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/spring/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://blog.xxyxpy.pub/tags/spring/"}]},{"title":"spring-02IOC和AOP","slug":"java/spring/spring-02IOC和AOP","date":"2017-12-25T07:50:57.561Z","updated":"2017-12-25T07:50:57.581Z","comments":true,"path":"2017/12/25/java/spring/spring-02IOC和AOP/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/spring/spring-02IOC和AOP/","excerpt":"IOC之注解方式更多注解可查看 spring常用注解汇总 在实际的生产环境尤其是在Spring Boot中注解方式是最常被使用的,使用注解可以摆脱繁琐的xml配置。如果要使用注解需要作如下配置: 添加spring-aop的jar包 修改xml配置文件主要是添加约束和配置注解扫描 注解扫描的包名可以是相应包的父级,比如配置day2则此包下面所有的子包都会被扫描","text":"IOC之注解方式更多注解可查看 spring常用注解汇总 在实际的生产环境尤其是在Spring Boot中注解方式是最常被使用的,使用注解可以摆脱繁琐的xml配置。如果要使用注解需要作如下配置: 添加spring-aop的jar包 修改xml配置文件主要是添加约束和配置注解扫描 注解扫描的包名可以是相应包的父级,比如配置day2则此包下面所有的子包都会被扫描 123456789101112131415<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--suppress SpringFacetInspection --><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:p=\"http://www.springframework.org/schema/p\" 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\"> <!-- 配置注解扫描 --> <context:component-scan base-package=\"day2.demo1\"/> </beans> 接口和实现类 1234567891011public interface UserService { void save();}// 如果不设置值,则默认名称为小写开头的类名userServiceImp@Service(\"userService\")public class UserServiceImpl implements UserService { @Override public void save() { System.out.println(\"serviceImpl save...\"); }} 测试 123456@Testpublic void test1() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\"applicationContext2.xml\"); UserService userService = (UserService) applicationContext.getBean(\"userService\"); userService.save();} 框架中对于Bean管理的常用注解作用在类上面的注解 @Component 组件,作用在类上面 @Controller 作用在Web层 @Service 作用在Service层 @Repository 作用在持久层(dao层) 作用在属性上面的注解,使用注解时不需要提供set方法 @Value 用于注入普通类型 @Autowired 默认按类型进行自动装配,适用于接口只有一个实现类时。如果想按名称进行注入同时使用@Qualifier指定名称 @Resource java提供的注解,需要指定name属性,即按名称进行注入。替代上面@Autowired和@Qualifier的情况 Bean的作用范围和生命周期注解 @Scope 相当于xml配置时的scope属性,可以有prototype、singleton(默认)等值 @PostConstruct 相当于init-method @PreDestroy 相当于destroy-method 1234567891011121314151617181920212223242526272829public interface UserDao { void save();}@Repositorypublic class UserDaoImpl implements UserDao { @Override public void save() { System.out.println(\"userDaoImpl save...\"); }}@Service(\"userService\")public class UserServiceImpl implements UserService { @Value(\"tom\") private String name; //@Resource(name = \"userDaoImpl\") @Autowired // 基于类型装配,如果有多个实现类需要同时使用@Qualifier @Qualifier(\"userDaoImpl\") private UserDao userDao; @Override public void save() { System.out.println(\"name:\" + name); userDao.save(); System.out.println(\"serviceImpl save...\"); }} Spring整合JUnit为了简化测试Spring框架提供了整合Junit的功能,使用此功能前需要先引入spring-test.jar,在具体的测试类上添加注解,并且可以注解注入配置文件。如下: 123456789101112@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(\"classpath:applicationContext.xml\")public class SpringDemo1 { @Resource(name=\"userService\") private UserService userService; @Test public void demo2(){ userService.save(); }} Spring核心功能之AOP也可参考 关于Spring AOP(AspectJ)你该知晓的一切 AOP概述 什么是AOP AOP为Aspect Oriented Programming的缩写,意为:面向切面编程 AOP是一种编程范式,隶属于软件工程范畴,用于指导开发者如何组织程序结构 AOP最早是由AOP联盟的组织提出并制定的一套规范。Spring将AOP思想引入到框架中 AOP是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术 利用AOP可以对业务各个部分进行隔离,从而使业务逻辑各部分之前的耦合度降低提高程序的可重用性,同时提高开发效率 AOP特点 面向切面编程思想 采取横向抽取机制,取代了传统纵向继承体系重复性代码。如性能监控,事务管理,日志记录等 可以在不修改源代码的前提下对程序的功能进行增强 Spring框架的AOP底层实现 底层也是采用的动态代理技术,代理的方式有两种: 基于JDK的动态代理。此方式必须要有接口和具体的实现类才可以生成代理对象 基于CGLIB的动态代理。对于没有实现接口的类采用此种方式,使用动态代理产生这个类的子类 使用AspectJ的XML方式完成AOP的开发 引入相关的开发jar包。AOP相关的spring-aop、com.springsource.org.aopalliance,AspectJ相关的com.springsource.org.aspectj.weaver、spring-aspects 修改applicationContext.xml配置文件添加AOP相关的schema约束,修改完成之后的文件如下: 123456789101112131415<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--suppress SpringFacetInspection --><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:p=\"http://www.springframework.org/schema/p\" xmlns:context=\"http://www.springframework.org/schema/context\" xmlns:aop=\"http://www.springframework.org/schema/aop\" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd\"> </beans> 定义接口和实现类 12345678910111213141516171819202122public interface CustomerDao { void save(); void update(); void delete();}@Repositorypublic class CustomerDaoImpl implements CustomerDao { @Override public void save() { System.out.println(\"impl save...\"); } @Override public void update() { System.out.println(\"impl update...\"); } @Override public void delete() { System.out.println(\"impl delete...\"); }} 定义切面类 123456@Component(\"myAspectXml\")public class MyAspectXml { public void log() { System.out.println(\"log sth...\"); }} 在applicationContext.xml中配置切面类和通知 12345678910<context:component-scan base-package=\"day2.demo2\"/><!-- 配置AOP --><aop:config> <!-- 引入切面类 --> <aop:aspect ref=\"myAspectXml\"> <!-- 定义通知类型:切面类的方法和切入点表达式 --> <aop:before method=\"log\" pointcut=\"execution(* day2.demo2.CustomerDaoImpl.save(..))\"/> </aop:aspect></aop:config> 测试 12345678910111213@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(\"classpath:applicationContext3.xml\")public class MyTest { @Resource(name = \"customerDao\") private CustomerDao customerDao; @Test public void test1() { customerDao.save(); customerDao.delete(); customerDao.update(); }} 切入点表达式在配置切入点的时候需要定义表达式,重点的格式如下:execution(public (..)),定义为:execution([访问修饰符] 返回类型 包名.类名.方法名(参数)) 访问修改符可以省略不写,并不是必须要出现的 返回值类型必须出现,根据实际的返回值编写,可以用*替代,表示所有包括void 包名可能有一坨多个,比如com.a.b.ClassA.method。com是不可以省略的但可以用*替代,*.a.b.ClassA.method;中间的包名可以使用*替代,com.a.*.ClassA.method;省略中间的多个包名可以使用..,com..ClassA.method 类名也可以用 替代,还有其它类似的写法如 DaoImpl 表达所有以DaoImpl结尾的 方法编写方法和类名相同 参数如果是一个的话可以用 * 号替代,当然也可以写实际的参数。如果想代表任意参数使用..,同样的也包括void 在实际的使用过程中通用的切入点表达式可以写成:execution(* pub.xxyxpy.service.*.*(..)) AOP的通知类型 前置通知。在目标类的方法执行之前执行,可以对方法的参数来做校验 1<aop:before method=\"log\" pointcut=\"execution(* *..*.*Dao.save*(..))\"/> 后置通知。方法正常执行后的通知,可以修改方法的返回值 1<aop:after-returning method=\"afterReturn\" pointcut=\"execution(* *..*.*Dao.update*(..))\"/> 最终通知。在目标类的方法执行之后执行,如果出现异常最终通知也会执行,可以用于释放资源 1<aop:after method=\"after\" pointcut=\"execution(* *..*.*Dao.delete*(..))\"/> 异常通知。在出现异常后通知,用于包装异常的信息 1<aop:after-throwing method=\"except\" pointcut=\"execution(* *..*.*Dao.updateExcept*(..))\"/> 环绕通知。在方法执行前后执行,目标方法默认不执行需要在切面类的方法中使用ProceedingJoinPoint的proceed方法来让目标对象的方法执行 1<aop:around method=\"around\" pointcut=\"execution(* *..*.*Dao.deleteA*(..))\"/> 测试使用 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364// 切面类@Component(\"myAspectXml\")public class MyAspectXml { public void log() { System.out.println(\"前置通知:log sth...\"); System.out.println(\"============================\"); } public void afterReturn() { System.out.println(\"后置通知:方法正常执行之后的。。。\"); System.out.println(\"============================\"); } public void after() { System.out.println(\"最终通知:目标类的方法执行之后执行\"); System.out.println(\"============================\"); } public void except() { System.out.println(\"异常通知:出现异常之后的通知,无异常不执行\"); System.out.println(\"============================\"); } public void around(ProceedingJoinPoint joinPoint) { System.out.println(\"环绕通知前\"); try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println(\"环绕通知后\"); System.out.println(\"============================\"); }}// 测试的实现类(接口省略)@Repository(\"customerDao\")public class CustomerDaoImpl implements CustomerDao { @Override public void save() { System.out.println(\"进 save 方法\"); } @Override public void update() { System.out.println(\"进 update 方法\"); } @Override public void updateExcept() { System.out.println(\"进 updateException 方法\"); int i = 1 / 0; } @Override public void delete() { System.out.println(\"进 delete 方法\"); } @Override public void deleteAround() { System.out.println(\"进 deleteAround 方法\"); }} applicationContext配置文件 123456789101112131415161718<context:component-scan base-package=\"day2.demo2\"/><!-- 配置AOP --><aop:config> <!-- 引入切面类 --> <aop:aspect ref=\"myAspectXml\"> <!-- 定义通知类型:切面类的方法和切入点表达式 --> <aop:before method=\"log\" pointcut=\"execution(* day2.demo2.CustomerDaoImpl.save(..))\"/> <!-- 后置通知 --> <aop:after-returning method=\"afterReturn\" pointcut=\"execution(* *..*.*Dao.update*(..))\"/> <!-- 最终通知 --> <aop:after method=\"after\" pointcut=\"execution(* *..*.*Dao.delete*(..))\"/> <!-- 异常通知 --> <aop:after-throwing method=\"except\" pointcut=\"execution(* *..*.*Dao.updateExcept*(..))\"/> <!-- 环绕通知 --> <aop:around method=\"around\" pointcut=\"execution(* *..*.*Dao.deleteA*(..))\"/> </aop:aspect></aop:config> 测试 123456789101112131415@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(\"classpath:applicationContext3.xml\")public class MyTest { @Resource(name = \"customerDao\") private CustomerDao customerDao; @Test public void test1() { customerDao.save(); customerDao.delete(); customerDao.deleteAround(); customerDao.update(); customerDao.updateExcept(); }} 使用注解的方式实现AOP几个通知类型的注解为: @Before 前置通知 @AfterReturning 后置通知 @Around 环绕通知 @After 最终通知 @AfterThrowing 异常通知 接口和实现类不变,需要修改配置文件以及切面类。测试结果和xml的方式相同 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748// 切面类@Component(\"myAspectXml\")@Aspectpublic class MyAspectXml { @Before(\"execution(* day2.demo3.CustomerDaoImpl.save(..))\") public void log() { System.out.println(\"前置通知:log sth...\"); System.out.println(\"============================\"); } @AfterReturning(\"execution(* *..*.*Dao.update*(..))\") public void afterReturn() { System.out.println(\"后置通知:方法正常执行之后的。。。\"); System.out.println(\"============================\"); } @After(\"execution(* *..*.*Dao.delete*(..))\") public void after() { System.out.println(\"最终通知:目标类的方法执行之后执行\"); System.out.println(\"============================\"); } @AfterThrowing(\"execution(* *..*.*Dao.updateExcept*(..))\") public void except() { System.out.println(\"异常通知:出现异常之后的通知,无异常不执行\"); System.out.println(\"============================\"); } //@Around(\"execution(* *..*.*Dao.deleteA*(..))\") @Around(\"MyAspectXml.fn()\") public void around(ProceedingJoinPoint joinPoint) { System.out.println(\"环绕通知前\"); try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println(\"环绕通知后\"); System.out.println(\"============================\"); } // 切入点 @Pointcut(\"execution(* *..*.*Dao.deleteA*(..))\") public void fn() { }} 配置文件 123456789<!-- 复制了接口和实现类到新的包 --><context:component-scan base-package=\"day2.demo3\"/><!-- 开启自动代理 --><aop:aspectj-autoproxy/><!-- 配置AOP,注解方式不需要配置切面 --><aop:config></aop:config> ","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"spring","slug":"java/ssh/spring","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/spring/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://blog.xxyxpy.pub/tags/spring/"}]},{"title":"spring-01简单使用","slug":"java/spring/spring-01简单使用","date":"2017-12-25T07:50:49.644Z","updated":"2017-12-25T07:50:49.662Z","comments":true,"path":"2017/12/25/java/spring/spring-01简单使用/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/spring/spring-01简单使用/","excerpt":"什么是spring框架2003年兴起的一个轻量级的java开源框架,替代之前的EJB。核心是控制反转(IoC)和面向切面编程(AOP) 框架特点: 方便解耦,简化开发。Spring框架其实是一个大的工厂,可以将所有的对象创建和依赖关系维护交给spring来管理 AOP编程支持。提供切面编程,可以方便的实现对程序进行权限拦截、运维监控等 声明式事务支持。只需要通过配置就可以完成对事务的管理,无需手动编程 方便程序的测试。Spring对Junit的支持,可以通过注解方便的测试spring程序 方便集成各种优秀框架。提供各种优秀框架(如:Struts2、hibernate、Mybatis等)的插件整合包 降低JavaEE Api的使用难度。对JavaEE开发中非常駗的一些api(jdbc、javamail、远程调用等)都提供了封装,使用这些api的难度大大降低","text":"什么是spring框架2003年兴起的一个轻量级的java开源框架,替代之前的EJB。核心是控制反转(IoC)和面向切面编程(AOP) 框架特点: 方便解耦,简化开发。Spring框架其实是一个大的工厂,可以将所有的对象创建和依赖关系维护交给spring来管理 AOP编程支持。提供切面编程,可以方便的实现对程序进行权限拦截、运维监控等 声明式事务支持。只需要通过配置就可以完成对事务的管理,无需手动编程 方便程序的测试。Spring对Junit的支持,可以通过注解方便的测试spring程序 方便集成各种优秀框架。提供各种优秀框架(如:Struts2、hibernate、Mybatis等)的插件整合包 降低JavaEE Api的使用难度。对JavaEE开发中非常駗的一些api(jdbc、javamail、远程调用等)都提供了封装,使用这些api的难度大大降低 环境准备下载框架开发包官网 下载地址 目录结构: docs api和开发规范 libs jar包和源码 schema 约束 创建web项目,引入spring开发包 核心功能包:Beans、Core、Context、Expression Language 日志包:com.springsource.org.apache.commons.logging-1.1.1.jar log4j的jar包:com.springsource.org.apache.log4j-1.2.15.jar 入门实例使用spring框架必须编写接口和实现类123456789public interface UserService { void sayHello();}public class UserServiceImpl implements UserService { public void sayHello() { System.out.println(\"hello~\"); }} 在src目录添加配置文件application.xml12345678910<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--suppress SpringFacetInspection --><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:p=\"http://www.springframework.org/schema/p\" xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\"> <bean class=\"day1.demo1.UserServiceImpl\" id=\"userService\"/> </beans> 测试 加载配置文件中classpath是可选项123456@Testpublic void test1() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\"classpath:applicationContext.xml\"); UserService userService = (UserService) applicationContext.getBean(\"userService\"); userService.sayHello();} 入门总结从上面的测试类中可以看出来最重的一个角色是ApplicationContext接口,此接口用于加载配置文件并可以返回指定的Bean对象。此接口有如下两个实现类: ClassPathXmlApplicationContext 用于加载类路径下的Spring配置文件 FileSystemXmlApplicationContext 用于加载本地磁盘下面的Spring文件,用的比较少 除了此接口外早期还提供了BeanFactory接口用于Bean的获取。用法如下: 12345public void run(){ BeanFactory factory = new XmlBeanFactory(new ClassPathResource(\"applicationContext.xml\")); UserService us = (UserService) factory.getBean(\"UserService\"); us.sayHello();} 两者的主要区别为ApplicationContext接口在加载配置文件时就会创建具体的Bean对象的实例,而BeanFactory采用延迟加载策略,只有在第一次getBean的时候才会初始化Bean 配置文件中Bean标签的配置 id 给Bean起个名字,在约束中采用id的约束,具有唯一性 name 一般不使用,不具有唯一性约束 class Bean对象类的全路径 scope 代表Bean的作用范围。有以下几个取值 singleton(单例,默认取值)、prototype(多例,在整合Struts2框架时,Action类交给spring管理而每次请求的数据是不一样的所以必须配置成多例)、request(应用在web项目中,每次http请求创建一个新的bean)、session(应用在web项目中,同一个Session共享一个实例)、globalsession(应用在web项目中,多服务器间的session) init-method 当bean被加载到容器时调用此属性指定的方法 destory-method 当bean从容器中删除时调用此属性指定的方法。在web容器中会自动调用,手工测试时需要使用ClassPathXmlApplicationContext的close()方法 框架的DI(依赖注入)IOC和DI的概念 IOC Inverse Of Control,控制反馈,将对象的创建权反转给spring DI Dependency Injection,依赖注入,在spring创建Bean对象时动态的将依赖对象注入到Bean组件中 构造函数注入修改实现类添加构造函数 123456789101112public class UserServiceImpl implements UserService { private String name; public UserServiceImpl(String name) { this.name = name; } public void sayHello() { System.out.println(\"hello~\"); } public String getName() { return name; }} xml的配置 123<bean class=\"day1.demo1.UserServiceImpl\" id=\"userService\"> <constructor-arg name=\"name\" value=\"zhangsan\"/></bean> 测试 123456@Testpublic void test2() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\"classpath:applicationContext.xml\"); UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean(\"userService\"); System.out.println(userService.getName());} 属性注入,需要提供属性的set方法123456789101112public class UserServiceImpl implements UserService { private String name; public void sayHello() { System.out.println(\"hello~\"); } public String getName() { return name; } public void setName(String name) { this.name = name; }} xml的配置 123<bean class=\"day1.demo1.UserServiceImpl\" id=\"userService\"> <property name=\"name\" value=\"zhangsan\"/></bean> 测试方法同上面的构造函数注入 注入其它引入(java类)123456789101112131415161718192021public class Car { private String carName; public String getCarName() { return carName; } public void setCarName(String carName) { this.carName = carName; }}public class UserServiceImpl implements UserService { private Car car; public void sayHello() { System.out.println(\"hello~\"); } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; }} xml配置 1234567<bean class=\"day1.demo1.Car\" id=\"car\"> <property name=\"carName\" value=\"QQ\"/></bean><bean class=\"day1.demo1.UserServiceImpl\" id=\"userService\"> <property name=\"car\" ref=\"car\"/></bean> 测试 123456@Testpublic void test3() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\"classpath:applicationContext.xml\"); UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean(\"userService\"); System.out.println(userService.getCar().getCarName());} p名称空间注入(Srping 2.5版本开始提供,基本不使用)需要先为xml配置文件添加schema xmlns:p="http://www.springframework.org/schema/p,假如现在UserServiceImpl中有Car和Name两个属性。xml中的注入方式如下: 12<bean class=\"day1.demo1.UserServiceImpl\" id=\"userService\" p:car-ref=\"car\" p:name=\"zhangsan\"></bean> SpEL方式注入(Spring 3.0版本开始提供,基本不使用)SpEL:Spring Expression Language是Spring的表达式语言,有一些自己的语法。格式为:#{SpEL}。使用此语法除了可以直接设置值外还可以调用类中的属性可方法 123456789<bean class=\"day1.demo1.Car\" id=\"car\"> <property name=\"carName\" value=\"QQ\"/></bean><bean class=\"day1.demo1.UserServiceImpl\" id=\"userService\"> <property name=\"car\" ref=\"car\"/> <!--<property name=\"name\" value=\"#{'abc'.toUpperCase()}\"/>--> <property name=\"name\" value=\"#{car.carName}\"/></bean> 数组、集合(List,Set,Map)以及Properties的注入1234567891011121314151617181920212223242526272829303132333435363738394041<bean class=\"day1.demo1.UserServiceImpl\" id=\"userService\"> <!-- arr --> <property name=\"arr\"> <array> <value>a</value> <value>b</value> <value>c</value> </array> </property> <!-- list --> <property name=\"list\"> <list> <value>1</value> <value>2</value> <value>3</value> </list> </property> <!-- set --> <property name=\"set\"> <set> <value>a</value> <value>a</value> <value>b</value> </set> </property> <!-- map --> <property name=\"map\"> <map> <entry key=\"a\" value=\"a-v\"/> <entry key=\"b\" value=\"b-v\"/> <entry key=\"c\" value=\"c-v\"/> </map> </property> <!-- properties --> <property name=\"pro\"> <props> <prop key=\"name\">张三</prop> <prop key=\"age\">18</prop> </props> </property></bean> 引入多个Spring配置文件实际开发过程中spring的配置文件可能分散在不同的子工程中,需要在web层进行整合。需要使用到import标签,如: 1234567891011121314<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--suppress SpringFacetInspection --><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:p=\"http://www.springframework.org/schema/p\" xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\"> <!--引入多个配置--> <import resource=\"applicationContext2.xml\"/> <import resource=\"classpath*:applicationContext-web.xml\"/> <import resource=\"classpath*:applicationContext-service.xml\"/> <import resource=\"classpath*:applicationContext-dao.xml\"/> </beans>","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"spring","slug":"java/ssh/spring","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/spring/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://blog.xxyxpy.pub/tags/spring/"}]},{"title":"spring boot点滴","slug":"java/spring boot/spring boot点滴","date":"2017-12-25T07:50:10.018Z","updated":"2018-12-04T06:49:50.327Z","comments":true,"path":"2017/12/25/java/spring boot/spring boot点滴/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/spring boot/spring boot点滴/","excerpt":"1 banner操作 在 src/main/resources 下新建 banner.txt 通过 http://patorjk.com/software/taag生成字符,拷贝进banner.txt 完成替换 关闭banner SpringApplication app = new SpringApplication(xx.class); app.setShowBanner(false); app.run(args) 2 加载外部配置文件@PropertySource 注解不支持加载yaml文件,必须为property","text":"1 banner操作 在 src/main/resources 下新建 banner.txt 通过 http://patorjk.com/software/taag生成字符,拷贝进banner.txt 完成替换 关闭banner SpringApplication app = new SpringApplication(xx.class); app.setShowBanner(false); app.run(args) 2 加载外部配置文件@PropertySource 注解不支持加载yaml文件,必须为property 3 加载外部的xml配置@ImportResource 导入外部的xml配置 4 启动时修改端口号 java -jar xx.jar –server.port=9090 运行时添加debug参数 java -jar xx.jar –debug 5 类型安全的配置方式 先定义一个实体 在配置中以特定的字符串开头,如book.xx 通过@ConfigurationProperties将属性和一个Bean的属性进行关联,从而实现类型安全的配置 6 日志 默认使用logback 配置日志文件 logging.file=d:/mylog/log.log 配置日志级别,格式为logging.level.包名=级别 logging.level.org.springframework.web=DEBUG 7 激活特定环境的配置 配置文件名如:application-prod.properties application-dev.properties 激活:spring.profile.active=prod 8 条件注解 @ConditionalOnBean 当容器中有指定的Bean的条件下 @ConditionalOnClass 当类路径下有指定的类的条件下 @ConditionalOnExpression 基于SpEL表达式作为判断条件 @ConditionalOnJava 基于jvm版本作为判断条件 @ConditionalOnJndi 在Jndi存在的条件下查找指定的位置 @ConditionalOnMissingBean 当容器没有指定Bean的情况下 @ConditionalOnMissingClass 当类路径下没有指定类的条件下 @ConditionalOnNotWebApplication 当前项目不是web项目的条件下 @ConditionalOnProperty 指定的属性是否有指定的值 @ConditionalOnResource 类路径是否有指定的值 @ConditionalOnSingleCandidate 当指定Bean在容器中只有一个,或者虽然有多个但是指定首选的Bean @ConditionalOnWebApplication 当前项目是web项目的条件下 9 web中的静态资源在WebMvcAutoConfiguration类的addResourceHandlers方法中定义了以下静态资源的自动配置 类路径文件。把类路径下面的/static /public /resources 和 /META-INF/resources文件夹下面的静态文件直接映射为/**,可以通过http://localhost:8080/\\*\\*来访问 webjar.将常用的脚本框架封装在jar包中。把/META-INF/resources/webjars/下的静态文件映射为/webjar/*,可以通过http://localhost:8080/webjar/\\*来访问 10 自动配置HttpMessageConverters在WebMvcAutoConfiguration中注解了messageConverters,在这里自动注入了非常多的messageConverter 如果需要新增自定义的转换器,可以直接自定义new HttpMessageConverters,把定义好的转换器添加进去 当然也可以定义配置类继承WebMvcConfigurerAdapter重写configureMessageConverters方法来add新的转换器进去 也可以继承WebMvcConfigurerAdapter重写extendMessageConverters方法,首先清空转换器列表,再加入自定义的转换器。 11 接管Spring Boot的Web配置如果Spring Boot 提供的Mvc配置不符合要求,可以通过配置类加上@EnableWebMvc注解来实现完全自己控制的Mvc配置 如果既要保留原有的配置又想要添加或修改新的,可以自定义一个配置类并继承WebMvcConfigurerAdapter,无须使用@EnableWebMvc注解 12 控制台乱码在pom文件中增加配置,增加一项虚拟机参数,完整代码如下: 1234567891011121314151617<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- spring-boot:run 中文乱码解决 --> <configuration> <fork>true</fork> <!--增加jvm参数--> <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments> </configuration> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> <version>1.2.5.RELEASE</version> </dependency> </dependencies></plugin> 13 动态通过代码来修改端口号在docker容器中启动参数为:java -jar /usr/local/xxx.jar ${PORT0} ,其端口号是通过参数传入的。此时如果代码中指定的端口号与容器的启动参数不一致就会造成项目无法访问。明显此时不能再硬编码写死端口号,而应该用启动时的这个参数。解决方案如下: a. spring boot 1.x 版本时实现EmbeddedServletContainerCustomizer接口即可实现。由于已经更新了2.0以上了,这里不需要再关注了。 1234@Overridepublic void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) { configurableEmbeddedServletContainer.setPort(8003);} b.spring boot 2.0版本删除了EmbeddedServletContainerCustomizer接口,改为实现WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>接口即可实现此功能 1234567891011121314151617@SpringBootApplicationpublic class BootCommonapiApplication implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>{ private static int port = 8080; public static void main(String[] args) { try { if (args != null && args.length > 0) { port = Integer.parseInt(args[0]); } } catch (Exception e) { } SpringApplication.run(BootCommonapiApplication.class, args); } @Override public void customize(ConfigurableServletWebServerFactory server) { server.setPort(port); }} c.官方推荐的启动时修改端口号是通过添加--server.port=1234来实现的,我们也可以利用这一点来修改main方法的args参数 123456789101112131415161718@SpringBootApplicationpublic class BootCommonapiApplication implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>{ private static int port = 8080; public static void main(String[] args) { try { if (args != null && args.length > 0) { port = Integer.parseInt(args[0]); args[0] = \"--server.port=\" + port; } } catch (Exception e) { } SpringApplication.run(BootCommonapiApplication.class, args); } @Override public void customize(ConfigurableServletWebServerFactory server) { server.setPort(port); }} 14 切面修改Spring Mvc的请求和响应信息使用自定义的RequestBodyAdvice和ResponseBodyAdvice来进行请求和响应的处理 SpringMvc/SpringBoot HTTP通信加解密 15 RestTemplate请求时中文乱码解决 RestTemplate中文乱码 由于默认的HttpMessageConverter是ISO-8859-1,所以只要修改相应converter的编码 123456789101112131415@Beanpublic RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(httpRequestFactory()); List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters(); int len = messageConverters.size(); for (int i = 0; i < len; i++) { HttpMessageConverter<?> converter = messageConverters.get(i); if (converter instanceof StringHttpMessageConverter) { converter = new StringHttpMessageConverter(StandardCharsets.UTF_8); messageConverters.set(i, converter); break; } } return restTemplate;} 参考: Spring Boot修改默认端口号 Spring Boot 2.0内嵌Tomcat定制:WebServiceFactoryCustomizer","categories":[{"name":"spring","slug":"spring","permalink":"http://blog.xxyxpy.pub/categories/spring/"}],"tags":[{"name":"spring boot","slug":"spring-boot","permalink":"http://blog.xxyxpy.pub/tags/spring-boot/"}]},{"title":"struts2-03值栈","slug":"java/struts2/struts2-03值栈","date":"2017-12-25T07:49:30.427Z","updated":"2017-12-25T07:49:30.454Z","comments":true,"path":"2017/12/25/java/struts2/struts2-03值栈/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/struts2/struts2-03值栈/","excerpt":"OGNL表达式OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写 所谓对象图,即以任意一个对象为根,通过OGNL可以访问与这个对象关联的其它对象 通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性 OGNL表达式在Struts2中的应用 Struts2引入OGNL表达式,主要是在jsp页面中获取值栈中的值 使用方法 先引入Struts2的标签库 <%@ taglib prefix=”s” uri=”/struts-tags” %> 再使用Struts2提供的标签中的标签 <s:property value=”OGNL表达式”/> 值栈(ValueStack)值栈就相当天Struts2框架的数据中转站,向值栈存放一些数据,然后在页面中从值栈获取到数据。具有如下特点:","text":"OGNL表达式OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写 所谓对象图,即以任意一个对象为根,通过OGNL可以访问与这个对象关联的其它对象 通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性 OGNL表达式在Struts2中的应用 Struts2引入OGNL表达式,主要是在jsp页面中获取值栈中的值 使用方法 先引入Struts2的标签库 <%@ taglib prefix=”s” uri=”/struts-tags” %> 再使用Struts2提供的标签中的标签 <s:property value=”OGNL表达式”/> 值栈(ValueStack)值栈就相当天Struts2框架的数据中转站,向值栈存放一些数据,然后在页面中从值栈获取到数据。具有如下特点: ValueStack是Struts2提供的一个接口,实现类为OgnlValueStack Action是多例的,一个请求就会创建一个Action实例ActionContext对象,此对象代表的是Action的上下文,同时还会创建一个ValueStack对象 创建的ValueStack对象中保存了Action对象和其它相关的对象 Struts2框架把ValueStack对象保存在Request中,属性名为:struts.valueStack 可以通过ValueStack vs = (ValueStack)request.getAttribute("struts.valueStack")获取 值栈的内部结构参考:内部数据结构 OgnlValueStack对象为值栈的实现类,内部由两部分组成: root栈(CompoundRoot,继承list接口) 内部是一个List结构,存储动作相关对象。访问时不需要加# context栈(OgnlContext,实现 Map接口) 内部一个Map结构,存储各种映射关系(k-v)。访问时需要加# context栈中有ValueStack的引用以及root栈的引用,并且context栈被ActionContext持有 默认会将request,session,application,parameters压入context栈中,另外会压入一个key为attr的集合,对此集合进行查找时会顺序检索request、session、application、parameters 获取值栈对象的三种方法12345ValueStack vs1 = (ValueStack) ServletActionContext.getRequest().getAttribute(\"struts.valueStack\");ValueStack vs2 =(ValueStack)ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);ValueStack vs3 = ActionContext.getContext().getValueStack(); 值栈数据的设置和获取设置值时主要是使用针对root栈的push和set方法,首先最后操作的数据肯定是会被放在栈顶的。默认框架是将当前访问的Action对象放在了栈顶,在jsp中可以通过<s:debug/>查看值栈的内容 push方法 valueStack.push(Object obj); 此方法底层调用了root对象的push方法,将元素放在了栈顶 123public void push(Object o) { root.push(o);} set方法 valueStack.set(String key, Object obj); 调用此方法会将k-v值放在map集合中并且放到栈顶,如果存入时map集合不存在会新创建集合。如果已经存在map集合则添加到集合中 1234567891011121314151617181920212223public void set(String key, Object o) { Map setMap = retrieveSetMap(); setMap.put(key, o);}private Map retrieveSetMap() { Map setMap; Object topObj = peek(); // 先查看栈顶的是不是一个map集合如果是返回此集合,如果不是则创建map集合并返回 if (shouldUseOldMap(topObj)) { setMap = (Map) topObj; } else { setMap = new HashMap(); setMap.put(MAP_IDENTIFIER_KEY, \"\"); // 创建并push到栈顶 push(setMap); } return setMap;}private boolean shouldUseOldMap(Object topObj) { return topObj instanceof Map && ((Map) topObj).get(MAP_IDENTIFIER_KEY) != null;} 值栈的获取值根据从不同的地方(root栈、context栈)或者值的类型不同获取方法不同 另可参考值栈与ognl 1234567891011121314151617181920212223242526272829303132333435public class ValueStack2Action extends ActionSupport { public String getPp() { return pp; } public void setPp(String pp) { this.pp = pp; } private String pp; public String save() { ValueStack vs3 = ActionContext.getContext().getValueStack(); pp = \"pppp\"; // 向栈顶push值 vs3.push(\"tom\"); // 向栈顶set k-v值 // 如果原来顶部是map直接使用添加,如不是则new一个放在顶部 vs3.set(\"name1\", \"jack\"); MyUser user = new MyUser(); user.setUname(\"hello_name\"); vs3.set(\"user\", user); // 存入list集合 List<MyUser> ulist = new ArrayList<>(); ulist.add(new MyUser(\"ua\")); ulist.add(new MyUser(\"ub\")); ulist.add(new MyUser(\"uc\")); vs3.set(\"ulist\", ulist); // 设置值到context中 HttpServletRequest request = ServletActionContext.getRequest(); request.setAttribute(\"msg\", \"request_msg\"); request.getSession().setAttribute(\"msg\", \"session_msg\"); return SUCCESS; }} jsp页面 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><%@ taglib prefix=\"s\" uri=\"/struts-tags\" %><%@ taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\" %><html><head> <title>Title</title></head><body><%--访问action中的数据,需要提供set和get方法--%><s:property value=\"pp\"/> <br><%--访问字符串--%><s:property value=\"[1].top\"/> <br><hr><%--访问map中的数据--%><s:property value=\"[0].top.user.uname\"/> <br><s:property value=\"name1\"/> <br><s:property value=\"user.uname\"/> <br><hr><%--处理集合数据--%> <%--单索引--%><s:property value=\"ulist[0].uname\"/> <br><br> <%--遍历 * var编写上,把迭代产生的对象默认压入到context栈中,从context栈取值,加#号 * var不编写,默认把迭代产生的对象压入到root栈中 --%><s:iterator value=\"ulist\"> <s:property value=\"uname\"/> <br></s:iterator><br><s:iterator value=\"ulist\" var=\"u\"> <s:property value=\"#u.uname\"/> <br></s:iterator><hr><%--获取context栈中的内容 使用#--%>request.msg:<s:property value=\"#request.msg\"/> <br>session.msg:<s:property value=\"#session.msg\"/> <br>attr.msg:<s:property value=\"#attr.msg\"/> <br>parameter.id:<s:property value=\"#parameters.id\"/><hr><%--使用el和jstl--%><c:forEach items=\"${ulist}\" var=\"u\"> jsp - ${u.uname} <br></c:forEach><s:debug/><hr>N语法[0]:<s:property value=\"[0]\"/><br>N语法[1]:<s:property value=\"[1]\"/><br>N语法[0].top:<s:property value=\"[0].top\"/><br>N语法[1].top:<s:property value=\"[1].top\"/><br>N语法top:<s:property value=\"top\"/><br>N语法取值[0].user.uname:<s:property value=\"[0].user.uname\"/><br>N语法取值top.user.uname:<s:property value=\"top.user.uname\"/><br>N语法取值top.name1:<s:property value=\"top.name1\"/><br></body></html> 注意点: 1.访问context栈时需要加#号,比如request.msg 2.遍历列表时是否使用临时变量影响遍历时的对象的存储,如果不使用var是存储在root栈,使用时存储在context栈,进而会影响其取值 3.使用el表达式也可以获取到值栈中的数据,为什么? 因为在StrutsPrepareAndExecuteFilter类的doFilter方法中有request = prepare.wrapRequest(request);对原来的request作了包装处理,包装类为:StrutsRequestWrapper。 此类增强了request中的getAttribute方法,主要代码片断为: 12345678910111213> if (!alreadyIn && !key.contains(\"#\")) {> try {> // If not found, then try the ValueStack> ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.TRUE);> ValueStack stack = ctx.getValueStack();> if (stack != null) {> attribute = stack.findValue(key);> }> } finally {> ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.FALSE);> }> }> 4.N语法和top语法 规定栈顶的对象为[0],所以[0]表示从值栈的第0个对象开始取到最后一个对象。同理[N]表示从第N个开始取到最后一个 如果要访问第N个对象使用[N].top,top的意思是取第一个 如果直接使用top则拿到的是栈顶对象效果和[0].top一样 N语法和top语法后面带属性查找时都是从当前符合条件的对象开始向下查找,找到即返回 当N语法和top语法省略时直接调用的是OgnlValueStack中的findValue方法 123456789> N语法[0]:<s:property value=\"[0]\"/><br>> N语法[1]:<s:property value=\"[1]\"/><br>> N语法[0].top:<s:property value=\"[0].top\"/><br>> N语法[1].top:<s:property value=\"[1].top\"/><br>> N语法top:<s:property value=\"top\"/><br>> N语法取值[0].user.uname:<s:property value=\"[0].user.uname\"/><br>> N语法取值top.user.uname:<s:property value=\"top.user.uname\"/><br>> N语法取值top.name1:<s:property value=\"top.name1\"/><br>> OGNL表达式的特殊符号#$% #符号 首先是可以用来取context栈中的值。另外可用来构建map集合 1234567891011121314<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><%@taglib prefix=\"s\" uri=\"/struts-tags\" %><html><head> <title>Title</title></head><body><h3>编写表单</h3><s:form action=\"\" method=\"POST\"> <s:radio name=\"sex\" list=\"{'男','女'}\"></s:radio> <s:radio name=\"sex\" list=\"#{'0':'男','1':'女'}\"></s:radio></s:form></body></html> %符号 强制字符串解析成ognl表达式,假设#request.msg是从文本框中取的值。 在使用%时如果表达式中的值用’’引起来则不起作用,当作字符串处理。如: %{‘#request.msg’} $符号 在配置文件中使用ognl表达式,如下载的配置文件 123456<action name=\"download1\" class=\"cn.demo2.DownloadAction\"> <result name=\"success\" type=\"stream\"> <param name=\"contentType\">${contentType}</param> <param name=\"contentDisposition\">attachment;filename=${downFilename}</param> </result></action> ","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"struts2","slug":"java/ssh/struts2","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/struts2/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"struts2","slug":"struts2","permalink":"http://blog.xxyxpy.pub/tags/struts2/"}]},{"title":"struts2-02拦截器、请求封装以及结果跳转","slug":"java/struts2/struts2-02拦截器、请求封装以及结果跳转","date":"2017-12-25T07:49:15.118Z","updated":"2017-12-25T07:49:15.151Z","comments":true,"path":"2017/12/25/java/struts2/struts2-02拦截器、请求封装以及结果跳转/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/struts2/struts2-02拦截器、请求封装以及结果跳转/","excerpt":"在Struts2框架中使用Servlet的apiJavaWeb中原先使用的是Servlet作为web层框架,换成使用Struts时也提供了Servlet相关api的访问。访问时主要有两种方式: 使用原生的Servlet框架中提供了ServletActionContext,该类中提供了一些静态的方法,如:getPageContext()、getRequest()、getResponse()、getServletContext() 使用案例:","text":"在Struts2框架中使用Servlet的apiJavaWeb中原先使用的是Servlet作为web层框架,换成使用Struts时也提供了Servlet相关api的访问。访问时主要有两种方式: 使用原生的Servlet框架中提供了ServletActionContext,该类中提供了一些静态的方法,如:getPageContext()、getRequest()、getResponse()、getServletContext() 使用案例: 1234567891011121314151617public class Demo4Action extends ActionSupport { @Override public String execute() throws Exception { // get request HttpServletRequest request = ServletActionContext.getRequest(); // 向域中存入其它对象 // request request.setAttribute(\"msg\", \"request msg\"); // session request.getSession().setAttribute(\"msg\", \"session msg\"); // application request.getServletContext().setAttribute(\"msg\", \"application msg\"); ServletActionContext.getServletContext().setAttribute(\"msg\", \"new application msg\"); return SUCCESS; }} jsp页面 1234567891011<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>Title</title></head><body>${sessionScope.msg} <br>${requestScope.msg} <br>${applicationScope.msg} <br></body></html> 访问结果 完全解耦合的方式指的是不像上面一样直接操作Request等对象,而是通过框架另外提供的ActionContext类来操作。主要的方法有: static ActionContext getContext() – 获取ActionContext对象实例 java.util.Map<java.lang.String,java.lang.Object> getParameters() – 获取请求参数,相当于request.getParameterMap(); java.util.Map<java.lang.String,java.lang.Object> getSession() – 获取的代表session域的Map集合,就相当于操作session域。 java.util.Map<java.lang.String,java.lang.Object> getApplication() – 获取代表application域的Map集合 void put(java.lang.String key, java.lang.Object value) – 注意:向request域中存入值。 使用案例: 12345678910111213141516171819202122public class Demo3Action extends ActionSupport { @Override public String execute() throws Exception { // get action上下文 ActionContext context = ActionContext.getContext(); // get parameters Map<String, Object> parameters = context.getParameters(); // show parameter for (Map.Entry<String, Object> entry : parameters.entrySet()){ System.out.println(entry.getKey() + \"-\" + Arrays.toString((String[]) entry.getValue())); } // 向域中存入其它对象 // request context.put(\"msg\", \"request msg\"); // session context.getSession().put(\"msg\", \"session msg\"); // application context.getApplication().put(\"msg\", \"application msg\"); return SUCCESS; }} 对于ActionContext和ServletActionContext的区别可以查看深入理解 Action 之 ActionContext 和 ServletActionContext 结果页面的跳转两种跳转方式 局部结果页面。指的是一个方法返回对应一个页面,如:<result name="success">demo3/suc.jsp</result> 全局结果页面。针对当前的package中的action返回相同的name并且都跳转到同一个页面时。当局部和全局配置冲突时优先走局部的配置 123456789101112<package name=\"demo3\" namespace=\"/\" extends=\"struts-default\"> <!--当前package下面的全局配置,所有的此name应用于此配置--> <global-results> <result name=\"success\">demo3/suc.jsp</result> </global-results> <action name=\"demo3Action2\" class=\"com.action3.Demo3Action\"> <!--<result name=\"success\">demo3/suc.jsp</result>--> </action> <action name=\"demo4Action\" class=\"com.action3.Demo4Action\"> <!--<result name=\"success\">demo3/suc.jsp</result>--> </action></package> 结果页面类型主要是指action标签中的result标签下的type的属性配置,有以下值: dispatcher – 转发.type的默认值.Action—>JSP redirect – 重定向. Action—>JSP chain – 多个action之间跳转.从一个Action转发到另一个Action. Action—Action redirectAction – 多个action之间跳转.从一个Action重定向到另一个Action. Action—Action stream – 文件下载时候使用的 123456<action name=\"demo5Action_*\" class=\"com.action3.Demo5Action\" method=\"{1}\"> <!--redirectAction 多个action间重定向--> <!--<result name=\"success\" type=\"redirectAction\">demo5Action_update</result>--> <!--redirectAction 多个action间请求转发--> <result name=\"success\" type=\"chain\">demo5Action_update</result></action> 数据封装之前数据的获取是通过request的方法获取,效率比较低。使用框架后可以实现自动封装到对象。并且支持对集合的封装。Struts2提供了两种数据封装的方式: 属性驱动 提供对应属性的set方法进行数据封装 1234567891011121314151617181920public class Regist1Action extends ActionSupport{ private String username; private String password; private Integer age; public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setAge(Integer age) { this.age = age; } @Override public String execute() throws Exception { System.out.println(username + \"-\" + password + \"-\" + age); return NONE; }} jsp页面 1234567<h3>属性驱动</h3><form action=\"${ pageContext.request.contextPath }/reg/regist1.action\" method=\"post\"> 姓名:<input type=\"text\" name=\"username\" /><br/> 密码:<input type=\"password\" name=\"password\" /><br/> 年龄:<input type=\"text\" name=\"age\" /><br/> <input type=\"submit\" value=\"注册\" /></form> 例用ognl表达式封装到JavaBean 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152public class Regist2Action extends ActionSupport{ // 不初始化user时会框架会自动的new一个实例出来,并通过setUser方法设置值 // 如果初始化好就少调用一次setUser,此时可以不提供setUser方法 private NewUser user = new NewUser(); //public NewUser getUser() { // System.out.println(\"getUser....\"); // return user; //} public void setUser(NewUser user) { System.out.println(\"setUser....\"); this.user = user; } @Override public String execute() throws Exception { System.out.println(user); return NONE; }}public class NewUser { private String username; private String password; private Integer age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return \"NewUser{\" + \"username='\" + username + '\\'' + \", password='\" + password + '\\'' + \", age=\" + age + '}'; }} jsp页面 1234567<h3>属性驱动-封装到javabean对象</h3><form action=\"${ pageContext.request.contextPath }/reg/regist2.action\" method=\"post\"> 姓名:<input type=\"text\" name=\"user.username\" /><br/> 密码:<input type=\"password\" name=\"user.password\" /><br/> 年龄:<input type=\"text\" name=\"user.age\" /><br/> <input type=\"submit\" value=\"注册\" /></form> 模型驱动使用模型驱动时必须要实现ModelDriven接口,实现getModel()的方法,在getModel()方法中返回绑定的JavaBean对象。另外此对象必须手动的实例化 123456789101112public class Regist3Action extends ActionSupport implements ModelDriven<NewUser>{ NewUser user = new NewUser(); @Override public NewUser getModel() { return user; } @Override public String execute() throws Exception { System.out.println(user); return NONE; }} jsp页面 1234567<h3>模型驱动使用ModelDriven接口-封装到javabean对象</h3><form action=\"${ pageContext.request.contextPath }/reg/regist3.action\" method=\"post\"> 姓名:<input type=\"text\" name=\"username\" /><br/> 密码:<input type=\"password\" name=\"password\" /><br/> 年龄:<input type=\"text\" name=\"age\" /><br/> <input type=\"submit\" value=\"注册\" /></form> 封装数据到集合 封装到list集合 1234567891011121314151617public class Regist4Action extends ActionSupport { List<NewUser> list = new ArrayList<>(); public List<NewUser> getList() { return list; } public void setList(List<NewUser> list) { this.list = list; } @Override public String execute() throws Exception { System.out.println(Arrays.toString(list.toArray())); return NONE; }} jsp页面 1234567891011<h3>封装数据到list集合</h3><form action=\"${ pageContext.request.contextPath }/reg/regist4.action\" method=\"post\"> 姓名:<input type=\"text\" name=\"list[0].username\" /><br/> 密码:<input type=\"password\" name=\"list[0].password\" /><br/> 年龄:<input type=\"text\" name=\"list[0].age\" /><br/> <br> 姓名:<input type=\"text\" name=\"list[1].username\" /><br/> 密码:<input type=\"password\" name=\"list[1].password\" /><br/> 年龄:<input type=\"text\" name=\"list[1].age\" /><br/> <input type=\"submit\" value=\"注册\" /></form> 封装数据到map集合 12345678910111213141516171819public class Regist5Action extends ActionSupport { Map<String, NewUser> map; public Map<String, NewUser> getMap() { System.out.println(\"getMap ...\"); return map; } public void setMap(Map<String, NewUser> map) { System.out.println(\"setMap ...\"); this.map = map; } @Override public String execute() throws Exception { System.out.println(map); return NONE; }} jsp页面 1234567891011<h3>封装数据到map集合</h3><form action=\"${ pageContext.request.contextPath }/reg/regist5.action\" method=\"post\"> 姓名:<input type=\"text\" name=\"map['first'].username\" /><br/> 密码:<input type=\"password\" name=\"map['first'].password\" /><br/> 年龄:<input type=\"text\" name=\"map['first'].age\" /><br/> <br> 姓名:<input type=\"text\" name=\"map['second'].username\" /><br/> 密码:<input type=\"password\" name=\"map['second'].password\" /><br/> 年龄:<input type=\"text\" name=\"map['second'].age\" /><br/> <input type=\"submit\" value=\"注册\" /></form> 拦截器什么是拦截器拦截器是可以在方法或对象被访问前后执行的一些操作。比如要访问action类,通过拦截器可以实现访问前后的一些操作,比较典型的应用如页面访问的权限控制。在struts2框架中提供了各种默认的拦截器,可以在struts2的jar包内的struts-default.xml查看关于默认的拦截器与拦截器链的配置 拦截器 采用 责任链 模式 在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链责任链每一个节点,都可以继续调用下一个节点,也可以阻止流程继续执行 在struts2 中可以定义很多个拦截器,将多个拦截器按照特定顺序 组成拦截器栈 (顺序调用 栈中的每一个拦截器 ) 更多内容访问Struts2拦截器 拦截器和过滤器的区别 拦截器是基于JAVA反射机制的,而过滤器是基于函数回调的 过滤器依赖于Servlet容器,而拦截器不依赖于Servlet容器 拦截器只能对Action请求起作用(Action中的方法),而过滤器可以对几乎所有的请求起作用(CSS JSP JS) 自定义拦截器 首先必须要实现Interceptor接口,重写它的intercept方法 123456789public class DemoInterceptor extends AbstractInterceptor { @Override public String intercept(ActionInvocation actionInvocation) throws Exception { System.out.println(\"before action...\"); String result = actionInvocation.invoke(); System.out.println(\"after action...\"); return result; }} 然后在struts.xml中进行配置,将拦截器与action进行关联 支持两种方式的配置,配置时注意必须配置默认栈的拦截器。 因为引入自定义拦截器后默认栈的拦截器就不执行了 配置方式一 123456789101112<package name=\"interceptor\" namespace=\"/\" extends=\"struts-default\"> <!--定义拦截器方式一--> <interceptors> <interceptor name=\"DemoInterceptor\" class=\"com.interceptor.DemoInterceptor\"/> </interceptors> <action name=\"interaction\" class=\"com.action.interceptor.InterAction\"> <interceptor-ref name=\"DemoInterceptor\"/> <!--引入默认栈的拦截器栈--> <interceptor-ref name=\"defaultStack\"/> </action></package> 配置方式二:定义拦截器栈 1234567891011121314<package name=\"interceptor\" namespace=\"/\" extends=\"struts-default\"> <!--定义拦截器方式二:定义拦截器栈--> <interceptors> <interceptor name=\"DemoInterceptor\" class=\"com.interceptor.DemoInterceptor\"/> <interceptor-stack name=\"myStack\"> <interceptor-ref name=\"DemoInterceptor\"/> <interceptor-ref name=\"defaultStack\"/> </interceptor-stack> </interceptors> <action name=\"interaction\" class=\"com.action.interceptor.InterAction\"> <!--拦截器定义方式二:引入拦截器栈--> <interceptor-ref name=\"myStack\"/> </action></package> InterAction.java 1234567public class InterAction extends ActionSupport { @Override public String execute() throws Exception { System.out.println(\"action execute ...\"); return NONE; }} 访问执行结果为 拦截器设置属性值拦截器中定义了一些属性可以在配置文件中进行注入,比如在文件上传拦截器中可以定义允许上传的文件的类型,定义如下: 12345<!--引入默认拦截器--><interceptor-ref name=\"defaultStack\"> <!--设置上传文件类型,fileUpload为拦截器名称,allowedExtensions为其属性--> <param name=\"fileUpload.allowedExtensions\">.jpg,.png,.txt</param></interceptor-ref>","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"struts2","slug":"java/ssh/struts2","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/struts2/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"struts2","slug":"struts2","permalink":"http://blog.xxyxpy.pub/tags/struts2/"}]},{"title":"struts2-01入门","slug":"java/struts2/struts2-01入门","date":"2017-12-25T07:49:10.377Z","updated":"2017-12-25T07:49:10.392Z","comments":true,"path":"2017/12/25/java/struts2/struts2-01入门/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/struts2/struts2-01入门/","excerpt":"框架概述 Struts2是一个基于MVC设计模式的Web层框架,通过前端控制器(过滤器)进行请求拦截过滤 Struts2是Struts1的下一代产品,是在 struts1和WebWork的技术基础上进行了合并的全新的Struts2框架 Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以Struts 2可以理解为WebWork的更新产品 环境准备 下载开发包 到官网去下载,或度盘(http://pan.baidu.com/s/1dFb8mm1 密码: m3y4)","text":"框架概述 Struts2是一个基于MVC设计模式的Web层框架,通过前端控制器(过滤器)进行请求拦截过滤 Struts2是Struts1的下一代产品,是在 struts1和WebWork的技术基础上进行了合并的全新的Struts2框架 Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以Struts 2可以理解为WebWork的更新产品 环境准备 下载开发包 到官网去下载,或度盘(http://pan.baidu.com/s/1dFb8mm1 密码: m3y4) 解压struts-2.3.24-all.zip包,解压后文件如下 apps 框架提供的一些应用 libs 框架开发的jar包 docs 框架开发文档 src 框架源码 引入开发的jar包 发布产品中的jar包非常多,但并不是所有的都必须引入,必须引用的jar可以在apps中找到。找到apps目录下面的struts2-blank.war应用,解压后复制WEB-INF/lib目录下所有的jar包 Hello World 新建Action类 传统web中浏览器发起请求后进入servlet,在struts框架中先被拦截器拦截,然后进行相应的Action类处理。所以Action类是Struts处理请求, 封装数据的核心类。 123456public class HelloAction { public String sayHello(){ System.out.println(\"hello...\"); return \"ok\"; }} 配置struts.xml 配置文件名必须是struts.xml,并且位于src目录下 123456789101112<?xml version=\"1.0\" encoding=\"UTF-8\" ?><!DOCTYPE struts PUBLIC \"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN\" \"http://struts.apache.org/dtds/struts-2.3.dtd\"><struts> <package name=\"default\" namespace=\"/\" extends=\"struts-default\"> <action name=\"hello\" class=\"com.action.HelloAction\" method=\"sayHello\"> <result name=\"ok\">/demo1/suc.jsp</result> </action> </package></struts> suc.jsp 123456789<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %><html><head> <title>Title</title></head><body>jump ok..</body></html> 配置web.xml 必须要在web.xml中配置Struts2的前端控制器,只有经过前端控制器的拦截才能进入到Action类中执行。 注意:struts2的不同版本过滤器类可能不同 1234567891011121314<?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\"> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping></web-app> 访问 部署好之后访问http://localhost:端口号/应用名/hello.action Struts2的执行流程 在浏览器中访问相应的链接,发送请求到服务器端 请求经过Struts2的核心过滤器(StrutsPrepareAndExecuteFilter) 过滤器拦截完成之后,会根据struts.xml的配置文件找到请求的路径,定位到具体的Action类,通过反射的方式调用相应的方法 方法调用完成之后根据返回值再到struts.xml的配置文件找到跳转或重定向的页面 总结:页面–>StrutsPrepereAndExecuteFilter过滤器–>执行一系列拦截器(完成了部分代码)–>执行到目标Action–>返回字符串–>结果页面(result)–>页面跳转 Struts2框架配置文件的加载顺序 Struts2框架的核心是StrutsPrepareAndExecuteFilter过滤器。查看该类的doFilter方法就能发现该过滤器有两个功能: Prepare – 预处理,加载核心的配置文件 Execute – 执行,让部分拦截器执行 加载哪些配置文件以及加载顺序 查看Dispatcher类中的init方法会发现如下代码 1234567init_FileManager();init_DefaultProperties(); // [1]init_TraditionalXmlConfigurations(); // [2]init_LegacyStrutsProperties(); // [3]init_CustomConfigurationProviders(); // [5]init_FilterInitParameters() ; // [6]init_AliasStandardObjects() ; // [7] init_DefaultProperties();加载org/apache/struts2/default.properties init_TraditionalXmlConfigurations();加载struts-default.xml,struts-plugin.xml,struts.xml init_LegacyStrutsProperties();加载自定义的struts.properties. init_CustomConfigurationProviders();加载用户自定义配置提供者 init_FilterInitParameters();加载web.xml 需要重点关注的配置文件 default.properties 在org/apache/struts2/目录下,代表的是配置的是Struts2的常量的值 struts-default.xml 在Struts2的核心包下,代表的是Struts2核心功能的配置(Bean、拦截器、结果类型等) struts.xml 重点中的重点配置,代表WEB应用的默认配置,在工作中,基本就配置它就可以了!!(可以配置常量,达到对默认值覆盖的目的) web.xml 配置前端控制器(可以配置常量) 注意: default.properties、struts-default.xml以及struts-plugin.xml是框架的默认配置,不用修改。 struts-default.xml、struts.xml以及web.xml这三个配置文件可以自己修改struts2的常量。但是有一个特点:后加载的配置文件修改的常量的值,会覆盖掉前面修改的常量的值 总结 先加载default.properties文件,在org/apache/struts2/default.properties文件,都是常量。 又加载struts-default.xml配置文件,在核心的jar包最下方,struts2框架的核心功能都是在该配置文件中配置的。 再加载struts.xml的配置文件,在src的目录下,代表用户自己配置的配置文件 最后加载web.xml的配置文件 后加载的配置文件会覆盖掉之前加载的配置文件(在这些配置文件中可以配置常量) struts.xml配置文件配置说明 <package>标签,如果要配置<action>的标签,必须要先配置<package>标签,代表包的概念 name 包的名称,要求是唯一的,管理action配置 extends 继承,可以继承其他的包,只要继承了,那么该包就包含了其他包的功能,一般都是继承struts-default。比如在Apackage中定义了包级错误页跳转,B包想也走这个配置的话需要在extends中写上A包的name namespace 名称空间,一般与<action>标签中的name属性共同决定访问路径(通俗话:怎么来访问action),常见的配置有:namespace=”/“,根名称空间;namespace=”/aaa”,带有名称的名称空间;abstract 抽象的。这个属性基本很少使用,值如果是true,那么编写的包是被继承的 <action>标签,代表配置action类包含的属性 name 和package标签的namespace属性一起来决定访问路径的 class 配置Action类的全路径(默认值是ActionSupport类) method Action类中执行的方法,如果不指定,默认值是execute <result>标签,action类中方法执行,返回的结果跳转的页面。result参数详解 name 结果页面逻辑视图名称 type 结果类型(默认值是转发,也可以设置其他的值如重定向) Struts2常量配置 可以配置常量的页面 struts.xml 一般在此页面配置,在根节点struts的下一级配置。<constant name="key" value="value"></constant> web.xml 比较少在此页面配置。如果配置需要在StrutsPrepareAndExecuteFilter下面配置init-param节点,在其中配置初始化参数 需要了解的常量 eg:<constant name="struts.devMode" value="true" /> struts.i18n.encoding=UTF-8 – 指定默认编码集,作用于HttpServletRequest的setCharacterEncoding方法 struts.action.extension=action – 该属性指定需要Struts 2处理的请求后缀,该属性的默认值是action,即所有匹配*.action的请求都由Struts2处理。如果用户需要指定多个请求后缀,则多个后缀之间以英文逗号(,)隔开 struts.serve.static.browserCache=true – 设置浏览器是否缓存静态内容,默认值为true(生产环境下使用),开发阶段最好关闭 struts.configuration.xml.reload=false – 当struts的配置文件修改后,系统是否自动重新加载该文件,默认值为false(生产环境下使用) struts.devMode = false – 开发模式下使用,这样可以打印出更详细的错误信息 struts.enable.DynamicMethodInvocation = true 是否开启动态方法访问 指定多个配置文件在大部分应用里,随着应用规模的增加,系统中Action的数量也会大量增加,导致struts.xml配置文件变得非常臃肿。为了避免struts.xml文件过于庞大、臃肿,提高struts.xml文件的可读性,我们可以将一个struts.xml配置文件分解成多个配置文件,然后在struts.xml文件中包含其他配置文件。 1234<include file=\"struts2/struts-sysadmin.xml\"/><!--引用外部的配置文件--><include file=\"com/action/struts_part1.xml\"/><include file=\"struts-part2.xml\"/> Action类的编写 直接定义类,无继承无接口实现 实现Action接口。此接口中定义了五个常量,和一个方法execute 继承ActionSupport类,一般开发中多用此方案。此类中也实现了上项的Action接口 Action类的访问关于访问的后缀名上面也提到过,默认是.action如果要修改需要配置一下常量值。如: 1234<struts> <!--设置访问的后缀名,默认为action和空,添加do--> <constant name=\"struts.action.extension\" value=\"do,action,,\" /></struts> 通过action标签中的name属性配置 配置此属性后访问时会查询到method属性上定义的方法执行。 访问代码: <a href="${pageContext.request.contextPath}/addBook.action">添加图书</a> 配置 123<package name=\"demo2\" extends=\"struts-default\" namespace=\"/\"> <action name=\"addBook\" class=\"cn.demo2.BookAction\" method=\"add\"></action> <action name=\"deleteBook\" class=\"cn.demo2.BookAction\" method=\"delete\"></action> </package> 通过通配符访问 此种方式在开发过程中最为常用。通配符是指使用*代表任意的字符。 使用通配符的方式可以简化配置文件的代码编写,而且扩展和维护比较容易。 访问代码:<a href="${pageContext.request.contextPath}/order_add.action">添加订单</a> add即表示通配的内容,对应的访问的是相应action中的add方法。 配置 1<action name=\"order_*\" class=\"cn.demo2.OrderAction\" method=\"{1}\"></action> 通过动态方法访问 如果要开启动态方法访问必须要开启一个常量值,把struts.enable.DynamicMethodInvocation设置为true 访问代码:<a href="${pageContext.request.contextPath}/product!add.action">添加商品</a> 配置 1<action name=\"product\" class=\"cn.demo2.ProductAction\"></action> ","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"struts2","slug":"java/ssh/struts2","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/struts2/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"struts2","slug":"struts2","permalink":"http://blog.xxyxpy.pub/tags/struts2/"}]},{"title":"Jdbc-03工具类DbUtils","slug":"java/jdbc/Jdbc-03工具类DbUtils","date":"2017-12-25T07:49:00.236Z","updated":"2017-12-25T07:49:00.453Z","comments":true,"path":"2017/12/25/java/jdbc/Jdbc-03工具类DbUtils/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/jdbc/Jdbc-03工具类DbUtils/","excerpt":"DbUtils简介DbUtils是同Apache组织开源的一个jdbc操作工具,可以简化对jdbc的操作 可以把结果转化为List,Array,Set等java集合 数据库连接自动关闭 准备引入commons-dbutils-1.4.jar","text":"DbUtils简介DbUtils是同Apache组织开源的一个jdbc操作工具,可以简化对jdbc的操作 可以把结果转化为List,Array,Set等java集合 数据库连接自动关闭 准备引入commons-dbutils-1.4.jar 增删改主要有操作通过QueryRunner类来完成,初始化时需要一个DataSource(连接池),正好可以用上面的c3p0提供的. 12345678@Testpublic void insert() throws SQLException { // DataSource ds = DataSourceUtils.getDataSource(); QueryRunner qr = new QueryRunner(ds); String sql = \"insert into category values(?,?)\"; qr.update(sql, \"c027\", \"hhe\");} update方法有多个重载,传入sql和参数的这个最常用 连接的自动关闭可以看到这里并不需要手动的关闭连接,查看源码会发现连接是被工具自动关闭了.当然是否自动关闭连接是一个可选项,以调用update方法的时候可以传入 12345678910111213141516private int update(Connection conn, boolean closeConn, String sql, Object... params) throws SQLException { ... try { stmt = this.prepareStatement(conn, sql); this.fillStatement(stmt, params); rows = stmt.executeUpdate(); } catch (SQLException e) { this.rethrow(e, sql, params); } finally { close(stmt); if (closeConn) { close(conn); } } return rows; } 查询数据DbUtils的强大之处在增删改的时候体现的不是特别明显,查询时自动绑定到JavaBean才是它的强大之处.主要支持以下几种结果集: ArrayHandler, 将查询结果的第一条记录封装成数组,返回 ArrayListHandler, 将查询结果的每一条记录封装成数组,将每一个数组放入list中返回 BeanHandler, 将查询结果的第一条记录封装成指定的bean对象,返回 BeanListHandler, 将查询结果的每一条记录封装成指定的bean对象,将每一个bean对象放入list中 返回. ColumnListHandler, 将查询结果的指定一列放入list中返回 MapHandler, 将查询结果的第一条记录封装成map,字段名作为key,值为value 返回 MapListHandler, 将查询结果的每一条记录封装map集合,将每一个map集合放入list中返回 ScalarHandler,针对于聚合函数 例如:count(*) 返回的是一个Long值 其中加粗的四个最为常用.下面演示查询单条和多个的使用 JavaBean创建123456789101112131415161718192021222324252627282930313233public class Category { private String cId; private String cName; public Category(String cId, String cName) { this.cId = cId; this.cName = cName; } public Category() { } public String getcId() { return cId; } public void setcId(String cId) { this.cId = cId; } public String getcName() { return cName; } public void setcName(String cName) { this.cName = cName; } @Override public String toString() { return \"cId:\" + this.cId + \";cName:\" + this.cName; }} 查询单个记录12345678@Testpublic void querySingle() throws SQLException { DataSource ds = DataSourceUtils.getDataSource(); QueryRunner qr = new QueryRunner(ds); String sql = \"select * from category where cId = ?\"; Category category = qr.query(sql, new BeanHandler<>(Category.class), \"c001\"); System.out.println(category);} cId:c001;cName:电器 查询列表数据12345678910@Testpublic void queryList() throws SQLException { DataSource ds = DataSourceUtils.getDataSource(); QueryRunner qr = new QueryRunner(ds); String sql = \"select * from category\"; List<Category> list = qr.query(sql, new BeanListHandler<>(Category.class)); for (Category c : list) { System.out.println(c); }} cId:c001;cName:电器cId:c002;cName:服饰cId:c003;cName:化妆品cId:c004;cName:书籍cId:c015;cName:裤子cId:c023;cName:衣服cId:c025;cName:裤子cId:c027;cName:hhe 总结DbUtils封装jdbc操作主要通过QueryRunner类和ResultSetHandler接口的实现类.提供set,map,list等多个绑定方式.并自动整个c3p0连接池,自动关闭连接.不仅提升开发效率,对于程序执行效率也无太大的影响.","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"jdbc","slug":"java/jdbc","permalink":"http://blog.xxyxpy.pub/categories/java/jdbc/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"jdbc","slug":"jdbc","permalink":"http://blog.xxyxpy.pub/tags/jdbc/"}]},{"title":"Jdbc-02连接池","slug":"java/jdbc/Jdbc-02连接池","date":"2017-12-25T07:48:48.165Z","updated":"2017-12-25T07:48:48.198Z","comments":true,"path":"2017/12/25/java/jdbc/Jdbc-02连接池/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/jdbc/Jdbc-02连接池/","excerpt":"为什么需要连接池根本原因是由于数据库连接的打开和关闭开销比较大,如果每次使用时都不断的开关比较消耗资源.通过连接池去管理一组已经创建好的连接池,需要时就从中去取,使用完成后就归还回来.这样可以极大的提高程序的执行效率. 自己实现简易连接池使用静态代理的方式来实现一个自己的连接池.大概的思路是: 创建MyConnection实现Connection接口并且持有连接池集合的引用,重写close方法在执行close时执行连接还回操作; 另外添加一个连接池工具MyConnectionPool来管理所有的连接; 在使用JdbcUtils进行close操作时即会调用MyConnection的close方法.","text":"为什么需要连接池根本原因是由于数据库连接的打开和关闭开销比较大,如果每次使用时都不断的开关比较消耗资源.通过连接池去管理一组已经创建好的连接池,需要时就从中去取,使用完成后就归还回来.这样可以极大的提高程序的执行效率. 自己实现简易连接池使用静态代理的方式来实现一个自己的连接池.大概的思路是: 创建MyConnection实现Connection接口并且持有连接池集合的引用,重写close方法在执行close时执行连接还回操作; 另外添加一个连接池工具MyConnectionPool来管理所有的连接; 在使用JdbcUtils进行close操作时即会调用MyConnection的close方法. MyConnection类 12345678910111213141516171819public class MyConnection implements Connection { // 连接池集合的引用 private final LinkedList<Connection> list; // 当前连接 private final Connection con; public MyConnection(Connection con, LinkedList<Connection> list) { // 通过构造函数进行注入 this.con = con; this.list = list; } @Override public void close() throws SQLException { list.addLast(this); System.out.println(\"归还一个Connection,还有:\" + list.size()); } 省略其它实现的方法,都直接调用默认实现...} MyConnectionPool类 1234567891011121314151617181920212223242526272829303132public class MyConnectionPool { // 使用LinekedList而非ArrayList,因为LinkedList使用链表实现添加和删除效率高 private final static LinkedList<Connection> list = new LinkedList<>(); // 静态初始化,默认生成3个,JdbcUtils工具类在上一篇中 static { for (int i = 0; i < 3; i++) { Connection con = JdbcUtils.getConnection02(); list.addLast(con); } } /** * 返回一个连接,考虑并发 * @return */ public static synchronized Connection getConnection(){ if (list.isEmpty()){ addConnection(3); } Connection con = list.removeFirst(); MyConnection myCon = new MyConnection(con, list); System.out.println(\"取出一个Connection,还有:\" + list.size()); return myCon; } private static void addConnection(int count){ for (int i = 0; i < count; i++) { Connection con = JdbcUtils.getConnection02(); list.addLast(con); } }} 使用JUnit测试 12345678910111213141516171819@Testpublic void delete2(){ Connection con = MyConnectionPool.getConnection(); PreparedStatement statement = null; try { statement = con.prepareStatement(\"DELETE FROM category where cid = ?\"); statement.setObject(1, \"aa\"); int result = statement.executeUpdate(); if (result > 0){ System.out.println(\"success\"); } } catch (SQLException e) { e.printStackTrace(); }finally { // close方法调用时实际上是调用了MyConnection中的close方法 JdbcUtils.close(con, statement); }} 取出一个Connection,还有:2归还一个Connection,还有:3 从输出结果可以看到连接池集合中一直存在多个连接,通过链表每次取还.可以在取出时判断是否取空,如果取空了再执行创建操作.在还回时也可以判断是否过多,超过一定数量时也可以直接执行连接关闭.第三方连接池类库如dbcp,c3p0也是使用的这样的思想. dbcp特点 apache组织提供的连接池产品 使用时需要引入commons-dbcp-1.4.jar和commons-pool-1.5.6.jar 连接配置信息可以硬编码也可以通过配置文件进行配置 所有连接需要手动关闭 配置在src目录下创建配置文件dbcp.properties 1234driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/mytestusername=rootpassword=root 使用DataSource为其所提供的连接池对象 1234567891011121314151617public class DbcpUse { @Test public void test() throws Exception { Properties properties = new Properties(); properties.load(new FileInputStream(\"src/dbcp.properties\")); DataSource dataSource = new BasicDataSourceFactory().createDataSource(properties); // 获取 Connection con = dataSource.getConnection(); String sql = \"INSERT INTO category VALUES(?,?)\"; PreparedStatement statement = con.prepareStatement(sql); statement.setObject(1, \"c023\"); statement.setObject(2, \"衣服\"); statement.executeUpdate(); // 关闭 JdbcUtils.close(con, statement); }} c3p0特点 推荐使用,相比较dbcp使用的更广泛 具有自动回收空闲连接的功能 连接配置信息可以硬编码也可以通过配置文件进行配置,配置文件也支持xml 配置支持配置文件和xml文件两种配置方式,配置文件名称必须为c3p0.一般使用xml方式配置,因为此种配置方式支持多数据源 c3p0.properties 1234c3p0.driverClass=com.mysql.jdbc.Driverc3p0.jdbcUrl=jdbc:mysql://localhost:3306/mytestc3p0.user=rootc3p0.password=root c3p0.xml xml方式配置时对于特殊特号需要转义,如&转义成&amp; 也支持配置初始的连接池大小等等 12345678910111213141516171819202122232425262728293031323334<c3p0-config> <default-config> <property name=\"user\">root</property> <property name=\"password\">root</property> <property name=\"driverClass\">com.mysql.jdbc.Driver</property> <property name=\"jdbcUrl\">jdbc:mysql://localhost:3306/mytest?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false</property> <property name=\"initialPoolSize\">10</property> <property name=\"maxIdleTime\">30</property> <property name=\"maxPoolSize\">100</property> <property name=\"minPoolSize\">10</property> </default-config> <named-config name=\"myApp\"> <property name=\"user\">root</property> <property name=\"password\">root</property> <property name=\"driverClass\">com.mysql.jdbc.Driver</property> <property name=\"jdbcUrl\">jdbc:mysql://localhost:3306/jeesite?useSSL=false</property> <property name=\"initialPoolSize\">10</property> <property name=\"maxIdleTime\">30</property> <property name=\"maxPoolSize\">100</property> <property name=\"minPoolSize\">10</property> </named-config> <named-config name=\"store\"> <property name=\"user\">root</property> <property name=\"password\">root</property> <property name=\"driverClass\">com.mysql.jdbc.Driver</property> <property name=\"jdbcUrl\">jdbc:mysql://localhost:3306/store?useSSL=false</property> <property name=\"initialPoolSize\">10</property> <property name=\"maxIdleTime\">30</property> <property name=\"maxPoolSize\">100</property> <property name=\"minPoolSize\">10</property> </named-config></c3p0-config> 使用DataSource工具类 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657public class DataSourceUtils { private static ComboPooledDataSource dataSource = new ComboPooledDataSource(); /** * 获取默认的datasource * @return */ public static DataSource getDataSource() { return dataSource; } /** * 获取默认数据库的连接 * @return * @throws SQLException */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 根据指定的数据库创建datasource * @param dbName 配置文件中配置的数据库名称 * @return */ public static DataSource getDataSource(String dbName) { return new ComboPooledDataSource(dbName); } /** * 根据指定的数据库创建connection * @param dbName 配置文件中配置的数据库名称 * @return * @throws SQLException */ public static Connection getConnection(String dbName) throws SQLException { return getDataSource(dbName).getConnection(); } /** * 关闭连接,Statement或ResultSet * @param closeables */ public static void close(AutoCloseable... closeables) { for (AutoCloseable a : closeables) { if (null == a) { continue; } try { a.close(); } catch (Exception e) { e.printStackTrace(); } } }} 测试使用 12345678910111213141516171819202122232425262728293031public class C3p0Use { /** * 不使用工具类 */ @Test public void test() throws Exception { ComboPooledDataSource cds = new ComboPooledDataSource(); Connection con = cds.getConnection(); String sql = \"INSERT INTO category VALUES(?,?)\"; PreparedStatement statement = con.prepareStatement(sql); statement.setObject(1, \"c025\"); statement.setObject(2, \"裤子\"); statement.executeUpdate(); JdbcUtils.close(con, statement); } /** * 使用DataSourceUtils测试 */ @Test public void test2() throws SQLException { Connection con = DataSourceUtils.getConnection(); String sql = \"INSERT INTO category VALUES(?,?)\"; PreparedStatement statement = con.prepareStatement(sql); statement.setObject(1, \"c026\"); statement.setObject(2, \"裤子1\"); statement.executeUpdate(); DataSourceUtils.close(con, statement); }} c3p0初始化时会有一些初始化信息输出 信息: Initializing c3p0 pool… com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000….. 总结解决连接的管理问题后可以使数据库的操作效率更高,但编码工作仍然非常的繁琐.一般情况下都需要以下几步: 创建预编译的Statement 设置参数 执行Statement 关闭连接 执行一般的插入,删除或修改操作时尚可以接受.但如果此时需要获取到一个对象或者是一个列表,还需要对查询结果的ResultSet进行遍历,比较麻烦.下面考虑使用Apache组织提供的工具dbutils来解决这些问题.","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"jdbc","slug":"java/jdbc","permalink":"http://blog.xxyxpy.pub/categories/java/jdbc/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"jdbc","slug":"jdbc","permalink":"http://blog.xxyxpy.pub/tags/jdbc/"}]},{"title":"Jdbc-01基础","slug":"java/jdbc/Jdbc-01基础","date":"2017-12-25T07:48:39.535Z","updated":"2018-02-09T09:46:06.626Z","comments":true,"path":"2017/12/25/java/jdbc/Jdbc-01基础/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/jdbc/Jdbc-01基础/","excerpt":"一个查询功能创建表结构12345678create table category( cid varchar(20) primary key, cname varchar(20));insert into category values('c001','电器');insert into category values('c002','服饰');insert into category values('c003','化妆品');insert into category values('c004','书籍');","text":"一个查询功能创建表结构12345678create table category( cid varchar(20) primary key, cname varchar(20));insert into category values('c001','电器');insert into category values('c002','服饰');insert into category values('c003','化妆品');insert into category values('c004','书籍'); 数据查询首先需要在lib里面添加mysql的数据库驱动,如:mysql-connector-java-5.1.39-bin.jar 1234567891011121314151617181920212223@Testpublic void query() throws Exception{ //注册驱动 Class.forName(\"com.mysql.jdbc.Driver\"); //获取连接 Connection conn=DriverManager.getConnection(\"jdbc:mysql://localhost:3306/mytest\", \"root\", \"root\"); //编写sql String sql=\"select * from category WHERE cid = ?\"; //创建语句执行者 PreparedStatement st=conn.prepareStatement(sql); //设置参数 st.setString(1, \"c001\"); //执行sql ResultSet rs=st.executeQuery(); //处理结果 while(rs.next()){ System.out.println(rs.getString(\"cid\")+\"::\"+rs.getString(\"cname\")); } //释放资源. rs.close(); st.close(); conn.close();} c001::电器 驱动加载的问题另外一种注册驱动的方式: 1DriverManager.registerDriver(new com.mysql.jdbc.Driver()); 其实本质上Class.forName("com.mysql.jdbc.Driver")也是调用的上述方法.查看Driver类的源码会发现有静态代码块,静态代码块会在类首次加载时执行,并且只执行一次. 123456789101112public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException(\"Can't register driver!\"); } }} java类加载的三种方式 12345678// 1.Class.forName(全限定名)Class<String> stringClass;stringClass = Class.forName(\"java.lang.String\");// 2.通过类stringClass = String.class;// 3.通过实例String str = \"test\";stringClass = str.getClass(); 工具类的抽取初级版本每次查询时都要去重新注册驱动显然是不合理的,可以将驱动注册和获取连接抽取成工具类. 12345678910111213141516171819public class JdbcUtils { static { try { //加载驱动 Class.forName(\"com.mysql.jdbc.Driver\"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() { Connection con = null; try { con = DriverManager.getConnection(\"jdbc:mysql://localhost:3306/mytest\", \"root\", \"root\"); } catch (Exception e) { e.printStackTrace(); } return con; }} 这样每次调用静态方法getConnection()就能拿到一个connection,而且多次获取连接时驱动也只注册一次.但是会产生另外的一个问题:耦合. 这样设置的话驱动类和数据库连接相关内容就和代码耦合了,以后切换数据库(扯淡,基本不会换库,从没经历过)和换数据库连接就需要改代码.显然不能忍,所以要考虑通过配置的方式获取 升级版本src目录下添加db.properties配置文件,内容如下: 1234driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/mytestusername=rootpassword=root 修改后的工具类: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556public class JdbcUtils { private static final String DRIVER_CLASS_NAME; private static final String URL; private static final String USERNAME; private static final String PASSWORD; //读取配置文件内容 static { ResourceBundle rb = ResourceBundle.getBundle(\"db\"); DRIVER_CLASS_NAME = rb.getString(\"driverClassName\"); URL = rb.getString(\"url\"); USERNAME = rb.getString(\"username\"); PASSWORD = rb.getString(\"password\"); } static { try { //加载驱动 Class.forName(DRIVER_CLASS_NAME); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection02(){ try { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } catch (SQLException e) { e.printStackTrace(); } return null; } public static Connection getConnection01() { Connection con = null; try { con = DriverManager.getConnection(\"jdbc:mysql://localhost:3306/mytest\", \"root\", \"root\"); } catch (Exception e) { e.printStackTrace(); } return con; } /** * 关闭连接,Statement或ResultSet * @param closeables */ public static void close(AutoCloseable... closeables){ for (AutoCloseable a : closeables) { if (null == a){ continue; } try { a.close(); } catch (Exception e) { e.printStackTrace(); } } }} 配置文件的读取这里是通过ResourceBundle类来进行配置读取,当然也可以通过Properties类完成配置的读取: 12345678Properties pro = new Properties();InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(\"db.properties\");try { pro.load(is); String className = pro.get(\"driverClassName\");} catch (IOException e) { e.printStackTrace();} 非托管资源的关闭工具类中添加了一个静态的close方法,参数为可变参数(实际上是一个数组),传入的参数必须直接或间接实现AutoCloseable接口.关于非jvm管理的资源(文件资源,数据库资源,网络资源…)都需要手动关闭. Connection和PreparedStatement都实现了AutoCloseable接口.下面通过工具类来完成插入,添加以及修改功能. 插入数据12345678910111213141516171819202122@Testpublic void insert() { Connection con = null; PreparedStatement st = null; try { con = JdbcUtils.getConnection02(); String sql = \"insert into category values (?, ?)\"; st = con.prepareStatement(sql); st.setString(1, \"手机\"); st.setString(2, \"c006\"); int i = st.executeUpdate(); if (1 == i){ System.out.println(\"ok\"); } else { System.out.println(\"fail\"); } } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtils.close(con, st); }} ok 更新数据更新,插入和删除数据除了sql语句不同外,其它处理方式基本相同 12345678910111213141516171819202122@Testpublic void update() { Connection con = null; PreparedStatement st = null; try { con = JdbcUtils.getConnection02(); String sql = \"update category set cname = ? where cid = ?\"; st = con.prepareStatement(sql); st.setString(1, \"手机\"); st.setString(2, \"c006\"); int i = st.executeUpdate(); if (1 == i){ System.out.println(\"ok\"); } else { System.out.println(\"fail\"); } } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtils.close(con, st); }} ok 删除数据123456789101112131415161718192021@Testpublic void delete() { Connection con = null; PreparedStatement st = null; try { con = JdbcUtils.getConnection02(); String sql = \"DELETE from category where cid = ?\"; st = con.prepareStatement(sql); st.setString(1, \"c006\"); int i = st.executeUpdate(); if (1 == i){ System.out.println(\"ok\"); } else { System.out.println(\"fail\"); } } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtils.close(con, st); }} ok 其它sql中的参数问题 除了setXX设置具体类型外还可以通过setObject方式进行设置,不需要关注具体的类型 ResultSet数据获取 通过getXX获取数据时除了可以通过列名还可以通过索引来获取 总结Jdbc的体系是非常庞大的,但正常使用时基本用到的CRUD也就是这些了.工具类中的数据库连接每次使用时都会创建和关闭,而连接池的创建是比较消耗资源的,此时就需要考虑如何对连接进行管理","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"jdbc","slug":"java/jdbc","permalink":"http://blog.xxyxpy.pub/categories/java/jdbc/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"jdbc","slug":"jdbc","permalink":"http://blog.xxyxpy.pub/tags/jdbc/"}]},{"title":"hibernate-07HQL和QBC查询","slug":"java/hibernate/hibernate-07HQL和QBC查询","date":"2017-12-25T07:48:34.466Z","updated":"2017-12-25T07:48:34.509Z","comments":true,"path":"2017/12/25/java/hibernate/hibernate-07HQL和QBC查询/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/hibernate/hibernate-07HQL和QBC查询/","excerpt":"Hibernate框架的查询方式 唯一标识OID查询。session.get(对象.class,OID) 对象导航方式,由一方加载另一方时 HQL检索方式 QBC检索方式 SQL检索方式(不常用)","text":"Hibernate框架的查询方式 唯一标识OID查询。session.get(对象.class,OID) 对象导航方式,由一方加载另一方时 HQL检索方式 QBC检索方式 SQL检索方式(不常用) HQL查询介绍HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似。在hibernate的各种查询方式中hql查询使用的最为广泛。 HQL与SQL的关系 HQL 查询语句是面向对象的,Hibernate负责解析HQL查询语句, 然后根据对象-关系映射文件中的映射信息, 把 HQL 查询语句翻译成相应的 SQL 语句. HQL 查询语句中的主体是域模型中的类及类的属性 SQL 查询语句是与关系数据库绑定在一起的. SQL查询语句中的主体是数据库表及表的字段 HQL查询演示 基本查询 12345678910@Testpublic void run1() { Session session = HibernateUtils.openSession(); Query query = session.createQuery(\"from Customer \"); List<Customer> list = query.list(); for (Customer c : list) { System.out.println(c); } session.close();} 也可以直接链式编程 1List<Customer> list = session.createQuery(\"from Customer \").list(); 使用别名查询 12session.createQuery(\"from Customer c\").list();session.createQuery(\"select c from Customer c\").list(); 排序查询 123session.createQuery(\"from Customer order by cust_id\").list();// 升序session.createQuery(\"from Customer order by cust_id desc\").list();// 降序session.createQuery(\"select c from Customer c order by c.cust_name desc\").list(); 分页查询 不同数据库的分页查询可以自动判断 123456789101112@Testpublic void run5() { Session session = HibernateUtils.openSession(); Query query = session.createQuery(\"from Customer c order by c.cust_name asc\"); query.setFirstResult(1); // 第0条开始 query.setMaxResults(3); // 获取3条数据 List<Customer> list = query.list(); for (Customer c : list) { System.out.println(c); } session.close();} 带条件查询 123456789101112131415@Testpublic void run6() { Session session = HibernateUtils.openSession(); Query query = session.createQuery(\"from Customer c where c.cust_name like ? and id > ?\"); //query.setString(0, \"%e%\"); // 索引从0开始 //query.setLong(1, 5); // 索引从0开始 //通用的设置值方法 query.setParameter(0, \"%e%\"); query.setParameter(1, 5L); List<Customer> list = query.list(); for (Customer c : list) { System.out.println(c); } session.close();} 投影查询,只查询几个字段,返回object数组 1234567891011@Testpublic void run7() { Session session = HibernateUtils.openSession(); Query query = session.createQuery(\"select c.cust_name,c.cust_id from Customer c \"); //List<Customer> list = query.list(); // 使用customer类型接收会报错 List<Object[]> list = query.list(); for (Object[] objects : list) { System.out.println(Arrays.toString(objects)); } session.close();} 投影查询,只查询几个字段。 用JavaBean接收,没有查询出来的列在封装的对象中都是null 12345678910@Testpublic void run8() { Session session = HibernateUtils.openSession(); Query query = session.createQuery(\"select new Customer(c.cust_name,c.cust_level) from Customer c \"); List<Customer> list = query.list(); // 此时可以使用customer类型接收 for (Customer c : list) { System.out.println(c); } session.close();} 聚合函数查询 主要有:count(),sum(),avg(),max(),min() 123456789@Testpublic void run9() { Session session = HibernateUtils.openSession(); Query query = session.createQuery(\"select count(*) from Customer c \"); //Query query = session.createQuery(\"select count(c) from Customer c \"); Long l = (Long) query.uniqueResult(); System.out.println(\"total:\" + l); session.close();} QBC查询Query By Criteria 按条件进行查询 基本使用 简单查询 12345678910@Testpublic void run1() { Session session = HibernateUtils.openSession(); Criteria criteria = session.createCriteria(Customer.class); List<Customer> list = criteria.list(); for (Customer c : list){ System.out.println(c); } session.close();} 排序查询 1234567891011@Testpublic void run2() { Session session = HibernateUtils.openSession(); Criteria criteria = session.createCriteria(Customer.class); criteria.addOrder(Order.desc(\"cust_id\")); List<Customer> list = criteria.list(); for (Customer c : list){ System.out.println(c); } session.close();} 分页查询,与HQL分页设置相同 123456789101112@Testpublic void run3() { Session session = HibernateUtils.openSession(); Criteria criteria = session.createCriteria(Customer.class); criteria.setFirstResult(1); criteria.setMaxResults(3); List<Customer> list = criteria.list(); for (Customer c : list){ System.out.println(c); } session.close();} 其它各种条件查询 Restrictions.eq – 相等 Restrictions.gt – 大于号 Restrictions.ge – 大于等于 Restrictions.lt – 小于 Restrictions.le – 小于等于 Restrictions.between – 在之间 Restrictions.like – 模糊查询 Restrictions.in – 范围 Restrictions.and – 并且 Restrictions.or – 或者 123456789101112131415161718192021222324@Testpublic void run4() { Session session = HibernateUtils.openSession(); Criteria criteria = session.createCriteria(Customer.class); criteria.add(Restrictions.like(\"cust_name\", \"%e%\")); criteria.add(Restrictions.ge(\"cust_id\", 5L)); List<Customer> list = criteria.list(); for (Customer c : list){ System.out.println(c); } session.close(); **以下为伪代码** // in List<Long> params = new ArrayList<>(); params.add(1L); params.add(3L); params.add(5L); criteria.add(Restrictions.in(\"cust_id\", params)); // or criteria.add(Restrictions.or(Restrictions.eq(\"cust_id\", 1L), Restrictions.eq(\"cust_id\", 3L))); // null判断 criteria.add(Restrictions.isNull(\"cust_level\"));} 聚合查询 使用criteria.setProjection()方法实现聚合查询,传入Projection接口。 Projections类封装了各种取合方式的接口实现类的构造,使用时直接使用Projections类调用方法返回相应的实现类 123456789101112131415@Testpublic void run9() { Session session = HibernateUtils.openSession(); Criteria criteria = session.createCriteria(Customer.class); criteria.setProjection(Projections.count(\"cust_id\")); Long l = (Long) criteria.uniqueResult(); System.out.println(l); // 使用count*后又做明细查询前需要先清除掉projection criteria.setProjection(null); List<Customer> list = criteria.list(); for(Customer c : list){ System.out.println(c); } session.close();} 离线条件查询离线条件查询使用的是DetachedCriteria接口进行查询,离线条件查询对象在创建的时候,不需要使用Session对象,只是在查询的时候使用Session对象即可。 123456789101112@Testpublic void run10() { // 创建离线查询对象 DetachedCriteria criteria = DetachedCriteria.forClass(Customer.class); Session session = HibernateUtils.openSession(); criteria.add(Restrictions.between(\"cust_id\", 1L, 5000L)); List<Customer> list = criteria.getExecutableCriteria(session).list(); for(Customer c : list){ System.out.println(c); } session.close();} SQL查询在Hibernate中使用sql方式进行查询应用场景较少。Sql查询时可以将结果绑定到实体或Object数组。 封装到对象 123456789101112@Testpublic void run2(){ Session session = HibernateUtils.openSession(); //创建sql查询接口 SQLQuery sqlQuery = session.createSQLQuery(\"select * from cst_customer\"); sqlQuery.addEntity(Customer.class); List<Customer> list = sqlQuery.list(); for (Customer c : list){ System.out.println(c); } session.close();} 封装到Object数组 1234567891011@Testpublic void run1(){ Session session = HibernateUtils.openSession(); //创建sql查询接口 SQLQuery sqlQuery = session.createSQLQuery(\"select * from cst_customer\"); List<Object[]> list = sqlQuery.list(); for (Object[] objs : list){ System.out.println(Arrays.toString(objs)); } session.close();} HQL多表查询 多表的查询进来使用HQL语句进行查询,HQL语句和SQL语句的查询语法比较类似。 内连接查询 显式内连接 select * from customers c inner join orders o on c.cid = o.cno; 隐式内连接 select * from customers c,orders o where c.cid = o.cno; 外连接查询 左外连接 select * from customers c left join orders o on c.cid = o.cno; 右外连接 select * from customers c right join orders o on c.cid = o.cno; HQL的多表查询 非迫切返回结果是Object[] 123456789101112@Testpublic void run1() { Session session = HibernateUtils.openSession(); // 客户和联系人的关联方式 Query query = session.createQuery(\"from Customer c join c.linkmans\"); // 默认返回的是数组 List<Object[]> list = query.list(); for (Object[] objs : list) { System.out.println(Arrays.toString(objs)); } session.close();} 迫切连接返回的结果是对象,把客户的信息封装到客户的对象中 1234567891011121314@Testpublic void run2() { Session session = HibernateUtils.openSession(); // 客户和联系人的关联方式 // 使用fetch 迫切连接 Query query = session.createQuery(\"from Customer c join fetch c.linkmans\"); List<Customer> list = query.list(); //手动解决数据重复问题,使用set集合 Set<Customer> set = new HashSet<>(list); for (Customer c : list) { System.out.println(c); } session.close();} 内连接查询 内连接使用 inner join ,默认返回的是Object数组 1234567Session session = HibernateUtils.getCurrentSession();Transaction tr = session.beginTransaction();List<Object[]> list = session.createQuery(\"from Customer c inner join c.linkmans\").list();for (Object[] objects : list) { System.out.println(Arrays.toString(objects));}tr.commit(); 迫切内连接:inner join fetch ,返回的是实体对象 12345678Session session = HibernateUtils.getCurrentSession();Transaction tr = session.beginTransaction();List<Customer> list = session.createQuery(\"from Customer c inner join fetch c.linkmans\").list();Set<Customer> set = new HashSet<Customer>(list);for (Customer customer : set) { System.out.println(customer);}tr.commit(); 左外连接查询 左外连接: 封装成Object数组,和内连接相同 迫切左外连接 left join fetch,封装成对象,和内连接相同。","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"hibernate","slug":"java/ssh/hibernate","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/hibernate/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"hiberante","slug":"hiberante","permalink":"http://blog.xxyxpy.pub/tags/hiberante/"}]},{"title":"hibernate-06映射关系之多对多","slug":"java/hibernate/hibernate-06映射关系之多对多","date":"2017-12-25T07:48:21.398Z","updated":"2017-12-25T07:48:21.412Z","comments":true,"path":"2017/12/25/java/hibernate/hibernate-06映射关系之多对多/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/hibernate/hibernate-06映射关系之多对多/","excerpt":"多对多单向关联用户和角色是典型的多对多关系。通过数据库去描述时必须引入第三张关系表,表中保存两者的主键。 单向关联时从用户出发加载出角色。 User.java 123456public class User { private Integer id; private String userName; private Set<Role> roles = new HashSet<>(); ...省略get,set}","text":"多对多单向关联用户和角色是典型的多对多关系。通过数据库去描述时必须引入第三张关系表,表中保存两者的主键。 单向关联时从用户出发加载出角色。 User.java 123456public class User { private Integer id; private String userName; private Set<Role> roles = new HashSet<>(); ...省略get,set} Role.java 1234public class Role { private Integer id; private String roleName;} User.hbm.xml 12345678910111213141516<hibernate-mapping package=\"com.domain2\"> <class name=\"User\" table=\"user_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"userName\"/> <!-- 多对多,table表示中间表 --> <set name=\"roles\" table=\"user_role_p\" cascade=\"save-update\"> <!-- user_id为中间表列,user_p的主键 --> <key column=\"user_id\"></key> <!-- role_id为中间表列,role_p的主键 --> <many-to-many column=\"role_id\" class=\"Role\"></many-to-many> </set> </class></hibernate-mapping> Role.hbm.xml 12345678<hibernate-mapping package=\"com.domain2\"> <class name=\"Role\" table=\"role_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"roleName\"/> </class></hibernate-mapping> 测试 123456789101112131415@Testpublic void test1() { Session session = HibernateUtils.openSession(); session.beginTransaction(); User user = new User(); user.setUserName(\"张三\"); Role role1 = new Role(); role1.setRoleName(\"admin\"); Role role2 = new Role(); role2.setRoleName(\"manager\"); user.getRoles().add(role1); user.getRoles().add(role2); session.save(user); session.getTransaction().commit();} 多对多双向关联单向关联时通过用户可以获取角色,双向关联时通过角色同样可以获取用户。需要在角色中添加用户的引用,User相关设置不变。 Role.java 12345public class Role { private Integer id; private String roleName; private Set<User> users = new HashSet<>();} Role.hbm.xml 12345678910111213<hibernate-mapping package=\"com.domain2\"> <class name=\"Role\" table=\"role_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"roleName\"/> <set name=\"users\" table=\"user_role_p\" cascade=\"save-update\"> <key column=\"role_id\"></key> <many-to-many column=\"user_id\" class=\"User\"/> </set> </class></hibernate-mapping> 测试 1234567891011121314151617@Testpublic void test1() { Session session = HibernateUtils.openSession(); session.beginTransaction(); User user = new User(); user.setUserName(\"张三\"); Role role1 = new Role(); role1.setRoleName(\"admin\"); role1.getUsers().add(user); Role role2 = new Role(); role2.setRoleName(\"manager\"); role2.getUsers().add(user); user.getRoles().add(role1); user.getRoles().add(role2); session.save(user); session.getTransaction().commit();} 在一方不放弃外键维护时执行上面的测试代码会报错。原因是双方都在维护中间表,插入重复数据时违反主键约束。 修改Role.hbm.xml,添加inverse=”true”。表示角色放弃外键维护 12345678910111213<hibernate-mapping package=\"com.domain2\"> <class name=\"Role\" table=\"role_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"roleName\"/> <set name=\"users\" table=\"user_role_p\" cascade=\"save-update\" inverse=\"true\"> <key column=\"role_id\"></key> <many-to-many column=\"user_id\" class=\"User\"/> </set> </class></hibernate-mapping> 重新测试后结果正常","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"hibernate","slug":"java/ssh/hibernate","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/hibernate/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"hiberante","slug":"hiberante","permalink":"http://blog.xxyxpy.pub/tags/hiberante/"}]},{"title":"hibernate-05映射关系之一对多","slug":"java/hibernate/hibernate-05映射关系之一对多","date":"2017-12-25T07:48:17.338Z","updated":"2017-12-25T07:48:17.357Z","comments":true,"path":"2017/12/25/java/hibernate/hibernate-05映射关系之一对多/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/hibernate/hibernate-05映射关系之一对多/","excerpt":"一对多单向关联之前说了多对一的关联,多对一反过来正好是一对多关联。一对多时加载一方可以将多方加载出来。比如现在查询部门,可以通过部门查询到下面所有的用户。 单向关联时一方中持有多方的引用 ,使用set集合。 User.java 12345public class User { private Integer id; private String userName; ...省略get,set}","text":"一对多单向关联之前说了多对一的关联,多对一反过来正好是一对多关联。一对多时加载一方可以将多方加载出来。比如现在查询部门,可以通过部门查询到下面所有的用户。 单向关联时一方中持有多方的引用 ,使用set集合。 User.java 12345public class User { private Integer id; private String userName; ...省略get,set} User.hbm.xml 12345678<hibernate-mapping package=\"com.domain2\"> <class name=\"User\" table=\"user_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"userName\"/> </class></hibernate-mapping> Dept.java 12345public class Dept { private Integer id; private String deptName; private Set<User> users = new HashSet<>();} Dept.hbm.xml 12345678910111213<hibernate-mapping package=\"com.domain2\"> <class name=\"Dept\" table=\"dept_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"deptName\"/> <set name=\"users\" cascade=\"save-update\"> <!-- 多方user表中的部门id列名 --> <key column=\"deptid\"></key> <one-to-many class=\"User\"></one-to-many> </set> </class></hibernate-mapping> 测试 首先会插入部门表生成部门id数据,然后插入用户表。最后再根据用update更新关联关系字段。插入3条记录,生成的sql语句有5条。这其实是一种损耗性能的,正常情况下只需要3条语句就行了。后面会讲到通过配置inverse属性来解决此问题 123456789101112131415@Testpublic void test1() { Session session = HibernateUtils.openSession(); session.beginTransaction(); User user = new User(); user.setUserName(\"张三\"); User user2 = new User(); user2.setUserName(\"李四\"); Dept dept = new Dept(); dept.setDeptName(\"市场部\"); dept.getUsers().add(user); dept.getUsers().add(user2); session.save(dept); session.getTransaction().commit();} 一对多双向关联双向关联与单向关联的区别是通过多方来加载一方,即多方中持有一方的引用。在用户和部门中表现为用户中持有部门的引用。所以只需要修改User类以及其配置文件。 User.java 12345public class User { private Integer id; private String userName; private Dept dept;} User.hbm.xml 12345678910<hibernate-mapping package=\"com.domain2\"> <class name=\"User\" table=\"user_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"userName\"/> <many-to-one name=\"dept\" class=\"Dept\" column=\"deptid\"/> </class></hibernate-mapping> 测试 与上面不同的是需要在user对象中添加入dept的引用,同样插入3条数据生成5条sql语句。 另外有一点不同的是在插入user数据时同时插入了deptid值,这是由于user中持有了dept引用 inverse控制反转inverse英文即反转的意思。inverse属性表达的是是否反转外键的控制权,默认值是false,即不反转由本方维护。 看到上面双向一对多时明显生成了两条多余的update语句,为什么会生成5条sql呢?我们可以分析一下部门保存时都发生了啥 1 保存部门,生成部门id主键。一条sql 2 由于设置了级联保存,继续插入user表数据,生成两条user表的insert。user对象中持有了dept,hibernate框架会在部门保存之后回写生成的id值。所以在user数据插入时是可以得到部门id的,这也是为什么在user表insert时有deptid列。两条sql 3 由于部门中默认的是维护外键关系的,所以在user表数据插入成功之后进行外键维护。维护的方式是根据2中得到的user表的主键和1中得到的dept表的主键进行更新。两条sql 明显第3步是多余的,因为在第2步中deptid已经插入成功了。只要让部门放弃对于外键的维护更新语句就不再用了,所以需要将inverse属性设置为true。设置后再保存发现只有3条insert语句 Dept.hbm.xml 1234567891011121314<hibernate-mapping package=\"com.domain2\"> <class name=\"Dept\" table=\"dept_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"deptName\"/> <set name=\"users\" cascade=\"save-update\" inverse=\"true\"> <!-- 多方user表中的部门id列名 --> <key column=\"deptid\"></key> <one-to-many class=\"User\"></one-to-many> </set> </class></hibernate-mapping> cascade和inverse可参考inverse、cascade属性详解 inverse的作用上面已经讲过,表示是否放弃外键的维护权。如果让一方维护外键会造成多余的update语句,所以一般一方都放弃维护,由多方维护外键。因为外键就在多的一方,所以让多方维护外键不会造成多生成sql的情况。 cascade表示级联操作,配置多个时可以用逗号隔开。有如下取值 none – 不使用级联 save-update – 级联保存或更新 delete – 级联删除 delete-orphan – 孤儿删除.(注意:只能应用在一对多关系可以将一的一方认为是父方.将多的一方认为是子方.孤儿删除:在解除了父子关系的时候.将子方记录就直接删除) all – 除了delete-orphan的所有情况.(包含save-update delete) all-delete-orphan – 包含了除delete-orphan的所有情况.(包含save-update delete delete-orphan) cascade和inverse是相互独立的,cascade不影响inverse","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"hibernate","slug":"java/ssh/hibernate","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/hibernate/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"hiberante","slug":"hiberante","permalink":"http://blog.xxyxpy.pub/tags/hiberante/"}]},{"title":"hibernate-04映射关系之多对一","slug":"java/hibernate/hibernate-04映射关系之多对一","date":"2017-12-25T07:48:11.289Z","updated":"2017-12-25T07:48:11.306Z","comments":true,"path":"2017/12/25/java/hibernate/hibernate-04映射关系之多对一/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/hibernate/hibernate-04映射关系之多对一/","excerpt":"多对一单向关联多对一关联使用的场景非常多,比如用户对部门、客户对订单、学生对班级等等。维护映射关系的思路是在多方添加维护关联关系的字段,比如在用户表中添加部门的id。 User.java 1234567public class User { private Integer id; private String userName; // 用户与部门,多对一 private Dept dept; ...省略get,set}","text":"多对一单向关联多对一关联使用的场景非常多,比如用户对部门、客户对订单、学生对班级等等。维护映射关系的思路是在多方添加维护关联关系的字段,比如在用户表中添加部门的id。 User.java 1234567public class User { private Integer id; private String userName; // 用户与部门,多对一 private Dept dept; ...省略get,set} User.hbm.xml 12345678910<hibernate-mapping package=\"com.domain2\"> <class name=\"User\" table=\"user_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"userName\"/> <many-to-one name=\"dept\" column=\"deptid\" class=\"Dept\" cascade=\"all\"/> </class></hibernate-mapping> Dept.java 1234public class Dept { private Integer id; private String deptName;} Dept.hbm.xml 12345678<hibernate-mapping package=\"com.domain2\"> <class name=\"Dept\" table=\"dept_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"deptName\"/> </class></hibernate-mapping> 测试 首先会插入部门表生成部门id数据,然后插入用户表插入时用到了刚刚生成的部门id。 12345678910111213141516@Testpublic void test1() { Session session = HibernateUtils.openSession(); session.beginTransaction(); User user = new User(); user.setUserName(\"张三\"); User user2 = new User(); user2.setUserName(\"李四\"); Dept dept = new Dept(); dept.setDeptName(\"市场部\"); user.setDept(dept); user2.setDept(dept); session.save(user); session.save(user2); session.getTransaction().commit();}","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"hibernate","slug":"java/ssh/hibernate","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/hibernate/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"hiberante","slug":"hiberante","permalink":"http://blog.xxyxpy.pub/tags/hiberante/"}]},{"title":"hibernate-03映射关系之一对一","slug":"java/hibernate/hibernate-03映射关系之一对一","date":"2017-12-25T07:48:06.083Z","updated":"2017-12-25T07:48:06.098Z","comments":true,"path":"2017/12/25/java/hibernate/hibernate-03映射关系之一对一/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/hibernate/hibernate-03映射关系之一对一/","excerpt":"映射关系分类Hibernate学习最主要的是要掌握映射关系的配置。映射关系一般分为一对一、一对多、多对一以及多对多,同时有些关系中还包含有单向关联和双向关联。 假定现在有用户、用户扩展信息、部门以及角色表。对应的关系有: 用户-用户扩展信息:一对一 用户-部门:多对一 部门-用户:一对多 用户-角色:多对多","text":"映射关系分类Hibernate学习最主要的是要掌握映射关系的配置。映射关系一般分为一对一、一对多、多对一以及多对多,同时有些关系中还包含有单向关联和双向关联。 假定现在有用户、用户扩展信息、部门以及角色表。对应的关系有: 用户-用户扩展信息:一对一 用户-部门:多对一 部门-用户:一对多 用户-角色:多对多 一对一单向关联(主键)对于用户信息比较多的情况,需要将用户信息拆表存储。这样就可以保证查询主要信息时效率比较高。拆表存储后两个表的关系表现为一对一。两个表之间的关系可以使用外键(即扩展表中持有用户表的id),也可以使用同一个主键。 使用同一主键时可以是一个表先生成主键然后再用到另外一个表中,当然也可以都设置成assigned,自己先成uuid去设置为主键。 User.java 123456789101112131415161718192021222324252627282930public class User { private Integer id; private String userName; // 用户与用户扩展信息,一对一 private UserInfo userInfo; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public UserInfo getUserInfo() { return userInfo; } public void setUserInfo(UserInfo userInfo) { this.userInfo = userInfo; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; }} UserInfo.java 1234567891011121314151617181920public class UserInfo { private Integer id; private String gender; public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; }} User.hbm.xml,这里设置的user表的主键为foreign生成策略,forgeign会取得另外一个关联对象的标识 12345678910111213141516<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE hibernate-mapping PUBLIC \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\" \"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd\"><hibernate-mapping package=\"com.domain2\"> <class name=\"User\" table=\"user_p\"> <id name=\"id\"> <generator class=\"foreign\"> <param name=\"property\">userInfo</param> </generator> </id> <property name=\"userName\"/> <!-- 一对一 --> <one-to-one name=\"userInfo\" class=\"UserInfo\"/> </class></hibernate-mapping> UserInfo.hbm.xml 123456789101112<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE hibernate-mapping PUBLIC \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\" \"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd\"><hibernate-mapping package=\"com.domain2\"> <class name=\"UserInfo\" table=\"userinfo_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"gender\"/> </class></hibernate-mapping> 测试一下保存用户 123456789101112@Testpublic void test1() { Session session = HibernateUtils.openSession(); session.beginTransaction(); User user = new User(); user.setUserName(\"张三\"); UserInfo userInfo = new UserInfo(); userInfo.setGender(\"男\"); user.setUserInfo(userInfo); session.save(user); session.getTransaction().commit();} 结果 一对一双向关联(主键)单向关联由User找到UserInfo,双向关联指的是互相持有。由UserInfo也能找到User。通过主键关联时两个关联方还是保持相同的主键值。 User相关的类及配置文件不变,UserInfo的类及配置文件变化如下: UserInfo.java添加User属性 1234567891011121314151617181920212223242526272829public class UserInfo { private Integer id; private String gender; private User user; public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public User getUser() { return user; } public void setUser(User user) { this.user = user; }} UserInfo.hbm.xml 123456789101112131415<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE hibernate-mapping PUBLIC \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\" \"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd\"><hibernate-mapping package=\"com.domain2\"> <class name=\"UserInfo\" table=\"userinfo_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"gender\"/> <!-- 一对一双向 --> <!--cascade=\"all\" 必须添加,表示级联--> <one-to-one name=\"userInfo\" class=\"UserInfo\" cascade=\"all\"/> </class></hibernate-mapping> 测试:保存方式和上面的单向关联相同。这里测试下先查询UserInfo,然后根据UserInfo显示User中的UserName 123456789/** * 一对一双向关联 */@Testpublic void test2() { Session session = HibernateUtils.openSession(); UserInfo userInfo = session.get(UserInfo.class, 11); System.out.println(userInfo.getUser().getUserName());} 查询结果中可以发现查询语句是一个join查询,根据id查询出了两张表中的数据。结果如下: 一对一单向关联(外键)上面的关联方式两者用的相同的主键维护。还可以通过外键进行维护,比如在User表添加一列持有UserInfo的主键,然后将这一列设置为不可重复(unique)。 User.java 1234567public class User { private Integer id; private String userName; // 用户与用户扩展信息,一对一 private UserInfo userInfo; 省略get,set方法} UserInfo.java 12345public class UserInfo { private Integer id; private String gender; 省略get,set方法} User.hbm.xml,cascade=”all” 必须添加,表示级联 12345678910<hibernate-mapping package=\"com.domain2\"> <class name=\"User\" table=\"user_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"userName\"/> <many-to-one name=\"userInfo\" column=\"userInfoId\" unique=\"true\" cascade=\"all\"/> </class></hibernate-mapping> UserInfo.hbm.xml 123456789<hibernate-mapping package=\"com.domain2\"> <class name=\"UserInfo\" table=\"userinfo_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"gender\"/> </class></hibernate-mapping> 测试:保存User时自动保存UserInfo 123456789101112131415/*** 一对一单向关联,外建*/@Testpublic void test3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); User user = new User(); user.setUserName(\"张三\"); UserInfo userInfo = new UserInfo(); userInfo.setGender(\"男\"); user.setUserInfo(userInfo); session.save(user); session.getTransaction().commit();} 结果:可以看到User表中添加了一列存储UserInfo表中的主键,而且生成了唯一索引和外键 一对一双向关联(外键)这个和上面讲到的单向关联唯一的区别就是在UserInfo中也持有User。User相关的类及配置不变,只需要修改UserInfo即可 UserInfo.java 123456public class UserInfo { private Integer id; private String gender; private User user; 省略get,set方法} UserInfo.hbm.xml 1234567891011<hibernate-mapping package=\"com.domain2\"> <class name=\"UserInfo\" table=\"userinfo_p\"> <id name=\"id\"> <generator class=\"native\"/> </id> <property name=\"gender\"/> <!-- property-ref属性为关系字段的名称 --> <one-to-one name=\"user\" class=\"User\" property-ref=\"userInfo\"/> </class></hibernate-mapping> 测试:保存时还和单向关联一样,只不过是在查询时可以通过只查询UserInfo得到User 123456789101112131415/*** 一对一双向关联,外建*/@Testpublic void test4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); User user = new User(); user.setUserName(\"张三\"); UserInfo userInfo = new UserInfo(); userInfo.setGender(\"男\"); user.setUserInfo(userInfo); session.save(user); session.getTransaction().commit();}","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"hibernate","slug":"java/ssh/hibernate","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/hibernate/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"hiberante","slug":"hiberante","permalink":"http://blog.xxyxpy.pub/tags/hiberante/"}]},{"title":"hibernate-02缓存和事务","slug":"java/hibernate/hibernate-02缓存和事务","date":"2017-12-25T07:47:59.197Z","updated":"2017-12-25T07:47:59.212Z","comments":true,"path":"2017/12/25/java/hibernate/hibernate-02缓存和事务/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/hibernate/hibernate-02缓存和事务/","excerpt":"持久化对象的状态当java类与数据库中的某个表建立了映射关系时,这个类就称为持久化类。所以持久化类=java类+hbm配置文件。 Hibernate为了管理持久化类的状态,将持久化类分为三种状态: 瞬时态(Transient Object):没有持久化标识OID(主键),没有被纳入到Session对象的管理 获得瞬时态对象User user = new User(); 瞬时态对象转换为持久态,调用Session的save或saveOrUpdate方法 瞬时态对象转换为脱管态,为此对象设置OIDuser.setId(1); 持久态(Persistent Object):有持久化标识OID,已被纳入到Session对象的管理。具有自动更新数据库的能力 获得持久态对象,调用Session的get或load方法 持久态转换成瞬时态,调用Session的delete方法。相当于把这条记录干没了 持久态转换成脱管态,调用Session的close、evict、clear方法 脱管态(Detached Object):有持久化标识OID,没有被纳入到Session对象的管理 获得脱管态对象User user = new User();user.setId(1); 脱管态转换成持久态,调用Session的save或saveOrUpdate或lock方法 脱管态转换成瞬时态user.setId(null);","text":"持久化对象的状态当java类与数据库中的某个表建立了映射关系时,这个类就称为持久化类。所以持久化类=java类+hbm配置文件。 Hibernate为了管理持久化类的状态,将持久化类分为三种状态: 瞬时态(Transient Object):没有持久化标识OID(主键),没有被纳入到Session对象的管理 获得瞬时态对象User user = new User(); 瞬时态对象转换为持久态,调用Session的save或saveOrUpdate方法 瞬时态对象转换为脱管态,为此对象设置OIDuser.setId(1); 持久态(Persistent Object):有持久化标识OID,已被纳入到Session对象的管理。具有自动更新数据库的能力 获得持久态对象,调用Session的get或load方法 持久态转换成瞬时态,调用Session的delete方法。相当于把这条记录干没了 持久态转换成脱管态,调用Session的close、evict、clear方法 脱管态(Detached Object):有持久化标识OID,没有被纳入到Session对象的管理 获得脱管态对象User user = new User();user.setId(1); 脱管态转换成持久态,调用Session的save或saveOrUpdate或lock方法 脱管态转换成瞬时态user.setId(null); Person类 12345678910111213141516public class Person { private String pid; private String pname; public String getPid() { return pid; } public void setPid(String pid) { this.pid = pid; } public String getPname() { return pname; } public void setPname(String pname) { this.pname = pname; }} Person.hbm.xml映射文件,由于hibernate.cfg.xml中配置的<property name="hibernate.hbm2ddl.auto">update</property>所以可以自动生成数据库表结构。 123456789101112<?xml version=\"1.0\" encoding=\"utf-8\" ?><!DOCTYPE hibernate-mapping PUBLIC \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\" \"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd\"><hibernate-mapping> <class name=\"com.domain.Person\" table=\"t_person\"> <id name=\"pid\" column=\"pid\"> <generator class=\"uuid\"/> </id> <property name=\"pname\" column=\"pname\" length=\"30\"/> </class></hibernate-mapping> Junit测试方法 1234567891011121314151617181920212223242526272829@Testpublic void run2(){ Session session = HibernateUtils.openSession(); Transaction transaction = session.beginTransaction(); Person p = new Person(); //瞬时态 p.setPname(\"pppp\"); //持久态,保存之后即有了主键值 session.save(p); transaction.commit(); session.close(); System.out.println(p.getPid()); //脱管态session关闭 p.setPname(\"ccc\");}/** * 自动更新数据的能力,使用的是快照机制 */@Testpublic void run3(){ Session session = HibernateUtils.openSession(); Transaction transaction = session.beginTransaction(); User3 user3 = session.get(User3.class, 1); user3.setName(\"哈哈\"); //无需调用session.update(user3)即可完成更新 transaction.commit(); session.close();} 缓存缓存其实就是一块连续的内存空间,用于存储数据。再次使用时从内存中读取,提高程序的性能。 Hibernate框架提供的两种缓存 一级缓存:自带的不可卸载的。生命周期和Session一致,称为Session级别的缓存 二级缓存:默认关闭,需要手动配置才可以使用。二级缓存可以在多个Session中共享数据,称为SessionFactory级别的缓存。 Session对象的缓存在Session接口中有一系列的java集合,这些集合构成了Session级别的缓存,将对象存放到一级缓存中只要session没有结束生命周期那么对象在Session中就一直放着。 证明一缓存的存在。思路:两次查询同一行数据,只有一条sql语句 12345678910111213/** * 证明session中的一级缓存存在 * 使用两次查询同一个id,会发现只执行一次sql */@Testpublic void run44(){ Session session = HibernateUtils.openSession(); Person p = session.get(Person.class, \"8ae5bae15dc1246f015dc124714e0000\"); System.out.println(p.getPname()); Person pp = session.get(Person.class, \"8ae5bae15dc1246f015dc124714e0000\"); System.out.println(pp.getPname()); session.close();} 结果: 缓存的手动控制 Session.clear(),清空缓存 Session.evict(Object entity),从一级缓存中清除指定的实体对象 Session.flush(),刷出缓存 事务关于事务的详细说明可以查看 丢失更新问题什么是丢失更新?如果不考虑隔离性,会产生写入数据丢失的问题。这一类问题就是丢失更新 例如:两个事务同时操作一条记录,A事务修改完成提交,B事务无论是否提供都会使A事务的操作丢失 解决方案 悲观锁。数据库的一种锁机制,在当前操作的sql语句后面添加for update,当A事务进行操作时,其他事务不能操作。只有当A事务提交或回滚释放后其他事务才可以操作。 Hibernate中使用悲观锁session.get(Customer.class, 1,LockMode.UPGRADE); 乐观锁。采用版本号的机制来解决。给表添加一个字段version,默认值是0。当A事务操作完该记录时会检查版本号,如果和当前的不同不提交事务,只有相同时才提交事务。并且把版本号+1。 Hibernate中使用乐观锁要先在JavaBean中添加一个属性如Integer version,然后再在实体的配置文件中添加 12<!--乐观锁--><version name=\"version\"/> 绑定本地Session在之前的web事务中讲到了在service中开启事务,将Connection绑定到ThreadLocal对象中以保证事务的一致性。在Hibernate框架中使用Session来开启事务,所以需要传递Session对象,同样的也是通过绑定到ThreadLocal来实现。 想实现自动绑定功能需要在hibernate.cfg.xml中添加如下内容: 12<!--开启绑定本地session--><property name=\"hibernate.current_session_context_class\">thread</property> 假设现在在service层执行两步操作,保存两个客户。考虑这个操作是一个事务,要么同时成功,要么同时失败。这里使用的工具类即为上一篇的HibernateUtils类,只是为说明问题,customer和user类就不贴了。伪代码如下: 1234567891011121314public void save(Customer c1, Customer c2) { Session currentSession = HibernateUtils.getCurrentSession(); Transaction tr = currentSession.beginTransaction(); CustomerDao dao = new CustomerDao(); try { dao.save1(c1); int res = 10 / 0; dao.save2(c2); tr.commit(); } catch (Exception e) { e.printStackTrace(); tr.rollback(); }}","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"hibernate","slug":"java/ssh/hibernate","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/hibernate/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"hiberante","slug":"hiberante","permalink":"http://blog.xxyxpy.pub/tags/hiberante/"}]},{"title":"hibernate-01入门和单表使用","slug":"java/hibernate/hibernate-01入门和单表使用","date":"2017-12-25T07:47:51.642Z","updated":"2017-12-25T07:47:51.668Z","comments":true,"path":"2017/12/25/java/hibernate/hibernate-01入门和单表使用/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/hibernate/hibernate-01入门和单表使用/","excerpt":"hibernate框架概述Hibernate是一个开放源代码的对象关系映射(ORM)框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用。 Hibernate能使程序员通过操作对象来操作数据库表记录。 什么是ORMORM即对象关系映射(Object Relational Mapping) O:面向对象领域的Object(JavaBean对象) R:关系数据库领域的Relational(表结构) M:映射Mapping(xml的配置文件) Hibernate的优点 对jdbc访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码 是基于jdbc的主流持久化框架,是一个优秀的orm实现,它很大程度简化了dao层编码工作 轻量级框架并且性能好。映射的灵活性很出色。支持多种数据库和多种(一对多,多对多)映射关系。","text":"hibernate框架概述Hibernate是一个开放源代码的对象关系映射(ORM)框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用。 Hibernate能使程序员通过操作对象来操作数据库表记录。 什么是ORMORM即对象关系映射(Object Relational Mapping) O:面向对象领域的Object(JavaBean对象) R:关系数据库领域的Relational(表结构) M:映射Mapping(xml的配置文件) Hibernate的优点 对jdbc访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码 是基于jdbc的主流持久化框架,是一个优秀的orm实现,它很大程度简化了dao层编码工作 轻量级框架并且性能好。映射的灵活性很出色。支持多种数据库和多种(一对多,多对多)映射关系。 入门案例下载hibernate5运行环境hibernate5-5.0.7final 度盘(链接: http://pan.baidu.com/s/1slHg2Ep 密码: nuj1) 建cst_customer表基于mysql数据库创建的表,可以自己创建相应的数据库 12345678910111213CREATE TABLE `cst_customer` ( `cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)', `cust_name` varchar(32) NOT NULL COMMENT '客户名称(公司名称)', `cust_user_id` bigint(32) DEFAULT NULL COMMENT '负责人id', `cust_create_id` bigint(32) DEFAULT NULL COMMENT '创建人id', `cust_source` varchar(32) DEFAULT NULL COMMENT '客户信息来源', `cust_industry` varchar(32) DEFAULT NULL COMMENT '客户所属行业', `cust_level` varchar(32) DEFAULT NULL COMMENT '客户级别', `cust_linkman` varchar(64) DEFAULT NULL COMMENT '联系人', `cust_phone` varchar(64) DEFAULT NULL COMMENT '固定电话', `cust_mobile` varchar(16) DEFAULT NULL COMMENT '移动电话', PRIMARY KEY (`cust_id`)) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8; 搭建hibernate开发环境 创建web工程 引入mysql的驱动包jar(链接: http://pan.baidu.com/s/1nuEW3Rb 密码: 8tp8) 引入hibernate开发需要的jar(hibernate-release-5.0.7.Final/lib/required目录下所有jar包) 日志jar包(链接: http://pan.baidu.com/s/1jI65xpc 密码: msvk) 编写JavaBean类123456789101112131415161718192021222324252627282930313233import java.util.HashSet;import java.util.Set;public class Customer { private Long cust_id; private String cust_name; private Long cust_user_id; private Long cust_create_id; private String cust_source; private String cust_industry; private String cust_level; private String cust_linkman; private String cust_phone; private String cust_mobile; 生成get,set方法。省略此部分代码 @Override public String toString() { return \"Customer{\" + \"cust_id=\" + cust_id + \", cust_name='\" + cust_name + '\\'' + \", cust_user_id=\" + cust_user_id + \", cust_create_id=\" + cust_create_id + \", cust_source='\" + cust_source + '\\'' + \", cust_industry='\" + cust_industry + '\\'' + \", cust_level='\" + cust_level + '\\'' + \", cust_linkman='\" + cust_linkman + '\\'' + \", cust_phone='\" + cust_phone + '\\'' + \", cust_mobile='\" + cust_mobile + '\\'' + '}'; }} 创建映射文件一般映射文件的命名为实体名.hbm.xml,并且放在JavaBean的包下面。 <class> 标签 用来将类与数据库表建立映射关系 name 类的全路径 table 表名(如果类名与表名相同可以省略不配置) catalog 数据库名称,因为在连接字符串中已经配置所以一般都省略不写 可选配置dynamic-insert和dynamic-update,动态插入和更新,默认值是false。表示在执行插入和更新时对于null值的字段是否执行插入或更新操作 <id>标签 用来将类中的属性与表中的主键建立映射 name 类中的属性名 column 表中的字段名(如果属性名和字段名相同可以省略不配置) length字段的长度。如果表已存在可不指定,如果表不存在需要由hibernate生成表结构,最好指定,否则使用hibernate默认值 generator指主键的生成策略 生成策略2 assigned 由程序员负责生成,在save之前必须设置值 sequence 采用数据库提供的sequence机制生成主键,比如oracle数据库 identity由数据库自己生成的自增主键,比如sqlserver、mysql等数据库 native 框架根据数据库自动判断支持的策略,从identity、sequence和hilo中选择一个 uuid 使用guid作为主建 increment 由hibernate在内存中生成主键,每次增量为1。由于是hibernate生成所以只能有一个hibernate应用进程访问数据库,否则就产生主建冲突,不能在集群情况下使用 <property>标签 用来将类中的普通属性与表中的字段建立映射 name 类中的属性名 column 表中的字段名(同样的如果属性名与字段名相同可以省略不配置) length 数据的长度,一般不配置 type 数据类型,不般不配置 12345678910111213141516171819202122232425262728<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE hibernate-mapping PUBLIC \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\" \"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd\"><hibernate-mapping> <!-- 配置类和表结构的映射 --> <class name=\"com.domain.Customer\" table=\"cst_customer\"> <!-- 配置id 见到name属性,JavaBean的属性 见到column属性,是表结构的字段 --> <id name=\"cust_id\" column=\"cust_id\"> <!-- 主键的生成策略 --> <generator class=\"native\"/> </id> <!-- 配置其他的属性 --> <property name=\"cust_name\" column=\"cust_name\"/> <property name=\"cust_user_id\" column=\"cust_user_id\"/> <property name=\"cust_create_id\" column=\"cust_create_id\"/> <property name=\"cust_source\" column=\"cust_source\"/> <property name=\"cust_industry\" column=\"cust_industry\"/> <property name=\"cust_level\" column=\"cust_level\"/> <property name=\"cust_linkman\" column=\"cust_linkman\"/> <property name=\"cust_phone\" column=\"cust_phone\"/> <property name=\"cust_mobile\" column=\"cust_mobile\"/> </class></hibernate-mapping> hibernate核心配置文件需要在src目录下面进行创建。 有两种方式去实现配置,一种是通过properties文件配置。这种方式下需要手动加载映射配置文件,然后读取其中的内容。比较麻烦。一般使用另外一种即xml文件的方式,此种方式下可以自动加载映射的配置文件。 数据库连接(必须配置) 数据库方言,指定哪种数据库(必须配置) 是否显示语句,是否格式化语句,是否自动更新等(可选配置) 引入映射配置文件 1234567891011121314151617181920212223242526272829<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE hibernate-configuration PUBLIC \"-//Hibernate/Hibernate Configuration DTD 3.0//EN\" \"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd\"><hibernate-configuration> <!-- 先配置SessionFactory标签,一个数据库对应一个SessionFactory标签 --> <session-factory> <property name=\"hibernate.connection.driver_class\">com.mysql.jdbc.Driver</property> <property name=\"hibernate.connection.url\">jdbc:mysql://localhost:3306/mytest?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false</property> <property name=\"hibernate.connection.username\">root</property> <property name=\"hibernate.connection.password\">root</property> <!-- 数据库的方言 --> <property name=\"hibernate.dialect\">org.hibernate.dialect.MySQLDialect</property> <!-- 可选配置 --> <!-- 显示SQL语句,在控制台显示 --> <property name=\"hibernate.show_sql\">true</property> <!-- 格式化SQL语句 --> <property name=\"hibernate.format_sql\">true</property> <!--生成表结构--> <!--<property name=\"hibernate.hbm2ddl.auto\">create</property>--> <!--更新表结构--> <property name=\"hibernate.hbm2ddl.auto\">update</property> <!-- 映射配置文件,需要引入映射的配置文件 --> <mapping resource=\"com/domain/Customer.hbm.xml\"/> </session-factory></hibernate-configuration> 测试代码这里使用junit进行测试 12345678910111213141516171819202122232425262728293031323334353637public class Test1 { @Test public void testSave() { //读取hibernate.cfg.xml文件 Configuration cfg = new Configuration(); cfg.configure(); //如果cfg文件中没配置mapping需要自己手动加载配置文件 //cfg.addResource(\"com/domain/Customer.hbm.xml\") //创建sessionFactory SessionFactory factory = cfg.buildSessionFactory(); //取得session Session session = null; try { //开启session session = factory.openSession(); //开启事务 Transaction tr = session.beginTransaction(); Customer c = new Customer(); c.setCust_name(\"测试\"); c.setCust_level(\"1\"); c.setCust_phone(\"110\"); //保存数据 session.save(c); //提交事务 tr.commit(); } catch (Exception e) { e.printStackTrace(); } finally { //释放资源 if (session != null) { session.close(); } factory.close(); } }} hibernate常用的接口和类Configuration类用于配置并启动Hibernate,hibernate通过该对象来获得对象-关系映射文件中的元数据以及动态配置的hibernate属性,然后创建SessionFactory对象。 加载映射文件的方式根据核心配置文件配置的格式不同而不同,这里只说最常用的使用xml配置核心配置文件时的加载方式。使用Configuration configuration = new Configuration().configure();即可完成配置的加载。调用configure()方法时要求核心 SessionFactory类是一个工厂类,用于生成Session对象。一般在应用启动时初始化完成。 特点 由Configuration通过加载配置文件创建该对象 SessionFactory对象中保存了当前的数据库配置信息和所有的映射关系以及预定义的sql语句。同时还负责维护Hibernate的二级缓存 关于预定义的sql语句 使用Configuration类创建SessionFactory对象时已经在此对象中缓存了一些sql语句 常见的缓存语句有通过主键执行的crud操作 这样实现的目的是为了效率更高 一个SessionFactory即对应一个数据库,应该用从该对象中获得Session实例 SessionFactory是线程安全的,它的一个实现可以被应用的多线程共享 SessionFactory重量级的,不能随意的创建或销毁它的实例 SessionFactory需要一个较大的缓存,用来存放预定义的sql语句及实体的映射信息。另外可以配置一个缓存插件,这个插件被称之为hibernate的二级缓存,被多线程所共享 工具类由于SessionFactory如此的重,所以不能每次调用都创建一次。抽取一个工具类HibernateUtils 123456789101112131415161718192021222324import org.hibernate.Session;import org.hibernate.SessionFactory;import org.hibernate.cfg.Configuration;public class HibernateUtils { private static final Configuration CONFIG; private static final SessionFactory FACTORY; static { CONFIG = new Configuration().configure(); FACTORY = CONFIG.buildSessionFactory(); } public static Session openSession(){ return FACTORY.openSession(); } /** * 从ThreadLocal实例中获取当前线程的session * @return */ public static Session getCurrentSession(){ return FACTORY.getCurrentSession(); }} 测试一下调用 12345678910@Testpublic void testSave2() { Session session = HibernateUtils.openSession(); Transaction transaction = session.beginTransaction(); Customer c = new Customer(); c.setCust_name(\"aaaa\"); session.save(c); transaction.commit(); session.close();} Session接口特点 是在hibernate中最常用的接口,也被称为持久化管理器。它提供了和持久化相关的操作。比如crud,加载和查询实例对象 Session是应用程序与数据库之间交互操作的一个单线程对象。是hibernate的运作中心 Session不是线程安全的,避免多个线程使用同一个Session 所有的持久化对象必须在session的管理下才可以进行持久化操作 Session对象有一个一级缓存,显示的执行flush之前所有的持久化操作的数据都缓存在Session对象中。每个实例都有自己的一级缓存 轻量级,创建和销毁不消耗资源,每次请求都分配独立的Session实例 持久化类与Session关联起来后就具有了持久化的能力 常用方法 save(obj) delete(obj) get(class,id) update(obj) saveOrUpdate(obj) createQuery() Transaction接口事务的接口。常用的方法有commit和rollback 特点 hibernate默认情况下事务不自动提交,需要自己手动提交 如果没有开启事务,那么每一个Session操作都是独立的一个事务","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"},{"name":"hibernate","slug":"java/ssh/hibernate","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/hibernate/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"},{"name":"hiberante","slug":"hiberante","permalink":"http://blog.xxyxpy.pub/tags/hiberante/"}]},{"title":"Python小技巧","slug":"python/python小技巧","date":"2017-12-25T07:47:43.251Z","updated":"2017-12-25T07:47:43.262Z","comments":true,"path":"2017/12/25/python/python小技巧/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/python/python小技巧/","excerpt":"将数组内数据拼接常用于接拼sql的in操作 1234L = [1, 2, 3, 4, 5, 6, 7]# join时要求必须是字符串,而str可以将int转成字符串print(\",\".join(str(i) for i in L))del_sql = \"delete from table where id in ({0})\".format(\",\".join(str(i) for i in L))","text":"将数组内数据拼接常用于接拼sql的in操作 1234L = [1, 2, 3, 4, 5, 6, 7]# join时要求必须是字符串,而str可以将int转成字符串print(\",\".join(str(i) for i in L))del_sql = \"delete from table where id in ({0})\".format(\",\".join(str(i) for i in L))","categories":[{"name":"python","slug":"python","permalink":"http://blog.xxyxpy.pub/categories/python/"}],"tags":[{"name":"套路","slug":"套路","permalink":"http://blog.xxyxpy.pub/tags/套路/"},{"name":"python","slug":"python","permalink":"http://blog.xxyxpy.pub/tags/python/"}]},{"title":"solr环境搭建","slug":"linux/solr环境搭建","date":"2017-12-25T07:47:28.122Z","updated":"2017-12-25T07:47:28.134Z","comments":true,"path":"2017/12/25/linux/solr环境搭建/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/linux/solr环境搭建/","excerpt":"solr的运行必须要有jdk环境 单机版如果发现zookeeper启动不了,可以先删除data目录下的zookeeper_server.pid文件 集群版为了实现高可用至少需要7台服务器。","text":"solr的运行必须要有jdk环境 单机版如果发现zookeeper启动不了,可以先删除data目录下的zookeeper_server.pid文件 集群版为了实现高可用至少需要7台服务器。 使用zookeeper进行分布式管理。由于zookeeper的投票选举机制,所以至少需要有三台zookeeper服务器。 一个collection至少分为两个分片,每个分片至少需要一个主(master)一个从(salve),所以至少需要四台tomcat服务器 zookeeper 集群搭建a. 复制压缩包到服务器 b. 解压缩 c. 复制解压之后的目录到安装位置,复制三份(或者仅复制一份处理完之后再根据它进行复制) 123tar -zxvf zookeeper-3.4.6.tar.gz mkdir /usr/local/application/solr/zookeepermv zookeeper-3.4.6 /usr/local/application/solr/solrcloud/zookeeper01 d. 创建data目录,并在目录下面建myid文件,向其中写入实例的id。如1 1echo 1>myid e. 修改配置文件。把conf目录下面的zoo_sample.cfg重命名为zoo.cfg,并修改data路径,端口号以及添加各个节点的端口 12345678mv zoo_sample.cfg zoo.cfg# 修改dataDirdataDir=/usr/local/application/solr/zookeeper/zookeeper01/data# 修改clientport,第一个可以保持2181不变# 底部添加各个节点的内部通讯端口和投票选举端口都不能重复server.1=192.168.80.128:2881:3881server.2=192.168.80.128:2882:3882server.3=192.168.80.128:2883:3883 f. 复制zookeeper01为02和03,并按上面的方法修改02和03目录内的信息,主要是data目录和端口号 12cp -r zookeeper01/ zookeeper02cp -r zookeeper01/ zookeeper03 g. 创建启动和关闭的shell脚本 12345678910vim startup.shchmod u+x startup.sh ./zookeeper01/bin/zkServer.sh start./zookeeper02/bin/zkServer.sh start./zookeeper03/bin/zkServer.sh startvim shutdown.shchmod u+x shutdown.sh ./zookeeper01/bin/zkServer.sh stop./zookeeper02/bin/zkServer.sh stop./zookeeper03/bin/zkServer.sh stop 最终启动后的效果是只有一个leader其它都是follower,可以通过zkServer.sh status查看单个节点的状态 solr集群 a. 复制单机版本solr的tomcat四份,修改其端口号。四个tomcat的端口号不同 1234cp -r tomcat-solr/ solrcloud/tomcat-solr01cp -r tomcat-solr/ solrcloud/tomcat-solr02cp -r tomcat-solr/ solrcloud/tomcat-solr03cp -r tomcat-solr/ solrcloud/tomcat-solr04 b. 复制四个solrhome出来,对应到各个solr实例。原始的solrhome以是solr解压后的的solr-4.10.3/example/solr目录 123456789cp -r solr /usr/local/application/solr/solrcloud/solrhome01cp -r solr /usr/local/application/solr/solrcloud/solrhome02cp -r solr /usr/local/application/solr/solrcloud/solrhome03cp -r solr /usr/local/application/solr/solrcloud/solrhome04# 修改solr实例对应的solrhomevim tomcat-solr01/webapps/solr/WEB-INF/web.xml vim tomcat-solr02/webapps/solr/WEB-INF/web.xml vim tomcat-solr03/webapps/solr/WEB-INF/web.xml vim tomcat-solr04/webapps/solr/WEB-INF/web.xml c. 修改solrhome下面的solr.xml,配置tomcat的ip和端口号 1234vim solrhome01/solr.xmlvim solrhome02/solr.xmlvim solrhome03/solr.xmlvim solrhome04/solr.xml d. 让zookeeper统一管理配置文件。需要把solrhome/collection1/conf目录上传到zookeeper。上传任意的solrhome中的配置文件即可。 使用工具上传配置文件:solr-4.10.3/example/scripts/cloud-scripts/zkcli.sh 123456./zkcli.sh -zkhost 192.168.80.128:2184,192.168.80.128:2182,192.168.80.128:2183 -cmd upconfig -confdir /usr/local/application/solr/solrcloud/solrhome01/collection1/conf -confname myconf# 查看上传之后的配置,使用zookeeper目录下面的bin/zkCli.sh命令# 如果连接的端口号非2181需要使用 ./zkCli.sh -server 192.168.25.154:2182ls /ls /configsls /configs/myconf e. 修改每个tomcat/bin目录下的catalina.sh 文件,关联solr和zookeeper。修改JAVA_OPTS 1JAVA_OPTS="-DzkHost=192.168.80.128:2184,192.168.80.128:2182,192.168.80.128:2183" f. 访问solr实例 http://192.168.80.128:8280/solr/ g. 创建新的collection并进行分片处理 http://192.168.80.128:8180/solr/admin/collections?action=CREATE&name=collection2&numShards=2&replicationFactor=2 h. 删除不用的collection http://192.168.80.128:8180/solr/admin/collections?action=DELETE&name=collection1 i. solr配置文件如:schema.xml的更新 查到的资料说是可以热更新,即不删除原来的文件直接上传即覆盖。试了几次没有成功,一直报文件已经存在。可能是因为版本的原因。所以采用的是先删除原来的文件再上传的策略。 zookeeper常用命令 Solr集群更新配置的方式 1234567# 连接到zookeeper集群中的任意节点./zookeeper01/bin/zkCli.sh -server 192.168.80.128:2184# 删除schema.xml文件 myconf为配置文件目录名,可以在solr的cloud->tree中查看delete /configs/myconf/schema.xml# 上传新的配置文件,使用的是put命令 使用solr下面的工具 /example/scripts/cloud-scripts/zkcli.sh# putfile 后面第一个参数为zookeeper集群中的路径 后一个参数为要上传的文件的路径./zkcli.sh -zkhost 192.168.80.128:2184,192.168.80.128:2182,192.168.80.128:2183 -cmd putfile /configs/myconf/schema.xml /usr/local/application/solr/solrcloud/solrhome01/collection2_shard2_replica1/conf/schema.xml 上传完成之后可以在ui界面中更新solr的配置文件,按钮在core admin页面。通过reload按钮即可进行更新,这样有一个麻烦的地方是需要一个一个solr打开去reload。另外一种方式是通过命令进行更新 1http://192.168.80.128:8380/solr/admin/collections?action=RELOAD&name=collection2 使用solrj管理集群第一步:把solrJ相关的jar包添加到工程中。 第二步:创建一个SolrServer对象,需要使用CloudSolrServer子类。构造方法的参数是zookeeper的地址列表。 第三步:需要设置DefaultCollection属性。 第四步:创建一SolrInputDocument对象。 第五步:向文档对象中添加域 第六步:把文档对象写入索引库。 第七步:提交。 junit测试 1234567891011121314151617181920212223import org.apache.solr.client.solrj.impl.CloudSolrServer;import org.apache.solr.common.SolrInputDocument;import org.junit.Test;/** * Created on 2017/9/22. */public class TestSolrCloud { @Test public void testSolrCloud() throws Exception { CloudSolrServer solrServer = new CloudSolrServer(\"192.168.80.128:2184,192.168.80.128:2182,192.168.80.128:2183\"); solrServer.setDefaultCollection(\"collection2\"); SolrInputDocument document = new SolrInputDocument(); document.addField(\"id\", \"test001\"); document.addField(\"item_title\", \"测试商品\"); document.addField(\"item_desc\", \"很好的东东\"); solrServer.add(document); solrServer.commit(); }} 整合到spring中。在使用时使用接口去自动注入","categories":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/tags/linux/"},{"name":"solr","slug":"solr","permalink":"http://blog.xxyxpy.pub/tags/solr/"}]},{"title":"redis环境搭建","slug":"linux/redis环境搭建","date":"2017-12-25T07:45:07.711Z","updated":"2018-01-10T07:54:58.016Z","comments":true,"path":"2017/12/25/linux/redis环境搭建/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/linux/redis环境搭建/","excerpt":"准备工作redis编译时需要gcc环境,所以需要先安装gcc 1yum install gcc gcc-c++ 下载安装包1wget http://download.redis.io/releases/redis-3.2.5.tar.gz 安装123456# 解压到/usr/local/目录下tar zxf redis-3.2.5.tar.gz -C /usr/local/# 进入解压后的目录cd /usr/local/redis-3.2.5# 安装到/usr/local/redis目录make PREFIX=/usr/local/redis install 启动","text":"准备工作redis编译时需要gcc环境,所以需要先安装gcc 1yum install gcc gcc-c++ 下载安装包1wget http://download.redis.io/releases/redis-3.2.5.tar.gz 安装123456# 解压到/usr/local/目录下tar zxf redis-3.2.5.tar.gz -C /usr/local/# 进入解压后的目录cd /usr/local/redis-3.2.5# 安装到/usr/local/redis目录make PREFIX=/usr/local/redis install 启动 12# 进入到redis下面的bin目录进行启动./redis-server 此种启动方式为前端启动,即会独占一个连接窗口。一旦窗口关闭连接即中止。需要修改成后端模式启动 通过修改redis.conf配置文件的方式来实现 123456789# 拷贝源码目录下的redis.conf到安装目录bin下面cp /usr/local/redis-3.2.5/redis.conf /usr/local/redis/bin# 修改配置文件vim /usr/local/redis/bin/redis.conf# daemonize no 修改为 daemonize yes,此配置文件也可以修改启动的端口号。默认为6379# 后端启动./redis-server redis.conf# 查看启动是否成功ps aux|grep redis 连接1234567# 通过bin目录下的redis-cli进行连接# 当前服务器./redis-cli# 指定ip默认6379端口./redis-cli -h 192.168.1.2# 指定ip和端口./redis-cli -h 192.168.1.2 -p 6380 外部连接时会发现无法进行连接。因为考虑到安全的问题默认开启保护模式,仅允许127.0.0.1的ip进行连接。要修改的话需要修改redis.conf 修改bin bin 0.0.0.0 表示允许所有的ip远程访问 修改protected-mode protected-mode no 修改完成之后再使用连接工具RedisDesktopManager尝试连接 停止1./redis-cli shutdown 集群版本redis集群中至少应该有三个节点。要保证集群的高可用,需要每个节点有一个备份机。 所以至少需要6台服务器,复制出来6个实例,设置端口号7001-7006 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566# 安装rubyyum install rubyyuminstall rubygems# 安装gem-redis 本机下载安装https://rubygems.org/gems/redis/versions/3.2.2# 下载完上传到centosgem install redis-3.2.2.gem# 新建集群目录cd /usr/localmkdir redis-cluster# 查看安装后的rb文件cd /usr/local/redis-3.2.5/src/ll *.rb# 复制到redis-cluster下面cp *.rb /usr/local/redis-cluster/# 复制单机版的实例到集群目录下面cd /usr/local/rediscp -r bin redis-cluster/redis01# 修改端口号 开启集群配置cluster-enabled去掉注释,值为yesvim redis-conf# 复制出来其它五个实例cp -r redis01 redis02......# 启动每一个实例,通过脚本批量启动vim start-all.shcd redis01./redis-server redis.confcd ..cd redis02./redis-server redis.confcd ..cd redis03./redis-server redis.confcd ..cd redis04./redis-server redis.confcd ..cd redis05./redis-server redis.confcd ..cd redis06./redis-server redis.confcd ..# 添加执行权限chmod +x start-all.sh# 使用ruby脚本搭建集群,外部访问时需要开放端口号# --replicas 1:为集群中的每个主节点创建一个从节点./redis-trib.rb create --replicas 1 192.168.80.129:7001 192.168.80.129:7002 192.168.80.129:7003 192.168.80.129:7004 192.168.80.129:7005 192.168.80.129:7006# 此时有可能会报node is not empty的错误,需要删除节点目录下面的dump.rdb和nodes.conf# 这是单机版本节点的数据库文件,删除后重启启动执行即可# 连接集群,连接之后set一个key,切换一个集群端口去查看这个keyredis01/redis-cli -p 7002 -cset abc 123redis01/redis-cli -p 7005 -cget abc# 关闭集群vim shutdown-all.sh./redis01/redis-cli -p 7001 shutdown./redis01/redis-cli -p 7002 shutdown./redis01/redis-cli -p 7003 shutdown./redis01/redis-cli -p 7004 shutdown./redis01/redis-cli -p 7005 shutdown./redis01/redis-cli -p 7006 shutdown# 添加权限chmod +x shutdown-all.sh docker容器中启动redis 使用外部的配置启动,并且将数据保存在外部的data目录 1docker run -p 6379:6379 --name myredis -v /usr/local/myredis/conf/redis.conf:/etc/redis/redis.conf -v /usr/local/myredis/data:/data -d redis:3.2 redis-server /etc/redis/redis.conf 参考: redis集群搭建 redis集群搭建报node is not emtpy redis集群的搭建 redis命令参考-集群教程","categories":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/categories/linux/"}],"tags":[{"name":"redis","slug":"redis","permalink":"http://blog.xxyxpy.pub/tags/redis/"}]},{"title":"jdk mysql环境搭建","slug":"linux/jdk mysql tomcat环境搭建","date":"2017-12-25T07:44:37.129Z","updated":"2017-12-25T07:44:37.154Z","comments":true,"path":"2017/12/25/linux/jdk mysql tomcat环境搭建/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/linux/jdk mysql tomcat环境搭建/","excerpt":"jdk a 卸载原来的openjdk 123rpm -qa |grep java# 如果有openjdk相关的包,全部删除掉。使用如下的命令rpm -e --nodeps 具体的包名 b 解压jdk到指定的目录 123cd /usr/localmkdir javatar -zxf jdk1.8.0_131.tar.gz","text":"jdk a 卸载原来的openjdk 123rpm -qa |grep java# 如果有openjdk相关的包,全部删除掉。使用如下的命令rpm -e --nodeps 具体的包名 b 解压jdk到指定的目录 123cd /usr/localmkdir javatar -zxf jdk1.8.0_131.tar.gz c 设置环境变量 1234567891011121314vim /etc/profile# 添加如下的信息# set java environmentexport JAVA_HOME=/usr/local/java/jdk1.8.0_131export JRE_HOME=${JAVA_HOME}/jreexport CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/libexport PATH=${JAVA_HOME}/bin:${PATH}# 刷新配置source /etc/profile# 查看java版本java -version mysql https://www.cnblogs.com/starof/p/4680083.html a 卸载自带的mysql 123rpm -qa |grep mysql# 如果有mysql相关的包,全部删除掉。使用如下的命令rpm -e --nodeps 具体的包名 b 解压mysql到指定的目录 123cd /usr/localmkdir mysqltar -xvf MySQL-5.6.22-1.el6.i686.rpm-bundle.tar -C /usr/local/mysql c 安装依赖项 12yum -y install libaio.so.1 libgcc_s.so.1 libstdc++.so.6yum update libstdc++-4.4.7-4.el6.x86_64 d 安装服务端 1rpm -ivh MySQL-server-5.6.22-1.el6.i686.rpm e 安装客户端 1rpm -ivh MySQL-client-5.6.22-1.el6.i686.rpm f 启动mysql服务 12service mysql statusservice mysql start g 登录 服务启动时生成了一个随机密码在/root/.mysql_secret可以进行修改 12345msyql –u root -p# 登录成功之后修改密码mysql> use mysql;mysql> UPDATE user SET Password = PASSWORD('newpass') WHERE user = 'root';mysql> FLUSH PRIVILEGES; h 设置开机自启动 1234# 加入到系统服务:chkconfig --add mysql# 自动启动:chkconfig mysql on i 开启远程服务 123# 默认root是不支持远程登录的grant all privileges on *.* to 'root' @'%' identified by '123456';flush privileges; j 设置防火墙 123456# 设置对3306端口进行放行,否则本机外无法进行连接/sbin/iptables -I INPUT -p tcp --dport 3306 -j ACCEPT/etc/rc.d/init.d/iptables save/etc/rc.d/init.d/iptables restart #重启服务 # 查看端口是否已经开放 /etc/init.d/iptables status","categories":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/tags/linux/"},{"name":"jdk","slug":"jdk","permalink":"http://blog.xxyxpy.pub/tags/jdk/"},{"name":"mysql","slug":"mysql","permalink":"http://blog.xxyxpy.pub/tags/mysql/"}]},{"title":"docker","slug":"linux/docker","date":"2017-12-25T07:44:20.398Z","updated":"2018-01-15T05:44:02.732Z","comments":true,"path":"2017/12/25/linux/docker/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/linux/docker/","excerpt":"安装在centos中安装时内核需要在3.10以上,所以必须要centos7以上版本 os的下载可以访问阿里云 安装教程可参考鸟哥的linux 12345678910# 查看系统内核版本uname -r# 安装yum -y install docker# 更新yum -y update docker# 启动service docker start# docker 开机启动systemctl enable docker 设置镜像加速进入阿里云镜像仓库设置,通过修改daemon配置文件/etc/docker/daemon.json来使用加速器:","text":"安装在centos中安装时内核需要在3.10以上,所以必须要centos7以上版本 os的下载可以访问阿里云 安装教程可参考鸟哥的linux 12345678910# 查看系统内核版本uname -r# 安装yum -y install docker# 更新yum -y update docker# 启动service docker start# docker 开机启动systemctl enable docker 设置镜像加速进入阿里云镜像仓库设置,通过修改daemon配置文件/etc/docker/daemon.json来使用加速器: 12345678sudo mkdir -p /etc/dockersudo tee /etc/docker/daemon.json <<-'EOF'{ "registry-mirrors": ["https://xxx.mirror.aliyuncs.com"]}EOFsudo systemctl daemon-reloadsudo systemctl restart docker 登录docker hub类似于maven的仓库,需要先在官方网站申请帐号。然后进行登录 12345678# logindocker login# 下载java镜像 docker pull 镜像名docker pull java# 查看本地所有镜像docker images# 镜像检索 docker search 镜像名docker search redis 镜像操作12345678910# 查看本地所有镜像docker images# 下载镜像 docker pull 镜像名docker pull java# 镜像检索 docker search 镜像名docker search redis# 删除 docker rmi image-id# 删除所有docker rmi ${docker images -q} 容器操作运行命令docker run --name container-name -d image-name –name 参数是为容器取的名字 -d 表示detached,执行完这句命令后控制台不会被独占 image-name 指要使用哪个镜像来运行容器 假如运行redis,相当于添加到容器列表 docker run –name test-redis -d redis 容器启动主要通过docker run + 可选参数 + 容器编号/容器名 + 启动命令 如: docker run hello-world -d 后台启动容器,默认情况下启动容器是非后台启动,启动以后会自动进入容器内部,而且一旦退出容器,容器也会自动关闭。加上该命令后,使用后台启动的方式启动容器,启动以后不会自动进入容器内部,只会返回容器编号。容器在后台自动运行。 -p port:port 将指定容器的端口号映射为机器的端口号,前一个port为主机的端口号,后一个port为容器的端口号。 -v path:path 将主机的一个硬盘目录或文件挂载到容器的指定位置。前一个path为主机的目录路径或文件,可以为绝对路径,也可以是一个自定义名称,如果是自定义名称,你可以在/var/lib/docker/volumes目录下找到;后一个path为容器内的路径,一般是绝对路径。 --name 给容器起一个别名 介绍了启动容器的基本命令,我们试着来直接启动一个java容器。 12> docker run -d -it -p 8080:8080 -v /data/test.jar:/data/test.jar --name java docker.io/java java -jar /data/test.jar> 容器开机启动 1docker run --restart=always --name first-mysql -p 3306:3306 -e MYSQL\\_ROOT\\_PASSWORD=123456 -d mysql:latest –restart :参数来设置容器开机启动。no-container:不重启on-failure-container:退出状态非0时重启always:始终重启 如果创建时未指定自启动可以通过以下命令添加,详情使用参考官方说明 1docker update --restart=always 容器标识 查看运行中的容器列表 docker ps 查看运行和停止状态的容器列表 docker ps -a 查看容器占用的大小 docker ps -a -s 停止容器 docker stop container-name/container-id docker stop test-redis 启动容器docker start container-name/container-id docker start test-redis 端口映射 容器中运行的软件所使用的端口在本机和本机的局域网内是不能访问的,需要将dicker容器中的端口映射到当前主要的端口上。 映射容器的6379端口到本地的6378端口 docker run -d -p 6378:6379 --name port-redis -d redis 删除容器 单个docker rm container-id 所有docker rm ${docker ps -a -q} 容器日志 指定容器的日志 docker logs container-name/container-id eg:dokcer logs port-redis 查看最近的容器日志 docker logs 容器标识 -f 登录容器运行中的容器产是一个功能完备的linux操作系统,可以像常规系统一样登录并访问容器,并可以通过exit退出。命令如下: docker exec -it container-name/container-id bash 在容器中安装vim 先执行apt-get update,这个命令的作用是:同步 /etc/apt/sources.list 和 /etc/apt/sources.list.d 中列出的源的索引,这样才能获取到最新的软件包. 等更新完毕以后再敲命令:apt-get install vim命令即可. DockerfileFROM 指令:指明当前镜像继承的基镜像。编译当前镜像时会自动下载基镜像 eg:FROM unbuntu MAINTAINER 指令:指明当前镜像的作者 eg:MAINTAINER wyf RUN 指令:可以在当前镜像上执行Linux命令并形成一个新的层。RUN是编译时的动作 eg:RUN /bin/bash -c "echo helloworld" 或 RUN ["/bin/bash", "-c", "echo hello"] CMD 指令:指明启动镜像容器时的默认行为。一个dockerFile只能有一个cmd指令。cmd指令里设定的命令可以在运行镜像时使用参数覆盖。CMD是运行run的动作 eg:CMD echo "this is a test" EXPOSE 指令:指定容器在运行时监听的端口 eg:EXPOSE 8080 ENV 指令:用来设置环境变量 eg:ENV myName=wyf 或 ENV myName wyf ADD 指令:从当前工作目录复制文件到镜像目录中 eg:ADD test.txt /mydir/ ENTRYPOINT 指令:配置给容器一个可执行的命令,这意味着在每次使用镜像创建容器时一个特定的应用程序可以被设置为默认程序。同时也意味着该镜像每次被调用时仅能运行指定的应用。类似于CMD,Docker只允许一个ENTRYPOINT,多个ENTRYPOINT会抵消之前所有的指令,只执行最后的ENTRYPOINT指令。 eg:ENTRYPOINT ["executable", "param1","param2"]ENTRYPOINT command param1 param2 WORKDIR 指令:指定RUN CMD 与 ENTRYPOINTT命令的工作目录 eg:WORKDIR /path/to/workdir USER 指令:镜像正在运行时设置一个UID eg: USER 1241123123 VOLUME 指令:授权访问从容器内到主机上的目录 eg: VOLUME ["/data"] 1234567891011121314151617181920212223# 上传一个编译好的jar到centos# 在同级目录下面新建Dockerfile文件,内容如下:# 基镜像为java8FROM java:8# 设置作者MAINTAINER wyf# 将restful.jar添加到镜像中,重命名为restful.jarADD restful.jar restful.jar# 运行镜像的容器,端口为8077EXPOSE 8077# 启动时运行 java -jar restful.jarENTRYPOINT ["java","-jar","/restful.jar"]# 编译镜像 xxyxpy/restful 为镜像名称 .表示DockerFile的路径为当前目录docker build -t xxyxpy/restful .# 编译完成之后可以查看本地的镜像docker images# 运行编译完成的镜像 映射容器的8080端口(restful.jar的启动端口)到服务器的8077端口docker run -d --name restful -p 8077:8080 xxyxpy/restful# 此时可以通过服务器的ip:8077进行访问 docker 安装 mysql MySQL 官方Docker镜像的使用 更多docker命令","categories":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/categories/linux/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://blog.xxyxpy.pub/tags/docker/"}]},{"title":"ActiveMq环境搭建","slug":"linux/activeMq环境搭建","date":"2017-12-25T07:44:04.259Z","updated":"2018-01-04T01:35:01.790Z","comments":true,"path":"2017/12/25/linux/activeMq环境搭建/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/linux/activeMq环境搭建/","excerpt":"环境部署activemq运行需要有jdk环境。如果环境中安装的是jdk1.8则需要安装5.15.0及以上版本,因为从此版本开始是基于1.8进行编译的。如果使用低版本mq服务器页面显示可能会有问题。 5.15.0版本 链接: https://pan.baidu.com/s/1o8brZBG 密码: 7juz a 解压压缩包到指定的目录。使用./activemq start启动服务。 b 服务启动后可以进行管理后台,链接为http://ip:8161/admin.登录的用户名和密码都是admin c 如果在管理后台访问时遇到503错误,需要修改hosts文件","text":"环境部署activemq运行需要有jdk环境。如果环境中安装的是jdk1.8则需要安装5.15.0及以上版本,因为从此版本开始是基于1.8进行编译的。如果使用低版本mq服务器页面显示可能会有问题。 5.15.0版本 链接: https://pan.baidu.com/s/1o8brZBG 密码: 7juz a 解压压缩包到指定的目录。使用./activemq start启动服务。 b 服务启动后可以进行管理后台,链接为http://ip:8161/admin.登录的用户名和密码都是admin c 如果在管理后台访问时遇到503错误,需要修改hosts文件 1234567# 查看机器名cat /etc/sysconfig/network# 将机器添加到hosts文件中vim /etc/hostslocalhost localhost.localdomain localhost4 localhost4.localdomain4 myCentos# 重启服务./activemq restart 简单使用由于一些版本的jar中包含有spring中的类,容易导致在引用和加载容器的时候发生冲突问题难以被排查。建议使用此5.11.2版本的jar包 引用jar包 12345<dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId> <version>5.11.2</version></dependency> 测试类,主要测试queue(点对点)和topic(广播) 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165import org.apache.activemq.ActiveMQConnectionFactory;import org.junit.Test;import javax.jms.*;public class TestActiveMq { /** * 发送queue * @throws Exception */ @Test public void testQueueProducer() throws Exception { //1.创建一个连接工厂对象ConnectionFactory对象。需要指定mq服务的ip及端口 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(\"tcp://192.168.88.129:61616\"); //2.使用ConnectionFactory创建一个连接Connection对象 Connection connection = connectionFactory.createConnection(); //3.开启连接。调用Connection对象的start方法 connection.start(); //4.使用Connection对象创建一个Session对象 //第一个参数是是否开启事务,一般不使用事务。保证数据的最终一致,可以使用消息队列实现。 //如果第一个参数为true,第二个参数自动忽略。如果不开启事务false,第二个参数为消息的应答模式。一般自动应答就可以。 Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.使用Session对象创建一个Destination对象,两种形式queue、topic。现在应该使用queue //参数就是消息队列的名称 Queue queue = session.createQueue(\"test-queue\"); //6.使用Session对象创建一个Producer对象 MessageProducer producer = session.createProducer(queue); //7.创建一个TextMessage对象 //TextMessage textMessage = new ActiveMQTextMessage(); //textMessage.setText(\"hello activeMq\"); TextMessage textMessage = session.createTextMessage(\"hello activeMq\"); //8.发送消息 producer.send(textMessage); //9.关闭资源 producer.close(); session.close(); connection.close(); } /** * 接收queue * @throws Exception */ @Test public void testQueueConsumer() throws Exception { //1.创建一个连接工厂对象ConnectionFactory对象。需要指定mq服务的ip及端口 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(\"tcp://192.168.88.129:61616\"); //2.使用ConnectionFactory创建一个连接Connection对象 Connection connection = connectionFactory.createConnection(); //3.开启连接。调用Connection对象的start方法 connection.start(); //4.使用Connection对象创建一个Session对象 //第一个参数是是否开启事务,一般不使用事务。保证数据的最终一致,可以使用消息队列实现。 //如果第一个参数为true,第二个参数自动忽略。如果不开启事务false,第二个参数为消息的应答模式。一般自动应答就可以。 Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.使用Session对象创建一个Destination对象,两种形式queue、topic。现在应该使用queue //参数就是消息队列的名称 Queue queue = session.createQueue(\"test-queue\"); //6.使用Session创建一个Consumer对象 MessageConsumer consumer = session.createConsumer(queue); //7.向Consumer对象中设置一个MessageListener对象,用来接收消息 consumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { if (message instanceof TextMessage) { try { System.out.println(((TextMessage) message).getText()); } catch (JMSException e) { e.printStackTrace(); } } } }); // 系统等待消息 //while (true) { // Thread.sleep(100); //} System.in.read(); // 关闭资源 consumer.close(); session.close(); connection.close(); } /** * 发送topic * @throws Exception */ @Test public void testTopicProducer() throws Exception { //1.创建一个连接工厂对象ConnectionFactory对象。需要指定mq服务的ip及端口 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(\"tcp://192.168.88.129:61616\"); //2.使用ConnectionFactory创建一个连接Connection对象 Connection connection = connectionFactory.createConnection(); //3.开启连接。调用Connection对象的start方法 connection.start(); //4.使用Connection对象创建一个Session对象 //第一个参数是是否开启事务,一般不使用事务。保证数据的最终一致,可以使用消息队列实现。 //如果第一个参数为true,第二个参数自动忽略。如果不开启事务false,第二个参数为消息的应答模式。一般自动应答就可以。 Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.使用Session对象创建一个Destination对象,两种形式queue、topic。现在应该使用queue //参数就是消息队列的名称 Topic topic = session.createTopic(\"test-topic\"); //6.使用Session对象创建一个Producer对象 MessageProducer producer = session.createProducer(topic); //7.创建一个TextMessage对象 //TextMessage textMessage = new ActiveMQTextMessage(); //textMessage.setText(\"hello activeMq\"); TextMessage textMessage = session.createTextMessage(\"topic activeMqaaa\"); //8.发送消息 producer.send(textMessage); //9.关闭资源 producer.close(); session.close(); connection.close(); } /** * 接收topic * @throws Exception */ @Test public void testTopicConsumer() throws Exception { //1.创建一个连接工厂对象ConnectionFactory对象。需要指定mq服务的ip及端口 ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(\"tcp://192.168.88.129:61616\"); //2.使用ConnectionFactory创建一个连接Connection对象 Connection connection = connectionFactory.createConnection(); //3.开启连接。调用Connection对象的start方法 connection.start(); //4.使用Connection对象创建一个Session对象 //第一个参数是是否开启事务,一般不使用事务。保证数据的最终一致,可以使用消息队列实现。 //如果第一个参数为true,第二个参数自动忽略。如果不开启事务false,第二个参数为消息的应答模式。一般自动应答就可以。 Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //5.使用Session对象创建一个Destination对象,两种形式queue、topic。现在应该使用queue //参数就是消息队列的名称 Topic topic = session.createTopic(\"test-topic\"); //6.使用Session创建一个Consumer对象 MessageConsumer consumer = session.createConsumer(topic); //7.向Consumer对象中设置一个MessageListener对象,用来接收消息 consumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { if (message instanceof TextMessage) { try { System.out.println(((TextMessage) message).getText()); } catch (JMSException e) { e.printStackTrace(); } } } }); // 系统等待消息 //while (true) { // Thread.sleep(100); //} System.out.println(\"topic consumer3\"); System.in.read(); // 关闭资源 consumer.close(); session.close(); connection.close(); } } 整合springtopic模式实例 生产者配置1234567891011121314151617181920212223242526272829303132333435<beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:context=\"http://www.springframework.org/schema/context\" xmlns:p=\"http://www.springframework.org/schema/p\" xmlns:aop=\"http://www.springframework.org/schema/aop\" xmlns:tx=\"http://www.springframework.org/schema/tx\" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd\"> <!-- jsm服务厂商提供的ConnectionFactory --> <bean class=\"org.apache.activemq.ActiveMQConnectionFactory\" id=\"mqConnectionFactory\"> <constructor-arg name=\"brokerURL\" value=\"tcp://192.168.80.128:61616\"/> </bean> <!-- spring对ConnectionFactory的封装 --> <!--<bean class=\"org.springframework.jms.connection.SingleConnectionFactory\" id=\"connectionFactory\">--> <!--<property name=\"targetConnectionFactory\" ref=\"mqConnectionFactory\"/>--> <!--</bean>--> <!-- 配置JmsTemplate --> <bean class=\"org.springframework.jms.core.JmsTemplate\" id=\"jmsTemplate\"> <property name=\"connectionFactory\" ref=\"mqConnectionFactory\"/> </bean> <!-- 配置消息的destination对象 --> <bean class=\"org.apache.activemq.command.ActiveMQQueue\" id=\"test-queue\"> <constructor-arg name=\"name\" value=\"test-queue\"/> </bean> <bean class=\"org.apache.activemq.command.ActiveMQTopic\" id=\"test-topic\"> <constructor-arg name=\"name\" value=\"test-topic\"/> </bean> <bean class=\"org.apache.activemq.command.ActiveMQTopic\" id=\"itemAddtopic\"> <constructor-arg name=\"name\" value=\"item-add-topic\"/> </bean></beans> 消费者配置123456789101112131415161718192021222324252627282930313233343536373839404142434445464748<beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:context=\"http://www.springframework.org/schema/context\" xmlns:p=\"http://www.springframework.org/schema/p\" xmlns:aop=\"http://www.springframework.org/schema/aop\" xmlns:tx=\"http://www.springframework.org/schema/tx\" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd\"> <!-- jsm服务厂商提供的ConnectionFactory --> <bean class=\"org.apache.activemq.ActiveMQConnectionFactory\" id=\"mqConnectionFactory\"> <constructor-arg name=\"brokerURL\" value=\"tcp://192.168.80.128:61616\"/> </bean> <!-- spring对ConnectionFactory的封装 --> <bean class=\"org.springframework.jms.connection.SingleConnectionFactory\" id=\"connectionFactory\"> <property name=\"targetConnectionFactory\" ref=\"mqConnectionFactory\"/> </bean> <!-- 配置消息的destination对象 --> <bean class=\"org.apache.activemq.command.ActiveMQQueue\" id=\"test-queue\"> <constructor-arg name=\"name\" value=\"test-queue\"/> </bean> <bean class=\"org.apache.activemq.command.ActiveMQTopic\" id=\"test-topic\"> <constructor-arg name=\"name\" value=\"test-topic\"/> </bean> <bean class=\"org.apache.activemq.command.ActiveMQTopic\" id=\"itemAddtopic\"> <constructor-arg name=\"name\" value=\"item-add-topic\"/> </bean> <!-- 配置消息的接收者 --> <bean class=\"com.taotao.activemq.listener.MyMessageListener\" id=\"myMessageListener\"/> <bean class=\"org.springframework.jms.listener.DefaultMessageListenerContainer\"> <!--<property name=\"connectionFactory\" ref=\"connectionFactory\"/>--> <property name=\"connectionFactory\" ref=\"mqConnectionFactory\"/> <property name=\"destination\" ref=\"test-queue\"/> <property name=\"messageListener\" ref=\"myMessageListener\"/> </bean> <!-- 同步索引库 --> <bean class=\"com.taotao.activemq.listener.ItemAddMessageListener\" id=\"itemAddMessageListener\"/> <bean class=\"org.springframework.jms.listener.DefaultMessageListenerContainer\"> <!--<property name=\"connectionFactory\" ref=\"connectionFactory\"/>--> <property name=\"connectionFactory\" ref=\"mqConnectionFactory\"/> <property name=\"destination\" ref=\"itemAddtopic\"/> <property name=\"messageListener\" ref=\"itemAddMessageListener\"/> </bean> </beans> junit测试添加消息监听者 MyMessageListener 123456789101112131415161718192021import javax.jms.Message;import javax.jms.MessageListener;import javax.jms.TextMessage;/** * 接收消息测试 * Created on 2017/9/25. */public class MyMessageListener implements MessageListener { @Override public void onMessage(Message message) { try { TextMessage textMessage = (TextMessage) message; String text = textMessage.getText(); System.out.println(text); } catch (Exception e) { e.printStackTrace(); } }} 测试类,消费者只要开启就会一直监听是否有消息 123456789101112131415161718192021222324252627282930313233343536373839404142434445import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.jms.core.JmsTemplate;import org.springframework.jms.core.MessageCreator;import javax.jms.*;/** * 整合spring * Created on 2017/9/25. */public class TestActiveMqSpring { /** * 生产者 * @throws Exception */ @Test public void testJmsTemplate() throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\"classpath:spring/applicationContext-activemq.xml\"); JmsTemplate jmsTemplate = applicationContext.getBean(JmsTemplate.class); // 获取destination对象 Destination destination = (Destination) applicationContext.getBean(\"test-queue\"); // send jmsTemplate.send(destination, new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { TextMessage textMessage = session.createTextMessage(\"spring activemq send queue message3\"); return textMessage; } }); } /** * 消费者 * @throws Exception */ @Test public void testJmsTemplateConsumer() throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\"classpath:spring/applicationContext-activemq-receiver.xml\"); System.in.read(); } } 商品添加时同步索引库添加消息监听者 ItemAddMessageListener 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950import com.taotao.common.pojo.SearchItem;import com.taotao.search.mapper.SearchItemMapper;import org.apache.solr.client.solrj.SolrServer;import org.apache.solr.common.SolrInputDocument;import org.springframework.beans.factory.annotation.Autowired;import javax.jms.Message;import javax.jms.MessageListener;import javax.jms.TextMessage;/** * 监听商品添加事件,接收到消息的处理 * Created on 2017/9/25. */public class ItemAddMessageListener implements MessageListener { @Autowired private SearchItemMapper searchItemMapper; @Autowired private SolrServer solrServer; @Override public void onMessage(Message message) { try { // 取商品id TextMessage textMessage = (TextMessage) message; String text = textMessage.getText(); long itemId = Long.valueOf(text); //根据商品id查询数据,取商品信息 //等待事务提交 或者把消息发送放在表现层 Thread.sleep(1000); SearchItem searchItem = searchItemMapper.getItemById(itemId); SolrInputDocument document = new SolrInputDocument(); // 操作文档 document.addField(\"id\", searchItem.getId()); document.addField(\"item_title\", searchItem.getTitle()); document.addField(\"item_sell_point\", searchItem.getSell_point()); document.addField(\"item_price\", searchItem.getPrice()); document.addField(\"item_image\", searchItem.getImage()); document.addField(\"item_category_name\", searchItem.getCategory_name()); document.addField(\"item_desc\", searchItem.getItem_desc()); solrServer.add(document); solrServer.commit(); } catch (Exception e) { e.printStackTrace(); } }} 添加商品时发送消息,部分代码展示 123456789101112131415161718192021222324252627@Autowiredprivate JmsTemplate jmsTemplate;@Resource(name = \"itemAddtopic\")private javax.jms.Destination destination;@Overridepublic TaotaoResult addItem(TbItem item, String desc) { // 生成商品id Long itemId = IDUtils.genItemId(); ... // 插入到商品表 tbItemMapper.insert(item); // 插入到商品描述表 ... tbItemDescMapper.insert(tbItemDesc); // 向activemq发送商品添加信息 jmsTemplate.send(destination, new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { TextMessage message = session.createTextMessage(itemId.toString()); return message; } }); return TaotaoResult.ok();}","categories":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/tags/linux/"},{"name":"activemq","slug":"activemq","permalink":"http://blog.xxyxpy.pub/tags/activemq/"}]},{"title":"ssh整合","slug":"java/ssh整合","date":"2017-12-25T07:43:49.050Z","updated":"2017-12-25T07:43:49.593Z","comments":true,"path":"2017/12/25/java/ssh整合/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/java/ssh整合/","excerpt":"需要的jar包整合前需要导入三个框架的jar包,如果是使用的maven管理直接配置pom.xml文件即可 Struts2 14个jar struts-2.3.24\\apps\\struts2-blank\\WEB-INF\\lib*.jar – Struts2需要的所有jar包 struts2-spring-plugin-2.3.24.jar —Struts2整合Spring的插件包,在spring中提供","text":"需要的jar包整合前需要导入三个框架的jar包,如果是使用的maven管理直接配置pom.xml文件即可 Struts2 14个jar struts-2.3.24\\apps\\struts2-blank\\WEB-INF\\lib*.jar – Struts2需要的所有jar包 struts2-spring-plugin-2.3.24.jar —Struts2整合Spring的插件包,在spring中提供 Hibernate 20个jar hibernate-release-5.0.7.Final\\lib\\required*.jar – Hibernate框架需要的jar包 slf4j-api-1.6.1.jar – 日志接口 slf4j-log4j12-1.7.2.jar – 日志实现 mysql-connector-java-5.1.7-bin.jar – MySQL的驱动包 Spring IOC核心包 AOP核心包 JDBC模板和事务核心包 Spring整合JUnit测试包 Spring整合Hibernate核心包 Spring整合Struts2核心包","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"ssh","slug":"java/ssh","permalink":"http://blog.xxyxpy.pub/categories/java/ssh/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"}]},{"title":"Intellij IDEA","slug":"ide/Intellij IDEA","date":"2017-12-25T07:43:06.548Z","updated":"2017-12-25T07:43:06.958Z","comments":true,"path":"2017/12/25/ide/Intellij IDEA/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/ide/Intellij IDEA/","excerpt":"设置->keymap中有中文偶然发现这篇博文各种乱码解决方案试了一下问题竟然解决了 需要修改idea64.exe.vmoptions文件,如果是用的32位的修改idea.exe.vmoptions。添加如下内容 -Duser.country=EN -Duser.language=us 添加后重启idea,显示为英文。问题解决","text":"设置->keymap中有中文偶然发现这篇博文各种乱码解决方案试了一下问题竟然解决了 需要修改idea64.exe.vmoptions文件,如果是用的32位的修改idea.exe.vmoptions。添加如下内容 -Duser.country=EN -Duser.language=us 添加后重启idea,显示为英文。问题解决 spring-boot-devtools热部署无效 打开设置,compiler中的Build project automatically需要勾中 Shift+Ctrl+Alt+/,选择Registry。勾中compiler.automake.allow.when.app.running spring boot远程调试如果要进行远程调试必须要保证远程服务器端和本地的代码相同。 // 在使用java指令启动程序时需要附加额外的参数以开启外部调试,如下-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8888(自定义调试端口) // 完整的启动指令是类似下面java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8888(自定义调试端口) -jar application.jar 在idea中编辑配置添加remote,输入远程的ip和端口号。然后启动debug模式 tomcat 远程调试关于前提条件和idea中的操作方式与上面的spring boot中调试一致,需要修改的就是服务端tomcat中的配置。 在服务器Tomcat的bin目录下的catalina.sh中增加如下配置 // windowsset CATALINA_OPTS=”-agentlib:jdwp=transport=dt_socket,address=8888(自定义调试端口),server=y,suspend=n %CATALINA_OPTS%” // linuxexport CATALINA_OPTS=”-agentlib:jdwp=transport=dt_socket,address= 8888(自定义调试端口),server=y,suspend=n $CATALINA_OPTS” tomcat热部署使用maven实现tomcat热部署 修改tomcat的conf/tomcat-users.xml配置文件。添加用户名、密码、权限,配置完成之后需要重启tomcat 123<role rolename=\"manager-gui\" /><role rolename=\"manager-script\" /><user username=\"tomcat\" password=\"tomcat\" roles=\"manager-gui, manager-script\"/> 配置tomcat插件,需要修改pom.xml文件 123456789101112131415161718<build> <plugins> <!-- 配置Tomcat插件 --> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <configuration> <port>8081</port> <!-- / 为webapps下面的root目录 --> <path>/</path> <!-- tomcat服务地址 --> <url>http://192.168.25.135:8080/manager/text</url> <username>tomcat</username> <password>tomcat</password> </configuration> </plugin> </plugins></build> 使用maven命令部署 1234# 首次部署,目录为空时tomcat7:deploy# 重新部署,目录不为空tomcat7:redeploy","categories":[{"name":"IDE","slug":"IDE","permalink":"http://blog.xxyxpy.pub/categories/IDE/"}],"tags":[{"name":"Intellij","slug":"Intellij","permalink":"http://blog.xxyxpy.pub/tags/Intellij/"}]},{"title":"ashx获取post的json","slug":"csharp/ashx获取post的json","date":"2017-12-25T07:43:02.231Z","updated":"2017-12-25T07:43:02.630Z","comments":true,"path":"2017/12/25/csharp/ashx获取post的json/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/csharp/ashx获取post的json/","excerpt":"json请求数据 1234{ \"tianyantag\": \"common\", \"mytoken\": \"123456\"} ashx代码","text":"json请求数据 1234{ \"tianyantag\": \"common\", \"mytoken\": \"123456\"} ashx代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758public class WeixinToken : IHttpHandler { public void ProcessRequest(HttpContext context) { // 流转json字符串 String postJson = ReadRequestData(context.Request.InputStream); JObject jObject = JObject.Parse(postJson); // 取json中的值 String tianyantag = jObject["tianyantag"].ToString(); String mytoken = jObject["mytoken"].ToString(); // 序列化 String json = JsonConvert.SerializeObject(new { Code = 0, Message = "请求token错误", Token = ""}); // 输出响应信息 WriteBack(json); } /// <summary> /// 获取请求的json字符串 /// </summary> /// <param name="requestStream"></param> /// <returns></returns> public string ReadRequestData(Stream requestStream) { using (Stream stream = requestStream) { try { using (StreamReader strReader = new StreamReader(stream)) { return strReader.ReadToEnd(); } } catch (Exception ex) { return string.Empty; } } } public bool IsReusable { get { return false; } } private void WriteBack(String backStr) { HttpContext.Current.Response.ContentType = "text/plain"; HttpContext.Current.Response.ContentEncoding = Encoding.UTF8; HttpContext.Current.Response.Clear(); HttpContext.Current.Response.Write(backStr); HttpContext.Current.ApplicationInstance.CompleteRequest(); } }","categories":[{"name":"c#","slug":"c","permalink":"http://blog.xxyxpy.pub/categories/c/"}],"tags":[{"name":"ashx","slug":"ashx","permalink":"http://blog.xxyxpy.pub/tags/ashx/"}]},{"title":"software key","slug":"common/激活码","date":"2017-12-25T07:42:52.174Z","updated":"2018-07-13T02:20:12.234Z","comments":true,"path":"2017/12/25/common/激活码/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/common/激活码/","excerpt":"SQLyog 姓名(Name):cr173 序列号(Code):8d8120df-a5c3-4989-8f47-5afc79c56e7c 或者(OR) 姓名(Name):cr173 序列号(Code):59adfdfe-bcb0-4762-8267-d7fccf16beda 或者(OR) 姓名(Name):cr173 序列号(Code):ec38d297-0543-4679-b098-4baadf91f983 VS 2017 Visual Studio 2017(VS2017) 企业版Enterprise 注册码:NJVYC-BMHX2-G77MM-4XJMR-6Q8QF Visual Studio 2017(VS2017) 专业版Professional 激活码key:KBJFW-NXHK6-W4WJM-CRMQB-G3CDH","text":"SQLyog 姓名(Name):cr173 序列号(Code):8d8120df-a5c3-4989-8f47-5afc79c56e7c 或者(OR) 姓名(Name):cr173 序列号(Code):59adfdfe-bcb0-4762-8267-d7fccf16beda 或者(OR) 姓名(Name):cr173 序列号(Code):ec38d297-0543-4679-b098-4baadf91f983 VS 2017 Visual Studio 2017(VS2017) 企业版Enterprise 注册码:NJVYC-BMHX2-G77MM-4XJMR-6Q8QF Visual Studio 2017(VS2017) 专业版Professional 激活码key:KBJFW-NXHK6-W4WJM-CRMQB-G3CDH Sublime 3.x最新sublime text 3 注册码license after Build 3133,other version —– BEGIN LICENSE —–TwitterInc200 User LicenseEA7E-8900071D77F72E 390CDD93 4DCBA022 FAF6079061AA12C0 A37081C5 D0316412 4584D13694D7F7D4 95BC8C1C 527DA828 560BB037D1EDDD8C AE7B379F 50C9D69D B35179EF2FE898C4 8E4277A8 555CE714 E1FB0E43D5D52613 C3D12E98 BC49967F 7652EED29D2D2E61 67610860 6D338B72 5CF95C69E36B85CC 84991F19 7575D828 470A92AB—— END LICENSE —— SecureCRT安装及破解 VmWare 5A02H-AU243-TZJ49-GTC7K-3C61NVF5XA-FNDDJ-085GZ-4NXZ9-N20E6UC5MR-8NE16-H81WY-R7QGV-QG2D8ZG1WH-ATY96-H80QP-X7PEX-Y30V4AA3E0-0VDE1-0893Z-KGZ59-QGAVF EditPlus 注册名:host1991 序列号:14F50-CD5C8-E13DA-51100-BAFE6 注册名:360xw 注册码:93A52-85B80-A3308-BF130-40412 注册名:kariryo 注册码:5387D-12450-BCZ8B-D6W0B-85TE1 注册名:crsky 注册码:7879E-5BF58-7DR23-DAOB2-7DR30 注册名:zzidc 注册码:11276-E9720-D59E6-BD0E0-2965C 注册名:gainet 注册码:74E92-05B20-3D39C-90E5F-A55E8 注册名:kuaiyun 注册码:9152C-84978-1A3E3-64290-992FD","categories":[{"name":"激活码","slug":"激活码","permalink":"http://blog.xxyxpy.pub/categories/激活码/"}],"tags":[{"name":"激活码","slug":"激活码","permalink":"http://blog.xxyxpy.pub/tags/激活码/"}]},{"title":"SqlServer小技巧","slug":"database/SqlServer小技巧","date":"2017-12-25T07:42:39.966Z","updated":"2017-12-25T07:42:40.327Z","comments":true,"path":"2017/12/25/database/SqlServer小技巧/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/database/SqlServer小技巧/","excerpt":"删除重复的数据一般用于重复记录的处理,比如同一个酒店id存在多行,需要删除重复的只保留一行. 前提条件:需要有唯一主键信息","text":"删除重复的数据一般用于重复记录的处理,比如同一个酒店id存在多行,需要删除重复的只保留一行. 前提条件:需要有唯一主键信息 方法一:直接判断删除 12delete from table where id not in ( select min(id) from table group by HotelId) 方法二:使用临时表 1234with t as ( select id,ROW_NUMBER() over(partition by hotelid order by id) as rn from table)delete from table where id in (select id from t where rn > 1)","categories":[{"name":"database","slug":"database","permalink":"http://blog.xxyxpy.pub/categories/database/"}],"tags":[{"name":"sqlserver","slug":"sqlserver","permalink":"http://blog.xxyxpy.pub/tags/sqlserver/"},{"name":"套路","slug":"套路","permalink":"http://blog.xxyxpy.pub/tags/套路/"}]},{"title":"Hello World","slug":"hello-world","date":"2017-12-25T07:41:24.079Z","updated":"2017-12-25T07:41:24.437Z","comments":true,"path":"2017/12/25/hello-world/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/hello-world/","excerpt":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment","categories":[{"name":"myBlog","slug":"myBlog","permalink":"http://blog.xxyxpy.pub/categories/myBlog/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://blog.xxyxpy.pub/tags/hexo/"},{"name":"nexT","slug":"nexT","permalink":"http://blog.xxyxpy.pub/tags/nexT/"}]},{"title":"vps相关","slug":"linux/vps相关","date":"2017-12-25T07:26:54.806Z","updated":"2017-12-25T07:26:55.345Z","comments":true,"path":"2017/12/25/linux/vps相关/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/25/linux/vps相关/","excerpt":"路由测试 Linux中使用 路由追踪 测试VPS回程路由 服务器一键测试包(12月22日更新) wget 安装 1yum -y install wget ss开心版","text":"路由测试 Linux中使用 路由追踪 测试VPS回程路由 服务器一键测试包(12月22日更新) wget 安装 1yum -y install wget ss开心版 链接 魔改bbr加速 第一步安装最合适的内核,这里都指定v4.11.9 1yum install -y wget && wget --no-check-certificate -O C71.sh https://raw.githubusercontent.com/xratzh/CBBR/master/C71.sh && sudo bash C71.sh 安装新内核后,输入Y,系统会自动断开putty,重启一次,重新登录后启用新的内核。 第二步使用以下的脚本编译开启魔改的bbr。 1wget --no-check-certificate -O C72.sh https://raw.githubusercontent.com/xratzh/CBBR/master/C72.sh && sudo bash C72.sh 结束后,显示Finish表示正常,或者执行lsmod |grep 'bbr_powered' 结果不为空,则加载模块成功。 锐速安装 破解-官方地址91yun.co 优秀的VPS TCP加速软件 —— 一键锐速安装脚本(开心版) VPS/服务器优化网络、加速方法总结与参考 服务器一键测试包(12月22日更新) 1234# centos7需要更换内核,然后重启安装rpm -ivh http://soft.91yun.org/ISO/Linux/CentOS/kernel/kernel-3.10.0-229.1.2.el7.x86_64.rpm --forcewget -N --no-check-certificate https://raw.githubusercontent.com/91yun/serverspeeder/master/serverspeeder-all.sh && bash serverspeeder-all.sh# wget -N --no-check-certificate https://github.com/91yun/serverspeeder/raw/master/serverspeeder.sh && bash serverspeeder.sh 锐速优化 锐速参数优化及配置文件详解 锐速(ServerSpeeder/LotServer)配置参数简单说明,合理修改配置 提高加速效果 1234567891011121314151617181920212223# ServerSpeeder 打开配置文件:vi /serverspeeder/etc/config#发包增强 此值越高越激进,消耗的流量也越多,# 但其实也没有多少,建议48~64,过高有效包会减少,速度不增反减,# 不过对个别延迟和丢包不小的线路,可以调到600initialCwndWan="50"# 关闭使锐速间歇掉速度的罪魁祸首shaperEnable="0"acc="1"advacc="1"advinacc="1"wankbps="1000000"waninkbps="1000000"# 这两个参数代表加速宽带为 1Gbps,再加个 0 就是10Gbps了maxmode="1"initialCwndWan="44"# 这个参数请根据回程延迟修改# 保存重启锐速service serverSpeeder restart 设置vps定时重启 1234567891011121314151617# 安装 crontabyum install vixie-cron crontabschkconfig crond onservice crond start# 编辑定时脚本crontab -e# 每天5点定时重启0 5 * * * /sbin/reboot# 保存退出重启生效service crond stopservice crond start# 查看最后一次系统启动的时间。who -b # 查看当前系统运行时间who -r # 系统历史启动的时间last reboot 安装apache 123456789101112131415161718# 1.安装Apacheyum install httpd# 2.设置服务器开机自动启动Apachesystemctl enable httpd.service# 若要验证是否自动启动可在重启服务器后在终端键入以下命令来检测Apache是否已经启动systemctl is-enabled httpd.service# 如果看到了enable这样的响应,则表示Apache已经启动成功# 3.手动启动Apachesystemctl start httpd.service# 在浏览器中输入IP地址即可验证是否启动成功# 4.手动重启Apachesystemctl restart httpd.service# 5.手动停止Apachesystemctl stop httpd.service# 6.安装目录介绍# Apache默认将网站的根目录指向/var/www/html# 默认的主配置文件/etc/httpd/conf/httpd.conf# 配置存储在的/etc/httpd/conf.d/目录 安装php探针 必须要先有apache和php环境 Linux安装PHP探针 12345678910111213141516171819202122# 首先要安装Apahce 及 phpyum -y install httpd php# 下载探针wget http://www.yahei.net/tz/tz.zip# 创建目录mkdir -p /var/www/html# 移动到指定目录mv tz.zip /var/www/html# 解压unzip tz.zip# 重命名mv tz.php index.php# 修改apache的端口号vi /etc/httpd/conf/httpd.conf# 修改为指定端口如10000Listen 10000# 开放此端口firewall-cmd --add-port=10000/tcp# 重启apachesystemctl restart httpd.service# 访问你的ip:12345","categories":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/tags/linux/"},{"name":"vps","slug":"vps","permalink":"http://blog.xxyxpy.pub/tags/vps/"}]},{"title":"JavaScript(7)DOM对象","slug":"前端/JS/JavaScript 高级程序设计/JavaScript(7)DOM对象","date":"2017-12-17T07:01:16.000Z","updated":"2018-03-13T02:06:10.607Z","comments":true,"path":"2017/12/17/前端/JS/JavaScript 高级程序设计/JavaScript(7)DOM对象/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/17/前端/JS/JavaScript 高级程序设计/JavaScript(7)DOM对象/","excerpt":"DOM(Document Object Model,文档对象模型)是针对HTML和XML文档的一个API。它描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。 节点层次DOM可以将任何HTML或XML文档描绘成一个由多层节点构成的结构。节点分为几种不同的类型,每种类型分别表示文档中不同的信息及标记。每个节点都有各自的特点、数据和方法,另外也和其他节点存在某种关系。节点之间的关系构成了层次,而所有页面标记则表现为一个以特定节点为根节点为的树形结构。 12345678910<html> <head> <title>Sample Page</title> </head> <body> <p> Hello World! </p> </body></html> 文档节点(document)是每个文档的根节点。在上面的例子中文档节点只有一个子节点,即<html>元素,我们称之为文档元素。文档元素是文档的最外层元素,文档中的其他所有元素都包含在文档元素中。每个文档只能有一个文档元素。在HTML页面中,文档元素始终都是<html>元素。 每一段标记都可以通过树中的一个节点来表示:HTML元素通过元素节点表示,特性(attribute)通过特性节点表示,文档类型通过文档类型节点表示,而注释则通过注释节点表示。总共有12种节点类型,这些类型都继承自一个基类型。","text":"DOM(Document Object Model,文档对象模型)是针对HTML和XML文档的一个API。它描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。 节点层次DOM可以将任何HTML或XML文档描绘成一个由多层节点构成的结构。节点分为几种不同的类型,每种类型分别表示文档中不同的信息及标记。每个节点都有各自的特点、数据和方法,另外也和其他节点存在某种关系。节点之间的关系构成了层次,而所有页面标记则表现为一个以特定节点为根节点为的树形结构。 12345678910<html> <head> <title>Sample Page</title> </head> <body> <p> Hello World! </p> </body></html> 文档节点(document)是每个文档的根节点。在上面的例子中文档节点只有一个子节点,即<html>元素,我们称之为文档元素。文档元素是文档的最外层元素,文档中的其他所有元素都包含在文档元素中。每个文档只能有一个文档元素。在HTML页面中,文档元素始终都是<html>元素。 每一段标记都可以通过树中的一个节点来表示:HTML元素通过元素节点表示,特性(attribute)通过特性节点表示,文档类型通过文档类型节点表示,而注释则通过注释节点表示。总共有12种节点类型,这些类型都继承自一个基类型。 Node类型DOM1级定义了一个Node接口,该接口将由DOM中的所有节点类型实现。这个Node接口在JavaScript中是作为Node类型实现的。JavaScript中的所有节点类型都继承自Node类型,因此所有节点类型都共享着相同的基本属性和方法。每个节点都有一个nodeType属性,用于表明节点的类型。节点类型由在Node类型中定义的下列12个数值常量来表示,任何节点类型必居其一: Node.ELEMENT_NODE(1) Node.ATTRIBUTE_NODE(2) Node.TEXT_NODE(3) Node.CDATA_SECTION_NODE(4) Node.ENTITY_REFERENCE_NODE(5) Node.ENTITY_NODE(6) Node.PROCESSING_INSTRUCTION_NODE(7) Node.COMMENT_NODE(8) Node.DOCUMENT_NODE(9) Node.DOCUMENT_TYPE_NODE(10) Node.DOCUMENT_FRAGMENT_NODE(11) Node.NOTATION_NODE(12) 通过比较上面的这些常量,可以很容易的确定节点的类型。例如: 12345678// 确认是否是元素节点if(someNode.nodeType == Node.ELEMENT_NODE){ //...}// 对于不支持Node.ELEMENT_NODE常量的浏览器if(someNode.nodeType == 1){ //...} nodeName和nodeValue属性要了解节点的具体信息,可以使用nodeName和nodeValue这两个属性。这两个属性的值完全取决于节点的类型。在使用这两个值以前,最好是像下在这样先检测一下节点的类型。 123if(someNode.nodeType == 1){ value = someNode.nodeName; // nodeName 的值是元素的标签名} 在这个例子中,首先检查节点类型,看它是不是一个元素。如果是,则取得并保存nodeName的值。对于元素节点,nodeName中保存的始终都是元素的标签名,而nodeValue的值则始终为null。 节点关系 在HTML中可以将<body>元素看成是<html>元素的子元素,相应的可以将<html>元素看成是<body>元素的父元素。而<head元素则可以看成是<body>元素的同胞元素,因为它们都是同一个父元素<html>的直接子元素。 childNodes每个节点都有一个childNodes属性,其中保存着一个NodeList对象。NodeList是一种类数组对象(伪数组),用于保存一组有序的节点,可以通过位置来访问这些节点。虽然可以通过方括号语法来访问NodeList的值,而且这个对象也有length属性,但它并不是Array的实例。NodeList对象的独特之处在于,它实际上是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList对象中。我们常说,NodeList是有生命,有呼吸的对象,而不是在我们第一次访问他们的某个瞬间拍摄下来的一张快照。访问保存在NodeList中的节点可以通过方括号也可以使用item()方法。 123var firstChild = someNode.childNodes[0];var secondChild = someNode.childNodes.item(1);var count = someNode.childNodes.lenght; 注意lenght属性表示的是访问NodeList的那一刻,其中包含的结点数量。对arguments对象可以使用Array.prototype.slice()方法可以将其转换为数组。而采用同样的方法,也可以将NodeList对象转换为数组。 1var arrayOfNodes = Array.prototype.slice.call(someNode.childNods, 0); parentNode、previousSibling、nextSibling每个节点都有一个parentNode属性,该属性指向文档树中的父节点。包含在childNodes列表中的所有节点都具有相同的父节点,因此它们的parentNode属性都指向同一个节点。此外,包含在childNodes列表中的每个节点相互之间都是同胞节点。通过使用列表中的每个节点的previousSibling和nextSibling属性,可以访问同一列表中的其他节点。列表中第一个节点的previousSibling属性值为null,最后一个节点的nextSibling属性值同样也为null。 12345if(someNode.nextSibling === null){ alert('Last node');} else if (some.previousSibling === null) { alert('First node');} 当然,如果列表中只有一个节点那么该节点的previousSibling和nextSibling都为null。 firstChild、lastChild父节点与其第一个和最后一个子节点之间也存在特殊关系。父节点的firstChild和lastChild属性分别指向其childNodes列表中的第一个和最后一个节点。其中,someNode.firstChild的值始终等于someNode.childNodes[0],而someNode.lastChild的值始终等于someNode.childNodes[somNode.childNodes.length - 1]。在只有一个子节点的情况下,firstChild和lastChild指向同一个节点。如果没有子节点那么firstChild和lastChild值都为null。 hasChildNodes()hasChildNodes()方法可以用来确定一个节点是否包含子节点,包含时返回true,不包含时返回false。 ownerDocumentownerDocument是所有的节点都有的属性。该属性指向表示整个文档的文档节点。返回#document,即整个页面。这种关系表示的是任何节点都属于它所在的文档,任何节点都不能同时存在于两个或更多个文档中。通过这个属性,我们可以不必在节点层次中通过层层回溯达到顶端,而可以直接访问文档节点。 操作节点 appendChild()用于向childNodes列表的末尾添加一个节点。添加节点之后childNodes的新增节点、父节点以及以前的最后一个子节点的关系指针都会相应地得到更新。更新完成之后,appendChild()返回新增的节点。 123var returnedNode = someNode.appendChild(newNode);alert(returnedNode == newNode); // truealert(someNode.lastNode == newNode); // true 如果传入到appendChild()中的节点已经是文档的一部分了,那结果就是将该节点从原来的位置转移到新位置。即使可以将DOM树看成是由一系列指针连接起来的,但任何DOM节点都不能同时出现在文档中的多个位置上。因此,如果在调用appendChild()时传入了父节点的第一个子节点,那么该节点就会成为父节点的最后一个子节点。 123var returnedNode = someNode.appendChild(someNode.firstChild);alert(returnedNode == someNode.firstChild); // falsealert(returnedNode == someNode.lastChance); // true insertBefore()如果需要把节点放在childNodes列表中某个特定的位置上,而不是放在末尾,可以使用insertBefore()方法。这个方法接收两个参数:要插入的节点和作为参照的节点。插入完成之后,被插入的节点会变成参照节点的前一个同胞节点(previousSibling),同时被方法返回。如果参照节点是null则insertBefore()和appendChild()执行相同的操作。 12345678910// 插入后成为最后一个子节点var returnedNode = someNode.insertBefore(newNode, null);alert(newNode == someNode.lastChild);// true// 插入后成为第一个子节点returnedNode = someNode.insertBefore(newNode, someNode.firstChild);alert(returnedNode == newNode); // truealert(newNode == someNode.firstChild); //true// 插入到最后一个子节点前面returnedNode = someNode.insertBefore(newNode, someNode.lastChild);alert(newNode == someNode.childNodes[someNode.childNodes.length - 2]); // true replaceChild()替换节点。接收两个参数:要插入的节点和要替换的节点。要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点占据其位置。插入节点时,该节点的所有关系指针都会从被它替换的节点复制过来。尽管从技术上讲,被替换的节点仍然还在文档中,但它在文档中已经没有了自己的位置。 1234// 替换第一个子节点var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);// 替换最后一个子节点returnedNode = someNode.replaceChild(newNode, someNode.lastChild); removeChild()用于移除节点,只接受一个参数,即要移除的节点。被移除的节点将成为方法的返回值。与replaceChild()方法一样通过removeChild()移除的节点仍然为文档所有,不过在文档中已经没有了自己的位置。 1234// 移除第一个子节点var formerFirstChild = someNode.removeChild(someNode.firstChild);// 移除最后一个子节点var formerLastChild = someNode.removeChild(someNode.lastChild); 以上四种方法操作的都是某个节点的子节点,也就是说要使用这几个方法必须先取得父节点。另外,并不是所有类型的节点都有子节点,发果在不支持子节点的节点上调用了这些方法,将会导致错误发生。 其它方法 cloneNode()复制节点。用于创建调用这个方法的节点的一个完全相同的副本。该方法接受一个布尔值参数,表示是否执行深复制。参数为true执行深复制,也就是复制节点及整个子节点树;参数为false的情况下执行浅复制,即只复制节点本身。复制后返回的节点副本属于文档所有,但是并没有为其指定父节点。因此,这个节点副本就成为了一个“孤儿”,除非通过appendChild()、insertBefore()或replaceChild()将它添加到文档中。假设有如下的html代码 12345<ul> <li>item 1</li> <li>item 2</li> <li>item 3</li></ul> 假设<ul>元素的引用保存在了变量myList中,可以通过下列代码看出cloneNode()方法两种模式的不同。 1234var deepList = myList.cloneNode(true);alert(deepList.childNodes.length); // 3 或 7,深复制包括子节点。部分浏览器将回车和空白符当作一个元素var shallowList = myList.cloneNode(false);alert(shallowList.childNodes.length); // 0,浅复制不包括子节点 需要注意的是cloneNode()方法不会复制添加到DOM节点中的JavaScript属性,例如事件处理程序等。这个方法只复制特性、子节点,其他一切都不会复制。 normalize()用于处理文档树中的文本节点。由于解析器的实现或DOM操作等原因,可能会出现文本节点不包含文本,或者接连出现两个文本节点的情况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况,如果找到了空文本节点,则删除它;如果找到相邻的文本节点则将它们合并为一个文本节点。 Document类型javascript通过Document类型表示文档。在浏览器中,document对象是HTMLDocument(继承自Document类型)的一个实例,表示整个HTML页面。而且,document对象是window对象的一个属性,因此可以将其作为全局对象来访问。Document节点具有下列特征: nodeType值为9; nodeName值为”#document”; nodeValue值为null; parentNode值为null; ownerDocument值为null; 其子节点可能是一个DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或Comment。 Document类型可以表示HTML页面或者其他基于XML的文档。不过,最常见的应用还是作为HTMLDocument实例的document对象。通过这个文档对象,不仅可以取得与页面有关的信息,而且还能操作页面的外观及其底层结构。 文档的子节点documentElement虽然DOM标准规则Document节点的子节点可以是DocumentType、Element、ProcessingInstruction或Comment,但还有两个内置的访问其子节点的快捷方式。第一个就是documentElement属性,该属性始终指向HTML页面中的<html>元素。另一个就是通过childNodes列表访问文档元素,但通过documentElement属性则能更快捷、更直接地访问该元素。以下面的页面为例。 12345<html> <body> </body></html> 此页面经过浏览器解析后,文档中只包含一个子节点,即<html>元素。可通过如下方式访问这个元素 123var html = document.documentElement; // 取得对<html>的引用alert(html == document.childNodes[0]); // truealert(html == document.firstChild); // true bodydocument对象的body属性直接指向<body>元素 1var body = document.body; // 取得对<body>的引用 doctypeDocument另一个可能的子节点是DocumentType。通常将<!DOCTYPE>标签看成一个与文档其他部分不同的实例,可以通过doctype属性来访问它的信息。不过不同的浏览器对document.doctype的支持差别很大。 1var doctype = document.doctype; // 取得对 <!DOCTYPE> 的引用 文档信息作为HTMLDocument的一个实例,document对象还有一些标准的Document对象所没有的属性。这些属性提供了document对象所表现的网页的一些信息。 title包含着<title>元素中的文本。通过这个属性可以获取当前页面的标题,也可以修改当前页面的标题并反映在浏览器的标题栏中。 1234// 取得文档标题var originalTitle = document.title;// 设置文档标题document.title = 'New Page Title'; URL、domain、referrerURL属性中包含页面完整的URL,domain属性只包含页面的域名,referrer属性中够在着链接到当前页面的那个页面的URL。在没有来源页面的情况下,referrer属性中可能会包含空字符串。所以这些信息都存在于请求的HTTP头部,只不过是通过这些属性让我们能够在javascript中访问它们而已。这三个属性中只有domain是可以设置的,但在设置时有一些限制,如果原来是子域名则只能设置为主域名,并且设置完成之后不可以再修改为子域名。 12345678910// 取得完整的urlvar url = document.URL;// 取得域名var domain = document.domain;// 取得来源页面的urlvar referrer = document.referrer;// 假设页面来自p2p.wrox.com域document.domain = 'wrox.com'; // 成功, 松散的document.domain = 'nczonline.net'; // 失败document.domain = 'p2p.wrox.com'; // 失败,紧绷的 查找元素getElementById()根据元素的id来查找,接收一个参数:要取得的元素的id。如果找到相应的元素则返回该元素,如果不存在带有相应id的元素则返回null。 1234<div id='myDiv'> Some Text</div>var div = document.getElementById('myDiv'); // 取得div getElementsByTagName()根据元素的标签名获取元素列表,这个方法接受一个参数,即要取得元素的标签名,而返回的是包含零或多个元素的NodeList。在HTML文档中,这个方法会返回一个HTMLCollection对象,作为一个动态集合,该对象与NodeList非常类似。 12345// 获取所有的li标签var lis = document.getElementsByTagName('li');alert(lis.length); // 输出li标签的数量alert(lis[0].innerText); // 输出第一个li元素的文本alert(lis.item(0).innerText); // 输出第一个li元素的文本 namedItem()使用这个方法可以通过元素的name特性取得集合中的项。例如有如下的<li>元素。 12345<ul> <li>a</li> <li name=\"bbb\">b</li> <li>c</li></ul> 那么可以用如下方式取得第二个li元素 1var secondLi = lis.namedItem('bbb'); 按名称访问 在提供索引访问项的基础上还支持按名称访问项,如: 1var secondLi = lis['bbb']; 对HTMLCollection而言,我们可以向方括号中传入数值或字符串形式的索引值。在后台,对数值索引就会调用item(),而对字符串索引就会调用namedItem()。 取得所有元素 要想取得所有元素可以向getElementsByTagName()中传入*。在javascript和css中,星号通常表示全部。 1var allElements = document.getElementsByTagName('*'); getElementsByName()根据name特性查找元素。返回指定name的元素集合。 12345678<fieldset> <legend>which color do you prefer ?</legend> <ul> <li><input type=\"radio\" value=\"red\" name=\"color\" id=\"colorRed\"><label for=\"colorRed\">Red</label></li> <li><input type=\"radio\" value=\"red\" name=\"color\" id=\"colorGreen\"><label for=\"colorGreen\">Green</label></li> <li><input type=\"radio\" value=\"red\" name=\"color\" id=\"colorBlue\"><label for=\"colorBlue\">Blue</label></li> </ul></fieldset> 123// 获取所有的radiovar radios = document.getElementsByName('color');console.log('getElementsByName:' + radios); 特殊集合除了属性和方法,document对象还有一些特殊的集合。这些集合都是HTMLCollection对象,为访问文档常用的部分提供了快捷方式。包括: document.anchors,包含文档中所有带name特性的<a>元素; document.applets,包含文档中所有的<applet>元素; document.forms,包含文档中所有的<form>元素,与document.getElementsByTagName(‘form’)结果相同; document.images,包含文档中所有的<img>元素,与document.getElementsByTagName(‘img’)结果相同; document.links,包含文档中所有带href特性的<a>元素 文档写入将输出流写入到网页中。主要使用以下4个方法:write()、writeln()、open()和close()。其中,write()和writeln()方法都接受一个字符串参数,即要写入到输出流中的文本。write()会原样写入,writeln()会在字符串的末尾添加一个换行符(\\n) 123// write writeln open closedocument.write('<strong>' + (new Date()).toString() + '</strong><br>');document.writeln('<strong>' + (new Date()).toString() + '</strong>'); 如果在文档加载结束之后再调用document.write(),那么输出的内容将会重写整个页面。 123window.onload = function(){ document.write('hello world');} 方法open()和close()分别用于打开和关闭网页的输出流,如果是在页面加载期间使用write()或writeln()方法则不需要用到这两个方法。 Element类型除了Document类型外,Element类型是web编程中最常用的类型,它提供了对元素标签名、子节点及特性的访问。Element节点具有以下特征: nodeType值为1; nodeName值为元素的标签名; nodeValue值为null; parentNode可能是Document或Element; 其子节可能是Element、Text、Comment、ProcessingInstruction、CDATASection或EntityReference。 nodeName、tagName1<div id='myDiv'></div> 123var div = document.getElementById('myDiv');alert(div.tagName); // DIValert(div.tagName == div.nodeName); // true 由于在HTML中标签名始终都以大写表示,所以div.tagName实际上输出的是DIV而不是div。而在XML中大小写与源码一致。所以在标签名判断时最好是转化为相同的大小写形式。 123if(div.tagName.toLowerCase() === 'div'){ // ...} HTML元素所有的HTML元素都由HTMLElement类型表示,不是直接通过这个类型也是通过它的子类型来表示。HTMLElement类型直接继承自Element并添加了一些属性。 id,元素在文档中的唯一标识符; title,有关元素的附加说明信息,一般通过工具提示条显示出来; lang,元素内容的语言代码,很少使用; dir,语言的方向。ltr(从左至右)或rtl(从右至左),也很少使用; className,与元素的class特性对应,即为元素指定的css类。没有命名为class是因为class是ECMAScript的保留字。 上述这些属性都可以用来取得或修改相应的特性值。 12<div id=\"myDiv\" class=\"bd\" title=\"Body text\" style=\"height: 100px;\" lang=\"en\" onclick=\"{alert(1);}\" dir=\"ltr\" my_attribute=\"hello\">Some text</div> 可以通过javascript代码来获取和设置属性值。 123456789var div = document.getElementById('myDiv');console.log(div.id + '-' + div.className + '-' + div.title + '-' + div.lang + '-' + div.dir);console.log(div.tagName);console.log(div.tagName == div.nodeName);// editdiv.dir = 'rtl';div.aaa = 'aaa';console.log(div.getAttribute('aaa')); // nullconsole.log(div.aaa); 并不是所有的属性修改都会在页面中直观地表现出来。对id或lang的修改对用户而言是透明不可见的,而对title的修改则只会在鼠标移动到这个元素之上时才会显示出来。对dir的修改会在属性被重写的那一刻,立即影响页面中文本的左右对齐方式。修改className时如果新类关联了与此前不同的css样式,那么就会立即应用新的样式。 使用圆点操作符设置的非标准属性必须通过圆点操作符才可以进行获取,如上面的aaa属性。 取得特性每个元素都有一个或多个特性,这些特性的用途是给出相应元素或其内容的附加信息。操作特性的DOM方法主要有三个,分别是getAttribute()、setAttribute()、removeAttribute()。这三个方法可以针对任何特性使用,包含那些以HTMLElement类型属性的形式定义的特性。 123456789101112131415// attributeconsole.log(div.getAttribute('myDiv') + '-' + div.getAttribute('class')+ '-' + div.getAttribute('title')+ '-' + div.getAttribute('lang')+ '-' + div.getAttribute('dir')+ '-' + div.getAttribute('my_attribute'));// 自定义的特性无法作为属性被访问到console.log('my_attribute:' + div.my_attribute);// set attributediv.setAttribute('class', 'ft');div.setAttribute('bbb', 'bbb');// remove attributediv.removeAttribute('class');console.log(div.getAttribute('class')); 需要特别注意class特性值,应该传入class而不是className,后者只有在通过对象属性访问特性时才用。如果给定名称的特性不存在,getAttribute()返回null。特性的名称不需要区分大小写。 setAttribute()支持设置自定义特性,获取时必须使用getAttribute()方法。通过圆点符设置的属性,获取时也必须使用圆点符,使用getAttribute()方法会返回null 根据HTML5规范,自定义特性应该加上data-前缀以便验证。任何元素的所有特性,也都可以通过DOM元素本身的属性来访问。另外通过圆点符设置的自定义属性有可能不会被添加到DOM中,通过setAttribute()方式添加的会被添加到DOM中。 有两类特殊的特性,它们虽然有对应的属性名,但属性的值与通过getAttribute()返回的值并不相同。分别是style和类似于onclick这样的事件处理程序。 style 用于通过Css为元素指定样式。在通过getAttribute()访问时,返回的style特性值中包含的是css文本,而通过属性来访问它则会返回一个对象。由于style属性是用于以编程方式访问元素样式的,因此并没有直接映射到style特性。 123// styleconsole.log(div.style); // css对象console.log(div.getAttribute('style')); // height: 100px; onclick类事件 当在元素上使用时,onclick特性中包含的是JavaScript代码,如果通过getAttribute()访问则会返回相应代码的字符串。访问onclick属性时则会返回一个javascript函数。 123// onclickconsole.log(div.onclick); // 返回一个函数对象console.log(div.getAttribute('onclick')); // {alert(1);} 由于存在这些差别,在通过javascript以编程方式操作DOM时开发人员经常不使用getAttribute(),而是只使用对象的属性。只有在取得自定义特性值的情况下,才使用getAttribute()方法。 在使用removeAttribute()时,不仅会清除特性的值而且也会从元素中完全删除特性。 attributesElement类型是使用attributes属性的唯一一个DOM节点类型。attributes属性中包含一个NamedNodeMap,与NodeList类似,也是一个动态的集合。元素的每一个特性都由一个Attr节点表示,每个节点都保存在NamedNodeMap对象中。此对象拥有下列方法: getNamedItem(name):返回nodeName属性等于name的节点; removeNamedItem(name):从列表中移除nodeName属性等于name的节点; setNamedItem(node):向列表中添加节点,以节点的nodeName属性为索引; item(pos):返回位于数据pos位置处的节点。 attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,而节点的nodeValue就是特性的值。 123456789101112// attributesvar attributes = div.attributes;// get idconsole.log(attributes.getNamedItem('id').nodeValue);var id = attributes['id'].nodeValue;// set idattributes['id'].nodeValue = 'someOtherId';console.log(attributes.getNamedItem('id').nodeValue);// removevar oldAttr = attributes.removeNamedItem('id');// setattributes.setNamedItem(newAttr); 创建元素使用document.createElement()方法可以创建新元素,此方法只接受一个参数,即要创建元素的标签名。这个标签名在HTML文档中不区分大小写。使用此方法创建新元素的同时,也为新元素设置了ownerDocument属性。此时还可以为新元素添加新的特性或子元素。 1234567var div = document.createElement('div');div.id = 'myNewDiv';div.className = 'box';div.appendChild(document.createTextNode('abcdefg'));console.log(div.ownerDocument);// appendChilddocument.body.appendChild(div); 新创建的元素必须添加到DOM中才会在浏览器中显示出来。通过appendChild()、insertBefore()、replaceChild()方法添加到文档树中。 元素的子节点元素可以有任意多个数量的子节点和后代节点。其childNodes属性中包含了它的所有子节点,这些子节点可能是元素、文本节点、注释或处理指令。不同的浏览器看待子节点存在显著的不同。 12345<ul id=\"myList\"> <li>item 1</li> <li>item 2</li> <li>item 3</li></ul> 对于以上代码,在IE中<ul>元素有3个子节点,分别是3个<li>元素,而在其它浏览器中有7个元素,包括3个<li>元素和4个文本节点(表示<li>元素之间的空白符)。如果一定要只返回所有的元素节点可以使用children属性,或者判断nodeType是否等于1。 1234567for (var i = 0, nodes = document.getElementById('myList').childNodes, len = nodes.length; i < len; i++) { // 1 元素节点 if (nodes[i].nodeType == 1) { console.log(nodes[i]); }}var children = document.getElementById('myList').children; Text类型文本节点由Text类型表示,包含的是可以照字面解释的纯文本内容。纯文本可以包含转义后的HTML字符,但不能包含HTML代码。Text节点具有以下特征: nodeType的值为3; nodeName的值为#text; nodeValue的值为节点所包含的文本; parentNode是一个Element; 不支持(没有)子节点。 可以通过nodeValue属性或data属性访问Text节点中包含的文本,这两个属性中包含的值相同。对任何一方的修改会在另外方反映出来。可使用以下方法来操作节点中的文本: appendData(text):将text添加到节点的末尾; deleteData(offset,count):从offset指定的位置开始删除count个字符; insertData(offset,text):在offset指定的位置插入text; replaceData(offset,count,text):用text替换从offset指定的位置开始到offset+count为止处的文本; splitText(offset):从offset指定的位置将当前文本节点分成两个文本节点; subStringData(offset,count):提取从offset指定位置开始到offset+count为止处的字符串。 除了以上的方法外,文本节点还有一个lenght属性,保存着节点中字符的数目。而且,nodeValue.length和data.lenght中也保存着相同的值。 在默认情况下,每个可以内容的元素最多只能有一个文本节点,而且必须确实有内容存在。 123456// 无内容,无文本节点<div></div>// 有一个空格,有文本节点<div> </div>// 有内容,有文本节点<div>aaa</div> 可以使用以下代码来访问这些文本子节点并修改它 123var textNode = div.firstChild; // or div.childNodes[0]// editdiv.firstChild.nodeValue = 'bbb'; 在修改文本节点时需要注意的是字符串会经过HTML编码。 创建文本节点使用document.createTextNode()方法来创建新的文本节点,这个方法接受一个参数,要插入的文本。与设置已有文本节点的样,作为参数的文本也将按钮HTML进行编码。在创建文本节点的同时,也会为其设置ownerDocument属性。 123456var element = document.createElement('div');element.className = 'message';var textNode = document.createTextNode('Hello World');element.appendChild(textNode);var textNode2 = document.createTextNode('!!!');element.appendChild(textNode2); 在添加多个文本节点时,如果两个文本节点是相邻的同胞节点,那么这两个节点中的文本就会连起来显示,中间不会有空格。 规范化文本节点DOM文档中存在相邻的同胞节点很容易造成混乱,会分不清哪个文本节点表示哪个字符串。另外,DOM文档中出现相邻文本节点的情况也不在少数,于是催生了合并相邻文本节点的方法。这个方法normalize()是由Node类型定义的。如果在一个包含两个或多个文本节点的父元素上调用normalize(),则会将所有的文本节点合并成一个节点。 1234567891011121314var element = document.createElement('div');element.className = 'message';var textNode = document.createTextNode('Hello World');element.appendChild(textNode);var textNode2 = document.createTextNode('!!!');element.appendChild(textNode2);document.body.appendChild(element);console.log(element.childNodes.length);element.normalize();// 合并console.log(element.childNodes.length);console.log(element.firstChild.nodeValue); 分割文本节点与normalize()相反的一个方法是splitText(),它会将一个文本节点分成两个文本节点。原来的文本节点将包含从开始到指定位置之前的内容,新文本节点将包含剩下的文本,此方法会返回一个新文本节点,新节点与原节点的parentNode相同。 123456// splitText()var newNode = element.firstChild.splitText(5);console.log(element.childNodes.length);console.log(element.firstChild.nodeValue);console.log(element.childNodes[1].nodeValue);console.log(element.firstChild.parentNode == newNode.parentNode); // true Comment类型注释在DOM中是通过Comment类型来表示的。Comment节点具有以下特征: nodeType值为8; nodeName值为#comment; nodeValue值是注释的内容; parentNode可能是Document或Element; 不支持(没有)子节点。 Comment类型与Text类型继承自相同的基类,因此它拥有除splicText()之外的所有字符串操作方法。也可以通过nodeValue和data属性来取得注释的内容。创建注释节点可以使用document.createComment()方法,为其会发主入注释文本。 12345678910111213141516171819<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><div id=\"myDiv\"><!--A Comment--></div></body></html><script> var div = document.getElementById('myDiv'); var comment = div.firstChild; console.log(comment.data); console.log(document.doctype.name);</script> DocumentFragment类型在所有的节点类型中只有DocumentFragment在文档中没有对应的标记。DOMC规则文档片段(document fragment)是一种轻量级的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。DocumentFragment节点具有如下特征: nodeType值为11; nodeName值为#document-fragment; nodeValue值为null; parentNode值为null; 子节点可以是Element、Text、Comment、ProcessingInstruction、CDATASection或EntityReference。 虽然不能把文档片段直接添加到文档中,但是可以将它作为一个仓库来使用,即可以在里面保存将来可能会添加到文档中的节点。 123456789101112131415161718192021222324<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><ul id=\"myList\"></ul></body></html><script> var fragment = document.createDocumentFragment(); var ul = document.getElementById('myList'); var li = null; for (var i = 0; i < 3; i++) { li = document.createElement('li'); li.appendChild(document.createTextNode('Item' + i)); fragment.appendChild(li); } ul.appendChild(fragment);</script> Attr类型元素的特性在DOM中以Attr类型来表示。在所有浏览器中都可以访问Attr类型的构造函数和原型。从技术角度讲,特性就是存在于元素的attributes属性中的节点。特性节点具有下列特征: nodeType值为2; nodeName值是特性的名称; nodeValue值是特性的值; parentNode的值为null; 在HTML中不支持(没有)子节点。 开发人员最常使用的是getAttribute()、setAttribute()和removeAttribute()方法,很少直接引用特性节点。Attr对象有3个属性:name、value和specified。其中name是特性名称(同nodeName),value是特性的值(同nodeValue),而specified是一个布尔值,用以区别特性是在代码中指定的还是默认的。 1234567891011121314151617181920212223<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title></head><body><ul id=\"myList\"></ul></body></html><script> var element = document.getElementById('myList'); var attr = document.createAttribute('align'); attr.value = 'left'; element.setAttributeNode(attr); console.log(element.attributes['align'].value); console.log(element.getAttributeNode('align').value); console.log(element.getAttribute('align'));</script> DOM操作技术动态脚本用于动态的加载js代码,可以是外部的js或使用文本拼接脚本(行内方式)。 1234567891011121314function loadScript(url) { var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; document.body.appendChild(script);}loadScript('client.js');var script = document.createElement('script');script.type = 'text/javascript';script.appendChild(document.createTextNode('function sayHi(){console.log(\"hi\")}'));document.body.appendChild(script);sayHi(); 动态样式同样可以动态添加外部样式或内联样式 1234567891011121314// 外部样式var link = document.createElement('link');link.rel = 'stylesheet';link.type = 'text/css';link.href = 'style.css';var head = document.getElementsByTagName('head')[0];head.appendChild(link);// 内联var style = document.createElement('style');style.type = 'text/css';style.appendChild(document.createTextNode('body{background-color:green;}'));var head = document.getElementsByTagName('head')[0];head.appendChild(style); 操作表格ECMAScript中提供了个操作表格的方法和属性: <table>元素 caption:保存着对caption元素的指针; tBodies:是一个tbody元素的HTMLCollection; tFoot:保存着对tfoot元素的指针; tHead:保存着对thead元素的指针; rows:表格中所有行的HTMLCollection; createTHead():创建thead元素,将其放到表格中并返回引用; createTFoot()):创建tfoot元素,将其放到表格中并返回引用; createCaption():创建caption元素,将其放到表格中并返回引用; deleteTHead():删除thead元素; deleteTFoot():删除tfoot元素; deleteTCaption():删除caption元素; deleteRow(pos):删除指定位置的行; insertRow(pos):向rows集合中指定位置插入一行,返回新插入行的引用。 <tbody>元素 rows:表格中tbody元素所有行的HTMLCollection; deleteRow(pos):删除指定位置的行; insertRow(pos):向rows集合中指定位置插入一行,返回新插入行的引用。 <tr>元素 cells:表格中tr元素所有单元格的HTMLCollection; deleteCell(pos):删除指定位置的单元格; insertCell(pos):向cells集合中指定位置插入一个单元格,返回新插入单元格的引用。 12345678910111213141516171819202122var table = document.createElement('table');table.border = 1;table.width = '100%';var tbody = document.createElement('tbody');table.appendChild(tbody);tbody.insertRow(0);var firstRow = tbody.rows[0];firstRow.insertCell(0);firstRow.cells[0].appendChild(document.createTextNode('Cell 1,1'));firstRow.insertCell(1);firstRow.cells[1].appendChild(document.createTextNode('Cell 2,1'));tbody.insertRow(1);var secondRow = tbody.rows[1];secondRow.insertCell(0);secondRow.cells[0].appendChild(document.createTextNode('Cell 1,2'));secondRow.insertCell(1);secondRow.cells[1].appendChild(document.createTextNode('Cell 2,2'));document.body.appendChild(table);","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://blog.xxyxpy.pub/tags/JavaScript/"},{"name":"JavaScript高级程序设计","slug":"JavaScript高级程序设计","permalink":"http://blog.xxyxpy.pub/tags/JavaScript高级程序设计/"}]},{"title":"HTML表格和表单","slug":"前端/HTML/Html表格和表单","date":"2017-12-11T07:01:16.000Z","updated":"2018-01-18T01:32:10.328Z","comments":true,"path":"2017/12/11/前端/HTML/Html表格和表单/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/11/前端/HTML/Html表格和表单/","excerpt":"表格一般用法展示数据,是对网页的一种补充 cellspacing 单元格间距,默认是2 cellpadding 内容到边框的距离 align(left, right, center) 在td上面,设置内容居中;在table上面设置表格居中;在tr上面设置整行居中 bgcolor 设置背景颜色 1234567891011<table border=\"1\" withd=\"150\" height=\"300\" cellspacing=\"20\" cellpadding=\"10\" align=\"center\" bgcolor=\"yellow\"> <tr align=\"center\"> <td align=\"center\">张三</td> <td>18</td> </tr> <tr> <td>李四</td> <td>20</td> </tr></table>","text":"表格一般用法展示数据,是对网页的一种补充 cellspacing 单元格间距,默认是2 cellpadding 内容到边框的距离 align(left, right, center) 在td上面,设置内容居中;在table上面设置表格居中;在tr上面设置整行居中 bgcolor 设置背景颜色 1234567891011<table border=\"1\" withd=\"150\" height=\"300\" cellspacing=\"20\" cellpadding=\"10\" align=\"center\" bgcolor=\"yellow\"> <tr align=\"center\"> <td align=\"center\">张三</td> <td>18</td> </tr> <tr> <td>李四</td> <td>20</td> </tr></table> 标准结构完整的结构更有利于seo优化,如果变更thead和tfoot的宽高需要在tr标签上设置width和height 1234567891011121314151617181920<table border=\"1\" withd=\"150\" height=\"300\"> <thead> <tr> <td></td> <td></td> </tr> </thead> <tbody> <tr> <td></td> <td></td> </tr> </tbody> <tfoot> <tr> <td></td> <td></td> </tr> </tfoot></table> 表头和表格合并 caption 标签可以设置表格的名称,跟在表格顶部 colspan 合并横向单元格, 合并列 rowspan 合并纵向单, 合并行 123456789101112131415161718<table border=\"1\" width=\"500\" height=\"300\" align=\"center\"> <caption>表头</caption> <tr> <td colspan=\"2\">张三 22</td> <!-- <td>20</td> --> <td rowspan=\"3\">学生</td> </tr> <tr> <td>张三</td> <td>20</td> <!-- <td rowspan=\"2\">学生</td> --> </tr> <tr> <td>张三</td> <td>20</td> <!-- <td>学生</td> --> </tr></table> 标题、边框颜色和垂直对齐 th标签设置表格的标题,自动的加粗和居中 bordercolor设置边框的颜色 valign 设置垂直居中,默认是middle,top上,bottom 底 1234567891011121314151617<table border=\"1\" bordercolor=\"red\" width=\"500\" height=\"300\" cellspacing=\"0\"> <tr> <th>张三</th> <th>20</th> <th>学生</th> </tr> <tr> <td valign=\"top\">张三</td> <td>20</td> <td>学生</td> </tr> <tr> <td>张三</td> <td>20</td> <td>学生</td> </tr></table> 细线表格将table的背景设置为绿色,td的背景设置成白色,td之间的距离设置为1 table背景色 bgcolor为green td背景色,在每个td上或者在tr上设置bgcolor为white cellspacing单元格之间的距离 123456789101112131415161718192021222324252627282930313233343536373839404142434445<table width=\"350\" height=\"250\" bgcolor=\"green\" cellspacing=\"1\" align=\"center\"> <caption>课程表</caption> <tr bgcolor=\"white\"> <th colspan=\"2\"></th> <th>星期一</th> <th>星期二</th> <th>星期三</th> <th>星期四</th> <th>星期五</th> </tr> <tr bgcolor=\"white\"> <td rowspan=\"2\">上午</td> <td>1</td> <td>语文</td> <td>数学</td> <td>物理</td> <td>化学</td> <td>生物</td> </tr> <tr bgcolor=\"white\"> <td>2</td> <td>体育</td> <td>音乐</td> <td>几何</td> <td>画画</td> <td>舞蹈</td> </tr> <tr bgcolor=\"white\"> <td rowspan=\"2\">下午</td> <td>1</td> <td>体育</td> <td>画画</td> <td>音乐</td> <td>语文</td> <td>音乐</td> </tr> <tr bgcolor=\"white\"> <td>2</td> <td>英语</td> <td>舞蹈</td> <td>体育</td> <td>唱歌</td> <td>体育</td> </tr></table> 表单用于搜集用户的信息 基本使用 action 用于处理表单中的信息,通常由服务端来进行处理 method 表单提交的方式,一般有get、post input-text 文本输入框,maxlength控制输入的最大长度,readonly控制只读,disabled未激活状态 name 属性,控件名称。提交时传递到服务器的名称 value 属性,提交时传递到服务器的值 input-password 密码输入框 input-radio 单选框,通过name进行分组,默认选中需要设置checked="checked" select 下拉选择框,option为选项标签,默认选中需要设置selected="selected",设置可以多选需要在select上设置multiple。可以通过设置optgroup进行下拉列表的分组 input-checkbox 多选框,通过name进行分组,默认选中需要设置checked="checked" textarea 多行文本框.cols控制可见的列数,rows控制可见的行数 input-file 文件上传 input-button 普通按钮不能实现提交的功能,显示的值通过value来设置 input-image 图片按钮。可以实现提交的功能 input-reset 重置按钮。将信息恢复到默认状态,已经输入的信息将会丢失 fieldset 对表单信息进行分组。通过legend子标签设置分组名称 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758<form action=\"1.php\" method=\"get\"> <!-- 文本框 --> 用户名:<input type=\"text\" name=\"username\" maxlength=\"6\" readonly=\"readonly\" disabled=\"disabled\" value=\"王者荣耀\"><br> <!-- 密码框 --> 密&nbsp;&nbsp;码:<input type=\"password\" name=\"pwd\"><br> <!-- 单选框 --> 性别:<input type=\"radio\" name=\"gender\" checked=\"checked\">男 <input type=\"radio\" name=\"gender\">女 <br> <!-- 下拉列表 --> 省(市):<select name=\"fav\"> <option>河北</option> <option>山东</option> <option>山西</option> <option selected=\"selected\">北京</option> </select> <br> 省(市):<select name=\"fav2\" multiple=\"multiple\"> <option>河北</option> <option>山东</option> <option>山西</option> <option selected=\"selected\">北京</option> </select> <br> <!-- 对下拉列表的信息进行分组 --> 市(区):<select name=\"fav3\"> <optgroup label=\"北京市\"> <option>昌平区</option> <option>海淀区</option> <option>朝阳区</option> <option>大兴区</option> </optgroup> <optgroup label=\"广州市\"> <option>昌平区</option> <option>海淀区</option> <option>朝阳区</option> <option>大兴区</option> </optgroup> </select> <br> <!-- 多选框 --> 爱好:<input type=\"checkbox\" name=\"fav4\" value=\"football\">足球 <input type=\"checkbox\" checked=\"checked\" name=\"fav4\" value=\"basketball\">篮球 <input type=\"checkbox\" name=\"fav4\" value=\"pingpong\">乒乓 <br> <!-- 多行文本框 --> <textarea cols=\"30\" rows=\"10\"></textarea><br> <!-- 文件上传 --> <input type=\"file\"><br> <!-- 表单进行分组 --> <fieldset> <legend>按钮分组</legend> <!-- 普通按钮 --> <input type=\"button\" value=\"普通按钮\"><br> <!-- 图片按钮 --> <input type=\"image\" src=\"button.jpg\"><br> <!-- 重置按钮 --> <input type=\"reset\"> <br> </fieldset> <input type=\"submit\"></form> H5表单控件 input-url 网址控件 input-date 日期控件 input-time 时间控件 input-email 邮件控件 input-number 数字控件,step属性设置步长默认是1。max,min等 input-range 滑块控件,同样可以通过step设置步长 12345678910111213141516<form action=\"1.php\" method=\"post\"> <!-- 网址控件 --> <input type=\"url\"> <br> <!-- 日期控件 --> <input type=\"date\"> <br> <!-- 时间控件 --> <input type=\"time\"> <br> <!-- 邮件控件 --> <input type=\"email\"> <br> <!-- 数字控件 --> <input type=\"number\" step=\"3\"> <br> <!-- 滑块控件 --> <input type=\"range\" step=\"20\"> <input type=\"submit\"></form> 标签语义化 语义化是指去掉css样式表之后格式依然很清析。实现语义化的注意事项: 尽可能少的使用无语义的标签div和span 语义不明显时尽量使用p标签。 不要使用纯样式标签,如:b、font、u等,改用css设置 需要强调的文本可以包含在strong(加粗)或者em(斜体)标签中","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"HTML","slug":"HTML","permalink":"http://blog.xxyxpy.pub/tags/HTML/"}]},{"title":"JavaScript(6)BOM对象","slug":"前端/JS/JavaScript 高级程序设计/JavaScript(6)BOM对象","date":"2017-12-07T07:01:16.000Z","updated":"2018-03-13T02:06:10.606Z","comments":true,"path":"2017/12/07/前端/JS/JavaScript 高级程序设计/JavaScript(6)BOM对象/","link":"","permalink":"http://blog.xxyxpy.pub/2017/12/07/前端/JS/JavaScript 高级程序设计/JavaScript(6)BOM对象/","excerpt":"什么是BOMBOM是指浏览器对象模型,其提供了很多对象用于访问浏览器的一些功能,这些功能与任何网页内容无关。主要提供的对象有: window对象 location对象 navigator对象 screen对象 history对象 window对象window是BOM的核心对象,它表示浏览器的一个实例。在浏览器中window对象有双重角色,它既是通过JavaScript访问浏览器窗口的一个接口,又是ECMAScript规定的Global对象。这意味着在网页中定义的任何一个对象、变量和函数,都以window作为其Global对象,因此有权访问parseInt()等方法。 全局作用域在全局作用域中声明的变量、函数都会变成window对象的属性和方法。","text":"什么是BOMBOM是指浏览器对象模型,其提供了很多对象用于访问浏览器的一些功能,这些功能与任何网页内容无关。主要提供的对象有: window对象 location对象 navigator对象 screen对象 history对象 window对象window是BOM的核心对象,它表示浏览器的一个实例。在浏览器中window对象有双重角色,它既是通过JavaScript访问浏览器窗口的一个接口,又是ECMAScript规定的Global对象。这意味着在网页中定义的任何一个对象、变量和函数,都以window作为其Global对象,因此有权访问parseInt()等方法。 全局作用域在全局作用域中声明的变量、函数都会变成window对象的属性和方法。 12345678var age = 29;function sayAge() { alert(this.age);}alert(window.age); // 29sayAge();// 29window.sayAge();// 29 定义全局变量与在window对象上直接定义属性的区别是:直接定义的全局变量不可以通过delete操作符删除,而直接在window对外上定义的属性可以。例如: 1234567// 定义在window对象上的属性可以delete而,全局变量不行var age = 29;window.color = \"red\";delete window.age;delete window.color;console.log(window.age); // 29console.log(window.color); // undefined 根本的原因在于使用var语句添加的window属性有一个名为[[Configurable]]的特性值被设置为false,因此这样定义的属性不可以通过delete操作符删除。 尝试访问未声明的变量会排出错误,但是通过查询window对象可以知道某个可能未声明的变量是否存在。例如: 12345// 查询window对象判断是否是未声明变量var newValue = oldValue; // 报错,因为oldValue未定义var newValue = window.oldValue; // 不会报错,属性查询。newValue值为undefinedconsole.log(newValue); 窗口关系及框架如果页面中包含框架,则每个框架都拥有自己的window对象,并且保存在frames集合中。在frames集合中可以通过数值索引(从0开始,从左至右,从上到下)或者框架名称来访问相应的window对象。每个window对象都有一个name属性,其中包含框架的名称。如以下代码: 12345678910111213141516171819<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <title>Title</title> <script> </script></head><body><frameset rows=\"160.*\"> <frame src=\"frame.htm\" name=\"topFrame\"> <frameset cols=\"50%,50%\"> <frame src=\"anotherframe.htm\" name=\"leftFrame\"> <frame src=\"yetanotherframe.htm\" name=\"rightFrame\"> </frameset></frameset></body></html> 上面的代码创建一个框架集,其中一个框架居上,两个框架居下。可以通过window.frames[0]或者window.frames["topFrame"]来引用上方的框架。不过仍然推使用top而非window来引用这些框架(如:top.frames[0])。因为top对象始终指向最高(最外)层的框架也就是浏览器窗口,使用它可以确保在一个框架中正确地访问另一个框架。因为对于在一个框架中编写的任何代码来说,其中的window对象指向的都是那个框架的特定实例,而非最高层的框架。所以访问到leftFrame可以有如下方式 123456window.frames[1];window.frames['leftFrame'];top.frames[1];top.frames['leftFrame'];frames[1];frames['leftFrame']; 与top相对的另一个window对象是parent。顾名思义,parent对象始终指向当前框架的直接上层框架。在某些情况下,parent可能等于top。在没有框架的情况下,parent一定等于top(此时他们都等于window)。 与框架有关的最后一个对象是self,它始终指向window。实际上,self和window对象可以互换使用。引入self对象的目的只是为了与top和parent对象对应起来,因此它不格外包含其他值。所有这些对象都是windw对象的属性,可以通过window.top、window.parent、window.self等形式来访问。同时这也意味着可以将不同层次的window对象连起来使用。如window.parent.parent.frames[0]。 窗口位置用来确定和修改window对象位置的属性和方法有很多。各个浏览器都提供了screenLeft和screenTop属性,分别用于表示窗口相对于屏幕左边和上边的位置。通过screenX和screenY属性也可以确定左边和上边的位置(部分浏览器不支持)。由于各个浏览器厂商实现的不同,在使用时需要进行必要的测试。以下代码兼容性的获取窗口左边和上边的位置 123var leftPos = (typeof window.screenLeft == \"number\") ? window.screenLeft : window.screenX;var topPos = (typeof window.screenTop == \"number\") ? window.screenTop : window.screenY;console.log(leftPos + \":\" + topPos); 移动窗口 使用moveTo()和moveBy()可以装饰窗口精确的移动到一个新的位置。moveTo()接收两个参数分别指新位置的水平和垂直位置坐标,而moveBy()接收的是相对于当前位置,水平和垂直方向上移动的像素数。需要说明的是在大部分浏览器中,这两个方法都会被禁用掉,一般只会对使用window.open()打开的窗口起作用。 123456789// 可能被禁用,只对window.open()打开的窗口起作用// 移动窗口到屏幕左上角window.moveTo(0, 0);// 向下移动100pxwindow.moveBy(0, 100);// 移动到200, 300window.moveTo(200, 300);// 左移50pxwindow.moveBy(-50, 0); 窗口大小大部分浏览器都可以通过innerWidth、innerHeight、outerWidth和outerHeight这四个属性来获取页面大小和浏览器窗口大小。 12345678// 浏览器窗口宽度console.log(window.outerWidth);// 浏览器窗口高度console.log(window.outerHeight);// 浏览器内部页面宽度console.log(window.innerWidth);// 浏览器内部页面高度console.log(window.innerHeight); 另外使用resizeTo()和resizeBy()方法可以调整浏览器容器的大小。这两个方法都接收两个参数,其中resizeTo()接收浏览器窗口的新宽度和新高度。需要注意的是这两个方法也有可能被浏览器禁用掉,并且不适用于框架,只能对最外层的window对象使用。 123456// 调整到 100 100window.resizeTo(100, 100);// 调整到 200 150window.resizeTo(100, 50);// 调整到 300 300window.resizeTo(300, 300); 导航和打开窗口使用window.open()方法既可以导航到一个特定的URL,也可以打开一个新的浏览器窗口。这个方法可以接收4个参数:要加载的URL、窗口目标、一个特性字符串以及一个表示新页面是否取代浏览器历史记录中当前加载页面的布尔值。通常只须要传递第一个参数,最后一个参数只在不打开新窗口的情况下使用。 如果为window.open()传递了第二个参数,而且该参数是已有窗口或框架的名称,那么就会在具有该名称的窗口或框架中加载第一个参数指定的URL。 12// 在topFrame窗口或框架中打开http://www.baidu.comwindow.open('http://www.baidu.com', 'topFrame'); 当然第二个参数也可以是下列任何一个特殊窗口名称:_self、parent、_top、_blank 弹出窗口如果给window.open()传递的第二个参数不是已经存在的窗口或框架,那么该方法就会根据第三个参数位置上传入的字符串创建一个新窗口或新标签页。如果没有传入第三个参数,则会打开一个带有全部默认设置(工具栏、地址栏和状态栏等)的新浏览器窗口(或新的标签)。在不打开新窗口的情况下会忽略第三个参数。关于第三个参数的可选项可参考此链接 12345678910111213// 在原来窗口中跳转window.open(\"http://www.baidu.com\", \"_self\");// 打开新窗口var baidu = window.open(\"http://www.baidu.com\", \"\", \"height=400,width=400,top=10,left=10,resizable=yes\");baidu.resizeTo(500, 500);baidu.moveBy(100, 100);baidu.close();// 打开空白新窗口,并显示内容myWindow=window.open('','','width=200,height=100');myWindow.document.write(\"<p>这是'我的窗口'</p>\");myWindow.focus(); window.open()调用后会返回对新窗口的引用,所以可以对其进行其它调整或移动操作。甚至可以调用close()方法去关闭这个新窗口。close()方法仅适用于通过window.open()打开的窗口。对于浏览器的主窗口没有得到用户的允许是不能关闭的。弹出窗口关闭后窗口的引用仍然还在,但除了检测其closed属性(baidu.closed)外已经没有其他作用了。 新创建的window对象有一个opener属性,其中保存着打开它的原始窗口对象。这个属性只在弹出窗口中的最外层window对象(top)中有定义,而且指向调用window.open()的窗口或框架。 12// openerconsole.log(baidu.opener == window); // true 间歇调用和超时调用setTimeout() setTimeout()方法指定一段时间后执行代码,接受两个参数:要执行的代码和以ms表示的时间(即在执行代码前需要等待多少ms)。第一个参数可以是一个包含JavaScript代码的字符串(不推荐这么做),也可以是一个函数。如: 12345setTimeout(\"console.log('hello world !')\", 1000);setTimeout(function () { console.log(\"hello world !\")}, 1000); setTimeout()方法调用之后会返回一个数值id(超时调用id),此id是该计划执行代码的唯一标识,可以通过它来取消超时调用。如果要取消超时调用,可以调用clearTimeout()方法并将相应的超时调用id作为参数传给它。 123456// 取消执行var timeoutId = setTimeout(function () { alert(\"hello world!\");}, 4000);// cancelclearTimeout(timeoutId); setInterval() setInterval()方法设置间歇调用,指每隔一定时间调用。同样接收两个参数:要执行的代码和每次执行前需要等待的ms。 12345// 轮询, 每隔一秒打印一次setInterval(\"console.log('hello world')\", 1000);setInterval(function () { console.log('hello world');}, 1000); 此方法同样也会返回一个数值id,可以通过此id进行取消 12345678910function incrementNumber() { num++; console.log(num); if (num == max) { clearInterval(intervalId); console.log(\"ok\"); }}intervalId = setInterval(incrementNumber, 500); 其实通过setTimeout()方法也可以实现间歇调用的效果。一般使用超时调用来模拟间歇调用是一种最佳模式,原因是间歇调用可能会在前一个调用结束之前就启动。而使用setTimeout时可以避免这个问题 123456789101112var num = 0;var max = 10;function incrementNumber() { num++; console.log(num); if (num < max) { setTimeout(incrementNumber, 500); } else { console.log('ok'); }}setTimeout(incrementNumber, 500); 系统对话框浏览器通过alert()、confirm()和prompt()三个方法调用系统对话框向用户显示消息。系统对话框与在浏览器中显示的网页没有关系,也不包含HTML。它们的外观由操作系统或浏览器设置决定。另外它们打开的对话框都是同步和模态的,也就是说会阻塞代码的执行。 alert() 接受一个字符串并将其显示给用户。其实就是弹出一个对话框,包含指定的文本和确定按钮。 1alert('hello world'); confirm() 与alert()传入的参数相同,不同点在于显示时除了确定按钮外还有取消按钮,这两个按钮让用户决定是否执行给定的操作。此外此方法具有返回值,点确定时返回true,取消时返回false。 12var result = confirm('hello world');console.log(result); prompt() 一个提示框,用于接收用户的输入,接收到的是文本信息。此方法接受两个参数:要显示给用户的提示文本和文本输入域的默认值(可以是空字符串)。此方法具有返回值,如果用户点了确定则返回文本输入域的值,如果点了取消或用其它方式关闭对话框则返回null 1234var name = prompt('what\\'s your name?', \"tom\");if (name != null) { alert('welcome, ' + name);} 查找和打印 查找和打印对话框都是异步显示的,能够将控制权立即交还给脚本。其效果与用户通过浏览器菜单的查找和打印命令打开的对话框相同。 12345// 显示 打印 对话框window.print();// 显示 查找 对话框window.find(); location对象更多内容可查看 location是最有用的BOM对象之一,它提供了与当前窗口中加载的文档有关的信息,另外还提供了导航的功能。它既是window对象的属性与是document对象的属性,即window.location和document.location引用的是同一个对象。location对象的用户不只表现在它保存着当前文档的信息,还表现在它将URL解析为独立的片段,让开发人员可以通过不同的属性访问这些片段。所有属性如下表: 属性 描述 hash 返回url中的锚部分。即#后面的内容,包含# host 返回服务器名称和端口号(如果有)。如:www.baidu.com:80 hostname 返回服务器名称,不带端口号。如:www.baidu.com href 返回当前加载页面的完整URL,同location对象的toString()方法返回值 pathname 返回URL路径名。如/js/bom/location对象.html port 返回端口号,如果无端口号则为空 protocol 返回页面使用的协议。如:http: search 返回查询字符串,以?开头。如:?name=tom&age=18 12345678console.log(\"hash:\" + location.hash);console.log(\"host:\" + location.host);console.log(\"hostname:\" + location.hostname);console.log(\"href:\" + location.href);console.log(\"pathname:\" + location.pathname);console.log(\"port:\" + location.port);console.log(\"protocol:\" + location.protocol);console.log(\"search:\" + location.search); 查询字符串参数通过location.search属性可以获取所有的查询字符串,但无法得到每一个查询属性的值。可以通过下面的函数对查询属性数组进行返回。 12345678910111213141516171819202122console.log(location.search);function getQueryStringArgs() { var qs = location.search.length > 0 ? location.search.substring(1) : \"\"; var args = {}; var qsArray = qs.length > 0 ? qs.split('&') : []; // 遍历参数列表 for (var i = 0; i < qsArray.length; i++) { var param = qsArray[i].split('='); if (param.length != 2) { continue; } var name = decodeURIComponent(param[0]); var value = decodeURIComponent(param[1]); if (name.length) { args[name] = value; } } return args;}var arg = getQueryStringArgs();console.log(arg); 位置操作assign() 此方法接受一个URL参数,调用后立即打开新的URL并在浏览器的历史记录中生成一条记录。如果是将location.href或window.href设置为一个URL值,也会以该值调用assign()方法。 1234// 以下方法调用效果相同location.assign('http://www.baidu.com');window.location = 'http://www.baidu.com';location.href = 'http://www.baidu.com'; 另外修改location对象的其他属性(hash、search、hostname、pathname和port等)也可以改变当前加载页面。每次修改(除hash属性)外都会以新的URL重新加载页面。要禁用这种行为可以使用replace()方法。此方法只接受一个参数,即要导航到的URL。结果虽然导致浏览器位置改变,但是不会在历史记录中生成新记录。所以调用之后用户无法回到前一个页面。 123setTimeout(function () { location.replace('http://www.wrox.com/');}, 1000); reload() 用于重新加载当前的页面。如果不传递参数,页面会以最有效的方式重新加载,所以有可能会从浏览器缓存中进行加载。如果要强制从服务器重新加载,需要传递参数true 12location.reload();location.reload(true); navigator对象此对象包含有关浏览器的信息。更多信息可参考 检测插件 可使用navigator来检测浏览器是否包含特定的插件。对于非IE的浏览器可以使用plugins数组来实现,该数组的每一页都包含下列属性: name:插件的名字 description:插件的描述 filename:插件的文件名 length:插件所处理的MIME类型数 123456789101112// 检测插件function hasPlugin(name) { name = name.toLowerCase(); for (var i = 0; i < navigator.plugins.length; i++) { if (navigator.plugins[i].name.toLowerCase().indexOf(name) > -1) { return true; } } return false;}console.log(hasPlugin('pdf')); screen对象此对象一般只用来表明客户端的能力。其中包含浏览器窗口外部的显示器的信息,如像素宽度和高度等。 比如通过如下代码来使窗口占据整个屏幕空间。需要说明的是resizeTo方法可能被浏览器禁用 12// availWidth availHeight 用来获取屏幕的宽高像素window.resizeTo(screen.availWidth, screen.availHeight); history对象此对象保存着用户上网的历史记录,从窗口被打开的那一刻算起。因为history是window对象的属性,所以每个窗口、标签至框架都有自己的history对象与特定的window对象关联。 通过history对象可以实现在不清楚实际URL的情况下实现后退和前进。另外使用go()方法可以在用户的历史记录中任意跳转(向前或向后) 1234// 退一页history.go(-1);// 进两页history.go(2); 另外还可以使用简写方法back()和forward()代替go()来模拟浏览器的后退和前进按钮 12history.back();history.forward(); 除了以上方法外history对象还有一个lenght属性,保存着历史记录的数量。可以用来确定用户是否一开始就打开了你的页面。 123if (history.length == 0) { // 用户打开窗口后的第一个页面}","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://blog.xxyxpy.pub/tags/JavaScript/"},{"name":"JavaScript高级程序设计","slug":"JavaScript高级程序设计","permalink":"http://blog.xxyxpy.pub/tags/JavaScript高级程序设计/"}]},{"title":"JavaScript(5)函数表达式","slug":"前端/JS/JavaScript 高级程序设计/JavaScript(5)函数表达式","date":"2017-11-30T07:01:16.000Z","updated":"2018-03-16T08:50:41.735Z","comments":true,"path":"2017/11/30/前端/JS/JavaScript 高级程序设计/JavaScript(5)函数表达式/","link":"","permalink":"http://blog.xxyxpy.pub/2017/11/30/前端/JS/JavaScript 高级程序设计/JavaScript(5)函数表达式/","excerpt":"函数定义和声明提升1234567// 调用可以在函数声明前functionName();// 函数声明function functionName(arg0, arg1, arg2){ // 函数体}alert(functionName.name); // functionName 函数表达式12345// 调用发生错误,必须在函数表达式之后functionName();var functionName = function(arg0, arg1, arg2){ // 函数体}","text":"函数定义和声明提升1234567// 调用可以在函数声明前functionName();// 函数声明function functionName(arg0, arg1, arg2){ // 函数体}alert(functionName.name); // functionName 函数表达式12345// 调用发生错误,必须在函数表达式之后functionName();var functionName = function(arg0, arg1, arg2){ // 函数体} 递归递归函数是在一个函数通过名字调用自身的情况下构成的。如阶乘函数: 1234567function factorial(num){ if(num <= 1){ return 1; } else { return num * factorial(num - 1); }} 以上函数在下面的代码中调用会出错。由于在factorial函数中引用到了factorial变量,而其值又被设置成为了null。所以anotherFactorial在执行使用到factorial时报错。 123var anotherFactorial = factorial;factorial = null;alert(anotherFactorial(4)); // 出错! 此时可以使用arguments.callee解决这个问题,arguments.callee是一个指向正在执行函数的指针,因此可以使用它来实现对函数的递归调用。 1234567function factorial(num){ if(num <= 1){ return 1; } else { return num * arguments.callee(num - 1); // 与函数名称解除关联 }} 闭包闭包是指有权访问另一个函数作用域中的变量的函数。所以闭包本质上是一个函数。 创建闭包的常见方式是在一个函数内部创建另一个函数。 123456789101112131415161718192021222324function createComparisonFunction(propertyName) { return function (object1, object2) { // 访问外部函数中的变量 propertyName var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } };}var Person = function (name, age) { this.name = name; this.age = age;};var person1 = new Person(\"tom\", 19);var person2 = new Person(\"jack\", 20);var compare = createComparisonFunction(\"age\");console.log(compare(person1, person2)); // -1 createComparisonFunction函数返回一个匿名函数,在匿名函数内部调用了外部函数中的变量propertyName,即使这个内部函数被返回了,而且在其它地方被调用了,但它仍然可以访问变量propertyName。 之所以还能访问这个变量,是因为内部作用域链中包含createComparisonFunction的作用域。 当某个函数第一次被调用时会创建一个执行环境及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([[Scope]])。然后,使用this、arguments和其它命名参数的值来初始化函数的活动对象(activationobject)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链终点的全局执行环境 。在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。看下面的例子: 12345678910function compare(value1, value2){ if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; }}var result = compare(5, 10); 以上代码先定义compare()函数,然后又在全局作用域中进行调用。当第一次调用时会创建一个包含this、arguments、value1、value2的活动对象。全局执行环境的变量对象(包含this、result和compare)在compare()执行环境的作用域链中处于第二位。 后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部环境的变量对象则只在函数的执行过程中存在。在创建compare()函数时会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并推入执行环境作用域链的前端。 对于上面这个例子中compare()函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局对象。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是,闭包的情况又有所不同。在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。因此,在createComparisonFunction()函数内部定义的匿名函数的作用域链中,实际上将包含外部函数createComparisonFunction()的活动对象。如果执行以下代码: 123456// 创建函数var compare = createComparisonFunction(\"name\");// 调用函数var result = compareNames({name: \"jack\"}, {name: \"tom\"});// 解除对匿名函数的引用(以便释放内存)compare = null; 在匿名函数从createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction()中定义的所有变量。更为重要的是createComparisonFunction()函数在执行完毕后其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换包话说,当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。 在完成比较后通过将comare设置为null解除该函数的引用,就等于通知垃圾回收进程将其清除。随着匿名函数的作用域链被销毁,其它作用域(除了全局作用域)也都可以安全地销毁了。 闭包与变量作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。 1234567891011function createFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function () { return i; } } return result;}var my = createFunctions(); // 数组内函数保存的i值都是10 当函数createFunctions()函数返回后,变量i的值都是10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10。但是可以通过创建另外一个匿名函数强制让闭包的行为符合预期。 12345678910111213function createFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function (num) { return function() { return num; }; }(i); } return result;}var my = createFunctions(); // 数组内函数保存的i值都是实际的索引值 在这个版本中没有把闭包直接赋值给数组,而是定义了一个匿名函数,并将立即执行该其名函数的结果赋给数组。这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,传入的变量i被复制给参数num。而在匿名函数内部又创建并返回了一个访问num的闭包。这样一来,result数组中的每个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值了。 关于this对象this对象是在运行时基于函数的执行环境绑定的,在全局函数中this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过匿名函数的执行环境具有全局性,因此this对象通常指向window。但有时候由于编写闭包的方式不同,看起来并不明显。 1234567891011var name = \"The Window\";var object = { name: \"My Object\", getNameFunc: function () { return function () { return this.name; // 匿名函数中 this 代表window } }};alert(object.getNameFunc()()); // The Window getNameFunc()返回一个匿名函数,而匿名函数又返回this.name。所以object.getNameFunc()()会立即执行匿名函数并返回this.name值。 为什么匿名函数无法取得外部作用域中的this对象呢? 因为每个函数在被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时由于自己内部也有所以只会搜索到自己活动对象中的this和arguments,无法取到外部作用域中的这两个变量。而匿名函数中的this指向window,所以只会取到在window中定义的name值,而取不到外部作用域中的。 把外部作用域中的this对象保存在一个闭包能够访问的变量里就可以让闭包访问该对象了。通过以下方式来实现取外部作用域中的name 12345678910111213var name = \"The Window\";var object2 = { name: \"My Object\", getNameFunc: function () { var that = this; return function () { return that.name; // that 代表 object2 } }};alert(object2.getNameFunc()()); // My Object 在几种特殊情况下this的值可能会意外的改变。比如下面的例子 12345678910var name = \"The Window\";var object3 = { name: \"My Object\", getName: function () { return this.name; }};alert(object3.getName());// \"My Object\"alert((object3.getName)());// \"My Object\"alert((object3.getName = object3.getName)());// \"The Window\",object3.getName是一个函数this值得不到维持 执行object3.getName()时返回”My Object”因为this.name就是object3.name。第二次调用时虽然加了括号,但是this的值得到了维持指向的还是object3(因为object3.getName和(object3.getName)的定义是相同的)。 第三次调用时先执行了一条赋值语句,然后再调用赋值后的结果。因为赋值表达式的值是函数本身,所以this值不能得到维持,指向了window,所以返回”The Window” 模仿块级作用域JavaScript中没有块线作用域的概念,这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。定义之后在函数内部就可以访问这个变量,即使重新声明(不带初始化)也不会改变它的值 1234567function outputNumbers(count) { for (var i = 0; i < count; i++) { alert(i); } var i; // 重新声明,不改变它的值 alert(i); // 可以访问} 匿名函数可以用来模仿块级作用域并避免这个问题。用作块级作用域的匿名函数语法为: 123(function(){ // 这里是块级作用域})(); 以上代码定义并立即调用了一个匿名函数。将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。 当临时需要一些变量时就可以使用块级作用域。例如: 123456789function outputNumbers(count) { (function () { for (var i = 0; i < count; i++) { alert(i); } })(); alert(i); // 报错,无法访问到i} 变量i只能在块级作用域内部使用,使用后即被销毁。在私有作用域中能够访问变量count是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数,因为过多的全局变量和函数很容易导致命名冲突。通过创建私有作用域,每个开发人员既可以使用自己的变量,又不必担心搞乱全局作用域。如: 123456(function () { var now = new Date(); if (now.getMonth() == 0 && now.getDate() == 1) { alert(\"Happy new year!\"); }})(); 变量now是匿名函数中的变量,不需要在全局作用域中创建它,而且全局作用域中也无法访问到它。并且这种作用可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕就可以立即销毁其作用域链。 私有变量严格来讲JavaScript中没有私有成员的概念,所有对象属性都是公有的。不过有一个私有变量的概念。任何在函数中定义的变量都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。如: 1234function add(num1, num2) { var sum = num1 + num2; return sum;} 在这个函数内部,有3个私有变量:num1、num2和sum。在函数内部可以访问它们,外部无法访问到。 如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些问题。而利用这一点,就可以创建用于访问私有变量的公有方法。我们把有权访问私有变量和私有函数的公有方法称为特权方法。有两种在对象上创建特权方法的方式。 第一种,在构造函数中定义 12345678910111213function MyObject() { var privateVariable = 10; function privateFunction() { return false; } // 特权方法 this.publicMethod = function () { privateVariable++; return privateFunction(); }}var a = new MyObject();alert(a.publicMethod()); 能够在构造函数中定义特权方法是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。对这个例子而言,变量privateVariable和函数privateFunction()只能通过特权方法publicMethod()来访问。通过此方式可以隐藏那些不应该被直接修改的数据 。 构造函数模式中定义的好入是每个实例中的私有变量都是互不相同的。缺点是只能使用构造函数模式来达到目的,每个实例都会创建同样的一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题 静态私有变量通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。代码如下: 1234567891011121314151617// 方法内为私有作用域(function () { // 私有变量和私有函数 var privateVariable = 10; function privateFunction() { return false; } // 构造函数 MyObject = function () { }; // 公有/特权方法 MyObject.prototype.publicMethod = function () { privateVariable++; return privateFunction(); }})(); 这个模式创建了一个私有作用域,并且在其中封装了一个构造函数及相应的方法。特权方法是在原型上定义的,这体现了典型的原型模式,区别于构造函数模式的好处原型会被所有的实例所引用,方法不会跟随实例进行创建。 另外在定义构造函数时不使用函数声明而使用函数表达式是因为函数声明只能创建局部函数,如果是局部函数的话外部无法访问到。出于同样的原因MyObject在声明时也没有使用var关键字,这样的话MyObject就会创建成一个全局变量,能够在私有作用域之外被访问到。 与构造函数中定义特权方法的区别有: 私有变量和函数是由实例共享的,改变其中一个实例的值会立即反映到其它实例上 特权方法定义在原型上,因为所有的实例都使用同一个函数。特权方法作为一个闭包,总着保存着对包含作用域的引用 示例代码: 12345678910111213141516171819202122(function () { var name = \"\"; Person = function (value) { name = value; }; Person.prototype.getName = function () { return name; }; Person.prototype.setName = function (value) { name = value; }})();var p1 = new Person(\"a\");alert(p1.getName());var p2 = new Person(\"b\");p1.setName(\"c\"); // 影响所有的实例alert(p1.getName()); // calert(p2.getName()); // c 在上面的例子中Person构造函数与getName()和setName()方法一样,都有权访问私有变量name。在这种模式下变量name就变成了一个静态的由所有实例共享的属性。也就是说在一个实例上调用setName()会影响所有实例。 以这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。 模块模式(单例模式)模块是为单例创建私有变量和特权方法。所谓单例指的是只有一个实例对象。通常情况下JavaScript是以对象字面量的方式来创建单例对象的。 123456var singleton = { name: value, method: function() { }}; 模块模式通过为单例添加私有变量和特权方法能够使其得到增强,语法形式为: 1234567891011121314151617var singleton = function(){ // 私有变量和函数 var privateVariable = 10; function privateFunction(){ return false; } // 特权/公有方法和属性 return { publicProperty: true, publicMethod: function(){ privateVariable++; return privateFunction(); } }}();alert(singleton.publicProperty);alert(singleton.publicMethod()); 模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部,首先定义了私有变量和函数。然后将一个对象字面量作为函数的值返回。返回的对象字面量中只包含可以公开的属性和方法。由于这个对象是在匿名函数内部定义的因此它的仅有方法有权访问私有变量和函数(因为闭包)。从本质上来讲,这个对象字段量定义的是单例的公共接口。这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。例如: 1234567891011121314151617var application = function () { // 私有变量和函数 var components = new Array(); // 初始化,传进去一些东西。 components.push(new BaseComponent()); // 公共 return { getComponentCount: function () { return components.length; }, registerComponent: function (component) { if (typeof component == \"object\") { components.push(component); } } }}(); 在Web应用程序中,经常需要使用一个单例来管理应用程序级的信息。这个简单的例子创建了一个用于管理组件的application对象。在创建这个对象的过程中,首先声明了一个私有的components数组,并向数组中添加了一个BaseComponent的新实例。而返回对象的getComponentCount()和registerComponent()方法都是有权访问数组components的特权方法。 简言之,如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。 增强的模块模式有人进一步改进了模块模式,即在返回对象之前加入对其增加的代码。这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性或方法对其加以增强的情况。比如: 12345678910111213141516171819202122var CustomerType = function () {};// 为单例创建私有变量和特权方法var singleton = function () { // 私有变量和函数 var privateVariable = 10; function privateFunction() { return false; } // 创建对象,指定的类型 var object = new CustomerType(); // 特权/公有方法和属性 object.publicProperty = true; object.publicMethod = function () { privateVariable++; return privateFunction(); }; // 返回这个对象 return object;}();alert(singleton.publicProperty);alert(singleton.publicMethod()); 如果前面演示模块模式的例子中的application对象必须是BaseComponent的实例,那么就可以使用以下代码: 1234567891011121314151617181920212223242526var BaseComponent = function () {};var application = function () { // 私有变量和函数 var components = new Array(); // 初始化 components.push(new BaseComponent()); // 创建application的一个局部副本 var app = new BaseComponent(); // 公共接口 app.getComponentCount = function () { return components.length; }; app.registerComponent = function (component) { if (typeof component == \"object\") { components.push(component); } }; return app;}();alert(application.getComponentCount()); // 1application.registerComponent(new BaseComponent());alert(application.getComponentCount()); //2 和模块模式最主要的不同之处在于命名变量app的创建过程,因为它必须是BaseComponent的实例。这个实例实际上是application对象的局部变量。此后又为app对象添加了能够访问私有变量的公共方法。最后一步是返回app对象,结果仍然是将它赋值给全局变量application。","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://blog.xxyxpy.pub/tags/JavaScript/"},{"name":"JavaScript高级程序设计","slug":"JavaScript高级程序设计","permalink":"http://blog.xxyxpy.pub/tags/JavaScript高级程序设计/"}]},{"title":"JavaScript(4)面向对象","slug":"前端/JS/JavaScript 高级程序设计/JavaScript(4)面向对象","date":"2017-11-25T07:01:16.000Z","updated":"2018-03-16T05:40:13.431Z","comments":true,"path":"2017/11/25/前端/JS/JavaScript 高级程序设计/JavaScript(4)面向对象/","link":"","permalink":"http://blog.xxyxpy.pub/2017/11/25/前端/JS/JavaScript 高级程序设计/JavaScript(4)面向对象/","excerpt":"对象的定义 对象是一组无序属性的集合,属性可以包含基本值、对象或者函数 理解对象属性类型数据属性数据属性包含一个数据值的位置,在这个位置可以读取和写入值。有四个特性: configuraable:默认值为true。表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性; enumerable:默认值为true。表示能否通过for-in循环返回属性,直接设置的属性默认是可以循环的; writable:默认值为true。表示能否修改属性的值,直接设置的属性默认是true; value:默认值为undefined。包含这个属性的数据值。读取属性值时从这个位置读,写入时写入到此位置。 1234// “name”的[[Value]]特性为指定的值,其它三个都为truevar person = { name: \"tom\"};","text":"对象的定义 对象是一组无序属性的集合,属性可以包含基本值、对象或者函数 理解对象属性类型数据属性数据属性包含一个数据值的位置,在这个位置可以读取和写入值。有四个特性: configuraable:默认值为true。表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性; enumerable:默认值为true。表示能否通过for-in循环返回属性,直接设置的属性默认是可以循环的; writable:默认值为true。表示能否修改属性的值,直接设置的属性默认是true; value:默认值为undefined。包含这个属性的数据值。读取属性值时从这个位置读,写入时写入到此位置。 1234// “name”的[[Value]]特性为指定的值,其它三个都为truevar person = { name: \"tom\"}; 修改数据属性 使用Object.defineProperty(arg1,arg2,arg3)方法来修改属性默认的特性。 参数1:属性所在的对象 参数2:属性的名称 参数3:一个描述符对象。属性必须是configurable、enumerable、writable和value。设置其中的一或多个值,可以修改对应的特性值。 1234567891011121314Object.defineProperty(person1, \"name\", { // 设置为不可以修改 writable: false, // 不可删除的属性 configurable: false, value: \"tom\"});alert(person1.name);// 修改属性值无效person1.name = \"jack\";alert(person1.name);// 不可删除该属性delete person2.name;alert(person2.name); 使用注意点: 一旦把某个属性的configurable设置为 false,再通过Object.defineProperty修改此属性的特性值就会报错 在调用Object.defineProperty方法时,如果不指定configurable、enumerable和writable特性的默认值都是false 访问器属性访问器属性包含一对get和set函数,不包含数据值。此属性有如下4个特性: configurable:和上面的一致,表示是否可以删除属性重新定义,默认true enumerable:和上面的一致,表示是否可以for-loop循环,默认true get:在读取属性时调用的函数,默认值为undefined set:在写入属性时调用的函数,默认值为undefined 定义访问器属性不能直接定义,同样需要使用Object.defineProperty 123456789101112131415161718var book = { _year: 2004, edition: 1};Object.defineProperty(book, \"year\", { get: function () { return this._year; }, set: function (v) { if (v > 2004) { this._year = v; this.edition += v - 2004; } }});book.year = 2005;alert(book.edition); 定义多个属性由于对象可以包含多个属性,所以有同时定义多个属性的需求。通过Object.defineProperties(arg1,arg2)方法来完成定义。 参数1:属性所在的对象 参数2:要修改对象的属性,使用对象字面量表示 123456789101112131415161718192021222324var book1 = {};Object.defineProperties(book1, { // 定义属性_year _year: { configurable: true, value: 2004 }, // 定义属性edition edition: { value: 1 }, // 定义属性year year: { get: function () { return this._year; }, set: function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }}); 读取属性的特性使用Object.getOwnPropertyDescriptor(arg1,arg2)方法可以获取到给定属性的描述符。返回值是一个对象,如果是访问器属性这个对象的属性有configurable、enumerable、get和set;如果是数据属性这个对象的属性有:configurable、enumerable、writable和value 参数1:属性所在的对象 参数2:属性的名称,字符串表示 12345678910// book1为上面定义的对象var descriptor = Object.getOwnPropertyDescriptor(book1, \"_year\");alert(descriptor.value); // 2004alert(descriptor.configurable); // truealert(typeof descriptor.get); // undefineddescriptor = Object.getOwnPropertyDescriptor(book1, \"year\");alert(descriptor.value); // undefinedalert(descriptor.enumerable); // falsealert(typeof descriptor.get); // function 创建对象工厂模式在函数内部来创建对象,并给对象设置属性,最后返回此对象。 1234567891011121314151617function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { alert(this.name); }; // 返回创建的对象 return o;}var person1 = createPerson(\"Tom\", 29, \"Software Engineer\");var person2 = createPerson(\"Jack\", 19, \"Docker\");person1.sayName();person2.sayName(); 构造函数模式此模式利用函数无返回值时就返回自身的特点来实现。使new操作符来实例化对象,在函数内部使用this引用实例化的对象来设置属性。与工厂模式的区别: 不显示的创建对象 直接将属性和方法赋值给了this对象 没有return语句 1234567891011121314function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); };}var person1 = new Person(\"Tom\", 29, \"Software Engineer\");var person2 = new Person(\"Jack\", 19, \"Docker\");person1.sayName();person2.sayName(); 此种方式调用构造函数实际上会经历以下4个步骤: 创建新对象; 将构造函数的作用域赋给新对象(因此this就指向了这个新对象); 执行构造函数中的代码(为这个新对象添加属性); 返回新对象。 构造函数属性创建的person1和person2都保存着Person的不同实例。这两个对象都有一个constructor(构造函数)属性。该属性指向Person 12alert(person1.constructor == Person);alert(person2.constructor == Person); 通过instanceof来检测对象的类型可以看到person1和person2既是Object的实例(因为所有的对象都是直接或间接继承自Object)同时也是Person的实例 1234alert(person1 instanceof Person);alert(person2 instanceof Person);alert(person1 instanceof Object);alert(person2 instanceof Object); 构造函数当作函数任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数如果不通过new操作符来调用,那它跟普通函数也没有区别。 12345678910// 当作构造函数调用var person1 = new Person(\"Tom\", 29, \"Software Engineer\");person1.sayName();// 作为普通函数调用Person(\"Ping\", 10, \"Cooker\");alert(job); // 是window对象的,window.job// 在另一个对象的作用域中调用var o = new Object();Person.call(o, \"zhang\", 9, \"Fisher\"); // 创建的属性和方法属于o作用域o.sayName(); // zhang 构造函数的问题以这种方式构造的实例对像对于sayName方法分别创建了实例。即alert(person1.sayName == person2.sayName);会返回false。尝试把sayName方法定义在函数的作用域之外可解决此问题。 123456789function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName;}function sayName(){ alert(this.name);} 像上面这样去定义构造函数好像是解决了问题,现在不同的实例sayName中都包含了同一个指向函数的指针,所以实例间是共享在全局作用域中定义的sayName函数。但这样产生了新的问题: 全局作用域事定义的函数实际上只能被某一个对象调用 如果对象需定义很多的方法,那么就需要定义很多个全局函数 以上问题可以通过使用原型模式来解决 原型模式我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象包含由所有的实例共享的属性和方法。简单说prototype就是通过调用构造函数而创建的那个实例对象的原型对象。 1234567891011121314function Person() { }Person.prototype.name = \"Tom\";Person.prototype.age = 29;Person.prototype.job = \"Cooker\";Person.prototype.sayName = function () { alert(this.name);};var person1 = new Person();alert(person1.name);var person2 = new Person();alert(person2.name);alert(person1.sayName == person2.sayName); // true 理解原型对象只要创建了一个新的函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针(即原型中的constructor属性指向的是拥有此原型函数)。如上面例子中的Person.prototype.constructor指向Person。 通过这个构造函数,可以继续为原型对象添加其他属性和方法。 创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性,其它方法都是从Object继承而来的。 当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。一般浏览器都将此属性定义为__proto__。所以person1.__proto__==Person.prototype返回为true。此连接存在于实例与构造函数的原型对象之间,而不存在于实例与构造函数之间 属性和方法的查找虽然创建的实例(person1)没有定义属性和方法,但是确可以调用(sayName)或访问(name)。是因为每当代码读取某个对象的属性时,都会进行一次搜索,目的是给定属性的名称。搜索规则为: 先从对象实例本身开始,如果找到则返回该属性的值 如果没有找到则会继续搜索指针指向的原型对象,在原型对象中查找此名称的属性,找到则返回属性值 isPrototypeOf()和getPrototypeOf() isPrototypeOf()用来判断函数的原型对象是不是实例的原型 getPrototypeOf()用来返回实例的原型对象,person1执行后返回的结果为Person.prototype 123alert(Person.prototype.isPrototypeOf(person1)); // Person的原型是不是person1的原型 truealert(Object.getPrototypeOf(person1) == Person.prototype); // truealert(Object.getPrototypeOf(person1).name); // 相当于Person.prototype.name 屏蔽原型属性或方法如果在实例中添加同名和属性或方法,原型是的会被屏蔽掉。类似于java中的重写机制。这是因为搜索总是先从实例开始,如果找到就返回,不会再查找原型对象 hasOwnProperty()方法可用于检测属性是否存在于实例中 1234567891011// 修改实例中的属性,获取时优先取实例中的。原型中的被屏蔽person1.name = \"Jack\";person1.sayName = function () { alert(\"abcd\");};person1.sayName();alert(person1.name);alert(\"检测属性是否存在于实例中:\" + person1.hasOwnProperty(\"name\")); // 检测属性是否存在于实例中// 删除实例中的属性delete person1.name;alert(person1.name); // 重新访问到原型中的属性 原型与in操作符in操作符可以单独使用也可以在for-in循环中使用。 单独使用时可用于检测是否包含此属性,但无法确定是在原型中包含还是实例中包含。 for-in循环返回的是所有能够通过对象访问的、可枚举的(enumerable)属性。其中既包括存在于实例中的属性,也包含存在于原型中的属性。屏蔽了原型中的不可枚举属性的实例属性也会在for-in循环中返回,因为根据规定所有开发人员定的属性都是可枚举的。 12345678910111213// 检测是否存在此属性,无论是原型还是实例中alert(\"name\" in person1);// 在原型中时返回true,在实例中时返回falsefunction hasPrototypeProperty(object, name) { return !object.hasOwnProperty(name) && (name in object);}person1.name = \"Jack\";// 遍历属性for (var prop in person1) { console.log(prop);} 获取所有的可枚举属性使用Object.keys方法可以获得实例上所有的可枚举属性的字符串数组。 12var keys = Object.keys(Person.prototype);alert(keys); // 结果中包含不可枚举的constructor属性 更简单的原型语法当为原型设置多个属性和方法时上面的设置方式就有点麻烦了。可以使用对象字面量的方式一次性完成设置。 123456789101112131415161718function Person() {}// Person.prototype.name = \"Tom\";// Person.prototype.age = 29;// Person.prototype.job = \"Cooker\";// Person.prototype.sayName = function () {// alert(this.name);// };Person.prototype = { constructor: Person, // 指定constructor的值 name: \"Tom\", age: 29, job: \"Cooker\", sayName: function () { alert(this.name); }}; 这种方式相当于完全重写了prototype对象。所以原来函数原型中的constructor属性也被覆盖成Object了。所以需要在设置时指定好constructor的值。但是自定义属性的enumerable默认为true,而原生的constructor属性是不可枚举的。所以还需要将其设置为不可枚举。 1234567891011var friend = new Person();alert(friend instanceof Object); // truealert(friend instanceof Person); // truealert(friend.constructor == Person); // falsealert(friend.constructor == Object); // true// 重设置constructor之后设置为不可枚举Object.defineProperty(Person.prototype, \"constructor\", { enumerable: false, value: Person}); 原型的动态性修改原型的属性或方法会立即反映到相应的实例上。这是因为实例和原型之间的连接是指针,而不是副本。实例在调用属性或方法时如果本身不包含会到原型中进行查找。 如果先创建实例,再重写原型对象调用时会发生错误。因为引用的是旧的原型对象,而不是最新的 12345678910111213function Person{};var friend = new Person();// 重新指定原型Person.prototype = { constructor: Person, // 指定constructor的值 name: \"Tom\", age: 29, job: \"Cooker\", sayName: function () { alert(this.name); }};friend.sayName();// 错误,原有原型中无此方法 原生对象的原型所有的原生引用类型(Array、Object、String等)都在其构造函数的原型上定义了方法。所以可以通过原生对象的原型添加新的方法。但是一般不推荐这么做,有可能会意外的重写原生的方法。 原型对象的问题首先它省略了为构造函数传递初始化参数环节,结果所有的实例在默认情况下都取得相同的属性值。另外对于引用类型的属性不同的实例持有的是同一个引用,任何一个实例修改引用中的值,都会立即反映到其它实例上,比如为数组添加或删除项。 组合构造函数模式和原型模式此方式是创建自定义类型最常见的方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。最终的结果是每个实例都会有自己的一份实例属性的副本,同时又共享着对方法的引用,最大限度的节省了内存。另外这种混合模式还支持向构造函数传递参数,集两种模式之长。 12345678910111213141516171819202122// 构造函数function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = [\"Shelby\", \"Court\"];}// 原型Person.prototype = { constructor: Person, sayName: function () { alert(this.name); }};var person1 = new Person(\"Tom\", 29, \"Software Engineer\");var person2 = new Person(\"Jack\", 19, \"Docker\");// 不影响person2中的数组person1.friends.push(\"aaa\");alert(person1.friends);alert(person2.friends);alert(person1.sayName == person2.sayName) 动态原型模式上面的组合模式使用起来稍显麻烦,需要分别定义构造函数与原型。动态原型模式即用于解决此问题。通过在构造函数中定义原型来一次性的完成定义。 123456789101112131415function Person(name, age, job) { this.name = name; this.age = age; this.job = job; // 原型中未定义sayName时,this.sayName返回undefined // 所以原型赋值只有在初次调用构造函数时会执行 if (typeof this.sayName != \"function\") { Person.prototype.sayName = function () { alert(this.name); } }}var person1 = new Person(\"Tom\", 29, \"Software Engineer\");person1.sayName(); 寄生构造函数模式寄生构造函数模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象,表面上看像是构造函数模式和工厂模式。相比构造函数模式多了返回值,相对工厂模式创建对象是需要使用new操作符。 构造函数在不返回值的情况下,默认会返回新对象实例(new出来的对象)。而通过在构造函数未尾添加一个return语句,可以重写调用构造函数时返回的值。 12345678910111213function Person(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { alert(this.name); }; return o;}var person1 = new Person(\"Tom\", 29, \"Software Engineer\");person1.sayName(); 此模式可以用在特殊情况下为对象创建构造函数。假设现在需要创建一个特殊的数组,由于不可以直接修改Array构造函数,可以使用此模式实现 123456789101112function SpecialArray() { var values = new Array(); values.push.apply(values, arguments); // 特殊数组,添加了以管道符分隔的方法 values.toPipedString = function () { return this.join('|'); }; return values;}var colors = new SpecialArray(\"red\", \"blue\", \"green\");alert(colors.toPipedString()); 稳妥构造函数模式所谓稳妥对象指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(禁止使用this和new)或者在防止数据被其它应用程序改动时使用。 此模式遵循与寄生构造函数类似的模式,但有两点不同: 一是新创建对象的实例方法不引用this 二是不使用new操作符调用构造函数 1234567891011function Person(name, age, job) { var o = new Object(); // 定义私有变量或函数 o.sayName = function () { alert(name); }; return o;}var person1 = Person(\"Tom\", 29, \"Software Engineer\");person1.sayName(); 这样person1中保存的是一个稳妥对象,除了调用sayName()方法外没有别的方式可以访问其数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的方法访问传入到构造函数中的原始数据。 继承ECMAScript中只支持实现继承,无法实现接口继承。其实现继承主要是依靠原型链来实现的 原型链基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。 构造函数、原型和实例的关系: 每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应的另一个原型中也包含着指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。 123456789101112131415161718function SuperType() { this.property = true;}SuperType.prototype.getSuperValue = function () { return this.property;};function SubType() { this.subProperty = false;}// 重写原型对象,继承SuperTypeSubType.prototype = new SuperType();SubType.prototype.getSubValue = function () { return this.subProperty;};var instance = new SubType();alert(instance.getSuperValue()); // true,调用到继承的方法 将SubType的原型对象重写之后,其constructor指向的是SuperType。 默认的原型所有的引用类型默认都继承自Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype. 确定原型和实例的关系可以通过两种方式确定原型和实例之间的关系。 第一种是使用instanceof操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数结果就会返回true。 123alert(instance instanceof Object); // truealert(instance instanceof SuperType); // truealert(instance instanceof SubType); // true 第二种方式是使用isPrototypeOf方法,同样只要是原型链中出现过的原型都可以说是该原型链所派生的实例的原型。 123alert(Object.isPrototypeOf(instance));alert(SuperType.isPrototypeOf(instance));alert(SubType.isPrototypeOf(instance)); 谨慎的定义新方法必须要注意的是替换原型相当天替换了原来原型的引用,所以如果需要重写超类型中的某个方法或者添加超类型中不存在的方法必须要在替换原型语句之后执行。否则操作的还是旧的原型引用替换完成之后并不会生效。 原型链的问题对于包含引用类型值的原型会被所有的实例所共享,会存在实例A修改引用的数据导致其它实例数据变化的情况。另外一个问题是创建子类型的实例时不能向超类型的构造函数中传递参数。所以实践中很少会单独使用原型链。 借用构造函数此方式用于解决原型中包含引用类型值所带来的问题,在子类型的构造函数内部调用超类型构造函数。调用时还可以向超类型构造函数中传参数。 12345678910111213141516171819function SuperType(name) { this.colors = [\"red\", \"blue\", \"green\"]; this.name = name;}function SubType() { // 继承SuperType,call或apply都可以 SuperType.call(this, \"tom\");}var instance1 = new SubType();// 修改自身实例的colorsinstance1.colors.push(\"black\");alert(instance1.colors);alert(instance1.name);var instance2 = new SubType();alert(instance2.colors);alert(instance2.name = \"jack\"); 借用构造函数的问题此模式无法避免构造函数模式创建对象的问题:对于方法每个实例中都会创建引用,造成资源的浪费。并且超类型的原型中定义的方法,对子类型而言也是不可见的,所以此方式很少单独使用。 组合继承将原型链和借用构造函数的技术组合到一起,从而发挥两者之长的一种继承模式。背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数实现对实例属性的继承。这样既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。 12345678910111213141516171819202122232425262728function SuperType(name) { this.colors = [\"red\", \"blue\", \"green\"]; this.name = name;}SuperType.prototype.sayName = function () { alert(this.name);};function SubType(name, age) { // 继承属性 SuperType.call(this, name); this.age = age;}// 继承方法SubType.prototype = new SuperType();SubType.prototype.sayAge = function () { alert(this.age);};var instance1 = new SubType(\"tom\", 29);instance1.colors.push(\"black\");alert(instance1.colors);instance1.sayName();instance1.sayAge();// 每一个实例的原型中都有继承自SuperType的colorsvar instance2 = new SubType(\"jack\", 20);alert(instance2.colors);instance2.sayName();instance2.sayAge(); 原型式继承借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。提供以下构造函数 123456// 构造函数function object(o) { function F(){} F.prototype = o; return new F();} 在object函数内部先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。从本质上讲object()对传入其中的对象执行了一次浅复制。ES5中新增的Object.create()方法实现了一上object函数同样的功能。可以传入两个参数第一个为要复制的对象,第二个参数与Object.defineProperties()方法中的第二个参数格式相同:每个属性的描述符。以这种方式指定的任何属性都会覆盖原型对象上的同名属性 123456789101112131415161718192021222324var person = { name: \"Tom\", friends: [\"Jack\", \"Helen\", \"Van\"]};// var person1 = object(person);var person1 = Object.create(person);person1.name = \"aaa\";person1.friends.push(\"zs\");// 两个实例共享friends// var person2 = object(person);var person2 = Object.create(person);person2.name = \"bbb\";person2.friends.push(\"ls\");var person3 = Object.create(person, { name: { value: \"Tom2\" }});alert(person1.name);alert(person2.name);alert(person.friends); 寄生式继承寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。 1234567891011121314151617181920212223// 构造函数function object(o) { function F(){} F.prototype = o; return new F();}function createAnother(original) { var clone = Object.create(original); // var clone = object(original); clone.sayHi = function () { alert(\"hi\"); }; return clone;}var person = { name: \"tom\", friends: [\"jack\", \"helen\", \"van\"]};var anotherPerson = createAnother(person);anotherPerson.sayHi(); 新对象anotherPerson不仅具有person的所有属性和方法,而且还有自己的sayHi方法。 寄生组合式继承组合继承最大的问题是无论什么情况下都会调用两次超类型的构造函数,一次是在创建子类型原型时,一次是在子类型构造函数内部。这样导致的问题是子类型的原型和实例中都会包含超类型对象的全部实例属性。 如上面组合继承中,第一次调用SuperType构造函数时,SubType.prototype会得到两个属性name和colors,他们都是SuperType的实例属性,只不过现在位于SubType的原型中。当调用SubType构造函数时又会调用一次SuperType构造函数,这一次又在新对象上创建了实例属性name和colors。于是这两个属性就屏蔽了原型中的两个同名属性。 通过寄生组合式继承可以解决上面的问题。所谓寄生组合式继承即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。背后的思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546// 构造函数function object(o) { function F(){} F.prototype = o; return new F();}function inheritPrototype(subType, superType) { // 复制超类型的原型副本到子类型的原型 // var prototype = object(superType.prototype); var prototype = Object.create(superType.prototype); // 重新设置子类型原型的构造函数为子类型本身,不替换的话是超类型 prototype.constructor = subType; subType.prototype = prototype;}function SuperType(name) { this.colors = [\"red\", \"blue\", \"green\"]; this.name = name;}SuperType.prototype.sayName = function () { alert(this.name);};function SubType(name, age) { // 继承属性,只调用一次超类型构造函数 SuperType.call(this, name); this.age = age;}// 继承方法// SubType.prototype = new SuperType();inheritPrototype(SubType, SuperType);SubType.prototype.sayAge = function () { alert(this.age);};var instance1 = new SubType(\"tom\", 29);instance1.colors.push(\"black\");alert(instance1.colors);instance1.sayName();instance1.sayAge();// 每一个实例的原型中都有继承自SuperType的colorsvar instance2 = new SubType(\"jack\", 20);alert(instance2.colors);instance2.sayName();instance2.sayAge();","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://blog.xxyxpy.pub/tags/JavaScript/"},{"name":"JavaScript高级程序设计","slug":"JavaScript高级程序设计","permalink":"http://blog.xxyxpy.pub/tags/JavaScript高级程序设计/"}]},{"title":"JavaScript(3)引用类型","slug":"前端/JS/JavaScript 高级程序设计/JavaScript(3)引用类型","date":"2017-11-18T07:01:16.000Z","updated":"2018-03-27T08:25:28.360Z","comments":true,"path":"2017/11/18/前端/JS/JavaScript 高级程序设计/JavaScript(3)引用类型/","link":"","permalink":"http://blog.xxyxpy.pub/2017/11/18/前端/JS/JavaScript 高级程序设计/JavaScript(3)引用类型/","excerpt":"Object类型创建Object实例1234567891011// 构造函数表示法var person = new Object();// var person = {};person.name = \"tom\";person.age = 12;// 对象字面量表示法var person2 = { name : \"tom\", age : 12} 使用字面量表示法时属性可以是字符串。如"name" : "tom".对象字面量可以用于向函数传递可选参数 123456789101112131415161718function displayInfo(args) { var output = \"\"; if (typeof args.name == \"string\") { output += \"Name: \" + args.name + \"\\n\"; } if (typeof args.age == \"number\") { output += \"Age: \" + args.age + \"\\n\"; } alert(output);}displayInfo({ name: \"abcde\", age : 22})displayInfo({ name: \"abcde\"})","text":"Object类型创建Object实例1234567891011// 构造函数表示法var person = new Object();// var person = {};person.name = \"tom\";person.age = 12;// 对象字面量表示法var person2 = { name : \"tom\", age : 12} 使用字面量表示法时属性可以是字符串。如"name" : "tom".对象字面量可以用于向函数传递可选参数 123456789101112131415161718function displayInfo(args) { var output = \"\"; if (typeof args.name == \"string\") { output += \"Name: \" + args.name + \"\\n\"; } if (typeof args.age == \"number\") { output += \"Age: \" + args.age + \"\\n\"; } alert(output);}displayInfo({ name: \"abcde\", age : 22})displayInfo({ name: \"abcde\"}) 访问对象属性一般使用对象.属性名来访问属性的值,同时也支持使用对象["属性名"]进行访问,这种方式一般用于直接使用属性名会导致语法错误的情况,如属性名为first name,使用点表示方法会造成语法错误。 1234alert(person2[\"name\"]);var myAge = \"age\";alert(person2[myAge]);//alert(person2.first name) // 语法错误 Array类型与其它语言最大的不同是数组中可以存放不同类型的数据,这点非常像c#中的ArrayList. 创建Array实例构造函数表示法可以去掉new操作符,但是不建议这么做。 123456789// 不指定数量var colors = new Array();// 指定数量colors = new Array(20);// 指定初始值colors = new Array(\"red\", \"blue\", \"green\");// 字面量表示法colors = [\"red\", \"blue\", \"green\"];var names = []; // 空 取值通过索引的方式去取值,如colors[0] 当取值的索引大于数组的长度时会触发数组的自动增长。另外数组的lenght属性并不是只读的,所以可以通过修改它来实现数组的伸缩,变小后访问之前的索引会报undefined,变大后访问新的索引也会报undefined。 通过给数组的length索引设置值可以实现数组的平滑增加,如colors[colors.length]="white" 1234567891011colors = [\"red\", \"blue\", \"green\"];// 访问alert(colors[0]);colors[3] = \"black\";alert(colors.length); // 自动增长到4个大小// 修改 lengthcolors.length = 2;alert(colors[2]); // undefined// 平滑增加数组colors[colors.length] = \"white\";alert(colors.length); 确定是否是数组有两种方式可以来确定是不是数组变量,instanceof和Array.isArray()方法。 12345// 确定是否是数组// instanceof 只适用于一个全局执行环境(多个框架即会有多个环境)alert(colors instanceof Array);// Array.isArray() 最终确定这个值是不是数组alert(Array.isArray(colors)); 转换方法所有对象都具有toLocaleString()、toString()以及valueOf()方法。其中调用数组的toString()和valueOf()会返回相同的值(以每个值的字符串形式拼接成的一个以逗号分隔的字符串),实际上在显示时会自动的调用数组中每项的toString()方法。toLocaleString()方法一般情况下的返回和其它两个是相同的,不同之处在于调用时会自动调用数组中每项的toLocaleString()方法。 12345678910111213141516171819202122232425262728colors = [\"red\", \"blue\", \"green\"];alert(colors.toString());alert(colors.toLocaleString());alert(colors.valueOf());alert(colors);// toLocaleString() 使用var person1 = { toLocaleString: function () { return \"person1-toLocalString\"; }, toString: function () { return \"person1\"; }};var person2 = { toLocaleString: function () { return \"person2\"; }, toString: function () { return \"person2\"; }};var person = [person1, person2];alert(person.toString()); // person1,person2alert(person.toLocaleString()); // person1-toLocalString,person2 默认情况下是使用逗号来分隔显示数组的,可以通过join()方法来自定义分隔符 1alert(colors.join('||')); 栈方法桡是一种先入后出的数据结构。ECMAScript为数组提供了push()和pop()方法来实现类似于栈的行为。 push方法在推入数据之后返回完成后数组的长度 pop方法在弹出数据之后返回被弹出的数据项 1234567var colors = new Array();var count = colors.push(\"red\", \"green\"); // 响应count值为2count = colors.push(\"yellow\");alert(count); // 3var item = colors.pop();alert(item); // yellow 队列方法队列是一种先入先出的数据结构。需要有从前面取出数据的方法,这个方法就是shift() shift方法从数据前面取出数据之后返回被取出的数据项 unshift方法实现的功能和shift正好相反,从数据前面插入数据并返回数组的长度 12345678var colors = new Array();var count = colors.push(\"red\", \"green\"); // 响应count值为2var item2 = colors.shift();alert(item2); // redcolors.unshift(\"black\", \"white\");alert(colors + \": \" + colors.length); // black,white,green: 3 数组重排序翻转 使用reverse()方法可以实现数组的翻转,调用后修改原来的数组 12var values = [1, 2, 3, 4, 5];alert(values.reverse()); 排序 翻转数组不够灵活无法实现大部分场景的需求,基于此提供sort()方法来完成排序功能。排序后也会修改原数组。默认是会升序排列,实际上是调用了每一项的toString()方法来比较字符串完成排序,这也是为什么对数值排序结果达不到预期的原因。 基于以上原因,一般情况下使用sort方法时会传入一个比较函数(通过返回-1,0,1)来实现真正的排序。 12345678910111213141516171819var value2 = [1, 11, 22, 2, 33 ,3];alert(value2.sort());alert(value2.sort(compare)); // 传入排序函数,完成真正的排序。// 默认升序,修改返回值可以实现降序结果function compare(value1, value2) { if (value1 < value2) { return -1; } else if (value1 == value2) { return 0; } else { return 1; }}// 更简单的比较函数function compare2(value1, value2) { return value1 - value2;} 操作方法concat(),向当前数组尾部插入数据并返回新的数组 array.concat(args…) args为可变参数,可以是单个数据也可以是数组 slice(),从数组指定位置开始取指定数量数据生成新的数组 array.slice(arg1,arg2) 无参时返回数组的副本 一个参数时表示从指定索引开始取到结束 两个参数时表示从指定索引开始到指定的数量 如果参数为负数则用数组长度+负数来确定位置 splice(),从数组指定位置删除数组并返回删除的数组。此函数配合不同的参数可以实现不同的功能 array.splice(num1, num2) 实现删除功能。从指定的开始索引位置(num1)删除指定数量(num2)的数据 array.splice(num1, 0, args…) 实现插入功能。从指定的开始索引位置(num1)删除0条数据,并在此位置插入数据可以是多个 array.splice(num1,num2,args…) 实现替换功能。指定开始索引位置(num1)要删除num2条数据,并在此位置插入的任意数量的项 123456789101112131415161718192021222324var colors = [\"red\", \"green\", \"yellow\"];var colors2 = colors.concat(\"black\", [\"red\", \"green\"]);alert(colors2);var colors3 = colors2.slice(); // 默认拷贝全部alert(colors3);var colors4 = colors3.slice(0, 2); // 从0索引开始取2个alert(colors4);var colors5 = colors3.slice(3); // 从索引3开始取到结束alert(colors5);// 删除,指定删除开始的位置和要删除的项数var colors6 = colors3.splice(0,2); // 删除前两项,返回删除的数组alert(colors3);alert(colors6);// 插入,指定开始位置、要删除的项数(0)、插入的项(可以多个)var colors7 = colors3.splice(2, 0, colors6, \"haha\"); // 返回空数组alert(colors3);// 替换,指定位置插入任意数量的项同时删除任意数量的项。指定开始位置、要删除的项数、要插入的任意数量的项var colors8 = colors2.splice(2, 2, \"haha\", [\"hehe\"]); // 返回删除的项数组alert(colors2);alert(colors8); 位置方法indexOf()和lastIndexOf()用于实现查找功能。分别为向后和向前查找,都支持传入两个参数,第一个参数查找的数据为必传项,第二个参数为开始查找的索引(可选项)。当查找到时返回索引值,无结果时返回-1 1234var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];alert(numbers.indexOf(3));alert(numbers.indexOf(3, 3));alert(numbers.lastIndexOf(3, 1)); 迭代方法1234567891011121314151617181920var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];// every 每项都返回true,结果为truevar everyResult = numbers.every(function (value, index, array) { return value > 2; });alert(\"every:\" + everyResult);// filter 返回true的项组成的数组var filterResult = numbers.filter(function (value, index, array) { return value > 2; });alert(\"filter:\" + filterResult);// forEach 无返回值numbers.forEach(function (value, index, array) { console.log(value);return; });// map 将调用的结果组成数组返回var mapResult = numbers.map(function (value, index, array) { return value * index; });alert(\"map:\" + mapResult);// some 只要有一项为true,返回truevar someResult = numbers.some(function (value, index, array) { return value > 2; })alert(\"some:\" + someResult); 缩小方法使用reduce()和reduceRight()方法实现缩小功能,这两个方法都会迭代数组所有的项。区别在于一个是从前向后,一个是从后向前。都有四个参数,前两个是必项,后两个为可选。 参数1:前次迭代的结果,prev 参数2:当前项,cur 参数3:当前项的索引,index 参数4:当前迭代的数组,array 12345678910var values = [1, 2, 3, 4, 5];// reducevar sum = values.reduce(function(prev, cur, index, array){ return prev + cur;});alert(sum); // 15// 指定初始值sum = values.reduce(function(prev, cur, index, array){ return prev + cur;}, 1);alert(sum); // 16// reduceRightsum = values.reduceRight(function(prev, cur, index, array){ return prev + cur;}, 1);alert(sum); // 16 Date类型创建Date实例可以使用多种方式构造Date实例。 Date.parse和Date.UTC可以不显式的使用,构造函数会根据传入的参数是字符串还是数值来自动进行调用 123456789101112131415// 构造函数,默认取得当前的日期和时间var now = new Date();alert(now);// Date.parse(),传入字符串不支持时返回NaNvar someDate = new Date(Date.parse(\"May 25, 2004\"));// 等价new Date(\"May 25, 2004\");// Date.UTC(), 月份和小时数从0开始var y2k = new Date(Date.UTC(2000, 0)); // 等价 new Date(2000, 0);var allFives = new Date(Date.UTC(2015, 4, 5, 17, 55, 55));// 计算间隔var start = Date.now();setTimeout(function () { var end = Date.now(); alert(end - start);}, 3000); 继承的方法与其它的引用类型相同,Date也重写了toLocaleString()、toString和valueOf()方法 toLocaleString() 方法会按照与浏览器设置的地区相适应的格式返回日期和时间 toString()方法则通常返回带有时区信息的日期和时间 valueOf()方法返回日期的毫秒值,因此可以用于日期比较 1234567891011121314151617// 构造函数,默认取得当前的日期和时间var now = new Date();// alert(now);// Date.parse(),传入字符串不支持时返回NaNvar someDate = new Date(Date.parse(\"May 25, 2004\"));// 等价new Date(\"May 25, 2004\");// Date.UTC(), 月份和小时数从0开始var y2k = new Date(Date.UTC(2000, 0)); // 等价 new Date(2000, 0);var allFives = new Date(Date.UTC(2015, 4, 5, 17, 55, 55));alert(now > someDate);// 计算间隔var start = Date.now();setTimeout(function () { var end = Date.now(); alert(end - start);}, 3000); 格式化方法 toDateString():显示星期几 月 日 和 年 toTimeString():显示时 分 秒 和 时区 toLocaleDateString():显示星期几 月 日 和 年 toLocaleTimeString():显示 时 分 秒 toUTCString():显示完成的UTC日期 1234567var now = new Date();alert(\"toDateString:\" + now.toDateString());alert(\"toTimeString:\" + now.toTimeString());alert(\"toLocaleDateString:\" + now.toLocaleDateString());alert(\"toLocaleTimeString:\" + now.toLocaleTimeString());alert(\"toUTCString:\" + now.toUTCString()); 更多方法参考菜鸟教程 RegExp类型更多内容可参考 ECMAScript通过RegExp类型来支持正则表达式。语法如下: var expression = /pattern/ flags; pattern可以是任何简单或复杂的正则表达式(字符、限定符、分组、向前查找及反向引用) flags标志有3种 g 全局模式,此模式将应用于所有的字符串,而非发现第一个匹配项时立即停止 i 不区分大小写模式,运行时忽略大小写 m 多行模式,妈在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项 123456// 匹配所有的at的实例var pattern1 = /at/g;// 不区分大小写匹配bat或cat,只匹配第一个var pattern2 = /[bc]at/i;// 匹配所有以at结尾的3个字符,不区分大小写var pattern3 = /.at/gi; 元字符 ([{\\^$|?*+.}]) 元字符在使用时如果要作为字符串的一部分需要进行转义 1234// 匹配第一个[bc]at,不区分大小写var p1 = /\\[bc\\]at/i;// 匹配所有以.at不区分大小写var p2 = /\\.at/gi; 构造函数同样可以使用构造函数构造RegExp对象,语法为: var p = new RegExp(patternStr, flag); 由于使用字符串构造,所以某些情况下需要对字符串进行双重转义。 如字面量 /\\[bc\\]at/ 的等价字符串为 “\\\\[bc\\\\]at” 1var p = new RegExp(\"[bc]at\", \"i\"); 实例属性一般比较少使用,主要用来判断其设置的flag global:布尔值,是否设置g标志 ignoreCase:布尔值,是否设置i标志 multiline:布尔值,是否设置m标志 lastIndex:整数,下一个搜索开始的字符位置,从0算起 source:正则表达式的字符串表示 实例方法最常使用的是实例方法是exec(),此方法用于返回包含第一个匹配项信息的数组,没有匹配项时返回null。 有两个属性index(表示匹配到的位置)和input(应用正则表达式的字符串)。 在返回的数组中,第一项是与整个模式匹配的字符串,其它项是模式中的捕获组匹配的字符串。 123456var text = \"mom and dad and baby\";var pattern = /mom( and dad( and baby)?)?/gi;// 匹配到3组字符var matches = pattern.exec(text);alert(matches.index);alert(matches.input); 关于全局匹配g 即使设置了全局匹配,每次也只会返回一个匹配项。要查找下一个时需要继续调用exec方法。而如果不设置g标志即使能匹配多个,每次调用时还是只返回第一个。 123456789var text = \"mom and dad and babymom and dad and baby\";var pattern = /mom( and dad( and baby)?)?/gi;// 匹配到3组字符var matches = pattern.exec(text);alert(matches.index);alert(matches.input);matches = pattern.exec(text);alert(matches.index);alert(matches.input); toLocaleString()和toString() 这两个方法都会返回下则表达式的字面量,与创建正则表达式的方式无关。 12345var text = \"mom and dad and babymom and dad and baby\";var pattern = /mom( and dad( and baby)?)?/gi;alert(pattern.toLocaleString());alert(pattern.toString()); Function类型函数实际上是对象。每个函数都是Function类型的实例,而且具有属性和方法。函数名是指向函数对象的指针。 定义函数可以通过函数式声明、函数表达式和Function构造函数来定义 12345678910// 函数式声明function sum1(num1, num2) { return num1 + num2;}// 函数表达式var sum2 = function (num1, num2) { return num1 + num2;};// 构造函数。不推荐使用var sum3 = new Function(\"num1\", \"num2\", \"return num1 + num2;\") 可以将指定的变量赋值为函数 123456function sum1(num1, num2) { return num1 + num2;}var anotherSum = sum1;alert(sum1(10, 20));alert(anotherSum(10, 20)); 函数声明与函数式表达式解析器在向执行环境中加载数据时会先读取函数声明,使其在执行任何代码前可用,函数式表达式则必须等到解析器执行到它所在的代码行。 12345678alert(sum1(10, 20)); // 正确function sum1(num1, num2) { return num1 + num2;}alert(sum2(1, 2)); // 错误var sum2 = function (num1, num2) { return num1 + num2;}; 作为值返回函数不仅可以作为参数传递,还可以作为另外一个函数的结果返回。 123456789101112131415161718192021222324252627function add10(num) { return num + 10;}var result = callSomeFunction(add10, 10);// 返回一个函数的执行结果function callSomeFunction(someFunction, args) { return someFunction(args);}alert(result);// 函数体作为返回值function createComparisonFunction(propertyName) { return function (o1, o2) { var v1 = o1[propertyName]; var v2 = o2[propertyName]; if (v1 < v2) { return -1; } else if(v1 > v2) { return 1; } else { return 0; } }}var data = [{name: \"tom\", age: 28}, {name: \"jack\", age: 30}];data.sort(createComparisonFunction(\"name\"));alert(data[0].name); 函数内部属性callee属性 是一个指向拥有arguments对象的函数的指针。简单来说arguments.callee就代表着当前函数 12345678910111213141516// callee 一个指向拥有arguments对象的函数的指针function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); }}// 使用calleefunction factorial2() { if (num <= 1) { return 1; } else { return num * arguments.callee(num - 1); }} this属性 代表函数据以执行的环境对象。比如在全局作用域中this对象引用的就是window 函数属性和方法每个函数都包含有length和prototype这两个属性。其中length指的是函数希望接收的命名参数的个数,prototype属性保存了函数的实例方法。 apply()方法 用于在特定的作用域内调用函数。第一个参数是运行函数的作用域,第二个参数是参数数组 123456789function sum(num1, num2) { return num1 + num2;}function callSum(num1, num2) { return sum.apply(this, arguments);}alert(callSum(10, 20)); call()方法 与apply功能相同,不同之处在于传参时第一个参数this没有变化,而其余的参数必须要直接传递给函数 123456789function sum(num1, num2) { return num1 + num2;}function callSum2(num1, num2) { return sum.call(this, num1, num2);}alert(callSum2(10, 20)); bind()方法 此方法会创建一个函数的实例,其this值会被绑定到传递给bind函数的值。相当于修改了原来的方法的运行环境。 12345678window.color = \"red\";var o = {color: \"blue\"};function sayColor() { alert(this.color);}var objectSayColor = sayColor.bind(o);objectSayColor(); // bluesayColor(); // red 基本包装类型为了便于操作ECMAScript提供了3个特殊的引用类型:Boolean、Number、String。每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。 123var s1 = \"hello world\";// 执行此语句时自动创建了一个String类型的实例,然后在实例上调用方法,最后销毁实例var s2 = s1.substring(2); Boolean类型 var booleanObject = new Boolean(true); 用处不大,使用时容易造成误解。 123var falseObject = new Boolean(false);var result = falseObject && true; // 对象被转化为truealert(result); // true。 Number类型 var numberObject = new Number(100); toFixed()方法 按指定的小数位返回数值的字符串表示,会执行四舍五入。 12var num = 10.005;alert(num.toFixed(2)); // \"10.01\" toExponential() 返回以指数的表示法的字符串形式,也可以指定小数位数 toPrecision()方法 返回最适合当前数值的格式,可能是固定大小也可能是指数格式 String类型更多方法参考 var stringObject = new String(“hello world”); length属性 用于返回字符串的字符数量,汉字也算一个字符。 方法 说明 charAt(index) 传入从0开始的索引,返回其字符。同样可以使用[index]访问 charCodeAt(index) 传入索引,返回字符的编码 concat(args…) 用于字符串拼接,可以接收任意多个参数 slice(index1,index2) 截取字符,参数分别为开始的索引和结束的索引 subString(index1,index2) 与slice效果相同。不同之处在于索引为负值时的处理 subStr(index,num) 截取字符,从指定的索引开始截取指定数量 indexOf(str) 从开头向后搜索子字符串,返回位置索引 lastIndexOf(str) 从末尾向前搜索子字符串,返回位置索引 trim() 去除字符串前后空格 toLowerCase() 转小写 toUpperCase() 转大写 match(regexp) 以数组形式返回符合正则表达式的匹配 search(regexp / str) 传入字符串或正则表达式,找到时返回索引,找不到返回-1 replace(regexp / str, newstr) 用新字符替换旧字符或与正则表达式匹配的字符 split(regexp / str ?, limit?) 用指定的字符串或正则表达式分隔字符串成数组,limit指数组返回的长度 localeCompare(str) 比较两个字符串,返回-1, 1, 0 fromCharCode(args…) 接收多个字符编码,转换为一个字符串 单体内置对象Global对象也称全局对象。不属于任何其它对象的属性和方法都属于Global对象的。比如之前说到的isNaN() isFinite() parseInt() parseFloat() URI编码方法 encodeURI() decodeURI() 用于整个链接的编码和解码。如:http://www.baidu.com encodeURIComponent() decodeURIComponent() 用于部分链接的编码和解码。如:index.html eval() 对传入的字符串进行ECMAScript解析并执行,被执行的代码拥有与执行环境相同的作用域链 window对象 window对象是global对象的一个属性,在全局环境中定义的变量都属于window对象 Math对象包含有大量的数学计算用到的方法和值。参考 最常用的有 max()最大值 min()最小值 ceil()向上取整 floor()向下取值 round()四舍五入 random()0~1间的随机数,不包含0和1","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://blog.xxyxpy.pub/tags/JavaScript/"},{"name":"JavaScript高级程序设计","slug":"JavaScript高级程序设计","permalink":"http://blog.xxyxpy.pub/tags/JavaScript高级程序设计/"}]},{"title":"rabbitmq环境搭建","slug":"linux/rabbitmq环境搭建","date":"2017-11-03T01:43:30.595Z","updated":"2017-11-03T01:43:30.604Z","comments":true,"path":"2017/11/03/linux/rabbitmq环境搭建/","link":"","permalink":"http://blog.xxyxpy.pub/2017/11/03/linux/rabbitmq环境搭建/","excerpt":"","text":"安装123456789101112131415# 首先需要安装erlang环境# 在线安装,在线安装较慢也可以下载下来手动安装rpm -Uvh http://www.rabbitmq.com/releases/erlang/erlang-19.0.4-1.el7.centos.x86_64.rpm# 手动安装rpm -i erlang-19.0.4-1.el7.centos.x86_64.rpm# 查看安装是否成功rpm -qa|grep erlang# 安装rabbitmqrpm -Uvh http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.12/rabbitmq-server-3.6.12-1.el7.noarch.rpm# 提示需要socat,安装一下后重新安装rabbitmqyun install socat# 手动安装rabbitmqrpm -i rabbitmq-server-3.6.12-1.el7.noarch.rpm rpm -qa|grep rabbitmq 运行12345678# 开启service rabbitmq-server start# 关闭service rabbitmq-server stop# 查看状态rabbitmqctl status# 重启service rabbitmq-server restart 访问默认在15672端口上开启web访问服务,直接访问时无法连接。需要安装维护插件 123rabbitmq-plugins enable rabbitmq_management# rebootservice rabbitmq-server restart 再次打开http://192.168.80.129:15672即可打开登录界面,用户名和密码都是guest 登录直接用guest是无法完成登录的,官网介绍的是默认不允许guest在非localhost的机器上面登录。需要修改配置 12345vim /etc/rabbitmq/rabbitmq.config# 添加如下内容,注意最后一个.是必须的[{rabbit, [{loopback_users, []}]}].# reboot 即可访问登录service rabbitmq-server restart 错误处理在集成spring boot时报Failed to declare queue,原因是没有这个queue,需要在管理页面添加这个queue。 另外如果要启用新的用户,也可以在管理界面进行添加 参考: centos7安装rabbitmq spring boot 集成rabbitmq spring boot中使用rabbitmq","categories":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/categories/linux/"}],"tags":[{"name":"rabbitmq","slug":"rabbitmq","permalink":"http://blog.xxyxpy.pub/tags/rabbitmq/"}]},{"title":"JavaScript(2)变量作用域和内存","slug":"前端/JS/JavaScript 高级程序设计/JavaScript(2)变量作用域和内存","date":"2017-10-25T07:01:16.000Z","updated":"2018-01-23T01:47:39.036Z","comments":true,"path":"2017/10/25/前端/JS/JavaScript 高级程序设计/JavaScript(2)变量作用域和内存/","link":"","permalink":"http://blog.xxyxpy.pub/2017/10/25/前端/JS/JavaScript 高级程序设计/JavaScript(2)变量作用域和内存/","excerpt":"基本类型和引用类型的值基本类型(Undefined、Null、Number、String、Boolean)是按值访问的。而引用类型的值是保存在内存中对象的引用,所以引用类型的值是按引用访问的。 动态属性引用类型可以动态的添加属性和方法,而基本类型不可以 1234567var person = new Object();person.name = \"tom\";alert(person.name);var name = \"abcd\";name.age = 27;alert(name.age); // undefined,只能为引用类型动态添加属性 复制变量值复制基本类型的值,会在变量对象上创建一个新值,两者互不影响;","text":"基本类型和引用类型的值基本类型(Undefined、Null、Number、String、Boolean)是按值访问的。而引用类型的值是保存在内存中对象的引用,所以引用类型的值是按引用访问的。 动态属性引用类型可以动态的添加属性和方法,而基本类型不可以 1234567var person = new Object();person.name = \"tom\";alert(person.name);var name = \"abcd\";name.age = 27;alert(name.age); // undefined,只能为引用类型动态添加属性 复制变量值复制基本类型的值,会在变量对象上创建一个新值,两者互不影响; 复制引用类型的值,复制的是指针值,而两个变量的指针指向的是同一个内存地址,即同一个引用对象。所以修改任何一个变量都会影响另一个变量。 123456789// 基本类型复制,拷贝值var num1 = 5;var num2 = num1; // 两者互不影响// 引用类型复制,拷贝引用。互相影响var obj1 = new Object();var obj2 = obj1;obj1.name = \"jack\";alert(obj2.name); // jack 传递参数ECMAScript中所有函数的参数都是按值传递的。也就是说把函数外部的值复制给函数内部的参数。基本类型复制值的副本,引用类型复制的引用。 所以向函数内部传递一个引用类型,在函数内修改这个引用类型时会反应到外部的引用对象上。 检测类型typeof用于检测变量是不是基本数据类型。如果变量是一个对象或null,此操作会显示为object。假如要检测一个变量是否是Person类型的对象,就无从下手了。此时需要用到ECMAScript提供的instanceof操作符 1234alert(person instanceof Object);alert(colors instanceof Array)var obj1 = new Object();alert(obj1 instanceof Array); 执行环境和作用域执行环境定义了变量或函数有权限访问的其它数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。 全局执行环境 最外围的执行环境,一般被认为是window对象。因此所有全局变量和函数都是作为window对象的属性和方法创建的,当关闭网页或浏览器此环境中的变量和函数才会销毁。 私有执行环境 每个函数都有自己的执行环境。当执行流进入函数时,函数的环境就会被推入一个环境栈中。执行完成之后栈将其环境弹出,把控制权返回给之前执行的环境。退出私有执行环境时,在此环境中创建的变量和函数会被销毁。 标识符解析在执行环境中使用一个变量标识符时会沿着作用域链(从当前环境向后一直到全局环境形成的一条链)查找这个标识符,直到找到它。 1234567var color = \"red\";function getColor() { var color = \"blue\"; return window.color;// 访问到全局执行环境}alert(getColor()); 延长作用域链当执行流进入下列语句时作用域链会得到加长: try-catch语句的catch块 with语句 这两个语句都会在作用域链的前端添加一个变量对象。with语句会将指定的对象添加到作用域链中。catch语句会创建一个新的变量对象,其中包含被抛出的错误对象的声明。 没有块级作用域ECMAScript并不像其它高级语言那样存在块级作用域。在块中声明的变量在外部可以访问到,通过以下代码可以看出: 123456789// 没有块级作用域if (true) { var s = \"haha\";}alert(s);for (var i = 1; i < 10; i++) {}alert(i); 垃圾回收JavaScript和现代的高级语言一样具有自动垃圾回收机制,不需要开发人员手动的去释放内存。回收机制通过有两个策略: 标记清除当变量进入环境,就将其标遍为进入环境。此时变量不可以进行释放。变量离开环境就标记为离开。垃圾回收器在执行时会根据不同的标记执行不同的操作(是否进行清除等)。 引用计数通过记录变量被引用的次数来决定是否清除。如果引用次数为0说明可以被清除掉。但会存在A引用B,而B又引用A的循环引用问题。导致无法完成清除。所以在不需要使用时最好给对象赋值为null以方法垃圾回收器及时清除。","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://blog.xxyxpy.pub/tags/JavaScript/"},{"name":"JavaScript高级程序设计","slug":"JavaScript高级程序设计","permalink":"http://blog.xxyxpy.pub/tags/JavaScript高级程序设计/"}]},{"title":"JavaScript(1)基本概念","slug":"前端/JS/JavaScript 高级程序设计/JavaScript(1)基本概念","date":"2017-10-21T07:01:16.000Z","updated":"2018-01-30T09:59:06.853Z","comments":true,"path":"2017/10/21/前端/JS/JavaScript 高级程序设计/JavaScript(1)基本概念/","link":"","permalink":"http://blog.xxyxpy.pub/2017/10/21/前端/JS/JavaScript 高级程序设计/JavaScript(1)基本概念/","excerpt":"defer和asynjs代码放在head中时,按照浏览器解析的速度需要等到js下载并运行完成之后才会解析到body显示出来页面内容。所以当有不涉及页面呈现相关的js时一般会放在body的底部。 1234567891011<!DOCTYPE html><head> <meta charset=\"UTF-8\"> <title>Document</title></head><body> <!-- 内容 --> <script type=\"text/javascript\" src=\"exampl1.js\"></script> <script type=\"text/javascript\" src=\"exampl2.js\"></script></body></html> 除了以上写法之外ECMAScript还提供了另外的两种方案,使用defer和async。使用这两个关键字都可以实现js(必须是外部js,页面内定义的js不行)的异步加载,即在加载完成内容之后再加载和运行js。","text":"defer和asynjs代码放在head中时,按照浏览器解析的速度需要等到js下载并运行完成之后才会解析到body显示出来页面内容。所以当有不涉及页面呈现相关的js时一般会放在body的底部。 1234567891011<!DOCTYPE html><head> <meta charset=\"UTF-8\"> <title>Document</title></head><body> <!-- 内容 --> <script type=\"text/javascript\" src=\"exampl1.js\"></script> <script type=\"text/javascript\" src=\"exampl2.js\"></script></body></html> 除了以上写法之外ECMAScript还提供了另外的两种方案,使用defer和async。使用这两个关键字都可以实现js(必须是外部js,页面内定义的js不行)的异步加载,即在加载完成内容之后再加载和运行js。 defer虽然js被写在了head中加载,但是由于使用了defer实际上会在加载到</html>之后才会加载,并且会按先后顺序加载。 1234567891011<!DOCTYPE html><head> <meta charset=\"UTF-8\"> <title>Document</title> <script type=\"text/javascript\" defer=\"defer\" src=\"exampl1.js\"></script> <script type=\"text/javascript\" defer=\"defer\" src=\"exampl2.js\"></script></head><body> <!-- 内容 --></body></html> async与defer一样都是异步加载,唯一不同的是加载顺序不固定。有可能先加载exampl2.js 1234567891011<!DOCTYPE html><head> <meta charset=\"UTF-8\"> <title>Document</title> <script type=\"text/javascript\" async src=\"exampl1.js\"></script> <script type=\"text/javascript\" async src=\"exampl2.js\"></script></head><body> <!-- 内容 --></body></html> noscript元素当浏览器不支持或关闭JavaScript功能时显示此元素中的内容,其它情况不显示 12345678910111213<!DOCTYPE html><head> <meta charset=\"UTF-8\"> <title>Document</title> <script type=\"text/javascript\" async src=\"exampl1.js\"></script> <script type=\"text/javascript\" async src=\"exampl2.js\"></script></head><body> <noscript> <p>本页面需要浏览器支持(启用)JavaScript</p> </noscript></body></html> 严格模式严格模式为JavaScript定义了一种琐的解析与执行模型。 如果要在整个脚本中启用,需要在顶部添加"use strict";。如果需要在函数中启用: 1234function doSomething(){ \"use strict\"; // 函数体} 使用var定义变量局部变量与全局变量通过var定义的变量将成为定义变量作用域中的局部变量。也就是说在该作用域外无法使用该变量。如果想在作用域外部使用,需要省略var来创建一个全局变量。 123456789101112function test() { var message = 100;}test();alert(message);// 错误/********** 全局变量************/function test() { message = \"hi\"; // 全局变量}test();alert(message); // 正确 同时定义多个变量可以在一条语句中同时定义多个变量,赋值或不赋值均可。为了提高可读性,建议换行 123var message = \"hi\", found = false, age; 数据类型5种简单数据类型和1种复杂数据类型 简单数据类型:Undefined、Null、Boolean、Number和String 复杂数据类型:Object typeof操作符typeof用来检测给定变量的数据类型,由于是操作符而不是函数所以检测时可以不使用括号(使用也不会报错) 12345678function test() { message = \"hi\"; // 全局变量}test();alert(message);alert(typeof message);alert(typeof(message));alert(typeof 90); 检测变量时可能的返回值: “undefined” 如果这个值未定义 “boolean” 如果这个值是布尔值 “string” 如果这个值是字符串 “number” 如果这个值是数值 “object” 如果这个值是对象或null “function” 如果这个值是函数 调用typeof null会返回object,因为特殊值null被认为是一个空的对象引用 Undefined类型Undefined类型只有珍上值,即特殊的undefined。在使用var声明变量但未对其加以初始化时变量的值就是undefined。如: 12var miss; // 未经初始化,默认取得 undefined 值alert(miss == undefined); // true 未初始化与未定义的区别。主要的是区别在于未初始化还是给默认的undefined值。而未定义变量是无法进行除了typeof以外的操作的。 1234var message;alert(message); // \"undefined\"alert(age);// 报错,由于未定义变量alert(typeof age); // \"undefined\" Null类型Null类型是第二个只有一个值的数据类型。特殊的值是null,null值表示一个空对象的指针。所以typeof检测null时会返回”object” 12var car = null;alert(typeof car); // \"object\" 如果定义的变量将来是为了要保存对象,那么最好将该变量初始化为null。这样的话就可以通过检查是不是null来检测其有没有保存了对象的引用。 123if(car != null){ ...} undefined值是派生自null值的,所以两者相等性测试会返回true 1alert(undefined == null); // true Boolean类型Boolean类型只有两个值,true和false,区分大小写,大写的只是标识符,并不是Boolean类型 12var a = true;var b = false; 与其它类型的转换任何类型都可以使用转换函数Boolean()转换成Boolean值 12var message = \"hello\";var messageAsBoolean = Boolean(message); 各种数据类型转换成Boolean值的规则如下: 数据类型 转换为true 转换为false Boolean true false String 任何非空字符串 “”(空字符串) Number 任何非0数字值(包括无穷大) 0和NaN Object 任何对象 null Undefined n/a undefined 自动执行转换当在如if等的判断语句中使用会触发自动的Boolean()转换 1234var message = \"hello\";if(message){ alert(\"Value is true\");} Number类型Number类型既包含整数也包含浮点数。 整数整数表示时默认是十进制,支持八进制与十六进制。 八进制表示时第一位必须是0,数字序列为0~7 十六进制表示时必须以0x开头,后面跟十六进制的数字(0~9及A~F),字母不区分大小写 进行算术运算时都会被转换成十进制参与计算 12345678// 八进制var octNum1 = 070; // 56var octNum2 = 079; // 无效,9超过7.解析为79var octNum3 = 08; // 无效,解析为8// 十六进制var hexNum1 = 0xA; // 10var hexNum2 = 0x1f; // 31 浮点数浮点数是指数值中包含小数点并且小数点后必须至少有一位数字。由于浮点数占用的内存空间是整数的两倍,所以js会将小数点后没有数字或者本身就是整数(1.0)的转换为整数来保存。 指数表示法 对于极大或极小的数为了看起来方便可以使用科学计数法表示,使用时不区分e的大小写。 12var floatNum1 = 3.125e7; // 等于31250000var floatNum2 = 3e-7; // 0.0000003 浮点数的精度 最高精度是17位小数,在运算时会损失精度。如0.1+0.2结果可能不是0.3,而是0.3000000000000004 数值范围ECMAScript能表示的最小数值保存在Number.MIN_VALUE中,最大数据被保存在Number.MAX_VALUE中。 如果运算结果超出了数值范围那么结果会被自动转换成Infinity值,负数对应-Infinity(负无穷),正数对应+Infinity(正无穷)。需要说明的是Infinity值无法参与运算。 如果想要确定一个值是不是有穷的话可以命名用isFinite()函数,如果值在最大与最小之间会返回true 12var result = Number.MAX_VALUE + Number.MAX_VALUE;alert(isFinite(result)); // false NaNNaN,即非数值(Not a Number),是一个特殊的数值,用于表示一个本来要返回数值的操作数未返回数值的情况。比如除0时会返回NaN,不影响其它代码的运行。 特点: 任何涉及到NaN的操作都会返回NaN。如NaN/10 NaN与任何值都不相等,包括其本身。如alert(NaN == NaN); // false isNaN() 使用isNaN来判断传入的参数是否“不是数值”。传入的参数会被尝试转换成数值,如:“10”,Boolean。任何不能被转换成数值的值都会导致这个函数返回true。 12345alert(isNaN(NaN)); // truealert(isNaN(10)); // falsealert(isNaN(\"10\")); // falsealert(isNaN(\"blue\")); // truealert(isNaN(true)); // false,Boolean值true被转换为1,false被转换为0 当isNaN()作用于对象时会首先调用对象的valueOf()方法,然后确定该方法返回的值是否可以转换为数值。如果不能则基于这个返回值再调用toString()方法,再测试返回值。 数值转换通过Number()、parseInt()和parseFloat()可以把非数值转换为数值。 Number()转型函数,可以作用于任何的数据类型。转换规则为: 如果是Boolean值,true转换为1,false转换为0 如果是数字,简单的传入与返回 如果是null值,返回0 如果是undefined,返回NaN 如果是字符串,规则相对复杂。具体为: 如果只包含数字(包括前带加号或头号的情况),将其转换为十进制数值。”1”转为1,”123”转为123,”011”转为11(忽略前导的0) 如果包含有效的浮点格式,如”1.1”转换为1.1,而”1.1.1”转换会报NaN。同样的会忽略前导的0即”01.1”转换为1.1 如果包含有效的十六进制格式,如”0xf”,会转换为相同大小的十六进制整数值 如果是空的(不包含任何字符),则将其转换为0 如果包含除上述格式之外的字符,则转换为NaN 如果是对象,则先调用对象的valueOf()方法后按前面的规则进行转换。若结果为NaN则调用返回值的toString()方法,然后再按前面的字符串转换规则进行转换 1234var num1 = Number(\"hello world\"); // NaNvar num2 = Number(\"\"); // 0var num3 = Number(\"000011\"); // 11var num4 = Number(true); // 1 parseInt()多用于处理整数。转换时更多的是看其是否符合数值模式。它会忽略字符串前面的空格,直到找到第一个非空格字符。如果第一个字符不是数字字符或正负号,就会返回NaN(Number()返回0)。如果符合条件继续解析下一个字符,直到解析完或遇到非数字字符。例如:”1234blue”会被转换为1234,”22.5”转换为22。对于有小数点的情况需要注意的是它不是四舍五入,而是直接舍掉小数点 需要注意的是parseInt()能识别出整数格式(十进制,十六进制,八进制),如果字符串以”0x”开头且后跟数字字符会被当作十六进制整数来转换(”0xF”转换为15)。如果以”0”开头且后跟数字字符会被当作八进制来转换 1234567var num1 = parseInt(\"1234blue\"); // 1234var num2 = parseInt(\"\"); // NaNvar num3 = parseInt(\"oxA\"); // 10var num4 = parseInt(\"22.5\"); // 22var num5 = parseInt(\"070\"); // 56var num6 = parseInt(\"70\"); // 70var num7 = parseInt(\"oxf\"); // 15 es5中parseInt()已经不具备解析八进制的能力,”070”会被解析成为十进制的70 指定转换的进制 在转换时可以在第二个参数中指定转换时的进制,前提是要知道待转换的字符串是多少进制的。在指定第二参数的情况下可以忽略进制的特殊标记(即八进制的0和16进制的0x) 12var num1 = parseInt(\"AF\", 16);var num2 = parseInt(\"70\", 8); parseFloat()与parseInt()相同也是从第一个字符开始解析直到结束或者遇到一个无效的浮点数字为止。所以字符串中第一个小数点是有效的,后面的小数点无效。另外它与parseInt()的另一个区别在于会始终忽略前导0,所以说是不支持八进制与十六进制的,所以这两个进制进来会被转换为0。另外要注意的是如果待转换的本身就是一个整数,会被转换为整数返回 123456var num1 = parseFloat(\"1234blue\"); // 1234var num2 = parseFloat(\"oxA\"); // 0var num3 = parseFloat(\"22.5\"); // 22.5var num4 = parseFloat(\"22.5.2\"); // 22.5var num5 = parseFloat(\"098.5\"); // 985var num6 = parseFloat(\"3.125e7\"); // 31250000 String类型String类型用于表示由0或多个16位Unicode字符组成的字符序列,即字符串。可以由单引号或双引号表示。 字符字面量也称为转义序列,会被当作为1个字符来解析。 字面量 含义 说明 \\n 换行 \\t 制表 \\b 空格 \\r 回车 \\f 进纸 \\\\ 斜杠 \\’ 单引号 ‘He said,\\’hey.\\’’ \\” 双引号 “He said,\\”hey.\\”” \\x nn 以十六进制代码nn表示的一个字符(n为0~F) \\x 41 表示 A(ascii 的 65) \\u nnnn 以十六进制代码nnnn表示的一个Unicode字符(n为0~F) \\u 03a3表示希腊字符∑ 字符串的不可变性与java等其它高级语言相同JS中的字符串也是不可变的。一旦创建,修改后会创建新的字符串,原来的会被销毁掉。看如下代码: 12var lang = \"Java\";lang = lang + \"Script\"; 第二行相加时首先会创建一个容纳10个字符的新字符串,然后填充上”Java”和”Script”。最后销毁掉原来的这两个字符。 转换为字符串要把一个值转换为字符串有两种方式。一种是使用几乎每个值都有的toString()方法。另外一种是通过加号拼接一个空的字符串(””) toString()数值、布尔值、对象和字符串值都有toString()方法,需要注意的是字符串使用toString()时会返回字符串的一个副本。null和undefined值没有这个方法。 数值在调用toString()时如果不传入参数会默认输出十进制字符串表示,可以转入相应的进制作为参数。 123456var num = 10;alert(num.toString()); // \"10\"alert(num.toString(2)); // \"1010\"alert(num.toString(8)); // \"12\"alert(num.toString(10));// \"10\"alert(num.toString(16));// \"a\" String()此函数能够将任何类型的值 转换为字符串。转换规则为: 有toString()方法时,调用该方法(无参)并返回相应的结果 值为null,调用时会返回”null 值为undefined,调用时会返回undefined Object类型ECMAScript中的对象就是一组数据和功能的集合。对象可以通过执行new操作符后跟要创建的对象类型的名称来创建。从而创建Object类型的实例并为其添加属性和方法。 1var o = new Object(); // 不可以带参数 Object类型是所有它的实例的基础,有点类似Java中的Object对象。所以Object类型所具有的属性和方法同样存在于更具体的对象中。Object的每个实例都具有以下的属性和方法: Constructor:保存着用于创建当前对象的函数,即构造函数。如上面的构造函数就是Object() hasOwnProperty(propertyName):检查给定的属性在当前对象实例中是否存在,传入的是字符串形式的属性名 isPrototypeOf(object):检查传入的对象是否是另一个对象的原型 propertyIsEnumerable(protertyName):检查给定的属性是否能够用for-in语句来枚举 toLocaleString():返回对象的字符串表示,与执行环境的地区对应 toString():返回对象的字符串表示 valueOf():返回对象的字符串、数值或布尔值表示。通常于toString()方法的返回值相同 操作符一元操作符ECMAScript中最简单的操作符,只能操作一个值 递增和递减操作符分为前置型和后置型。前置型即在变量之前,如++i,后置型在变量之后,如--i.两者的区别在于操作符作用的时机,后置型在语句被求值之后才会执行。而前置型在求值之前就会执行。可以看下面的操作: 1234567var i = 20;var j = 2;var num = i-- + j; // 结果为22var m = 20;var n = 2;var result = --m + n; // 结果为21 递增和递减操作符不仅作用于整数,还可以用于字符串、布尔值、浮点数以及对象。规则如下: 包含有效数字字符的字符串时先将字符串转换为数字再执行加减1操作,字符串变量变成数值变量 不包含有效数字字符的字符串时将变量的值设置为NaN,字符串变量变成数值变量 布尔值false,先转换为0再加减1,布尔值变量变成数值变量 布尔值true,先转换为1再加减1,布尔值变量变成数值变量 浮点数,加减1 对象,先调用对象的valueOf()方法以取得一个可供操作的值。然后对该值应用前面的规则。如果结果是NaN,则再调用toString()方法后再应用前面的规则。对象变更变成数值变量 一元加和减操作符即是我们通常所说的正负号。 +不会对数值产生任何影响,对于非数值会像Number()转型函数一样对这个值执行转换。 -会将正数数值转换为负数,同样的对于非数据操作方法和+相同,最后将得到的数值转换为负数。 位操作符此操作符会按内存中表示数值的位来操作数值。 无论是正数还是负数在内存中都是以二进制码进行存储。负数使用的格式是二进制补码。计算补码需要经过以下3个步骤: 求这个数值绝对值的二进制码 对结果进行反码。0变1,1变0 得到的反码加1即求得了补码 虽然负数是以补码的形式存储的,不过在ECMAScript中显示的时候还是以正数的二进制码加上-展示的。如-5显示为-101 对特殊的NaN和Infinity值应用位操作时这两个值都会被当成0来处理 如果对非数值应用位操作符,会先用Number()将该值转换为一个数值,然后再进行位操作,结果为数值 按位非(NOT)此操作符用波浪线~表示,其结果为数值的反码。 按位与(AND)此操作符用和号&表示,需要两个操作数。运算时将两个数值的每一位对齐,然后对相同位置上面的两个数执行AND操作。两个数都是1时返回1,其它情况返回0 按位或(OR)此操作符用竖线|表示,需要两个操作数。运算时将两个数值的每一位对齐,然后对相同位置上面的两个数执行OR操作。两个数都是0时返回0,其它情况返回1 按位异或用^来表示,同位置上的两个数只有一个1时才返回1,两位都是1或0时返回0 左移用两个小于号<<表示。会将数值的所有位向左移动指定的位数,左移之后的空位用0来填充。左移不影响数值的正负 有符号右移用两个大于号>>表示。会将数值向右移动但保留正负号,右移之后的空位用符号位的值来填充 无符号右移用3个大于号>>>表示。无符号右移会以0来填充空位,所以正数与有符号右移相同,但是负数由于填充了0直接导致其变成了正数,结果相差会非常大 布尔操作符一共有3个:非(NOT)、与(AND)和或(OR) 逻辑非使用一个!表示。适用于所有的数据类型,首先会将它的操作数转换为一个布尔值,然后再求反。规则如下: 对象,返回false 空字符串,返回true 非空字符串,返回false 数值0,返回true 任意非0数值(包含Infinity),返回false null,返回true NaN,返回true undefined,返回true 逻辑与使用&&表示,必须两个都为true时才会返回true。此操作符属于短路操作,即如果第一个操作数能决定结果那么不会再对第二个操作数求值 在有一个操作数不是布尔值的情况下,此操作不一定返回布尔值: 如果第一个操作数是对象,则返回第二个操作数 如果第二个操作数是对象,则只有在第一个操作数的求值结果为true的情况下才会返回该对象 如果两个操作数都是对象,返回第二个操作数 如果有一个是null,返回null 如果有一个是NaN,返回NaN 如果有一个是undefined,返回undefined 逻辑或使用||表示,只要有一个为true结果为true。同样属于短路操作符 在有一个操作数不是布尔值的情况下,此操作不一定返回布尔值: 如果第一个操作数是对象,则返回第一个操作数 如果第一个操作数求值结果是false,则返回第二个操作数 如果两个操作数都是对象,返回第一个操作数 如果有一个是null,返回null 如果有一个是NaN,返回NaN 如果有一个是undefined,返回undefined 乘性操作符ECMAScript定义了3个乘性操作符:乘法、除法和求模 乘法*操作符。特殊规则: 超出ECMAScript的表示范围返回Infinity或-Infinity 有一个操作数是NaN则结果是NaN Infinity*非0结果是Infinity或-Infinity Infinity*0结果是NaN Infinity*Infinity结果是Infinity 有一个操作数不是数值,则调用Number()转换为数值再应用上面的规则 除法/操作符。特殊规则: 超出ECMAScript的表示范围返回Infinity或-Infinity 有一个操作数是NaN则结果是NaN Infinity/Infinity结果是NaN 0/0结果是NaN 非0/0结果是Infinity或-Infinity Infinity/非0结果是Infinity或-Infinity Infinity*0结果是NaN 有一个操作数不是数值,则调用Number()转换为数值再应用上面的规则 求模%操作符。特殊规则: 都是数值,执行除法求得余数 Infinity%非0结果是NaN 非0%0结果是NaN Infinity%Infinity结果是NaN 非0%Infinity结果是非0数 0%任何数 结果是0 有一个操作数不是数值,则调用Number()转换为数值再应用上面的规则 加性操作符加法+特殊情况看原书 减法-特殊情况看原书 关系操作符小于<,大于>,小于等于<=和大于等于>=,用于两个值进行比较。特殊情况看原书 在比较字符串时比较字符串中对应位置的每个字符的字符编码值。 任何操作数与NaN进行比较时结果都是false 相等操作符ECMAScript提供了两组操作符。 相等和不相等–先转换再比较 相等使用==,不相等使用!=。比较时需要先转换类型 全等和不全等–仅比较而不转换 全等使用===,不全等使用!==。比较时不转换类型,直接比较值,所以返回true的前提条件是类型要相等。 条件操作符即常见的三目运算符。 赋值运算符即=号。 逗号运算符一般用于一次声明多个变量。还可以用于赋值,此进会返回表达式的最后一项,如: 1var num = (5,1,3,4,0); // num值为0 语句if没啥说的 do-while注意至少执行一次 for没啥说的 for-in遍历可迭代对象,如json对象 1234var myJson = {'name': 'tom', 'age': 18};for(var k in myJson) { console.log(myJson[k]); // k为json对象的key} label在代码中添加标签,以便将来使用。一般与for语句等循环配合使用 label:statement; 123start:for(var i=0; i<count; i++){ alert(i);} break,continue可以使用这两个关键词与label语名联用。 1234567891011haha:for(var i = 0;i<10;i++){ for(var j=0;j<10;j++){ if (i==5&&j==5) { break haha; } num++; }}alert(num); // 55,如果不使用label标签则结果为95 with将代码的作用域设置到一个特定的环境中。 with(expression) statement; 123456789var qs = location.search.substring(1);var hostName = location.hostname;var url = location.href;with(location){ var qs = search.substring(1); var hostName = hostname; var url = href;} switch支持任何数据类型。每个case的值不一定是常量,可以是变量甚至是表达式。比较时使用的时全等操作符,因此不会发生类型转换。 函数ECMAScript中使用function关键字来声明函数。基本语法为: 123function functionName(arg0, arg1, arg2, ..., argn){ statements} 返回值通过return关键字来进行返回函数的执行结果,如果没有结果可以直接用return;结束函数,所以在return之后的语句不会再执行。 参数 ECMAScript函数不介意传递进来多少个参数,即使定义函数时未定义参数也可以传入多个参数。同理,如果定义了两个参数,也可以传入小于或大于两个参数。究其原因是参数的内部是用数组来表示的,函数接收的是这相数组。有如下特点: 在函数体内可通过arguments来访问参数数组arguments[0]、arguments[1]分别表示第1和第2个参数。 可以使用arguments.length属性来确定实际传入的参数个数。 如第一个参数名为name,则其值和arguments[0]的值相等,修改arguments[0]的值name的值也会被修改掉。但这并不表示两者共用了相同的内存空间,内存空间依然独立,只是值会同步。 若定义3个参数,只传入2个。则第3个参数将自动被赋值为undefined 没有重载ECMAScript函数没有签名,如果定义了两个同名的函数,此函数只属于后定义的。 1234567function add(num){ return num + 100;}function add(num){ return num + 200;}var result = add(100); // 300","categories":[{"name":"前端","slug":"前端","permalink":"http://blog.xxyxpy.pub/categories/前端/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://blog.xxyxpy.pub/tags/JavaScript/"},{"name":"JavaScript高级程序设计","slug":"JavaScript高级程序设计","permalink":"http://blog.xxyxpy.pub/tags/JavaScript高级程序设计/"}]},{"title":"nginx环境搭建","slug":"linux/nginx环境搭建","date":"2017-10-12T06:07:38.000Z","updated":"2018-01-04T06:15:15.517Z","comments":true,"path":"2017/10/12/linux/nginx环境搭建/","link":"","permalink":"http://blog.xxyxpy.pub/2017/10/12/linux/nginx环境搭建/","excerpt":"安装环境自动安装 gcc 安装nginx需要先将官网下载的源码进行编译,编译依赖gcc环境,如果没有gcc环境,需要安装gcc 1yum install gcc-c++ PCRE PCRE(PerlCompatible Regular Expressions)是一个Perl库,包括 perl 兼容的正则表达式库。nginx的http模块使用pcre来解析正则表达式,所以需要在linux上安装pcre库。 注:pcre-devel是使用pcre开发的一个二次开发库。nginx也需要此库。","text":"安装环境自动安装 gcc 安装nginx需要先将官网下载的源码进行编译,编译依赖gcc环境,如果没有gcc环境,需要安装gcc 1yum install gcc-c++ PCRE PCRE(PerlCompatible Regular Expressions)是一个Perl库,包括 perl 兼容的正则表达式库。nginx的http模块使用pcre来解析正则表达式,所以需要在linux上安装pcre库。 注:pcre-devel是使用pcre开发的一个二次开发库。nginx也需要此库。 1yum install -y pcre pcre-devel zlib zlib库提供了很多种压缩和解压缩的方式,nginx使用zlib对http包的内容进行gzip,所以需要在linux上安装zlib库。 1yum install -y zlib zlib-devel openssl OpenSSL是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。nginx不仅支持http协议,还支持https(即在ssl协议上传输http),所以需要在linux安装openssl库。 1yum install -y openssl openssl-devel 编译将nginx-1.8.0.tar.gz拷贝至linux服务器并解压: 12tar -zxf nginx-1.8.0.tar.gzcd nginx-1.8.0 配置 configure \\表示命令未结束 prefix 表示要安装的目录 将临时文件目录指定为/var/temp/nginx,需要在/var下创建temp及nginx目录 123456789101112./configure \\--prefix=/usr/local/application/nginx \\--pid-path=/var/run/nginx/nginx.pid \\--lock-path=/var/lock/nginx.lock \\--error-log-path=/var/log/nginx/error.log \\--http-log-path=/var/log/nginx/access.log \\--with-http_gzip_static_module \\--http-client-body-temp-path=/var/temp/nginx/client \\--http-proxy-temp-path=/var/temp/nginx/proxy \\--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \\--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \\--http-scgi-temp-path=/var/temp/nginx/scgi 执行完成之后看到生成了configure文件 编译安装 12345makemake install# 查看安装目录cd /usr/local/application/nginxll 启动 运行运行前需要先在var下创建好相应的临时目录 1234mkdir /var/temp/nginx/client -p./sbin/nginx# 查看启动的进程 master是主进程 另外一个是工作进程ps aux |grep nginx 启动成功之后默认监听80端口可以用ip直接查看 停止 1234# 此方式相当于先查出nginx进程id再使用kill命令强制杀掉进程。./nginx -s stop# 此方式停止步骤是待nginx进程处理任务完毕进行停止。./nginx -s quit 重启 12345678# 对nginx进行重启相当于先停止nginx再启动nginx,即先执行停止命令再执行启动命令。./nginx -s quit./nginx# 重新加载配置文件:当nginx的配置文件nginx.conf修改后,要想让配置生效需要重启nginx,# 使用-s reload不用先停止nginx再启动nginx即可将配置信息在nginx中生效./nginx -s reload# 检测配置./nginx -t 通过端口区分虚拟机修改conf目录下面的nginx.conf.一个server就是一个虚拟主机,添加一个server修改端口号和对应的root目录。 修改完成之后reload配置,此时可以使用ip和端口号进行访问 123456789101112131415161718192021222324252627282930313233343536373839#user nobody;worker_processes 1;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } server { listen 81; server_name localhost; location / { root html81; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html81; } }} 通过域名区分虚拟主机需要修改hosts文件来实现域名的绑定,使用hosts文件修改工具。 链接: https://pan.baidu.com/s/1mibwvpe 密码: vmva a 修改hosts文件,添加如下内容 12192.168.80.128 www.test.com192.168.80.128 www.abc.com b 修改nginx.conf,添加两个server.修改其server_name为hosts中添加的域名 首先监听的是80端口,另外需要修改root目录 123456789101112131415161718192021222324252627server { listen 80; server_name www.test.com; #charset koi8-r; #access_log logs/host.access.log main; location / { root html_test; index index.html index.htm; }}server { listen 80; server_name www.abc.com; #charset koi8-r; #access_log logs/host.access.log main; location / { root html_abc; index index.html index.htm; }} c 添加相应的html目录并reload一下配置 此时再访问www.abc.com 和 www.test.com 就定向到了nginx的两个虚拟主机 实现反向代理正向代理指的是代理客户端的请求。而反向代理指的是代理服务器端,由于一个外网ip只能绑定到一台服务器上,通过反射代理机制就可以实现对多个服务进行访问,nginx实现对这多个服务的代理。 此种方式和上面的域名区分虚拟主机类似,不同的是提供的服务不再是静态html文件,可以在一个可用的tomcat服务。 a 配置hosts文件添加如下的域名映射 12192.168.80.128 www.baiduhaha.com192.168.80.128 www.taobaohaha.com b 修改nginx.conf,添加两个server 注意location中不再是root节点 123456789101112131415161718192021222324252627282930313233 upstream tomcat1 {server 192.168.80.128:7080; } server { listen 80; server_name www.baiduhaha.com; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://tomcat1; index index.html index.htm; } } upstream tomcat2 {server 192.168.80.128:7081; } server { listen 80; server_name www.taobaohaha.com; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://tomcat2; index index.html index.htm; } } c 在服务器上面启动两个tomcat服务,端口号分别为7080和7081 reload一下nginx的服务 访问www.baiduhaha.com即显示7080tomcat主页 访问www.taobaohaha.com即显示7081tomcat主页 实现负载均衡假设我们的www.taobaohaha.com现在需要由多台机器提供服务,那就需要在upstream中配置多个server。默认不设置weight时是平均分配,设置了之后会按设置的权限对请求进行分流。模拟此效果需要再复制生成个tomcat服务 123456789101112131415161718 upstream tomcat2 {server 192.168.80.128:7081 weight=3;server 192.168.80.128:7082 weight=1; } server { listen 80; server_name www.taobaohaha.com; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://tomcat2; index index.html index.htm; } }","categories":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/categories/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://blog.xxyxpy.pub/tags/linux/"},{"name":"nginx","slug":"nginx","permalink":"http://blog.xxyxpy.pub/tags/nginx/"}]},{"title":"java_other","slug":"java/tool/java_other","date":"2017-07-08T01:34:28.000Z","updated":"2018-01-05T01:22:52.224Z","comments":true,"path":"2017/07/08/java/tool/java_other/","link":"","permalink":"http://blog.xxyxpy.pub/2017/07/08/java/tool/java_other/","excerpt":"泛型中的T和?参考1 参考2 T 表示具体的java类型,用于泛型的定义和使用 ?表示不确定的java类型,一般只用于泛型的使用中。如:List<?> myList = new ArrayList<String>(); 更详细的区别可以看下例:","text":"泛型中的T和?参考1 参考2 T 表示具体的java类型,用于泛型的定义和使用 ?表示不确定的java类型,一般只用于泛型的使用中。如:List<?> myList = new ArrayList<String>(); 更详细的区别可以看下例: 12345678910111213141516171819202122232425public class Generic { public void test1(List<?> list) { // 返回Object类型 Object o = list.get(0); // 无法add,因为无法确定?是什么类型, // 而此时添加的是Object,会违返类型约束 //list.add(o); } public <T> void test2(List<T> list) { // 返回已知的T类型 T t = list.get(0); // 添加成功 list.add(t); } public static void main(String[] args) { List<String> myList = new ArrayList<>(); myList.add(\"abc\"); for (String s : myList) { System.out.println(s); } }} Arrays.asList生成的List为什么不可以插入先看一下效果 12345678Integer[] ints = {1,2,3,4,5};List<Integer> asList = Arrays.asList(ints);asList.add(7);// 执行后异常Exception in thread \"main\" java.lang.UnsupportedOperationException at java.util.AbstractList.add(AbstractList.java:148) at java.util.AbstractList.add(AbstractList.java:108) at com.test.Generic.main(Generic.java:29) 分析下源码发现Arrays中的ArrayList是它自己的内部类继承自AbstractList<E>,并且只实现了部分方法,add方法并没有实现。所以使用时调用的时抽象父类的add方法该方法的定义直接拋出了异常 123public void add(int index, E element) { throw new UnsupportedOperationException();} 那如果真的有添加的需求要如何操作?需要自己定义一个非内部类的ArrayList,加入转换后的集合再进行添加操作 123456Integer[] ints = {1,2,3,4,5};List<Integer> asList = Arrays.asList(ints);//asList.add(7);List<Integer> list = new ArrayList<>();list.addAll(asList);list.add(7); Thread和Runnable的关系 Thread为线程对象,在实例化时可以传入一个runnable接口的实现 两者应该算是父子关系,Thread实例的start方法调用的就是runnable接口中的run 线程池 传统的创建线程存在的问题 当需要线程的时候就需要进行初始化操作,性能差 可以无限制的创建线程,当存在循环创建时很容易出现线程爆增。线程越多占用内存越大,线程之间资源竟然越频繁,并且系统需要在线程之间进行上下文切换,最终可以死机或内存溢出 123456Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println(\"haha...\"); }}); java提供的线程池功能 Java通过Executors类提供四种线程池。 newCachedThreadPool创建一个可缓存的线程池,通常用于处理时间较短的异步任务。如果60s内不使用则会被从缓存中清除掉。当无可用线程时会自动创建新线程 12345678910111213141516171819// 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程@Testpublic void cachedThreadPool() { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int index = i; try { Thread.sleep(index * 1000); } catch (InterruptedException e) { e.printStackTrace(); } cachedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(index); } }); }} newFixedThreadPool创建一个定长线程池,使用时如果无可用的线程则其它 的任务进入队列进行等待 123456789101112131415161718192021222324252627282930/** * 高优先级线程 */class MaxPriorityThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setPriority(Thread.MAX_PRIORITY); return t; }}public static void fixedThreadPool() { // 获取处理器核数 int j = Runtime.getRuntime().availableProcessors(); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(j, new MaxPriorityThreadFactory()); for (int i = 0; i < 100; i++) { final int index = i; fixedThreadPool.execute(new Runnable() { @Override public void run() { try { System.out.println(index); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }} newScheduledThreadPool创建一个定长线程池,支持定时及周期性的任务 1234567891011121314151617181920212223242526272829303132333435// 定时public static void scheduledThreadPool1() { ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4); // 延迟3s执行 scheduledThreadPool.schedule(new Runnable() { @Override public void run() { System.out.println(\"延迟3s执行\"); } }, 3, TimeUnit.SECONDS);}// 延迟1s后每3s执行一次,前一个任务开始执行就开始读秒public static void scheduledThreadPool2() { ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4); // 延迟3s执行 scheduledThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(\"延迟1s执行,然后每3s执行一次\"); } }, 1, 3, TimeUnit.SECONDS);}// 延迟1s后每3s执行一次,前一个任务执行完成后开始读秒public static void scheduledThreadPool3() { ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4); // 延迟3s执行 scheduledThreadPool.scheduleWithFixedDelay(new Runnable() { @Override public void run() { System.out.println(\"延迟1s执行,然后每3s执行一次\"); } }, 1, 3, TimeUnit.SECONDS);} newSingleThreadScheduledExecutor创建一个单线程化的线程池 123456789101112131415161718// 由于是单线程的线程池,任务会顺序执行public static void singleThreadPool() { ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int index = i; singleThreadExecutor.execute(new Runnable() { @Override public void run() { try { System.out.println(index); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }} ","categories":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/categories/java/"},{"name":"tool","slug":"java/tool","permalink":"http://blog.xxyxpy.pub/categories/java/tool/"}],"tags":[{"name":"java","slug":"java","permalink":"http://blog.xxyxpy.pub/tags/java/"}]},{"title":"mongodb入门","slug":"nosql/mongodb入门","date":"2017-06-20T07:01:16.000Z","updated":"2018-01-23T01:59:43.041Z","comments":true,"path":"2017/06/20/nosql/mongodb入门/","link":"","permalink":"http://blog.xxyxpy.pub/2017/06/20/nosql/mongodb入门/","excerpt":"安装安装64位的环境,因为32位的最大只支持2G存储。可以到官网下载,即将安装的环境为Centos 7.3 123456789101112# 上传到服务器解压文件到指定位置tar -zxf mongodb-linux-x86_64-rhel70-3.4.9.tgzmv mongodb-linux-x86_64-rhel70-3.4.9 /usr/local/mongodb# 添加参数到环境变量vim /etc/profile# 添加的内容export MONGODB_HOME=/usr/local/mongodbexport PATH=$MONGODB_HOME/bin:$PATH# 添加完成之后重新加载环境变量source /etc/profile 启动","text":"安装安装64位的环境,因为32位的最大只支持2G存储。可以到官网下载,即将安装的环境为Centos 7.3 123456789101112# 上传到服务器解压文件到指定位置tar -zxf mongodb-linux-x86_64-rhel70-3.4.9.tgzmv mongodb-linux-x86_64-rhel70-3.4.9 /usr/local/mongodb# 添加参数到环境变量vim /etc/profile# 添加的内容export MONGODB_HOME=/usr/local/mongodbexport PATH=$MONGODB_HOME/bin:$PATH# 添加完成之后重新加载环境变量source /etc/profile 启动 123456789101112131415161718192021222324# 创建数据库目录mkdir -p /data/mongodbmkdir -p /data/mongodb/logtouch /data/mongodb/log/mongodb.log# 添加配置文件vim /etc/mongodb.conf# 配置文件内容dbpath=/data/mongodblogpath=/data/mongodb/log/mongodb.loglogappend=trueport=27017fork=true#auth = true # 先关闭, 创建好用户在启动# 通过配置文件启动mongod -f /etc/mongodb.conf# 连接mongodb服务,通过mongo命令,由于已经添加到环境变量可以直接使用mongo# 从Mongodb的admin中关闭系统use admindb.shutdownServer() 配置文件参数说明 mongodb的参数说明:–dbpath 数据库路径(数据文件)–logpath 日志文件路径–master 指定为主机器–slave 指定为从机器–source 指定主机器的IP地址–pologSize 指定日志文件大小不超过64M.因为resync是非常操作量大且耗时,最好通过设置一个足够大的oplogSize来避免resync(默认的 oplog大小是空闲磁盘大小的5%)。–logappend 日志文件末尾添加–port 启用端口号–fork 在后台运行–only 指定只复制哪一个数据库–slavedelay 指从复制检测的时间间隔–auth 是否需要验证权限登录(用户名和密码) 注:mongodb配置文件里面的参数很多,定制特定的需求,请参考官方文档 通过Robo连接因为mongodb是基于json进行操作的,所以在命令行中操作时非常的麻烦。使用可视化工具Robo去访问和操作更方便,提供了linux和windows两种版本。下载地址如下: 链接: https://pan.baidu.com/s/1c1ZCkBu 密码: 53n5 在连接之前需要先开放默认的27017端口 123456# 可以关闭防火墙,或者单独添加27017端口chkconfig iptables off/sbin/iptables -I INPUT -p tcp --dport 27017 -j ACCEPT/etc/rc.d/init.d/iptables save/etc/rc.d/init.d/iptables restart 然后安装robo后直接connect即可。 数据类型 Object ID:文档ID。每个文档都有一个_id,如果不设置的话会默认生成一个12个字节的16进制数。前4个字节为当前的时间戳,接下来3个字节为机器id,接下来2个字节为MongoDb的服务进程id,最后三个字节是增量值 String:字符串,最常用,必须是有效的UTF-8 Boolean:存储一个布尔值,true或false Integer:整数可以是32位或64位,这取决于服务器 Double:存储浮点值 Arrays:数组或列表,多个值存储到一个键 Object:用于嵌入式的文档,即一个值为一个文档 Null:存储Null值 Timestamp:时间戳 Date:存储当前日期或时间的UNIX时间格式 数据库操作默认连接的是test数据库,如果不创建数据库直接进行操作则操作的数据会存放在test数据库中 12345678# 查看当前的数据库db# 显示所连接服务上面的所有数据库show dbs# 切换数据库,如果不存在仅指向数据库,不执行创建,只有当插入数据或创建集合时才创建库use mydb# 删除数据库,执行此操作时删除当前连接的数据库db.dropDatebase() 集合操作 显示所有集合 show collections 创建集合 db.createCollection(name, options) db.createCollection(“stu”) 可选参数options,capped 表示设置上限,size 表示最大字节的大小。max表示最大的文档数量。当capped为true时size必须指定 db.createCollection(“sub”, { capped : true, size : 10 } ) 删除集合 db.集合名.drop() 数据操作 插入 123456# 不指定_id参数,mongodb自动分配db.stu.insert({name:'gj',gener:1})# 指定_id参数s1={_id:'20160101',name:'hr'}s1.gender=0db.stu.insert(s1) 查询全部 1db.stu.find() 更新 语法 db.集合.update({queryjson},{updatejson},{multi:true or false}) queryjson指的是查询条件,类似于update中的where。无条件时也要写{} updatejson指的是更新的数据 multi是可选项,默认值是false表示只更新查找到的第一条记录。值为true时表示更新所有 123456# 更新所有的文档,可能会造成其它属性的丢失db.stu.update({name:'hr'},{name:'mnc'})# 指定属性更新,使用$set操作符db.stu.update({name:'gj'},{$set:{name:'gj1'}})# 修改多条匹配的数据,如果没有gender属性会自动添加db.stu.update({},{$set:{gender:0}},{multi:true}) 保存 语法 db.集合.save({savejson}) 如果文档的_id存在则修改,如果不存在则插入 1234# 更新db.stu.save({_id:'20160101',name:'haha',gender:1})# 添加db.stu.save({name:'jack',gender:1}) 删除 语法 db.集合.remove({queryjson},{justOne:true or false}) justOne 表示是否只删除一行,默认是false删除多行。 1234# 只删除一条db.stu.remove({gender:0},{justOne:true})# 全部删除db.stu.remove({}) 有size限制的集合插入 当插入的数据超过限制的max大小时,后面插入的数据会覆盖前面插入的 1234567db.createCollection('sub',{capped:true,size:3,max:3})db.sub.insert({name:'zs',age:18})db.sub.insert({name:'ls',age:19})db.sub.insert({name:'ww',age:20})# 覆盖第一条数据db.sub.insert({name:'zl',age:21})db.sub.find() 数据查询 基本查询 语法 db.集合.find({queryjson}) 123456789# 查询全部db.sub.find()# 带条件查询db.sub.find({age:20})# 只返回符合条件的第一个findOne()db.sub.update({age:19},{$set:{age:20}})db.sub.finOne({age:20})# 格式化查询结果pretty(),只在命令行窗口时有效果db.sub.find({age:20}).pretty() 比较运算符 等于,默认是等于判断,没有运算符 小于$lt 小于等于$lte 大于$gt 大于等于$gte 不等于$ne 12# 查询年龄大于20的记录db.sub.find({age:{$gt:20}}) 逻辑运算符 逻辑运算符指并且或者之类的关系,查询时可以有多个条件 多个条件之前使用逻辑运算符连接 逻辑与:默认查询时的json就是逻辑与的关系,用逗号隔开 逻辑或:使用$or 123456# 多个逻辑与条件:查询年龄大于等于20并且name为ww的记录db.sub.find({age:{$gte:20},name:'ww'})# 使用逻辑或$or:查询name为zl或者年龄等于20的记录db.sub.find({$or:[{name:'zl'},{age:20}]})# 逻辑与和或一起使用:查询name为zl或者年龄大于等于20 并且name为ls的记录db.sub.find({$or:[{name:'zl'},{age:{$gte:20}}],name:'ls'}) 范围运算符 使用$in和$nin判断是否在某个范围内 1234# 查询年龄为10或20的记录db.sub.find({age:{$in:[10,20]}})# 查询年龄不为10或20的记录db.sub.find({age:{$nin:[10,20]}}) 正则表达式查询 使用//或$regex编写正则表达式 123# 查询name以l开头的记录db.sub.find({name:/^l/})db.sub.find({name:{$regex:'^l'}}) 自定义函数查询 使用js中的function来进行查询返回,用$where 1234# 查询年龄大于20的记录db.sub.find({$where:function(){ return this.age>20}}) 使用limit和skip分页 limit用于读取指定数量的文档 db.集合.find({queryjson}).limit(number) skip用于跳过指定数量的文档 db.集合.find({queryjson}).skip(number) 两者搭配使用可以实现分页查询的功能 1234567891011121314151617# 向stu中添加测试数据db.stu.insert({name:'zhangsan',gender:1,age:23}db.stu.insert({name:'lisi',gender:1,age:33}db.stu.insert({name:'wangwu',gender:0,age:13}db.stu.insert({name:'zhaoliu',gender:0,age:40}db.stu.insert({name:'wangerma',gender:1,age:30}# 查询gender为0,只取前两条db.stu.find({gender:0}).limit(2)# 查询age大于20记录,跳过前两条db.stu.find({age:{$gt:20}}).skip(2)# 生成分页测试数据for(i=0;i<15;i++){ db.t1.insert({name:'name' + i, age:i})}# 每页3条,查询第3页数据db.t1.find().skip(6).limit(3) 投影查询 指定需要输出的文档字段,防止输出不必要的字段造成性能的下降 db.集合.find({queryjson},{col1:1,col2:0,col3:1…}) 对于需要显示的字段,设置值为1。不设置不显示 对于_id默认显示,如果不显示需要显示的设置为0 1234# 只展示name和_iddb.stu.find({},{name:1})# 只展示namedb.stu.find({},{name:1,_id:0}) 排序 使用sort()对结果集进行排序 db.集合.find({queryjson}).sort({col1:1,col2:-1,…}) 参数1为升序,-1为降序 多个条件时逗号分隔 12# 查询age大于0的数据,按性别降序再按age升序db.stu.find({age:{$gt:0}}).sort({gender:-1,age:1}) 统计个数 使用count()进行文档数的统计,提供两种语法 db.集合.find({queryjson}).count() db.集合.cont({queryjson}) 123# 统计age大于0的文档数db.stu.find({age:{$gt:0}}).count()db.stu.count({age:{$gt:0}}) 消除重复 方法distinct()对数据进行去重 db.集合.distinct(‘column’,{conditionjson}) 12# 查找age大于18的性别,去重db.stu.distinct('gender',{age:{$gt:18}}) 索引 生成测试数据100W条 12use test3for(i=0;i<1000000;i++){db.t1.insert({name:'name'+i})} 查看查询的执行计划 12345db.t1.find({name:'name500000'}).explain('executionStats')# 显示内容中主要关注时间...\"executionTimeMillis\" : 303,... 创建索引 123456# 1指为按升序创建索引,-1表示降序db.t1.ensureIndex({\"name\":1})# 也支持同时为多个字段创建索引,即关系型数据库中的复合索引。如db.collectionName.ensureIndex({'col1':1,'col2':-1})# 在后台创建索引db.collectionName.ensureIndex({'col1':1,'col2':-1},{background:true}) 索引创建完成之后再次查看执行计划 12345db.t1.find({name:'name500000'}).explain('executionStats')# 显示内容中主要关注时间,变化为86ms,说明索引的效果还是比较明显的...\"executionTimeMillis\" : 86,... 聚合 聚合主要用于计算数据,类似sql中的sum()、avg() 语法: db.集合.aggregate([{管道:{表达式}},{管道:{表达式}},…]) 常用管道 文档处理完毕后,通过管道进行下一次处理 $group:将集合中的文档分组,可用于统计结果 $match:过滤数据,只输出符合条件的文档 $project:修改输入文档的结构,如重命名、增加、删除字段、创建计算结果 $sort:将输入文档排序后输出 $limit:限制聚合管道返回的文档数 $skip:跳过指定数量的文档,并返回余下的文档 $unwind:将数组类型的字段进行拆分 表达式 处理输入文档并输出 语法:’$列名’ 常用表达式: $sum:计算总和, $sum:1同count表示计数 $avg:计算平均值 $min:获取最小值 $max:获取最大值 $push:在结果文档中插入值到一个数组中 $first:根据资源文档的排序获取第一个文档数据 $last:根据资源文档的排序获取最后一个文档数据 $group 将集合中的文档分组,可用于统计结果 _id表示分组的依据,使用某个字段时格式为:$字段 12345678910111213141516171819202122232425262728293031323334353637383940# 统计gender的总人数 使用$sum表达式db.stu.aggregate([ { $group:{ _id:'$gender', counter:{$sum:1} } }])# 将集合中所有的文档分成一组_id:null。统计学生总人数,平均年龄# 使用$avg表达式db.stu.aggregate([ { $group:{ _id:null, counter:{$sum:1}, avgAge:{$avg:'$age'} } }])# 透视数据。统计学生的性别及对应的学生姓名# 使用$push表达式,将结果推到数组中db.stu.aggregate([ { $group:{ _id:'$gender', name:{$push:'$name'} } }])# 使用$$ROOT可以将文档内容整个加入到结果集的数组中db.stu.aggregate([ { $group:{ _id:'$gender', name:{$push:'$$ROOT'} } }]) $match 用于过滤数据,只输出符合条件的文档 使用标准的查询操作 123456789101112131415161718192021222324# 查询年龄大于20的学生db.stu.aggregate([ { $match:{ age:{$gt:20} } }])# 统计年龄大于20的男女生人数db.stu.aggregate([ { $match:{ age:{$gt:20} } }, { $group:{ _id:'$gender', counter:{ $sum:1 } } }]) $project 修改输入文档的结构,如重命名、增加、删除字段、创建计算结果 类似于投影查询的实现 12345678910111213141516171819# 查询学生的姓名及年龄db.stu.aggregate([ { $project:{ _id:0, name:1, age:1 } }])# 统计男女生人数,返回指定的字段db.stu.aggregate([ { $group:{_id:'$gender',counter:{$sum:1}} }, { $project:{_id:0,counter:1} }]) $sort 将输入文档排序后输出 123456789# 查询学生信息,按年龄升序db.stu.aggregate([ {$sort:{$age:1}}])# 统计男女生人数,按总人数降序db.stu.aggregate([ {$group:{_id:'$gender',counter:{$sum:1}}}, {$sort:{counter:-1}}]) $limit 、\\$skip $limit用于限制管道返回的文档数 $skip用于跳过指定数量的文档,并返回余下的文档 1234567891011121314db.stu.aggregate([ {$limit:2}])db.stu.aggregate([ {$skip:2}])# 统计男生、女生人数,按人数升序,取第二条数据db.stu.aggregate([ {$group:{_id:'$gender',counter:{$sum:1}}}, {$sort:{counter:1}}, {$skip:1}, {$limit:1}]) $unwind 将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值 正常数据处理语法 db.集合.aggregate([{$unwind:’\\$字段名称’}]) 处理空数组、非数组、null、无此字段的情况 db.集合.aggregate([{$unwind:{ path:’$字段名称’, preserveNullAndEmptyArrays:true or false }}]) 12345678910111213141516171819202122232425# 正常数据db.t2.insert({_id:1,item:'t-shirt',size:['s','m','l']})db.t2.aggregate([ {$unwind:'$size'}])# 空数组,非数组,null,无数据的情况db.t3.insert([ {_id:1,item:'a',size:['s','m','l']}, {_id:2,item:'b',size:[]}, {_id:3,item:'c',size:['m']}, {_id:4,item:'d'}, {_id:5,item:'e',size:null}])# 使用此语法时数据会丢失db.t3.aggregate([ {$unwind:'$size'}])db.t3.aggregate([ {$unwind:{ path:'$size', preserveNullAndEmptyArrays:true }}]) 用户权限mongodb默认不开启用户认证,直接通过ip和端口号即可完成连接。如果需要开启权限管理需要修改配置文件mongodb.conf 采用用户-角色-数据库的安全管理方式。一个用户可以对应多个角色 角色说明:Read:允许用户读取指定数据库readWrite:允许用户读写指定数据库dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profileuserAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。root:只在admin数据库中可用。超级账号,超级权限 修改之前需要先创建一个超级管理员帐户,否则无法完成登录。如果遇到服务无法停止的情况下可以kill掉服务的进程 查询mongodb的服务的pid ps aux | grep mongo 找到pid杀掉,比如pid为100 kill 100 创建管理员帐户 1234567891011121314151617# 连接到mongomongo# 进入到admin库use admin# 添加用户db.createUser( { user: "admin", pwd: "mongodb:passok", roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] })# 查看用户show usersdb.system.users.find()# 停止服务db.shutdownServer() 开启权限认证 1234# 添加内容到配置文件echo "auth = true" >> /etc/mongodb.conf# 启动服务mongod -f /etc/mongodb.conf 登录验证 1234567891011mongod# 无权限查看所有的库show dbsuse admin# 登录db.auth('admin','mongodb:passok')# 登录成功show dbs# 也可以使用以下方式进行登录mongo -u 'admin' -p 'mongodb:passok' --authenticationDatabase 'admin' 创建修改用户 使用超级管理员登录之后可以创建普通用户,使用户只有特定的数据库的访问权限 123456789101112db.createUser({ user:'t1', pwd:'123', roles:[{role:'readWrite',db:'test1'}]})# 连接mongo -u t1 -p 123 --authenticationDatabase test1# 添加数据db.t1.insert({name:'ls'})# 修改用户,必须使用admin帐户操作use test1db.updateUser('t1',{pwd:'456'}) 主从复制为了保证服务的持续可用,可以设置多台服务器进行数据同步。在主服务器出现故障时可以自动切换到从节点,此时从节点自动变成为主节点。要搭建此套服务至少需要两台服务器,实现的机制是每隔一段时间自动拷贝所有的改动到从节点。 创建两个数据库目录t1、t2 12mkdir t1mkdir t2 启动两个mongod服务,replSet的名称相同 1234# 主mongod --bind_ip 192.168.80.129 --port 27018 --dbpath /root/t1 --replSet rs0# 从mongod --bind_ip 192.168.80.129 --port 27019 --dbpath /root/t2 --replSet rs0 操作主节点 12345678910111213mongo --host 192.168.80.129 --port 27018# 初始化rs.initiate() # rs是mongodb的内置对象# 查看当前状态,members中只有一个服务rs.status()# 添加从节点,可以添加多个rs.add('192.168.80.129:27019')# 查看当前状态,members中有两个服务。并且此时提示符变为:rs0:PRIMARYrs.status()# 向主节点中插入数据use test1for(i=0;i<10;i++){db.t1.insert({_id:i})}db.t1.find() 操作从节点 123456# 连接,提示符为:rs0:SECONDARYmongo --host 192.168.80.129 --port 27019# 设置slave,否则无法查询数据rs.slaveOk()use test1db.t1.find() 删除从节点 1rs.remove('192.168.80.129:27019') 主从切换 123# 关闭主节点重新启动,发现从节点自动变化为主节点# Member 192.168.80.129:27019 is now in state PRIMARY# 连接原来的主节点提示符为:rs0:SECONDARY> 备份恢复 备份 语法 mongodump -h dbhost -d dbname -o dbdirectory -h:服务器地址,也可以指定端口号 -d:需要备份的数据库名称 -o:备份的数据存放位置,此目录中存放着备份出来的数据 12mkdir test1bakmongodump -h 192.168.80.129:27019 -d test1 -o ~/test1bak 恢复 语法 mongorestore -h dbhost -d dbname –dir dbdirectory -h:服务器地址 -d:需要恢复的数据库实例 –dir:备份数据所在位置 1mongorestore -h 192.168.80.129:27018 -d test2 --dir ~/test1bak/test1 参考: CentOS下安装mongodb Mongodb用户权限管理 Mongodb索引","categories":[{"name":"nosql","slug":"nosql","permalink":"http://blog.xxyxpy.pub/categories/nosql/"}],"tags":[{"name":"mongodb","slug":"mongodb","permalink":"http://blog.xxyxpy.pub/tags/mongodb/"}]},{"title":"判断回文数","slug":"java/tool/判断回文数","date":"2017-06-08T12:03:05.000Z","updated":"2017-12-25T07:41:54.195Z","comments":true,"path":"2017/06/08/java/tool/判断回文数/","link":"","permalink":"http://blog.xxyxpy.pub/2017/06/08/java/tool/判断回文数/","excerpt":"判断回文数 什么是回文数 先上度娘链接:回文数 大概的意思就是左右对称,如:abcba、abba、12321、123321","text":"判断回文数 什么是回文数 先上度娘链接:回文数 大概的意思就是左右对称,如:abcba、abba、12321、123321 撸代码123456789101112131415161718192021public class Palindrome { public static boolean isPalindrome(String str){ int i = str.length(); int j = 0; while (j < i/2 && str.charAt(j) == str.charAt(i-j-1)){ System.out.println(str.charAt(j) + \"-\" + str.charAt(i-j-1)); j++; } return j == i/2; } @Test public void test(){ //String s = \"abcdabcd\"; //String s = \"abcba\"; //String s = \"abba\"; int i = 1221; String s = String.valueOf(i); System.out.println(Palindrome.isPalindrome(s)); }}","categories":[{"name":"数据结构和算法","slug":"数据结构和算法","permalink":"http://blog.xxyxpy.pub/categories/数据结构和算法/"}],"tags":[{"name":"回文数","slug":"回文数","permalink":"http://blog.xxyxpy.pub/tags/回文数/"}]},{"title":"hexo配置","slug":"hexo配置","date":"2017-04-12T06:07:38.000Z","updated":"2017-12-26T05:59:30.691Z","comments":true,"path":"2017/04/12/hexo配置/","link":"","permalink":"http://blog.xxyxpy.pub/2017/04/12/hexo配置/","excerpt":"Hexo发布并部署 使用hexo d -g命令 配置标签(tags)默认情况下标签而是没有的,需要手动去创建启用。","text":"Hexo发布并部署 使用hexo d -g命令 配置标签(tags)默认情况下标签而是没有的,需要手动去创建启用。 1.创建标签页在hexo的要目录下执行如下命令: $ hexo new page “tags” 2.编辑标签页 title: 标签type: “tags”date: 2017-04-12 13:58:20comments: false 其中comments: false表示禁止评论。 3.文章中添加标签 title: Hello Worldcategories:- myBlogtags:- hexo- nexT 最终效果 NexT主题设置主页不显示全文 进入hexo博客项目的themes/next目录 用文本编辑器打开_config.yml文件 搜索”auto_excerpt”,把enable由false改为true Icarus主题官方说明 使用自定义","categories":[],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://blog.xxyxpy.pub/tags/hexo/"}]}]}