-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
385 lines (385 loc) · 753 KB
/
search.xml
File metadata and controls
385 lines (385 loc) · 753 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[zookeeper]]></title>
<url>%2F2019%2F09%2F07%2Fzookeeper%2F</url>
<content type="text"><![CDATA[参考:https://juejin.im/post/5d0bd358e51d45105e0212db 安装目录结构bin —> 包括了linux和window的运行程序的运行目录conf —> zookeeper的配置zoo.cfgcontrib —> 其他一些组件和发行版本dist-maven —> maven发布下的一些jar包docs —> 文档lib —> 库recipe —> 一些应用实例src —> zookeeper的源码,因为zookeeper是java写出来的 zoo.cfg配置tickTime:用于计算的时间单元。比如session超时:N*tickTimeinitLimit:用于集群,允许送节点连接并同步到master节点的初始化连接时间,以tickTime的倍数来表示。syncLimit:用于集群,master主节点与从节点之间发送消息,请求和应答时间长度(心跳机制),以tickTime的倍数来表示。dataDir:必须配置,相关数据如事务文件的存储目录dataLogDir:日志目录,如果不指定和dataDir为同一个目录clientPort:链接服务器的端口,默认2181 ##zookeeper基本数据模型 基本数据模型可以理解为linux/unix的文件目录,比如/usr/local,/是根节点 每一个节点东都之为znode,可以有子节点,也可以有数据 每一个节点分为临时节点和永久节点,临时节点在客户端断开后消失 每一个zk节点都有各自的版本号,可以通过命令行显示节点信息 每当节点数据发生变化,那么节点的版本号会累加(乐观锁) 删除/修改果实节点,版本号不匹配则会报错 每一个zk节点存储的数据不宜过大,限制是1m 节点可以设置权限acl,可以通过权限来限制用户的访问 zookeeper数据模型的基本操作客户端连接 用官方给的cli连接,运行后显示12WatchedEvent state:SyncConnected type:None path:null[zk: localhost:2181(CONNECTED) 0] 表示连接到localhost的2181端口 命令help命令可以看所有命令的帮助 ###ls命令 相当于linux的ls或者ll命令,是查看某一个节点下的节点例如:ls /是查看根节点下的节点 stat 相当于status,查看是某一个节点的状态信息,比如cZxid是创建后zk为节点分配的id,ctime是节点创建的时间,mZxid表示修改后的id,mtime是修改后的时间,pZxid表示子节点id,cversion表示子节点的版本,dataVersion表示当前节点数据的版本号,aclVersion表示权限版本号,dataLength数据长度等信息,numChilden表示孩子结点的个数 例如:123456789101112[zk: localhost:2181(CONNECTED) 3] stat /zookeepercZxid = 0x0ctime = Thu Jan 01 08:00:00 CST 1970mZxid = 0x0mtime = Thu Jan 01 08:00:00 CST 1970pZxid = 0x0cversion = -2dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 0numChildren = 2 ls2命令 相当于ls+stat 也可以运行ls -s create命令 创建节点的命令 持久节点 直接通过create path value所创建 create /xwm表示创建/xwm节点,此时执行ls/会有zookeeper和xwm两个节点 临时节点 create -e path value create -e /xwm/temp表示创建临时节点/xwm/temp,此时执行ls2 /xwm会发现下面的temp节点并且/xwm节点的版本号会增加1当此session断开过了心跳机制的期限时间以后临时节点会被抛弃 顺序节点 create -s path value create -s /xwm/shun表示创建顺序节点,第一个节点id是1,第二个是2,执行两边后的结果[shun0000000001, shun0000000002, tmp] set命令 set path data [version]给path设置data,后面可以设置版本号如果后面的版本号不是最新的节点的版本号,则会报错,乐观锁的体现 get命令 get [-s] [-w] path获取某个节点的数据 delete命令 delete [-v version] path删除节点操作,version是版本号,乐观锁机制 getAcl getAcl [-s] path获取某个节点的权限 setAcl setAcl [-s] [-v version] [-R] path acl设置某个节点的权限 addauth addauth scheme auth添加某个权限,比如addauth digest lee:lee zk的作用体现 master节点选举,主节点挂了以后,从节点就会接受工作,并且保证这个节点都是唯一的,这也是所谓的首脑模式,从而保证我们的集群是高可用的。 统一配置文件管理,即只需要部署一台服务器,则可以把相同的配置文件同步更新到其他所有服务器,此操作在云计算中用的特别多 发布与订阅,类似于消息队列mq,dubbo发布者把数据存在zonde上,订阅者会读取这个数据。 提供了分布式锁,分布式环境中不同进程之间争夺资源,类似于多线程问题中的锁 集权管理,集群中保证数据的强一致性 zk的session的基本原理 客户端与服务端之间的链接存在会话 每个会话都可以设置一个超时时间 心跳结束,session则过期 session过期,则此session创建的临时节点znode会被抛弃 心跳机制:客户端向服务端的ping包请求 watcher机制 针对每个节点得操作,都会有一个监督者->watcher 可以理解为触发器,当节点有一些变化的时候,比如更新删除都会触发watcher事件,类似于触发器 zk中watcher是一次性的,触发后立即销毁 父节点,子节点 增删改都会出发其watcher 针对不同类型的操作,触发的watcher事件也不同,比如创建事件,删除事件,节点数据变化事件 命令行学习watcher 通过get [-w] path设置watcher stat [-w] path设置watcher例如执行 stat -w /xwm,此时就给xwm设置一个watcher,当修改/xwm得时候,如set /xwm 123时,会触发一个1WatchedEvent state:SyncConnected type:NodeDataChanged path:/xwm 格式的watcher,当再次执行set命令,则不会执行 watcher使用场景 统一资源配置例如一个集群中所有的节点都一样,给他们设置watcher,当一个改变了,客户端就会收到一个watcher事件,客户端就会进行相应的操作集群中其他的机器 acl权限控制 针对节点可以设置相关读写等权限,目的为了保障数据安全性 权限permissions可以指定不同的权限范围以及角色 命令 getAcl:获取某个节点的acl权限信息 setAcl:设置某个节点的acl权限信息 addauth:输入认证授权信息,注册时输入明文密码(登录)但是在zk系统里,密码是以加密的形式存在的 获取到一个节点的权限:getAcl /xwm/tmp12'world,'anyone: cdrwa zk的acl通过[scheme:id:permissions]来构成权限列表scheme:代表采用的某种权限机制id:代表允许访问的用户permissions:权限组和字符串 scheme world:world下只有一个id,即只有一个用户,也就是anyone,那么组合写法就是world:anyone:[permissions] auth:代表认证登录,需要注册用户权限就可以,形式为auth:user:passworld:[permissions] digest:需要对密码加密才能访问,组合形式为digest:username:BASE64(SHA1(password)):[permissions] auth和digest的区别就是前者是明文,后者是密文setAcl/path auth:lee:lee:cdrwa与setAcl /path digest:BASE64(SHA1(password))cdrwa是等价的,在通过addauth digest lee:lee后都能操作指定节点的权限。 ip:但设置为ip指定的ip地址,对此限制ip进行访问,比如ip:192.168.1.1:[permissions] super:代表超级管理员,拥有所有的权限 permissions 权限字符串缩写crdwa create:创建子节点 read:获取节点/子节点 write:设置节点数据 delete:删除子节点 admin:设置权限 设置super权限 修改zkServer,增加super管理员在运行Java的代码中添加1"-Dzookeeper.DigestAuthenticationProvider.spuerDigest=hello:JD3gbdfzNg9biCKhgtpVo+3enoI=" 则添加了超级管理员hello:123456 重启zkServer 登录超级管理员1addauth digest hello:123456 zk四字命令 four letter words zk可以通过它自身提供的简写命令来和服务器进行交互 需要nc命令,安装yum install nc echo [commond] | nc [ip] [port] commond envi环境变量 conf输出相关服务配置的详细信息。 cons列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息。包括“接受 / 发送”的包数量、会话 id 、操作延迟、最后的操作执行等等信息。 dump列出未经处理的会话和临时节点。 envi输出关于服务环境的详细信息(区别于 conf 命令)。 reqs列出未经处理的请求 ruok测试服务是否处于正确状态。如果确实如此,那么服务返回“imok ”,否则不做任何相应。 stat输出关于性能和连接的客户端的列表。 wchs列出服务器 watch 的详细信息。 wchc通过 session 列出服务器 watch 的详细信息,它的输出是一个与watch 相关的会话的列表。 wchp通过路径列出服务器 watch 的详细信息。它输出一个与 session相关的路径。 mntr监控zk健康信息 zk集群]]></content>
<tags>
<tag>zookeeper</tag>
</tags>
</entry>
<entry>
<title><![CDATA[mac入门之brew使用]]></title>
<url>%2F2019%2F09%2F02%2Fmac%E5%85%A5%E9%97%A8%E4%B9%8Bbrew%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[安装直接执行官网上的命令:https://brew.sh/如果提示xbox的问题,要去苹果开发者平台去下载xcode的包 基本使用安装服务brew install [email protected] 安装mysql的5.7版本 安装的目录默认在/usr/local/opt 搜索服务brew search mysql 搜索所有mysql相关的包 启动和停止服务brew services start [email protected] services stop [email protected] 查看程序信息brew info mysqlbrew info [email protected] 其中就有程序运行完的一些信息,比如设置全局变量之类的 安装带有可视化页面的程序brew cask install sequel-pro]]></content>
<tags>
<tag>mac</tag>
</tags>
</entry>
<entry>
<title><![CDATA[docker入门]]></title>
<url>%2F2019%2F08%2F24%2Fdocker%E5%85%A5%E9%97%A8%2F</url>
<content type="text"></content>
<tags>
<tag>docker</tag>
</tags>
</entry>
<entry>
<title><![CDATA[lambda学习]]></title>
<url>%2F2019%2F07%2F30%2Flambda%E5%AD%A6%E4%B9%A0%2F</url>
<content type="text"><![CDATA[基本使用lambda表达式简介什么是lambda表达式 lambda是java8添加的一个新特性,就是一个匿名函数 为什么使用lambda表达式 使用lambda可以对一个接口进行非常简洁的实现。123456789101112131415161718192021222324public class Main { public static void main(String[] args) { //1. 使用接口实现类 Comparator comparator = new MyComperator(); //2. 使用匿名内部类 Comparator comparator1 = new Comparator() { @Override public int compare(int a, int b) { return a-b; } }; //3.使用lambda表达式来实现接口 Comparator comparator2 = (a,b)->a-b; }}class MyComperator implements Comparator{ @Override public int compare(int a, int b) { return 0; }}interface Comparator{ int compare(int a,int b);} lambda对接口的要求 虽然可以用lambda表达是对某些接口进行简单实现,但是并不是所有接口都可以用lambda表达式来实现。要求接口中定义的必须要实现的抽象方法只能有一个。 在Java8对接口加了一个新的特性:default.lambda表达式要求要实现的方法只能有一个 @FunctionalInterface 修饰函数是接口(抽象方法只有一个),所以用lambda表达式的接口一般都加上此注解 lambda基础表达式 lambda表达式是一个匿名函数,由参数列表和方法体构成 ()用来描述参数列表 {}用来描述方法体 ->lambda运算符,读作goes to 123456789101112131415161718192021222324252627282930//无参无返回值@FunctionalInterfacepublic interface LambdaNoneReturnNoneParameter { void test();}//单参无返回值@FunctionalInterfacepublic interface LambdaNoneReturnSingleParameter { void test(int n);}//多参无返回值@FunctionalInterfacepublic interface LambdaNoneReturnMutipleParameter { void test(int a,int b);}//无参有返回值@FunctionalInterfacepublic interface LambdaSingleReturnNoneParameter { int test();}//单参有返回值@FunctionalInterfacepublic interface LambdaSingleReturnSingleParameter { int test(int a);}//多参有返回值@FunctionalInterfacepublic interface LambdaSingleReturnMutipleParameter { int test(int a,int b);} lambda基础实现12345678910111213141516171819202122232425262728293031323334353637383940414243//无参无返回LambdaNoneReturnNoneParameter lambda1 = ()->{ System.out.println("hello world");};lambda1.test();//hello world//无返回值,单个参数LambdaNoneReturnSingleParameter lambda2 = (int a)->{ System.out.println(a);};lambda2.test(10);//10//无返回值,多个参数LambdaNoneReturnMutipleParameter lambda3 = (int a,int b)->{ System.out.println(a+b);};lambda3.test(10,20);//30//无参数有返回值LambdaSingleReturnNoneParameter lambda4=()->{ System.out.println("lambda4"); return 100;};System.out.println(lambda4.test());//lambda4/n100//有返回值,一个参数LambdaSingleReturnSingleParameter lambda5 = (int a)->{ System.out.println("lambda5"); return a*3;};System.out.println(lambda5.test(4));//lambda5/n12//有返回值,多个参数LambdaSingleReturnMutipleParameter lambda6 = (int a,int b)->{ System.out.println("lambda6"); return a+b;};System.out.println(lambda6.test(5,6));//lambda6/n11 lambda语法精简简化参数类型 由于在接口的抽象方法中已经定义了参数的数量和类型,所以在lambda表达式中,参数的类型可以忽略 备注: 如果需要省略类型,则每一个参数的类型都要省略12345LambdaNoneReturnMutipleParameter lambda1 = (a,b)->{ System.out.println(a+b);};lambda1.test(2,3);//5 简化参数小括号 如果参数列表中,参数的数量只有一个,此时小括号可以省略12345LambdaNoneReturnSingleParameter lambda2 = a->{ System.out.println(a);};lambda2.test(5);//5 简化方法大括号 如果此时方法体中只有一条语句,此时大括号可以省略123LambdaNoneReturnSingleParameter lambda3 = a-> System.out.println(a);lambda3.test(6);//6 简化return 如果方法体中唯一的一条语句是一个返回语句,则省略大括号的同时也必须省略return1234567LambdaSingleReturnNoneParameter lambda4 = ()->10;System.out.println(lambda4.test());//10LambdaSingleReturnMutipleParameter lambda5 = (a,b)->a+b;System.out.println(lambda5.test(5,4));//9 语法进阶## 方法引用 一般方法的引用 语法:方法的隶属者::方法名 注意: 1.参数数量和类型一定要和接口中定义的方法一致 2.返回值的类型一定要和接口中定义方法一致12345678910111213141516171819public class Syntax3 { private static int change(int a){ return a*2; } public static void main(String[] args) { //定一个方法,相当于只有一个值的参数,相当于return change(a); LambdaSingleReturnSingleParameter lambda1 = a->change(a); //方法引用:引用了'类::方法名'的静态change方法的实现 LambdaSingleReturnSingleParameter lambda2 = Syntax3::change; //引用对象的方法 Syntax3 syntax3 = new Syntax3(); LambdaSingleReturnSingleParameter lambda3 = syntax3::change2; } private int change2(int a){ return a*3; }} 构造方法的引用精简1234567891011121314public class Person { public String name; public int age; public Person() { System.out.println("无参构造方法执行了"); } public Person(String name, int age) { this.name = name; this.age = age; System.out.println("有参构造方法执行了"); }} 1234567891011121314151617181920public class Syntax4 { public static void main(String[] args) { PersonCreater creater = ()->new Person(); //构造方法的引用 PersonCreater creater1 = Person::new; Person a = creater.getPerson(); //无参构造方法执行了 PersonCreater2 creater2 = Person::new; Person b = creater2.getPerson("是我", 18); //有参构造方法执行了 }}interface PersonCreater{ Person getPerson();}interface PersonCreater2{ Person getPerson(String name,int age);} 构造方法的引用精简是依托于接口中参数,对应构造方法的参数 lambda操作集合列表排序1 需求:一直再一个ArrayList中有若干个Person对象,将这些Person对象按照年龄进行降序排序 123456789101112ArrayList<Person> list = new ArrayList<>();list.add(new Person("a", 17));list.add(new Person("b", 12));list.add(new Person("c", 3));list.add(new Person("d", 144));list.add(new Person("e", 15));list.add(new Person("f", 18));list.add(new Person("g", 1));list.sort((o1, o2) -> o2.age - o1.age);System.out.println(list); 实现原理就是实现了sort中的Comparator接口的compare方法 排序2 使用TreeSet自带的排序 12345678910111213141516//使用Lambda表达式来实现Comparetor接口,并实例化一个TreeSet对象TreeSet<Person> set = new TreeSet<>((o1, o2) -> { if (o1.age > o2.age) { return -1; } else { return 1; }});set.add(new Person("a", 17));set.add(new Person("b", 12));set.add(new Person("c", 3));set.add(new Person("d", 144));set.add(new Person("e", 15));set.add(new Person("f", 18));set.add(new Person("g", 1));System.out.println(set); 因为Tree会根据排序规则是否为0判断是否为统一元素去重,所以表达和上面有所不同 集合遍历用list的forEach方法遍历,lambda实现一个Consumer12345678910ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list,1,2,3,4,5,6,7);//对所有输出list.forEach(System.out::println);//仅输出偶数list.forEach(ele->{ if(ele%2==0){ System.out.println(ele); }}); 删除集合元素 删除满足条件的集合元素,删除年龄为偶数的元素 123456789101112ArrayList<Person> list = new ArrayList<>();list.add(new Person("a", 17));list.add(new Person("b", 12));list.add(new Person("c", 3));list.add(new Person("d", 144));list.add(new Person("e", 15));list.add(new Person("f", 18));list.add(new Person("g", 1));//删除年龄为偶数的元素list.removeIf(person -> person.age % 2 == 0);System.out.println(list); 是重写了removeIf的参数Predicate,lambda实现了他的test方法 操作线程创建线程 匿名内部类通过实现Runnable接口创建线程123456Thread thread = new Thread(()->{ for (int i = 0; i <100 ; i++) { System.out.println(i); }});thread.start(); 系统内置函数式接口参考防挂 闭包123456789public static void main(String[] args) { System.out.println(getNumber().get());}private static Supplier<Integer> getNumber(){ int num = 10; return ()->{ return num; };} 此时输出是10 问题:按说执行完getNumber了局部变量num就会销毁,怎么还能获取到值 闭包会提升变量的生命周期 lambda中使用的局部变量一定是常量,如果没有写final,编译时候会自动加上final,不要修改lambda中调用的局部变量的值]]></content>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[nginx总结]]></title>
<url>%2F2019%2F07%2F22%2Fnginx%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[nginx安装和基本讲解(centos7)nginx安装 打开http://nginx.org/en/download.html Mainline version是开发版本,Stable version是稳定版本,Legacy versions是历史版本 打开最下方的stable and mainline可以看centos的安装教程 sudo yum install yum-utils 创建/etc/yum.repos.d/nginx.repo写入以下内容12345678910111213[nginx-stable]name=nginx stable repobaseurl=http://nginx.org/packages/centos/7/$basearch/gpgcheck=1enabled=1gpgkey=https://nginx.org/keys/nginx_signing.key[nginx-mainline]name=nginx mainline repobaseurl=http://nginx.org/packages/mainline/centos/7/$basearch/gpgcheck=1enabled=0gpgkey=https://nginx.org/keys/nginx_signing.key 上面是稳定版本,下面是开发版本 如果想安装开发版本执行sudo yum-config-manager –enable nginx-mainline 安装命令sudo yum install nginx 使用nginx-v验证安装成功 基本参数的使用nginx安装目录 使用npm -ql nginx可以看已经安装服务在操作系统上的文件和目录 路径 类型 作用 /etc/logrotate.d/nginx 配置文件 Nginx日志轮转,用于logrotate服务的日志切割和清理 /etc/nginx/etc/nginx/nginx.conf/etc/nginx/conf.d/etc/nginx/conf.d/default.conf 目录、配置文件 nginx主配置文件nginx.conf是主要配置在没有做变更的情况下会读default.conf,默认加载的配置文件 /etc/nginx/fastcgi_params/etc/nginx/uwsgi_params/etc/nginx/scgi_params 配置文件 cgi配置相关,fastcgi配置 /etc/nginx/koi-utf/etc/nginx/koi-win/etc/nginx/win-utf 配置文件 编码转换映射转化文件 /etc/nginx/mime.types 配置文件 设置http协议的Content-Type与扩展名对应关系 /usr/lib/systemd/system/nginx-debug.service/usr/lib/systemd/system/nginx.service/etc/sysconfig/nginx/etc/sysconfig/nginx-debug 配置文件 用于配置出系统守护进程管理器管理方式 /usr/lib64/nginx/modules/etc/nginx/modules 目录 nginx模块目录 /usr/sbin/nginx/usr/sbin/nginx-debug 命令 nginx服务的启动管理的终端命令 /usr/share/doc/nginx-x.xx.x/usr/share/doc/nginx-x.xx.x/COPYRIGHT/usr/share/man/man8/nginx.8.gz 文件、目录 nginx的手册和帮助文件 /var/cache/nginx 目录 nginx的缓存目录 /var/log/nginx 目录 nginx的日志目录 nginx安装编译参数 使用nginx -V可查看安装编译参数 编译选项 作用 –prefix=/etc/nginx–sbin-path=/usr/sbin/nginx–modules-path=/usr/lib64/nginx/modules–conf-path=/etc/nginx/nginx.conf–error-log-path=/var/log/nginx/error.log–http-log-path=/var/log/nginx/access.log–pid-path=/var/run/nginx.pid–lock-path=/var/run/nginx.lock 安装目录或者路径 –http-client-body-temp-path=/var/cache/nginx/client_temp–http-proxy-temp-path=/var/cache/nginx/proxy_temp–http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp–http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp–http-scgi-temp-path=/var/cache/nginx/scgi_temp 执行对应模块时nginx所保留的临时性文件 –user=nginx–group=nginx 设定nginx进程启动的用户和组用户 –with-cc-opt=’parameters’ 设置额外的参数将被添加到CFLAGS变量 –with-ld-opt=’parameters’ 设置附加的参数,链接系统库 默认配置语法 打开/etc/nginx/nginx.conf文件可以配置nginx,文件中最下面一行1include /etc/nginx/conf.d/*.conf; 表示包含了这个目录下的所有conf文件,打开了只有一个default.conf nginx.conf包含一下配置 基本配置user:设置nginx服务的系统使用用户,和上面编译参数中–user对应worker_processes:工作进程数,跟nginx多进程有关系,增大并发处理,一般和cpu核心数保持一致error_log:nginx的错误日志路径pid:nginx服务启动时候pid events模块表示nginx使用epoll内存模型的参数worker_connections:每个进程成允许最大连接数,可以是最大65535,一般一万左右use:工作进程数,使用哪种内核模型,linux是select epoll http大括号中表示http协议的一些配置 1234567891011121314151617181920212223242526272829303132333435363738http{ #子配置文件 include /etc/nginx/mime.types; default_type application/octet-stream; #日志类型 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; #日志文件存放 access_log /var/log/nginx/access.log main; #nginx一大优势,默认打开 sendfile on; #tcp_nopush on; #超时时间 keepalive_timeout 65; server { # 监听端口,服务名 listen 80; server_name localhost; #自己主机名或域名 #location控制每一层路径访问 #表示每个目录下默认访问index.html或者index.htm #如果访问根/,那么去/usr/share/nginx/html文件夹下找index.html和index.htm location / { root /usr/share/nginx/html; index index.html index.htm; } #错误码,在500,502,503,504时候都访问/50x.html error_page 500 502 503 504 /50x.html; #访问/50x.html存放在这个目录下 location = /50x.html { root /usr/share/nginx/html; } } server{ ... ... }} 一个http有多个server,一个server有多个location nginx配置日志配置 error.log nginx处理http请求的错误状态,以及本身运行服务的状态 access_log nginx每一次http请求的访问状态 log_format配置log_format将产生的变量组织到一次,写入access_log中 语法:Syntax: log_format name [escape=defalt|json]log_format:配置关键字,name表示格式名字,后面是所有的字符串变量 默认配置:log_format combined “…”Context:http表示只能配置到http的模块下面 access_log12345678....#日志类型log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';#日志文件存放access_log /var/log/nginx/access.log main;.... 上述代码定义了一个名叫main的日志类型,然后在access_log中声明了地址和使用main日志类型 小技巧:配置完了可以运行 nginx -tc /etc/nginx/nginx.conf检查配置语法是否正确 log变量 http请求变量arg_PARAMETER:请求头中的参数http_HEADER:请求头,如$http_user_agentsent_http_HEADER:返回的头部(response的header) 都要小写,横杠换成下划线 内置变量内置变量比较多,打开官方文档查看 http://nginx.org/en/docs/http/ngx_http_core_module.html#variables 自定义变量//TODO nginx模块官方模块–with-http_stub_status_module Nginx的客户端状态,用于监控nginx当前的一些信息配置语法:stub_status,打开此功能默认没配置需要配置在server或者location中 123456789http{ ... server{ ... location /mystatus { stub_status; } }} 此时访问url/mystatus会闲事当前nginx客户端状态 Active connections: 1server accepts handled requests5 5 5Reading: 0 Writing: 1 Waiting: 0 Active connections表示nginx当前活跃的连接数server accepts handled requests下面的三个数第一个数表示nginx处理接受握手总次数第二个数表示nginx处理的连接数第三个表示nginx表示总得请求书正常是第一第二要相等表示没有丢失 Reading: 0 Writing: 1 Waiting: 0表示正在读、正在写、正在等待(没读没写有链接)的个数 –with-http_random_index_module 目录中选择一个随机主页 语法:random_index on|off;默认:random_index off;限制:location 12345location / { root /usr/share/nginx/html; random_index on; #index index.html index.htm;} 此时访问/,会将/usr/share/nginx/html目录下的文件随机返回一个 注意此模块不会选择隐藏的文件作为随机主页 –with-http-sub-module 用于http内容替换 sub_filter 指定字符串的替换 语法:sub_filter string replacement;默认:无限制:http,server,location123456789http{ server{ location / { root /usr/share/nginx/html; index index.html index.htm; sub_filter 'Welcome' 'www'; } }} 会将返回的第一个Welcome替换成www sub_filter_last_modified 用于服务端和客户端进行请求校验服务端内容是否发生改变,判断是否有更新,用于缓存语法:sub_filter_last_modified no|off默认:sub_filter_last_modified off;限制:http,server,location sub_filter_once on|off 匹配第一个还是所有的语法:sub_filter_once on|off默认:sub_filter_once on限制:http,server,location 12345678910http{ server{ location / { root /usr/share/nginx/html; index index.html index.htm; sub_filter 'Welcome' 'www'; sub_filter_once off; } }} 会将返回结果的所有Welcome替换成www 第三方模块nginx的请求限制连接频率的限制 http请求建立在一次TCP连接基础之上 一次TCP连接至少产生一次HTTP请求 limit_conn_zone 存储链接状态的空间zone语法:limit_conn_zone key zone=name:size默认:无限制:http key:以什么作为限制对象的名字,比如以ip地址为限制变量,那么就写$binary_remote_addr为keyzone:name表示实现限制的时候调用的空间名字,size是这个空间的大小,1M共享空间可以保存3.2万个32位的状态,1.6万个64位的状态。如果共享内存空间被耗尽,服务器将会对后续所有的请求返回 503 (Service Temporarily Unavailable) 错误。 limit_conn 配置请求限制语法:limit_conn zone number;默认:无限制:http,server,location zone:上面存储空间的名字,和上面的zone对应number:并发限制的个数 这个无论如何我也实现不了效果 //TODO 请求频率的限制limit_req_zone 存储的空间zone语法:limit_req_zone key zone=name:size rate=rate默认:无限制:http key:以什么作为限制对象的名字,比如以ip地址为限制变量,那么就写$binary_remote_addr为keyzone:name表示实现限制的时候调用的空间名字,size是这个空间的大小rate:请求的速率,秒为单位 limit_req语法:limit_req zone=name [burst=number] [nodelay];默认:无限制:http,server,locationzone:调用的空间名字 其中burst和nodelay参考这篇博客 nginx访问控制基于ip的访问控制http_access_module语法:allow address|CIDR|unix:|all默认:无限制:http,server,location,limit_except 语法:deny address|CIDR|unix:|all默认:无限制:http,server,location,limit_except 1234567http{ deny 10.6.5.99; allow all; server{ ... }} 此时只有10.6.5.99是不能访问返回403,其他都能访问 注意,如果allow all在上面,此时都能访问,deny将不起作用 也可以使用ip段的方式,如192.168.0.0/24 http_x_forwarded_for 直接用remote_addr只能看到最后一个ip x_forward_for可以获取到一路上获取的所有ip 是协议要求,但是不一定都有,也是可以被修改的 基于用户的信任登陆http_auth_basic_module语法:auth_basic string|off;默认:auth_basic off;限制:http,server,location,limit_except; 语法:auth_basic_user_file file;默认:无限制:http,server,location,limit_except 12345678910http { ... server { ... location / { root html; auth_basic "请输入你的用户名和密码"; auth_basic_user_file antu_conf; index index.html index.htm; } 此时会提示输入用户名和密码,并且用户名和密码在antu_conf文件中对应(位置相对于conf),密码文件格式参考 官方文档,可以使用htpasswd生成 静态资源web服务文件读取配置sendfile on|off 文件读取语法:sendfile on|off默认:sendfile off限制:http,server,location,if in location tcp_nopush sendfile开启的情况下,提高网络报的传输效率语法:tcp_nopush on|off;默认:tcp_nopush off;限制:http,server,location 原理相当于十个快递叫十个快递员优化成十个快递一次性叫一个,大文件适用 tcp_nodelay keepalive连接下,提高网络传输的实时性语法:tcp_nodelay on|off;默认:tcp_nodelay on;限制:http,server,location; 报文不要等待,直接发送 文件压缩gzip 压缩传输语法:gzip on|off;默认:gzip off;限制:http,server,location,if in location 原理是nginx端压缩,然后浏览器端解压缩,通用协议的压缩 gzip_comp_level 压缩比率语法:gzip_comp_level leve;默认:gzip_comp_level 1;限制:http,server,location 数字越大压缩比率越大,结果越小,但是会影响服务器性能 gzip_http_version gzip的http协议版本语法:gzip_http_version 1.0|1.1默认:gzip_http_version 1.1限制:http,server,location http_gzip_static_module 预读gzip功能,先找gzip文件,如果有直接发送这个,节省压缩的时间 预读的gz是事先自己压缩好的 http_gunzip_module 很少浏览器不支持gzip,需要用这个来压缩 浏览器缓存校验是否过期 Expires:http1.0的缓存协议,CacheControl(max-age):http:1.1的协议 定义了缓存的有效期 客户端先去检查是否在有效期内 如果超期了会调用etag和last-modified验证 超期验证last-modified后跟了一个时间,用来跟服务器端本地文件校验,如果服务端的时间和last-modified时间不一致,那么返回新文件 last-modified传输的是时间,精确到秒,如果是一秒内的改动就会出现问题 etag是以一串特殊的字符串来进行校验 常用的是etag来进行校验 如果命中缓存则状态码是304 nginx的过期设置expires 添加Cache-Control,Expires头,设置过期时间语法:expires [modified] time;expires epoch|max|off;默认:expires off;限制:http,server,location,if in location nginx跨域访问add_header语法:add_header name value [always]默认:无限制:http,server,location,if in location 实现跨站访问则要设置1Access-Control-Allow-Origin:允许跨域的域名,或者*允许所有跨域访问 防盗链 防止网站资源被盗用 http_refer语法:valid_referers none|blocked|server_names|string …默认:无限制:server,location 1234567location ~ .*\.(jpg|gif|png)$ { valid_referers none blocked 119.28.190.215; if ($invalid_referer) { return 403; } root /opt/app/code/images;} none : 允许没有http_refer的请求访问资源; blocked : 允许不是http://开头的,不带协议的请求访问资源; 119.28.190.215 : 只允许指定ip来的请求访问资源,可以用正则匹配; 根据header存储的refer头进行验证,防止被别的域名套用 防盗链有限,因为refer信息是可以伪造 代理proxy_pass 代理配置语法语法:proxy_pass URL;默认:无限制:location,if in location,limit_except 代理缓冲 代理开启缓冲语法:proxy_buffering on|off;默认:proxy_buffering on限制:http,server,location扩展:proxy_buffer_size,proxy_buffers,proxy_busy_buffers_size proxy_redirect 跳转重定向语法:proxy_redirect default|off|redirect replacement默认:proxy_redirect default;限制:http,server,location 当请求是301重定向的时候启用此模块,重写重定向301的地址 proxy_set_header 设置头信息语法:proxy_set_header field value;默认:proxy_set_header Host $proxy_host;proxy_set_header Connection close;限制:http,server,location扩展:proxy_hide_header隐藏头,proxy_set_body proxy_connect_timeout 代理超时语法:proxy_connect_timeout time;默认:proxy_connect_timeout 60s;限制:http,server,location 反向代理1234567891011121314location /{ proxy_pass http://127.0.0.1:8080; # 请求转向本虚拟机的8080端口 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; #获取真实ip proxy_connect_timeout 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;#获取代理者的真实ip proxy_redirect off;} 请求此location会被请求到http://127.0.0.1:8080地址 正向代理123456location{ if($http_x_forwarded_for !~* "^xxx\.xxx\.xxx\.xxx"){ return 403; } ...} 只允许xxx.xxx.xxx.xxx访问 正向代理参考此博客 负载均衡upstream语法:upstream name{…}默认:无限制:http12345678910111213http{ upstream hhh{ server 127.0.0.1:8080; server 127.0.0.1:8081; server 127.0.0.1:8082; } server{ location /{ proxy_pass http://hhh; //反向代理的配置 } }} 此时访问会时候会轮询访问8080,8081,8082 如果一个节点挂掉了会自动下线 参数 含义 down 当前server下线,不参与负载均衡 backup 预留的备份服务器,本身不提供服务,当同组其他节点都挂掉才会提供服务 max_fails 允许请求失败的次数 fail_tmeout 经过max_fails失败后,服务暂停的时间,默认是10秒,过了再次检查是否失败 max_conns 限制最大的接收的连接数]]></content>
<tags>
<tag>nginx</tag>
</tags>
</entry>
<entry>
<title><![CDATA[学习springboot]]></title>
<url>%2F2019%2F07%2F06%2F%E5%AD%A6%E4%B9%A0springboot%2F</url>
<content type="text"><![CDATA[手动装配spring模式注解装配 定义:一种用于生命在应用中扮演”组件”角色的注解 举例:@Component,@Service,@Configuration等 装配: <context:component-scan>或者@ComponentScan A stereotype annotation is an annotation that is used to declare the role that a component plays within the application. For example, the @Repository annotation in the Spring Framework is a marker for any class that fulfills the role or stereotype of a repository (also known as Data Access Object or DAO).@Component is a generic stereotype for any Spring-managed component. Any component annotated with @Component is a candidate for component scanning. Similarly, any component annotated with an annotation that is itself meta-annotated with @Component is also a candidate for component scanning. For example, @Service is meta-annotated with @Component . 模式注解是一种用于声明在应用中扮演”组件”角色的注解。如 Spring Framework 中的 @Repository 标注在任何类上 ,用于扮演仓储角色的模式注解。@Component 作为一种由 Spring 容器托管的通用模式组件,任何被 @Component 标准的组件均为组件扫描的候选对象。类似地,凡是被 @Component 元标注(meta-annotated)的注解,如 @Service ,当任何组件标注它时,也被视作组件扫描的候选对象 Spring Framework 注解 场景说明 起始版本 @Repository 数据仓储模式注解 2.0 @Component 通用组件模式注解 2.5 @Service 服务模式注解 2.5 @Controller Web 控制器模式注解 2.5 @Configuration 配置类模式注解(代替配置文件进行描述) 3.0 装配方式 <context:component-scan>方式 123456789101112<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring/context.xsd"> <!-- 激活注解驱动特性 --> <context:annotation-config /> <!-- 找寻被 @Component 或者其派生 Annotation 标记的类(Class),将它们注册为 Spring Bean --> <context:component-scan base-package="cn.xwmdream.dive.in.spring.boot" /></beans> @ComponentScan 方式 1234@ComponentScan(basePackages = "cn.xwmdream.dive.in.spring.boot")public class SpringConfiguration { ...} 自定义模式注解 @Component “派生性”1234567@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Servicepublic @interface FirstLevelService { String value() default "";} 派生性指的是@Component->@Service->@FirstLevelService派生性不是真的存在,类似class的继承 @Component “层次性”1234567@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@FirstLevelServicepublic @interface SecondLevelService { String value() default "";} @Component->@Service->@FirstLevelService->@SecondLevelService Spring @Enable 模块装配 具备相同的功能组件集合,组成所形成一个独立的单元 距离:@EnableWebMvc,@EnableAutoConfiguration等 实现:注解方式,编程方式 Spring Framework 3.1 开始支持”@Enable 模块驱动”。所谓”模块”是指具备相同领域的功能组件集合, 组合所形成一个独立的单元。比如 Web MVC模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管 理扩展)模块、Async(异步处理)模块等。 @Enable注解模块举例 框架实现 @Enable 注解模块 激活模块 Spring Framework @EnableWebMvc Web MVC 模块 @EnableTransactionManagement 事务管理模块 @EnableCaching Caching 模块 @EnableMBeanExport JMX 模块 @EnableAsync 异步处理模块 @EnableWebFlux Web Flux 模块 @EnableAspectJAutoProxy AspectJ 代理模块 Spring Boot @EnableAutoConfiguration 自动装配模块 @EnableManagementContext Actuator 管理模块 @EnableConfigurationProperties 配置属性绑定模块 @EnableOAuth2Sso OAuth2 单点登录模块 Spring Cloud @EnableEurekaServer Eureka服务器模块 @EnableConfigServer 配置服务器模块 @EnableFeignClients Feign客户端模块 @EnableZuulProxy 服务网关 Zuul 模块 @EnableCircuitBreaker 服务熔断模块 实现方式注解驱动方式1234567@Configurationpublic class HelloWorldConfiguration { @Bean public String hello(){ return "Hello world 中国"; }} 123456@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(HelloWorldConfiguration.class)public @interface EnableHelloWorld {} 1234567891011@EnableHelloWorldpublic class EnableHelloWorldBootstrap { public static void main(String[] args) { ConfigurableApplicationContext run = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class).web(WebApplicationType.NONE) .run(args); String ans = run.getBean("hello",String.class); System.out.println(ans); //关闭上下文 run.close(); }} EnableHelloWorld注解上import了HelloWorldConfiguration类,所以运行EnableHelloWorldBootstrap就可以获取到HelloWorldConfiguration类下的hello的bean能被装配到容器中 接口编程方式123456public class HelloWorldImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{HelloWorldConfiguration.class.getName()}; }} 1234567@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented//@Import(HelloWorldConfiguration.class)@Import(HelloWorldImportSelector.class)public @interface EnableHelloWorld {} EnableHelloWorld的import改成了HelloWorldImportSelector,而HelloWorldImportSelector的selectImports返回了HelloWorldConfiguration,所以可以将HelloWorldConfiguration下的bean装配到容器中。注意HelloWorldImportSelector的方法可以返回多个配置类 自定义条件装配基于配置方式实现 -@Profile12345678910111213141516171819202122232425262728public interface CalculateService { Integer sum(Integer... integers);}@Service@Profile("Java8")public class Java8CalculateService implements CalculateService { @Override public Integer sum(Integer... integers) { System.out.println("java8"); int sum = Stream.of(integers).reduce(0,Integer::sum); return sum; }}@Service@Profile("Java7")public class Java7CalculateService implements CalculateService { @Override public Integer sum(Integer... integers) { System.out.println("java7"); int sum = 0; for(int i= 0 ;i<integers.length;i++){ sum+=integers[i]; } return sum; }} 两个类都实现了CalculateService接口的service,但是@profile不同,调用的时候根据这个参数可以加载不同的对象 12345678910111213@SpringBootApplication(scanBasePackages = "com.example.studyspringboot.service")public class CalculateServiceBootStrap { public static void main(String[] args) { ConfigurableApplicationContext run = new SpringApplicationBuilder(CalculateServiceBootStrap.class).web(WebApplicationType.NONE) .profiles("Java8") .run(args); CalculateService calculateService = run.getBean(CalculateService.class); System.out.println("1+...+10:" +calculateService.sum(1,2,3,4,5,6,7,8,9,10)); //关闭上下文 run.close(); }} 可以看到加载上下文的时候指定profiles参数,不同的参数对应不同实现类上@Profile的参数 基于编程方式实现(Condition)自定义Condition123456789101112131415161718/** * 系统属性条件判断 */public class OnSystemPropertyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //获取到ConditionalOnSystemProperty注解的所有的参数 Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName()); //分别获取到name和value属性 String propertyName = String.valueOf(attributes.get("name")); String propertyValue = String.valueOf(attributes.get("value")); //获取系统的name属性值 String javaPropertyValue = System.getProperty(propertyName); //如果相同返回true return propertyValue.equals(javaPropertyValue); }} 自定义注解 12345678@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD})@Documented@Conditional(OnSystemPropertyCondition.class)public @interface ConditionalOnSystemProperty { String name(); String value();} 使用 1234567891011121314//我使用这台电脑的user.name是hp@Bean@ConditionalOnSystemProperty(value="hp",name="user.name")public String helloWorld(){ return "hello 2019";}public static void main(String[] args) { ConfigurableApplicationContext run = new SpringApplicationBuilder(ConditionalOnSystemPropertyBootStrap.class).web(WebApplicationType.NONE) .run(args); String h = run.getBean("helloWorld",String.class); System.out.println(h); //关闭上下文 run.close();} 在注解上使用Conditional指定一个Condition,当调用helloWorld的bean时会激活它头上的ConditionalOnSystemProperty注解,这个注解会调用OnSystemPropertyCondition的matches方法,如果true才会加载这个bean 我这台电脑的user.name是hp,所以在matches最后返回的值是true 自动装配实现方式 激活自动装配-@EnableAutoConfiguration 实现自动装配-xxxAutoConfiguration 配置自动装配实现-META-INF/spring.factories 自定义自动装配 创建自动装配类1234@Configuration// spring模式注解@EnableHelloWorld//spring@Enable模块装配@ConditionalOnSystemProperty(name="user.name",value="hp")//条件装配public class HelloWorldAutoConfiguration {} 在这个类上调用了@Enablehelloworld,使用之前的模块装配将’hello’bean装配进来 使用了ConditionalOnSystemProperty进行条件装配 ‘HelloWorldAutoConfiguration’条件判断: user.name == “hp”模式注解: @Configuration@Enable 模块: @EnableHelloWorld -> HelloWorldImportSelector -> HelloWorldConfiguration - > hello bean 创建(resources)/META-INF/spring.factories,指定自动装配类123#自动装配org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.example.studyspringboot.configuration.HelloWorldAutoConfiguration 会将HelloWorldAutoConfiguration装配到容器中 运行1234567891011@EnableAutoConfigurationpublic class EnableAutoConfigurationBootstrap { public static void main(String[] args) { ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationBootstrap.class).web(WebApplicationType.NONE) .run(args); String hello = context.getBean("hello", String.class); System.out.println(hello); //关闭上下文 context.close(); }} 注解EnableAutoConfiguration会将spring.factories的HelloWorldAutoConfiguration类自动装配进来,然后装配@Enablehelloworld所指定的类 只是在springframework手动装配基础上,增加了spring.factories和EnableAutoConfiguration,实现了自动装配 理解SpringApplication 定义:Spring应用引导类,提供便利的自定义行为方法 场景:嵌入式Web应用和非Web应用 运行:SpringApplication#run(String …) SpringApplication基本使用SpringApplication运行1SpringApplication.run(xxx.class,args); 将启动Java的参数args传给了SpringApplication 自定义SpringApplicationSpringApplication准备阶段 配置:SpringBean源 推断:Web应用类型和主引导类(Main Class) 加载:应用上下文初始器和应用事件监听器 配置SpringBean源]]></content>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[redis安装和使用]]></title>
<url>%2F2019%2F06%2F23%2Fredis%E5%AE%89%E8%A3%85%E5%92%8C%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[windows下安装下载文件redis官方没有提供windows的版本微软一个团队提供了windows版本下的redis,不过更新速度比官方慢很多下载地址 下载zip文件 安装redis服务解压到一个目录后用cmd打开此目录1redis-server.exe --service-install redis.windows.conf --loglevel verbose 最后的loglevel命令表示记录日志的等级 执行成功后即可在系统的服务管理页面找到redis的服务 其他命令 卸载服务:redis-server –service-uninstall 开启服务:redis-server –service-start 停止服务:redis-server –service-stop 重命名服务:redis-server –service-name name 重命名服务,需要写在前三个参数之后,例如: 以下将会安装并启动三个不同的Redis实例作服务: 123456redis-server --service-install --service-name redisService1 --port 10001redis-server --service-start --service-name redisService1redis-server --service-install --service-name redisService2 --port 10002redis-server --service-start --service-name redisService2redis-server --service-install --service-name redisService3 --port 10003redis-server --service-start --service-name redisService3 测试12345#精简模式:redis-cli.exe#指定模式:redis-cli.exe -h 127.0.0.1 -p 6379 -a requirepass#(-h 服务器地址 -p 指定端口号 -a 连接数据库的密码[可以在redis.windows.conf中配置],默认无密码) 更多教程 设置密码在conf文件中添加1requirepass 666 然后在redis-cli连上以后使用1auth 666 来验证密码后使用 整合springboot 添加依赖 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency> 写入配置文件 1234567891011121314151617181920212223242526272829303132333435363738#Matser的ip地址redis.hostName=192.168.177.128#端口号redis.port=6382#如果有密码redis.password=#客户端超时时间单位是毫秒 默认是2000redis.timeout=10000#最大空闲数redis.maxIdle=300#连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal#redis.maxActive=600#控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性redis.maxTotal=1000#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。redis.maxWaitMillis=1000#连接的最小空闲时间 默认1800000毫秒(30分钟)redis.minEvictableIdleTimeMillis=300000#每次释放连接的最大数目,默认3redis.numTestsPerEvictionRun=1024#逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1redis.timeBetweenEvictionRunsMillis=30000#是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个redis.testOnBorrow=true#在空闲时检查有效性, 默认falseredis.testWhileIdle=true#redis集群配置spring.redis.cluster.nodes=192.168.177.128:7001,192.168.177.128:7002,192.168.177.128:7003,192.168.177.128:7004,192.168.177.128:7005,192.168.177.128:7006spring.redis.cluster.max-redirects=3#哨兵模式#redis.sentinel.host1=192.168.177.128#redis.sentinel.port1=26379#redis.sentinel.host2=172.20.1.231#redis.sentinel.port2=26379]]></content>
<tags>
<tag>redis</tag>
</tags>
</entry>
<entry>
<title><![CDATA[jvm学习(持续更新)]]></title>
<url>%2F2019%2F06%2F03%2Fjvm%E5%AD%A6%E4%B9%A0%2F</url>
<content type="text"><![CDATA[先看一个内存溢出1234List<Demo> demoList = new ArrayList<Demo>();while(true){ demoList.add(new Demo());} 上述代码会不断创建Demo对象,直到内存溢出报错12345678Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:265) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231) at java.util.ArrayList.add(ArrayList.java:462) at cn.xwmdream.Main.main(Main.java:11) 分析内存溢出 在虚拟机参数中添加-XX:+HeapDumpOnOutOfMemoryError可以查看虚拟机内存溢出的错误 同时增加 -Xms20m -Xmx20m可以限制堆大小最大最小都是20m,可以更快复现堆内存溢出错误 此时在根目录就会产生一个.hprof文件记录了此次内存溢出错误的快照 打开mat(Memory Analyzer Tool)内存分析工具,在file中加载刚刚生成的hprof文件图片错误 通过摘要信息发现Problem Suspect1表示可能出现问题的区域,占了16m(一共内存限制是20m) 点击上面图标从左数第三个打开dominator_tree查看堆内存树信息图片错误 shallow Heap表示对象本身占用的内存大小(不包括引用对象),Retained Heap表示当前对象大小加上当前可直接或者间接引用对象大小的总和 可以看到内存泄漏发生在main方法(占用了96%的内存),是因为Object对象,点开是因为Demo对象太多(810325个)导致了内存泄漏,此时就要去main方法分析这一段代码并解决即可,针对这个样例程序可以优化代码或者改大堆内存限制或者增大物理内存 java发展史 java技术体系 code->编译器->class->java vm 对于相同的class在不同平台使用不同的javavm可是实现相同的效果 java技术体系 Java程序设计语言 各硬件平台上的Java虚拟机 class文件格式 Java api 第三方Java类库 Java虚拟机产品Sum Classic Vm 世界上第一款商用的虚拟机 1996 java1.0发布的虚拟机 纯解释器的方式来执行Java代码,所以运行效率比较慢 Exact VM sun公司对上面虚拟机的优化 Exact Memory Management准确时内存管理 编译器和解释器混合工作以及两级编译器 只在Solaris平台发布,英雄气短 HotSpot VM 现在jdk用的虚拟机,称霸武林 引入更多代码优化的技术 KVM kilobyte 简单,轻量,高度可移植 在手机平台上运行 JRockit BEA公司开发 世界上最快的Java虚拟机 专注于服务端应用 优势: 垃圾收集器; MissionControl服务套件该套件开销小,可以寻找生产环境中的内存泄漏 java类加载 再Java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的 提供了更大的灵活性,增加了更多的可能性 类加载器深入剖析]]></content>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[位运算]]></title>
<url>%2F2019%2F05%2F23%2F%E4%BD%8D%E8%BF%90%E7%AE%97%2F</url>
<content type="text"><![CDATA[二进制计算机都是用的二级制运算的,别问为啥,问就是计算机只会01计算,算不到2具体二进制运算等一些操作可以百度一下,或者看一下这个,(我百度搜的) 比如21的二进制是10101 二进制运算 二进制运算和普通加减乘除运算差不多,都是几个数一顿操作最后得出另一个数,比如1+0=1,比如1&0=0 与运算(逻辑乘法) 与(&)运算:都1为1,否则为0 比如5&45:1014:100=:100=4 比如17&2111: 101121:10101&=:00001 =111的二进制是四位,21的二进制是五位,运算的时候后端对齐,前端补零(前面补多少个都可以,反正计算机也不会站起来打你),所以11相当于01011 或(逻辑加法) 或(|)运算:都0为0,否则为1 比如5&45:1014:100=:101=5 比如17&2111:0101121:10101|=:11111 =31 非运算(逻辑否定) 非(~)运算:0为1,1为0 比如~55:101~:010 =2 但是你们在编译器上打出来绝对不会是2,实际上等于-6,因为还涉及到负数形式(负数相关操作) 此样例只是说明非运算是使0成1,1成0,不要被误导 <<左移运算 左移运算就是向左移动 21的二进制是10101左移就是将他二进制整个向左移动21<<1就是21的二进制10101向左移动1位:101010 = 4242<<1就是42的二进制101010向左移动1位:1010100 = 8421<<2就是21的二进制10101向左移动2位:1010100 = 84 左移运算就是将整个二进制向左移动几位,右边增加的位置补0即可 左移运算其实就是乘以2,所以在写代码的时候如果有乘以2,4,8…的情况可以用左移1,2,3…位来完成,因为位运算的执行效率比乘法高 >>右移运算 右移运算就是向右移动 21的二进制是10101右移就是将他二进制整个向右移动21>>1就是21的二进制10101向右移动1位:1010 = 1010>>1就是42的二进制1010向右移动1位:101 = 521>>2就是21的二进制10101向右移动2位:101 = 5 右移运算就是将整个二进制向右移动几位,减少的位置就减少了,相当于从后面砍了几位 右移运算相当于除以2,所以….. ^异或操作 异或操作:相同为0,不同为121:1010112:01100=:11001 = 25 组合操作判断eofc语言编程中多行输入通常用scanf(…)!=EOF来判定其实在c语言源码中EOF的值是-1,所以上面那句话等同于scanf(…)!=-1而-1的二进制是32个1,具体看负数运算,所以32个1取反就是32个0,等于0所以while(scanf(…)!=EOF)可以改写成while(~scanf(…)!=0),即while(~scanf(…)){…} 将某一位设置成某个数将n的第i(从零开始)位设置为11234int set1(int n,int i){ n=n|(1<<i); return n;} 1<< i是让第i位为1,然后1或上任何数都是1,其他位上都是0,0或上任何数都让原数字保持不变 将n的第i(从零开始)位设置为01234int set0(int n,int i){ n =n&(~(1<<i)); return n;} ~ (1<< i)操作让第i位为0,其他位为1,1与任何数都是任何数,0与任何数都是0 123456789101112131415161718#include<stdio.h>int set1(int n,int i){ n=n|(1<<i); return n;}int set0(int n,int i){ n =n&(~(1<<i)); return n;}int main(){ int a = 5;//二进制是101 printf("%d\n",set1(a,1));//将第一位设置为1,即111为7 printf("%d\n",set1(a,2));//将第2位设置为1,因为第二位本来就是1,所以没变,即101为5 printf("%d\n",set0(a,0));//将第0位设置为0,即100为4 printf("%d\n",set0(a,1));//将第一位设置为0,没变,即101为5 return 0;}]]></content>
<tags>
<tag>c语言</tag>
</tags>
</entry>
<entry>
<title><![CDATA[mysql的zip安装方法]]></title>
<url>%2F2019%2F04%2F26%2Fmysql%E7%9A%84zip%E5%AE%89%E8%A3%85%E6%96%B9%E6%B3%95%2F</url>
<content type="text"><![CDATA[windows安装方法 下载文件先去清华大学镜像站下载文件 找到想要版本的mysql,进去下载对应的zip文件,我下载的是mysql8.0的64位版:mysql-8.0.11-winx64.zip 下载完成后解压到合适的位置,如:C:\mysql-8.0.11 配置解压完后在根目录创建一个名为my.ini的文件,添加以下内容1234567891011121314151617[mysqld]character-set-server=utf8port = 3306# 设置mysql的安装目录basedir=D:\\mysql-8.0.11# 设置mysql数据库的数据的存放目录datadir=D:\\mysql-8.0.11\\datadefault-storage-engine = INNODBcollation-server = utf8_general_ci[mysql]default-character-set=utf8[mysql.server]default-character-set=utf8[mysql_safe]default-character-set=utf8[client]default-character-set=utf8 basedir是解压目录,datadir是mysql存放数据的目录 然后打开电脑的环境变量 新建系统变量MYSQL_HOME=C:\mysql-8.0.11(解压路径) 在path中增加%MYSQL_HOME%\bin 在mysql的bin目录下运行初始化系统命令 1mysqld --initialize 初始化成功后,会在data文件夹下生成一些文件,其中xxx.err文件中说明了root账户的临时密码如 1[Server] A temporary password is generated for root@localhost: JafC,2cE<C# 那么JafC,2cE<C#就是临时密码,一般在第二行就会看到 注册mysql服务 1mysqld -install MySQL 启动mysql服务 1net start MySQL 停止mysql服务1net stop MySQL 先用root和临时密码登录数据库 ubuntu18安装mysql说明:此种方式完全参考官方提供的教程https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/。 注意:通过APT方式安装的版本都是现在最新的版本,现在我安装的是5.7.18。通过这种方式安装好之后开机自启动都已经配置好,和命令行上的环境变量,无需手动配置。 (可省略)下载官方提供的mysql-apt-config.deb包进行APT源设置,下载地址:https://dev.mysql.com/downloads/repo/apt/ 下载了,然后运行sudo dpkg -i xxx.deb 运行这个安装包 第一个确定进去选择5.7,然后选ok 然后运行sudo apt-get update 然后运行sudo apt-get install mysql-server 中间会让你输入密码 如果依赖不足,输入sudo apt-get install -f 打开etc\mysql\mysql.conf.d\mysql.cnf在下面加上1234567891011character-set-server=utf8default-storage-engine = INNODBcollation-server = utf8_general_ci[mysql]default-character-set=utf8[mysql.server]default-character-set=utf8[mysql_safe]default-character-set=utf8[client]default-character-set=utf8 服务管理123456#启动sudo service mysql start#停止sudo service mysql stop#服务状态sudo service mysql status 通用 执行更改新密码 1ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password'; 刷新 1flush privileges; 退出重启服务器用新密码登录即可 改密码1update user set authentication_string = password("new_password") where user='root';]]></content>
<tags>
<tag>mysql</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ajax以及跨域]]></title>
<url>%2F2019%2F04%2F19%2Fajax%E4%BB%A5%E5%8F%8A%E8%B7%A8%E5%9F%9F%2F</url>
<content type="text"><![CDATA[XMLHttpRequest对象:实例化:1var request = new XMLHttpRequest(); 请求: open(method,url,async)请求方法,地址,同步还是异步true是异步,false是同步 send(string)请求发送到服务器,get请求可以不写string参数,post可以写一个参数 setRequestHeader(“Content-type”,”application/x-www-form-urlencoded”); send(“name=王二狗&sex=男”) 获取响应: responseText:获得字符串形式的响应数据 responseXML:获得xml形式的响应数据 status和statusText以数字和文本形式返回http状态码 getAllResponseHeader()获取所有的响应报头 getResponseHeader()查询响应中的某个字段的值 readyState属性:0:请求未初始化,open还没有被调用1:服务器链接已建立,open已经调用了2:请求已接受,也就是接受到头信息了3:请求处理中,也就是接受到响应主题了4:请求已经完成,且响应已经就绪,也就是响应完成了 监听响应过程:12345678910111213var request = new XMLHttpRequest();request.open("GET","url",true);request.send();request.onreadystatechange = function(){ if(request.readyState==4){ if(request.status==200){ //做一些事情,request.responseText } else{ alert(request.status); } }} js解析json:123var jsondata = '{"sites":[{"name":"Runoob", "url":"www.runoob.com"},{"name":"Google", "url":"www.google.com"},{"name":"Taobao", "url":"www.taobao.com"}]}';var jsonobj = JSON.parse(jsondata);alert(jsonobj.sites[0].name) 会弹出sites列表的第一项的name值:Runoob 在线验证json正确性的网站:http://jsonlint.cn/ jQuery实现ajax:一个jquery在线资源库:http://www.bootcdn.cn/all/ jQuery.ajax([settings]) type:类型,”POST”或”GET”,默认为”GET”url :发送请求的地址data:是一个对象,连同请求发送到服务器的数据dataType:预期服务器返回的数据类型。如果不指定,jQuery将自动根据HTTP包MINE信息来智能判断,一般我们采用json格式,可以设置为“json”或者”text”,如果设置为json格式,自动解析成jsonobj success:是一个方法,请求成功后的毁掉函数。传入返回后的数据,以及包含成功代码的字符串 error:是一个方法,请求失败时调用此函数。传入XMLHttpRequest对象123456789101112$.ajax({ type:"GET", url:"Main", dataType:"json", success:function(data){ alert(data.success); }, error:function(jqXHR){ alert("错误码"+jqXHR.status); }})` 返回值是{“success”:true},如果访问成功,则弹出true123456789101112131415$.ajax({ type:"POST", url:"Main", dataType:"json", data:{ name:"as" }, success:function(data){ alert(data.name); }, error:function(jqXHR){ alert("错误码"+jqXHR.status); }})` 服务器会返回{“name”:”得到的name”},如果访问成功,则弹出as 跨域:一个域名组成:协议+子域名+主域名+端口号+请求资源地址 当协议,子域名,主域名,端口号中任何一个不同的时候,就算跨域 js出于安全考虑,不允许跨域调用对象 可以使用xhr2(ie10以下不支持)解决跨域问题: 只需服务端添加两个header为”Access-Control-Allow-Origin:*“和”Access-Control-Allow-Methods:GET,POST” java服务器可以写:12response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Methods", "GET,POST");]]></content>
<tags>
<tag>js</tag>
</tags>
</entry>
<entry>
<title><![CDATA[sublime text3的配置代码]]></title>
<url>%2F2019%2F03%2F19%2Fsublime%20text3%E7%9A%84%E9%85%8D%E7%BD%AE%E4%BB%A3%E7%A0%81%2F</url>
<content type="text"><![CDATA[我习惯的配置代码12345678910111213141516171819{ "auto_find_in_selection": true, "bold_folder_labels": true, "font_face": "consolas", "font_size": 15, "highlight_line": true, "highlight_modified_tabs": true, "hot_exit": false, "ignored_packages": [ "Vintage" ], "remember_open_files": false, "tab_size": 4, "translate_tabs_to_spaces": true, "trim_trailing_white_space_on_save": true, "update_check":false, "word_wrap": true} 配置中文快捷键ctrl + ~ 打开网址https://packagecontrol.io/installation,输入对应的代码 比如text31import urllib.request,os,hashlib; h = '6f4c264a24d933ce70df5dedcf1dcaee' + 'ebe013ee18cced0ef93d5f746d80ef60'; 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) 等反应完 ctrl+p 输入localization就能看到相应的中文汉化插件]]></content>
<tags>
<tag>sublime text</tag>
</tags>
</entry>
<entry>
<title><![CDATA[springboot入门]]></title>
<url>%2F2019%2F03%2F11%2Fspringboot%E5%85%A5%E9%97%A8%2F</url>
<content type="text"><![CDATA[1、Spring Boot 简介 简化Spring应用开发的一个框架;整个Spring技术栈的一个大整合;J2EE开发的一站式解决方案; 2、微服务2014,martin fowler微服务:架构风格(服务微化)一个应用应该是一组小型服务;可以通过HTTP的方式进行互通;单体应用:ALL IN ONE微服务:每一个功能元素最终都是一个可独立替换和独立升级的软件单元;详细参照微服务文档 3、环境准备1、MAVEN设置;给maven 的settings.xml配置文件的profiles标签添加 123456789101112<profile> <id>jdk-1.8</id> <activation> <activeByDefault>true</activeByDefault> <jdk>1.8</jdk> </activation> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> </properties></profile> 2、IDEA设置整合maven进来; 4、Spring Boot HelloWorld一个功能:浏览器发送hello请求,服务器接受请求并处理,响应Hello World字符串; 1、创建一个maven工程;(jar)2、导入spring boot相关的依赖1234567891011<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version></parent><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency></dependencies> 3、编写一个主程序;启动Spring Boot应用123456789101112/** * @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用 */@SpringBootApplicationpublic class HelloWorldMainApplication { public static void main(String[] args) { // Spring应用启动起来 SpringApplication.run(HelloWorldMainApplication.class,args); }} 4、编写相关的Controller、Service 必须和主程序同一个包或者子包中 12345678910@Controllerpublic class HelloController { //访问/hello就直接把字符串返回给浏览器 @ResponseBody @RequestMapping("/hello") public String hello(){ return "Hello World!"; }} @ResponseBody表示这个方法直接把字符串返回给浏览器,如果一个类中所有方法都是这样,也可以把这个注解写到类上,就不用一个个方法写了 5、运行主程序测试 访问http://localhost:8080/hello 6、简化部署 将这个应用打成jar包,直接使用java -jar的命令进行执行; 123456789<!-- 这个插件,可以将应用打包成一个可执行的jar包;--><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build> 加载这个插件后点击idea侧面的maven 点击Lifecycle标签,点击package命令,就可以生成jar 生成的jar在项目的target下的那个以项目名和版本号命名的jar 5、Hello World探究1、POM文件1、父项目1234567891011121314<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version></parent>他的父项目是<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>1.5.9.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath></parent>他来真正管理Spring Boot应用里面的所有依赖版本; Spring Boot的版本仲裁中心; 以后我们导入依赖默认是不需要写版本;(没有在dependencies里面管理的依赖自然需要声明版本号) 2、启动器1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency> spring-boot-starter-==web==: spring-boot-starter:spring-boot场景启动器;帮我们导入了web模块正常运行所依赖的组件; Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器 2、主程序类,主入口类12345678910/** * @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用 */@SpringBootApplicationpublic class HelloWorldMainApplication { public static void main(String[] args) { // Spring应用启动起来 SpringApplication.run(HelloWorldMainApplication.class,args); }} @SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用; 12345678910@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication { @SpringBootConfiguration:Spring Boot的配置类; 标注在某个类上,表示这是一个Spring Boot的配置类; @Configuration:配置类上来标注这个注解; 配置类 —– 配置文件;配置类也是容器中的一个组件;@Component @EnableAutoConfiguration:开启自动配置功能; 以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能;这样自动配置才能生效; 123@AutoConfigurationPackage@Import(EnableAutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { @AutoConfigurationPackage:自动配置包 @Import(AutoConfigurationPackages.Registrar.class): Spring的底层注解@Import,给容器中导入一个组件;导入的组件由AutoConfigurationPackages.Registrar.class; ==将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;== @Import(EnableAutoConfigurationImportSelector.class); 给容器中导入组件? EnableAutoConfigurationImportSelector:导入哪些组件的选择器; 将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中; 会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件; 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作; SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,classLoader); ==Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;==以前我们需要自己配置的东西,自动配置类都帮我们; J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-1.5.9.RELEASE.jar; 使用Spring Initializer快速创建Spring Boot项目1、IDEA:使用 Spring Initializer快速创建项目IDE都支持使用Spring的项目创建向导快速创建一个Spring Boot项目; 选择我们需要的模块;向导会联网创建Spring Boot项目; 默认生成的Spring Boot项目; 主程序已经生成好了,我们只需要我们自己的逻辑 resources文件夹中目录结构 static:保存所有的静态资源; js css images; templates:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf); application.properties:Spring Boot应用的配置文件;可以修改一些默认设置; 二、配置文件1、配置文件SpringBoot使用一个全局的配置文件,配置文件名是固定的;•application.properties•application.yml配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好; YAML(YAML Ain’t Markup Language) YAML A Markup Language:是一个标记语言 YAML isn’t Markup Language:不是一个标记语言;标记语言: 以前的配置文件;大多都使用的是 xxxx.xml文件; YAML:以数据为中心,比json、xml等更适合做配置文件; YAML:配置例子 12server: port: 8081 XML:123<server> <port>8081</port></server> 2、YAML语法:1、基本语法k:(空格)v:表示一对键值对(空格必须有); 以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的 123server: port: 8081 path: /hello 属性和值也是大小写敏感; 2、值的写法字面量:普通的值(数字,字符串,布尔) k: v:字面直接来写; 字符串默认不用加上单引号或者双引号; “”:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思 name: “zhangsan \n lisi”:输出;zhangsan 换行 lisi ‘’:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据 name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi 对象、Map(属性和值)(键值对): k: v:在下一行来写对象的属性和值的关系;注意缩进 对象还是k: v的方式 123friends: lastName: zhangsan age: 20 行内写法: 1friends: {lastName: zhangsan,age: 18} 数组(List、Set):用- 值表示数组中的一个元素 1234pets: - cat - dog - pig 行内写法 1pets: [cat,dog,pig] 3、配置文件值注入配置文件123456789101112person: lastName: hello age: 18 boss: false birth: 2017/12/12 maps: {k1: v1,k2: 12} lists: - lisi - zhaoliu dog: name: 小狗 age: 12 javaBean:1234567891011121314151617181920212223242526/** * 将配置文件中配置的每一个属性的值,映射到这个组件中 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定; * prefix = "person":配置文件中哪个下面的所有属性进行一一映射 * * 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能; * */@Component@ConfigurationProperties(prefix = "person")public class Person { private String lastName; private Integer age; private Boolean boss; private Date birth; private Map<String,Object> maps; private List<Object> lists; private Dog dog; //..getter&setter}public class Dog{ private String name; private int age;} Component表示注入到spring,只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能; ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;prefix = “person”:表示和配置文件中名为person的值进行绑定 我们可以导入配置文件处理器,以后编写配置就有提示了 123456<!--导入配置文件处理器,配置文件进行绑定就会有提示--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> 测试代码123456@AutowiredPerson person;@Testpublic void contextLoads() { System.out.println(person);} Autowired表示从容器中取出person对象 上述结果即可输出在yml文件中配置的person对象的值 使用properties配置文件配置12345678910#配置person1person1.lastName=是我person1.age=18person1.boss=falseperson1.birth=2017/12/12person1.maps.k1=v1person1.maps.k2=l2person1.lists=lisi,zhaoliuperson1.dog.name=小狗狗person1.dog.age=12 记得修改Person类上绑定的名称为person1 注意这种map的写法 但是这种方法会乱码 1、properties配置文件在idea中默认utf-8可能会乱码调整 使用@Value获取配置的值12345678910111213141516171819@Component@ConfigurationProperties(prefix = "person")public class Person { /** * <bean class="Person"> * <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> * <bean/> */ @Value("${person.last-name}") private String lastName; @Value("#{11*2}") private Integer age; @Value("true") private Boolean boss; private Date birth; private Map<String,Object> maps; private List<Object> lists; private Dog dog; @Value支持:字面量/${key}从环境变量、配置文件中获取值/#{SpEL} 2、@Value获取值和@ConfigurationProperties获取值比较 @ConfigurationProperties @Value 功能 批量注入配置文件中的属性 一个个指定 松散绑定(松散语法) 支持 不支持 SpEL 不支持 支持 JSR303数据校验 支持 不支持 复杂类型封装 支持 不支持 配置文件yml还是properties他们都能获取到值; 如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value; 如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties; 3、配置文件注入值数据校验123456789101112@Component@ConfigurationProperties(prefix = "person")@Validatedpublic class Person { /** * <bean class="Person"> * <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> * <bean/> */ //lastName必须是邮箱格式 @Email private String lastName; 在类上加注解Validated 在需要验证的值上添加相应的标签,例如@Email 此方法必须是使用ConfigurationProperties绑定值,@Value绑定不会验证 4、@PropertySource&@ImportResource&@Bean@PropertySource:加载指定的配置文件; 1234567891011121314151617181920212223242526272829/** * 将配置文件中配置的每一个属性的值,映射到这个组件中 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定; * prefix = "person":配置文件中哪个下面的所有属性进行一一映射 * * 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能; * @ConfigurationProperties(prefix = "person")默认从全局配置文件中获取值; * */@PropertySource(value = {"classpath:person.properties"})@Component@ConfigurationProperties(prefix = "person")//@Validatedpublic class Person { /** * <bean class="Person"> * <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> * <bean/> */ //lastName必须是邮箱格式 // @Email //@Value("${person.last-name}") private String lastName; //@Value("#{11*2}") private Integer age; //@Value("true") private Boolean boss; @ImportResource:导入Spring的配置文件,让配置文件里面的内容生效; Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别; 想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上,可以加载到main方法的配置类上 12@ImportResource(locations = {"classpath:beans.xml"})导入Spring的配置文件让其生效 不来编写Spring的配置文件 12345678<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="helloService" class="com.atguigu.springboot.service.HelloService"></bean></beans> 注解配置bean的方式SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式 1、配置类@Configuration——>Spring配置文件 2、使用@Bean给容器中添加组件 12345678910111213141516/** * @Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件 * * 在配置文件中用<bean><bean/>标签添加组件 * */@Configurationpublic class MyAppConfig { //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名 @Bean public HelloService helloService02(){ System.out.println("配置类@Bean给容器中添加组件了..."); return new HelloService(); }} 在容器中的id是方法名 ##4、配置文件占位符 1、随机数12${random.value}、${random.int}、${random.long}${random.int(10)}、${random.int[1024,65536]} 2、占位符获取之前配置的值,如果没有可以是用:指定默认值123456789person.last-name=张三${random.uuid}person.age=${random.int}person.birth=2017/12/15person.boss=falseperson.maps.k1=v1person.maps.k2=14person.lists=a,b,cperson.dog.name=${person.hello:hello}_dogperson.dog.age=15 5、Profile1、多Profile文件我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml 默认使用application.properties的配置; 2、yml支持多文档块方式1234567891011121314151617181920server: port: 8081spring: profiles: active: prod #激活prod环境---server: port: 8083spring: profiles: dev---server: port: 8084spring: profiles: prod #指定属于哪个环境 3、激活指定profile 1、在配置文件中指定 spring.profiles.active=dev 2、命令行: java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar –spring.profiles.active=dev; 可以直接在测试的时候,配置传入命令行参数 3、虚拟机参数; -Dspring.profiles.active=dev 配置文件加载位置springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件 –file:./config/ –file:./ –classpath:/config/ –classpath:/ 优先级由高到底,高优先级的配置会覆盖低优先级的配置; SpringBoot会从这四个位置全部加载主配置文件;互补配置; ==我们还可以通过spring.config.location来改变默认的配置文件位置== 项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置; java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar –spring.config.location=G:/application.properties 外部配置加载顺序==SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置== 1.命令行参数 所有的配置都可以在命令行上进行指定 java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar –server.port=8087 –server.context-path=/abc 多个配置用空格分开; –配置项=值 2.来自java:comp/env的JNDI属性 3.Java系统属性(System.getProperties()) 4.操作系统环境变量 5.RandomValuePropertySource配置的random.*属性值 ==由jar包外向jar包内进行寻找;== ==优先加载带profile== 6.jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件 7.jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件 ==再来加载不带profile== 8.jar包外部的application.properties或application.yml(不带spring.profile)配置文件 9.jar包内部的application.properties或application.yml(不带spring.profile)配置文件 10.@Configuration注解类上的@PropertySource 11.通过SpringApplication.setDefaultProperties指定的默认属性 所有支持的配置加载来源; 参考官方文档 8、自动配置原理配置文件到底能写什么?怎么写?自动配置原理; 配置文件能配置的属性参照 1、自动配置原理:1)、SpringBoot启动的时候加载主配置类,开启了自动配置功能 ==@EnableAutoConfiguration== 2)、@EnableAutoConfiguration 作用: 利用EnableAutoConfigurationImportSelector给容器中导入一些组件? 可以查看selectImports()方法的内容; List configurations = getCandidateConfigurations(annotationMetadata, attributes);获取候选的配置 1234SpringFactoriesLoader.loadFactoryNames()扫描所有jar包类路径下 META-INF/spring.factories把扫描到的这些文件的内容包装成properties对象从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中 ==将 类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;== 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration 每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置; 3)、每一个自动配置类进行自动配置功能; 4)、以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理; 12345678910111213141516171819202122232425262728@Configuration //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件@EnableConfigurationProperties(HttpEncodingProperties.class) //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中@ConditionalOnWebApplication //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效@ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final HttpEncodingProperties properties; //只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) { this.properties = properties; } @Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件? public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE)); return filter; } 根据当前不同的条件判断,决定这个配置类是否生效? 一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的; 5)、所有在配置文件中能配置的属性都是在xxxxProperties类中封装者‘;配置文件能配置什么就可以参照某个功能对应的这个属性类 1234@ConfigurationProperties(prefix = "spring.http.encoding") //从配置文件中获取指定的值和bean的属性进行绑定public class HttpEncodingProperties { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); 精髓: 1)、SpringBoot启动会加载大量的自动配置类 2)、我们看我们需要的功能有没有SpringBoot默认写好的自动配置类; 3)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了) 4)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值; xxxxAutoConfigurartion:自动配置类; 给容器中添加组件 xxxxProperties:封装配置文件中相关属性; 2、细节1、@Conditional派生注解(Spring注解版原生的@Conditional作用)作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效; @Conditional扩展注解 作用(判断是否满足当前指定条件) @ConditionalOnJava 系统的java版本是否符合要求 @ConditionalOnBean 容器中存在指定Bean; @ConditionalOnMissingBean 容器中不存在指定Bean; @ConditionalOnExpression 满足SpEL表达式指定 @ConditionalOnClass 系统中有指定的类 @ConditionalOnMissingClass 系统中没有指定的类 @ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean @ConditionalOnProperty 系统中指定的属性是否有指定的值 @ConditionalOnResource 类路径下是否存在指定资源文件 @ConditionalOnWebApplication 当前是web环境 @ConditionalOnNotWebApplication 当前不是web环境 @ConditionalOnJndi JNDI存在指定项 自动配置类必须在一定的条件下才能生效; 我们怎么知道哪些自动配置类生效; ==我们可以通过启用 debug=true属性;来让控制台打印自动配置报告==,这样我们就可以很方便的知道哪些自动配置类生效; 1234567891011121314151617181920212223=========================AUTO-CONFIGURATION REPORT=========================Positive matches:(自动配置类启用的)----------------- DispatcherServletAutoConfiguration matched: - @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) - @ConditionalOnWebApplication (required) found StandardServletEnvironment (OnWebApplicationCondition)Negative matches:(没有启动,没有匹配成功的自动配置类)----------------- ActiveMQAutoConfiguration: Did not match: - @ConditionalOnClass did not find required classes 'javax.jms.ConnectionFactory', 'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition) AopAutoConfiguration: Did not match: - @ConditionalOnClass did not find required classes 'org.aspectj.lang.annotation.Aspect', 'org.aspectj.lang.reflect.Advice' (OnClassCondition) 三、日志1、日志框架市面上的日志框架; JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j…. 日志门面 (日志的抽象层) 日志实现 JCL(Jakarta Commons Logging) SLF4j(Simple Logging Facade for Java) jboss-logging Log4j JUL(java.util.logging) Log4j2 Logback 左边选一个门面(抽象层)、右边来选一个实现; 日志门面: SLF4J; 日志实现:Logback; SpringBoot:底层是Spring框架,Spring框架默认是用JCL;‘ ==SpringBoot选用 SLF4j和logback;== 2、SLF4j使用1、如何在系统中使用SLF4j https://www.slf4j.org以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法; 给系统里面导入slf4j的jar和 logback的实现jar 123456789import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info("Hello World"); }} 图示; 每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件; 2、遗留问题a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx 统一日志记录,即使是别的框架和我一起统一使用slf4j进行输出? 如何让系统中所有的日志都统一到slf4j; ==1、将系统中其他日志框架先排除出去;== ==2、用中间包来替换原有的日志框架;== ==3、我们导入slf4j其他的实现== 3、SpringBoot日志关系1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId></dependency> SpringBoot使用它来做日志功能; 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> 底层依赖关系 总结: 1)、SpringBoot底层也是使用slf4j+logback的方式进行日志记录 2)、SpringBoot也把其他的日志都替换成了slf4j; 3)、中间替换包? 123456@SuppressWarnings("rawtypes")public abstract class LogFactory { static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = "http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j"; static LogFactory logFactory = new SLF4JLogFactory(); 4)、如果我们要引入其他框架?一定要把这个框架的默认日志依赖移除掉? Spring框架用的是commons-logging; 12345678910<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions></dependency> exclusions表示虽然引入spring-core,但是移除commons-logging ==SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;== 4、日志使用;1、默认配置123456789101112131415//记录器Logger logger = LoggerFactory.getLogger(getClass());@Testpublic void contextLoads() { //System.out.println(); //日志的级别; //由低到高 trace<debug<info<warn<error //可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效 logger.trace("这是trace日志..."); logger.debug("这是debug日志..."); //SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;root级别 logger.info("这是info日志..."); logger.warn("这是warn日志..."); logger.error("这是error日志...");} 日志输出格式: %d表示日期时间, %thread表示线程名, %-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息, %n是换行符 --> %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n SpringBoot修改日志的默认配置 application.properties123456789101112131415logging.level.com.atguigu=trace#logging.path=# 不指定路径在当前项目下生成springboot.log日志# 可以指定完整的路径;#logging.file=G:/springboot.log# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件logging.path=/spring/log# 在控制台输出的日志的格式logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n# 指定文件中日志输出的格式logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n logging.file logging.path Example Description (none) (none) 只在控制台输出 指定文件名 (none) my.log 输出日志到my.log文件 (none) 指定目录 /var/log 输出到指定目录的 spring.log 文件中 2、指定配置 SpringBoot默认帮我们配置好了日志,位置是org.springframework.boot.logging包下,对应不同的日志系统有不同的默认配置 给resource类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了 Logging System Customization Logback logback-spring.xml, logback-spring.groovy, logback.xml or logback.groovy Log4j2 log4j2-spring.xml or log4j2.xml JDK (Java Util Logging) logging.properties logback.xml:直接就被日志框架识别了; logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能 1234<springProfile name="staging"> <!-- configuration to be enabled when the "staging" profile is active --> 可以指定某段配置只在某个环境下生效</springProfile> 如:12345678910<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <springProfile name="dev"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern> </springProfile> <springProfile name="!dev"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n</pattern> </springProfile> </layout> </appender> 指定在dev环境和不是dev环境输出不同格式的日志 如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误 no applicable action for [springProfile] 5、切换日志框架可以按照slf4j的日志适配图,进行相关的切换; slf4j+log4j的方式; 12345678910111213141516171819<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>logback-classic</artifactId> <groupId>ch.qos.logback</groupId> </exclusion> <exclusion> <artifactId>log4j-over-slf4j</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId></dependency> 切换为log4j2 123456789101112131415 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-logging</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId></dependency> 四、Web开发1、简介使用SpringBoot; 1)、创建SpringBoot应用,选中我们需要的模块; 2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来 3)、自己编写业务代码; 自动配置原理? 这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx 12xxxxAutoConfiguration:帮我们给容器中自动配置组件;xxxxProperties:配置类来封装配置文件的内容; 2、SpringBoot对静态资源的映射规则;123@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)public class ResourceProperties implements ResourceLoaderAware { //可以设置和静态资源有关的参数,缓存时间等 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364WebMvcAuotConfiguration: @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } Integer cachePeriod = this.resourceProperties.getCachePeriod(); if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration( registry.addResourceHandler("/webjars/**") .addResourceLocations( "classpath:/META-INF/resources/webjars/") .setCachePeriod(cachePeriod)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern(); //静态资源文件夹映射 if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration( registry.addResourceHandler(staticPathPattern) .addResourceLocations( this.resourceProperties.getStaticLocations()) .setCachePeriod(cachePeriod)); } } //配置欢迎页映射 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping( ResourceProperties resourceProperties) { return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(), this.mvcProperties.getStaticPathPattern()); } //配置喜欢的图标 @Configuration @ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true) public static class FaviconConfiguration { private final ResourceProperties resourceProperties; public FaviconConfiguration(ResourceProperties resourceProperties) { this.resourceProperties = resourceProperties; } @Bean public SimpleUrlHandlerMapping faviconHandlerMapping() { SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); //所有 **/favicon.ico mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler())); return mapping; } @Bean public ResourceHttpRequestHandler faviconRequestHandler() { ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); requestHandler .setLocations(this.resourceProperties.getFaviconLocations()); return requestHandler; } } ==1)、所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;== webjars:以jar包的方式引入静态资源; http://www.webjars.org/ localhost:8080/webjars/jquery/3.3.1/jquery.js 123456<!--引入jquery-webjar-->在访问的时候只需要写webjars下面资源的名称即可 <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.1</version> </dependency> ==2)、”/**” 访问当前项目的任何资源,都去(静态资源的文件夹)找映射== 12345"classpath:/META-INF/resources/","classpath:/resources/","classpath:/static/","classpath:/public/""/":当前项目的根路径 localhost:8080/abc === 去静态资源文件夹里面找abc ==3)、欢迎页; 静态资源文件夹下的所有index.html页面;被”/**”映射;== localhost:8080/ 找index页面 ==4)、所有的 **/favicon.ico 都是在静态资源文件下找;== 3、模板引擎JSP、Velocity、Freemarker、Thymeleaf SpringBoot推荐的Thymeleaf; 语法更简单,功能更强大; 1、引入thymeleaf;1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency> 2、Thymeleaf使用1234567891011@ConfigurationProperties(prefix = "spring.thymeleaf")public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8"); private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html"); public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; // 从上图可知道默认路径在classpath:/templates/,默认后缀名是.html,只要把对应的html放入那个文件夹,thymeleaf就能自动渲染 简单使用 导入依赖(上面) 在templates文件夹下创建要给hh.html 123...<h1>成功</h1>... 创建controller 12345678@Controllerpublic class HelloController { @RequestMapping("/abc") public String hello(){ //调用模板中的hh.html return "hh"; }} 访问http://localhost:8080/abc即可看到hh.html上的页面 可以看出没有加入ResponseBody注解后会自动当成模板渲染 高级使用 导入thymeleaf的名称空间(导入后有语法提示) 1<html lang="en" xmlns:th="http://www.thymeleaf.org"> 修改controller 123456789@Controllerpublic class HelloController { @RequestMapping("/abc") public String hello(Map<String,String> map) { map.put("hello","你好"); return "hh"; }} 使用thymeleaf语法取出hello的值 123456789101112<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Title</title></head><body> <h1>成功!</h1> <!--th:text 将div里面的文本内容设置为 --> <div th:text="${hello}">这是显示欢迎信息</div></body></html> 效果是第一行成功,第二行只有一个你好,因为thymeleaf吧那个div里面的文本换成了hello对应的文本 3、语法规则1)、th:text;改变当前元素里面的文本内容; th:任意html属性;来替换原生属性的值 2)、表达式? 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869Simple expressions:(表达式语法) Variable Expressions: ${...}:获取变量值;OGNL; 1)、获取对象的属性、调用方法 2)、使用内置的基本对象: #ctx : the context object. #vars: the context variables. #locale : the context locale. #request : (only in Web Contexts) the HttpServletRequest object. #response : (only in Web Contexts) the HttpServletResponse object. #session : (only in Web Contexts) the HttpSession object. #servletContext : (only in Web Contexts) the ServletContext object. ${session.foo} 3)、内置的一些工具对象:#execInfo : information about the template being processed.#messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.#uris : methods for escaping parts of URLs/URIs#conversions : methods for executing the configured conversion service (if any).#dates : methods for java.util.Date objects: formatting, component extraction, etc.#calendars : analogous to #dates , but for java.util.Calendar objects.#numbers : methods for formatting numeric objects.#strings : methods for String objects: contains, startsWith, prepending/appending, etc.#objects : methods for objects in general.#bools : methods for boolean evaluation.#arrays : methods for arrays.#lists : methods for lists.#sets : methods for sets.#maps : methods for maps.#aggregates : methods for creating aggregates on arrays or collections.#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration). Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样; 补充:配合 th:object="${session.user}: <div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div> Message Expressions: #{...}:获取国际化内容 Link URL Expressions: @{...}:定义URL; @{/order/process(execId=${execId},execType='FAST')} Fragment Expressions: ~{...}:片段引用表达式 <div th:insert="~{commons :: main}">...</div>Literals(字面量) Text literals: 'one text' , 'Another one!' ,… Number literals: 0 , 34 , 3.0 , 12.3 ,… Boolean literals: true , false Null literal: null Literal tokens: one , sometext , main ,…Text operations:(文本操作) String concatenation: + Literal substitutions: |The name is ${name}|Arithmetic operations:(数学运算) Binary operators: + , - , * , / , % Minus sign (unary operator): -Boolean operations:(布尔运算) Binary operators: and , or Boolean negation (unary operator): ! , notComparisons and equality:(比较运算) Comparators: > , < , >= , <= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne )Conditional operators:条件运算(三元运算符) If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue)Special tokens: No-Operation: _ 例子Controller12345678910@Controllerpublic class HelloController { @RequestMapping("/abc") public String hello(Map<String,Object> map) { map.put("hello","<h1>你好</h1>"); map.put("list", Arrays.asList("张三","李四","王五")); return "hh"; }} htmlhh.html123456789101112131415161718<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p th:text="${hello}"></p> <p th:utext="${hello}"></p> <!-- 每次遍历都会生成一个h4标签--> <h4 th:each="user:${list}" th:text="${user}"></h4> <h4> <span th:each="user:${list}">[[${user}]]</span> </h4> </body></html> 结果 4、SpringMVC自动配置https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications 1. Spring MVC auto-configurationSpring Boot 自动配置好了SpringMVC 以下是SpringBoot对SpringMVC的默认配置:==(WebMvcAutoConfiguration)== Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans. 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?)) ContentNegotiatingViewResolver:组合所有的视图解析器的; ==如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;== Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars Static index.html support. 静态首页访问 Custom Favicon support (see below). favicon.ico 自动注册了 of Converter, GenericConverter, Formatter beans. Converter:转换器; public String hello(User user):类型转换使用Converter Formatter 格式化器; 2017.12.17===Date; 12345@Bean@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")//在文件中配置日期格式化的规则public Formatter<Date> dateFormatter() { return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件} ==自己添加的格式化器转换器,我们只需要放在容器中即可== Support for HttpMessageConverters (see below). HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User—Json; HttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverter; ==自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中(@Bean,@Component)== Automatic registration of MessageCodesResolver (see below).定义错误代码生成规则 Automatic use of a ConfigurableWebBindingInitializer bean (see below). ==我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)== 12初始化WebDataBinder;请求数据=====JavaBean; org.springframework.boot.autoconfigure.web:web的所有自动场景; If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver you can declare a WebMvcRegistrationsAdapter instance providing such components. If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc. 2、扩展SpringMVC1234567<mvc:view-controller path="/hello" view-name="success"/><mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/hello"/> <bean></bean> </mvc:interceptor></mvc:interceptors> ==编写一个配置类(@Configuration),是实现WebMvcConfigurer;不能标注@EnableWebMvc==; 既保留了所有的自动配置,也能用我们扩展的配置; 123456789//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/at").setViewName("hh"); }} 表示访问/at就会映射到hh.html,会被模板解析 原理: 1)、WebMvcAutoConfiguration是SpringMVC的自动配置类 2)、在做其他自动配置时会导入;@Import(EnableWebMvcConfiguration.class) 123456789101112131415161718@Configurationpublic static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); //从容器中获取所有的WebMvcConfigurer @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); //一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用; @Override // public void addViewControllers(ViewControllerRegistry registry) { // for (WebMvcConfigurer delegate : this.delegates) { // delegate.addViewControllers(registry); // } } }} 3)、容器中所有的WebMvcConfigurer都会一起起作用; 4)、我们的配置类也会被调用; 效果:SpringMVC的自动配置和我们的扩展配置都会起作用; 3、全面接管SpringMVC;SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了 我们需要在配置类中添加@EnableWebMvc即可; 123456789//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能@EnableWebMvc@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/at").setViewName("hh"); }} 原理: 为什么@EnableWebMvc自动配置就失效了; 1)@EnableWebMvc的核心 12@Import(DelegatingWebMvcConfiguration.class)public @interface EnableWebMvc { 2)、 12@Configurationpublic class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { 3)、 12345678910@Configuration@ConditionalOnWebApplication@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class })//容器中没有这个组件的时候,这个自动配置类才生效@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration { 4)、@EnableWebMvc将WebMvcConfigurationSupport组件导入进来; 5)、导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能; 5、如何修改SpringBoot的默认配置模式: 1)、SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来; 2)、在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置 3)、在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置 6、RestfulCRUD1)、默认访问首页1234567891011121314151617181920//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能//@EnableWebMvc 不要接管SpringMVC@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/at").setViewName("hh"); } @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void addViewControllers(ViewControllerRegistry registry) { //首页默认转到hh视图 registry.addViewController("/").setViewName("hh"); } }; }} 2)、国际化1)、编写国际化配置文件; 2)、使用ResourceBundleMessageSource管理国际化资源文件 3)、在页面使用fmt:message取出国际化内容 步骤: 1)、编写国际化配置文件,抽取页面需要显示的国际化消息 2)、SpringBoot自动配置好了管理国际化资源文件的组件; 123456789101112131415161718192021@ConfigurationProperties(prefix = "spring.messages")public class MessageSourceAutoConfiguration { private String basename = "messages"; //我们的配置文件可以直接放在类路径下叫messages.properties; @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); if (StringUtils.hasText(this.basename)) { //设置国际化资源文件的基础名(去掉语言国家代码的) messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(this.basename))); } if (this.encoding != null) { messageSource.setDefaultEncoding(this.encoding.name()); } messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale); messageSource.setCacheSeconds(this.cacheSeconds); messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat); return messageSource; } 可以在配置文件中配置spring.messages的值指定国际化值的地址 比如上图中配置在了resource的i18n的下的login.properties,那么就在application.properties配置1spring.messages.basename=i18n.login 3)、去页面获取国际化的值; 12345678910111213141516171819202122232425262728293031323334<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>Signin Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet"> <!-- Custom styles for this template --> <link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet"> </head> <body class="text-center"> <form class="form-signin" action="dashboard.html"> <img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72"> <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1> <label class="sr-only" th:text="#{login.username}">Username</label> <input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus=""> <label class="sr-only" th:text="#{login.password}">Password</label> <input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required=""> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"/> [[#{login.remember}]] </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button> <p class="mt-5 mb-3 text-muted">© 2017-2018</p> <a class="btn btn-sm">中文</a> <a class="btn btn-sm">English</a> </form> </body></html> 在页面中通过th:text=”#{…}”或者[[#{…}]]的方式获取国际化的值,比如#{login.btn} 效果:根据浏览器语言设置的信息切换了国际化; 原理: 国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象); 12345678910111213 @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver() { if (this.mvcProperties .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; }默认的就是根据请求头带来的区域信息获取Locale进行国际化 自定义点击链接国际化123456789101112131415161718192021222324252627/** * 可以在链接上携带区域信息 * 比如localhost:8080/index?h=en_US */public class MyLocaleResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { String l = request.getParameter("l"); Locale locale = Locale.getDefault(); if(!StringUtils.isEmpty(l)){ String[] split = l.split("_"); locale = new Locale(split[0],split[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { }//在config配置的Java中添加自己写的国际化解析器 @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); }} 通过连接中l的值来切换区域信息 3)、登陆开发期间模板引擎页面修改以后,要实时生效 1)、禁用模板引擎的缓存 12# 禁用缓存spring.thymeleaf.cache=false 2)、页面修改完成以后ctrl+f9:重新编译; 登录页面12345678910<body> <!--登陆错误消息的显示--> <p style="color: #c80000;" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p> <form action="/loginController" method="post"> <input type="text" name="username"/> <input type="password" name="password"/> <input type="submit"/> </form></body> 登陆的controller1234567891011//@RequestMapping(value = "/login" ,method = RequestMethod.POST)@PostMapping("/loginController")public String login(@RequestParam String username, @RequestParam String password, Map<String,Object> map){ if(!StringUtils.isEmpty(username)&&!StringUtils.isEmpty(password)&&"123456".equals(password)){ return "redirect:/main"; } else{ map.put("msg","用户名密码错误"); } return "login";} requestMapping后method设置为Post等同于直接用PostMapping,get,delete等同理当用户输入正确后那个redirect表示重定向到main指定的试图 配置视图12345678@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/main").setViewName("hh"); registry.addViewController("/login").setViewName("login"); }} 有一个问题,不登录可以直接访问main,所以要用到解析器 4)、拦截器进行登陆检查拦截器12345678910111213141516/** * 登陆检查, */public class LoginHandlerInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute("loginUser"); if(user==null){ request.setAttribute("msg","没有权限登陆"); request.getRequestDispatcher("login").forward(request,response); return false; }else{ return true; } }} 注册拦截器12345678 //所有的WebMvcConfigurerAdapter组件都会一起起作用@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/login","/loginController"); }} 表示注册拦截器,并且拦截所有url,但是除了/login和/loginController SpringBoot已经做好了静态资源映射,所以不用做静态资源的拦截,比如css,js 在Controller中添加session1234567891011@PostMapping("/loginController")public String login(@RequestParam String username, @RequestParam String password, Map<String,Object> map, HttpSession session){ if(!StringUtils.isEmpty(username)&&!StringUtils.isEmpty(password)&&"123456".equals(password)){ session.setAttribute("loginUser",username); return "redirect:/main"; } else{ map.put("msg","用户名密码错误"); } return "login";} 登录成功获取用户名1<h1>[[${session.loginUser}]]</h1> 可以在模板中直接调用session.loginUser获取保存在session中的值 5)、CRUD-员工列表实验要求: 1)、RestfulCRUD:CRUD满足Rest风格; URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作 普通CRUD(uri来区分操作) RestfulCRUD 查询 getEmp emp—GET 添加 addEmp?xxx emp—POST 修改 updateEmp?id=xxx&xxx=xx emp/{id}—PUT 删除 deleteEmp?id=1 emp/{id}—DELETE 2)、实验的请求架构; 实验功能 请求URI 请求方式 查询所有员工 emps GET 查询某个员工(来到修改页面) emp/1 GET 来到添加页面 emp GET 添加员工 emp POST 来到修改页面(查出员工进行信息回显) emp/1 GET 修改员工 emp PUT 删除员工 emp/1 DELETE 3)、员工列表: thymeleaf公共页面元素抽取12345678910111213141、抽取公共片段<div th:fragment="copy">&copy; 2011 The Good Thymes Virtual Grocery</div>2、引入公共片段<div th:insert="~{footer :: copy}"></div>~{templatename::selector}:模板名::选择器~{templatename::fragmentname}:模板名::片段名3、默认效果:insert的公共片段在div标签中如果使用th:insert等属性进行引入,可以不用写~{}:行内写法可以加上:[[~{}]];[(~{})]; 三种引入公共片段的th属性: th:insert:将公共片段整个插入到声明引入的元素中 th:replace:将声明引入的元素替换为公共片段 th:include:将被引入的片段的内容包含进这个标签中 1234567891011121314151617181920212223<footer th:fragment="copy">&copy; 2011 The Good Thymes Virtual Grocery</footer>引入方式<div th:insert="footer :: copy"></div><div th:replace="footer :: copy"></div><div th:include="footer :: copy"></div>效果<div> <footer> &copy; 2011 The Good Thymes Virtual Grocery </footer></div><footer>&copy; 2011 The Good Thymes Virtual Grocery</footer><div>&copy; 2011 The Good Thymes Virtual Grocery</div> 引入片段的时候传入参数: 123456789101112131415161718<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar"> <div class="sidebar-sticky"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link active" th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}" href="#" th:href="@{/main.html}"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"> <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path> <polyline points="9 22 9 12 15 12 15 22"></polyline> </svg> Dashboard <span class="sr-only">(current)</span> </a> </li><!--引入侧边栏;传入参数--><div th:replace="commons/bar::#sidebar(activeUri='emps')"></div> 6)、CRUD-员工添加添加页面 123456789101112131415161718192021222324252627282930313233343536<form> <div class="form-group"> <label>LastName</label> <input type="text" class="form-control" placeholder="zhangsan"> </div> <div class="form-group"> <label>Email</label> <input type="email" class="form-control" placeholder="[email protected]"> </div> <div class="form-group"> <label>Gender</label><br/> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="1"> <label class="form-check-label">男</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="0"> <label class="form-check-label">女</label> </div> </div> <div class="form-group"> <label>department</label> <select class="form-control"> <option>1</option> <option>2</option> <option>3</option> <option>4</option> <option>5</option> </select> </div> <div class="form-group"> <label>Birth</label> <input type="text" class="form-control" placeholder="zhangsan"> </div> <button type="submit" class="btn btn-primary">添加</button></form> 提交的数据格式不对:生日:日期; 2017-12-12;2017/12/12;2017.12.12; 日期的格式化;SpringMVC将页面提交的值需要转换为指定的类型; 2017-12-12—Date; 类型转换,格式化; 默认日期是按照/的方式; 7)、CRUD-员工修改修改添加二合一表单 123456789101112131415161718192021222324252627282930313233343536373839404142<!--需要区分是员工修改还是添加;--><form th:action="@{/emp}" method="post"> <!--发送put请求修改员工数据--> <!--1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)2、页面创建一个post表单3、创建一个input项,name="_method";值就是我们指定的请求方式--> <input type="hidden" name="_method" value="put" th:if="${emp!=null}"/> <input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}"> <div class="form-group"> <label>LastName</label> <input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}"> </div> <div class="form-group"> <label>Email</label> <input name="email" type="email" class="form-control" placeholder="[email protected]" th:value="${emp!=null}?${emp.email}"> </div> <div class="form-group"> <label>Gender</label><br/> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}"> <label class="form-check-label">男</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}"> <label class="form-check-label">女</label> </div> </div> <div class="form-group"> <label>department</label> <!--提交的是部门的id--> <select class="form-control" name="department.id"> <option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option> </select> </div> <div class="form-group"> <label>Birth</label> <input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"> </div> <button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button></form> 8)、CRUD-员工删除123456789101112131415161718192021<tr th:each="emp:${emps}"> <td th:text="${emp.id}"></td> <td>[[${emp.lastName}]]</td> <td th:text="${emp.email}"></td> <td th:text="${emp.gender}==0?'女':'男'"></td> <td th:text="${emp.department.departmentName}"></td> <td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td> <td> <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a> <button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button> </td></tr><script> $(".deleteBtn").click(function(){ //删除当前员工的 $("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit(); return false; });</script> 7、错误处理机制1)、SpringBoot默认的错误处理机制默认效果: 1)、浏览器,返回一个默认的错误页面 浏览器发送请求的请求头: 2)、如果是其他客户端,默认响应一个json数据 原理: 可以参照ErrorMvcAutoConfiguration;错误处理的自动配置; 给容器中添加了以下组件 1、DefaultErrorAttributes: 12345678910帮我们在页面共享信息;@Overridepublic Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, requestAttributes); addErrorDetails(errorAttributes, requestAttributes, includeStackTrace); addPath(errorAttributes, requestAttributes); return errorAttributes;} 2、BasicErrorController:处理默认/error请求 12345678910111213141516171819202122232425@Controller@RequestMapping("${server.error.path:${error.path:/error}}")public class BasicErrorController extends AbstractErrorController { @RequestMapping(produces = "text/html")//产生html类型的数据;浏览器发送的请求来到这个方法处理 public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes( request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); //去哪个页面作为错误页面;包含页面地址和页面内容 ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView == null ? new ModelAndView("error", model) : modelAndView); } @RequestMapping @ResponseBody //产生json数据,其他客户端来到这个方法处理; public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity<Map<String, Object>>(body, status); } 3、ErrorPageCustomizer: 12@Value("${error.path:/error}")private String path = "/error"; 系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则) 4、DefaultErrorViewResolver: 123456789101112131415161718192021222324@Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { //默认SpringBoot可以去找到一个页面? error/404 String errorViewName = "error/" + viewName; //模板引擎可以解析这个页面地址就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext); if (provider != null) { //模板引擎可用的情况下返回到errorViewName指定的视图地址 return new ModelAndView(errorViewName, model); } //模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html return resolveResource(errorViewName, model); } 步骤: 一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理; 1)响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的; 1234567891011protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { //所有的ErrorViewResolver得到ModelAndView for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null;} 2)、如何定制错误响应:1)、如何定制错误的页面; 1)、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面; 我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html); 页面能获取的信息; timestamp:时间戳 status:状态码 error:错误提示 exception:异常对象 message:异常消息 errors:JSR303数据校验的错误都在这里1<h1>[[${timestamp}]]</h1> 2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找; 3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;(org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration) 2)、如何定制错误的json数据; 1)、自定义异常处理&返回定制json数据; 1234567891011121314//这个注解表示是一个异常处理器@ControllerAdvicepublic class MyExceptionHandler { @ResponseBody @ExceptionHandler(UserNotExistException.class)//表示处理这个错误,如果出现了这个错误就调用这个方法 public Map<String,Object> handleException(Exception e){ Map<String,Object> map = new HashMap<>(); map.put("code","user.notexist"); map.put("message",e.getMessage()); return map; }}//没有自适应效果... 2)、转发到/error进行自适应响应效果处理 1234567891011121314@ExceptionHandler(UserNotExistException.class) public String handleException(Exception e, HttpServletRequest request){ Map<String,Object> map = new HashMap<>(); //传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程 /** * Integer statusCode = (Integer) request .getAttribute("javax.servlet.error.status_code"); */ request.setAttribute("javax.servlet.error.status_code",500); map.put("code","user.notexist"); map.put("message",e.getMessage()); //转发到/error return "forward:/error"; } 3)、将我们的定制数据携带出去;出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法); 1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中; 2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到; 容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的; 自定义ErrorAttributes 1234567891011//给容器中加入我们自己定义的ErrorAttributes@Componentpublic class MyErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace); map.put("company","atguigu"); return map; }} 最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容, 8、配置嵌入式Servlet容器SpringBoot默认使用Tomcat作为嵌入式的Servlet容器; 问题? 1)、如何定制和修改Servlet容器的相关配置;1、修改和server有关的配置(ServerProperties【也是EmbeddedServletContainerCustomizer】); 123456789server.port=8081server.context-path=/crudserver.tomcat.uri-encoding=UTF-8//通用的Servlet容器设置server.xxx//Tomcat的设置server.tomcat.xxx 2、编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置 1234567891011@Bean //一定要将这个定制器加入到容器中public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){ return new EmbeddedServletContainerCustomizer() { //定制嵌入式的Servlet容器相关的规则 @Override public void customize(ConfigurableEmbeddedServletContainer container) { container.setPort(8083); } };} 要配置在Config的类中(有注解@Configuration) 2)、注册Servlet三大组件【Servlet、Filter、Listener】由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。 注册三大组件用以下方式 注册servlet 创建servlet 123456public class Myservlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().println("hello myservlet"); }} 在配置类中配置servletServletRegistrationBean 1234567//注册三大组件@Configurationpublic class MyServerConfig { @Bean public ServletRegistrationBean myServlet(){ return new ServletRegistrationBean(new Myservlet(),"/myServlet"); } 注册其他组件FilterRegistrationBean 1234567@Beanpublic FilterRegistrationBean myFilter(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new MyFilter()); registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet")); return registrationBean;} ServletListenerRegistrationBean 12345@Beanpublic ServletListenerRegistrationBean myListener(){ ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener()); return registrationBean;} SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet; DispatcherServletAutoConfiguration中: 1234567891011121314151617@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public ServletRegistrationBean dispatcherServletRegistration( DispatcherServlet dispatcherServlet) { ServletRegistrationBean registration = new ServletRegistrationBean( dispatcherServlet, this.serverProperties.getServletMapping()); //默认拦截: / 所有请求;包静态资源,但是不拦截jsp请求; /*会拦截jsp //可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径 registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup( this.webMvcProperties.getServlet().getLoadOnStartup()); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } return registration;} 2)、SpringBoot能不能支持其他的Servlet容器; 3)、替换为其他嵌入式Servlet容器 默认支持: Tomcat(默认使用) 12345<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> 引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;</dependency> Jetty 1234567891011121314151617<!-- 引入web模块 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions></dependency><!--引入其他的Servlet容器--><dependency> <artifactId>spring-boot-starter-jetty</artifactId> <groupId>org.springframework.boot</groupId></dependency> Undertow 1234567891011121314151617<!-- 引入web模块 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions></dependency><!--引入其他的Servlet容器--><dependency> <artifactId>spring-boot-starter-undertow</artifactId> <groupId>org.springframework.boot</groupId></dependency> 4)、嵌入式Servlet容器自动配置原理;EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置? 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)@Configuration@ConditionalOnWebApplication@Import(BeanPostProcessorsRegistrar.class)//导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件//导入了EmbeddedServletContainerCustomizerBeanPostProcessor://后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作public class EmbeddedServletContainerAutoConfiguration { @Configuration @ConditionalOnClass({ Servlet.class, Tomcat.class })//判断当前是否引入了Tomcat依赖; @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)//判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器 public static class EmbeddedTomcat { @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { return new TomcatEmbeddedServletContainerFactory(); } } /** * Nested configuration if Jetty is being used. */ @Configuration @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedJetty { @Bean public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() { return new JettyEmbeddedServletContainerFactory(); } } /** * Nested configuration if Undertow is being used. */ @Configuration @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class }) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedUndertow { @Bean public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() { return new UndertowEmbeddedServletContainerFactory(); } } 1)、EmbeddedServletContainerFactory(嵌入式Servlet容器工厂) 1234567public interface EmbeddedServletContainerFactory { //获取嵌入式的Servlet容器 EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers);} 2)、EmbeddedServletContainer:(嵌入式的Servlet容器) 3)、以TomcatEmbeddedServletContainerFactory为例 123456789101112131415161718192021222324@Overridepublic EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { //创建一个Tomcat Tomcat tomcat = new Tomcat(); //配置Tomcat的基本环节 File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat")); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); //将配置好的Tomcat传入进去,返回一个EmbeddedServletContainer;并且启动Tomcat服务器 return getTomcatEmbeddedServletContainer(tomcat);} 4)、我们对嵌入式容器的配置修改是怎么生效? 1ServerProperties、EmbeddedServletContainerCustomizer EmbeddedServletContainerCustomizer:定制器帮我们修改了Servlet容器的配置? 怎么修改的原理? 5)、容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor 12345678910111213141516171819202122232425262728293031323334353637//初始化之前@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件 if (bean instanceof ConfigurableEmbeddedServletContainer) { // postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean); } return bean;}private void postProcessBeforeInitialization( ConfigurableEmbeddedServletContainer bean) { //获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值; for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { customizer.customize(bean); }}private Collection<EmbeddedServletContainerCustomizer> getCustomizers() { if (this.customizers == null) { // Look up does not include the parent context this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>( this.beanFactory //从容器中获取所有这葛类型的组件:EmbeddedServletContainerCustomizer //定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件 .getBeansOfType(EmbeddedServletContainerCustomizer.class, false, false) .values()); Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE); this.customizers = Collections.unmodifiableList(this.customizers); } return this.customizers;}ServerProperties也是定制器 步骤: 1)、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】 2)、容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor; 只要是嵌入式的Servlet容器工厂,后置处理器就工作; 3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法 ###5)、嵌入式Servlet容器启动原理; 什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat; 获取嵌入式的Servlet容器工厂: 1)、SpringBoot应用启动运行run方法 2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext 3)、refresh(context);刷新刚才创建好的ioc容器; 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } }} 4)、 onRefresh(); web的ioc容器重写了onRefresh方法 5)、webioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer(); 6)、获取嵌入式的Servlet容器工厂: EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory(); 从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置; 7)、使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer()); 8)、嵌入式的Servlet容器创建对象并启动Servlet容器; 先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来; ==IOC容器启动创建嵌入式的Servlet容器== 9、使用外置的Servlet容器嵌入式Servlet容器:应用打成可执行的jar 优点:简单、便携; 缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】); 外置的Servlet容器:外面安装Tomcat—应用war包的方式打包; 步骤1)、必须创建一个war项目;(利用idea创建好目录结构) 2)、将嵌入式的Tomcat指定为provided; 12345<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope></dependency> 3)、必须编写一个SpringBootServletInitializer的子类,并调用configure方法 123456789public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { //传入SpringBoot应用的主程序 return application.sources(SpringBoot04WebJspApplication.class); }} 4)、启动服务器就可以使用; 原理jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器; war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器; servlet3.0(Spring注解版): 8.2.4 Shared libraries / runtimes pluggability: 规则: 1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例: 2)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名 3)、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类; 流程: 1)、启动Tomcat 2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer: Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer 3)、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例; 4)、每一个WebApplicationInitializer都调用自己的onStartup; 5)、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法 6)、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器 1234567891011121314151617181920212223242526272829303132333435363738protected WebApplicationContext createRootApplicationContext( ServletContext servletContext) { //1、创建SpringApplicationBuilder SpringApplicationBuilder builder = createSpringApplicationBuilder(); StandardServletEnvironment environment = new StandardServletEnvironment(); environment.initPropertySources(servletContext, null); builder.environment(environment); builder.main(getClass()); ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); builder.initializers(new ParentContextApplicationContextInitializer(parent)); } builder.initializers( new ServletContextApplicationContextInitializer(servletContext)); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); //调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来 builder = configure(builder); //使用builder创建一个Spring应用 SpringApplication application = builder.build(); if (application.getSources().isEmpty() && AnnotationUtils .findAnnotation(getClass(), Configuration.class) != null) { application.getSources().add(getClass()); } Assert.state(!application.getSources().isEmpty(), "No SpringApplication sources have been defined. Either override the " + "configure method or add an @Configuration annotation"); // Ensure error pages are registered if (this.registerErrorPageFilter) { application.getSources().add(ErrorPageFilterConfiguration.class); } //启动Spring应用 return run(application);} 7)、Spring的应用就启动并且创建IOC容器 1234567891011121314151617181920212223242526272829303132333435public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); context = createApplicationContext(); analyzers = new FailureAnalyzers(context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新IOC容器 refreshContext(context); afterRefresh(context, applicationArguments); listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); }} ==启动Servlet容器,再启动SpringBoot应用== 五、Docker1、简介Docker是一个开源的应用容器引擎;是一个轻量级容器技术; Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像; 运行中的这个镜像称为容器,容器启动是非常快速的。 2、核心概念docker主机(Host):安装了Docker程序的机器(Docker直接安装在操作系统之上); docker客户端(Client):连接docker主机进行操作; docker仓库(Registry):用来保存各种打包好的软件镜像; docker镜像(Images):软件打包好的镜像;放在docker仓库中; docker容器(Container):镜像启动后的实例称为一个容器;容器是独立运行的一个或一组应用 使用Docker的步骤: 1)、安装Docker 2)、去Docker仓库找到这个软件对应的镜像; 3)、使用Docker运行这个镜像,这个镜像就会生成一个Docker容器; 4)、对容器的启动停止就是对软件的启动停止; 3、安装Docker在linux虚拟机上安装docker步骤: 1234567891011121314151、检查内核版本,必须是3.10及以上uname -r如果不是的话运行yum update2、安装dockeryum install docker3、输入y确认安装4、启动docker[root@localhost ~]# systemctl start docker[root@localhost ~]# docker -vDocker version 1.12.6, build 3e8e77d/1.12.65、开机启动docker[root@localhost ~]# systemctl enable dockerCreated symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.6、停止dockersystemctl stop docker 4、Docker常用命令&操作1)、镜像操作 操作 命令 说明 检索 docker search 关键字 eg:docker search redis 我们经常去docker hub上检索镜像的详细信息,如镜像的TAG。 拉取 docker pull 镜像名:tag :tag是可选的,tag表示标签,多为软件的版本,默认是latest 列表 docker images 查看所有本地镜像 删除 docker rmi image-id 删除指定的本地镜像 https://hub.docker.com/ 2)、容器操作软件镜像(QQ安装程序)—-运行镜像—-产生一个容器(正在运行的软件,运行的QQ); 步骤: 1234567891011121314151617181920212223242526272829301、搜索镜像[root@localhost ~]# docker search tomcat2、拉取镜像[root@localhost ~]# docker pull tomcat3、根据镜像启动容器docker run --name mytomcat -d tomcat:latest4、docker ps查看运行中的容器5、 停止运行中的容器docker stop 容器的id6、查看所有的容器docker ps -a7、启动容器docker start 容器id8、删除一个容器 docker rm 容器id9、启动一个做了端口映射的tomcat[root@localhost ~]# docker run -d -p 8888:8080 tomcat-d:后台运行-p: 将主机的端口映射到容器的一个端口 主机端口:容器内部的端口10、为了演示简单关闭了linux的防火墙service firewalld status ;查看防火墙状态service firewalld stop:关闭防火墙11、查看容器的日志docker logs container-name/container-id更多命令参看https://docs.docker.com/engine/reference/commandline/docker/可以参考每一个镜像的文档 3)、安装MySQL示例1docker pull mysql 错误的启动 1234567891011121314151617[root@localhost ~]# docker run --name mysql01 -d mysql42f09819908bb72dd99ae19e792e0a5d03c48638421fa64cce5f8ba0f40f5846mysql退出了[root@localhost ~]# docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES42f09819908b mysql "docker-entrypoint.sh" 34 seconds ago Exited (1) 33 seconds ago mysql01538bde63e500 tomcat "catalina.sh run" About an hour ago Exited (143) About an hour ago compassionate_goldstinec4f1ac60b3fc tomcat "catalina.sh run" About an hour ago Exited (143) About an hour ago lonely_fermi81ec743a5271 tomcat "catalina.sh run" About an hour ago Exited (143) About an hour ago sick_ramanujan//错误日志[root@localhost ~]# docker logs 42f09819908berror: database is uninitialized and password option is not specified You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD;这个三个参数必须指定一个 正确的启动 12345[root@localhost ~]# docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysqlb874c56bec49fb43024b3805ab51e9097da779f2f572c22c695305dedd684c5f[root@localhost ~]# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESb874c56bec49 mysql "docker-entrypoint.sh" 4 seconds ago Up 3 seconds 3306/tcp mysql01 做了端口映射 12345[root@localhost ~]# docker run -p 3306:3306 --name mysql02 -e MYSQL_ROOT_PASSWORD=123456 -d mysqlad10e4bc5c6a0f61cbad43898de71d366117d120e39db651844c0e73863b9434[root@localhost ~]# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESad10e4bc5c6a mysql "docker-entrypoint.sh" 4 seconds ago Up 2 seconds 0.0.0.0:3306->3306/tcp mysql02 几个其他的高级操作 123456docker run --name mysql03 -v /conf/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag把主机的/conf/mysql文件夹挂载到 mysqldocker容器的/etc/mysql/conf.d文件夹里面改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下(/conf/mysql)docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci指定mysql的一些配置参数 六、SpringBoot与数据访问1、JDBC123456789<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope></dependency> 123456spring: datasource: username: root password: xxx url: jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=UTF-8&serverTimezone=UTC driver-class-name: com.mysql.jdbc.Driver 效果: 默认是用com.zaxxer.hikari.HikariDataSource作为数据源; 数据源的相关配置都在DataSourceProperties里面; 自动配置原理: org.springframework.boot.autoconfigure.jdbc: 1、参考DataSourceConfiguration,根据配置创建数据源,默认使用Tomcat连接池;可以使用spring.datasource.type指定自定义的数据源类型; 2、SpringBoot默认可以支持; 1org.apache.tomcat.jdbc.pool.DataSource、HikariDataSource、BasicDataSource、 3、自定义数据源类型 1234567891011121314/** * Generic DataSource configuration. */@ConditionalOnMissingBean(DataSource.class)@ConditionalOnProperty(name = "spring.datasource.type")static class Generic { @Bean public DataSource dataSource(DataSourceProperties properties) { //使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性 return properties.initializeDataSourceBuilder().build(); }} 自动加载sql文件4、DataSourceInitializer:ApplicationListener; 作用: 1)、runSchemaScripts();运行建表语句; 2)、runDataScripts();运行插入数据的sql语句; 默认只需要将文件命名为: 123456789schema-*.sql、data-*.sql默认规则:schema.sql,schema-all.sql;可以使用spring: datasource: schema: - classpath:department.sql 指定位置schema是一个列表属性,可以指定多个值 只有第一次才会执行schema的文件,如果要始终初始化需要配置123spring: datasource: initialization-mode: always 5、操作数据库:自动配置了JdbcTemplate操作数据库 运行jdbc1234567@AutowiredJdbcTemplate jdbcTemplate;@Testpublic void test(){ List<Map<String,Object>> ans = jdbcTemplate.queryForList("select * from user"); System.out.println(ans);} 从容器中获取jdbcTemplate,执行他的查询列表方法 2、整合Druid数据源导入数据源 去maven仓库找最新的druid12345<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.14</version></dependency> 12 123456789101112131415161718192021222324252627282930313233343536373839404142@Configurationpublic class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druid(){ return new DruidDataSource(); } //配置Druid的监控 //1、配置一个管理后台的Servlet @Bean public ServletRegistrationBean statViewServlet(){ ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); Map<String,String> initParams = new HashMap<>(); initParams.put("loginUsername","admin"); initParams.put("loginPassword","123456"); initParams.put("allow","");//默认就是允许所有访问 initParams.put("deny","192.168.15.21"); bean.setInitParameters(initParams); return bean; } //2、配置一个web监控的filter @Bean public FilterRegistrationBean webStatFilter(){ FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); Map<String,String> initParams = new HashMap<>(); initParams.put("exclusions","*.js,*.css,/druid/*"); bean.setInitParameters(initParams); bean.setUrlPatterns(Arrays.asList("/*")); return bean; }} 3、整合MyBatis12345<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version></dependency> 步骤: 1)、配置数据源相关属性(见上一节Druid) 2)、给数据库建表 3)、创建JavaBean 4)、注解版bean123456789public class User { private int id; private String username; @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss") private Date birthday; private String sex; private String address;} mapper1234567891011121314//指定这是一个操作数据库的mapper@Mapperpublic interface UserMapper { @Select("select * from user") public List<User> getAllUser(); @Select("select * from user where id = #{id}") public User getUserById(int id); //表示使用了自增ID,属性名是id @Options(useGeneratedKeys = true,keyProperty = "id") @Insert("insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})") public int insertUser(User user);} controller123456789101112131415161718192021222324@Controllerpublic class HelloController { @Autowired UserMapper userMapper; @RequestMapping("/abc") @ResponseBody public List<User> hello(Map<String,Object> map) { return userMapper.getAllUser(); } @ResponseBody @GetMapping("/cba/{id}") public User u(@PathVariable("id") int id){ return userMapper.getUserById(id); } @ResponseBody @GetMapping("/bba") public int a(User user){ System.out.println(user); return userMapper.insertUser(user); }} mapper的insert方法使用了Options注解,表示使用了自增id,自增的属性名是id cba路径后跟一个数字可以把那个数字当成id传到方法里 User中的birthday定义了他的输入和输出格式(DateTimeFormat是输入,JsonFormat是输出格式) bba方法中要从url中创建一个user对象,就要把他的所有属性都输入到url参数中,日期格式要按照它属性的输入格式,比如http://localhost:8080/bba?username=我&sex=1&address=beijing&birthday=2017-08-09 2015:40:00 问题: 自定义MyBatis的配置规则;给容器中添加一个ConfigurationCustomizer; 123456789101112131415@org.springframework.context.annotation.Configurationpublic class MyBatisConfig { @Bean public ConfigurationCustomizer configurationCustomizer(){ return new ConfigurationCustomizer(){ @Override public void customize(Configuration configuration) { //表示开启驼峰命名法配置规则 configuration.setMapUnderscoreToCamelCase(true); } }; }} 123456789使用MapperScan批量扫描所有的Mapper接口;@MapperScan(value = "cn.xwmdream.springboot.mapper")@SpringBootApplicationpublic class SpringBoot06DataMybatisApplication { public static void main(String[] args) { SpringApplication.run(SpringBoot06DataMybatisApplication.class, args); }} 此时cn.xwmdream.springboot.mapper下所有的接口都到容器里 5)、配置文件版mapper123public interface UserMapper { public List<User> getAllUser();} mybatis-config.xmlmybatis/mybatis-config.xml123456<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration></configuration> UserMapper.xmlmybatis/mapper/UserMapper.xml123456789<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="cn.xwmdream.springbootdemo.mapper.UserMapper"> <select id="getAllUser" resultType="cn.xwmdream.springbootdemo.bean.User"> select * from user </select></mapper> 123mybatis: config-location: classpath:mybatis/mybatis-config.xml 指定全局配置文件的位置 mapper-locations: classpath:mybatis/mapper/*.xml 指定sql映射文件的位置 注意要在SpringBootApplication上添加MapperScan 使用123456789101112@Controllerpublic class HelloController { @Autowired UserMapper userMapper; @RequestMapping("/abc") @ResponseBody public List<User> hello(Map<String,Object> map) { return userMapper.getAllUser(); }} 更多使用参照 http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/ 4、整合SpringData JPA1)、SpringData简介 2)、整合SpringData JPAJPA:ORM(Object Relational Mapping); 1)、编写一个实体类(bean)和数据表进行映射,并且配置好映射关系; 12345678910111213//使用JPA注解配置映射关系@Entity //告诉JPA这是一个实体类(和数据表映射的类)@Table(name = "tbl_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user;public class User { @Id //这是一个主键 @GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键 private Integer id; @Column(name = "last_name",length = 50) //这是和数据表对应的一个列 private String lastName; @Column //省略默认列名就是属性名 private String email; 2)、编写一个Dao接口来操作实体类对应的数据表(Repository) 123//继承JpaRepository来完成对数据库的操作public interface UserRepository extends JpaRepository<User,Integer> {} 3)、基本的配置JpaProperties 1234567spring: jpa: hibernate:# 更新或者创建数据表结构 ddl-auto: update# 控制台显示SQL show-sql: true 七、启动配置原理几个重要的事件回调机制 配置在META-INF/spring.factories ApplicationContextInitializer SpringApplicationRunListener 只需要放在ioc容器中 ApplicationRunner CommandLineRunner 启动流程: 1、创建SpringApplication对象12345678910111213141516initialize(sources);private void initialize(Object[] sources) { //保存主配置类 if (sources != null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } //判断当前是否一个web应用 this.webEnvironment = deduceWebEnvironment(); //从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListener setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //从多个配置类中找到有main方法的主配置类 this.mainApplicationClass = deduceMainApplicationClass();} 2、运行run方法12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); //获取SpringApplicationRunListeners;从类路径下META-INF/spring.factories SpringApplicationRunListeners listeners = getRunListeners(args); //回调所有的获取SpringApplicationRunListener.starting()方法 listeners.starting(); try { //封装命令行参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //准备环境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成 Banner printedBanner = printBanner(environment); //创建ApplicationContext;决定创建web的ioc还是普通的ioc context = createApplicationContext(); analyzers = new FailureAnalyzers(context); //准备上下文环境;将environment保存到ioc中;而且applyInitializers(); //applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法 //回调所有的SpringApplicationRunListener的contextPrepared(); // prepareContext(context, environment, listeners, applicationArguments, printedBanner); //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded(); //s刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版 //扫描,创建,加载所有组件的地方;(配置类,组件,自动配置) refreshContext(context); //从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调 //ApplicationRunner先回调,CommandLineRunner再回调 afterRefresh(context, applicationArguments); //所有的SpringApplicationRunListener回调finished方法 listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } //整个SpringBoot应用启动完成以后返回启动的ioc容器; return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); }} 3、事件监听机制配置在META-INF/spring.factories ApplicationContextInitializer 123456public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("ApplicationContextInitializer...initialize..."+applicationContext); }} SpringApplicationRunListener 123456789101112131415161718192021222324252627282930313233public class HelloSpringApplicationRunListener implements SpringApplicationRunListener { //必须有的构造器 public HelloSpringApplicationRunListener(SpringApplication application, String[] args){ } @Override public void starting() { System.out.println("SpringApplicationRunListener...starting..."); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { Object o = environment.getSystemProperties().get("os.name"); System.out.println("SpringApplicationRunListener...environmentPrepared.."+o); } @Override public void contextPrepared(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...contextPrepared..."); } @Override public void contextLoaded(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...contextLoaded..."); } @Override public void finished(ConfigurableApplicationContext context, Throwable exception) { System.out.println("SpringApplicationRunListener...finished..."); }} 配置(META-INF/spring.factories) 12345org.springframework.context.ApplicationContextInitializer=\com.atguigu.springboot.listener.HelloApplicationContextInitializerorg.springframework.boot.SpringApplicationRunListener=\com.atguigu.springboot.listener.HelloSpringApplicationRunListener 只需要放在ioc容器中 ApplicationRunner 1234567@Componentpublic class HelloApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("ApplicationRunner...run...."); }} CommandLineRunner 1234567@Componentpublic class HelloCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner...run..."+ Arrays.asList(args)); }} 八、自定义starterstarter: 1、这个场景需要使用到的依赖是什么? 2、如何编写自动配置 12345678910111213@Configuration //指定这个类是一个配置类@ConditionalOnXXX //在指定条件成立的情况下自动配置类生效@AutoConfigureAfter //指定自动配置类的顺序@Bean //给容器中添加组件@ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置@EnableConfigurationProperties //让xxxProperties生效加入到容器中自动配置类要能加载将需要启动就加载的自动配置类,配置在META-INF/spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ 3、模式: 启动器只用来做依赖导入; 专门来写一个自动配置模块; 启动器依赖自动配置;别人只需要引入启动器(starter) mybatis-spring-boot-starter;自定义启动器名-spring-boot-starter 步骤: 1)、启动器模块 12345678910111213141516171819202122<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.atguigu.starter</groupId> <artifactId>atguigu-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> <!--启动器--> <dependencies> <!--引入自动配置模块--> <dependency> <groupId>com.atguigu.starter</groupId> <artifactId>atguigu-spring-boot-starter-autoconfigurer</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies></project> 2)、自动配置模块 123456789101112131415161718192021222324252627282930313233343536373839<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.atguigu.starter</groupId> <artifactId>atguigu-spring-boot-starter-autoconfigurer</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>atguigu-spring-boot-starter-autoconfigurer</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.10.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!--引入spring-boot-starter;所有starter的基本配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies></project> 1234567891011121314151617181920212223242526package com.atguigu.starter;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix = "atguigu.hello")public class HelloProperties { private String prefix; private String suffix; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; }} 123456789101112131415161718package com.atguigu.starter;public class HelloService { HelloProperties helloProperties; public HelloProperties getHelloProperties() { return helloProperties; } public void setHelloProperties(HelloProperties helloProperties) { this.helloProperties = helloProperties; } public String sayHellAtguigu(String name){ return helloProperties.getPrefix()+"-" +name + helloProperties.getSuffix(); }} 12345678910111213141516171819202122package com.atguigu.starter;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration@ConditionalOnWebApplication //web应用才生效@EnableConfigurationProperties(HelloProperties.class)public class HelloServiceAutoConfiguration { @Autowired HelloProperties helloProperties; @Bean public HelloService helloService(){ HelloService service = new HelloService(); service.setHelloProperties(helloProperties); return service; }} 更多SpringBoot整合示例https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples]]></content>
<tags>
<tag>java</tag>
<tag>spring</tag>
</tags>
</entry>
<entry>
<title><![CDATA[spring_mvc]]></title>
<url>%2F2019%2F03%2F05%2Fspring-mvc%2F</url>
<content type="text"><![CDATA[什么是springmvc springmvc是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合。 springmvc是一个基于mvc的web框架。 springmvc框架 用户发送请求至前端控制器DispatcherServlet。 DispatcherServlet收到请求调用HandlerMapping处理器映射器。 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 DispatcherServlet调用HandlerAdapter处理器适配器。 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。 Controller执行完成返回ModelAndView。 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。 ViewReslover解析后返回具体View。 DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。 DispatcherServlet响应用户。 组件: 前端控制器DispatcherServlet(不需要程序员开发)作用接收请求,响应结果,相当于转发器,中央处理器。有了DispatcherServlet减少了其它组件之间的耦合度。 处理器映射器HandlerMapping(不需要程序员开发)作用:根据请求的url查找Handler 处理器适配器HandlerAdapter作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler 处理器Handler(需要程序员开发)注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler 视图解析器View resolver(不需要程序员开发)作用:进行视图解析,根据逻辑视图名解析成真正的视图(view) 视图View(需要程序员开发jsp)View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…) 前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。处理器映射器(HandlerMapping):根据URL去查找处理器处理器(Handler):(需要程序员去写代码处理逻辑的)处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用)视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面 Hello World需求 spring mvc+mybatis(商品订单管理)。 商品列表查询 ### 依赖 spring所有的jar包(一定包括spring-webmvc-3.2.0.RELEASE.jar) mybatis以及数据库相关的jar jstl.jar 配置前端控制器在web.xml中配置前端控制器12345678910111213141516171819202122<web-app> <!-- springmvc前端控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- contextConfigLocation配置springmvc加载的配置文件(配置处理器映射器、适配器等等) 如果不配置contextConfigLocation,默认加载的是/WEB-INF/servlet名称-serlvet.xml(springmvc-servlet.xml) --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 第一种:*.action,访问以.action结尾 由DispatcherServlet进行解析 第二种:/,所以访问的地址都由DispatcherServlet进行解析,对于静态文件的解析需要配置不让DispatcherServlet进行解析 使用此种方式可以实现 RESTful风格的url 第三种:/*,这样配置不对,使用这种配置,最终要转发到一个jsp页面时, 仍然会由DispatcherServlet解析jsp地址,不能根据jsp页面找到handler,会报错。 --> <url-pattern>*.action</url-pattern> </servlet-mapping></web-app> load-on-startup:表示servlet随服务启动; url-pattern:*.action的请交给DispatcherServlet处理。 contextConfigLocation:指定springmvc配置的加载位置,如果不指定则默认加载WEB-INF/[DispatcherServlet 的Servlet 名字]-servlet.xml。 配置处理器适配器在classpath的springmvc.xml中配置处理器适配器1234567891011121314151617<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /></beans> 适配器都继承HandlerAdapter 适配器中有一个supports方法是判断这个适配器是执行哪个接口的handle 开发Handler(处理request) 需要实现 controller接口,才能由org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter适配器执行。 cn.xwmdream.ssm.controller.ItemsController11234567891011121314151617181920212223242526272829public class ItemsController1 implements Controller{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //调用service查找 数据库,查询商品列表,这里使用静态数据模拟 List<Items> itemsList = new ArrayList<Items>(); //向list中填充静态数据 Items items_1 = new Items(); items_1.setName("联想笔记本"); items_1.setPrice(6000f); items_1.setDetail("ThinkPad T430 联想笔记本电脑!"); Items items_2 = new Items(); items_2.setName("苹果手机"); items_2.setPrice(5000f); items_2.setDetail("iphone6苹果手机!"); itemsList.add(items_1); itemsList.add(items_2); //返回ModelAndView ModelAndView modelAndView = new ModelAndView(); //相当 于request的setAttribut,在jsp页面中通过itemsList取数据 modelAndView.addObject("itemsList", itemsList); //指定视图 modelAndView.setViewName("/WEB-INF/jsp/item/itemsList.jsp"); return modelAndView; }} 视图编写 使用了jstl解析器,一定要引入这个jar包 /WEB-INF/jsp/item/itemsList.jsp 12345678910111213141516171819202122232425262728293031323334353637383940414243444546<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%><%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>查询商品列表</title></head><body> <form action="${pageContext.request.contextPath }/item/queryItem.action" method="post"> 查询条件: <table width="100%" border=1> <tr> <td><input type="submit" value="查询" /></td> </tr> </table> 商品列表: <table width="100%" border=1> <tr> <td>商品名称</td> <td>商品价格</td> <td>生产日期</td> <td>商品描述</td> <td>操作</td> </tr> <c:forEach items="${itemsList }" var="item"> <tr> <td>${item.name }</td> <td>${item.price }</td> <td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss" /></td> <td>${item.detail }</td> <td><a href="${pageContext.request.contextPath }/item/editItem.action?id=${item.id}">修改</a></td> </tr> </c:forEach> </table> </form></body></html> 一个foreach遍历传入的itemsList 配置Handler将编写Handler在spring容器加载springmvc.xml12<!-- 配置Handler --><bean name="/queryItems_test.action" class="cn.xwmdream.ssm.controller.ItemsController1" /> 配置处理器映射器在classpath下的springmvc.xml中配置处理器映射器1234<!-- 处理器映射器 将bean的name作为url进行查找 ,需要在配置Handler时指定beanname(就是url)所有的映射器都实现 HandlerMapping接口。--><bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" /> 此时handler需要配置路径12<!-- 配置Handler --><bean name="/queryItems_test.action" class="cn.xwmdream.ssm.controller.ItemsController1" /> 配置视图解析器在classpath下的springmvc.xml中配置视图解析器 需要配置解析jsp的视图解析器。12345<!-- 视图解析器 解析jsp解析,默认使用jstl标签,classpath下的得有jstl的包 --><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"></bean> 运行 运行http://localhost:8080/TestSpringMvc/queryItems_test.action 前面是项目地址,后面是配置的handler的地址 非注解的处理器映射器和适配器非注解的处理器映射器BeanNameUrlHandlerMapping 这个映射器是通过handler的name设置映射路径,所以handler的bean需要配置name属性(见hello world) SimpleUrlHandlerMapping12345678910<!--简单url映射 --><bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <!-- 对itemsController1进行url映射,url是/queryItems1.action --> <prop key="/queryItems1.action">itemsController1Id</prop> <prop key="/queryItems2.action">itemsController1Id</prop> </props> </property></bean> 这个映射器是通过参数的方式配置路径,值是handler的id,所以handler需要配置id 12<!-- 配置Handler --><bean id="itemsController1Id" class="cn.xwmdream.ssm.controller.ItemsController1" /> 多个映射器可以并存,前端控制器判断url能让哪些映射器映射,就让正确的映射器处理。 非注解的处理器适配器SimpleControllerHandlerAdapter 配置适配器 1<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> 要求编写的Handler实现 Controller接口(见hello world) HttpRequestHandlerAdapter 配置适配器 1<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" /> 要求编写的Handler实现 HttpRequestHandler接口 123456789101112131415161718192021@Overridepublic void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //调用service查找 数据库,查询商品列表,这里使用静态数据模拟 List<Items> itemsList = new ArrayList<Items>(); //向list中填充静态数据 Items items_1 = new Items(); items_1.setName("联想笔记本"); items_1.setPrice(6000f); items_1.setDetail("ThinkPad T430 联想笔记本电脑!"); Items items_2 = new Items(); items_2.setName("苹果手机"); items_2.setPrice(5000f); items_2.setDetail("iphone6苹果手机!"); itemsList.add(items_1); itemsList.add(items_2); //转发数据 request.setAttribute("itemsList",itemsList); request.getRequestDispatcher("/WEB-INF/jsp/item/itemsList.jsp").forward(request,response);} 使用此方法可以通过修改response,设置响应的数据格式,比如响应json数据(通过getWriter,类似servlet) 123response.setCharacterEncoding("utf-8");response.setContentType("application/json;charset=utf-8");response.getWriter().write("json串"); DispatcherSerlvet.properties 如果不在springmvc.xml中配置处理映射器、适配器、视图解析器等组件,使用默认加载的。 默认加载的文件 这里面配置了多个映射器适配器视图解析器等组件,如果项目中没有指定会对应不同版本加载不同的默认组件 注解的处理器映射器和适配器默认映射器在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping注解映射器。在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping注解映射器。 默认适配器在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter注解适配器。在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter注解适配器。 配置注解映射器和适配器1234<!--注解映射器 --><bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" /><!--注解适配器 --><bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" /> 当然也可以换一种方式加载123456<!-- 使用 mvc:annotation-driven代替上边注解映射器和注解适配器配置mvc:annotation-driven默认加载很多的参数绑定方法,比如json转换解析器就默认加载了,如果使用mvc:annotation-driven不用配置上边的RequestMappingHandlerMapping和RequestMappingHandlerAdapter实际开发时使用mvc:annotation-driven --><mvc:annotation-driven></mvc:annotation-driven> 开发注解Handler cn.xwmdream.ssm.controller.ItemsController112345678910111213141516171819202122232425262728293031323334//使用Controller标识 它是一个控制器@Controllerpublic class ItemsController1 { // 商品查询列表 // @RequestMapping实现 对queryItems方法和url进行映射,一个方法对应一个url // 一般建议将url和方法写成一样 @RequestMapping("/queryItems") public ModelAndView queryItems() throws Exception { // 调用service查找 数据库,查询商品列表,这里使用静态数据模拟 List<Items> itemsList = new ArrayList<Items>(); // 向list中填充静态数据 Items items_1 = new Items(); items_1.setName("联想笔记本"); items_1.setPrice(6000f); items_1.setDetail("ThinkPad T430 联想笔记本电脑!"); Items items_2 = new Items(); items_2.setName("苹果手机"); items_2.setPrice(5000f); items_2.setDetail("iphone6苹果手机!"); itemsList.add(items_1); itemsList.add(items_2); // 返回ModelAndView ModelAndView modelAndView = new ModelAndView(); // 相当 于request的setAttribut,在jsp页面中通过itemsList取数据 modelAndView.addObject("itemsList", itemsList); // 指定视图 modelAndView.setViewName("/WEB-INF/jsp/item/itemsList.jsp"); return modelAndView; }} 加载注解handler12345678<!-- 对于注解的Handler可以单个配置实际开发中建议使用组件扫描 --><!-- <bean class="cn.itcast.ssm.controller.ItemsController3" /> --><!-- 可以扫描controller、service、...这里让扫描controller,指定controller的包 --><context:component-scan base-package="cn.xwmdream.ssm.controller"></context:component-scan> 调试访问:http://localhost:8080/TestSpringMvc/queryItems.action 记得后面的.action 视图解析器12345678910<!-- 视图解析器解析jsp解析,默认使用jstl标签,classpath下的得有jstl的包 --><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 配置jsp路径的前缀 --> <property name="prefix" value="/WEB-INF/jsp/"/> <!-- 配置jsp路径的后缀 --> <property name="suffix" value=".jsp"/></bean> 可以配置前缀和后缀,比如要访问/WEB-INF/jsp/item/itemsList.jsp时候,只需要指定item/itemsList即可 源码分析(了解)通过前端控制器源码分析springmvc的执行过程。 前端控制器接收请求调用doDiapatch 前端控制器调用处理器映射器查找 Handler 调用处理器适配器执行Handler,得到执行结果ModelAndView 视图渲染,将model数据填充到request域。视图解析,得到view:调用view的渲染方法,将model数据填充到request域]]></content>
<tags>
<tag>java</tag>
<tag>spring</tag>
</tags>
</entry>
<entry>
<title><![CDATA[log4j的使用]]></title>
<url>%2F2019%2F03%2F01%2Flog4j%E7%9A%84%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[参考 详解log4j2(上) - 从基础到实战 详解log4j2(下) - Async/MongoDB/Flume Appender 按日志级别区分文件输出 简介 log for java 一个Java的日志框架 hello world配置依赖 log4j-api-2.5.jar log4j-core-2.5.jar 下载地址 运行12345678910import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;...Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);logger.trace("trace level");logger.debug("debug level");logger.info("info level");logger.warn("warn level");logger.error("error level");logger.fatal("fatal level"); ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property ‘log4j2.debug’ to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 220:49:25.138 [main] ERROR - error level20:49:25.142 [main] FATAL - fatal level 第一行是提示没有找到配置文件,需要在classpath目录下创建log4j.json、log4j.jsn、log4j2.xml等名称的文件 配置文件创建一个log4j2.xml1234567891011121314<?xml version="1.0" encoding="UTF-8"?><Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> </Appenders> <Loggers> <Root level="error"> <AppenderRef ref="Console" /> </Root> </Loggers></Configuration> 重新运行代码,不会提示警告信息 来看我们添加的配置文件log4j2.xml,以Configuration为根节点,有一个status属性,这个属性表示log4j2本身的日志信息打印级别。如果把status改为TRACE再执行测试代码,可以看到控制台中打印了一些log4j加载插件、组装logger等调试信息。 日志简介日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出。对于Loggers中level的定义同样适用。 详解配置文件Configuration123<Configuration status="WARN" monitorInterval="300"> ...</Configuration> 有一个status属性,这个属性表示log4j2本身的日志信息打印级别。如果把status改为TRACE再执行测试代码,可以看到控制台中打印了一些log4j加载插件、组装logger等调试信息。 monitorInterval属性,含义是每隔300秒重新读取配置文件,可以不重启应用的情况下修改配置,还是很好用的功能。 ### Appenders1234567891011121314151617<Configuration status="error"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> <File name="MyFile" fileName="D:/logs/app.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </File> </Appenders> <Loggers> <Root level="error"> <AppenderRef ref="Console" /> <AppenderRef ref="MyFile" /> </Root> </Loggers></Configuration> 包含了多个Appender 其中root的log输出到两个地方,第一个是控制台,第二个是输出到文件,同时都输出 Appender可以理解为日志的输出目的地,这里配置了一个类型为Console的Appender,也就是输出到控制台。Console节点中的PatternLayout定义了输出日志时的格式: 格式 含义 %d{HH:mm:ss.SSS} 表示输出到毫秒的时间 %t 输出当前线程名称 %-5level 输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0 %logger 输出logger名称,因为Root Logger没有名称,所以没有输出 %msg 日志文本 %n 换行 %F 输出所在的类文件名,如Client.java %L 输出行号 %M 输出所在方法名 %l 输出语句所在的行数, 包括类名、方法名、文件名、行数 Loggers12345678910111213141516<Configuration status="error"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> </Appenders> <Loggers> <Root level="fatal"> <AppenderRef ref="Console" /> </Root> <Logger name="mylog" level="debug" additivity="false"> <AppenderRef ref="Console" /> </Logger> </Loggers></Configuration> 1Logger logger = LogManager.getLogger("mylog"); 增加了一个mylog的logger,他的等级是debug,输出大于等于debug的日志信息 additivity=”false”表示在该logger中输出的日志不会再延伸到父层logger。这里如果改为true,会用root logger指定的方式输出例如一个logger的level是trace,root的level是error,如果additivity=”true”他会同时使用root指定的方式和logger指定的方式输出全部日志,也就是说level是用的logger的,输出方式用的root和logger都有 实用型配置配置一个按时间和文件大小滚动的RollingRandomAccessFile Appender,名字真是够长,但不光只是名字长,相比RollingFileAppender有很大的性能提升,官网宣称是20-200%。 Rolling的意思是当满足一定条件后,就重命名原日志文件用于备份,并从新生成一个新的日志文件。例如需求是每天生成一个日志文件,但是如果一天内的日志文件体积已经超过1G,就从新生成,两个条件满足一个即可。这在log4j 1.x原生功能中无法实现,在log4j2中就很简单了。1234567891011121314151617181920212223242526272829<Configuration status="error"> <properties> <property name="LOG_HOME">D:/logs</property> <property name="FILE_NAME">mylog</property> </properties> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> <RollingRandomAccessFile name="MyFile" fileName="${LOG_HOME}/${FILE_NAME}.log" filePattern="${LOG_HOME}/$${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i.log"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> <Policies> <TimeBasedTriggeringPolicy interval="1" /> <SizeBasedTriggeringPolicy size="1 MB" /> </Policies> <DefaultRolloverStrategy max="20" /> </RollingRandomAccessFile> </Appenders> <Loggers> <Root level="error"> <AppenderRef ref="Console" /> <AppenderRef ref="MyFile" /> </Root> </Loggers></Configuration> 定义了两个常量方便后面复用 RollingRandomAccessFile的属性: fileName 指定当前日志文件的位置和文件名称 filePattern 指定当发生Rolling时,文件的转移和重命名规则 SizeBasedTriggeringPolicy 指定当文件体积大于size指定的值时,触发Rolling DefaultRolloverStrategy 指定最多保存的文件个数 TimeBasedTriggeringPolicy 这个配置需要和filePattern结合使用,注意filePattern中配置的文件重命名规则是${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i,最小的时间粒度是mm,即分钟,TimeBasedTriggeringPolicy指定的size是1,结合起来就是每1分钟生成一个新文件。如果改成%d{yyyy-MM-dd HH},最小粒度为小时,则每一个小时生成一个文件。%i表示第几个文件 自定义配置文件路径log4j2默认在classpath下查找配置文件,可以修改配置文件的位置。在非web项目中:123456File file = new File("D:/log4j2.xml");BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));final ConfigurationSource source = new ConfigurationSource(in);Configurator.initialize(null, source);Logger logger = LogManager.getLogger("mylog"); 如果是web项目,在web.xml中添加12345678<context-param> <param-name>log4jConfiguration</param-name> <param-value>/WEB-INF/conf/log4j2.xml</param-value></context-param><listener> <listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class></listener> 按日志级别区分文件输出有些人习惯按日志信息级别输出到不同名称的文件中,如info.log,error.log,warn.log等,在log4j2中可通过配置Filters来实现。 假定需求是把INFO及以下级别的信息输出到info.log,WARN和ERROR级别的信息输出到error.log,FATAL级别输出到fatal.log,配置文件如下:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263<Configuration status="WARN" monitorInterval="300"> <properties> <property name="LOG_HOME">D:/logs</property> </properties> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> <RollingRandomAccessFile name="InfoFile" fileName="${LOG_HOME}/info.log" filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log"> <Filters> <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL" /> <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" /> </Filters> <PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread][%file:%line] - %msg%n" /> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="10 MB" /> </Policies> <DefaultRolloverStrategy max="20" /> </RollingRandomAccessFile> <RollingRandomAccessFile name="ErrorFile" fileName="${LOG_HOME}/error.log" filePattern="${LOG_HOME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log"> <Filters> <ThresholdFilter level="fatal" onMatch="DENY" onMismatch="NEUTRAL" /> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" /> </Filters> <PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread][%file:%line] - %msg%n" /> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="10 MB" /> </Policies> <DefaultRolloverStrategy max="20" /> </RollingRandomAccessFile> <RollingRandomAccessFile name="FatalFile" fileName="${LOG_HOME}/fatal.log" filePattern="${LOG_HOME}/$${date:yyyy-MM}/fatal-%d{yyyy-MM-dd}-%i.log"> <Filters> <ThresholdFilter level="fatal" onMatch="ACCEPT" onMismatch="DENY" /> </Filters> <PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread][%file:%line] - %msg%n" /> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="10 MB" /> </Policies> <DefaultRolloverStrategy max="20" /> </RollingRandomAccessFile> </Appenders> <Loggers> <Root level="trace"> <AppenderRef ref="Console" /> <AppenderRef ref="InfoFile" /> <AppenderRef ref="ErrorFile" /> <AppenderRef ref="FatalFile" /> </Root> </Loggers></Configuration> 测试代码:123456789public static void main(String[] args) { Logger logger = LogManager.getLogger(Client.class); logger.trace("trace level"); logger.debug("debug level"); logger.info("info level"); logger.warn("warn level"); logger.error("error level"); logger.fatal("fatal level");} 异步写日志1234567891011121314151617181920212223242526272829303132333435<Configuration status="WARN" monitorInterval="300"> <properties> <property name="LOG_HOME">D:/logs</property> <property name="FILE_NAME">mylog</property> </properties> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> <RollingRandomAccessFile name="MyFile" fileName="${LOG_HOME}/${FILE_NAME}.log" filePattern="${LOG_HOME}/$${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i.log"> <PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread][%file:%line] - %msg%n" /> <Policies> <TimeBasedTriggeringPolicy interval="1" /> <SizeBasedTriggeringPolicy size="10 MB" /> </Policies> <DefaultRolloverStrategy max="20" /> </RollingRandomAccessFile> <Async name="Async"> <AppenderRef ref="MyFile" /> </Async> </Appenders> <Loggers> <Logger name="asynclog" level="trace" additivity="false" > <AppenderRef ref="Async" /> </Logger> <Root level="error"> <AppenderRef ref="Console" /> </Root> </Loggers></Configuration> 测试代码:123456789public static void main(String[] args) { Logger logger = LogManager.getLogger("asynclog"); logger.trace("trace level"); logger.debug("debug level"); logger.info("info level"); logger.warn("warn level"); logger.error("error level"); logger.fatal("fatal level");} 输出到MongoDB添加依赖:12345678910<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-nosql</artifactId> <version>2.5</version></dependency><dependency> <groupId>org.mongodb</groupId> <artifactId>mongo-java-driver</artifactId> <version>3.2.2</version></dependency> 配置文件:123456789101112131415161718192021<Configuration status="WARN" monitorInterval="300"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> <NoSql name="databaseAppender"> <MongoDb databaseName="test" collectionName="errorlog" server="localhost" port="27017" /> </NoSql> </Appenders> <Loggers> <Logger name="mongolog" level="trace" additivity="false"> <AppenderRef ref="databaseAppender" /> </Logger> <Root level="error"> <AppenderRef ref="Console" /> </Root> </Loggers></Configuration> 输出到FlumeFlume配置(flume-conf.properties)1234567891011121314151617181920agent1.sources=source1agent1.sinks=sink1agent1.channels=channel1agent1.sources.source1.type=avroagent1.sources.source1.channels=channel1agent1.sources.source1.bind=0.0.0.0agent1.sources.source1.port=41414agent1.sinks.sink1.type=file_rollagent1.sinks.sink1.sink.directory=D:/logagent1.sinks.sink1.channel=channel1agent1.sinks.sink1.sink.rollInterval=86400agent1.sinks.sink1.sink.batchSize=100agent1.sinks.sink1.sink.serializer=textagent1.sinks.sink1.sink.serializer.appendNewline = falseagent1.channels.channel1.type=fileagent1.channels.channel1.checkpointDir=D:/log/checkpointagent1.channels.channel1.dataDirs=D:/log/data 启动Flume(注:测试环境为windows)1flume-ng.cmd agent --conf ../conf/ --conf-file ../conf/flume-conf.properties -name agent1 添加依赖:12345<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-flume-ng</artifactId> <version>2.5</version></dependency> 配置文件:12345678910111213<Configuration status="WARN" monitorInterval="300"> <Appenders> <Flume name="eventLogger" compress="false"> <Agent host="127.0.0.1" port="41414" /> <RFC5424Layout enterpriseNumber="18060" includeMDC="true" appName="MyApp" /> </Flume> </Appenders> <Loggers> <Root level="trace"> <AppenderRef ref="eventLogger" /> </Root> </Loggers></Configuration>]]></content>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[mybatis入门]]></title>
<url>%2F2019%2F02%2F11%2Fmybatis%E5%85%A5%E9%97%A8%2F</url>
<content type="text"><![CDATA[对原生态jdbc程序中问题总结 数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响 数据库性能。设想:使用数据库连接池管理数据库连接。 将sql语句硬编码到java代码中,如果sql 语句修改,需要重新编译java代码,不利于系统维护。设想:将sql语句配置在xml配置文件中,即使sql变化,不需要对java代码进行重新编译。 向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中,不利于系统维护。设想:将sql语句及占位符号和参数全部配置在xml中。 从resutSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,,不利于系统维护。 设想:将查询的结果集,自动映射成java对象。mybatis框架mybatis是什么?mybatis是一个持久层的框架,是apache下的顶级项目。mybatis托管到goolecode下,再后来托管到github下。 mybatis让程序将主要精力放在sql上,通过mybatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写sql)满足需要sql语句。 mybatis可以将向 preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象。(输出映射) mybatis框架 获取会话工厂 获取会话对象 会话操作 会话提交和关闭 Hello World需求根据用户id(主键)查询用户信息根据用户名称模糊查询用户信息添加用户删除用户更新用户 依赖lib mybatis(github下载) jdbc数据库驱动下载与平台无关的Platform Independent,下载zip格式(注意版本问题,点Looking for previous GA versions?是以前版本) 创建文件 在src同级新建一个名为config的Source Folder 在config下创建一个名为sqlmap的包,用来存放对应关系 创建一个SqlMapConfig.xml文件配置mybatis的运行环境,数据源,事务等 数据库12345678910CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL COMMENT '用户名称', `birthday` date DEFAULT NULL COMMENT '生日', `sex` char(1) DEFAULT NULL COMMENT '性别', `address` varchar(256) DEFAULT NULL COMMENT '地址', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (1,'王五',NULL,'2',NULL),(10,'张三','2014-07-10','1','北京市'),(16,'张小明',NULL,'1','河南郑州'),(22,'陈小明',NULL,'1','河南郑州'),(24,'张三丰',NULL,'1','河南郑州'),(25,'陈小明',NULL,'1','河南郑州'),(26,'王五',NULL,NULL,NULL); bean类12345678public class User { //属性名要和数据表中字段对应 private int id; private String username; private String sex; private Date birthday; private String address;} SqlMapConfig.xml配置mybatis的运行环境,数据源、事务等。123456789101112131415161718192021222324<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <!-- 和spring整合后 environments配置将废除--> <environments default="development"> <environment id="development"> <!-- 使用jdbc事务管理,事务控制由mybatis--> <transactionManager type="JDBC" /> <!-- 数据库连接池 由mybatis控制--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="6" /> </dataSource> </environment> </environments> <!-- 加载映射文件 --> <mappers> <mapper resource = "sqlmap/User.xml"/> </mappers></configuration> 注意如果和spring整合后就不用这样配置了 根据用户id(主键)查询用户信息映射文件config/sqlmap/User.xml1234567891011121314151617181920212223<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- namespace是命名空间,作用就是对sql进行分类化管理,理解为sql隔离注意:使用mapper代理方法开发,namespace有特殊重要的作用 --><mapper namespace="test"> <!-- 在映射文件配置很多sql语句 --> <!-- 通过id查询用户表记录 --> <!-- 通过select执行数据库查询 id:标识映射文件中的sql 将sql语句封装到mappedStatement对象中,所以将id称为statement的id parameterType:指定输入类型参数,这里指定int型 #{}表示一个占位符号 #{id}:其中id表示输入类型的参数,参数名称就是id,如果输入参数是基本类型,#{}中的名可以任意,可以用value或者其他 resultType:指定输出结果所映射的类型,select指定resultType表示将单条记录映射成Java对象 --> <select id="findUserById" parameterType="int" resultType="cn.xwmdream.Main.User"> select * from user where id=#{hhh} </select></mapper> 在映射文件中写入sql语句,并且声明了输入类型和输出类型 使用#{}表示占位符,比如传入了1,那么实际sql为select * from user where id=’1’; 代码1234567891011121314151617181920//mybatis配置文件String resource = "SqlMapConfig.xml";//得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);//创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通过工厂得到SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();//通过SqlSession操作数据库//第一个参数:映射文件中statement的id,命名空间.id//第二个参数:指定映射文件中所匹配的paramenterType类型的参数,这个案例的参数是int类型,所以传入一个数字就行//结果是和映射文件中resultType类型的对象//查询的是一条记录User user = sqlSession.selectOne("test.findUserById", 1);System.out.println(user);//释放资源sqlSession.close(); 根据用户名称模糊查询用户名映射文件123456<!-- 根据用户名称模糊查询用户信息,可能返回多条resultType:指定就是单条记录所对应的Java类型,也就是返回List<User>类型,但是这里只用指定User即可 --><select id="findUserByName" parameterType="java.lang.String" resultType="cn.xwmdream.Main.User"> select * from user where username like #{hhh}</select> 使用了like进行模糊匹配,这是传入的参数需要用%等一些占位符 运行12345678910111213141516171819//mybatis配置文件String resource = "SqlMapConfig.xml";//得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);//创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通过工厂得到SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();//通过SqlSession操作数据库//第一个参数:映射文件中statement的id,命名空间.id//第二个参数:指定映射文件中所匹配的paramenterType类型的参数,这个案例的参数是int类型,所以传入一个数字就行//结果是和映射文件中resultType类型的对象List<User> user = sqlSession.selectList("test.findUserByName", "%小%");System.out.println(user);//释放资源sqlSession.close(); 使用了selectList返回List 因为是模糊查询,所以需要% 另一种方法 上面的话每一个参数都要加%,试想可不可以把%写到sql语句中 修改映射文件 123<select id="findUserByName" parameterType="java.lang.String" resultType="cn.xwmdream.Main.User"> select * from user where username like '%${value}%'</select> 在映射文件中用${}代替#{},表示拼接的意思,就是把传入的值不加任何修饰拼接到sql串中 如果传入的是基本类型,那么${}中只能使用value 但是这样会引起sql注入,所以不建议使用 添加用户映射文件1234567<!-- 添加用户parameterType:指定输入 参数类型是pojo(包括 用户信息)#{}中指定pojo的属性名,接收到pojo对象的属性值,mybatis通过OGNL获取对象的属性值--><insert id="insertUser" parameterType="cn.xwmdream.Main.User"> insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address})</insert> 运行123456789101112131415161718//mybatis配置文件String resource = "SqlMapConfig.xml";//得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);//创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通过工厂得到SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();User user = new User("杰西","0",new Date(),"美国加拿大");//通过SqlSession操作数据库//第一个参数:映射文件中statement的id,命名空间.id//第二个参数:指定映射文件中所匹配的paramenterType类型的参数int ans = sqlSession.insert("test.insertUser",user);System.out.println(ans);//提交sqlSession.commit();//释放资源sqlSession.close(); 查看提交记录的主键对于mysql自增主键,执行提交记录后会生成一个自增主键如果查看自增主键的值,需要在刚刚插入记录之后执行1select LAST_INSERT_ID(); 修改映射文件,使insert之后执行上述命令获取插入的id123456789101112131415161718192021222324252627<!-- 添加用户parameterType:指定输入 参数类型是pojo(包括 用户信息)#{}中指定pojo的属性名,接收到pojo对象的属性值,mybatis通过OGNL获取对象的属性值--><insert id="insertUser" parameterType="cn.xwmdream.Main.User"> <!-- 将插入数据的主键返回,返回到user对象中 SELECT LAST_INSERT_ID():得到刚insert进去记录的主键值,只适用与自增主键 keyProperty:将查询到主键值设置到parameterType指定的对象的哪个属性 order:SELECT LAST_INSERT_ID()执行顺序,相对于insert语句来说它的执行顺序 resultType:指定SELECT LAST_INSERT_ID()的结果类型 --> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> SELECT LAST_INSERT_ID() </selectKey> insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address}) <!-- 使用mysql的uuid()生成主键 执行过程: 首先通过uuid()得到主键,将主键设置到user对象的id属性中 其次在insert执行时,从user对象中取出id属性值 --> <!-- <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String"> SELECT uuid() </selectKey> insert into user(id,username,birthday,sex,address) value(#{id},#{username},#{birthday},#{sex},#{address}) --></insert> 1234567891011121314...//通过工厂得到SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();User user = new User("杰西","0",new Date(),"美国加拿大");//通过SqlSession操作数据库//第一个参数:映射文件中statement的id,命名空间.id//第二个参数:指定映射文件中所匹配的paramenterType类型的参数int ans = sqlSession.insert("test.insertUser",user);System.out.println(ans);//提交sqlSession.commit();System.out.println(user.getId());//释放资源sqlSession.close(); 在insert中增加了一个selectKey指定他的order为AFTER表示在sql执行之后执行那个命令,并且把int类型的返回值储存到id属性中 在java中因为已经把插入的递增主键存入到id中,所以可以通过getId得到插入的id 上述代码中还有一个在insert之前生成一个字符串类型的uuid,并且插入到数据库中 删除和更新用户映射文件123456789101112131415161718<!-- 删除 用户根据id删除用户,需要输入 id值 --><delete id="deleteUser" parameterType="java.lang.Integer"> delete from user where id=#{id}</delete><!-- 根据id更新用户分析:需要传入用户的id需要传入用户的更新信息parameterType指定user对象,包括 id和更新信息,注意:id必须存在#{id}:从输入 user对象中获取id属性值 --><update id="updateUser" parameterType="cn.xwmdream.Main.User"> update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}</update> 运行123456789101112131415161718192021222324252627282930313233// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream);// 通过工厂得到SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();// 更新用户信息User user = new User();//必须设置iduser.setId(41);user.setUsername("王大军");user.setBirthday(new Date());user.setSex("2");user.setAddress("河南郑州");//更新用户信息int ans = sqlSession.update("test.updateUser", user);/*// 传入id删除 用户int ans = sqlSession.delete("test.deleteUser", 39);// 提交事务sqlSession.commit();*/System.out.println(ans);// 提交事务sqlSession.commit();// 关闭会话sqlSession.close(); 入门程序的查询不需要commit,其他都需要commit提交事务 mybatis开发dao的方法SqlSession使用范围SqlSessionFactoryBuilder通过SqlSessionFactoryBuilder创建会话工厂SqlSessionFactory将SqlSessionFactoryBuilder当成一个工具类使用即可,不需要使用单例管理SqlSessionFactoryBuilder。在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。 SqlSessionFactory通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory(工厂一旦创建,使用一个实例)。将来mybatis和spring整合后,使用单例模式管理sqlSessionFactory。 SqlSessionSqlSession是一个面向用户(程序员)的接口。SqlSession中提供了很多操作数据库的方法:如:selectOne(返回单个对象)、selectList(返回单个或多个对象)、。 SqlSession是线程不安全的,在SqlSesion实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性。 SqlSession最佳应用场合在方法体内,定义成局部变量使用。 原始dao开发方法(程序员需要写dao接口和dao实现类)思路程序员需要写dao接口和dao实现类。需要向dao实现类中注入SqlSessionFactory(构造方法注入),在方法体内通过SqlSessionFactory创建SqlSession 接口和实现类1234567891011121314151617public interface UserDao { //根据id查询用户信息 public User findUserById(int id) throws Exception;}public class UserDaoImpl implements UserDao { private SqlSessionFactory sessionFactory; public UserDaoImpl(SqlSessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } @Override public User findUserById(int id) throws Exception { SqlSession session = sessionFactory.openSession(); User user = session.selectOne("test.findUserById",id); session.close(); return user; }} 程序调用123456789// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);UserDao userdao = new UserDaoImpl(sqlSessionFactory);System.out.println(userdao.findUserById(26)); 会显示id为26的User对象 总结原始 dao开发问题 dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。 调用sqlsession方法时将statement的id硬编码了 调用sqlsession方法时传入的变量,由于sqlsession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。 mapper代理程序员还需要编写mapper.xml映射文件程序员编写mapper接口需要遵循一些开发规范,mybatis可以自动生成mapper接口实现类代理对象。 编写mapper映射文件UserMapper.xml123456789101112131415<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- namespace是命名空间,作用就是对sql进行分类化管理,理解为sql隔离注意:使用mapper代理方法开发,namespace必须等于Mapper接口的全限定名 --><mapper namespace="cn.xwmdream.Main.UserMapper"> <select id="findUserById" parameterType="int" resultType="cn.xwmdream.Main.User"> select * from user where id=#{hhh}; </select> <select id="findUserByName" parameterType="java.lang.String" resultType="cn.xwmdream.Main.User"> select * from user where username like #{hhh} </select></mapper> 这个namespace必须等于Mapper接口的全限定名,statement的id必须等于接口的方法名 Mapper接口12345public interface UserMapper { //根据id查询用户信息 public User findUserById(int id) throws Exception; public List<User> findUserByName(String value) throws Exception;} 方法名必须和映射文件中的statement的id相同 方法的输入参数和返回值必须和映射文件的paramenterType和resultType的类型相同 在SqlMapConfig.xml中加载mapper.xml1234567891011<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> ... <!-- 加载映射文件 --> <mappers> <mapper resource = "sqlmap/UserMapper.xml"/> </mappers></configuration> 运行123456789101112131415161718// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//获取session对象SqlSession session = sqlSessionFactory.openSession();//通过getMapper获取接口对象UserMapper userMapper = session.getMapper(UserMapper.class);User user = userMapper.findUserById(24);System.out.println(user);//获取selectList对象List<User> userList = userMapper.findUserByName("%小%");System.out.println(userList);session.close(); mapper开发规范 在mapper.xml中namespace等于mapper接口地址 mapper.java接口中的方法名和mapper.xml中statement的id一致 mapper.java接口中的方法输入参数类型和mapper.xml中statement的parameterType指定的类型一致。 mapper.java接口中的方法返回值类型和mapper.xml中statement的resultType指定的类型一致。 mapper会根据标签是select或者insert调用不同的方法 如果mapper方法返回单个对象(非集合对象),代理对象内部通过selectOne查询数据库。 SqlMapConfig.xmlmybatis的全局配置文件SqlMapConfig.xml,配置内容如下: properties(属性) settings(全局配置参数) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境集合属性对象) environment(环境子属性对象) transactionManager(事务管理) dataSource(数据源) mappers(映射器) properties属性需求:将数据库连接参数单独配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值。在SqlMapConfig.xml中就不需要对数据库连接参数硬编码。 将数据库连接参数只配置在db.properties中,原因:方便对参数进行统一管理,其它xml可以引用该db.properties。db.properties123jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8jdbc.username=rootjdbc.password=xxx 在sqlMapConfig.xml加载属性文件:12345678910111213<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <!-- 加载属性文件 --> <properties resource="db.properties"> <!--properties中还可以配置一些属性名和属性值 --> <!-- <property name="jdbc.driver" value=""/> --> </properties> ...</configuration> 注意: MyBatis 将按照下面的顺序来加载属性: 在 properties 元素体内定义的属性首先被读取。 然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。 最后读取parameterType传递的属性,它会覆盖已读取的同名属性。所以建议parameterType中使用${} 建议: 不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。 在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX settings全局参数配置mybatis框架在运行时可以调整一些运行参数。比如:开启二级缓存、开启延迟加载。。 全局参数将会影响mybatis的运行行为。 typeAliases(别名)重点需求在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。 如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。 mybatis默认支持别名 别名 映射的类型 _byte byte _long long _short short _int int _integer int _double double _float float _boolean boolean string String byte Byte long Long short Short int Integer integer Integer double Double float Float boolean Boolean date Date decimal BigDecimal bigdecimal BigDecimal 自定义别名单个别名定义12345<!-- 针对单个别名定义type:类型的路径alias:别名 --><typeAlias type="cn.xwmdream.Main.User" alias="user"/> 引用别名:123<select id="findUserById" parameterType="int" resultType="user"> select * from user where id=#{hhh};</select> 批量定义别名(常用)1234<!-- 批量别名定义指定包名,mybatis自动扫描包中的po类,自动定义别名,别名就是类名(首字母大写或小写都可以)--><package name="cn.xwmdream.Main"/> 引用别名 123<select id="findUserById" parameterType="int" resultType="user"> select * from user where id=#{hhh};</select> 首字母大写小写都可以 typeHandlers(类型处理器)mybatis中通过typeHandlers完成jdbc类型和java类型的转换。 通常情况下,mybatis提供的类型处理器满足日常需要,不需要自定义. mybatis支持类型处理器: 类型处理器 Java类型 JDBC类型 BooleanTypeHandler Boolean,boolean 任何兼容的布尔值 ByteTypeHandler Byte,byte 任何兼容的数字或字节类型 ShortTypeHandler Short,short 任何兼容的数字或短整型 IntegerTypeHandler Integer,int 任何兼容的数字和整型 LongTypeHandler Long,long 任何兼容的数字或长整型 FloatTypeHandler Float,float 任何兼容的数字或单精度浮点型 DoubleTypeHandler Double,double 任何兼容的数字或双精度浮点型 BigDecimalTypeHandler BigDecimal 任何兼容的数字或十进制小数类型 StringTypeHandler String CHAR和VARCHAR类型 ClobTypeHandler String CLOB和LONGVARCHAR类型 NStringTypeHandler String NVARCHAR和NCHAR类型 NClobTypeHandler String NCLOB类型 ByteArrayTypeHandler byte[] 任何兼容的字节流类型 BlobTypeHandler byte[] BLOB和LONGVARBINARY类型 DateTypeHandler Date(java.util) TIMESTAMP类型 DateOnlyTypeHandler Date(java.util) DATE类型 TimeOnlyTypeHandler Date(java.util) TIME类型 SqlTimestampTypeHandler Timestamp(java.sql) TIMESTAMP类型 SqlDateTypeHandler Date(java.sql) DATE类型 SqlTimeTypeHandler Time(java.sql) TIME类型 ObjectTypeHandler 任意 其他或未指定类型 EnumTypeHandler Enumeration类型 VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)。 mappers(映射配置)通过resource加载单个映射文件和上面一样1234567<configuration> ... <!-- 加载映射文件 --> <mappers> <mapper resource = "sqlmap/UserMapper.xml"/> </mappers></configuration> 通过mapper接口加载单个mapper使用mapper映射的方式,光指定Java接口全限定名即可,但是xml要在同目录同名12345<!-- 通过mapper接口加载单个 映射文件遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录 中上边规范的前提是:使用的是mapper代理方法 --><mapper class="cn.xwmdream.Main.UserMapper"/> 按照上边的规范,将mapper.java和mapper.xml放在一个目录 ,且同名。 批量加载mapper(推荐使用) 和第二种方法一样,用package加载包名,则下面的所有mapper都会被加载 123456<!-- 批量加载mapper指定mapper接口的包名,mybatis自动扫描包下边所有mapper接口进行加载遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录中上边规范的前提是:使用的是mapper代理方法 --><package name="cn.xwmdream.Main"/> 需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录 中 输入映射通过parameterType指定输入参数的类型,类型可以是简单类型、hashmap、pojo的包装类型。 传递pojo的包装对象需求 修改上面的插入用户 这个方法可以用来做多种条件的联合查询(可能包括用户信息、其它信息,比如商品、订单的) 定义包装类型对象UserVo.java12345678910public class UserVo { private User usera; public User getUser() { return usera; } public void setUser(User usera) { this.usera = usera; } //其它类型} 针对上边需求,建议使用自定义的包装类型的对象。 在包装类型的pojo中将复杂的查询条件包装进去。 mapper.xml在UserMapper.xml中定义用户信息综合查询(查询条件复杂,通过高级查询进行复杂关联查询)。123456<insert id="insertUser" parameterType="cn.xwmdream.test.UserVo"> <selectKey keyProperty="usera.id" order="AFTER" resultType="java.lang.Integer"> SELECT LAST_INSERT_ID() </selectKey> insert into user(username,birthday,sex,address) value(#{usera.username},#{usera.birthday},#{usera.sex},#{usera.address})</insert> mapper.java1public int insertUser(UserVo uservo); 测试代码12345678910111213141516// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession session = sqlSessionFactory.openSession();UserMapper userMapper = session.getMapper(UserMapper.class);UserVo uservo = new UserVo();User user = new User("名字","1",new Date(),"ay");uservo.setUser(user);int ans = userMapper.insertUser(uservo);System.out.println(ans);System.out.println(user.getId());session.commit();session.close(); 在mapper.xml中传入了包装类型对象,可以用属性名.属性名的方法使用参数 注意重名问题,比如和自定义名称重名 传递hashmapSql映射文件定义如下:1234<!-- 传递hashmap综合查询用户信息 --><select id="findUserByHashmap" parameterType="hashmap" resultType="user"> select * from user where id=#{id} and username like '%${username}%'</select> 使用的是hashmap的key。 输出映射 输出映射就是返回值映射成什么对象的映射 分为两种,resultType(上面用过好几次了),resultMap(对resultType的增强) resultType映射成简单类型 int型 求总人数 映射文件123<select id="selectCount" resultType="int"> select count(*) from user;</select> 接口以及调用1234567891011121314151617//mapper接口public interface UserMapper { public int selectCount();}//调用// mybatis配置文件String resource = "SqlMapConfig.xml";// 得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);// 创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession session = sqlSessionFactory.openSession();UserMapper userMapper = session.getMapper(UserMapper.class);//能得到一个int类型的数字int ans = userMapper.selectCount();System.out.println(ans);session.close(); 把sql查询出来的count的值以int类型返回 String型 求id为1的人名 映射文件123<select id="findUserNameByid" parameterType="int" resultType="String"> select username from user where id=#{hhh};</select> 接口以及调用12345678910//接口public interface UserMapper { public String findUserNameByid(int id);}//调用...UserMapper userMapper = session.getMapper(UserMapper.class);String ans = userMapper.findUserNameByid(1);System.out.println(ans);session.close(); 把sql查询出来的username以一个String的值传回 自定义类型 求id为1的人的信息,如username,sex,address,birthday 自定义类型12345678public class User { private int id; private String username; private String se; private Date birthday; private String address; //set&get} 映射文件123<select id="findUserById" parameterType="int" resultType="cn.xwmdream.test.User"> select username,sex,address from user where id=#{hhh};</select> 接口以及调用12345678910//接口public interface UserMapper { public User findUserById(int id);}//调用...UserMapper userMapper = session.getMapper(UserMapper.class);User ans = userMapper.findUserById(1);System.out.println(ans);session.close(); 把查询出来的值以同名属性的方式赋值,username赋值给User.username 如果自定义对象中有构造方法,一定要留一个无参的构造方法 问题 没有select的值会赋值为默认空值 select了但是名字不一样的值也会赋值为默认空值,上面案例中自定义属性为se,但是sql中是sex,最后的结果se为null 第二个问题的解决办法可以通过修改sql比如1select username,sex se,address from user where id=#{hhh}; 在sql中把sex对应的名字改成se也可以使用resultMap resultMapmybatis中使用resultMap完成高级输出结果映射。 在Mapper标签内定义一个resultMap1234567891011121314151617181920212223<mapper namespace="cn.xwmdream.Main.UserMapper"> <!-- 定义resultMap 将SELECT id id_,username username_ FROM USER 和User类中的属性作一个映射关系 type:resultMap最终映射的java对象类型,可以使用别名 id:对resultMap的唯一标识 --> <resultMap type="cn.xwmdream.test.User" id="userResultMap"> <!-- id表示查询结果集中唯一标识 如果id字段名字修改,通过这个方式修改 column:查询出来的列名 property:type指定的pojo类型中的属性名 最终resultMap对column和property作一个映射关系 (对应关系) --> <id column="id" property="id"/> <!-- result:对普通名映射定义 column:查询出来的列名 property:type指定的pojo类型中的属性名 最终resultMap对column和property作一个映射关系 (对应关系) --> <result column="sex" property="se"/> </resultMap></mapper> 映射文件中使用resultMap123<select id="findUserById" parameterType="int" resultType="cn.xwmdream.test.User" resultMap="userResultMap"> select user.username,sex,address from user where id=#{hhh};</select> 此时再运行,sex便可以映射成自定义对象中的se 动态sql什么是动态sql mybatis核心 对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。 需求 通过id查人信息,如果id为0则输出所有人信息 映射文件123456789101112<select id="findUserById" parameterType="int" resultType="cn.xwmdream.test.User" resultMap="userResultMap"> select username,sex,address from user <!-- where可以自动去掉条件中的第一个and --> <where> <!-- 如果传入的数字不是0 --> <if test="value!=0"> and id=#{id} </if> </where></select> 如果是基本类型,那么要用value代表那个值 上述代码如果传入的值不是0,比如是1,那么最后的sql是select username,sex,address from user where id=1; 如果传入的id是0,那么最后的sql是select username,sex,address,from user;即可找到所有的用户 如果传入的是自定义对象1234567891011<select id="findUserById" parameterType="cn.xwmdream.test.User" resultType="cn.xwmdream.test.User" resultMap="userResultMap"> select user.username,sex,address from user <where> <if test="id!=0"> and id=#{id} </if> <if test="username!=null"> and username=#{username} </if> </where></select> 如果传入的User.id不是0且User.username不是null,最后的sql为 1select user.username,sex,address from user where id=1 and username='user'; 如果传入的id不是0,但是username是null,最后sql为 1select user.username,sex,address from user where id=1; sql片段 将上边实现的动态sql判断代码块抽取出来,组成一个sql片段。其它的statement中就可以引用sql片段。方便程序员进行开发。 #### 定义sql片段12345678910111213<mapper namespace="cn.xwmdream.Main.UserMapper"> <sql id="query_user_where"> <if test="id!=0"> and id=#{id} </if> <if test="username!=null"> and username=#{username} </if> </sql> <sql id="two"> select username,sex,address from user </sql></mapper> 调用123456<select id="findUserById" parameterType="cn.xwmdream.test.User" resultType="cn.xwmdream.test.User"> <include refid="two"></include> <where> <include refid="query_user_where"></include> </where></select> foreach就是用foreach循环对传入的列表对象拼接成字符串1234567891011121314151617181920<!-- 使用 foreach遍历传入idscollection:指定输入 对象中集合属性item:每个遍历生成对象中open:开始遍历时拼接的串close:结束遍历时拼接的串separator:遍历的两个对象中需要拼接的串 --> <!-- 使用实现下边的sql拼接: AND (id=1 OR id=10 OR id=16) --><foreach collection="ids" item="user_id" open="AND (" close=")" separator="or"> <!-- 每个遍历需要拼接的串 --> id=#{user_id}</foreach><!-- 实现 “ and id IN(1,10,16)”拼接 --><foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=","> <!-- 每个遍历需要拼接的串 --> #{user_id}</foreach> 第一个结果是AND (id=1 OR id=10 OR id=16) 第二个结果是and id IN(1,10,16) 高级用法数据库1234567891011121314151617181920212223242526272829303132333435363738CREATE TABLE `items` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL COMMENT '商品名称', `price` float(10,1) NOT NULL COMMENT '商品定价', `detail` text COMMENT '商品描述', `pic` varchar(64) DEFAULT NULL COMMENT '商品图片', `createtime` datetime NOT NULL COMMENT '生产日期', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;CREATE TABLE `orderdetail` ( `id` int(11) NOT NULL AUTO_INCREMENT, `orders_id` int(11) NOT NULL COMMENT '订单id', `items_id` int(11) NOT NULL COMMENT '商品id', `items_num` int(11) DEFAULT NULL COMMENT '商品购买数量', PRIMARY KEY (`id`), KEY `FK_orderdetail_1` (`orders_id`), KEY `FK_orderdetail_2` (`items_id`), CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '下单用户id', `number` varchar(32) NOT NULL COMMENT '订单号', `createtime` datetime NOT NULL COMMENT '创建订单时间', `note` varchar(100) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`), KEY `FK_orders_1` (`user_id`), CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL COMMENT '用户名称', `birthday` date DEFAULT NULL COMMENT '生日', `sex` char(1) DEFAULT NULL COMMENT '性别', `address` varchar(256) DEFAULT NULL COMMENT '地址', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8; 订单表:orders记录了用户所创建的订单(购买商品的订单) 订单明细表:orderdetail:记录了订单的详细信息即购买商品的信息 商品表:items记录了商品信息 用户表user:记录了购买商品的用户信息 数据1234insert into `items`(`id`,`name`,`price`,`detail`,`pic`,`createtime`) values (1,'台式机',3000.0,'该电脑质量非常好!!!!',NULL,'2015-02-03 13:22:53'),(2,'笔记本',6000.0,'笔记本性能好,质量好!!!!!',NULL,'2015-02-09 13:22:57'),(3,'背包',200.0,'名牌背包,容量大质量好!!!!',NULL,'2015-02-06 13:23:02');insert into `orderdetail`(`id`,`orders_id`,`items_id`,`items_num`) values (1,3,1,1),(2,3,2,3),(3,4,3,4),(4,4,2,3);insert into `orders`(`id`,`user_id`,`number`,`createtime`,`note`) values (3,1,'1000010','2015-02-04 13:22:35',NULL),(4,1,'1000011','2015-02-03 13:22:41',NULL),(5,10,'1000012','2015-02-12 16:13:23',NULL);insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (1,'王五',NULL,'2',NULL),(10,'张三','2014-07-10','1','北京市'),(16,'张小明',NULL,'1','河南郑州'),(22,'陈小明',NULL,'1','河南郑州'),(24,'张三丰',NULL,'1','河南郑州'),(25,'陈小明',NULL,'1','河南郑州'),(26,'王五',NULL,NULL,NULL); 关联查询一对一查询需求 查询订单信息,关联查询创建订单的用户信息 创建bean类User.java1234567public class User { private Integer id; private String username; private String sex; private String address; //...getter&setter} Orders.java123456789public class Orders { private Integer id; private Integer userId; private String number; private Date createtime; private String note; private User user; //...getter&setter} Orders类是结果的映射,但是这个映射中有一个User对象,接下来就需要用resultMap来创建映射规则 mapper1234567891011121314151617181920212223242526<mapper namespace="cn.xwmdream.mapper.MyMapper"> <resultMap type="cn.xwmdream.bean.Orders" id="OrdersUserResultMap"> <!-- 配置映射的订单信息 --> <!-- id:指定查询列中的唯 一标识,订单信息的中的唯 一标识,如果有多个列组成唯一标识,配置多个id column:订单信息的唯 一标识 列 property:订单信息的唯 一标识 列所映射到Orders中哪个属性 --> <id column="id" property="id" /> <result column="user_id" property="userId" /> <result column="number" property="number" /> <result column="createtime" property="createtime" /> <result column="note" property="note" /> <!-- 配置映射的关联的用户信息 --> <!-- association:用于映射关联查询单个对象的信息 property:要将关联查询的用户信息映射到Orders中哪个属性 --> <association property="user" javaType="cn.xwmdream.bean.User"> <!-- id:关联查询用户的唯 一标识 column:指定唯 一标识用户信息的列 javaType:映射到user的哪个属性 --> <id column="user_id" property="id" /> <result column="username" property="username" /> <result column="sex" property="sex" /> <result column="address" property="address" /> </association> </resultMap> <select id="findOrdersUserResultMap" resultType="cn.xwmdream.bean.Orders" resultMap="OrdersUserResultMap"> select orders.*,user.username,user.sex,user.address from orders,user where orders.user_id=user.id; </select></mapper> 接口MyMapper.java123public interface MyMapper { public List<Orders> findOrdersUserResultMap();} 运行12345678910111213//mybatis配置文件String resource = "SqlMapConfig.xml";//得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);//创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通过工厂得到SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();MyMapper mapper = sqlSession.getMapper(MyMapper.class);List<Orders> list = mapper.findOrdersUserResultMap();System.out.println(list);//释放资源sqlSession.close(); 一对多查询需求 查询订单及订单明细的信息。 sql语句 确定主查询表:订单表 确定关联查询表:订单明细表 在一对一查询基础上添加订单明细表关联即可。1SELECT orders.*,USER.username,USER.sex,USER.address,orderdetail.id orderdetail_id,orderdetail.items_id,orderdetail.items_num,orderdetail.orders_id FROM orders,USER,orderdetail WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id 要求: 对orders映射不能出现重复记录。 在orders.java类中添加List orderDetails属性。 最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的orderDetails属性中。 bean类创建Orderdetail.java1234567public class Orderdetail { private Integer id; private Integer itemsId; private String itemsNum; private Integer ordersId; //...getter&setter} 修改Orders.java12345public class Orders { //...多加一条属性 private List<Orderdetail> orderdetails; ///...getter&setter} mapper映射1234567891011121314151617181920212223242526272829303132333435363738394041<mapper namespace="cn.xwmdream.mapper.MyMapper"> <resultMap type="cn.xwmdream.bean.Orders" id="OrdersUserResultMap"> <!-- 配置映射的订单信息 --> <!-- id:指定查询列中的唯 一标识,订单信息的中的唯 一标识,如果有多个列组成唯一标识,配置多个id column:订单信息的唯 一标识 列 property:订单信息的唯 一标识 列所映射到Orders中哪个属性 --> <id column="id" property="id" /> <result column="user_id" property="userId" /> <result column="number" property="number" /> <result column="createtime" property="createtime" /> <result column="note" property="note" /> <!-- 配置映射的关联的用户信息 --> <!-- association:用于映射关联查询单个对象的信息 property:要将关联查询的用户信息映射到Orders中哪个属性 --> <association property="user" javaType="cn.xwmdream.bean.User"> <!-- id:关联查询用户的唯 一标识 column:指定唯 一标识用户信息的列 javaType:映射到user的哪个属性 --> <id column="user_id" property="id" /> <result column="username" property="username" /> <result column="sex" property="sex" /> <result column="address" property="address" /> </association> <!-- 订单明细信息 一个订单关联查询出了多条明细,要使用collection进行映射 collection:对关联查询到多条记录映射到集合对象中 property:将关联查询到多条记录映射到cn.itcast.mybatis.po.Orders哪个属性 ofType:指定映射到list集合属性中pojo的类型 --> <collection property="orderdetails" ofType="cn.xwmdream.bean.Orderdetail"> <!-- id:订单明细唯 一标识 property:要将订单明细的唯 一标识 映射到cn.xwmdream.bean.Orderdetail的哪个属性 --> <id column="orderdetail_id" property="id"/> <result column="items_id" property="itemsId"/> <result column="items_num" property="itemsNum"/> <result column="orders_id" property="ordersId"/> </collection> </resultMap> <select id="findOrdersUserResultMap" resultType="cn.xwmdream.bean.Orders" resultMap="OrdersUserResultMap"> SELECT orders.*,USER.username,USER.sex,USER.address,orderdetail.id orderdetail_id,orderdetail.items_id,orderdetail.items_num,orderdetail.orders_id FROM orders,USER,orderdetail WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id </select></mapper> 其中orderdetail.id的名字是id和之前的orders.id重名,所以要改名orderdetail.id orderdetail_id 小结mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。使用resultType实现:将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。 多对多查询需求 查询用户及用户购买商品信息。 sql语句查询主表是:用户表关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表:orders、orderdetail、items 1SELECT orders.*,USER.username,USER.sex,USER.address,orderdetail.id orderdetail_id,orderdetail.items_id,orderdetail.items_num,orderdetail.orders_id,items.name items_name,items.detail items_detail,items.price items_price FROM orders,USER,orderdetail,items WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id; 查询主体是用户表,一个用户有多个订单,一个订单有多个订单明细,一个订单明细有一个商品 bean类User类12345678public class User { private Integer id; private String username; private String sex; private String address; private List<Orders> ordersList;//...getter&setter&toString} Orders类123456789public class Orders { private Integer id; private Integer userId; private String number; private Date createtime; private String note; private List<Orderdetail> orderdetails;//...getter&setter&toString} Orderdetail类12345678public class Orderdetail { private Integer id; private Integer itemsId; private String itemsNum; private Integer ordersId; private Item item;//...getter&setter&toString} Item类1234567public class Item { private Integer id; private String detail; private float price; private String name;//...getter&setter&toString} 映射mapper123456789101112131415161718192021222324252627282930313233343536373839<mapper namespace="cn.xwmdream.mapper.MyMapper"> <resultMap type="cn.xwmdream.bean.User" id="OrdersUserResultMap"> <id column="user_id" property="id" /> <result column="username" property="username" /> <result column="sex" property="sex" /> <result column="address" property="address" /> <collection property="ordersList" ofType="cn.xwmdream.bean.Orders"> <id column="id" property="id" /> <result column="user_id" property="userId" /> <result column="number" property="number" /> <result column="createtime" property="createtime" /> <result column="note" property="note" /> <collection property="orderdetails" ofType="cn.xwmdream.bean.Orderdetail"> <id column="orderdetail_id" property="id" /> <result column="items_id" property="itemsId" /> <result column="items_num" property="itemsNum" /> <result column="orders_id" property="ordersId" /> <association property="item" javaType="cn.xwmdream.bean.Item"> <id column="items_id" property="id" /> <result column="items_name" property="name" /> <result column="items_detail" property="detail" /> <result column="items_price" property="price" /> </association> </collection> </collection> </resultMap> <select id="findUserAndItemsResultMap" resultType="cn.xwmdream.bean.Orders" resultMap="OrdersUserResultMap"> SELECT orders.*,USER.username,USER.sex,USER.address,orderdetail.id orderdetail_id,orderdetail.items_id,orderdetail.items_num,orderdetail.orders_id,items.name items_name,items.detail items_detail,items.price items_price FROM orders,USER,orderdetail,items WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id; </select></mapper> 延迟加载延迟加载:先从单表查询、需要时再从关联表去关联查询,不需要就不查询了,大大提高 数据库性能,因为查询单表要比关联查询多张表速度要快。例如要查询所有订单,并且提供查看订单用户的功能,但是并不是所有的订单都去看用户,只用点谁的订单才看谁的用户信息,这是就可以把用户信息来延迟加载 延迟加载配置mybatis默认没有开启延迟加载,需要在SqlMapConfig.xml中setting配置。 在mybatis核心配置文件(SqlMapConfig.xml)中配置:lazyLoadingEnabled、aggressiveLazyLoading 设置项 描述 允许值 默认值 lazyLoadingEnabled 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 true|false false aggressiveLazyLoading 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 true|false true SqlMapConfig.xml123456789<configuration> <!-- 全局配置参数,需要时再设置 --> <settings> <!-- 打开延迟加载 的开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 将积极加载改为消极加载即按需要加载 --> <setting name="aggressiveLazyLoading" value="false"/> </settings></configuration> 延迟加载例子 需求:查询所有订单,并查看订单用户 bean类 订单:Orders.java 123456789public class Orders { private Integer id; private Integer userId; private String number; private Date createtime; private String note; private User user; //getter&setter&toString} 用户:User.java 1234567public class User { private Integer id; private String username; private String sex; private String address; //getter&setter&toString} mapper映射MyMapper.java1234public interface MyMapper { public List<Orders> getAllOrders(); public User getUserById(int id);} MyMapper.xml123456789101112131415161718192021<mapper namespace="cn.xwmdream.mapper.MyMapper"> <resultMap type="cn.xwmdream.bean.Orders" id="OrdersUserResultMap"> <id column="id" property="id" /> <!-- 实现对用户信息进行延迟加载 select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement) 要使用userMapper.xml中findUserById完成根据用户id(user_id)用户信息的查询,如果findUserById不在本mapper中需要前边加namespace column:订单信息中关联用户信息查询的列,是user_id,相当于是传入第二个sql的值 关联查询的sql理解为: SELECT orders.*, (SELECT username FROM USER WHERE orders.user_id = user.id)username, (SELECT sex FROM USER WHERE orders.user_id = user.id)sex FROM orders --> <association property="user" javaType="cn.xwmdream.bean.User" select="getUserById" column="user_id"> </association> </resultMap> <select id="getUserById" parameterType="int" resultType="cn.xwmdream.bean.User"> select * from user where id=#{value} </select> <select id="getAllOrders" resultType="cn.xwmdream.bean.Orders" resultMap="OrdersUserResultMap"> select * from orders </select></mapper> 运行12345678910111213141516//mybatis配置文件String resource = "SqlMapConfig.xml";//得到配置文件流InputStream inputStream = Resources.getResourceAsStream(resource);//创建会话工厂,传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//通过工厂得到SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();MyMapper mapper = sqlSession.getMapper(MyMapper.class);List<Orders> list = mapper.getAllOrders();System.out.println(list);//释放资源sqlSession.close(); 刚开始调用getAllOrders只调用那一句select * from orders,不会调用所有的用户信息 当获取到的Orders对象去访问他的user属性时,才会去调用select * from user where id =?根据Orders的user_id获取到相应的用户信息 替代方法 其实就是可以先获取到所有的Orders,再根据需求调用他的User信息 mybatis就是把这两种合成一个了,把User当成Orders的一个属性,当调用这个属性时再去请求User信息,不调用就不请求了 查询缓存什么是查询缓存 mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。 mybaits提供一级缓存,和二级缓存 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。 如果缓存中有数据就不用从数据库中获取,大大提高系统性能 一级缓存 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。 一级缓存应用正式开发,是将mybatis和spring进行整合开发,事务控制在service中。一个service方法中包括 很多mapper方法调用。1234567service{ //开始执行时,开启事务,创建SqlSession对象 //第一次调用mapper的方法findUserById(1) //第二次调用mapper的方法findUserById(1),从一级缓存中取数据 //方法结束,sqlSession关闭} 如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。 二级缓存原理 首先开启mybatis的二级缓存。sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。 二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。UserMapper有一个二级缓存区域(按namespace分) ,其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中。 开启二级缓存 SqlMapConfig.xml12345678<configuration>... <!-- 全局配置参数,需要时再设置 --> <settings> <!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/> </settings></configuration> 方法 描述 允许值 默认值 cacheEnabled 对在此配置文件下的所有cache进行全局性开/关设置。 true false true mapper.xml在mapper对namespace开启二级缓存1234567<mapper namespace="xxxx"> <!-- 开启本mapper的namespace下的二缓存 type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache 要和ehcache整合,需要配置type为ehcache实现cache接口的类型 --> <cache type="xxx可以不写"/></mapper> useCache配置在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。1<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false"> 总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。 刷新缓存(就是清空缓存)在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。设置statement配置中的flushCache=”true” 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。如下:1<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true"> 总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。 二级应用场景 对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。 实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。 二级缓存局限性 mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。 spring和mybatis整合整合思路 需要spring通过单例方式管理SqlSessionFactory。 spring和mybatis整合生成代理对象,使用SqlSessionFactory创建SqlSession。(spring和mybatis整合自动完成) 持久层的mapper都需要由spring进行管理。 整合环境 mybatis用到的jar spring用到的logger 数据库jdbc用到的jar spring用到的jar dbcp spring的tx包 mybatis-spring sqlSessionFactory 在applicationContext.xml配置sqlSessionFactory和数据源 sqlSessionFactory在mybatis和spring的整合包下。 ApplicationContext.xml12345678910111213141516171819202122232425262728293031323334353637<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- 加载配置文件 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 数据源,使用dbcp --> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <!-- sqlSessinFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 加载mybatis的配置文件 --> <property name="configLocation" value="SqlMapConfig.xml" /> <!-- 数据源 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 原始dao接口 --> <bean id="userDao" class="cn.xwmdream.dao.UserDaoImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean></beans> 在src下创建一个properties文件,写入数据库的信息db.properties1234jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/mybatisjdbc.username=rootjdbc.password=x 用原始dao开发配置SqlMapConfig.xml123456789101112<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <typeAliases> <package name="cn.xwmdream.bean"/> </typeAliases> <mappers> <mapper resource="sqlmap/User.xml"/> </mappers></configuration> 配置User.xmlsqlmap/User.xml12345678910<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="test"> <select id="findUserById" parameterType="int" resultType="cn.xwmdream.bean.User"> SELECT * FROM USER WHERE id=#{value} </select></mapper> 创建bean类12345678public class User { private int id; private String username; private Date birthday; private String sex; private String address; //...getter&setter&tostring} 配置dao接口以及实现类1234567891011public interface UserDao { public User findUserById(int id);}public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao{ @Override public User findUserById(int id) { SqlSession sqlSession = this.getSqlSession(); User user = sqlSession.selectOne("test.findUserById",id); return user; }} 测试代码12345ApplicationContext applicationContext= new ClassPathXmlApplicationContext("classpath:applicationContext.xml");;UserDao userDao = (UserDao) applicationContext.getBean("userDao");//调用userDao的方法User user = userDao.findUserById(1);System.out.println(user); 用mapper开发创建mapper以及对应文件 要遵循规范,在同一个包下,同名UserMapper.xml123456789<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="cn.xwmdream.mapper.UserMapper"> <select id="findUserById" parameterType="int" resultType="cn.xwmdream.bean.User"> SELECT * FROM USER WHERE id=#{value} </select></mapper> UserMapper.java123public interface UserMapper { public User findUserById(int id);} 使用spring获取mapper在ApplicationContext.xml增加bean1234567891011<!-- mapper批量扫描,从mapper包中扫描出mapper接口,自动创建代理对象并且在spring容器中注册遵循规范:将mapper.java和mapper.xml映射文件名称保持一致,且在一个目录 中自动扫描出来的mapper的bean的id为mapper类名(首字母小写)--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 指定扫描的包名 如果扫描多个包,每个包中间使用半角逗号分隔 --> <property name="basePackage" value="cn.xwmdream.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/></bean> 会把cn.xwmdream.mapper下所有符合条件的mapper添加到spring容器中 自动扫描出来的mapper的bean的id为mapper类名(首字母小写) 修改SqlMapConfig.xml 因为mapper类已经让spring扫描出来了,所以在SqlMapConfig中就不用扫描了 123456<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration></configuration> 就是啥都没 #### 运行12345ApplicationContext applicationContext= new ClassPathXmlApplicationContext("classpath:applicationContext.xml");;UserMapper userDao = (UserMapper) applicationContext.getBean("userMapper");//调用userDao的方法User user = userDao.findUserById(1);System.out.println(user); 注意bean名首字母小写]]></content>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android自定义View学习2-绘制(Canvas+paint)]]></title>
<url>%2F2019%2F01%2F31%2Fandroid%E8%87%AA%E5%AE%9A%E4%B9%89View%E5%AD%A6%E4%B9%A02-%E7%BB%98%E5%88%B6(Canvas%2Bpaint)%2F</url>
<content type="text"><![CDATA[参考hencoder(极力推荐,讲得很细致,本文只是记录我学到的东西,不全,建议看这篇文章)文档Canvas文档paint 自定义绘制 自定义绘制的方式是重写绘制方法,其中最常用的是 onDraw() 绘制的关键是 Canvas 的使用 Canvas 的绘制类方法: drawXXX() (关键参数:Paint) Canvas 的辅助类方法:范围裁切和几何变换 可以使用不同的绘制方法来控制遮盖关系 Canvas确定画什么内容,paint决定怎么画 CanvasCanvas从名字来看是画布,在安卓中他是一个绘制工具,就是用来绘制 常用的几个绘制方法: 方法 作用 drawColor 颜色填充 drawCircle 画圆 drawRect 画矩形 drawPoint 画点 drawPoints 批量画点 drawOval 画椭圆 drawLine 画线 drawLines 批量画线 drawRoundRect 画圆角矩形 drawArc 画弧形或者扇形 drawBitmap() 画一个Bitmap drawText() 画文字 drawPath 画自定义图形 Canvas.drawColor(@ColorInt int color) 颜色填充这是最基本的 drawXXX() 方法:在整个绘制区域统一涂上指定的颜色。 例如 drawColor(Color.BLACK) 会把整个区域染成纯黑色,覆盖掉原有内容; drawColor(Color.parse(“#88880000”) 会在原有的绘制效果上加一层半透明的红色遮罩。 12drawColor(Color.BLACK); // 纯黑drawColor(Color.parse("#88880000")); // 半透明红色 类似的方法还有 drawRGB(int r, int g, int b) 和 drawARGB(int a, int r, int g, int b) ,它们和 drawColor(color) 只是使用方式不同,作用都是一样的。 1drawColor(Color.parse("#88880000")); // 半透明红色 drawCircle(float centerX, float centerY, float radius, Paint paint) 画圆前两个参数 centerX centerY 是圆心的坐标,第三个参数 radius 是圆的半径,单位都是像素,它们共同构成了这个圆的基本信息(即用这几个信息可以构建出一个确定的圆);第四个参数 paint 它提供基本信息之外的所有风格信息,例如颜色、线条粗细、阴影等。1canvas.drawCircle(300, 300, 200, paint); drawRect(float left, float top, float right, float bottom, Paint paint) 画矩形left, top, right, bottom 是矩形四条边的坐标。12345paint.setStyle(Style.FILL);canvas.drawRect(100, 100, 500, 500, paint);//画一个实心矩形paint.setStyle(Style.STROKE);canvas.drawRect(700, 100, 1100, 500, paint);//画一个空心矩形 另外,它还有两个重载方法 drawRect(RectF rect, Paint paint) 和 drawRect(Rect rect, Paint paint) ,让你可以直接填写 RectF 或 Rect 对象来绘制矩形。 drawPoint(float x, float y, Paint paint) 画点x 和 y 是点的坐标。点的大小可以通过 paint.setStrokeWidth(width) 来设置;点的形状可以通过 paint.setStrokeCap(cap) 来设置:ROUND 画出来是圆形的点,SQUARE 或 BUTT 画出来是方形的点。 注:Paint.setStrokeCap(cap) 可以设置点的形状,但这个方法并不是专门用来设置点的形状的,而是一个设置线条端点形状的方法。端点有圆头 (ROUND)、平头 (BUTT) 和方头 (SQUARE) 三种12345678paint.setStrokeWidth(20);paint.setStrokeCap(Paint.Cap.ROUND);canvas.drawPoint(50, 50, paint);//画一个半径是20的圆点paint.setStrokeWidth(20);paint.setStrokeCap(Paint.Cap.SQUARE);canvas.drawPoint(50, 50, paint);//画一个半径是20的方点 好像有点像 FILL 模式下的 drawCircle() 和 drawRect() ?事实上确实是这样的,它们和 drawPoint() 的绘制效果没有区别。各位在使用的时候按个人习惯和实际场景来吧,哪个方便和顺手用哪个。 drawPoints(float[] pts, int offset, int count, Paint paint) / drawPoints(float[] pts, Paint paint) 画点(批量)同样是画点,它和 drawPoint() 的区别是可以画多个点。pts 这个数组是点的坐标,每两个成一对;offset 表示跳过数组的前几个数再开始记坐标;count 表示一共要绘制几个点。说这么多你可能越读越晕,你还是自己试试吧,这是个看着复杂用着简单的方法。123float[] points = {0, 0, 50, 50, 50, 100, 100, 50, 100, 100, 150, 50, 150, 100};// 绘制四个点:(50, 50) (50, 100) (100, 50) (100, 100)canvas.drawPoints(points, 2 /* 跳过两个数,即前两个 0 */,8 /* 一共绘制 8 个数(4 个点)*/, paint); drawOval(float left, float top, float right, float bottom, Paint paint) 画椭圆只能绘制横着的或者竖着的椭圆,不能绘制斜的(斜的倒是也可以,但不是直接使用 drawOval(),而是配合几何变换,后面会讲到)。left, top, right, bottom 是这个椭圆的左、上、右、下四个边界点的坐标。12345paint.setStyle(Style.FILL);canvas.drawOval(50, 50, 350, 200, paint);//画一个实心椭圆paint.setStyle(Style.STROKE);canvas.drawOval(400, 50, 700, 200, paint);//画一个空心椭圆 另外,它还有一个重载方法 drawOval(RectF rect, Paint paint),让你可以直接填写 RectF 来绘制椭圆。 drawLine(float startX, float startY, float stopX, float stopY, Paint paint) 画线startX, startY, stopX, stopY 分别是线的起点和终点坐标。1canvas.drawLine(200, 200, 800, 500, paint); 由于直线不是封闭图形,所以 setStyle(style) 对直线没有影响。 drawLines(float[] pts, int offset, int count, Paint paint) / drawLines(float[] pts, Paint paint) 画线(批量)drawLines() 是 drawLine() 的复数版。12float[] points = {20, 20, 120, 20, 70, 20, 70, 120, 20, 120, 120, 120, 150, 20, 250, 20, 150, 20, 150, 120, 250, 20, 250, 120, 150, 120, 250, 120};canvas.drawLines(points, paint); drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) 画圆角矩形left, top, right, bottom 是四条边的坐标,rx 和 ry 是圆角的横向半径和纵向半径。1canvas.drawRoundRect(100, 100, 500, 300, 50, 50, paint); 另外,它还有一个重载方法 drawRoundRect(RectF rect, float rx, float ry, Paint paint),让你可以直接填写 RectF 来绘制圆角矩形。 drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 绘制弧形或扇形drawArc() 是使用一个椭圆来描述弧形的。left, top, right, bottom 描述的是这个弧形所在的椭圆;startAngle 是弧形的起始角度(x 轴的正向,即正右的方向,是 0 度的位置;顺时针为正角度,逆时针为负角度),sweepAngle 是弧形划过的角度;useCenter 表示是否连接到圆心,如果不连接到圆心,就是弧形,如果连接到圆心,就是扇形。12345paint.setStyle(Paint.Style.FILL); // 填充模式canvas.drawArc(200, 100, 800, 500, -110, 100, true, paint); // 绘制扇形canvas.drawArc(200, 100, 800, 500, 20, 140, false, paint); // 绘制弧形paint.setStyle(Paint.Style.STROKE); // 画线模式canvas.drawArc(200, 100, 800, 500, 180, 60, false, paint); // 绘制不封口的弧形 到此为止,以上就是 Canvas 所有的简单图形的绘制。除了简单图形的绘制, Canvas 还可以使用 drawPath(Path path) 来绘制自定义图形。 drawPath(Path path, Paint paint) 画自定义图形这个方法是先用path指定一个要画的路径,然后画出来比如画一个心12345// 使用 path 对图形进行描述(这段描述代码不必看懂)path.addArc(200, 200, 400, 400, -225, 225);path.arcTo(400, 200, 600, 400, -180, 225, false);path.lineTo(400, 542);canvas.drawPath(path,paint); 先用path绘制出新型的路径,然后画出来path具体使用看本章以下内容 drawBitmap(Bitmap bitmap, float left, float top, Paint paint) 画 Bitmap绘制 Bitmap 对象,也就是把这个 Bitmap 中的像素内容贴过来。其中 left 和 top 是要把 bitmap 绘制到的位置坐标。它的使用非常简单。 drawBitmap(bitmap, 200, 100, paint);12Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),R.mipmap.ic_launcher);canvas.drawBitmap(bitmap,0,0,paint); 它的重载方法: drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) drawBitmap 还有一个兄弟方法 drawBitmapMesh(),可以绘制具有网格拉伸效果的 Bitmap drawText(String text, float x, float y, Paint paint) 绘制文字界面里所有的显示内容,都是绘制出来的,包括文字。 drawText() 这个方法就是用来绘制文字的。参数 text 是用来绘制的字符串,x 和 y 是绘制的起点坐标。 canvas.drawText(text, 200, 100, paint);12String text = "你好,hello,拜拜,bye";canvas.drawText(text,100,100,paint); 还能设置多种字体,具体请看paint的相关用法 绘制范围的裁切绘制内容的几何变换 就是绘制四个顶点的变换 PaintPaint从名字看是颜料,在安卓中加强版颜料,比如颜色,是否空心,风格,阴影等 Paint.setColor(int color)/setARGB(int a, int r, int g, int b)设置颜色12paint.setColor(Color.RED); // 设置为红色canvas.drawCircle(300, 300, 200, paint); //画一个坐标300*300半径200的红色圆 setShader(Shader shader) 设置 Shader(着色器)LinearGradient 线性渐变123Shader shader = new LinearGradient(300, 100, 300, 500, Color.RED,Color.GREEN, Shader.TileMode.CLAMP);paint.setShader(shader);canvas.drawCircle(300, 300, 200, paint); 前四个参数表示两个点的x和y坐标,第五六个参数表示前面两个点的颜色,最后一个参数表示端点范围之外的着色规则 端点范围之外的着色规则一共有 3 个值可选: CLAMP, MIRROR 和 REPEAT。CLAMP会在端点之外延续端点处的颜色;MIRROR 是镜像模式;REPEAT 是重复模式。CLAMP:MIRROR:REPEAT: RadialGradient 辐射渐变辐射渐变很好理解,就是从中心向周围辐射状的渐变123Shader shader = new RadialGradient(300, 300, 200,Color.RED,Color.GREEN, Shader.TileMode.CLAMP);paint.setShader(shader);canvas.drawCircle(300, 300, 200, paint); centerX centerY:辐射中心的坐标 radius:辐射半径 centerColor:辐射中心的颜色 edgeColor:辐射边缘的颜色 tileMode:辐射范围之外的着色模式。CLAMP:MIRROR:REPEAT: SweepGradient 扫描渐变1Shader shader = new SweepGradient(300, 300, Color.parseColor("#E91E63"),Color.parseColor("#2196F3")); cx cy :扫描的中心 color0:扫描的起始颜色 color1:扫描的终止颜色 BitmapShader(图着色)用 Bitmap 来着色:1234Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.batman);Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);paint.setShader(shader);canvas.drawCircle(300, 300, 200, paint) bitmap:用来做模板的 Bitmap 对象 tileX:横向的 TileMode tileY:纵向的 TileMode。CLAMP:MIRROR:REPEAT: ComposeShader 混合着色器所谓混合,就是把两个 Shader 一起使用。12345Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);Shader shader1 = new SweepGradient(200, 200, Color.parseColor("#E91E63"), Color.parseColor("#2196F3"));Shader shader2 = new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);Shader shader = new ComposeShader(shader1,shader2,PorterDuff.Mode.SRC_OVER);paint.setShader(shader); 上面这段代码我是用了一个渐变着色器和一个图片着色器混合而成的效果 注意,如果是两个相同类型的shader在开启硬件加速的情况下是异常的 shaderA, shaderB:两个相继使用的 Shader mode: 两个 Shader 的叠加模式,即 shaderA 和 shaderB 应该怎样共同绘制。它的类型是 PorterDuff.Mode PorterDuff.ModePorterDuff.Mode 是用来指定两个图像共同绘制时的颜色策略的。它是一个 enum,不同的 Mode 可以指定不同的策略。「颜色策略」的意思,就是说把源图像绘制到目标图像处时应该怎样确定二者结合后的颜色,而对于 ComposeShader(shaderA, shaderB, mode) 这个具体的方法,就是指应该怎样把 shaderB 绘制在 shaderA 上来得到一个结合后的 Shader。最符合直觉的结合策略,就是我在上面这个例子中使用的 Mode: SRC_OVER。它的算法非常直观:就像上面图中的那样,把源图像直接铺在目标图像上。不过,除了这种,其实还有一些其他的结合方式。具体来说, PorterDuff.Mode 一共有 17 个,可以分为两类: Alpha 合成 (Alpha Compositing) 混合 (Blending)第一类,Alpha 合成,其实就是 「PorterDuff」 这个词所指代的算法。 「PorterDuff」 并不是一个具有实际意义的词组,而是两个人的名字(准确讲是姓)。这两个人当年共同发表了一篇论文,描述了 12 种将两个图像共同绘制的操作(即算法)。而这篇论文所论述的操作,都是关于 Alpha 通道(也就是我们通俗理解的「透明度」)的计算的,后来人们就把这类计算称为Alpha 合成 ( Alpha Compositing ) 。源图像和目标图像:Alpha 合成:第二类,混合,也就是 Photoshop 等制图软件里都有的那些混合模式(multiply darken lighten 之类的)。这一类操作的是颜色本身而不是 Alpha 通道,并不属于 Alpha 合成,所以和 Porter 与 Duff 这两个人也没什么关系,不过为了使用的方便,它们同样也被 Google 加进了 PorterDuff.Mode 里。 结论从效果图可以看出,Alpha 合成类的效果都比较直观,基本上可以使用简单的口头表达来描述它们的算法(起码对于不透明的源图像和目标图像来说是可以的),例如 SRC_OVER 表示「二者都绘制,但要源图像放在目标图像的上面」,DST_IN 表示「只绘制目标图像,并且只绘制它和源图像重合的区域」。 setColorFilter(ColorFilter colorFilter)颜色过滤 颜色过滤的意思,就是为绘制的内容设置一个统一的过滤策略,然后 Canvas.drawXXX() 方法会对每个像素都进行过滤后再绘制出来。 颜色效果就像透过一个有色玻璃或者有色光照射 LightingColorFilter简单的光照效果 LightingColorFilter 的构造方法是 LightingColorFilter(int mul, int add) ,参数里的 mul 和 add 都是和颜色值格式相同的 int 值,其中 mul 用来和目标像素相乘,add 用来和目标像素相加: 123R' = R * mul.R / 0xff + add.RG' = G * mul.G / 0xff + add.GB' = B * mul.B / 0xff + add.B 一个「保持原样」的「基本 LightingColorFilter 」,mul 为 0xffffff,add 为 0x000000(也就是0),那么对于一个像素,它的计算过程就是: 123R' = R * 0xff / 0xff + 0x0 = R // R' = RG' = G * 0xff / 0xff + 0x0 = G // G' = GB' = B * 0xff / 0xff + 0x0 = B // B' = B 基于这个「基本 LightingColorFilter 」,你就可以修改一下做出其他的 filter。比如,如果你想去掉原像素中的红色,可以把它的 mul 改为 0x00ffff (红色部分为 0 ) ,那么它的计算过程就是: 123R' = R * 0x0 / 0xff + 0x0 = 0 // 红色被移除G' = G * 0xff / 0xff + 0x0 = GB' = B * 0xff / 0xff + 0x0 = B 12paint.setColorFilter(new LightingColorFilter(0xff00ff,0x000000));//去除颜色中的绿色canvas.drawCircle(200, 650, 200, paint); 设置mul中绿色部分为0,可以去除图片中的绿色,也可以稍微大一些,减弱绿色效果 也可以设置add的值用来加强绿色部分12paint.setColorFilter(new LightingColorFilter(0xffffff,0x005000));canvas.drawCircle(200, 650, 200, paint); PorterDuffColorFilter这个 PorterDuffColorFilter 的作用是使用一个指定的颜色和一种指定的 PorterDuff.Mode 来与绘制对象进行合成。它的构造方法是 PorterDuffColorFilter(int color, PorterDuff.Mode mode) 其中的 color 参数是指定的颜色, mode 参数是指定的 Mode。同样也是 PorterDuff.Mode ,不过和 ComposeShader 不同的是,PorterDuffColorFilter 作为一个 ColorFilter,只能指定一种颜色作为源,而不是一个 Bitmap。 ColorMatrixColorFilter这个就厉害了。ColorMatrixColorFilter 使用一个 ColorMatrix 来对颜色进行处理。 ColorMatrix 这个类,内部是一个 4x5 的矩阵: [ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t ]通过计算, ColorMatrix 可以把要绘制的像素进行转换。对于颜色 [R, G, B, A] ,转换算法是这样的: R’ = aR + bG + cB + dA + e;G’ = fR + gG + hB + iA + j;B’ = kR + lG + mB + nA + o;A’ = pR + qG + rB + sA + t;ColorMatrix 有一些自带的方法可以做简单的转换,例如可以使用 setSaturation(float sat) 来设置饱和度;另外你也可以自己去设置它的每一个元素来对转换效果做精细调整。具体怎样设置会有怎样的效果,我就不讲了(其实是我也不太会)。如果你有需求,可以试一下程大治同学做的这个库:(StyleImageView)[https://github.com/chengdazhi/StyleImageView] 以上,就是 Paint 对颜色的第二层处理:通过 setColorFilter(colorFilter) 来加工颜色。 除了基本颜色的设置( setColor/ARGB(), setShader() )以及基于原始颜色的过滤( setColorFilter() )之外,Paint 最后一层处理颜色的方法是 setXfermode(Xfermode xfermode) ,它处理的是「当颜色遇上 View」的问题。 Paint.setXfermode(Xfermode xfermode)“Xfermode” 其实就是 “Transfer mode”,用 “X” 来代替 “Trans” 是一些美国人喜欢用的简写方式。严谨地讲, Xfermode 指的是你要绘制的内容和 Canvas 的目标位置的内容应该怎样结合计算出最终的颜色。但通俗地说,其实就是要你以绘制的内容作为源图像,以 View 中已有的内容作为目标图像,选取一个 PorterDuff.Mode 作为绘制内容的颜色处理方案。就像这样:123456Xfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);...canvas.drawBitmap(rectBitmap, 0, 0, paint); // 画方paint.setXfermode(xfermode); // 设置 Xfermodecanvas.drawBitmap(circleBitmap, 0, 0, paint); // 画圆paint.setXfermode(null); // 用完及时清除 Xfermode 又是 PorterDuff.Mode 。 PorterDuff.Mode 在 Paint 一共有三处 API ,它们的工作原理都一样,只是用途不同: 另外,从上面的示例代码可以看出,创建 Xfermode 的时候其实是创建的它的子类 PorterDuffXfermode。而事实上,Xfermode 也只有这一个子类。所以在设置 Xfermode 的时候不用多想,直接用 PorterDuffXfermode 吧。 其实在更早的 Android 版本中,Xfermode 还有别的子类,但别的子类现在已经 deprecated 了,如今只剩下了 PorterDuffXfermode。所以目前它的使用看起来好像有点啰嗦,但其实是由于历史遗留问题。 Xfermode 注意事项Xfermode 使用很简单,不过有两点需要注意: 使用离屏缓冲(Off-screen Buffer)实质上,上面这段例子代码,如果直接执行的话是不会绘制出图中效果的,程序的绘制也不会像上面的动画那样执行,而是会像这样: 为什么会这样?按照逻辑我们会认为,在第二步画圆的时候,跟它共同计算的是第一步绘制的方形。但实际上,却是整个 View 的显示区域都在画圆的时候参与计算,并且 View 自身的底色并不是默认的透明色,而且是遵循一种迷之逻辑,导致不仅绘制的是整个圆的范围,而且在范围之外都变成了黑色。就像这样: 这……那可如何是好? 要想使用 setXfermode() 正常绘制,必须使用离屏缓存 (Off-screen Buffer) 把内容绘制在额外的层上,再把绘制好的内容贴回 View 中。也就是这样: 通过使用离屏缓冲,把要绘制的内容单独绘制在缓冲层, Xfermode 的使用就不会出现奇怪的结果了。使用离屏缓冲有两种方式: Canvas.saveLayer() saveLayer() 可以做短时的离屏缓冲。使用方法很简单,在绘制代码的前后各加一行代码,在绘制之前保存,绘制之后恢复:12345678910int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);canvas.drawBitmap(rectBitmap, 0, 0, paint); // 画方paint.setXfermode(xfermode); // 设置 Xfermodecanvas.drawBitmap(circleBitmap, 0, 0, paint); // 画圆paint.setXfermode(null); // 用完及时清除 Xfermodecanvas.restoreToCount(saved); View.setLayerType() View.setLayerType() 是直接把整个 View 都绘制在离屏缓冲中。 setLayerType(LAYER_TYPE_HARDWARE) 是使用 GPU 来缓冲, setLayerType(LAYER_TYPE_SOFTWARE) 是直接直接用一个 Bitmap 来缓冲。 如果没有特殊需求,可以选用第一种方法 Canvas.saveLayer() 来设置离屏缓冲,以此来获得更高的性能。更多关于离屏缓冲的信息,可以看官方文档中对于硬件加速的介绍。 控制好透明区域使用 Xfermode 来绘制的内容,除了注意使用离屏缓冲,还应该注意控制它的透明区域不要太小,要让它足够覆盖到要和它结合绘制的内容,否则得到的结果很可能不是你想要的。我用图片来具体说明一下: 如图所示,由于透明区域过小而覆盖不到的地方,将不会受到 Xfermode 的影响。 好,到此为止,前面讲的就是 Paint 的第一类 API——关于颜色的三层设置:直接设置颜色的 API 用来给图形和文字设置颜色; setColorFilter() 用来基于颜色进行过滤处理; setXfermode() 用来处理源图像和 View 已有内容的关系。 再贴一次本章开始处的图作为回顾: Paint.setStyle(Paint.Style style)设置绘制风格而如果你想画的不是实心圆,而是空心圆(或者叫环形),也可以使用 paint.setStyle(Paint.Style.STROKE) 来把绘制模式改为画线模式。12paint.setStyle(Paint.Style.STROKE); // Style 修改为画线模式canvas.drawCircle(300, 300, 200, paint); //只会画出这个圆的边线 setStyle(Style style) 这个方法设置的是绘制的 Style 。Style 具体来说有三种: FILL, STROKE 和 FILL_AND_STROKE 。FILL 是填充模式,STROKE 是画线模式(即勾边模式),FILL_AND_STROKE 是两种模式一并使用:既画线又填充。它的默认值是 FILL,填充模式。 Paint.setStrokeWidth(float width)填充线条的宽度 在style为FILL_AND_STROKE或者STROKE模式下可以用这个设置填充线条的宽度123paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(20); // 线条宽度为 20 像素canvas.drawCircle(300, 300, 200, paint); //画一个线宽度为20的圆环 线条宽度 0 和 1 的区别默认情况下,线条宽度为 0,但你会发现,这个时候它依然能够画出线,线条的宽度为 1 像素。那么它和线条宽度为 1 有什么区别呢?其实这个和后面要讲的一个「几何变换」有关:你可以为 Canvas 设置 Matrix 来实现几何变换(如放大、缩小、平移、旋转),在几何变换之后 Canvas 绘制的内容就会发生相应变化,包括线条也会加粗,例如 2 像素宽度的线条在 Canvas 放大 2 倍后会被以 4 像素宽度来绘制。而当线条宽度被设置为 0 时,它的宽度就被固定为 1 像素,就算 Canvas 通过几何变换被放大,它也依然会被以 1 像素宽度来绘制。Google 在文档中把线条宽度为 0 时称作「hairline mode(发际线模式)」。 抗锯齿在绘制的时候,往往需要开启抗锯齿来让图形和文字的边缘更加平滑。开启抗锯齿很简单,只要在 new Paint() 的时候加上一个 ANTI_ALIAS_FLAG 参数就行:12Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//或者调用setAntiAlias(true) 抗锯齿的原理也并不是选择了更精细的算法来算出了更平滑的图形边缘。 实质上,锯齿现象的发生,只是由于图形分辨率过低,导致人眼察觉出了画面中的像素颗粒而已。换句话说,就算不开启抗锯齿,图形的边缘也已经是最完美的了,而并不是一个粗略计算的粗糙版本。 抗锯齿的原理是:修改图形边缘处的像素颜色,从而让图形在肉眼看来具有更加平滑的感觉 简单点说抗锯齿会失真 setStrokeCap(Paint.Cap cap)设置线头的形状。线头形状有三种:BUTT 平头、ROUND 圆头、SQUARE 方头。默认为 BUTT。 放出「平头」「圆头」「方头」这种翻译我始终有点纠结:既觉得自己翻译得简洁清晰尽显机智,同时又担心用词会不会有点太过通俗,让人觉得我不够高贵冷艳? 当线条的宽度是 1 像素时,这三种线头的表现是完全一致的,全是 1 个像素的点;而当线条变粗的时候,它们就会表现出不同的样子:虚线是额外加的,虚线左边是线的实际长度,虚线右边是线头。有了虚线作为辅助,可以清楚地看出 BUTT 和 SQUARE 的区别。 setStrokeJoin(Paint.Join join)设置拐角的形状。有三个值可以选择:MITER 尖角、 BEVEL 平角和 ROUND 圆角。默认为 MITER。 setStrokeMiter(float miter)这个方法是对于 setStrokeJoin() 的一个补充,它用于设置 MITER 型拐角的延长线的最大值。所谓「延长线的最大值」,是这么一回事: 当线条拐角为 MITER 时,拐角处的外缘需要使用延长线来补偿:而这种补偿方案会有一个问题:如果拐角的角度太小,就有可能由于出现连接点过长的情况。比如这样:所以为了避免意料之外的过长的尖角出现, MITER 型连接点有一个额外的规则:当尖角过长时,自动改用 BEVEL 的方式来渲染连接点。例如上图的这个尖角,在默认情况下是不会出现的,而是会由于延长线过长而被转为 BEVEL 型连接点:至于多尖的角属于过于尖,尖到需要转为使用 BEVEL 来绘制,则是由一个属性控制的,而这个属性就是 setStrokeMiter(miter) 方法中的 miter 参数。miter 参数是对于转角长度的限制,具体来讲,是指尖角的外缘端点和内部拐角的距离与线条宽度的比。也就是下面这两个长度的比:用几何知识很容易得出这个比值的计算公式:如果拐角的大小为 θ ,那么这个比值就等于 1 / sin ( θ / 2 ) 。 这个 miter limit 的默认值是 4,对应的是一个大约 29° 的锐角: 默认情况下,大于这个角的尖角会被保留,而小于这个夹角的就会被「削成平头」 setDither(boolean dither)设置图像的抖动。 setFilterBitmap(boolean filter)设置是否使用双线性过滤来绘制 Bitmap 。 图像在放大绘制的时候,默认使用的是最近邻插值过滤,这种算法简单,但会出现马赛克现象;而如果开启了双线性过滤,就可以让结果图像显得更加平滑。效果依然盗维基百科的图:1paint.setFilterBitmap(true); 加上这一行,在放大绘制 Bitmap 的时候就会使用双线性过滤了。 setPathEffect(PathEffect effect)使用 PathEffect 来给图形的轮廓设置效果。对 Canvas 所有的图形绘制有效,也就是 drawLine() drawCircle() drawPath() 这些方法。大概像这样:123456PathEffect pathEffect = new DashPathEffect(new float[]{10, 5}, 10);paint.setPathEffect(pathEffect);...canvas.drawCircle(300, 300, 200, paint); 下面就具体说一下 Android 中的 6 种 PathEffect。PathEffect 分为两类,单一效果的 CornerPathEffect DiscretePathEffect DashPathEffect PathDashPathEffect ,和组合效果的 SumPathEffect ComposePathEffect。 CornerPathEffect把所有拐角变成圆角。1234PathEffect pathEffect = new CornerPathEffect(20);paint.setPathEffect(pathEffect);...canvas.drawPath(path, paint); 它的构造方法 CornerPathEffect(float radius) 的参数 radius 是圆角的半径。 DiscretePathEffect把线条进行随机的偏离,让轮廓变得乱七八糟。乱七八糟的方式和程度由参数决定。1234PathEffect pathEffect = new DiscretePathEffect(20, 5);paint.setPathEffect(pathEffect);...canvas.drawPath(path, paint); DiscretePathEffect 具体的做法是,把绘制改为使用定长的线段来拼接,并且在拼接的时候对路径进行随机偏离。它的构造方法 DiscretePathEffect(float segmentLength, float deviation) 的两个参数中, segmentLength 是用来拼接的每个线段的长度, deviation 是偏离量。这两个值设置得不一样,显示效果也会不一样,具体的你自己多试几次就明白了 DashPathEffect使用虚线来绘制线条。1234PathEffect pathEffect = new DiscretePathEffect(20, 5);paint.setPathEffect(pathEffect);...canvas.drawPath(path, paint); 它的构造方法 DashPathEffect(float[] intervals, float phase) 中, 第一个参数 intervals 是一个数组,它指定了虚线的格式:数组中元素必须为偶数(最少是 2 个),按照「画线长度、空白长度、画线长度、空白长度」……的顺序排列,例如上面代码中的 20, 5, 10, 5 就表示虚线是按照「画 20 像素、空 5 像素、画 10 像素、空 5 像素」的模式来绘制;第二个参数 phase 是虚线的偏移量。 PathDashPathEffect这个方法比 DashPathEffect 多一个前缀 Path ,所以顾名思义,它是使用一个 Path 来绘制「虚线」。具体看图吧:12345Path dashPath = ...; // 使用一个三角形来做 dashPathEffect pathEffect = new PathDashPathEffect(dashPath, 40, 0, PathDashPathEffectStyle.TRANSLATE);paint.setPathEffect(pathEffect);...canvas.drawPath(path, paint); 它的构造方法 PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style) 中, shape 参数是用来绘制的 Path ; advance 是两个相邻的 shape 段之间的间隔,不过注意,这个间隔是两个 shape 段的起点的间隔,而不是前一个的终点和后一个的起点的距离; phase 和 DashPathEffect 中一样,是虚线的偏移;最后一个参数 style,是用来指定拐弯改变的时候 shape 的转换方式。style 的类型为 PathDashPathEffect.Style ,是一个 enum ,具体有三个值: TRANSLATE:位移ROTATE:旋转MORPH:变体 SumPathEffect这是一个组合效果类的 PathEffect 。它的行为特别简单,就是分别按照两种 PathEffect 分别对目标进行绘制。12345PathEffect dashEffect = new DashPathEffect(new float[]{20, 10}, 0);PathEffect discreteEffect = new DiscretePathEffect(20, 5);pathEffect = new SumPathEffect(dashEffect, discreteEffect);...canvas.drawPath(path, paint); ComposePathEffect这也是一个组合效果类的 PathEffect 。不过它是先对目标 Path 使用一个 PathEffect,然后再对这个改变后的 Path 使用另一个 PathEffect。12345PathEffect dashEffect = new DashPathEffect(new float[]{20, 10}, 0);PathEffect discreteEffect = new DiscretePathEffect(20, 5);pathEffect = new ComposePathEffect(dashEffect, discreteEffect);...canvas.drawPath(path, paint); 它的构造方法 ComposePathEffect(PathEffect outerpe, PathEffect innerpe) 中的两个 PathEffect 参数, innerpe 是先应用的, outerpe 是后应用的。所以上面的代码就是「先偏离,再变虚线」。而如果把两个参数调换,就成了「先变虚线,再偏离」。至于具体的视觉效果……我就不贴图了,你自己试试看吧! 上面这些就是 Paint 中的 6 种 PathEffect。它们有的是有独立效果的,有的是用来组合不同的 PathEffect 的,功能各不一样。 注意: PathEffect 在有些情况下不支持硬件加速,需要关闭硬件加速才能正常使用:Canvas.drawLine() 和 Canvas.drawLines() 方法画直线时,setPathEffect() 是不支持硬件加速的;PathDashPathEffect 对硬件加速的支持也有问题,所以当使用 PathDashPathEffect 的时候,最好也把硬件加速关了。 剩下的两个效果类方法:setShadowLayer() 和 setMaskFilter() ,它们和前面的效果类方法有点不一样:它们设置的是「附加效果」,也就是基于在绘制内容的额外效果。 setShadowLayer(float radius, float dx, float dy, int shadowColor)在之后的绘制内容下面加一层阴影。123paint.setShadowLayer(10, 0, 0, Color.RED);...canvas.drawText(text, 80, 300, paint); 效果就是上面这样。方法的参数里, radius 是阴影的模糊范围; dx dy 是阴影的偏移量; shadowColor 是阴影的颜色。如果要清除阴影层,使用 clearShadowLayer() 。注意: 在硬件加速开启的情况下, setShadowLayer() 只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。 如果 shadowColor 是半透明的,阴影的透明度就使用 shadowColor 自己的透明度;而如果 shadowColor 是不透明的,阴影的透明度就使用 paint 的透明度。 setMaskFilter(MaskFilter maskfilter)为之后的绘制设置 MaskFilter。上一个方法 setShadowLayer() 是设置的在绘制层下方的附加效果;而这个 MaskFilter 和它相反,设置的是在绘制层上方的附加效果。 到现在已经有两个 setXxxFilter(filter) 了。前面有一个 setColorFilter(filter) ,是对每个像素的颜色进行过滤;而这里的 setMaskFilter(filter) 则是基于整个画面来进行过滤。 MaskFilter 有两种: BlurMaskFilter 和 EmbossMaskFilter。 BlurMaskFilter 模糊效果的 MaskFilter。12345PathEffect dashEffect = new DashPathEffect(new float[]{20, 10}, 0);PathEffect discreteEffect = new DiscretePathEffect(20, 5);pathEffect = new ComposePathEffect(dashEffect, discreteEffect);...canvas.drawPath(path, paint); 它的构造方法 BlurMaskFilter(float radius, BlurMaskFilter.Blur style) 中, radius 参数是模糊的范围, style 是模糊的类型。一共有四种: NORMAL: 内外都模糊绘制 SOLID: 内部正常绘制,外部模糊 INNER: 内部模糊,外部不绘制 OUTER: 内部不绘制,外部模糊(什么鬼?) EmbossMaskFilter 浮雕效果的 MaskFilter。123paint.setMaskFilter(new EmbossMaskFilter(new float[]{0, 1, 1}, 0.2f, 8, 10));...canvas.drawBitmap(bitmap, 100, 100, paint); 它的构造方法 EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius) 的参数里, direction 是一个 3 个元素的数组,指定了光源的方向; ambient 是环境光的强度,数值范围是 0 到 1; specular 是炫光的系数; blurRadius 是应用光线的范围。 不过由于我没有在项目中使用过 EmbossMaskFilter,对它的每个参数具体调节方式并不熟,你有兴趣的话自己研究一下吧。 获取绘制的 Path这是效果类的最后一组方法,也是效果类唯一的一组 get 方法。这组方法做的事是,根据 paint 的设置,计算出绘制 Path 或文字时的实际 Path。 getFillPath(Path src, Path dst)所谓实际 Path ,指的就是 drawPath() 的绘制内容的轮廓,要算上线条宽度和设置的 PathEffect。默认情况下(线条宽度为 0、没有 PathEffect),原 Path 和实际 Path 是一样的;而在线条宽度不为 0 (并且模式为 STROKE 模式或 FLL_AND_STROKE ),或者设置了 PathEffect 的时候,实际 Path 就和原 Path 不一样了: 通过 getFillPath(src, dst) 方法就能获取这个实际 Path。方法的参数里,src 是原 Path ,而 dst 就是实际 Path 的保存位置。 getFillPath(src, dst) 会计算出实际 Path,然后把结果保存在 dst 里。 getTextPath(String text, int start, int end, float x, float y, Path path) / getTextPath(char[] text, int index, int count, float x, float y, Path path)「文字的 Path」。文字的绘制,虽然是使用 Canvas.drawText() 方法,但其实在下层,文字信息全是被转化成图形,对图形进行绘制的。 getTextPath() 方法,获取的就是目标文字所对应的 Path 。这个就是所谓「文字的 Path」。这两个方法, getFillPath() 和 getTextPath() ,就是获取绘制的 Path 的方法。之所以把它们归类到「效果」类方法,是因为它们主要是用于图形和文字的装饰效果的位置计算,比如自定义的下划线效果。 初始化类这一类方法很简单,它们是用来初始化 Paint 对象,或者是批量设置 Paint 的多个属性的方法。 reset()重置 Paint 的所有属性为默认值。相当于重新 new 一个,不过性能当然高一些啦。 set(Paint src)把 src 的所有属性全部复制过来。相当于调用 src 所有的 get 方法,然后调用这个 Paint 的对应的 set 方法来设置它们。 setFlags(int flags)批量设置 flags。相当于依次调用它们的 set 方法。1paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); 这行代码,和下面这两行是等价的:12paint.setAntiAlias(true);paint.setDither(true); setFlags(flags) 对应的 get 方法是 int getFlags()。 Paint.setTextSize(float textSize)通过 Paint.setTextSize(textSize),可以设置文字的大小。123456789String text = "你好,hello,拜拜,bye";paint.setTextSize(18);paint.setTextSize(18);canvas.drawText(text, 100, 25, paint);paint.setTextSize(36);canvas.drawText(text, 100, 70, paint);paint.setTextSize(60);canvas.drawText(text, 100, 145, paint);paint.setTextSize(84);canvas.drawText(text, 100, 240, paint); path 这一类方法还可以细分为两组:添加子图形(addxxx)和画线(xxxTo)(直线或曲线) addXXXaddCircle(float x, float y, float radius, Direction dir) 添加圆x, y, radius 这三个参数是圆的基本信息,最后一个参数 dir 是画圆的路径的方向。 路径方向有两种:顺时针 (CW clockwise) 和逆时针 (CCW counter-clockwise) 。对于普通情况,这个参数填 CW 还是填 CCW 没有影响。它只是在需要填充图形 (Paint.Style 为 FILL 或 FILL_AND_STROKE) ,并且图形出现自相交时,用于判断填充范围的setFillType会详细讲解12345Paint paint = new Paint();paint.setStyle(Paint.Style.FILL); // 填充模式Path path = new Path();path.addCircle(500,500,400,Path.Direction.CCW);canvas.drawPath(path,paint);//画一个圆 可以看出,path.AddCircle(x, y, radius, dir) + canvas.drawPath(path, paint) 这种写法,和直接使用 canvas.drawCircle(x, y, radius, paint) 的效果是一样的,区别只是它的写法更复杂。所以如果只画一个圆,没必要用 Path,直接用 drawCircle() 就行了。drawPath() 一般是在绘制组合图形时才会用到的。 其他的 Path.add-() 方法和这类似,例如: addOval(float left, float top, float right, float bottom, Direction dir) / addOval(RectF oval, Direction dir) 添加椭圆addRect(float left, float top, float right, float bottom, Direction dir) / addRect(RectF rect, Direction dir) 添加矩形addRoundRect(RectF rect, float rx, float ry, Direction dir) / addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Direction dir) / addRoundRect(RectF rect, float[] radii, Direction dir) / addRoundRect(float left, float top, float right, float bottom, float[] radii, Direction dir) 添加圆角矩形addPath(Path path) 添加另一个 Path上面这几个方法和 addCircle() 的使用都差不多,不再做过多介绍。 XXXTo 这一组和第一组 addXxx() 方法的区别在于,第一组是添加的完整封闭图形(除了 addPath() ),而这一组添加的只是一条线。 lineTo(float x, float y) / rLineTo(float x, float y) 画直线从当前位置向目标位置画一条直线, x 和 y 是目标位置的坐标。这两个方法的区别是,lineTo(x, y) 的参数是绝对坐标,而 rLineTo(x, y) 的参数是相对当前位置的相对坐标 (前缀 r 指的就是 relatively 「相对地」)。 当前位置:所谓当前位置,即最后一次调用画 Path 的方法的终点位置。初始值为原点 (0, 0)。123456Paint paint = new Paint();Path path = new Path();paint.setStyle(Paint.Style.STROKE);path.lineTo(100, 100); // 由当前位置 (0, 0) 向 (100, 100) 画一条直线path.rLineTo(100, 0); // 由当前位置 (100, 100) 向正右方 100 像素的位置画一条直线canvas.drawPath(path,paint); quadTo(float x1, float y1, float x2, float y2) / rQuadTo(float dx1, float dy1, float dx2, float dy2) 画二次贝塞尔曲线这条二次贝塞尔曲线的起点就是当前位置,而参数中的 x1, y1 和 x2, y2 则分别是控制点和终点的坐标。和 rLineTo(x, y) 同理,rQuadTo(dx1, dy1, dx2, dy2) 的参数也是相对坐标 贝塞尔曲线:贝塞尔曲线是几何上的一种曲线。它通过起点、控制点和终点来描述一条曲线,主要用于计算机图形学。概念总是说着容易听着难,总之使用它可以绘制很多圆润又好看的图形 参数两个坐标,他是能在当前坐标圆润的过度到第一个坐标,然后圆润的过度到第二个坐标 12path.quadTo(100,100,200,0);//从0,0过度到100,100再过度到200,0canvas.drawPath(path,paint); cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) / rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 画三次贝塞尔曲线和上面这个 quadTo() rQuadTo() 的二次贝塞尔曲线同理,cubicTo() 和 rCubicTo() 是三次贝塞尔曲线 moveTo(float x, float y) / rMoveTo(float x, float y) 移动到目标位置不论是直线还是贝塞尔曲线,都是以当前位置作为起点,而不能指定起点。但你可以通过 moveTo(x, y) 或 rMoveTo() 来改变当前位置,从而间接地设置这些方法的起点。1234path.lineTo(100, 100); // 画斜线path.moveTo(200, 100); // 我移~~path.lineTo(200, 0); // 画竖线canvas.drawPath(path,paint); moveTo(x, y) 虽然不添加图形,但它会设置图形的起点,所以它是非常重要的一个辅助方法。 另外,第二组还有两个特殊的方法: arcTo() 和 addArc()。它们也是用来画线的,但并不使用当前位置作为弧线的起点。 arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) / arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) / arcTo(RectF oval, float startAngle, float sweepAngle) 画弧形这个方法和 Canvas.drawArc() 比起来,少了一个参数 useCenter,而多了一个参数 forceMoveTo 。 少了 useCenter ,是因为 arcTo() 只用来画弧形而不画扇形,所以不再需要 useCenter 参数;而多出来的这个 forceMoveTo 参数的意思是,绘制是要「抬一下笔移动过去」,还是「直接拖着笔过去」,区别在于是否留下移动的痕迹。123paint.setStyle(Style.STROKE);path.lineTo(100, 100);path.arcTo(100, 100, 300, 300, -90, 90, true); // 强制移动到弧形起点(无痕迹) 123paint.setStyle(Style.STROKE);path.lineTo(100, 100);path.arcTo(100, 100, 300, 300, -90, 90, false); // 直接连线连到弧形起点(有痕迹) 此时画笔开始是开始的角度,画笔最终落点是结束的角度 addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle) / addArc(RectF oval, float startAngle, float sweepAngle)又是一个弧形的方法。一个叫 arcTo ,一个叫 addArc(),都是弧形,区别在哪里?其实很简单: addArc() 只是一个直接使用了 forceMoveTo = true 的简化版 arcTo() 。123paint.setStyle(Style.STROKE);path.lineTo(100, 100);path.addArc(100, 100, 300, 300, -90, 90); close作用是把当前的子图形封闭,即由当前位置向当前子图形的起点绘制一条直线。1234paint.setStyle(Paint.Style.STROKE);path.arcTo(100, 100, 300, 300, 0, 180, false); // 强制移动到弧形起点(无痕迹)path.arcTo(400, 400, 600, 600, 0, 180, false); // 强制移动到弧形起点(无痕迹)canvas.drawPath(path,paint); 12345paint.setStyle(Paint.Style.STROKE);path.arcTo(100, 100, 300, 300, 0, 180, false); // 强制移动到弧形起点(无痕迹)path.arcTo(400, 400, 600, 600, 0, 180, false); // 强制移动到弧形起点(无痕迹)path.close();canvas.drawPath(path, paint); close() 和 lineTo(起点坐标) 是完全等价的。 子图形:官方文档里叫做 contour 。但由于在这个场景下我找不到这个词合适的中文翻译(直译的话叫做「轮廓」),所以我换了个便于中国人理解的词:「子图形」。前面说到,第一组方法是「添加子图形」,所谓「子图形」,指的就是一次不间断的连线。一个 Path 可以包含多个子图形。当使用第一组方法,即 addCircle() addRect() 等方法的时候,每一次方法调用都是新增了一个独立的子图形;而如果使用第二组方法,即 lineTo() arcTo() 等方法的时候,则是每一次断线(即每一次「抬笔」),都标志着一个子图形的结束,以及一个新的子图形的开始。另外,不是所有的子图形都需要使用 close() 来封闭。当需要填充图形时(即 Paint.Style 为 FILL 或 FILL_AND_STROKE),Path 会自动封闭子图形。 Path 方法第二类:辅助的设置或计算Path.setFillType(Path.FillType ft) 设置填充方式前面在说 dir 参数的时候提到, Path.setFillType(fillType) 是用来设置图形自相交时的填充算法的: 方法中填入不同的 FillType 值,就会有不同的填充效果。FillType 的取值有四个: EVEN_ODD WINDING (默认值) INVERSE_EVEN_ODD INVERSE_WINDING其中后面的两个带有 INVERSE_ 前缀的,只是前两个的反色版本,所以只要把前两个,即 EVEN_ODD 和 WINDING,搞明白就可以了。 EVEN_ODD 和 WINDING 的原理有点复杂,直接讲出来的话信息量太大,所以我先给一个简单粗暴版的总结,你感受一下: WINDING 是「全填充」,而 EVEN_ODD 是「交叉填充」:之所以叫「简单粗暴版」,是因为这些只是通常情形下的效果;而如果要准确了解它们在所有情况下的效果,就得先知道它们的原理,即它们的具体算法。 EVEN_ODD 和 WINDING 的原理EVEN_ODD即 even-odd rule (奇偶原则):对于平面中的任意一点,向任意方向射出一条射线,这条射线和图形相交的次数(相交才算,相切不算哦)如果是奇数,则这个点被认为在图形内部,是要被涂色的区域;如果是偶数,则这个点被认为在图形外部,是不被涂色的区域。还以左右相交的双圆为例: 射线的方向无所谓,同一个点射向任何方向的射线 从上图可以看出,射线每穿过图形中的一条线,内外状态就发生一次切换,这就是为什么 EVEN_ODD 是一个「交叉填充」的模式。 WINDING即 non-zero winding rule (非零环绕数原则):首先,它需要你图形中的所有线条都是有绘制方向的: 然后,同样是从平面中的点向任意方向射出一条射线,但计算规则不一样:以 0 为初始值,对于射线和图形的所有交点,遇到每个顺时针的交点(图形从射线的左边向右穿过)把结果加 1,遇到每个逆时针的交点(图形从射线的右边向左穿过)把结果减 1,最终把所有的交点都算上,得到的结果如果不是 0,则认为这个点在图形内部,是要被涂色的区域;如果是 0,则认为这个点在图形外部,是不被涂色的区域。和 EVEN_ODD 相同,射线的方向并不影响结果。 所以,我前面的那个「简单粗暴」的总结,对于 WINDING 来说并不完全正确:如果你所有的图形都用相同的方向来绘制,那么 WINDING 确实是一个「全填充」的规则;但如果使用不同的方向来绘制图形,结果就不一样了。 图形的方向:对于添加子图形类方法(如 Path.addCircle() Path.addRect())的方向,由方法的 dir 参数来控制,这个在前面已经讲过了;而对于画线类的方法(如 Path.lineTo() Path.arcTo())就更简单了,线的方向就是图形的方向。 所以,完整版的 EVEN_ODD 和 WINDING 的效果应该是这样的:而 INVERSE_EVEN_ODD 和 INVERSE_WINDING ,只是把这两种效果进行反转而已,你懂了 EVEN_ODD 和 WINDING ,自然也就懂 INVERSE_EVEN_ODD 和 INVERSE_WINDING 了。 说在最后安卓自定义内容太多了。。网上找了很多资源,就不学习了,等到需要的时候再去看好了 扔物线 简书英勇青铜5有一系列关于安卓自定义的文章]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android自定义View学习1-准备基础知识]]></title>
<url>%2F2019%2F01%2F31%2Fandroid%E8%87%AA%E5%AE%9A%E4%B9%89View%E5%AD%A6%E4%B9%A01-%E5%87%86%E5%A4%87%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%2F</url>
<content type="text"><![CDATA[参考英勇青铜5hencoderCarson_Ho 基础坐标系View的位置只要由它的四个顶点来决定。分别对应于View的四个属性: top ,getTop()左上角的纵坐标 left,getLeft() 左上角的横坐标 right,getRight() 右下角的横坐标 bottom,getBottom() 右下角的纵坐标 获得方式12345678// 获取Top位置public final int getTop() { return mTop;}// 其余如下:getLeft(); //获取子View左上角距父View左侧的距离getBottom(); //获取子View右下角距父View顶部的距离getRight(); //获取子View右下角距父View左侧的距离 与MotionEvent中 get()和getRaw()的区别123456//get() :触摸点相对于其所在组件坐标系的坐标event.getX();event.getY();//getRaw() :触摸点相对于屏幕默认坐标系的坐标event.getRawX();event.getRawY(); 角度从左向右顺时针是安卓角度增大的方向 颜色Android中颜色相关内容Android中的颜色相关内容包括颜色模式,创建颜色的方式,以及颜色的混合模式等。 颜色模式Android支持的颜色模式: 以ARGB8888为例介绍颜色定义: 定义颜色的方式在java中定义颜色12345//java中使用Color类定义颜色int color = Color.GRAY; //灰色//Color类是使用ARGB值进行表示int color = Color.argb(127, 255, 0, 0); //半透明红色int color = 0xaaff0000; //带有透明度的红色 在xml文件中定义颜色在/res/values/color.xml 文件中如下定义:12345678<?xml version="1.0" encoding="utf-8"?><resources> //定义了红色(没有alpha(透明)通道) <color name="red">#ff0000</color> //定义了蓝色(没有alpha(透明)通道) <color name="green">#00ff00</color></resources> 在xml文件中以”#“开头定义颜色,后面跟十六进制的值,有如下几种定义方式:1234#f00 //低精度 - 不带透明通道红色#af00 //低精度 - 带透明通道红色#ff0000 //高精度 - 不带透明通道红色#aaff0000 //高精度 - 带透明通道红色 引用颜色的方式在java文件中引用xml中定义的颜色:1234//方法1int color = getResources().getColor(R.color.mycolor);//方法2(API 23及以上)int color = getColor(R.color.myColor); 在xml文件(layout或style)中引用或者创建颜色12345678<!--在style文件中引用--><style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/red</item></style><!--在layout文件中引用在/res/values/color.xml中定义的颜色-->android:background="@color/red"<!--在layout文件中创建并使用颜色-->android:background="#ff0000" 测量(Measure)]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android调用WebView以及和js交互]]></title>
<url>%2F2019%2F01%2F23%2Fandroid%E8%B0%83%E7%94%A8WebView%E4%BB%A5%E5%8F%8A%E5%92%8Cjs%E4%BA%A4%E4%BA%92%2F</url>
<content type="text"><![CDATA[参考:Android:这是一份全面 & 详细的Webview使用攻略 WebView的使用简介 Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。23 作用 显示和渲染Web页面 直接使用html文件(网络上或本地assets中)作布局 可和JavaScript交互调用 WebView控件功能强大,除了具有一般View的属性和设置外,还可以对url请求、页面加载、渲染、页面交互进行强大的处理。 使用 一般来说Webview可单独使用,可联合其工具类一起使用: Webview的最常用的工具类:WebSettings类、WebViewClient类、WebChromeClient类 Android 和 Js的交互 Webview类常用方法加载URL的三种方式12345678910111213//方式1. 加载一个网页:webView.loadUrl("http://www.google.com/");//方式2:加载apk包中的html页面webView.loadUrl("file:///android_asset/test.html");//方式3:加载手机本地的html页面webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");// 方式4: 加载 HTML 页面的一小段内容WebView.loadData(String data, String mimeType, String encoding)// 参数说明:// 参数1:需要截取展示的内容// 内容里不能出现 ’#’, ‘%’, ‘\’ , ‘?’ 这四个字符,若出现了需用 %23, %25, %27, %3f 对应来替代,否则会出现异常// 参数2:展示内容的类型// 参数3:字节码 WebView的状态1234567891011121314151617//激活WebView为活跃状态,能正常执行网页的响应webView.onResume() ;//当页面被失去焦点被切换到后台不可见状态,需要执行onPause//通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。webView.onPause();//当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview//它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗。webView.pauseTimers()//恢复pauseTimers状态webView.resumeTimers();//销毁Webview//在关闭了Activity时,如果Webview的音乐或视频,还在播放。就必须销毁Webview//但是注意:webview调用destory时,webview仍绑定在Activity上//这是由于自定义webview构建时传入了该Activity的context对象//因此需要先从父容器中移除webview,然后再销毁webview:rootLayout.removeView(webView);webView.destroy(); 关于前进 / 后退网页1234567891011//是否可以后退Webview.canGoBack()//后退网页Webview.goBack()//是否可以前进Webview.canGoForward()//前进网页Webview.goForward()//以当前的index为起始点前进或者后退到历史记录中指定的steps//如果steps为负数则为后退,正数则为前进Webview.goBackOrForward(intsteps) 常见用法:Back键控制网页后退 问题:在不做任何处理前提下 ,浏览网页时点击系统的“Back”键,整个 Browser 会调用 finish()而结束自身 目标:点击返回后,是网页回退而不是推出浏览器解决方案:在当前Activity中处理并消费掉该 Back 事件1234567public boolean onKeyDown(int keyCode, KeyEvent event) { if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) { mWebView.goBack(); return true; } return super.onKeyDown(keyCode, event);} 清除缓存数据12345678//清除网页访问留下的缓存//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.Webview.clearCache(true);//清除当前webview访问的历史记录//只会webview访问历史记录里的所有记录除了当前访问记录Webview.clearHistory();//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据Webview.clearFormData(); 常用工具类WebSettings类 作用:对WebView进行配置和管理 配置步骤 & 常见方法: 添加访问网络权限(AndroidManifest.xml) 1<uses-permission android:name="android.permission.INTERNET"/> 生成一个WebView组件(有两种方式) 1234//方式1:直接在在Activity中生成WebView webView = new WebView(this)//方法2:在Activity的layout文件里添加webview控件:WebView webview = (WebView) findViewById(R.id.webView1); 进行配置-利用WebSettings子类(常见方法) 123456789101112131415161718192021//声明WebSettings子类WebSettings webSettings = webView.getSettings();//如果访问的页面中要与Javascript交互,则webview必须设置支持JavascriptwebSettings.setJavaScriptEnabled(true);// 若加载的 html 里有JS 在执行动画等操作,会造成资源浪费(CPU、电量)// 在 onStop 和 onResume 里分别把 setJavaScriptEnabled() 给设置成 false 和 true 即可//支持插件webSettings.setPluginsEnabled(true);//设置自适应屏幕,两者合用webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小//缩放操作webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件//其他细节操作webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存webSettings.setAllowFileAccess(true); //设置可以访问文件webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式 常见用法:设置WebView缓存当加载 html 页面时,WebView会在/data/data/包名目录下生成 database 与 cache 两个文件夹,请求的 URL记录保存在 WebViewCache.db,而 URL的内容是保存在 WebViewCache 文件夹下 是否启用缓存: 123456789//优先使用缓存:WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//缓存模式如下://LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。//不使用缓存:WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); 结合使用(离线加载) 12345678910if (NetStatusUtil.isConnected(getApplicationContext())) { webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根据cache-control决定是否从网络上取数据。} else { webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//没网,则从本地获取,即离线加载}webSettings.setDomStorageEnabled(true); // 开启 DOM storage API 功能webSettings.setDatabaseEnabled(true); //开启 database storage API 功能webSettings.setAppCacheEnabled(true);//开启 Application Caches 功能String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;webSettings.setAppCachePath(cacheDirPath); //设置 Application Caches 缓存目录 注意: 每个 Application 只调用一次 WebSettings.setAppCachePath(),WebSettings.setAppCacheMaxSize() WebViewClient类 作用:处理各种通知 & 请求事件 常见方法: 常见方法1:shouldOverrideUrlLoading()作用:打开网页时不调用系统浏览器, 而是在本WebView中显示;在网页上的所有加载都经过这个方法,这个函数我们可以做很多操作。 1234567891011121314151617181920//步骤1. 定义Webview组件Webview webview = (WebView) findViewById(R.id.webView1);//步骤2. 选择加载方式//方式1. 加载一个网页:webView.loadUrl("http://www.google.com/");//方式2:加载apk包中的html页面webView.loadUrl("file:///android_asset/test.html");//方式3:加载手机本地的html页面webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");//步骤3. 复写shouldOverrideUrlLoading()方法,使得打开网页时不调用系统浏览器, 而是在本WebView中显示webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; }}); 常见方法2:onPageStarted() 作用:开始载入页面调用的,我们可以设定一个loading的页面,告诉用户程序在等待网络响应。123456webView.setWebViewClient(new WebViewClient(){ @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { //设定加载开始的操作 }}); 常见方法3:onPageFinished() 作用:在页面加载结束时调用。我们可以关闭loading 条,切换程序动作。123456webView.setWebViewClient(new WebViewClient(){ @Override public void onPageFinished(WebView view, String url) { //设定加载结束的操作 }}); 常见方法4:onLoadResource() 作用:在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。123456webView.setWebViewClient(new WebViewClient(){ @Override public boolean onLoadResource(WebView view, String url) { //设定加载资源的操作 }}); 常见方法5:onReceivedError() 作用:加载页面的服务器出现错误时(如404)调用。 App里面使用webview控件的时候遇到了诸如404这类的错误的时候,若也显示浏览器里面的那种错误提示页面就显得很丑陋了,那么这个时候我们的app就需要加载一个本地的错误提示页面,即webview如何加载一个本地的页面 123456789101112131415//步骤1:写一个html文件(error_handle.html),用于出错时展示给用户看的提示页面//步骤2:将该html文件放置到代码根目录的assets文件夹下//步骤3:复写WebViewClient的onRecievedError方法//该方法传回了错误码,根据错误类型可以进行不同的错误分类处理webView.setWebViewClient(new WebViewClient(){ @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl){ switch(errorCode){ case HttpStatus.SC_NOT_FOUND: view.loadUrl("file:///android_assets/error_handle.html"); break; } }}); 常见方法6:onReceivedSslError() 作用:处理https请求 webView默认是不处理https请求的,页面显示空白,需要进行如下设置:12345678910111213webView.setWebViewClient(new WebViewClient() { @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); //表示等待证书响应 // handler.cancel(); //表示挂起连接,为默认方式 // handler.handleMessage(null); //可做其他处理 }});// 特别注意:5.1以上默认禁止了https和http混用,以下方式是开启if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);} WebChromeClient类 作用:辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等。 常见使用 常见方法1: onProgressChanged() 作用:获得网页的加载进度并显示12345678910webview.setWebChromeClient(new WebChromeClient(){ @Override public void onProgressChanged(WebView view, int newProgress) { if (newProgress < 100) { String progress = newProgress + "%"; progress.setText(progress); } else { } }}); 常见方法2: onReceivedTitle() 作用:获取Web页中的标题 每个网页的页面都有一个标题,比如www.baidu.com这个页面的标题即“百度一下,你就知道”,那么如何知道当前webview正在加载的页面的title并进行设置呢?123456webview.setWebChromeClient(new WebChromeClient(){ @Override public void onReceivedTitle(WebView view, String title) { titleview.setText(title); }}); 常见方法3: onJsAlert() 作用:支持javascript的警告框 一般情况下在 Android 中为 Toast,在文本里面加入\n就可以换行1234567891011121314151617webview.setWebChromeClient(new WebChromeClient() { @Override public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { new AlertDialog.Builder(MainActivity.this) .setTitle("JsAlert") .setMessage(message) .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.confirm(); } }) .setCancelable(false) .show(); return true; }}); 常见方法4: onJsConfirm() 作用:支持javascript的确认框12345678910111213141516171819202122232425webview.setWebChromeClient(new WebChromeClient() { @Override public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) { new AlertDialog.Builder(MainActivity.this) .setTitle("JsConfirm") .setMessage(message) .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.confirm(); } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.cancel(); } }) .setCancelable(false) .show(); // 返回布尔值:判断点击时确认还是取消 // true表示点击了确认;false表示点击了取消; return true; }}); 常见方法5: onJsPrompt() 作用:支持javascript输入框点击确认返回输入框中的值,点击取消返回 null。1234567891011121314151617181920212223242526webview.setWebChromeClient(new WebChromeClient() { @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) { final EditText et = new EditText(MainActivity.this); et.setText(defaultValue); new AlertDialog.Builder(MainActivity.this) .setTitle(message) .setView(et) .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.confirm(et.getText().toString()); } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.cancel(); } }) .setCancelable(false) .show(); return true; }}); WebView和js的交互Android与JS通过WebView互相调用方法,实际上是: Android去调用JS的代码 JS去调用Android的代码 二者沟通的桥梁是WebView Android调用JS代码的方法(2种)通过WebView的loadUrl() 步骤1:将需要调用的JS代码以.html格式放到src/main/assets文件夹里 为了方便展示,本文是采用Andorid调用本地JS代码说明;实际情况时,Android更多的是调用远程JS代码,即将加载的JS代码路径改成url即可123456789101112131415// 文本名:javascript<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>Carson_Ho</title> // JS代码 <script> // Android需要调用的方法 function callJS(){ alert("Android调用了JS的callJS方法"); } </script> </head></html> android中调用12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455public class MainActivity extends AppCompatActivity { WebView mWebView; Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWebView =(WebView) findViewById(R.id.webview); WebSettings webSettings = mWebView.getSettings(); // 设置与Js交互的权限 webSettings.setJavaScriptEnabled(true); // 设置允许JS弹窗 webSettings.setJavaScriptCanOpenWindowsAutomatically(true); // 先载入JS代码 // 格式规定为:file:///android_asset/文件名.html mWebView.loadUrl("file:///android_asset/javascript.html"); button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 通过Handler发送消息 mWebView.post(new Runnable() { @Override public void run() { // 注意调用的JS方法名要对应上 // 调用javascript的callJS()方法 mWebView.loadUrl("javascript:callJS()"); } }); } }); // 由于设置了弹窗检验调用结果,所以需要支持js对话框 // webview只是载体,内容的渲染需要使用webviewChromClient类去实现 // 通过设置WebChromeClient对象处理JavaScript的对话框 //设置响应js 的Alert()函数 mWebView.setWebChromeClient(new WebChromeClient() { @Override public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this); b.setTitle("Alert"); b.setMessage(message); b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.confirm(); } }); b.setCancelable(false); b.create().show(); return true; } }); }} 特别注意:JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用。 此方法调用的时候会导致页面刷新,所以一般不用 通过WebView的evaluateJavascript() 优点:该方法比第一种方法效率更高、使用更简洁。 因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。 Android 4.4 后才可使用1234567// 只需要将第一种方法的loadUrl()换成下面该方法即可mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { //此处为 js 返回的结果 }}); #### 使用建议 因为第一种方法会刷新第二种方法只能在4.4之后使用,所以建议两种方法混合使用123456789101112final int version = Build.VERSION.SDK_INT;// 因为该方法在 Android 4.4 版本才可使用,所以使用时需进行版本判断if (version < 18) { mWebView.loadUrl("javascript:callJS()");} else { mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { //此处为 js 返回的结果 } });} 对于JS调用Android代码的方法有3种:通过WebView的addJavascriptInterface()进行对象映射 定义一个与JS对象映射关系的Android类:AndroidtoJsAndroidtoJs.java 123456789// 继承自Object类public class AndroidtoJs extends Object { // 定义JS需要调用的方法 // 被JS调用的方法必须加入@JavascriptInterface注解 @JavascriptInterface public void hello(String msg) { System.out.println("JS调用了Android的hello方法"); }} 将需要调用的JS代码以.html格式放到src/main/assets文件夹里需要加载JS代码:javascript.html 1234567891011121314151617<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>Carson</title> <script> function callAndroid(){ // 由于对象映射,所以调用test对象等于调用Android映射的对象 test.hello("js调用了android中的hello方法"); } </script> </head> <body> //点击按钮则调用callAndroid函数 <button type="button" id="button1" onclick="callAndroid()"></button> </body></html> 在Android里通过WebView设置Android类与JS代码的映射 1234567891011121314mWebView = (WebView) findViewById(R.id.webview);WebSettings webSettings = mWebView.getSettings();// 设置与Js交互的权限webSettings.setJavaScriptEnabled(true);// 通过addJavascriptInterface()将Java对象映射到JS对象//参数1:Javascript对象名//参数2:Java对象名mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象// 加载JS代码// 格式规定为:file:///android_asset/文件名.htmlmWebView.loadUrl("file:///android_asset/javascript.html"); 优点:使用简单,仅将Android对象和JS对象映射即可 缺点:存在严重的漏洞问题,具体请看文章:你不知道的 Android WebView 使用漏洞 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url 在JS约定所需要的Url协议1234567891011121314151617<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>Carson_Ho</title> <script> function callAndroid(){ /*约定的url协议为:js://webview?arg1=111&arg2=222*/ document.location = "js://webview?arg1=111&arg2=222"; } </script> </head> <body> <!-- 点击按钮则调用callAndroid()方法 --> <button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button> </body></html> 当该JS通过Android的mWebView.loadUrl(“file:///android_asset/javascript.html”)加载后,就会回调shouldOverrideUrlLoading() 在Android通过WebViewClient复写shouldOverrideUrlLoading()1234567891011121314151617181920212223242526272829303132333435mWebView = (WebView) findViewById(R.id.webview);WebSettings webSettings = mWebView.getSettings();// 设置与Js交互的权限webSettings.setJavaScriptEnabled(true);// 设置允许JS弹窗webSettings.setJavaScriptCanOpenWindowsAutomatically(true);// 步骤1:加载JS代码// 格式规定为:file:///android_asset/文件名.htmlmWebView.loadUrl("file:///android_asset/javascript.html");// 复写WebViewClient类的shouldOverrideUrlLoading方法mWebView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // 步骤2:根据协议的参数,判断是否是所需要的url // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数) //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的) Uri uri = Uri.parse(url); // 如果url的协议 = 预先约定的 js 协议 // 就解析往下解析参数 if ( uri.getScheme().equals("js")) { // 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议 // 所以拦截url,下面JS开始调用Android需要的方法 if (uri.getAuthority().equals("webview")) { // 步骤3: // 执行JS所需要调用的逻辑 System.out.println("js调用了Android的方法"); // 可以在协议上带有参数并传递到Android上 HashMap<String, String> params = new HashMap<>(); Set<String> collection = uri.getQueryParameterNames(); } return true; } return super.shouldOverrideUrlLoading(view, url); }}); 优点:不存在方式1的漏洞; 缺点:JS获取Android方法的返回值复杂。如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl ()去执行 JS 方法把返回值传递回去,相关的代码如下:1234567// Android:MainActivity.javamWebView.loadUrl("javascript:returnResult(" + result + ")");// JS:javascript.htmlfunction returnResult(result){ alert("result is" + result);} 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息 原理:Android通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框(即上述三个方法),得到他们的消息内容,然后解析即可。 例子将用拦截 JS的输入框(即prompt()方法)说明 :常用的拦截是:拦截 JS的输入框(即prompt()方法)因为只有prompt()可以返回任意类型的值,操作最全面方便、更加灵活;而alert()对话框没有返回值;confirm()对话框只能返回两种状态(确定 / 取消)两个值 加载js代码 123456789101112131415161718<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>Carson_Ho</title> <script> function clickprompt(){ // 调用prompt() var result=prompt("js://demo?arg1=111&arg2=222"); alert("demo " + result); } </script> </head> <!-- 点击按钮则调用clickprompt() --> <body> <button type="button" id="button1" onclick="clickprompt()">点击调用Android代码</button> </body></html> 在Android通过WebChromeClient复写onJsPrompt() 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354mWebView = (WebView) findViewById(R.id.webview);WebSettings webSettings = mWebView.getSettings();// 设置与Js交互的权限webSettings.setJavaScriptEnabled(true);// 设置允许JS弹窗webSettings.setJavaScriptCanOpenWindowsAutomatically(true);// 先加载JS代码// 格式规定为:file:///android_asset/文件名.htmlmWebView.loadUrl("file:///android_asset/javascript.html");mWebView.setWebChromeClient(new WebChromeClient() { // 拦截输入框(原理同方式2) // 参数message:代表promt()的内容(不是url) // 参数result:代表输入框的返回值 @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { // 根据协议的参数,判断是否是所需要的url(原理同方式2) // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数) //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的) Uri uri = Uri.parse(message); // 如果url的协议 = 预先约定的 js 协议 // 就解析往下解析参数 if ( uri.getScheme().equals("js")) { // 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议 // 所以拦截url,下面JS开始调用Android需要的方法 if (uri.getAuthority().equals("webview")) { // 执行JS所需要调用的逻辑 System.out.println("js调用了Android的方法"); // 可以在协议上带有参数并传递到Android上 HashMap<String, String> params = new HashMap<>(); Set<String> collection = uri.getQueryParameterNames(); //参数result:代表消息框的返回值(输入值) result.confirm("js调用了Android的方法成功啦"); } return true; } return super.onJsPrompt(view, url, message, defaultValue, result); } // 通过alert()和confirm()拦截的原理相同,此处不作过多讲述 // 拦截JS的警告框 @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { return super.onJsAlert(view, url, message, result); } // 拦截JS的确认框 @Override public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { return super.onJsConfirm(view, url, message, result); }}); 三种方式对比 如何避免内存泄漏 不在xml中定义 Webview ,而是在需要的时候在Activity中创建,并且Context使用 getApplicationgContext() 1234LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);mWebView = new WebView(getApplicationContext());mWebView.setLayoutParams(params);mLayout.addView(mWebView); 在 Activity 销毁( WebView )的时候,先让 WebView 加载null内容,然后移除 WebView,再销毁 WebView,最后置空。 1234567891011@Overrideprotected void onDestroy() { if (mWebView != null) { mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); mWebView.clearHistory(); ((ViewGroup) mWebView.getParent()).removeView(mWebView); mWebView.destroy(); mWebView = null; } super.onDestroy();} 总结]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[spring入门-整合junit和web]]></title>
<url>%2F2019%2F01%2F20%2Fspring%E5%85%A5%E9%97%A8-%E6%95%B4%E5%90%88junit%E5%92%8Cweb%2F</url>
<content type="text"><![CDATA[整合Junit 导入jar包 基本 :4+1 测试:spring-test-5.1.3.RELEASE.jar 让Junit通知spring加载配置文件 让spring容器自动进行注入123456789101112131415import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:applicationContext.xml")public class Main { @Autowired //与junit整合,不需要在spring xml配置扫描 private AccountService accountService; @Test public void demo() { accountService.transfer("jack", "rose", 1000); }} 整合web 导入jar spring-web-5.1.3.RELEASE.jar tomcat启动加载配置文件 servlet –> init(ServletConfig) –> 2 filter –> init(FilterConfig) –> web.xml注册过滤器自动调用初始化 listener –> ServletContextListener –> servletContext对象监听【】 spring提供监听器 ContextLoaderListener –> web.xml …. 如果只配置监听器,默认加载xml位置:/WEB-INF/applicationContext.xml 确定配置文件位置,通过系统初始化参数 ServletContext 初始化参数 web.xml <context-param> <param-name>contextConfigLocation <param-value>classpath:applicationContext.xml 代码 修改web.xml 1234567<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value></context-param><listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener> servlet中调用 12345// 从application作用域(ServletContext)获得spring容器//方式1: 手动从作用域获取ApplicationContext applicationContext = (ApplicationContext) this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);//方式2:通过工具获取ApplicationContext apppApplicationContext2 = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());]]></content>
<tags>
<tag>java</tag>
<tag>spring</tag>
</tags>
</entry>
<entry>
<title><![CDATA[spring入门-事务管理]]></title>
<url>%2F2019%2F01%2F20%2Fspring%E5%85%A5%E9%97%A8-%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86%2F</url>
<content type="text"><![CDATA[回顾事务 事务:一组业务操作ABCD,要么全部成功,要么全部不成功。 特性:ACID 原子性:整体 一致性:完成 隔离性:并发 持久性:结果 隔离问题: 脏读:一个事务读到另一个事务没有提交的数据 不可重复读:一个事务读到另一个事务已提交的数据(update) 虚读(幻读):一个事务读到另一个事务已提交的数据(insert) 隔离级别: read uncommitted:读未提交。存在3个问题 read committed:读已提交。解决脏读,存在2个问题 repeatable read:可重复读。解决:脏读、不可重复读,存在1个问题。 serializable :串行化。都解决,单事务。 样例 mysql 事务操作–简单 1234567891011121314151617//ABCD 一个事务Connection conn = null;try{ //1 获得连接 conn = ...; //2 开启事务 conn.setAutoCommit(false); A B C D //3 提交事务 conn.commit();} catche(){ //4 回滚事务 conn.rollback();} mysql 事务操作–Savepoint 1234567891011121314151617181920212223242526需求:AB(必须),CD(可选)Connection conn = null;Savepoint savepoint = null; //保存点,记录操作的当前位置,之后可以回滚到指定的位置。(可以回滚一部分)try{ //1 获得连接 conn = ...; //2 开启事务 conn.setAutoCommit(false); A B savepoint = conn.setSavepoint(); C D //3 提交事务 conn.commit();} catche(){ if(savepoint != null){ //CD异常 // 回滚到CD之前 conn.rollback(savepoint); // 提交AB conn.commit(); } else{ //AB异常 // 回滚AB conn.rollback(); }} 事务管理介绍导入jar spring-tx-5.1.3.RELEASE.jar(spring-framework中) 三个顶级接口 PlatformTransactionManager 平台事务管理器,spring要管理事务,必须使用事务管理器 进行事务配置时,必须配置事务管理器。 TransactionDefinition:事务详情(事务定义、事务属性),spring用于确定事务具体详情, 例如:隔离级别、是否只读、超时时间 等,进行事务配置时,必须配置详情。spring将配置项封装到该对象实例。 TransactionStatus:事务状态,spring用于记录当前事务运行状态。例如:是否有保存点,事务是否完成。 spring底层根据状态进行相应操作。 PlatformTransactionManager 事务管理器 导入jar包:需要时平台事务管理器的实现类(jdbc和tx) 常见的事务管理器 DataSourceTransactionManager ,jdbc开发时事务管理器,采用JdbcTemplate HibernateTransactionManager,hibernate开发时事务管理器,整合hibernate api详解 TransactionStatus getTransaction(TransactionDefinition definition) ,事务管理器 通过“事务详情”,获得“事务状态”,从而管理事务。 void commit(TransactionStatus status) 根据状态提交 void rollback(TransactionStatus status) 根据状态回滚 TransactionStatus TransactionDefinition 传播行为:在两个业务之间如何共享事务。 PROPAGATION_REQUIRED , required , 必须 【默认值】 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将创建一个新的事务。 PROPAGATION_SUPPORTS ,supports ,支持 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将以非事务执行。 PROPAGATION_MANDATORY,mandatory ,强制 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将抛异常。 PROPAGATION_REQUIRES_NEW , requires_new ,必须新的 如果A有事务,将A的事务挂起,B创建一个新的事务 如果A没有事务,B创建一个新的事务 PROPAGATION_NOT_SUPPORTED ,not_supported ,不支持 如果A有事务,将A的事务挂起,B将以非事务执行 如果A没有事务,B将以非事务执行 PROPAGATION_NEVER ,never,从不 如果A有事务,B将抛异常 如果A没有事务,B将以非事务执行 PROPAGATION_NESTED ,nested ,嵌套 A和B底层采用保存点机制,形成嵌套事务。 掌握:PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED 转账案例环境搭建 配置数据库 123456789create database my_bank;use my_bank;create table account( id int primary key auto_increment, username varchar(50), money int);insert into account(username,money) values('jack','10000');insert into account(username,money) values('rose','10000'); 导入jar 核心:4+1 aop : 4 (aop联盟、spring aop、aspectj规范、spring aspect) 数据库:2 (jdbc/tx) 驱动:mysql 连接池:c3p0 Dao层(接口+实现类) 1234567891011121314public interface AccountDao { void out(String outer,Integer money); void in(String iner,Integer money);}public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{ @Override public void out(String outer, Integer money) { this.getJdbcTemplate().update("update account set money = money - ? where username = ?", money,outer); } @Override public void in(String inner, Integer money) { this.getJdbcTemplate().update("update account set money = money + ? where username = ?", money,inner); }} Service层(接口+实现类) 12345678910111213141516public interface AccountService { void transfer(String outer, String inner, Integer money);}public class AccountServiceImpl implements AccountService{ private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String outer, String inner, Integer money) { accountDao.out(outer, money); //断电// int i = 1/0; accountDao.in(inner, money); }} spring配置 1234jdbc.driverClass=com.mysql.jdbc.Driverjdbc.jdbcUrl=jdbc:mysql://localhost:3306/my_bankjdbc.user=rootjdbc.password=x 123456789101112131415<context:property-placeholder location="classpath:jdbcInfo.properties"/><!-- 创建数据源 c3p0--><bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property></bean><!-- 配置dao --><bean id="AccountDaoId" class="cn.xwmdream.springMain.AccountDaoImpl"> <property name="dataSource" ref="dataSourceId"></property></bean><bean id="AccountServiceId" class = "cn.xwmdream.springMain.AccountServiceImpl"> <property name="AccountDao" ref="AccountDaoId"></property></bean> 测试1234String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);AccountService accountService = (AccountService) applicationContext.getBean("AccountServiceId");accountService.transfer("jack", "rose", 1000); 手动管理事务 spring底层使用 TransactionTemplate 事务模板进行操作。 操作 service 需要获得 TransactionTemplate spring 配置模板,并注入给service 模板需要注入事务管理器 配置事务管理器:DataSourceTransactionManager ,需要注入DataSource 修改代码 修改AccountService 1234567891011121314151617181920212223public class AccountServiceImpl implements AccountService{ private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } //需要spring注入模板 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } @Override public void transfer(final String outer,final String inner,final Integer money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus arg0) { accountDao.out(outer, money); //断电 int i = 1/0; accountDao.in(inner, money); } }); }} 修改配置文件 123456789101112131415161718192021222324<context:property-placeholder location="classpath:jdbcInfo.properties"/><!-- 创建数据源 c3p0--><bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property></bean><!-- 配置dao --><bean id="AccountDaoId" class="cn.xwmdream.springMain.AccountDaoImpl"> <property name="dataSource" ref="dataSourceId"></property></bean><bean id="AccountServiceId" class = "cn.xwmdream.springMain.AccountServiceImpl"> <property name="AccountDao" ref="AccountDaoId"></property> <property name="transactionTemplate" ref="transactionTemplate"></property></bean><!-- 创建模板 --><bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="txManager"></property></bean><!-- 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 --><bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSourceId"></property></bean> 工厂bean 生成代理:半自动 spring提供 管理事务的代理工厂bean TransactionProxyFactoryBean getBean() 获得代理对象 spring 配置一个代理 修改代码 service还改成原来的 123456789101112131415161718public class AccountServiceImpl implements AccountService{ private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } //需要spring注入模板 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } @Override public void transfer(final String outer,final String inner,final Integer money) { accountDao.out(outer, money); //断电 //int i = 1/0; accountDao.in(inner, money); }} 配置文件中增加 123456789101112131415161718192021222324<!-- 4 service 代理对象 4.1 proxyInterfaces 接口 4.2 target 目标类 4.3 transactionManager 事务管理器 4.4 transactionAttributes 事务属性(事务详情) prop.key :确定哪些方法使用当前事务配置 prop.text:用于配置事务详情 格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception 传播行为 隔离级别 是否只读 异常回滚 异常提交 例如: <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop> 默认传播行为,和隔离级别 <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly</prop> 只读 <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,+java.lang.ArithmeticException</prop> 有异常扔提交 --> <bean id="proxyAccountService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="proxyInterfaces" value="cn.xwmdream.springMain.AccountService"></property> <property name="target" ref="AccountServiceId"></property> <property name="transactionManager" ref="txManager"></property> <property name="transactionAttributes"> <props> <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop> </props> </property> </bean> 运行 1234String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);AccountService accountService = (AccountService) applicationContext.getBean("proxyAccountService");accountService.transfer("jack", "rose", 1000); AOP配置基于xml 在spring xml 配置aop 自动生成代理,进行事务的管理 配置管理器 配置事务详情 配置aop 还是用环境配置中的dao、service 修改配置文件 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 加载配置文件 "classpath:"前缀表示 src下 在配置文件之后通过 ${key} 获得内容 --> <context:property-placeholder location="classpath:jdbcInfo.properties"/> <!-- 创建数据源 c3p0--> <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 配置dao --> <bean id="AccountDaoId" class="cn.xwmdream.springMain.AccountDaoImpl"> <property name="dataSource" ref="dataSourceId"></property> </bean> <bean id="AccountServiceId" class = "cn.xwmdream.springMain.AccountServiceImpl"> <property name="AccountDao" ref="AccountDaoId"></property> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean> <!-- 创建模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="txManager"></property> </bean> <!-- 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSourceId"></property> </bean> <!-- 配置 事务详情(事务通知) , 在aop筛选基础上,对ABC三个确定使用什么样的事务。例如:AC读写、B只读 等 <tx:attributes> 用于配置事务详情(属性属性) <tx:method name=""/> 详情具体配置 propagation 传播行为 , REQUIRED:必须;REQUIRES_NEW:必须是新的 isolation 隔离级别 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/> </tx:attributes> </tx:advice> <!-- AOP编程,目标类有ABCD(4个连接点),切入点表达式 确定增强的连接器,从而获得切入点:ABC --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.xwmdream.springMain.*.*(..))"/> </aop:config></beans> 运行 1234String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);AccountService accountService = (AccountService) applicationContext.getBean("AccountServiceId");accountService.transfer("jack", "rose", 1000); AOP配置基于注解 1.配置事务管理器,将并事务管理器交予spring 2.在目标类或目标方法添加注解即可 @Transactional 修改代码 修改service实现类 12345678910111213141516171819202122//放类上是所有方法都使用事务@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)public class AccountServiceImpl implements AccountService{ private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } //需要spring注入模板 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } @Override //放方法上是这个方法都使用事务 //@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT) public void transfer(final String outer,final String inner,final Integer money) { accountDao.out(outer, money); //断电// int i = 1/0; accountDao.in(inner, money); }} 修改配置文件 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 加载配置文件 "classpath:"前缀表示 src下 在配置文件之后通过 ${key} 获得内容 --> <context:property-placeholder location="classpath:jdbcInfo.properties"/> <!-- 创建数据源 c3p0--> <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 配置dao --> <bean id="AccountDaoId" class="cn.xwmdream.springMain.AccountDaoImpl"> <property name="dataSource" ref="dataSourceId"></property> </bean> <bean id="AccountServiceId" class = "cn.xwmdream.springMain.AccountServiceImpl"> <property name="AccountDao" ref="AccountDaoId"></property> </bean> <!-- 4 事务管理 --> <!-- 4.1 事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSourceId"></property> </bean> <!-- 4.2 将管理器交予spring * transaction-manager 配置事务管理器 * proxy-target-class true : 底层强制使用cglib 代理 --> <tx:annotation-driven transaction-manager="txManager" proxy-target-class="false"/></beans> 运行 1234String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);AccountService accountService = (AccountService) applicationContext.getBean("AccountServiceId");accountService.transfer("jack", "rose", 1000); 注解中可以设置的值]]></content>
<tags>
<tag>java</tag>
<tag>spring</tag>
</tags>
</entry>
<entry>
<title><![CDATA[spring入门(AOP)]]></title>
<url>%2F2019%2F01%2F16%2Fspring%E5%85%A5%E9%97%A8-AOP%2F</url>
<content type="text"><![CDATA[AOP简介什么是AOP 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码 经典应用:事务管理、性能监视、安全检查、缓存 、日志等 Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码 AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入 AOP实现原理 aop底层将采用代理机制进行实现。 接口 + 实现类 :spring采用 jdk 的动态代理Proxy。 实现类:spring 采用 cglib字节码增强。 AOP术语 target:目标类,需要被代理的类。例如:UserService Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法 PointCut 切入点:已经被增强的连接点。例如:addUser() advice 通知/增强,增强代码。例如:after、before Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程. proxy 代理类 Aspect(切面): 是切入点pointcut和通知advice的结合 一个线是一个特殊的面。 一个切入点和一个通知,组成成一个特殊的面。 手动方法jdk的动态代理 JDK动态代理 对“装饰者”设计模式 简化。使用前提:必须有接口 动态代理过程 目标类:接口 + 实现类 切面类:用于存通知 MyAspect 工厂类:编写工厂生成代理 测试 创建目标接口和类 12345678910111213141516171819public interface UserService { public void addUser(); public void updateUser(); public void deleteUser();}public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("addUser"); } @Override public void updateUser() { System.out.println("updateUser"); } @Override public void deleteUser() { System.out.println("deleteUser"); }} 创建切面类 123456789public class MyAspect { public void before(){ System.out.println("之前"); } public void after(){ System.out.println("之后"); }} 创建工厂类 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class MyBeanFactory { public static UserService createService(){ //1 目标类 final UserService userService = new UserServiceImpl(); //2切面类 final MyAspect myAspect = new MyAspect(); /* 3 代理类:将目标类(切入点)和 切面类(通知) 结合 --> 切面 * Proxy.newProxyInstance * 参数1:loader ,类加载器,动态代理类 运行时创建,任何类都需要类加载器将其加载到内存。 * 一般情况:当前类.class.getClassLoader(); * 目标类实例.getClass().get... * 参数2:Class[] interfaces 代理类需要实现的所有接口 * 方式1:目标类实例.getClass().getInterfaces() ;注意:只能获得自己接口,不能获得父元素接口 * 方式2:new Class[]{UserService.class} * 例如:jdbc 驱动 --> DriverManager 获得接口 Connection * 参数3:InvocationHandler 处理类,接口,必须进行实现类,一般采用匿名内部 * 提供 invoke 方法,代理类的每一个方法执行时,都将调用一次invoke * 参数31:Object proxy :代理对象 * 参数32:Method method : 代理对象当前执行的方法的描述对象(反射) * 执行方法名:method.getName() * 执行方法:method.invoke(对象,实际参数) * 参数33:Object[] args :方法实际参数 * */ UserService proxService = (UserService)Proxy.newProxyInstance( MyBeanFactory.class.getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //前执行 myAspect.before(); //执行目标类的方法 Object obj = method.invoke(userService, args); //后执行 myAspect.after(); return obj; } }); return proxService; }} 测试 1234UserService userService = MyBeanFactory.createService();userService.addUser();userService.updateUser();userService.deleteUser(); 执行结果:之前addUser之后之前updateUser之后之前deleteUser之后 CGLIB字节码增强 没有接口,只有实现类。 采用字节码增强框架 cglib,在运行时 创建目标类的子类,从而对目标类进行增强。 导入jar包:自己导包(了解): 核心:hibernate-distribution-3.6.10.Final\lib\bytecode\cglib\cglib-2.2.jar 依赖:struts-2.3.15.3\apps\struts2-blank\WEB-INF\lib\asm-3.3.jarspring-core..jar 已经整合以上两个内容 代码和上面一样,不过没有接口工厂类:12345678910111213141516171819202122232425262728293031323334353637383940public class MyBeanFactory { public static UserServiceImpl createService(){ //1 目标类 final UserServiceImpl userServiceImpl = new UserServiceImpl(); //2切面类 final MyAspect myAspect = new MyAspect(); // 3.代理类 ,采用cglib,底层创建目标类的子类 //3.1 核心类 Enhancer enhancer = new Enhancer(); //3.2 确定父类 enhancer.setSuperclass(userServiceImpl.getClass()); /* 3.3 设置回调函数 , MethodInterceptor接口 等效 jdk InvocationHandler接口 * intercept() 等效 jdk invoke() * 参数1、参数2、参数3:以invoke一样 * 参数4:methodProxy 方法的代理 * * */ enhancer.setCallback(new MethodInterceptor(){ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //前 myAspect.before(); //执行目标类的方法 Object obj = method.invoke(userServiceImpl, args); // * 执行代理类的父类 ,执行目标类 (目标类和代理类 父子关系)和上面一样功能,只是两种形式 methodProxy.invokeSuper(proxy, args); //后 myAspect.after(); return obj; } }); //3.4 创建代理 UserServiceImpl proxService = (UserServiceImpl) enhancer.create(); return proxService; }} 这段代码每个方法会执行两遍,因为上面invoke和invokeSuper都是执行方法,用的时候只用执行一个就行 AOP联盟通知类型 AOP联盟为通知Advice定义了org.aopalliance.aop.Advice Spring按照通知Advice在目标类方法的连接点位置,可以分为5类 前置通知 org.springframework.aop.MethodBeforeAdvice:在目标方法执行前实施增强 后置通知 org.springframework.aop.AfterReturningAdvice:在目标方法执行后实施增强 环绕通知 org.aopalliance.intercept.MethodInterceptor:在目标方法执行前后实施增强 异常抛出通知 org.springframework.aop.ThrowsAdvice:在方法抛出异常后实施增强 引介通知 org.springframework.aop.IntroductionInterceptor:在目标类中添加一些新的方法和属性 重点掌握环绕通知,必须手动执行目标方法1234567try{ //前置通知 //执行目标方法 //后置通知} catch(){ //抛出异常通知} spring编写代理:半自动 让spring 创建代理对象,从spring容器中手动的获取代理对象。 导入jar包: 核心:4+1 AOP:AOP联盟(规范)下载地址 spring-aop(实现)(jar包在spring的framework中spring-aop-5.1.3.RELEASE.jar) 代码示例 接口+实现类: 1234567891011121314151617181920public interface UserService { public void addUser(); public void updateUser(); public void deleteUser();}public class UserServiceImpl implements UserService{ public void addUser() { System.out.println("addUser"); } public void updateUser() { System.out.println("updateUser"); } public void deleteUser() { System.out.println("deleteUser"); }} 切面类 1234567891011public class MyAspect implements MethodInterceptor { @Override public Object invoke(MethodInvocation mi) throws Throwable { System.out.println("前"); //手动执行目标方法 Object obj = mi.proceed(); System.out.println("后"); return obj; }} 配置文件 123456789101112131415161718192021222324<!-- 1 创建目标类 --><bean id="userServiceId" class="cn.xwmdream.springMain.UserServiceImpl"></bean><!-- 2 创建切面类 --><bean id="myAspectId" class="cn.xwmdream.springMain.MyAspect"></bean><!-- 3 创建代理类 * 使用工厂bean FactoryBean ,底层调用 getObject() 返回特殊bean * ProxyFactoryBean 用于创建代理工厂bean,生成特殊代理对象 interfaces : 确定接口们 通过<array>可以设置多个值:<property name="interfaces"><array><value></value></array></property> 只有一个值时,value="" target : 确定目标类 interceptorNames : 通知 切面类的名称,类型String[],如果设置一个值 value="" optimize :强制使用cglib <property name="optimize" value="true"></property> 底层机制 如果目标类有接口,采用jdk动态代理 如果没有接口,采用cglib 字节码增强 如果声明 optimize = true ,无论是否有接口,都采用cglib--><bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interfaces" value="cn.xwmdream.springMain.UserService"></property> <property name="target" ref="userServiceId"></property> <property name="interceptorNames" value="myAspectId"></property></bean> 测试 1234567String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);//获得代理类UserService userService = (UserService) applicationContext.getBean("proxyServiceId");userService.addUser();userService.updateUser();userService.deleteUser(); 运行结果:前addUser后前updateUser后前deleteUser后 全自动 从spring容器获得目标类,如果配置aop,spring将自动生成代理。 要确定目标类,aspectj 切入点表达式,导入jar包下载地址 代码实现类和切面类还是上面,变化的是配置文件 1234567891011121314151617181920212223242526272829<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 1 创建目标类 --> <bean id="userServiceId" class="cn.xwmdream.springMain.UserServiceImpl"></bean> <!-- 2 创建切面类(通知) --> <bean id="myAspectId" class="cn.xwmdream.springMain.MyAspect"></bean> <!-- 3 aop编程 3.1 导入命名空间 3.2 使用 <aop:config>进行配置 proxy-target-class="true" 声明时使用cglib代理 <aop:pointcut> 切入点 ,从目标对象获得具体方法 <aop:advisor> 特殊的切面,只有一个通知 和 一个切入点 advice-ref 通知引用 pointcut-ref 切入点引用 3.3 切入点表达式 execution(* com.itheima.c_spring_aop.*.*(..)) 选择方法 返回值任意 包 类名任意 方法名任意 参数任意 --> <aop:config proxy-target-class="true"> <aop:pointcut expression="execution(* cn.xwmdream.springMain.*.*(..))" id="myPointCut"/> <aop:advisor advice-ref="myAspectId" pointcut-ref="myPointCut"/> </aop:config></beans> 注意配置aop命名空间 切入点表示要代理的方法,就是上面的myPointCut的那个表达式 测试1234567String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);//获得代理类UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean("userServiceId");userService.addUser();userService.updateUser();userService.deleteUser(); 因为使用了cglib所以能直接获得实现类 AspectJ介绍 AspectJ是一个基于Java语言的AOP框架 Spring2.0以后新增了对AspectJ切点表达式支持 @AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面 新版本Spring框架,建议使用AspectJ方式来开发AOP 主要用途:自定义开发 导入jar包 4个: aop联盟规范 spring aop 实现 aspect 规范 下载地址 spring aspect 实现(框架中)(spring-aspects-5.1.3.RELEASE.jar) 切入点表达式 execution() 用于描述方法 【掌握】语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常) 修饰符,一般省略 public 公共方法 任意 返回值,不能省略 void 返回没有值 String 返回值字符串 任意 包,[省略] cn.xwmdream.crm 固定包 cn.xwmdream.crm..service crm包下面子包任意 (例如:cn.xwmdream.crm.staff.service) cn.xwmdream.crm.. crm包下面的所有子包(含自己) cn.xwmdream.crm..service.. crm包下面任意子包,固定目录service,service目录任意包 类,[省略] UserServiceImpl 指定类 Impl 以Impl结尾 User 以User开头 任意 方法名,不能省略 addUser 固定方法 add 以add开头 Do 以Do结尾 任意 (参数) () 无参 (int) 一个整型 (int ,int) 两个 (..) 参数任意 throws ,可省略,一般不写。综合1 execution( com.itheima.crm..service...(..))综合2 <aop:pointcut expression=”execution( com.itheima.WithCommit.*(..)) ||execution(* com.itheima.*Service.*(..))" id="myPointCut"/> within:匹配包或子包中的方法(了解) within(com.itheima.aop..*) this:匹配实现接口的代理对象中的方法(了解) this(com.itheima.aop.user.UserDAO) target:匹配实现接口的目标对象中的方法(了解) target(com.itheima.aop.user.UserDAO) args:匹配参数格式符合标准的方法(了解) args(int,int) bean(id) 对指定的bean所有的方法(了解) bean(‘userServiceId’)更多用法 AspectJ 通知类型 aop联盟定义通知类型,具有特性接口,必须实现,从而确定方法名称。 aspectj 通知类型,只定义类型名称。已经方法格式。 个数:6种,知道5种,掌握1中。 before:前置通知(应用:各种校验):在方法执行前执行,如果通知抛出异常,阻止方法运行 afterReturning:后置通知(应用:常规数据处理):方法正常返回后执行,如果方法中抛出异常,通知无法执行:必须在方法执行后才执行,所以可以获得方法的返回值。 around:环绕通知(应用:十分强大,可以做任何事情):方法执行前后分别执行,可以阻止方法的执行,必须手动执行目标方法 afterThrowing:抛出异常通知(应用:包装异常信息):方法抛出异常后执行,如果方法没有抛出异常,无法执行 after:最终通知(应用:清理现场):方法执行完毕后执行,无论方法中是否出现异常 重点学习环绕通知123456789try{ //前置:before //手动执行目标方法 //后置:afterRetruning} catch(){ //抛出异常 afterThrowing} finally{ //最终 after} 基于xml 目标类:接口 + 实现 切面类:编写多个通知,采用aspectj 通知名称任意(方法名任意) aop编程,将通知应用到目标类 测试 目标类+接口,还是上面的UserService和UserServiceImpl 切面类 1234567891011121314151617181920212223public class MyAspect { public void myBefore(JoinPoint joinPoint){ System.out.println("前置通知 : " + joinPoint.getSignature().getName()); } public void myAfterReturning(JoinPoint joinPoint,Object ret){ System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret); } //返回值必须是object public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("前"); //手动执行目标方法 Object obj = joinPoint.proceed(); System.out.println("后"); return obj; } public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ System.out.println("抛出异常通知 : " + e.getMessage()); } public void myAfter(JoinPoint joinPoint){ System.out.println("最终通知"); }} 配置xml 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960<!-- 1 创建目标类 --><bean id="userServiceId" class="cn.xwmdream.springMain.UserServiceImpl"></bean><!-- 2 创建切面类(通知) --><bean id="myAspectId" class="cn.xwmdream.springMain.MyAspect"></bean><!-- 3 aop编程 <aop:aspect> 将切面类 声明“切面”,从而获得通知(方法) ref 切面类引用 <aop:pointcut> 声明一个切入点,所有的通知都可以使用。 expression 切入点表达式 id 名称,用于其它通知引用--><aop:config> <aop:aspect ref="myAspectId"> <aop:pointcut expression="execution(* cn.xwmdream.springMain.UserServiceImpl.*(..))" id="myPointCut"/> <!-- 3.1 前置通知 <aop:before method="" pointcut="" pointcut-ref=""/> method : 通知,及方法名 pointcut :切入点表达式,此表达式只能当前通知使用。 pointcut-ref : 切入点引用,可以与其他通知共享切入点。 通知方法格式:public void myBefore(JoinPoint joinPoint){ 参数1:org.aspectj.lang.JoinPoint 用于描述连接点(目标方法),获得目标方法名等 例如: <aop:before method="myBefore" pointcut-ref="myPointCut"/> --> <!-- 3.2后置通知 ,目标方法后执行,获得返回值 <aop:after-returning method="" pointcut-ref="" returning=""/> returning 通知方法第二个参数的名称 通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object ret){ 参数1:连接点描述 参数2:类型Object,参数名 returning="ret" 配置的 例如: <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" /> --> <!-- 3.3 环绕通知 <aop:around method="" pointcut-ref=""/> 通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ 返回值类型:Object 方法名:任意 参数:org.aspectj.lang.ProceedingJoinPoint 抛出异常 执行目标方法:Object obj = joinPoint.proceed(); 例如: <aop:around method="myAround" pointcut-ref="myPointCut"/> --> <!-- 3.4 抛出异常 <aop:after-throwing method="" pointcut-ref="" throwing=""/> throwing :通知方法的第二个参数名称 通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ 参数1:连接点描述对象 参数2:获得异常信息,类型Throwable ,参数名由throwing="e" 配置 例如: <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> --> <!-- 3.5 最终通知 --> <aop:after method="myAfter" pointcut-ref="myPointCut"/> </aop:aspect></aop:config> 测试 1234567String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);//获得代理类UserService userService = (UserService) applicationContext.getBean("userServiceId");userService.addUser();userService.updateUser();userService.deleteUser(); 如果是抛出异常的方法,比如updateUser抛出异常,那么deleteUser不会执行 最终通知,只要切入点执行了,那么最终通知肯定会执行 如果一个通知私有,表示这个myBefore单独给这个表达式加通知1<aop:before method="myBefore" pointcut="execution(* cn.xwmdream.springMain.UserServiceImpl.*(..))"/> 基于注解 配置文件 123456789101112131415161718<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 1.扫描 注解类 --> <context:component-scan base-package="cn.xwmdream.springMain"></context:component-scan> <!-- 2.确定 aop注解生效 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy></beans> 目标类 1234567891011121314151617@Service("userServiceImplId")public class UserServiceImpl implements UserService{ public void addUser() { System.out.println("addUser"); } public void updateUser() { System.out.println("updateUser"); } public void deleteUser() { System.out.println("deleteUser"); }}public interface UserService { public void addUser(); public void updateUser(); public void deleteUser();} 切面类 1234567891011121314151617181920212223242526272829303132333435363738//先声明是一个bean,再声明是一个aspect@Component@Aspectpublic class MyAspect { //注解前置通知 //@Before("execution(* cn.xwmdream.springMain.UserServiceImpl.*(..))") public void myBefore(JoinPoint joinPoint){ System.out.println("前置通知 : " + joinPoint.getSignature().getName()); } //也可以声明公共切入点 @Pointcut("execution(* cn.xwmdream.springMain.UserServiceImpl.*(..))") private void myPointCut() { } //@AfterReturning(value="myPointCut()" ,returning="ret") public void myAfterReturning(JoinPoint joinPoint,Object ret){ System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret); } //返回值必须是object //@Around(value = "myPointCut()") public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("前"); //手动执行目标方法 Object obj = joinPoint.proceed(); System.out.println("后"); return obj; } //@AfterThrowing(value="myPointCut()" ,throwing="e") public void myAfterThrowing(JoinPoint joinPoint,Throwable e){ System.out.println("抛出异常通知 : " + e.getMessage()); } //@After(value="myPointCut()") public void myAfter(JoinPoint joinPoint){ System.out.println("最终通知"); }} 运行 1234567String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);//获得代理类UserService userService = (UserService) applicationContext.getBean("userServiceImplId");userService.addUser();userService.updateUser();userService.deleteUser(); aop注解总结 @Aspect 声明切面,修饰切面类,从而获得 通知。 通知 @Before 前置 @AfterReturning 后置 @Around 环绕 @AfterThrowing 抛出异常 @After 最终 切入点 @PointCut ,修饰方法 private void xxx(){} 之后通过“方法名”获得切入点引用 JdbcTemplate spring 提供用于操作JDBC工具类,类似:DBUtils。 依赖 连接池DataSource (数据源) 环境搭建 创建数据表 12345678910create database my_database;use my_database;create table t_user( id int primary key auto_increment, username varchar(50), password varchar(32));insert into t_user(username,password) values('jack','1234');insert into t_user(username,password) values('rose','5678'); 导入jar包 这里的jar spring-jdbc-5.1.3.RELEASE.jar(spring-framework中) spring-tx-5.1.3.RELEASE.jar(spring-framework中) commons-dbcp2-2.5.0.jar下载地址 commons-pool2-2.6.0.jar下载地址 tomcat的lib下还要有tomcat-juli.jar javabean12345678public class User { private Integer id; private String username; private String password; /* get&set */} 1.使用api方式(不常用,因为没使用spring)123456789101112//1 创建数据源(连接池) dbcpBasicDataSource dataSource = new BasicDataSource();// * 基本4项dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/my_database");dataSource.setUsername("root");dataSource.setPassword("6");//2 创建模板JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);//3 通过api操作jdbcTemplate.update("insert into t_user(username,password) values(?,?);", "tom","998"); 2.配置dbcp 创建UserDao,提供一个update 123456789101112public class UserDao{ //jdbc模板将有spring注入 private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public void updata(User user) { String sql = "update t_user set username=?,password=? where id=?"; Object[] args = {user.getUsername(),user.getPassword(),user.getId()}; jdbcTemplate.update(sql,args); }} 写配置文件 12345678910111213141516<!-- 创建数据源 --><bean id="dataSourceId" class="org.apache.tomcat.dbcp.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/my_database"></property> <property name="username" value="root"></property> <property name="password" value="xxx"></property></bean><!-- 创建模板 ,需要注入数据源--><bean id="jdbcTemplateId" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSourceId"></property></bean><!-- 配置dao --><bean id="userDaoId" class="cn.xwmdream.springMain.UserDao"> <property name="jdbcTemplate" ref="jdbcTemplateId"></property></bean> 注意他们的包名类名,如果不知道可以用第一种方法看看具体报名 用di注入了他们的参数 调用123456789User user = new User();user.setId(1);user.setUsername("name");user.setPassword("123");String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);//获得代理类UserDao userdao = (UserDao) applicationContext.getBean("userDaoId");userdao.updata(user); 3.使用c3p0只需要把数据源从BasicDataSource改成c3p01234567<!-- 创建数据源 --><bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/my_database"></property> <property name="user" value="root"></property> <property name="password" value="xxx"></property></bean> 4.使用JdbcDaoSupport JdbcDaoSupport内置了JdbcTemplate,所以就不用自己写JdbcTemplate 配置文件中只用配置dao和数据源 配置文件1234567891011<!-- 创建数据源 --><bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/my_database"></property> <property name="user" value="root"></property> <property name="password" value="x"></property></bean><!-- 配置dao --><bean id="userDaoId" class="cn.xwmdream.springMain.UserDao"> <property name="dataSource" ref="dataSourceId"></property></bean> 父类实现了dataSource的注入 Dao1234567public class UserDao extends JdbcDaoSupport{ public void updata(User user) { String sql = "update t_user set username=?,password=? where id=?"; Object[] args = {user.getUsername(),user.getPassword(),user.getId()}; this.getJdbcTemplate().update(sql,args); }} 直接用this.getJdbcTemplate()就能调用到模板进行相应操作 使用properties写入配置 创建一个properties 1234jdbc.driverClass=com.mysql.jdbc.Driverjdbc.jdbcUrl=jdbc:mysql://localhost:3306/my_databasejdbc.user=rootjdbc.password=xxx 配置文件 12345678910111213<!-- 加载配置文件 "classpath:"前缀表示 src下如果要写目录就是cn/xxx/xxx.properties 在配置文件之后通过 ${key} 获得内容--><context:property-placeholder location="classpath:jdbcInfo.properties"/><!-- 创建数据源 c3p0--><bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property></bean>]]></content>
<tags>
<tag>java</tag>
<tag>spring</tag>
</tags>
</entry>
<entry>
<title><![CDATA[spring入门-Bean]]></title>
<url>%2F2019%2F01%2F12%2Fspring%E5%85%A5%E9%97%A8-Bean%2F</url>
<content type="text"><![CDATA[简介spring Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来 轻量级:与EJB对比,依赖资源少,销毁的资源少。 spring由来Expert One-to-One J2EE Design and DevelopmentExpert One-to-One J2EE Development without EJB spring核心 控制反转(IoC)(Inverse of Control)原本是自己控制对象变成由spring控制bean对象 面向切面(AOP) spring优点 方便解耦,简化开发 (高内聚低耦合) Spring就是一个大工厂(容器),可以将所有对象创建和依赖关系维护,交给Spring管理 spring工厂是用于生成bean AOP编程的支持 Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能 声明式事务的支持 只需要通过配置就可以完成对事务的管理,而无需手动编程 方便程序的测试 Spring对Junit4支持,可以通过注解方便的测试Spring程序 方便集成各种优秀框架 Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持 降低JavaEE API的使用难度 Spring 对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低 配置环境 引入jar下载四个核心(beans、core、context、expression) + 1个依赖(commons-loggins.jar)四个核心:spring-framework的lib目录下依赖下载:commons-loggins.jar IOC(控制反转)入门 之前开发中,直接new一个对象即可。 学习spring之后,将由Spring创建对象实例–> IoC 控制反转(Inverse of Control)之后需要实例对象时,从spring工厂(容器)中获得,需要将实现类的全限定名称配置到xml文件中 就是把创建对象的控制权反转给了spring,以前是通过new,现在是通过spring创建 创建Bean类 12345public class TestClass { public void add() { System.out.println("a_add"); }} 创建配置文件在任意地址创建一个任意名字的xml文件,但是默认是在src目录下创建一个applicationContext.xml 1234567891011121314<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置service <bean> 配置需要创建的对象 id :用于之后从spring容器获得实例时使用的 class :需要创建实例的全限定类名 --> <bean id="TestClassId" class = "cn.xwmdream.springMain.TestClass"></bean></beans> spring读取xml文件调用类 12345678//读取xml文件String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);//通过xml中的id创建类TestClass test = (TestClass)applicationContext.getBean("TestClassId");//调用类方法test.add(); DI(依赖注入)入门 DI Dependency Injection ,依赖注入 概念123class B{ private A a;//这就是B类依赖于A类} 依赖:一个对象需要使用另外一个对象 注入:通过setter(set&get的那个set)方法进行另一个对象实例设置1234567//原来是private B b = new B();//spring之后是private B b;public void setB(B b){ this.b = b;} 模拟spring执行过程 动作 实现 对应spring 对应xml标签 创建service Service service = new Service() IOC <bean> 创建dao Dao dao = new Dao() IOC <bean> 把dao设置给service service.setDao(dao) DI <property> 入门实例 Dao.java 12345public class Dao { public void add() { System.out.println("a_add"); }} Service.java 123456789public class Service { private Dao dao; public void setDao(Dao dao) { this.dao = dao; } public void addDao() { dao.add(); }} 配置文件applicationContext.xml 12345678910111213141516171819<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- <property> 用于进行属性注入 name: bean中的属性名,通过setter方法获得 setBookDao ##> BookDao ##> bookDao ref :另一个bean的id值的引用 --> <!-- 创建service --> <bean id="serviceId" class="cn.xwmdream.springMain.Service"> <property name="dao" ref="DaoId"></property> </bean> <!-- 创建dao实例 --> <bean id="DaoId" class="cn.xwmdream.springMain.Dao"></bean></beans> 解释一下property标签,是因为Service类中有一个Dao的成员名字叫dao,所以name为dao,他是一个Dao类,引用Dao类的id,所以是DaoId 核心api BeanFactory :这是一个工厂,用于生成任意bean。采取延迟加载,第一次getBean时才会初始化Bean ApplicationContext:是BeanFactory的子接口,功能更强大。(国际化处理、事件传递、Bean自动装配、各种不同应用层的Context实现)。当配置文件被加载,就进行对象实例化。 ClassPathXmlApplicationContext 用于加载classpath(类路径、src)下的xml。加载xml运行时位置 –> /WEB-INF/classes/…xml FileSystemXmlApplicationContext 用于加载指定盘符下的xml。加载xml运行时位置 –> /WEB-INF/…xml 通过java web ServletContext.getRealPath() 获得具体盘符 Bean装配基于xml实例化方式 3种bean实例化方法:默认构造,静态工厂,实例工厂 默认构造上面ioc用配置文件实例化对象的方法必须有默认构造方法1<bean id="" class=""></bean>必须有默认构造方法 静态工厂 常用于spring整合其他框架(工具) 静态工厂:用于生产实例对象,所有的方法必须是static1<bean id="" class="工厂全限定类名" factory-method="静态方法"></bean> 举个栗子 创建工厂DaoFactory.java 12345678910public class MyBeanFactory { public static Dao createDao() { return new Dao(); }}class Dao { public void add() { System.out.println("a_add"); }} 配置信息applicationContext.xml 12345<!-- 将静态工厂创建的实例交予spring class 确定静态工厂全限定类名 factory-method 确定静态方法名 --><bean id="daoFactoryId" class="cn.xwmdream.springMain.MyBeanFactory" factory-method="createDao"></bean> 调用 12345678//自定义工厂Dao dao = MyBeanFactory.createDao();dao.add();//spring工厂String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);Dao dao1 = applicationContext.getBean("daoFactoryId",Dao.class);dao1.add(); 实例工厂 实例工厂:必须先有工厂实例对象,通过实例对象创建对象。提供所有的方法都是“非静态”的。 栗子 创建工厂DaoFactory.java 12345678910public class MyBeanFactory { public Dao createDao() { return new Dao(); }}class Dao { public void add() { System.out.println("a_add"); }} 配置信息applicationContext.xml 1234567<!-- 创建工厂实例 --><bean id="myBeanFactoryId" class="cn.xwmdream.springMain.MyBeanFactory"></bean><!-- 获得userservice * factory-bean 确定工厂实例 * factory-method 确定普通方法--><bean id="myDaoId" factory-bean="myBeanFactoryId" factory-method="createDao"></bean> 调用 123456789//自定义工厂MyBeanFactory myBeanFactory = new MyBeanFactory();Dao dao = myBeanFactory.createDao();dao.add();//spring工厂String xmlPath = "applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);Dao dao1 = applicationContext.getBean("myDaoId",Dao.class);dao1.add(); Bean种类普通bean之前操作的都是普通bean。 ,spring直接创建A实例,并返回 FactoryBean 是一个特殊的bean,具有工厂生成对象能力,只能生成特定的对象。 bean必须使用 FactoryBean接口,此接口提供方法 getObject() 用于获得特定bean。 先创建FB实例,使用调用getObject()方法,并返回方法的返回值相当于12FB fb = new FB();return fb.getObject(); BeanFactory 和 FactoryBean 对比 BeanFactory:工厂,用于生成任意bean。 FactoryBean:特殊bean,用于生成另一个特定的bean。例如:ProxyFactoryBean ,此工厂bean用于生产代理。 获得代理对象实例。AOP使用 作用域 作用域:用于确定spring创建bean实例个数 取值: singleton 单例,默认值。 prototype 多例,每执行一次getBean将获得一个实例。例如:struts整合spring,配置action多例。 配置信息1<bean id="" class="" scope="取值类型"></bean> 生命周期一共有十一个生命周期Spring Bean的生命周期(非常详细) 初始化和销毁 目标方法执行前后执行后,将进行初始化或销毁。12345<!-- init-method 用于配置初始化方法,准备数据等 destroy-method 用于配置销毁方法,清理资源等--><bean id="" class="" init-method="初始化方法名称" destroy-method="销毁的方法名称"> 12345//要求:1.容器必须close,销毁方法执行; 2.必须是单例的//applicationContext.getClass().getMethod("close").invoke(applicationContext);// * 此方法接口中没有定义,实现类提供//先把applicationContext定义成ClassPathXmlApplicationContext类型,然后调用close方法applicationContext.close(); 容器必须close,销毁方法才能执行 bean类不能是prototype(多例),否则不能调用销毁方法 BeanPostProcessor 后处理Bean 有两个方法,before和after spring 提供一种机制,只要实现此接口BeanPostProcessor,并将实现类提供给spring容器,spring容器将自动执行,在初始化方法前执行before(),在初始化方法后执行after()。 文档中的描述:Factory hook(勾子) that allows for custom modification of new bean instances, e.g. checking for marker interfaces(接口) or wrapping(装饰者) them with proxies(代理). spring提供工厂勾子,用于修改实例对象,可以生成代理对象,是AOP底层。 模拟 123456A a =new A();a = B.before(a)//before方法是在对象init前调用a.init();//初始化a = B.after(a);//after是在对象初始化后调用,可以在此返回代理对象,目的在目标方法前后执行(例如:开启事务、提交事务)a.addUser();a.destroy() B类实现了BeanPostProcessor接口,所以在A用spring实例化的前后会调用B.before(a)和B.after(a) 可以在before和after去写一些代码,比如可以在after中创建一个代理,代理可以在目标执行一些函数的前后执行一些操作,比如开启提交事务、计时等操作 如果是要创建代理,必须是接口创建类 BeanPostProcessor只需要在配置文件中写入class参数即可 例子 配置文件 12345<!-- 一个普通的类 --><bean id="daoFactoryId" class="cn.xwmdream.springMain.MyBeanFactory" init-method="init" destroy-method="destroy"></bean><!-- BeanPostProcessor在配置中声明 --><bean class="cn.xwmdream.springMain.MyBeanPostProcessor"></bean> 创建一个BeanPostProcessorMyBeanPostProcessor.java 123456789101112131415161718192021222324public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("前方法 : " + beanName); return bean; } @Override public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { System.out.println("后方法 : " + beanName); return Proxy.newProxyInstance( MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("------开启事务"); //执行目标方法 Object obj = method.invoke(bean, args); System.out.println("------提交事务"); return obj; }}); }} 在before中是原封不动的返回 在after中使用了代理,返回去的对象调用方法时候会执行”开启事务”/“提交事务”那一段代码 注意:使用代理的对象必须是用接口调用方法 注意Proxy、Method、InvocationHandler都是引用自java.lang.reflect.包下的 创建接口MyInterface.java 123public interface MyInterface { public void da();} 创建实现类 1234567891011public class MyBeanFactory implements MyInterface{ public void da() { System.out.println("da"); } public void destroy() { System.out.println("销毁了"); } public void init() { System.out.println("创建了"); }} 执行 123456//spring工厂String xmlPath = "applicationContext.xml";ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);MyInterface myBeanFactory = (MyInterface) applicationContext.getBean("daoFactoryId");myBeanFactory.da();applicationContext.close(); 执行结果:前方法 : daoFactoryId创建了后方法 : daoFactoryId——开启事务da——提交事务销毁了 可以用此方法在after中给创建的对象创建代理,但是必须是用接口调用才行 属性依赖注入 依赖注入方式:手动装配 和 自动装配 手动装配:一般进行配置信息都采用手动 基于xml装配:构造方法、setter方法 基于注解装配: 自动装配:struts和spring 整合可以自动装配 byType:按类型装配 byName:按名称装配 constructor构造装配, auto: 不确定装配。 构造方法 目标类User.java 123456789101112131415161718public class User { private String username; private Integer age; public User(Integer age, String username) { System.out.println("构造方法1"); this.age = age; this.username = username; } public User(String username, Integer age) { System.out.println("构造方法2"); this.username = username; this.age = age; } @Override public String toString() { return "User [username=" + username + ", age=" + age + "]"; }} spring配置applicationContext.xml 12345678910111213141516171819202122<!-- 构造方法注入 * <constructor-arg> 用于配置构造方法一个参数argument name :参数的名称 value:设置普通数据 ref:引用数据,一般是另一个bean id值 index :参数的索引号,从0开始 。如果只有索引,匹配到了多个构造方法时,默认使用第一个。 type :确定参数类型 例如:使用名称name <constructor-arg name="username" value="jack"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> 例如2:【类型type 和 索引 index】 <constructor-arg index="0" type="java.lang.String" value="1"></constructor-arg> <constructor-arg index="1" type="java.lang.Integer" value="2"></constructor-arg>--><bean id="userId" class="cn.xwmdream.springMain.User" > <constructor-arg index="0" type="java.lang.String" value="我的名字"></constructor-arg> <constructor-arg index="1" type="java.lang.Integer" value="2"></constructor-arg></bean><bean id="userId1" class="cn.xwmdream.springMain.User" > <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="username" value="还是我的名字"></constructor-arg></bean> 运行 123456String xmlPath = "applicationContext.xml";ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);User user = (User) applicationContext.getBean("userId");System.out.println(user);user = (User) applicationContext.getBean("userId1");System.out.println(user); 运行结果:构造方法2User [username=我的名字, age=2] index是第几个参数,从0开始,上面的配置文件说的是找第一个是String第二个是Integer类型的构造方法写入参数 第二个方式是通过参数名的形式创建构造方法 set方法 创建bean类Person.java1234567891011121314151617181920212223242526272829303132333435public class Person { private String username; private Integer age; private Address homeAddress; private Address workAddress; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Address getHomeAddress() { return homeAddress; } public void setHomeAddress(Address homeAddress) { this.homeAddress = homeAddress; } public Address getWorkAddress() { return workAddress; } public void setWorkAddress(Address workAddress) { this.workAddress = workAddress; } @Override public String toString() { return "Pserson [username=" + username + ", age=" + age + ", homeAddress=" + homeAddress + ", workAddress=" + workAddress + "]"; }} Address.java1234567891011121314151617181920public class Address { private String address; private String tel; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getTel() { return tel; } public void setTel(String tel) { this.tel = tel; } @Override public String toString() { return "Address [address=" + address + ", tel=" + tel + "]"; }} 配置文件123456789101112131415161718192021222324252627282930313233<!-- setter方法注入 * 普通数据 <property name="" value="值"> 等效 <property name=""> <value>值 * 引用数据 <property name="" ref="另一个bean"> 等效 <property name=""> <ref bean="另一个bean"/>--><bean id="personId" class="cn.xwmdream.springMain.Person"> <property name="username" value="名字"></property> <property name="age"> <value>1234</value> </property> <property name="homeAddress" ref="homeAddrId"></property> <property name="workAddress"> <ref bean="workAddrId"/> </property></bean><bean id="homeAddrId" class="cn.xwmdream.springMain.Address"> <property name="address" value="阜南"></property> <property name="tel" value="110"></property></bean><bean id="workAddrId" class="cn.xwmdream.springMain.Address"> <property name="address" value="北京八宝山"></property> <property name="tel" value="120"></property></bean> 普通数据等效值 引用数据等效 运行1234String xmlPath = "applicationContext.xml";ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);Person pserson = (Person) applicationContext.getBean("personId");System.out.println(pserson); 运行结果:Pserson [username=名字, age=1234, homeAddress=Address [address=阜南, tel=110], workAddress=Address [address=北京八宝山, tel=120]] p命名空间 对“setter方法注入”进行简化,替换,而是在 p命名空间使用前提,必须添加命名空间上述配置文件可以改成 12345678910111213<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 id="personId" class="cn.xwmdream.springMain.Person" p:username="我的名字" p:age="1234" p:homeAddress-ref="homeAddrId" p:workAddress-ref="workAddrId"> </bean> <bean id="homeAddrId" class="cn.xwmdream.springMain.Address" p:address="安阳" p:tel="110"> </bean> <bean id="workAddrId" class="cn.xwmdream.springMain.Address" p:address="北京" p:tel="120"> </bean></beans> 注意加上第三行的命名空间,空间地址就是第一行的beans换成p SpEL 对进行统一编程,所有的内容都使用value #{123}、#{‘jack’} : 数字、字符串 #{beanId} :另一个bean引用 #{beanId.propName} :操作数据 #{beanId.toString()} :执行方法 #{T(类).字段|方法} :静态方法或字段 1234567891011<!-- <property name="cname" value="#{'jack'}"></property> <property name="cname" value="#{customerId.cname.toUpperCase()}"></property> 通过另一个bean,获得属性,调用的方法 <property name="cname" value="#{customerId.cname?.toUpperCase()}"></property> ?. 如果对象不为null,将调用方法--><bean id="customerId" class="com.itheima.f_xml.d_spel.Customer" > <property name="cname" value="#{customerId.cname?.toUpperCase()}"></property> <property name="pi" value="#{T(java.lang.Math).PI}"></property></bean> 集合注入12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455<!-- 集合的注入都是给<property>添加子标签 数组:<array> List:<list> Set:<set> Map:<map> ,map存放k/v 键值对,使用<entry>描述 Properties:<props> <prop key=""></prop> 【】 普通数据:<value> 引用数据:<ref>--><bean id="collDataId" class="com.itheima.f_xml.e_coll.CollData" > <property name="arrayData"> <array> <value>DS</value> <value>DZD</value> <value>屌丝</value> <value>屌中屌</value> </array> </property> <property name="listData"> <list> <value>于嵩楠</value> <value>曾卫</value> <value>杨煜</value> <value>曾小贤</value> </list> </property> <property name="setData"> <set> <value>停封</value> <value>薄纸</value> <value>关系</value> </set> </property> <property name="mapData"> <map> <entry key="jack" value="杰克"></entry> <entry> <key><value>rose</value></key> <value>肉丝</value> </entry> </map> </property> <property name="propsData"> <props> <prop key="a">aa</prop> <prop key="b">bb</prop> <prop key="c">cc</prop> </props> </property></bean> 装配Bean 基于注解详情请参考 用注解替代xml文件 @Component取代@Component(“id”) 取代 web开发,提供3个@Component注解衍生注解(功能一样)取代 @Repository :dao层 @Service:service层 @Controller:web层 这三个注解和Component功能一样,只是用来区分不同的层,例如springmvc 依赖注入 ,给私有字段设置,也可以给setter方法设置普通值:@Value(“”)引用值: 方式1:按照【类型】注入 @Autowired 方式2:按照【名称】注入1 @Autowired @Qualifier("名称") 方式3:按照【名称】注入2 @Resource("名称") 方式3就是方式2两种注解合在一起,一样 4.生命周期 初始化:@PostConstruct 销毁:@PreDestroy5.作用域 @Scope(“prototype”) 多例 配置xml为扫描所需要的包 1234567891011<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 组件扫描,扫描含有注解的类 --> <context:component-scan base-package="cn.xwmdream.springMain"></context:component-scan></beans> 开始表演详情请参考 总结编写流程 导入jar包:4+1 –> beans,core,context,expression,commons-logging 编写目标类:dao和service spring配置文件 1234567891011121314151617181920212223IoC:<bean id="" class="" >DI:<bean> <property name="" value="" | ref="">实例化方式: 默认构造 静态工厂:<bean id="" class="工厂类" factory-method="静态方法"> 实例工厂:<bean id="工厂id" class="工厂类"> <bean id="" factory-bean="工厂id" factory-method="方法">作用域:<bean id="" class="" scope="singleton | prototype">生命周期:<bean id="" class="" init-method="" destroy-method=""> 后处理bean BeanPostProcessor接口,<bean class="注册"> ,对容器中所有的bean都生效属性注入 构造方法注入:<bean><constructor-arg index="" type="" > setter方法注入:<bean><property> p命名空间:简化<property> <bean p:属性名="普通值" p:属性名-ref="引用值"> 注意声明命名空间 SpEL:<property name="" value="#{表达式}"> #{123} #{'abc'} #{beanId.propName?.methodName()} #{T(类).静态方法|字段} 集合 数组<array> List <list> Set <set> Map <map><entry key="" value=""> Properties <props><prop key="">.... 核心api BeanFactory,延迟实例化bean,第一次调用getBean ApplicationContext 一般常用,功能更强 ClassPathXmlApplicationContext 加载classpath xml文件 FileSystemXmlApplicationContext 加载指定盘符文件 , ServletContext.getRealPath() 后处理bean对单一对象生效1234567@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if("userServiceId".equals(beanName)){ System.out.println("前方法 : " + beanName); } return bean;} ### 注解1. 扫描含有注解的类1<context:component-scan base-package="...."> 2. 常见的注解 @Component 组件,任意beanWEB @Controller web层 @Service service层 @Repository dao层 注入 –> 字段或setter方法 普通值:@Value 引用值:类型:@Autowired 名称1:@Autowired @Qualifier("名称") 名称2:@Resource("名称") 作用域:@Scope(“prototype”) 生命周期: 初始化:@PostConstruct 销毁方法:@PreDestroy 注解和xml混合使用 将所有的bean都配置xml中 将所有的依赖都使用注解 @Autowired 默认不生效。为了生效,需要在xml配置:context:annotation-config 总结: 注解1:<context:component-scan base-package=” “> 注解2:context:annotation-config 一般情况两个注解不一起使用。 “注解1”扫描含有注解(@Component 等)类,注入注解自动生效。 “注解2”只在xml和注解(注入)混合使用时,使注入注解生效。]]></content>
<tags>
<tag>java</tag>
<tag>spring</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android属性动画]]></title>
<url>%2F2019%2F01%2F10%2Fandroid%E5%B1%9E%E6%80%A7%E5%8A%A8%E7%94%BB%2F</url>
<content type="text"><![CDATA[参考:Android 属性动画:这是一篇很详细的 属性动画 总结&攻略如果想深入学习属性动画建议去看原帖,我写的只是我理解的一些内容,没有原帖全 普通动画只是改变视觉效果,没有改变属性属性动画就是不断修改一些属性得到的动画 ValueAnimator类 定义:属性动画机制中 最核心的一个类 实现动画的原理:通过不断控制 值 的变化,再不断 手动 赋给对象的属性,从而实现动画效果 ValueAnimator.ofInt(int values)作用:将初始值 以整型数值的形式 过渡到结束值 即估值器是整型估值器 - IntEvaluator 估值器123456789101112131415161718192021222324252627282930313233343536373839404142textView = (TextView) findViewById(R.id.tv_hello);// 创建动画作用对象:此处以Button为例// 步骤1:设置属性数值的初始值 & 结束值ValueAnimator valueAnimator = ValueAnimator.ofInt(textView.getLayoutParams().width, 500);// ofInt()作用有两个// 1. 创建动画实例// 2. 将传入的多个Int参数进行平滑过渡:此处传入0和1,表示将值从0平滑过渡到1// 如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推// ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置,即默认设置了如何从初始值 过渡到 结束值// 初始值 = 当前按钮的宽度// 结束值 = 500// 即默认设置了如何从初始值150 过渡到 结束值500// 步骤2:设置动画的播放各种属性valueAnimator.setDuration(2000);// 设置动画运行的时长2svalueAnimator.setStartDelay(0);// 设置动画延迟播放时间valueAnimator.setRepeatCount(3);// 设置动画重复播放次数 = 重放次数+1// 动画播放次数 = infinite时,动画无限重复valueAnimator.setRepeatMode(ValueAnimator.REVERSE);// 设置重复播放动画模式// ValueAnimator.RESTART(默认):正序重放// ValueAnimator.REVERSE:倒序回放// 步骤3:将改变的值手动赋值给对象的属性值:通过动画的更新监听器// 设置 值的更新监听器// 即:值每次改变、变化一次,该方法就会被调用一次valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { int currentValue = (Integer) animator.getAnimatedValue(); // 获得每次变化后的属性值 textView.getLayoutParams().width = currentValue; // 每次值变化时,将值手动赋值给对象的属性 // 即将每次变化后的值 赋 给按钮的宽度,这样就实现了按钮宽度属性的动态变化 // 步骤4:刷新视图,即重新绘制,从而实现动画效果 textView.requestLayout(); }});valueAnimator.start();// 启动动画 也就是一个在一定时间内变化的整数,使用addUpdateListener监听这个整数变化从而根据这个值进行不同的操作 使用了默认的估值器,估值器是在不同的时间返回不同的值,默认估值器的值是匀速变化 上面把每次更新的值通过改变textview的形式展现出来 ValueAnimator.ofFloat(float values) 和ofInt一样,只不过参数类型成了float ValueAnimator.oFloat()采用默认的浮点型估值器 (FloatEvaluator) ValueAnimator.ofInt()采用默认的整型估值器(IntEvaluator)在使用上完全没有区别,此处对ValueAnimator.oFloat()的使用就不作过多描述。 ValueAnimator.ofObject先说说动画中很重要的两个概念插值器和估值器 插值器和估值器详解 总的来说插值器就是给当时间进行到百分之多少的时候动画执行百分之多少 估值器是给你初始值和结束值还有动画执行百分比,返回这个百分比下应该取什么值 ValueAnimator.ofObject需要重写一个估值器 先设置一个装值的类Point.java 12345678910111213141516171819202122public class Point { private float x; private float y; @Override public String toString() { return "Point{" + "x=" + x + ", y=" + y + '}'; } public Point(float x, float y) { this.x = x; this.y = y; } public float getX() { return x; } public float getY() { return y; }} 写一个估值器 1234567891011121314151617class PointEvaluator implements TypeEvaluator { // 复写evaluate() // 在evaluate()里写入对象动画过渡的逻辑 @Override public Object evaluate(float fraction, Object startValue, Object endValue) { // 将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象 Point startPoint = (Point) startValue; Point endPoint = (Point) endValue; // 根据fraction来计算当前动画的x和y的值 float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX()); float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY()); // 将计算后的坐标封装到一个新的Point对象中并返回 return new Point(x, y); }} 这个估值器匀速改变x和y的值 调用12345678910ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), new Point(0,0), new Point(100,100));anim.setDuration(5000);anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Point currentPoint = (Point) animation.getAnimatedValue(); Log.d(TAG, "onAnimationUpdate: "+currentPoint); }});anim.start(); 其他方法和上面一样 从上面可以看出,其实ValueAnimator.ofObject()的本质还是操作值,只是是采用将 多个值 封装到一个对象里的方式 同时对多个值一起操作而已 ObjectAnimator类实现动画的原理直接对对象的属性值进行改变操作,从而实现动画效果 如直接改变 View的 alpha 属性 从而实现透明度的动画效果 继承自ValueAnimator类,即底层的动画实现机制是基于ValueAnimator类 ValueAnimator 类是先改变值,然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作; ObjectAnimator 类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作; 12345678910111213141516171819202122232425262728ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values);// ofFloat()作用有两个// 1. 创建动画实例// 2. 参数设置:参数说明如下// Object object:需要操作的对象// String property:需要操作的对象的属性// float ....values:动画初始值 & 结束值(不固定长度)// 若是两个参数a,b,则动画效果则是从属性的a值到b值// 若是三个参数a,b,c,则则动画效果则是从属性的a值到b值再到c值// 以此类推// 至于如何从初始值 过渡到 结束值,同样是由估值器决定,此处ObjectAnimator.ofFloat()是有系统内置的浮点型估值器FloatEvaluator,同ValueAnimator讲解anim.setDuration(500);// 设置动画运行的时长anim.setStartDelay(500);// 设置动画延迟播放时间anim.setRepeatCount(0);// 设置动画重复播放次数 = 重放次数+1// 动画播放次数 = infinite时,动画无限重复anim.setRepeatMode(ValueAnimator.RESTART);// 设置重复播放动画模式// ValueAnimator.RESTART(默认):正序重放// ValueAnimator.REVERSE:倒序回放animator.start();// 启动动画 例子1234567891011121314151617181920212223242526272829303132333435363738//透明度textView = (TextView) findViewById(R.id.tv_hello);ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "alpha", 1f, 0f, 1f);// 表示的是:// 动画作用对象是mButton// 动画作用的对象的属性是透明度alpha// 动画效果是:常规 - 全透明 - 常规animator.setDuration(5000);animator.start();//旋转ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "rotation", 0f, 360f);// 表示的是:// 动画作用对象是mButton// 动画作用的对象的属性是旋转alpha// 动画效果是:0 - 360animator.setDuration(5000);animator.start();//平移float curTranslationX = mButton.getTranslationX();// 获得当前按钮的位置ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX);// 表示的是:// 动画作用对象是mButton// 动画作用的对象的属性是X轴平移(在Y轴上平移同理,采用属性"translationY"// 动画效果是:从当前位置平移到 x=1500 再平移到初始位置animator.setDuration(5000);animator.start();//缩放ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "scaleX", 1f, 3f, 1f);// 表示的是:// 动画作用对象是mButton// 动画作用的对象的属性是X轴缩放// 动画效果是:放大到3倍,再缩小到初始大小animator.setDuration(5000);animator.start(); 属性 作用 数值类型 Alpha 控制View的透明度 float TranslationX 控制X方向的位移 float TranslationY 控制Y方向的位移 float ScaleX 控制X方向的缩放倍数 float ScaleY 控制Y方向的缩放倍数 float Rotation 控制以屏幕方向为轴的旋转度数 float RotationX 控制以X轴为轴的旋转度数 float RotationY 控制以Y轴为轴的旋转度数 float ObjectAnimator自定义属性值 其实每一个属性都是对应的set和get方法,ObjectAnimator每次都会对被动画对象的set和get方法进行赋值,比如修改”color”属性,那么对象就有setColor和getColor方法 自定义一个类,写一个set方法MyView.java 123456public class MyView{ private static final String TAG = "myView"; public void setAa(String color) { Log.d(TAG, "setAa: "+color); }} 写一个估值器,重写evaluate方法 123456class MyEvaluator implements TypeEvaluator { @Override public String evaluate(float fraction, Object startValue, Object endValue) { return "s"+fraction; }} 调用 12345MyView textView = new MyView();ObjectAnimator animator = ObjectAnimator.ofObject(textView, "aa", new MyEvaluator(), "", "");animator.setDuration(5000);animator.start(); 注意ObjectAnimator.ofObject第二个参数除了第一个字母可以忽略大小写,后面的字母一定要和set方法的大小写对应 额外的使用方法组合动画AnimatorSet12345AnimatorSet.play(Animator anim) //:播放当前动画AnimatorSet.after(long delay) //:将现有动画延迟x毫秒后执行AnimatorSet.with(Animator anim) //:将现有动画和传入的动画同时执行AnimatorSet.after(Animator anim) //:将现有动画插入到传入的动画之后执行AnimatorSet.before(Animator anim) //: 将现有动画插入到传入的动画之前执行 例子1234567891011121314// 步骤1:设置需要组合的动画效果ObjectAnimator translation = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX);// 平移动画ObjectAnimator rotate = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);// 旋转动画ObjectAnimator alpha = ObjectAnimator.ofFloat(mButton, "alpha", 1f, 0f, 1f);// 透明度动画// 步骤2:创建组合动画的对象AnimatorSet animSet = new AnimatorSet();// 步骤3:根据需求组合动画animSet.play(translation).with(rotate).before(alpha);animSet.setDuration(5000);// 步骤4:启动动画animSet.start(); 实现效果是平移动画和旋转动画同时进行,他们结束后执行透明度动画 ViewPropertyAnimator 从上面可以看出,属性动画的本质是对值操作 但Java是面向对象的,所以 Google 团队添加面向对象操作的属性动画使用 - ViewPropertyAnimator类 1234567891011121314151617181920212223// 使用解析View.animate().xxx().xxx();// ViewPropertyAnimator的功能建立在animate()上// 调用animate()方法返回值是一个ViewPropertyAnimator对象,之后的调用的所有方法都是通过该实例完成// 调用该实例的各种方法来实现动画效果// ViewPropertyAnimator所有接口方法都使用连缀语法来设计,每个方法的返回值都是它自身的实例// 因此调用完一个方法后可直接连缀调用另一方法,即可通过一行代码就完成所有动画效果// 以下是例子mButton = (Button) findViewById(R.id.Button);// 创建动画作用对象:此处以Button为例mButton.animate().alpha(0f);// 单个动画设置:将按钮变成透明状态mButton.animate().alpha(0f).setDuration(5000).setInterpolator(new BounceInterpolator());// 单个动画效果设置 & 参数设置mButton.animate().alpha(0f).x(500).y(500);// 组合动画:将按钮变成透明状态再移动到(500,500)处// 特别注意:// 动画自动启动,无需调用start()方法.因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成后,动画就会自动启动// 该机制对于组合动画也同样有效,只要不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动// 如果不想使用这一默认机制,也可以显式地调用start()方法来启动动画 监听动画1234567891011121314151617181920212223Animation.addListener(new AnimatorListener() { @Override public void onAnimationStart(Animation animation) { //动画开始时执行 } @Override public void onAnimationRepeat(Animation animation) { //动画重复时执行 } @Override public void onAnimationCancel()(Animation animation) { //动画取消时执行 } @Override public void onAnimationEnd(Animation animation) { //动画结束时执行 }});// 特别注意:每次监听必须4个方法都重写。 动画适配器 背景:有些时候我们并不需要监听动画的所有时刻 问题:但addListener(new AnimatorListener())监听器是必须重写4个时刻方法,这使得接口方法重写太累赘 解决方案:采用动画适配器(AnimatorListenerAdapter),解决 实现接口繁琐 的问题123456789anim.addListener(new AnimatorListenerAdapter() {// 向addListener()方法中传入适配器对象AnimatorListenerAdapter()// 由于AnimatorListenerAdapter中已经实现好每个接口// 所以这里不实现全部方法也不会报错 @Override public void onAnimationStart(Animator animation) { // 如想只想监听动画开始时刻,就只需要单独重写该方法就可以 }}); 作者:Carson_Ho链接:https://www.jianshu.com/p/2412d00a0ce4來源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RecyclerView分割线附上顶吸]]></title>
<url>%2F2019%2F01%2F02%2FRecyclerView%E5%88%86%E5%89%B2%E7%BA%BF%E9%99%84%E4%B8%8A%E9%A1%B6%E5%90%B8%2F</url>
<content type="text"><![CDATA[参考:RecyclerView 自定义ItemDecoration从入门到实现吸顶效果Solartisan/WaveSideBar 简单分割线实现分割线要继承RecyclerView.ItemDecoration,重写三个方法1234567891011121314151617public class MyItemDecoration extends RecyclerView.ItemDecoration { public MyItemDecoration(Context context) { } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state);int count=parent.getChildCount(); } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); }} onDraw方法和自定义View中的onDraw方法一样,是用来在屏幕上画东西的 onDrawOver 英文Over的意思在…的上面 ,可以理解成是图层关系,item的内容和分割线是第一层(要在第一层画东西要调用onDraw),而onDrawOver是第二层,位于onDraw的上面 getItemOffsets 看名字可以知道是设置item的偏移值,其实效果和padding一样 注意:三个方法中都有一个RecyclerView类型的parent,这个不是整个RecyclerView,而是当前屏幕显示的那几个RecyclerView,所以当滚动屏幕的时候会重新调用此类来绘制分割线。同理调用parent.getChildCount()获取到的时当前屏幕上显示的item个数 一条简单的红色分割线123456789101112131415161718192021222324252627282930313233343536public class MyItemDecoration extends RecyclerView.ItemDecoration { private int width; private int height; private int item_height; private int item_padding; private Paint paint; private Context context; public MyItemDecoration(Context context) { this.context = context; width=context.getResources().getDisplayMetrics().widthPixels; height=context.getResources().getDisplayMetrics().heightPixels; paint=new Paint(Paint.ANTI_ALIAS_FLAG|Paint.DITHER_FLAG); paint.setColor(Color.RED); item_height=DensityUtil.dip2px(context, 1); item_padding=DensityUtil.dip2px(context, 10); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state);int count=parent.getChildCount(); for (int i = 0; i < count; i++) { View view=parent.getChildAt(i); int top=view.getBottom(); int bottom=top+item_height; c.drawRect(0,top,width,bottom,paint); } } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); }} 以上是实现item底部的分割线 主要是调用onDraw方法画分割线,c.drawRect是画矩形,参数分别是左上右下和画笔对象 设置分割线左右边距就改成c.drawRect(0+padding,top,width-padding,bottom,paint); 因为onDraw和item内容处于一个图层,所以把分割线画的比较粗的话会和item内容重合,此时第三个方法就派上用场了,设置一个偏移值 12345@Overridepublic void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); outRect.bottom=item_height;} 该偏移值的意思是让列表中每一个item的底部向下空出一个item_height的宽度用来绘制分割线 实现顶吸顶部绘制一个固定的区域1234567891011121314151617181920212223242526272829303132333435public class MyItemDecoration extends RecyclerView.ItemDecoration { private int width; private int item_height; private Paint paint; private Paint paint2; public MyItemDecoration(Context context) { width=context.getResources().getDisplayMetrics().widthPixels; paint=new Paint(Paint.ANTI_ALIAS_FLAG|Paint.DITHER_FLAG); paint.setColor(Color.GRAY); item_height=DensityUtil.dip2px(context, 20); paint2=new Paint(Paint.ANTI_ALIAS_FLAG|Paint.DITHER_FLAG); paint2.setColor(Color.parseColor("#52ff0000")); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state);int count=parent.getChildCount(); for (int i = 0; i < count; i++) { View view=parent.getChildAt(i); int bottom=view.getTop(); int top=bottom-item_height; c.drawRect(0,top,width,bottom,paint); } } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); c.drawRect(0,0,width,item_height,paint2); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); outRect.top=item_height; }} 先把分隔条写到每一个item的上面,修改了onDraw和getItemOffsets 实现每一个间隔把顶部固定区域顶上去 123456789101112131415@Overridepublic void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); //获取屏幕上的第一个item View child0 = parent.getChildAt(0); //如果第一个item的Bottom<=分割线的高度 if (child0.getBottom() <= item_height) { //随着RecyclerView滑动 分割线的top=固定为0不动,bottom则赋值为child0的bottom值. c.drawRect(0, 0, width,child0.getBottom() , paint2); } else { //固定不动 c.drawRect(0, 0, width, item_height, paint2); }} 实现方法是获取屏幕第一个recyclerview的item,看他底部的距离,如果小于一个分割线的高度,那就把分割线高度弄成第一个item到底部的距离 一个简单完整的吸顶,用Canvas绘制参考:RecyclerView 自定义ItemDecoration从入门到实现吸顶效果效果:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110public class MyItemDecoration extends RecyclerView.ItemDecoration { private Paint.FontMetrics fontMetrics; private int wight; private int itemDecorationHeight; private Paint paint; private ObtainTextCallback callback; private float itemDecorationPadding; private TextPaint textPaint; private Rect text_rect=new Rect(); public MyItemDecoration(Context context, ObtainTextCallback callback) { //获取item宽度 wight=context.getResources().getDisplayMetrics().widthPixels; //绘制间隔背景为红色 paint=new Paint(Paint.ANTI_ALIAS_FLAG|Paint.DITHER_FLAG); paint.setColor(Color.RED); //间隔高度 itemDecorationHeight=DensityUtil.dip2px(context, 30); //间隔左右padding itemDecorationPadding=DensityUtil.dip2px(context, 10); this.callback = callback; //绘制间隔上的文字 textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); textPaint.setColor(Color.WHITE); textPaint.setTextAlign(Paint.Align.LEFT); textPaint.setTextSize(DensityUtil.dip2px(context, 25)); //绘制文字相关的参数 fontMetrics = new Paint.FontMetrics(); textPaint.getFontMetrics(fontMetrics); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); int count=parent.getChildCount(); for (int i = 0; i < count; i++) { View view=parent.getChildAt(i); int top=view.getTop()-itemDecorationHeight; int bottom=top+itemDecorationHeight; //获取该item在总列表里是第几个 int position = parent.getChildAdapterPosition(view); //通过callback获取到这个item的第一个字 String content = callback.getText(position); textPaint.getTextBounds(content,0, content.length(),text_rect); //如果需要画分割线 if(isFirstInGroup(position)) { c.drawRect(0,top,wight,bottom,paint); c.drawText(content, itemDecorationPadding, bottom-fontMetrics.descent, textPaint); } } } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); //获取到屏幕上第一个item View child0=parent.getChildAt(0); //获取到屏幕上第一个item是总列表中第几个 int position = parent.getChildAdapterPosition(child0); //获取到这个item的第一个字 String content = callback.getText(position); //如果已经重合了并且第一个item前需要画分割线,那么就按照第一个item的底部高度来画分割线 if(child0.getBottom()<=itemDecorationHeight&&isFirstInGroup(position+1)){ c.drawRect(0, 0, wight, child0.getBottom(), paint); c.drawText(content, itemDecorationPadding, child0.getBottom()-fontMetrics.descent, textPaint); } //否则就按照一个完整的分割线来画,这里通过textPaint来写不同的字 else { c.drawRect(0, 0, wight, itemDecorationHeight, paint); c.drawText(content, itemDecorationPadding, itemDecorationHeight-fontMetrics.descent, textPaint); } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); int position= parent.getChildAdapterPosition(view); //如果不是在同一组就腾出分割线需要的高度 if(isFirstInGroup(position)){ outRect.top=itemDecorationHeight; } } //回调接口,通过该回调获取item的内容的第一个文字 public interface ObtainTextCallback { String getText(int position); } //判断当前item和下一个item的第一个文字是否相同,如果相同说明是同一组,不需要画分割线 private boolean isFirstInGroup(int pos) { //如果是adapter的第一个position直接return,因为第一个item必须有分割线 if (pos == 0) { return true; } else { //否者判断前一个item的字符串 与 当前item字符串 是否相同 String prevGroupId = callback.getText(pos - 1); String groupId = callback.getText(pos); //如果前一个item和这个item的第一个字相同那就不用画分割线 if (prevGroupId.equals(groupId)) { return false; } else { return true; } } }} Activity1234567891011121314recyclerView = (RecyclerView)findViewById(R.id.recycler_view);LinearLayoutManager layoutManager = new LinearLayoutManager(this);recyclerView.setLayoutManager(layoutManager);final List<String> list = new ArrayList<String>();initList(list);Adapter adapter = new Adapter(list);recyclerView.setAdapter(adapter);//通过每一个item内容的第一个字进行分类recyclerView.addItemDecoration(new MyItemDecoration(this, new MyItemDecoration.ObtainTextCallback() { @Override public String getText(int position) { return list.get(position).substring(0,1); }})); 附上一个更完整的吸顶,用布局绘制参考:Solartisan/WaveSideBarhttps://github.com/Solartisan/TurboRecyclerViewHelper) 首先是beanFruit.java1234567891011121314151617181920212223242526public class Fruit { private String name; private int type; public Fruit(String name, int type) { this.name = name; this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getType() { return type; } public void setType(int type) { this.type = type; }} 这个bean包含了item和分割线的内容,用type区分 适配器FruitAdapter.java1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.BaseHolder> { private List<Fruit> mFruitList; private static final String TAG = "FruitAdapter"; //重写区分不同的元素,不同的type @Override public int getItemViewType(int position) { return mFruitList.get(position).getType(); } //不同的type创建不同的布局 @Override public BaseHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int type) { View view; final BaseHolder holder; if (type== 1) { view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.fruit_item, viewGroup, false); holder = new ItemHolder(view); } else { view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.head_item, viewGroup, false); holder = new HeadHolder(view); } return holder; } //填充不同的数据 @Override public void onBindViewHolder(@NonNull BaseHolder viewHolder, int i) { Fruit fruit = mFruitList.get(i); viewHolder.setData(fruit); } @Override public int getItemCount() { return mFruitList.size(); } //不同布局的Viewholder,先创建一个baseHolder,然后item和顶吸的分别继承 public abstract class BaseHolder extends RecyclerView.ViewHolder { public abstract void setData(Fruit fruit); public BaseHolder(@NonNull View itemView) { super(itemView); } } public class ItemHolder extends BaseHolder { private TextView textView; @Override public void setData(Fruit fruit) { textView.setText(fruit.getName()); } public ItemHolder(@NonNull View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.fruit_name); } } public class HeadHolder extends BaseHolder { private TextView textView; @Override public void setData(Fruit fruit) { textView.setText(fruit.getName().substring(0,1)); } public HeadHolder(@NonNull View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.head_name); } } public FruitAdapter(List<Fruit> fruitList) { mFruitList = fruitList; }} 这个adapter中提供了两个viewholder,一个是普通item,另一个是分割线 重写getItemViewType方法定义区分两种holder的方法,上面的区分方法是通过bean的type区分 onCreateViewHolder通过上面的方法判定不同的viewtype构造不同的holder onBindViewHolder填充数据 分割线PinnedHeaderDecoration.java 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168public class PinnedHeaderDecoration extends RecyclerView.ItemDecoration { private int mHeaderPosition; private int mPinnedHeaderTop; private static final String TAG = "PinnedHeaderDecoration"; private boolean mIsAdapterDataChanged; private Rect mClipBounds; //是在顶部固定的那个框框 private View mPinnedHeaderView; //父布局的整个adapter private RecyclerView.Adapter mAdapter; //一个安卓特有的稀疏数组,取代hashmap,key-value保存值,只需要指定value的类型即可 private final SparseArray<PinnedHeaderCreator> mTypePinnedHeaderFactories = new SparseArray<>(); //recyclerview适配器的监听 private final RecyclerView.AdapterDataObserver mAdapterDataObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { mIsAdapterDataChanged = true; } }; //初始化 public PinnedHeaderDecoration() { this.mHeaderPosition = -1; } @Override public void onDraw(Canvas c, final RecyclerView parent, RecyclerView.State state) { createPinnedHeader(parent); //mPinnedHeaderView是顶部固定的框框 if (mPinnedHeaderView != null) { int headerEndAt = mPinnedHeaderView.getTop() + mPinnedHeaderView.getHeight(); //获取列表可见部分的第一个view View v = parent.findChildViewUnder(c.getWidth() / 2, headerEndAt + 1); //判断这个v是不是分隔,如果是分割那么顶部的框框的顶要向上移动,否则就在0处,即最顶端 if (isPinnedView(parent, v)) { mPinnedHeaderTop = v.getTop() - mPinnedHeaderView.getHeight(); } else { mPinnedHeaderTop = 0; } //开始画分隔线,分隔线的头部就是顶部框框的底部 mClipBounds = c.getClipBounds(); mClipBounds.top = mPinnedHeaderTop + mPinnedHeaderView.getHeight(); c.clipRect(mClipBounds); } } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { if (mPinnedHeaderView != null) { //先保存c的状态 c.save(); mClipBounds.top = 0; c.clipRect(mClipBounds, Region.Op.UNION); //平移到框框顶部 c.translate(0, mPinnedHeaderTop); //绘制出顶部的框框 mPinnedHeaderView.draw(c); //把c还原到save的状态 c.restore(); } } private void createPinnedHeader(RecyclerView parent) { updatePinnedHeader(parent); RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager == null || layoutManager.getChildCount() <= 0) { return; } //获取第一个元素在整个recyclerview中是第几个 int firstVisiblePosition = ((RecyclerView.LayoutParams) layoutManager.getChildAt(0).getLayoutParams()).getViewAdapterPosition(); int headerPosition = findPinnedHeaderPosition(parent, firstVisiblePosition); if (headerPosition >= 0 && mHeaderPosition != headerPosition) { mHeaderPosition = headerPosition; int viewType = mAdapter.getItemViewType(headerPosition); RecyclerView.ViewHolder pinnedViewHolder = mAdapter.createViewHolder(parent, viewType); mAdapter.bindViewHolder(pinnedViewHolder, headerPosition); mPinnedHeaderView = pinnedViewHolder.itemView; // read layout parameters ViewGroup.LayoutParams layoutParams = mPinnedHeaderView.getLayoutParams(); if (layoutParams == null) { layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); mPinnedHeaderView.setLayoutParams(layoutParams); } int heightMode = View.MeasureSpec.getMode(layoutParams.height); int heightSize = View.MeasureSpec.getSize(layoutParams.height); if (heightMode == View.MeasureSpec.UNSPECIFIED) { heightMode = View.MeasureSpec.EXACTLY; } int maxHeight = parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom(); if (heightSize > maxHeight) { heightSize = maxHeight; } // measure & layout int ws = View.MeasureSpec.makeMeasureSpec(parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.EXACTLY); int hs = View.MeasureSpec.makeMeasureSpec(heightSize, heightMode); mPinnedHeaderView.measure(ws, hs); mPinnedHeaderView.layout(0, 0, mPinnedHeaderView.getMeasuredWidth(), mPinnedHeaderView.getMeasuredHeight()); } } private int findPinnedHeaderPosition(RecyclerView parent, int fromPosition) { if (fromPosition > mAdapter.getItemCount() || fromPosition < 0) { return -1; } for (int position = fromPosition; position >= 0; position--) { final int viewType = mAdapter.getItemViewType(position); if (isPinnedViewType(parent, position, viewType)) { return position; } } return -1; } private boolean isPinnedViewType(RecyclerView parent, int adapterPosition, int viewType) { PinnedHeaderCreator pinnedHeaderCreator = mTypePinnedHeaderFactories.get(viewType); return pinnedHeaderCreator != null && pinnedHeaderCreator.create(parent, adapterPosition); } private boolean isPinnedView(RecyclerView parent, View v) { int position = parent.getChildAdapterPosition(v); //如果获取的是非法值 if (position == RecyclerView.NO_POSITION) { return false; } return isPinnedViewType(parent, position, mAdapter.getItemViewType(position)); } private void updatePinnedHeader(RecyclerView parent) { RecyclerView.Adapter adapter = parent.getAdapter(); //获取的是recyclerview所有的adapter,如果adapter发生改变或者监听到内容发生改变执行 //重新绘制 if (mAdapter != adapter || mIsAdapterDataChanged) { //重置 resetPinnedHeader(); if (mAdapter != null) { mAdapter.unregisterAdapterDataObserver(mAdapterDataObserver); } mAdapter = adapter; if (mAdapter != null) { //设置监听 mAdapter.registerAdapterDataObserver(mAdapterDataObserver); } } } //重置header private void resetPinnedHeader() { mHeaderPosition = -1; mPinnedHeaderView = null; } public void registerTypePinnedHeader(int itemType, PinnedHeaderCreator pinnedHeaderCreator) { mTypePinnedHeaderFactories.put(itemType, pinnedHeaderCreator); } public interface PinnedHeaderCreator { boolean create(RecyclerView parent, int adapterPosition); }} 使用MainActivity.java 12345678final PinnedHeaderDecoration decoration = new PinnedHeaderDecoration();decoration.registerTypePinnedHeader(1, new PinnedHeaderDecoration.PinnedHeaderCreator() { @Override public boolean create(RecyclerView parent, int adapterPosition) { return true; }});mRecyclerView.addItemDecoration(decoration); registerTypePinnedHeader传的参数1是当adapter那个getviewtype中返回的是1的时候调用create方法,如果返回的是true,那就让他顶吸]]></content>
<tags>
<tag>android</tag>
<tag>recyclerview</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android逐帧动画]]></title>
<url>%2F2018%2F12%2F28%2Fandroid%E9%80%90%E5%B8%A7%E5%8A%A8%E7%94%BB%2F</url>
<content type="text"><![CDATA[参考:Android 逐帧动画:关于 逐帧动画 的使用都在这里了! 逐帧动画就是把动画的每一帧都表示出来,像一个gif一样 首先准备图片 在drawable下创建一个xml:frame_anim.xml 123456789<?xml version="1.0" encoding="utf-8"?><animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" > <item android:drawable="@drawable/signal1" android:duration="300"/> <item android:drawable="@drawable/signal2" android:duration="300"/> <item android:drawable="@drawable/signal3" android:duration="300"/> <item android:drawable="@drawable/signal4" android:duration="300"/></animation-list> 每一个item表示一个图片,duration表示这个图片持续的时间,最外层的oneshot表示循环播放,false表示循环播放,true只播放一次 在java中加载此资源123456789101112131415161718192021222324private Button start;private Button stop;private ImageView image;private AnimationDrawable animationDrawable;...start = (Button)findViewById(R.id.start);stop = (Button) findViewById(R.id.stop);image = (ImageView)findViewById(R.id.image);image.setImageResource(R.drawable.frame_anim);animationDrawable = (AnimationDrawable) image.getDrawable();start.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { animationDrawable.start(); }});stop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { animationDrawable.stop(); }}); 逐帧动画就像是一个gif,把每一帧的图片都指定了 避免图片过大造成内存泄漏]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android全局获取context]]></title>
<url>%2F2018%2F12%2F27%2Fandroid%E5%85%A8%E5%B1%80%E8%8E%B7%E5%8F%96context%2F</url>
<content type="text"><![CDATA[参考郭霖第二行代码 一个APP运行都是一个application,在这个application中分不同的activity等看清单文件也能看出12345678910<?xml version="1.0" encoding="utf-8"?><manifest> <application android:icon="@mipmap/ic_launcher" android:label="@string/app_name"> <activity android:name=".MainActivity"> ... </activity> </application></manifest> 整个清单文件先包括application,然后是里面的activity和server 程序启动时候会初始化一个application,当整个程序停止运行了此application才会释放 可以自己写一个application然后放一些全局的信息,如context 全局获取Context 首先重写一个application1234567891011public class MyApplication extends Application { private static Context context; @Override public void onCreate() { super.onCreate(); context = getApplicationContext(); } public static Context getContext(){ return context; }} 提供一个静态的context,重写oncreate方法,调用getApplicationContext获取全局context,这样调用MyApplication.getContext即可获取到context 在清单文件中把默认的application换成MyApplication123456789101112131415<application android:name="cn.xwmdream.MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> 在application中加上了一个Android:name的属性,指定了MyApplication 这样在任何一个地方调用MyApplication.getContext()方法即可获取到context 通过application通信可以在自定义的application提供以下方法来传递数据1234567private static String data;public static void setData(String d){ data = d;}public static String getData(){ return data;} 在不同的地方中通过set和get传递数据]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android补间动画]]></title>
<url>%2F2018%2F12%2F24%2Fandroid%E8%A1%A5%E9%97%B4%E5%8A%A8%E7%94%BB%2F</url>
<content type="text"><![CDATA[参考(请优先参考原文,比较详细,我写的只是我自己学到的一点东西,不全):Android动画:献上一份详细 & 全面的动画知识学习攻略Android 动画:手把手教你使用 补间动画 简介 补间动画的作用对象是View,如textView、button 不可用于组件的属性,如:颜色、背景、长度等等 只是修改了组件的影像位置,动画过程中点击是没有效果的,只能点击该view本应该在的位置才能响应 补间动画的分类 名称 原理 对应animation的子类 平移动画(Translate) 移动视图的位置 TranslateAnimation 缩放动画(Scale) 放大、缩小视图的大小 ScaleAnimation 旋转动画(Rotate) 旋转视图角度 RotateAnimation 透明度动画(Alpha) 改变视图透明度 AlphaAnimation 动画的创建动画创建分为xml中创建和Java中创建在xml中创建需要在res目录下创建一个anim文件夹 平移动画在xml中设置平移动画translate.xml12345678910111213141516171819202122<?xml version="1.0" encoding="utf-8"?>// 采用<translate /> 标签表示平移动画<translate xmlns:android="http://schemas.android.com/apk/res/android" // 以下参数是4种动画效果的公共属性,即都有的属性 android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果 android:startOffset ="1000" // 动画延迟开始时间(ms) android:fillBefore = "true" // 动画播放完后,视图是否会停留在动画开始的状态,默认为true android:fillAfter = "false" // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false android:fillEnabled= "true" // 是否应用fillBefore值,对fillAfter值无影响,默认为true android:repeatMode= "restart" // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart| android:repeatCount = "0" // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复 android:interpolator = "@android:anim/accelerate_interpolator" // 插值器,即影响动画的播放速度,下面会详细讲 // 以下参数是平移动画特有的属性 android:fromXDelta="0" // 视图在水平方向x 移动的起始值 android:toXDelta="500" // 视图在水平方向x 移动的结束值 android:fromYDelta="0" // 视图在竖直方向y 移动的起始值 android:toYDelta="500" // 视图在竖直方向y 移动的结束值 /> 在Java中运行动画12345button = (Button)findViewById(R.id.button);// 步骤1:创建 需要设置动画的 视图ViewAnimation translateAnimation = AnimationUtils.loadAnimation(this, R.anim.translate);// 步骤2:创建 动画对象 并传入设置的动画效果xml文件button.startAnimation(translateAnimation); 在Java中创建平移动画123456789101112button = (Button)findViewById(R.id.button);// 步骤1:创建 需要设置动画的 视图ViewAnimation translateAnimation = new TranslateAnimation(0,500,0,500);// 步骤2:创建平移动画的对象:平移动画对应的Animation子类为TranslateAnimation// 参数分别是:// 1. fromXDelta :视图在水平方向x 移动的起始值// 2. toXDelta :视图在水平方向x 移动的结束值// 3. fromYDelta :视图在竖直方向y 移动的起始值// 4. toYDelta:视图在竖直方向y 移动的结束值translateAnimation.setDuration(3000);// 固定属性的设置都是在其属性前加"set",如setDuration()button.startAnimation(translateAnimation); 缩放动画(Scale)xml中创建缩放动画12345678910111213141516171819202122232425262728293031323334353637<?xml version="1.0" encoding="utf-8"?>// 采用<scale/> 标签表示是缩放动画<scale xmlns:android="http://schemas.android.com/apk/res/android" // 以下参数是4种动画效果的公共属性,即都有的属性 android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果 android:startOffset ="1000" // 动画延迟开始时间(ms) android:fillBefore = "true" // 动画播放完后,视图是否会停留在动画开始的状态,默认为true android:fillAfter = "false" // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false android:fillEnabled= "true" // 是否应用fillBefore值,对fillAfter值无影响,默认为true android:repeatMode= "restart" // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart| android:repeatCount = "0" // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复 android:interpolator="@android:anim/accelerate_interpolator" // 插值器,即影响动画的播放速度,下面会详细讲 // 以下参数是缩放动画特有的属性 android:fromXScale="0.0" // 动画在水平方向X的起始缩放倍数 // 0.0表示收缩到没有;1.0表示正常无伸缩 // 值小于1.0表示收缩;值大于1.0表示放大 android:toXScale="2" //动画在水平方向X的结束缩放倍数 android:fromYScale="0.0" //动画开始前在竖直方向Y的起始缩放倍数 android:toYScale="2" //动画在竖直方向Y的结束缩放倍数 android:pivotX="50%" // 缩放轴点的x坐标 android:pivotY="50%" // 缩放轴点的y坐标 // 轴点 = 视图缩放的中心点 // pivotX pivotY,可取值为数字,百分比,或者百分比p // 设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。 // 设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。 // 设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT // 两个50%表示动画从自身中间开始,具体如下图 /> java中运行动画123button = (Button)findViewById(R.id.button);Animation scaleAnimation = AnimationUtils.loadAnimation(this, R.anim.scale);button.startAnimation(scaleAnimation); java创建缩放动画12345678910111213141516171819button = (Button)findViewById(R.id.button);Animation scaleAnimation = new ScaleAnimation(0,2,0,2,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);// 步骤2:创建缩放动画的对象 & 设置动画效果:缩放动画对应的Animation子类为RotateAnimation// 参数说明:// 1. fromX :动画在水平方向X的结束缩放倍数// 2. toX :动画在水平方向X的结束缩放倍数// 3. fromY :动画开始前在竖直方向Y的起始缩放倍数// 4. toY:动画在竖直方向Y的结束缩放倍数// 5. pivotXType:缩放轴点的x坐标的模式// 6. pivotXValue:缩放轴点x坐标的相对值// 7. pivotYType:缩放轴点的y坐标的模式// 8. pivotYValue:缩放轴点y坐标的相对值// pivotXType = Animation.ABSOLUTE:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理)// pivotXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)// pivotXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)scaleAnimation.setDuration(3000);// 固定属性的设置都是在其属性前加"set",如setDuration()button.startAnimation(scaleAnimation); 旋转动画xml中创建动画rotate.xml123456789101112131415161718192021222324<?xml version="1.0" encoding="utf-8"?>// 采用<rotate/> 标签表示是旋转动画<rotate xmlns:android="http://schemas.android.com/apk/res/android" // 以下参数是4种动画效果的公共属性,即都有的属性 android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果 android:startOffset ="1000" // 动画延迟开始时间(ms) android:fillBefore = "true" // 动画播放完后,视图是否会停留在动画开始的状态,默认为true android:fillAfter = "false" // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false android:fillEnabled= "true" // 是否应用fillBefore值,对fillAfter值无影响,默认为true android:repeatMode= "restart" // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart| android:repeatCount = "0" // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复 android:interpolator="@android:anim/accelerate_interpolator" // 插值器,即影响动画的播放速度,下面会详细讲 // 以下参数是旋转动画特有的属性 android:fromDegrees="0" // 动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针) android:toDegrees="270" // 动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针) android:pivotX="50%" // 旋转轴点的x坐标 android:pivotY="0" // 旋转轴点的y坐标 // 轴点 = 视图缩放的中心点// pivotX pivotY,可取值为数字,百分比,或者百分比p // 设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。 // 设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。 // 设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT // 两个50%表示动画从自身中间开始,具体如下图 /> java中创建旋转动画1234567891011121314151617181920button = (Button)findViewById(R.id.button);Animation rotateAnimation = new RotateAnimation(0,270,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);// 步骤2:创建旋转动画的对象 & 设置动画效果:旋转动画对应的Animation子类为RotateAnimation// 参数说明:// 1. fromDegrees :动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)// 2. toDegrees :动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)// 3. pivotXType:旋转轴点的x坐标的模式// 4. pivotXValue:旋转轴点x坐标的相对值// 5. pivotYType:旋转轴点的y坐标的模式// 6. pivotYValue:旋转轴点y坐标的相对值// pivotXType = Animation.ABSOLUTE:旋转轴点的x坐标 = View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理)// pivotXType = Animation.RELATIVE_TO_SELF:旋转轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)// pivotXType = Animation.RELATIVE_TO_PARENT:旋转轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)rotateAnimation.setDuration(3000);// 固定属性的设置都是在其属性前加"set",如setDuration()button.startAnimation(rotateAnimation); 透明动画xml创建透明动画alpha.xml12345678910111213141516171819<?xml version="1.0" encoding="utf-8"?>// 采用<alpha/> 标签表示是透明度动画<alpha xmlns:android="http://schemas.android.com/apk/res/android" // 以下参数是4种动画效果的公共属性,即都有的属性 android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果 android:startOffset ="1000" // 动画延迟开始时间(ms) android:fillBefore = "true" // 动画播放完后,视图是否会停留在动画开始的状态,默认为true android:fillAfter = "false" // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false android:fillEnabled= "true" // 是否应用fillBefore值,对fillAfter值无影响,默认为true android:repeatMode= "restart" // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart| android:repeatCount = "0" // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复 android:interpolator="@android:anim/accelerate_interpolator" // 插值器,即影响动画的播放速度,下面会详细讲 // 以下参数是透明度动画特有的属性 android:fromAlpha="1.0" // 动画开始时视图的透明度(取值范围: -1 ~ 1) android:toAlpha="0.0"// 动画结束时视图的透明度(取值范围: -1 ~ 1) /> java中运行此动画123button = (Button)findViewById(R.id.button);Animation alphaAnimation = AnimationUtils.loadAnimation(this, R.anim.alpha);button.startAnimation(alphaAnimation); java中创建透明动画12345678910button = (Button)findViewById(R.id.button);Animation alphaAnimation = new AlphaAnimation(1,0);// 步骤2:创建透明度动画的对象 & 设置动画效果:透明度动画对应的Animation子类为AlphaAnimation// 参数说明:// 1. fromAlpha:动画开始时视图的透明度(取值范围: -1 ~ 1)// 2. toAlpha:动画结束时视图的透明度(取值范围: -1 ~ 1)alphaAnimation.setDuration(3000);// 固定属性的设置都是在其属性前加"set",如setDuration()button.startAnimation(alphaAnimation); 组合动画xml中创建组合动画 most_animation.xml123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566<?xml version="1.0" encoding="utf-8"?>// 采用< Set/>标签<set xmlns:android="http://schemas.android.com/apk/res/android">// 组合动画同样具备公共属性 android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果 android:startOffset ="1000" // 动画延迟开始时间(ms) android:fillBefore = "true" // 动画播放完后,视图是否会停留在动画开始的状态,默认为true android:fillAfter = "false" // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false android:fillEnabled= "true" // 是否应用fillBefore值,对fillAfter值无影响,默认为true android:repeatMode= "restart" // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart| android:repeatCount = "0" // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复 android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲// 组合动画独特的属性 android:shareinterpolator = "true" // 表示组合动画中的动画是否和集合共享同一个差值器 // 如果集合不指定插值器,那么子动画需要单独设置// 组合动画播放时是全部动画同时开始// 如果想不同动画不同时间开始就要使用android:startOffset属性来延迟单个动画播放时间// 设置旋转动画,语法同单个动画 <rotate android:duration="1000" android:fromDegrees="0" android:toDegrees="360" android:pivotX="50%" android:pivotY="50%" android:repeatMode="restart" android:repeatCount="infinite" />// 设置平移动画,语法同单个动画 <translate android:duration="10000" android:startOffset = "1000"// 延迟该动画播放时间 android:fromXDelta="-50%p" android:fromYDelta="0" android:toXDelta="50%p" android:toYDelta="0" />// 设置透明度动画,语法同单个动画 <alpha android:startOffset="7000" android:duration="3000" android:fromAlpha="1.0" android:toAlpha="0.0" />// 设置缩放动画,语法同单个动画 <scale android:startOffset="4000" android:duration="1000" android:fromXScale="1.0" android:fromYScale="1.0" android:pivotX="50%" android:pivotY="50%" android:toXScale="0.5" android:toYScale="0.5" />// 特别注意:// 1. 在组合动画里scale缩放动画设置的repeatCount(重复播放)和fillBefore(播放完后,视图是否会停留在动画开始的状态)是无效的。// 2. 所以如果需要重复播放或者回到原位的话需要在set标签里设置// 3. 但是由于此处rotate旋转动画里已设置repeatCount为infinite,所以动画不会结束,也就看不到重播和回复原位</set> 在Java中运行动画1234button = (Button)findViewById(R.id.button);Animation translateAnimation = AnimationUtils.loadAnimation(this, R.anim.most_animation);// 步骤2:创建 动画对象 并传入设置的动画效果xml文件button.startAnimation(translateAnimation); java中创建组合动画123456789101112131415161718192021222324252627282930313233343536373839404142434445464748button = (Button)findViewById(R.id.button);// 创建 需要设置动画的 视图View// 组合动画设置AnimationSet setAnimation = new AnimationSet(true);// 步骤1:创建组合动画对象(设置为true)// 步骤2:设置组合动画的属性// 特别说明以下情况// 因为在下面的旋转动画设置了无限循环(RepeatCount = INFINITE)// 所以动画不会结束,而是无限循环// 所以组合动画的下面两行设置是无效的setAnimation.setRepeatMode(Animation.RESTART);setAnimation.setRepeatCount(1);// 设置了循环一次,但无效// 步骤3:逐个创建子动画(方式同单个动画创建方式,此处不作过多描述)// 子动画1:旋转动画Animation rotate = new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);rotate.setDuration(1000);rotate.setRepeatMode(Animation.RESTART);rotate.setRepeatCount(Animation.INFINITE);// 子动画2:平移动画Animation translate = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_PARENT,-0.5f, TranslateAnimation.RELATIVE_TO_PARENT,0.5f, TranslateAnimation.RELATIVE_TO_SELF,0 ,TranslateAnimation.RELATIVE_TO_SELF,0);translate.setDuration(10000);// 子动画3:透明度动画Animation alpha = new AlphaAnimation(1,0);alpha.setDuration(3000);alpha.setStartOffset(7000);// 子动画4:缩放动画Animation scale1 = new ScaleAnimation(1,0.5f,1,0.5f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);scale1.setDuration(1000);scale1.setStartOffset(4000);// 步骤4:将创建的子动画添加到组合动画里setAnimation.addAnimation(alpha);setAnimation.addAnimation(rotate);setAnimation.addAnimation(translate);setAnimation.addAnimation(scale1);button.startAnimation(setAnimation); 监听动画 监听动画分三个阶段,开始动画,结束动画,重复动画 在需要设置监听的动画设置一个listener12345678910111213141516Animation rotateAnimation = new RotateAnimation(0, 270, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);rotateAnimation.setDuration(3000);rotateAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { Log.d(TAG, "onAnimationStart: "); } @Override public void onAnimationEnd(Animation animation) { Log.d(TAG, "onAnimationEnd: "); } @Override public void onAnimationRepeat(Animation animation) { Log.d(TAG, "onAnimationRepeat: "); }}); 插值器和估值器 之前的动画无论是xml还是Java创建都会有一个interpolator属性,他是动画的插值器 插值器就是时间运行到总体的多少的时候动画执行到多少,用来控制整个动画的部分快慢 比如时间到了一半,让动画也执行整个动画的一半就是匀速具体参考这篇文章 activity的切换效果启动动画123456789Intent intent = new Intent(MainActivity.this,Main2Activity.class);startActivity(intent);overridePendingTransition(R.anim.alpha,R.anim.rotate);// 采用overridePendingTransition(int enterAnim, int exitAnim)进行设置// enterAnim:从Activity a跳转到Activity b,进入b时的动画效果资源ID// exitAnim:从Activity a跳转到Activity b,离开a时的动画效果资源Id// 特别注意// overridePendingTransition()必须要在startActivity(intent)后被调用才能生效 退出动画1234567891011@Overridepublic void finish() { super.finish(); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);// 采用overridePendingTransition(int enterAnim, int exitAnim)进行设置 // enterAnim:从Activity a跳转到Activity b,进入b时的动画效果资源ID // exitAnim:从Activity a跳转到Activity b,离开a时的动画效果资源Id // 特别注意 // overridePendingTransition()必须要在finish()后被调用才能生效 //调用的时android内置的动画} 自定义进出动画自定义淡入淡出动画 淡入 1234567<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" > <alpha android:duration="1500" android:fromAlpha="0.0" android:toAlpha="1.0" /></set> 淡出 1234567<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" > <alpha android:duration="1500" android:fromAlpha="1.0" android:toAlpha="0.0" /></set> 自定义滑动出入效果图片参考自这里手把手教你使用补间动画 向左滑动出去(位置2到位置1) 12345678<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:duration="500" android:fromXDelta="0%p" android:toXDelta="-100%p" /></set> 从右边滑动进来(位置3到位置2) 12345678<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:duration="500" android:fromXDelta="100%p" android:toXDelta="0%p" /></set> fragment切换动画 系统自带的动画 12345678910FragmentTransaction fragmentTransaction = mFragmentManager .beginTransaction();fragmentTransaction.setTransition(int transit);// 通过setTransition(int transit)进行设置// transit参数说明// 1. FragmentTransaction.TRANSIT_NONE:无动画// 2. FragmentTransaction.TRANSIT_FRAGMENT_OPEN:标准的打开动画效果// 3. FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:标准的关闭动画效果// 标准动画设置好后,在Fragment添加和移除的时候都会有。 自定义动画 123456789// 采用`FragmentTransavtion`的 `setCustomAnimations()`进行设置FragmentTransaction fragmentTransaction = mFragmentManager .beginTransaction();fragmentTransaction.setCustomAnimations( R.anim.in_from_right, R.anim.out_to_left);// 此处的自定义动画效果同Activity,此处不再过多描述 viewgroup动画视图组(ViewGroup)中子元素可以具备出场时的补间动画效果常用需求场景:为ListView的 item 设置出场动画 :设置子元素的出场动画res/anim/view_animation.xml 12345678910111213141516<?xml version="1.0" encoding="utf-8"?>// 此处采用了组合动画<set xmlns:android="http://schemas.android.com/apk/res/android" > android:duration="3000" <alpha android:duration="1500" android:fromAlpha="1.0" android:toAlpha="0.0" /> <translate android:fromXDelta="500" android:toXDelta="0" /></set> :设置 视图组(ViewGroup)的动画文件res/ anim /anim_layout.xml 12345678910111213141516171819202122<?xml version="1.0" encoding="utf-8"?>// 采用LayoutAnimation标签<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" android:delay="0.5" // 子元素开始动画的时间延迟 // 如子元素入场动画的时间总长设置为300ms // 那么 delay = "0.5" 表示每个子元素都会延迟150ms才会播放动画效果 // 第一个子元素延迟150ms播放入场效果;第二个延迟300ms,以此类推 android:animationOrder="normal" // 表示子元素动画的顺序 // 可设置属性为: // 1. normal :顺序显示,即排在前面的子元素先播放入场动画 // 2. reverse:倒序显示,即排在后面的子元素先播放入场动画 // 3. random:随机播放入场动画 android:animation="@anim/view_animation" // 设置入场的具体动画效果 // 将步骤1的子元素出场动画设置到这里 /> :为视图组(ViewGroup)指定andorid:layoutAnimation属性指定的方式有两种: XML / Java代码设置方式1:在 XML 中指定 1234567891011121314<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFF" android:orientation="vertical" > <ListView android:id="@+id/listView1" android:layoutAnimation="@anim/anim_layout" // 指定layoutAnimation属性用以指定子元素的入场动画 android:layout_width="match_parent" android:layout_height="match_parent" /></LinearLayout> 方式2:在Java代码中指定 这样就不用额外设置res/ anim /anim_layout.xml该xml文件了123456789101112ListView lv = (ListView) findViewById(R.id.listView1);Animation animation = AnimationUtils.loadAnimation(this,R.anim.anim_item); // 加载子元素的出场动画LayoutAnimationController controller = new LayoutAnimationController(animation);controller.setDelay(0.5f);controller.setOrder(LayoutAnimationController.ORDER_NORMAL);// 设置LayoutAnimation的属性lv.setLayoutAnimation(controller);// 为ListView设置LayoutAnimation的属性 上述二者的效果是一样的。 再次声明原文,https://www.jianshu.com/p/733532041f46]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[java注解]]></title>
<url>%2F2018%2F12%2F22%2Fjava%E6%B3%A8%E8%A7%A3%2F</url>
<content type="text"><![CDATA[慕课网 前言 注解就是代码中出现的@ 学习注解能够看懂别人写的代码,特别是框架相关的代码 让编程更简单,代码更加清晰 能让别人高看一眼,会使用自定义注解 注解是Java提供了一种源程序中的元素关联任何信息和任何元数据的途径和方法 jdk中的注解123@Override //重写覆盖的注释@Deprecated //过时注释,用在已经过时的方法上,此时调用该方法会中间有一个横线并且有警告@SuppressWarnings("deprecation")//压缩警告,如果实在要调用上面的方法但是不想要警告,在调用上面方法的方法前加上这个注释,就不会有警告 注解分类 按照运行机制分:1.源码注解、2.编译时注解、3.运行时注解 源码注解:只在源代码中存在,编译成class后就不存在了 编译时注解:注解在源码和class中都存在,告诉编译时候的注解,比如@Override 运行时注解,在运行阶段还会起作用,甚至会影响运行逻辑 按照来源分类:1.来自jdk的注解、2.来自第三方的注解、3.自定义注解 元注解:注解的注解 自定义注解一个小栗子1234567891011@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface MyDescription { String desc(); String author(); int age() default 18;} 用@interface修饰注解 成员以没有参数没有异常的方式来声明 成员可以指定一个default值 成员类型是受限的,合法的类型有基本类型(int,double…)和String,Class,Annotation,Enumeration 如果注解只有一个成员,则成员名只能是value(),在使用的时候可以忽略成员名和赋值(=) 注解类可以没有成员,没有成员的注解成为标识注解 元注解12345678910@Target({ElementType.METHOD, ElementType.TYPE})/*Target是注解的作用域CONSTRUCTOR:构造方法声明FIELD:字段声明LOCAL_VARIABLE:局部变量声明METHOD:方法声明PACKAGE:包声明PARAMETER:参数声明TYPE:类、接口*/ 123456@Retention(RetentionPolicy.RUNTIME)/*生命周期注解,即上面按照运行时分类SOURCE:只在源码显示,编译时会丢失CLASS:编译时会记录到class中,运行时忽略RUNTIME:运行时存在,可以通过反射读取*/ 12@Inherited/*表示允许子类继承*/ 12@Documented/*生成javaDoc的时候会包含注解的信息*/ 自定义注解的使用使用注解的语法@<注解名>(<成员名>=<成员值>,<成员名2>=<成员值2>)放在相应的类或方法上1234567@MyDescription(desc = "hello class", author = "world class",age = 19)public class Father { @MyDescription(desc = "hello method", author = "world method",age = 19) public void hh(){ }} 解析自定义注解概念:通过反射获取类、函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑 123456789101112131415161718192021222324252627282930313233public static void main(String[] args) { try { //通过反射获取类 Class c = Class.forName("cn.xwmdream.main.Father"); //获得类上的注解 if (c.isAnnotationPresent(MyDescription.class)) { MyDescription m = (MyDescription) c.getAnnotation(MyDescription.class); System.out.println(m.author()); } //获得方法上的注解 Method[] methods = c.getMethods(); for (Method m : methods) { if(m.isAnnotationPresent(MyDescription.class)){ MyDescription d = (MyDescription)m.getAnnotation(MyDescription.class); System.out.println(d.age()); } } //另一种解析方式 for (Method m:methods){ Annotation[] annotations = m.getAnnotations(); for (Annotation a : annotations){ if(a instanceof MyDescription){ MyDescription d = (MyDescription)a; System.out.println(d.desc()); } } } } catch (ClassNotFoundException e) { e.printStackTrace(); }} 注解的继承 注解的继承不能继承接口的注解,只能继承父类的注解 注解的继承只能继承类的注解,不能继承方法的注解]]></content>
</entry>
<entry>
<title><![CDATA[android文本框联想AppCompatAutoCompleteTextView]]></title>
<url>%2F2018%2F12%2F18%2Fandroid%E6%96%87%E6%9C%AC%E6%A1%86%E8%81%94%E6%83%B3AppCompatAutoCompleteTextView%2F</url>
<content type="text"><![CDATA[官方文档 基本使用activity_main.xml12345<android.support.v7.widget.AppCompatAutoCompleteTextView android:id="@+id/trainId" android:layout_width="match_parent" android:layout_height="wrap_content" /> 其他的一些属性 属性 描述 android:completionHint 设置出现在下拉菜单底部的提示信息 android:completionThreshold 设置触发补全提示信息的字符个数 android:dropDownHorizontalOffset 设置下拉菜单于文本框之间的水平偏移量 android:dropDownHeight 设置下拉菜单的高度 android:dropDownWidth 设置下拉菜单的宽度 android:singleLine 设置单行显示文本内容 android:dropDownVerticalOffset 设置下拉菜单于文本框之间的垂直偏移量 java中的调用MainActivity.java1234567AppCompatAutoCompleteTextView autoCompleteTextView = (AppCompatAutoCompleteTextView) findViewById(R.id.trainId);String[] mSearchHistoryArray = new String[]{"111", "222", "12"};ArrayAdapter mSearchAdapter = new ArrayAdapter<String>( context, android.R.layout.simple_dropdown_item_1line, mSearchHistoryArray);autoCompleteTextView.setAdapter(mSearchAdapter); 自定义AdApter 其实这个自定义和自定义listview很像,重写baseAdapter,实现Filterable(过滤器接口) 先写出来布局 1234567891011121314151617181920212223<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="TextView" app:layout_constraintStart_toStartOf="parent"/> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" android:src="@android:drawable/ic_delete"/></android.support.constraint.ConstraintLayout> 适配器 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172public class AutoCompleteAdapter extends BaseAdapter implements Filterable { private static final String TAG = "AutoCompleteAdapterr"; private String[] emails = {"@qq.com","@163.com","@sina.com","@gmail.com"}; private Context context; public AutoCompleteAdapter(Context context){ this.context = context; } private ArrayList<String> data = new ArrayList<>(); @Override public int getCount() { return data.size(); } @Override public Object getItem(int position) { return data.get(position); } @Override public long getItemId(int position) { return position; } private class ViewHolder{ TextView textView; ImageView imageView; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if(convertView==null){ convertView = LayoutInflater.from(context).inflate(R.layout.auto_complete_layout,parent,false); viewHolder = new ViewHolder(); viewHolder.textView = (TextView)convertView.findViewById(R.id.textView); viewHolder.imageView = (ImageView)convertView.findViewById(R.id.imageView); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder)convertView.getTag(); } viewHolder.textView.setText(data.get(position)); viewHolder.imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { data.remove(position); notifyDataSetChanged(); } }); return convertView; } @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { Log.d(TAG, "performFiltering: "+constraint); FilterResults results = new FilterResults(); ArrayList<String> newData = new ArrayList<>(); if(constraint != null && !constraint.toString().contains("@")){ for(String data : emails){ newData.add(constraint+data); } } results.values = newData; results.count = newData.size(); return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { data = (ArrayList)results.values; notifyDataSetChanged(); } }; }} BaseAdapter是一个普通的适配器,用了一个列表优化 重写的Filter实现了一个getFilter方法 Filter要实现两个抽象方法performFiltering是传入一个字符串,是用户在文本框中输入的字符串,要返回一个FilterResults记录返回的数据,values记录数据值,count记录数据的长度,上面的代码是把输入的字符串加上邮箱后缀组成一个列表这个值返回以后会传到publishResults方法,这个方法就是处理这个上面传过来的FilterResults,上面代码是把传入的数组赋值给data,然后调用notifyDataSetChanged方法更新列表]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[git基本操作]]></title>
<url>%2F2018%2F12%2F18%2Fgit%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C%2F</url>
<content type="text"><![CDATA[基本操作 git clone “url”把一个url克隆到本地 git status查看工作区的状态 git add 可以让git跟踪到一个文件的修改记录 git remote add origin 将本地仓库关联远程的仓库 git pull origin master –allow-unrelated-histories 如果本地仓库和远程仓库有冲突,需要使用命令: git commit -m “此次变更的评论” 提交当前暂存区到工作区 git pull把github上的代码拉到本地 git diff比较两个冲突文件的不同 git log查看git提交日志,会显示每一次提交的id git reset –hard 回退到之前某个id的版本 git reflog看到当前版本之前的版本号及id git rm -r –cached .重置所有缓存,注意后面有个点 git push -u origin master 提交到master分支 git remote remove origin 取消关联远程的仓库 rm -rf .git 删除本地git git是保存修改记录的工具里程碑 里程碑 = 稳定版本号. 里程碑的含义是: 一个阶段比较稳定的版本,正式提交发布出去.提供zip下载. 操作步骤: 在github网站上.进入项目首页. 横栏按钮(commits, branches, release等),找到release按钮. 找到按钮:draft a new release,点击进入下一页面. 填入版本号,以及说明信息. 完成后,点击publish release,将软件发布出去. 这样就完成里程碑建立,同时会自动生成zip下载链接. 分支开发以前版本中有一个bug,需要马上修复,但是master已经推送了一个新功能,此时就不能在master修复bug,因为新功能还没测试,这样发布会产生更严重问题,此时需要在以前版本为基础创建一个分支,修改bug,然后把此分支合并到master]]></content>
<tags>
<tag>git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android自定义组合控件]]></title>
<url>%2F2018%2F12%2F13%2Fandroid%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E5%90%88%E6%8E%A7%E4%BB%B6%2F</url>
<content type="text"><![CDATA[简介组合控件就是把已有的控件组合组合,作为一个新的控件让系统使用上图就是左右两个button和中间一个textView组成,他可以被当成一个控件向外面提供一些属性和方法 实现实现步骤 创建属性表 创建layout布局 继承一个viewgroup并加载布局 调用 创建属性表在value文件夹下创建一个attrs.xml新建一些属性1234567891011121314<declare-styleable name="MyTitleBar"> <attr name="title_background_color" format="reference|integer" /> <attr name="left_button_visible" format="boolean" /> <attr name="right_button_visible" format="boolean" /> <attr name="title_text" format="string" /> <attr name="title_text_color" format="color" /> <attr name="title_text_drawable" format="reference|integer" /> <attr name="right_button_text" format="string" /> <attr name="right_button_text_color" format="color" /> <attr name="right_button_drawable" format="reference|integer" /> <attr name="left_button_text" format="string" /> <attr name="left_button_text_color" format="color" /> <attr name="left_button_drawable" format="reference|integer" /></declare-styleable> 比如第一个属性,他是创建要给叫title_background_color的属性,属性的数据类型是reference或者integer类型的,具体都有什么类型的属性参考这里 创建一个layout布局在layout创建一个my_view.xml布局文件123456789101112131415161718192021222324252627282930313233<?xml version="1.0" encoding="utf-8"?><merge xmlns:android="http://schemas.android.com/apk/res/android"> <Button android:id="@+id/title_bar_left" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_marginLeft="5dp" android:background="@null" android:minWidth="45dp" android:minHeight="45dp" android:textSize="14sp" /> <TextView android:id="@+id/title_bar_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:singleLine="true" android:textSize="17sp" /> <Button android:id="@+id/title_bar_right" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="7dp" android:background="@null" android:minWidth="45dp" android:minHeight="45dp" android:textSize="14sp" /></merge> 里面写了上面说的包含的三个已有控件,这个自定义组合控件是继承RelativeLayout,但是布局中的根标签用的是merge,是因为一会java中加载控件的时候相当于外面会加一层RelativeLayout,所以这里根布局不用加但是里面控件的相对位置按照RelativeLayout来写就行了 继承一个viewgroup并加载布局123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105public class MyTitleBar extends RelativeLayout { private static final String TAG = "MyTitleBarr"; private Button titleBarLeftBtn; private Button titleBarRightBtn; private TextView titleBarTitle; public MyTitleBar(Context context) { this(context,null); } public MyTitleBar(final Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(context).inflate(R.layout.my_view, this, true); titleBarLeftBtn = (Button) findViewById(R.id.title_bar_left); titleBarRightBtn = (Button) findViewById(R.id.title_bar_right); titleBarTitle = (TextView) findViewById(R.id.title_bar_title); TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.MyTitleBar); if (attributes != null) { //处理titleBar背景色 int titleBarBackGround = attributes.getResourceId(R.styleable.MyTitleBar_title_background_color, R.color.colorPrimary); setBackgroundResource(titleBarBackGround); //先处理左边按钮 //获取是否要显示左边按钮 boolean leftButtonVisible = attributes.getBoolean(R.styleable.MyTitleBar_left_button_visible, true); if (leftButtonVisible) { titleBarLeftBtn.setVisibility(View.VISIBLE); } else { titleBarLeftBtn.setVisibility(View.INVISIBLE); } //设置左边按钮的文字 String leftButtonText = attributes.getString(R.styleable.MyTitleBar_left_button_text); if (!TextUtils.isEmpty(leftButtonText)) { titleBarLeftBtn.setText(leftButtonText); //设置左边按钮文字颜色 int leftButtonTextColor = attributes.getColor(R.styleable.MyTitleBar_left_button_text_color, Color.WHITE); titleBarLeftBtn.setTextColor(leftButtonTextColor); } //设置左边图片icon 这里是二选一 要么只能是文字 要么只能是图片 int leftButtonDrawable = attributes.getResourceId(R.styleable.MyTitleBar_left_button_drawable, R.mipmap.ic_launcher); if (leftButtonDrawable != -1) { titleBarLeftBtn.setCompoundDrawablesWithIntrinsicBounds(leftButtonDrawable, 0, 0, 0); //设置到哪个控件的位置() } //处理标题 //先获取标题是否要显示图片icon int titleTextDrawable = attributes.getResourceId(R.styleable.MyTitleBar_title_text_drawable,-1); if (titleTextDrawable != -1) { titleBarTitle.setBackgroundResource(titleTextDrawable); } else { //如果不是图片标题 则获取文字标题 String titleText = attributes.getString(R.styleable.MyTitleBar_title_text); if (!TextUtils.isEmpty(titleText)) { titleBarTitle.setText(titleText); } //获取标题显示颜色 int titleTextColor = attributes.getColor(R.styleable.MyTitleBar_title_text_color, Color.WHITE); titleBarTitle.setTextColor(titleTextColor); } //先处理右边按钮 //获取是否要显示右边按钮 boolean rightButtonVisible = attributes.getBoolean(R.styleable.MyTitleBar_right_button_visible, true); if (rightButtonVisible) { titleBarRightBtn.setVisibility(View.VISIBLE); } else { titleBarRightBtn.setVisibility(View.INVISIBLE); } //设置右边按钮的文字 String rightButtonText = attributes.getString(R.styleable.MyTitleBar_right_button_text); if (!TextUtils.isEmpty(rightButtonText)) { titleBarRightBtn.setText(rightButtonText); //设置右边按钮文字颜色 int rightButtonTextColor = attributes.getColor(R.styleable.MyTitleBar_right_button_text_color, Color.WHITE); titleBarRightBtn.setTextColor(rightButtonTextColor); } //设置右边图片icon 这里是二选一 要么只能是文字 要么只能是图片 int rightButtonDrawable = attributes.getResourceId(R.styleable.MyTitleBar_right_button_drawable, R.mipmap.ic_launcher); if (rightButtonDrawable != -1) { titleBarRightBtn.setCompoundDrawablesWithIntrinsicBounds(0, 0, rightButtonDrawable, 0); //设置到哪个控件的位置() } attributes.recycle(); } } public void setTitleClickListener(OnClickListener onClickListener) { if (onClickListener != null) { titleBarLeftBtn.setOnClickListener(onClickListener); titleBarRightBtn.setOnClickListener(onClickListener); } } public Button getTitleBarLeftBtn() { return titleBarLeftBtn; } public Button getTitleBarRightBtn() { return titleBarRightBtn; } public TextView getTitleBarTitle() { return titleBarTitle; }} 首先注意构造方法,有两个构造方法,只有一个参数的构造方法是在代码中动态new的时候调用的方法两个参数的构造方法是在布局中写的时候调用的构造方法,第二个参数attr就是在布局文件中写的属性集合 控件定义的属性的时候先通过以下代码获取到属性集合 1TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.MyTitleBar); 然后通过以下方式获取到具体属性的值,如果没有定义该属性值,就会获得第二个参数的值1boolean leftButtonVisible = attributes.getBoolean(R.styleable.MyTitleBar_left_button_visible, true); 注意获得单个属性值的时候是R.styleable.(attrs中declare-styleable的名字)_(属性的名字) 调用在布局中调用的时候要命名一个本地的APP空间,即可调用attrs的那些参数,也可以在类中多写一些方法能动态修改那些参数activity_main.xml12345678910111213<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <cn.xwmdream.myViews.MyTitleBar android:layout_width="match_parent" app:title_text="我是标题" android:layout_height="wrap_content" /></LinearLayout>]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[retrofit2]]></title>
<url>%2F2018%2F11%2F27%2Fretrofit2%2F</url>
<content type="text"><![CDATA[官方文档github 快速使用简单get先创建一个接口MyApi.java 1234public interface MyApi { @GET("/{pa}") Call<ResponseBody> get(@Path ("pa")String path,@Query("page")String upage, @Query("count")String ucount);} MainActivity.java12345678910111213141516171819Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.apiopen.top") .build();Call<ResponseBody> stringvalue = retrofit.create(MyApi.class).get("getTangPoetry","1","1");stringvalue.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) { try { Log.d(TAG, "onResponse: "+response.body().string()); } catch (IOException e) { e.printStackTrace(); } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { //如果访问失败的调用 Log.e(TAG, "onFailure: "+"失败了",t); }}); 表示get一个网址,网址地址是https://api.apiopen.top/getTangPoetry?page=1&count=1这个返回的结果是一个ResponseBody类型的值,通过调用body().string()获得内容字符串 简单post要post的是一个阿里云翻译的接口,有三个参数,s,q,s,并且需要在header中添加的值,具体内容看阿里云翻译https://trans.xiaohuaerai.com/trans 给出的结果值是1234{ "status": 200, "msg": "你好,世界"} 先根据结果创建一个item类Item.java1234567891011121314151617181920public class Item { private int status; private String msg; public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } @Override public String toString() { return "Item{" +"status=" + status +", msg='" + msg + '\'' +'}'; }} 先创建一个interface,实现一个post的方法,放入参数和header12345public interface MyApi { @FormUrlEncoded @POST("/trans") Call<Item> post(@Header ("Authorization")String code,@Field("d")String d, @Field("q")String q, @Field("s")String s);} MainActivity.java12345678910111213141516Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://trans.xiaohuaerai.com") .addConverterFactory(GsonConverterFactory.create()) .build();Call<Item> stringvalue = retrofit.create(MyApi.class).post("APPCODE xxxxx","zh-CN","hello","en");stringvalue.enqueue(new Callback<Item>() { @Override public void onResponse(Call<Item> call, retrofit2.Response<Item> response) { Log.d(TAG, "onResponse: "+response.body()); } @Override public void onFailure(Call<Item> call, Throwable t) { //如果访问失败的调用 Log.e(TAG, "onFailure: "+"失败了",t); }}); 注意response.body()才是格式化好的Item类这次使用了一个GsonConverterFactory工厂直接把结果转化成Item类 搭配rxjava先创建interfaceMyApi.java1234public interface MyApi { @GET("/{pa}") Observable<ResponseBody> get(@Path ("pa")String path, @Query("page")String upage, @Query("count")String ucount);} 注意返回值由call变成了Observable MainActivity.java12345678910111213141516171819202122232425262728293031Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.apiopen.top") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();MyApi stringvalue = retrofit.create(MyApi.class);stringvalue.get("getTangPoetry","1","1") .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<ResponseBody>() { @Override public void onSubscribe(Disposable d) { Log.d(TAG, "onSubscribe: "); } @Override public void onNext(ResponseBody responseBody) { try { Log.d(TAG, "onNext: "+responseBody.string()); } catch (IOException e) { e.printStackTrace(); } } @Override public void onError(Throwable e) { Log.e(TAG, "onError: ",e ); } @Override public void onComplete() { Log.d(TAG, "onComplete: "); } }); 更多搭配rxjava的方法参考之前写的博客 解析器上面快速使用post中用到了一个GsonConverterFactory工厂直接把结果转化成Item类 另外官方还给出了别的解析类 Gson: com.squareup.retrofit2:converter-gson Jackson: com.squareup.retrofit2:converter-jackson Moshi: com.squareup.retrofit2:converter-moshi Protobuf: com.squareup.retrofit2:converter-protobuf Wire: com.squareup.retrofit2:converter-wire Simple XML: com.squareup.retrofit2:converter-simplexml Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars 自定义结果解析器 自己解析返回的结果GsonConverFactory.create,点进去这个方法,主要看responseBodyConverter方法,这个是处理返回请求的,他是调用了GsonResponseBodyConverter来处理请求,点进去这个类,public T convert(ResponseBody value)这个方法就是返回要构造的对象,参数value就是返回的response的body值,可以通过value.string();得到具体的字符串,在这个方法里解析可以通过throw的方式调用观察者的onError方法 所以重写结果解析器需要复制出来GsonConverFactory这个类,并且复制GsonResponseBodyConverter和GsonRequestBodyConverter这两个类,并且重新写一下GsonResponseBodyConverter这个类的convert方法对结果进行解析123456789101112131415161718@Overridepublic T convert(ResponseBody value) throws IOException { String response = value.string(); Log.d("tag", "convert: " + response); //抛出异常,通过观察者的onError回掉接收结果 if (1 == 1) throw new JsonIOException("抛出异常"); JsonReader jsonReader = gson.newJsonReader(value.charStream()); try { T result = adapter.read(jsonReader); if (jsonReader.peek() != JsonToken.END_DOCUMENT) { throw new JsonIOException("JSON document was not fully consumed."); } return result; } finally { value.close(); }} 进阶使用retrofit底层是使用的okhttp,所以如果要设置监听器或者设置超时,cookie等关于链接的操作可以创建一个okhttpclient,然后retrofit绑定即可123OkhttpClient client = new OkhttpClient.Builder().build();Retrofit retrofit = new Retrofit.Builder().client(client) .build(); 更多retrofit的用法参考自此博客]]></content>
<tags>
<tag>android</tag>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[rxjava]]></title>
<url>%2F2018%2F11%2F27%2Frxjava%2F</url>
<content type="text"><![CDATA[github 简介rxjava是Java一个rx最近的多线程技术。。。 他是一个扩展观察者模式实现的一个多线程技术 观察者模式:观察者模式就是相当于警察(观察者)去观察小偷(被观察者),当小偷去偷东西(事件)的时候,警察能立刻知道,并做出相应的反应。不过在编程中是告诉被观察者在执行事件的过程中去提醒观察者。。现实中小偷不会偷东西时候提醒观察者 rxJavaRxJava 有四个基本概念:Observable (可观察者,即被观察者)、 Observer (观察者)、 subscribe (订阅)、事件。Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。 基本创建:12345678910111213141516171819202122232425262728Observable<String> oble = Observable.create(new ObservableOnSubscribe<String>() { @Override public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception { e.onNext("hello"); e.onComplete(); e.onNext("hello2"); }});Observer<String> oser = new Observer<String>() { @Override public void onSubscribe(@NonNull Disposable d) { Log.w("","onSubscribe"); } @Override public void onNext(@NonNull String s) { Log.w("","onNext = "+s); } @Override public void onError(@NonNull Throwable e) { Log.w("","onError" + e); } @Override public void onComplete() { Log.w("","onComplete"); }};Log.w("","subscribe");oble.subscribe(oser); subscribeonSubscribeonNext = helloonComplete 其中oble是一个被观察者,oser是一个观察者,被观察者可以调用onNext向观察者发送内容,此时观察者就能通过重写的onNext获取到数据,执行相应的操作 另外观察者还有oncomplete和onerror,如果执行了onComplete方法,那么就会断开联系,所以hello2没有显示出来,如果发生了错误会调用了onerror也会立马断开联系。 另一些简写被观察者上面的例子是create一个最基本的被观察者,当如果被观察者只有一个动作的时候就不需要那么复杂的操作,可以用一个just1Observable<String> observable = Observable.just("hello"); 这样就是只执行一个onNext(’hello’); 简写观察者当然对于观察者也是一样,如果不用考虑oncomplete和onerror也可以简写,创建一个consumer对象,重写accept方法就行,然后通过被观察者.subscribe(观察者)来建立联系12345678Observable<String> observable = Observable.just("hello");Consumer<String> consumer = new Consumer<String>() { @Override public void accept(String s) throws Exception { System.out.println(s); }};observable.subscribe(consumer); 创建完成或者错误的另一些方法可以创建一个action对象来处理oncomplete的事件,用一个consumer来处理onnext和onerror事件,最后重载subscribe的一些方法达到建立关系的目的12345678910111213141516171819202122Observable<String> observable = Observable.just("hello"); Action onCompleteAction = new Action() { @Override public void run() throws Exception { Log.i("kaelpu", "complete"); } }; Consumer<String> onNextConsumer = new Consumer<String>() { @Override public void accept(String s) throws Exception { Log.i("kaelpu", s); } }; Consumer<Throwable> onErrorConsumer = new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { Log.i("kaelpu", "error"); } }; observable.subscribe(onNextConsumer, onErrorConsumer, onCompleteAction);} 123456public final Disposable subscribe() {}public final Disposable subscribe(Consumer<? super T> onNext) {}public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError) {}public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete) {}public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete, Consumer<? super Disposable> onSubscribe) {}public final void subscribe(Observer<? super T> observer) {} 上面是subscribe一些重载方法 线程调度rxJava最大的好处就是能够在多线程的情况下去实现,主要能应用在Android更新UI上。。 在建立关系subscribe的时候会有一些方法123456789101112131415161718192021Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() { @Override public void subscribe(ObservableEmitter<Integer> emitter) throws Exception { Log.d("kaelpu", "Observable thread is : " + Thread.currentThread().getName()); Log.d("kaelpu", "emitter 1"); emitter.onNext(1); }});Consumer<Integer> consumer = new Consumer<Integer>() { @Override public void accept(Integer integer) throws Exception { Log.d("kaelpu", "Observer thread is :" + Thread.currentThread().getName()); Log.d("kaelpu", "onNext: " + integer); }};observable.subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(consumer);} 最后那个建立关系的意思是让被监听者在Schedulers.newThread()这个新线程上,然后让观察者在AndroidSchedulers.mainThread()这个主线程上,就实现了主线程更新UI的操作,主要就是subscribeOn是让被观察者运行的线程,observeOn是观察者运行的线程 进阶使用rxjava还有很多操作符可以满足绝大多数的异步场景Android RxJava:这是一份全面 & 详细 的RxJava操作符 使用攻略]]></content>
<tags>
<tag>android</tag>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android使用okhttp3]]></title>
<url>%2F2018%2F11%2F25%2Fandroid%E4%BD%BF%E7%94%A8okhttp3%2F</url>
<content type="text"><![CDATA[官网官方github官方文档 快速使用 添加依赖 添加权限1<uses-permission android:name="android.permission.INTERNET" /> get使用12345678910111213141516171819202122232425262728String url = "https://www.baidu.com";OkHttpClient client = new OkHttpClient.Builder() .callTimeout(5,TimeUnit.SECONDS) .readTimeout(10,TimeUnit.SECONDS) .writeTimeout(10,TimeUnit.SECONDS) .build();Request request = new Request.Builder() .get() .addHeader("name","value") .url(url) .build();//enqueue是异步调用,同步是excute,但是Android不能使用client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { //失败了调用的方法 Log.e(TAG, "失败了: ",e ); } @Override public void onResponse(Call call, Response response) throws IOException { //成功了调用的方法 Log.d(TAG, "onResponse: "+response.body().string()); if(response.body()!=null){ response.body().close(); } }}); OkHttpClient是创建一个连接,并且设置了连接超时时间和读取超时时间,以及写入超时时间 Request是构造了一个请求 get是请求方式 url是请求的url addHeader添加header 其中成功的回掉中有一个response参数,他是服务器返回的response .body()获取response的body部分的值,通常调用.body().string()获取具体内容经过我测试,body().string()方法使用一次后再次使用会报错,所以最好先取出来字符串然后进行操作 .isRedirect()判断是不是重定向,其实就是判断状态码是不是重定向的状态码 .isSuccessful()判断是不是获取成功了,其实就是判断状态码是不是成功的200 enqueue()是异步调用,如果涉及到更新ui,应该使用runOnUiThread或者其他的线程通信方法 post请求form请求123456789RequestBody requestBody = new FormBody.Builder() .add("user","zhangsan") .add("pass","123456") .build();Request request = new Request.Builder() .post(requestBody) .addHeader("name","value") .url(url) .build(); 只需要用FromBody实例化一个RequestBody,然后构建request时候把get()改成post(requestBody)即可 json方式请求post请求和上面一样,不同的是创建RequestBody的方法12String json = "{\"username\":\"zhangsan\"}";RequestBody requestBody = RequestBody.create(MediaType.get("application/json"),json); 拦截器Okhttp-wiki 之 Interceptors 拦截器 拦截器就是拦截网络请求与响应1234567891011121314public class MyInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); Log.d("tagtag", String.format("Sending request %s on %s%n%s",request.url(), chain.connection(), request.headers())); //放行获取response Response response = chain.proceed(request); long t2 = System.nanoTime(); Log.d("tagtag", String.format("Received response for %s in %.1fms%n%s",response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; }} 添加应用拦截器1OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new MyInterceptor()).build(); 添加网络拦截器1OkHttpClient client = new OkHttpClient.Builder().addNetworkInterceptor(new MyInterceptor()).build(); 应用拦截器和网络拦截器的区别上面的拦截器是应用拦截器,下面的拦截器是网络拦截器网络拦截器能拦截所有的网络请求,应用拦截器只能拦截应用发出和最后收到的请求和响应 打个比方:访问a地址,a地址会重定向到b那么此时网络拦截器能拦截到发送到a的request和返回a的response和发送给b的request和b返回的response应用拦截器只会拦截到发送给a的request和b返回的response 文件下载简单方式和上面get方式差不多,只不过不是获取response.body().string(),而是获取response的字节流,通过字节流写入到文件中先加入文件读写的权限12<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980OkHttpClient client = new OkHttpClient.Builder() .build();String url = "https://www.imooc.com/mobile/mukewang.apk";Request request = new Request.Builder().get() .url(url) .build();client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { //失败了调用的方法 Log.e(TAG, "失败了: ", e); } @Override public void onResponse(Call call, Response response) throws IOException { writeFile(response); }});private void writeFile(Response response) { InputStream inputStream = null; FileOutputStream fileOutputStream = null; String filePath = Environment.getExternalStorageDirectory().getAbsolutePath(); File file = new File(filePath, "mukewang.apk"); try { //获取response的数据流 inputStream = response.body().byteStream(); //创建本地文件 fileOutputStream = new FileOutputStream(file); //设置缓存字节流 byte[] bytes = new byte[1024]; int len = 0; //获取文件总大小 long filesize = response.body().contentLength(); //已经下载文件的大小 long sum =0; while ((len = inputStream.read(bytes)) != -1) { //把获取的写入 fileOutputStream.write(bytes,0,len); //当前已经下载的文件大小 sum+=len; //计算百分比 final int progress = (int)(sum*100/filesize); runOnUiThread(new Runnable() { @Override public void run() { progressBar.setProgress(progress); } }); } fileOutputStream.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (inputStream != null) { inputStream.close(); } if (fileOutputStream != null) { fileOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "完成啦", Toast.LENGTH_SHORT).show(); } });} 其中还调用了设置了一个progressBar显示长度 使用拦截器获取下载进度写一个接口,设置progress的值,让activity实现这个接口1234public interface ProgressListenere { void sendProgress(int i); void done();} 重写ResponseBody,在这里面获取下载的进度12345678910111213141516171819202122232425262728293031323334353637383940414243444546public class MyResponseBody extends ResponseBody { private ResponseBody mResponseBody; private BufferedSource mBufferedSource; private ProgressListenere mProgressListenere; public MyResponseBody(ResponseBody mResponseBody, ProgressListenere mProgressListenere) { this.mResponseBody = mResponseBody; this.mProgressListenere = mProgressListenere; } @Override public MediaType contentType() { return mResponseBody.contentType(); } @Override public long contentLength() { return mResponseBody.contentLength(); } @Override public BufferedSource source() { if(mBufferedSource==null){ mBufferedSource=Okio.buffer(getSource(mResponseBody.source())); } return mBufferedSource; } private Source getSource(Source source){ return new ForwardingSource(source) { long allSize=0; long sum=0; @Override public long read(Buffer sink, long byteCount) throws IOException { if(allSize==0){ allSize = contentLength(); } long len = super.read(sink, byteCount); sum+=(len>0?len:0); int progress=(int)(sum*100/allSize); if(len==-1){ mProgressListenere.done(); }else{ mProgressListenere.sendProgress(progress); } return len; } }; }} mainactivity中和上面差不多,只不过设置进度条的那部分不用写了,然后个构建client的时候要写一个网络拦截器,里面加载刚刚写的ResponseBody1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Response response = chain.proceed(chain.request()); return response.newBuilder().body(new MyResponseBody(response.body(),MainActivity.this)).build(); } }) .build();String url = "https://www.imooc.com/mobile/mukewang.apk";Request request = new Request.Builder().get() .url(url) .build();client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e(TAG, "失败了: ", e); } @Override public void onResponse(Call call, Response response) throws IOException { writeFile(response); }});private void writeFile(Response response) { InputStream inputStream = null; FileOutputStream fileOutputStream = null; String filePath = Environment.getExternalStorageDirectory().getAbsolutePath(); File file = new File(filePath, "mukewang.apk"); try { inputStream = response.body().byteStream(); fileOutputStream = new FileOutputStream(file); byte[] bytes = new byte[1024]; int len; while ((len = inputStream.read(bytes)) != -1) { fileOutputStream.write(bytes,0,len); } fileOutputStream.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (inputStream != null) { inputStream.close(); } if (fileOutputStream != null) { fileOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } } 处理cookies构建client的时候调用cookieJar方法实现一个CookieJar,saveFromResponse是用于保存网页上传回来的List,loadForRequest是发送一个List123456789101112OkHttpClient client = new OkHttpClient.Builder() .cookieJar(new CookieJar() { @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { } @Override public List<Cookie> loadForRequest(HttpUrl url) { return null; } }) .build(); 如果cookie为空,返回一个空的list,不能返回null如果想要保持session,则需要保存cookie中对应的sessionid即可 设置缓存1234int cachesize = 10*1024*1024;OkHttpClient client = new OkHttpClient.Builder() .cache(new Cache(getExternalCacheDir().getAbsoluteFile(),cachesize)) .build(); 之前写的Java使用okhttp3使用get访问url,使用json和text的方式posturl 要引入okhttp和okio还有阿里巴巴的fastjson的jar12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273import java.io.IOException;import java.util.Iterator;import java.util.Set;import com.alibaba.fastjson.JSONObject;import okhttp3.FormBody;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.RequestBody;import okhttp3.Response;public class HttpUtils { private static OkHttpClient client = new OkHttpClient(); public static String get(String url) throws IOException { Request request = new Request.Builder().url(url).build(); Response response = client.newCall(request).execute(); if (response.isSuccessful()) { return response.body().string(); } else { throw new IOException("Unexpected code " + response); } } // data例子:a=b&=c public static String Posttext(String url, String data) { String[] datas = data.split("&"); FormBody.Builder builder = new FormBody.Builder(); for (String dataa:datas) { builder.add(dataa.split("=")[0],dataa.split("=")[1]); } RequestBody requestBody = builder.build(); return Post(url,requestBody); } //data例子 {a:b,c:d} public static String Postjson(String url,String json) { FormBody.Builder builder = new FormBody.Builder(); JSONObject jsonobject = JSONObject.parseObject(json); Set<String> keySet = jsonobject.keySet(); Iterator<String> it = keySet.iterator(); while(it.hasNext()) { String a = it.next(); String b = jsonobject.getString(a); builder.add(a, b); } RequestBody requestBody = builder.build(); return Post(url,requestBody); } public static String Post(String url,RequestBody requestBody) { String result = null; Request request = new Request.Builder().url(url).post(requestBody).build(); try { Response response = client.newCall(request).execute(); result = response.body().string(); } catch (IOException e) { e.printStackTrace(); } return result; } public static void main(String[] args) throws IOException { System.out.println(get("http://localhost:8080/Qcb/FriendZoneServlet")); String json = "{\"context\":\"ccc\",\"icon\":\"aaaa\",\"name\":\"bbb\"}"; System.out.println(Postjson("http://localhost:8080/Qcb/FriendZoneServlet", json)); System.out.println(Posttext("http://localhost:8080/Qcb/FriendZoneServlet", "icon=abc&name=def&context=ghi")); }}]]></content>
<tags>
<tag>android</tag>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android中md设计]]></title>
<url>%2F2018%2F11%2F24%2Fandroid%E4%B8%ADmd%E8%AE%BE%E8%AE%A1%2F</url>
<content type="text"><![CDATA[Android Material Design:常用控件学习笔记 Android Material Design:使用Palette优化界面色彩搭配。 Design库-TabLayout属性详解 Tablayout使用全解,一篇就够了 Material Design系列之BottomSheet详解 Material Design系列,Behavior之BottomSheetBehavior与BottomSheetDialog BottomBarLayout——方便快捷地实现底部导航栏 ViewPager 全面总结 其他分享square出品的设置imageview为网络图片的框架网络图片框架smart-image-viewAndroid可伸缩布局-FlexboxLayout(支持RecyclerView集成)安卓输入联想AutoCompleteTextView]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android的mvvm模式]]></title>
<url>%2F2018%2F11%2F23%2Fandroid%E7%9A%84mvvm%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"><![CDATA[简介官方文档 m:modelv:view(Activity)vm:viewModel一个view要和一个viewModel绑定 我认为就是通过view层直接操作viewModel,通过ObservableInt等基本类型或者ObservableField直接绑定layout上的值 准备工作先在app的build.gradle中打开dataBinding123456android { ....... dataBinding{ enabled = true; }} 布局文件在原本布局的基础上增加根布局layout,并加入data标签,在data标签中增加要给variable,指定一个type,就是viewmodel类,name是这个类在这个xml文件中的名字,下面调用viewmodel的时候都用vm调用123456789101112131415161718<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> <data> <variable name="vm1" type="cn.xwmdream.news.ViewModel"/> </data> <!--原本布局--> <LinearLayout> <TextView android:text="@{vm1.textt}" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout></layout> activity中加载布局的方式也变了1234567@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); final ViewModel viewModel = new ViewModel(this); binding.setVm1(viewModel);} 先用binding绑定布局,然后创建一个viewmodel和binding绑定,这个viewmodel会传入布局文件中当成上面指定的vm1 ViewModel123public class ViewModel { public ObservableField<String> textt = new ObservableField<String>("helloworld");} 这个viewmodel中有一个textt的String类型的属性,和上面布局中的一个textview绑定,当这个textt的值发生改变时,xml中那个textview的text值也会发生改变 直接写一个例子,mvvm的RecyclerViewactivity_main.xml123456789101112131415161718192021222324252627282930313233343536373839404142434445<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> <data> <variable name="vm1" type="cn.xwmdream.news.ViewModel"/> </data> <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" app:setback="@{vm1.color}" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/bar_zone" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_scrollFlags="scroll|enterAlways|snap" /> </android.support.design.widget.AppBarLayout> <TextView android:id="@+id/tv_errormessage" android:visibility="@{vm1.errorVisibility}" android:text="@string/error" android:layout_width="match_parent" android:layout_height="match_parent" /> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipe_refresh" android:setRefreshing="@{vm1.refer}" android:OnRefreshListener="@{vm1.refresh}" android:visibility="@{vm1.listVisibility}" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.v7.widget.RecyclerView android:id="@+id/rv_recycler" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.SwipeRefreshLayout> </android.support.design.widget.CoordinatorLayout></layout> MainActivity.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748package cn.xwmdream.news;import android.databinding.DataBindingUtil;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.GridLayoutManager;import java.util.ArrayList;import java.util.List;import cn.xwmdream.news.databinding.ActivityMainBinding;/** * @author Administrator */public class MainActivity extends AppCompatActivity implements ViewModel.DataListener { private List<NewBean> itemsBeanList; private int page = 1; private static final String TAG = "MainAcclivity"; private NewAdapter adapter; private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); final ViewModel viewModel = new ViewModel(this); binding.setVm1(viewModel); page = 0; //通过调用binding的绑定界面的id操作控件 setSupportActionBar(binding.toolbar); binding.rvRecycler.setLayoutManager(new GridLayoutManager(this, 2)); //创建适配器 itemsBeanList = new ArrayList<NewBean>(); adapter = new NewAdapter(this,itemsBeanList); //加载适配器 binding.rvRecycler.setAdapter(adapter); viewModel.getmess(page++); } @Override public void updatedata(List<NewBean> newdata) { int count = itemsBeanList.size(); int newcount = newdata.size(); itemsBeanList.addAll(newdata); adapter.notifyItemRangeChanged(count + 1, newcount); }} ViewModel.java1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556package cn.xwmdream.news;import android.databinding.BindingAdapter;import android.databinding.ObservableBoolean;import android.databinding.ObservableField;import android.databinding.ObservableInt;import android.graphics.Color;import android.support.design.widget.CoordinatorLayout;import android.support.v4.widget.SwipeRefreshLayout;import android.view.View;import java.util.ArrayList;import java.util.List;import java.util.Random;public class ViewModel { public ObservableInt errorVisibility = new ObservableInt(View.GONE); public ObservableInt listVisibility = new ObservableInt(View.VISIBLE); public ObservableBoolean refer = new ObservableBoolean(false); public ObservableField<String> color = new ObservableField<String>("#000000"); private List<NewBean> mlist; private DataListener listener; public ViewModel(DataListener listener){ this.listener=listener; } public void getmess(int i){ if (mlist == null) { mlist = new ArrayList<NewBean>(); } mlist.clear(); for (int j = i; j < i+10; j++) { mlist.add(new NewBean(String.format("标题%d",j),String.format("内容%d",j))); } listener.updatedata(mlist); } public SwipeRefreshLayout.OnRefreshListener refresh(){ return new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { refer.set(true); Random random = new Random(); getmess(random.nextInt(100)); refer.set(false); } }; } //让view层实现的接口,用户向view层发送数据 public interface DataListener{ void updatedata(List<NewBean> list); } @BindingAdapter("app:setback") public static void setbackk(CoordinatorLayout layout,ObservableField<String> color){ layout.setBackgroundColor(Color.parseColor(color.get())); }} NewBean.java12345678910111213141516171819202122package cn.xwmdream.news;public class NewBean { private String title; private String content; public NewBean(String title, String content) { this.title = title; this.content = content; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; }} news_title_item.xml12345678910111213141516171819202122232425262728293031323334<?xml version="1.0" encoding="utf-8"?><layout> <data> <variable name="vm" type="cn.xwmdream.news.ItemViewModel" /> </data> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" android:id="@+id/cardview" android:onClick="@{vm.onmyclick}" app:cardCornerRadius="5dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/item_jian_ju" android:orientation="vertical"> <!--因为有getTitle方法,所以可以直接写title--> <TextView android:id="@+id/tv_itemtitle" android:layout_width="match_parent" android:text="@{vm.title}" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_itemcontent" android:text="@{vm.content}" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </android.support.v7.widget.CardView></layout> NewAdapter.java1234567891011121314151617181920212223242526272829303132333435363738394041424344454647package cn.xwmdream.news;import android.content.Context;import android.databinding.DataBindingUtil;import android.support.annotation.NonNull;import android.support.v7.widget.RecyclerView;import android.view.LayoutInflater;import android.view.ViewGroup;import java.util.List;import cn.xwmdream.news.databinding.NewsTitleItemBinding;public class NewAdapter extends RecyclerView.Adapter<NewAdapter.ViewHolder>{ private Context context; private List<NewBean> mItemList; @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { NewsTitleItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(context),R.layout.news_title_item,viewGroup,false); final ViewHolder holder = new ViewHolder(binding); return holder; } @Override public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) { NewBean itemsBean = mItemList.get(i); viewHolder.bindData(itemsBean); } @Override public int getItemCount() { return mItemList.size(); } static class ViewHolder extends RecyclerView.ViewHolder{ NewsTitleItemBinding binding; public ViewHolder(NewsTitleItemBinding binding) { super(binding.cardview); this.binding=binding; } public void bindData(NewBean newBean){ if (binding.getVm() == null) { binding.setVm(new ItemViewModel(newBean)); }else{ binding.getVm().setBean(newBean); } } } public NewAdapter(Context context, List<NewBean> newBeanList){ this.context=context; mItemList = newBeanList; }} ItemViewModel.java12345678910111213141516171819202122package cn.xwmdream.news;import android.view.View;import android.widget.Toast;public class ItemViewModel { private NewBean nb; public ItemViewModel(NewBean newBean){ nb=newBean; } public void onmyclick(View view){ Toast.makeText(view.getContext(), nb.getTitle(), Toast.LENGTH_SHORT).show(); } public String getTitle(){ return nb.getTitle(); } public String getContent(){ return nb.getContent(); } public void setBean(NewBean newBean) { this.nb=newBean; }} 学到的点 这个程序是用了mvvm模式,其中有两个view(主页面和item页面),和两个viewmodel layout中可以指定属性是viewmodel的值,如activity_main.xml的27行,错误信息的textview的visibility就和viewmodel的errorVisibility值发生绑定,当viewmodel的errorvisibility值改变了,那layout中相应的属性也就发生变化 当binding绑定布局后可以直接使用binding.id操作布局上的控件,如MainActivity的第30行,直接操作id为toolbar的控件 activity_main.xml第三十行是一个SwipeRefreshLayout,他有一个监听方法,OnRefreshListener,可以使用34行的操作给他绑定这个监听方法,相当于原来findviewbyid().OnRefreshListener(),直接给他设置了一个监听,是viewmodel的那个方法,同样33行也是,本来是一个setRefreshing(boolean)的一个方法,直接在xml文件中绑定了里面的值,后续直接修改那个值就相当于原来调用的setRefreshing activity_main.xml的第13行,直接在xml文件中指定了一个app:setback的属性,同时在viewmodel中第52行使用@BindingAdapter({“setback”})绑定了这个setback这个方法,在xml文件中传入了一个值,这样的话在创建页面的时候就会调用vm的那个方法,传入的第一个参数是这个空间,第二个参数是传入的值,这个方法必须是静态无返回值的,而且参数必须是vm中定义的 news_title_item.xml第14行直接设置了点击事件,对应ItemViewModel.java第九行 NewAdapter.java第16行那个NewsTitleItemBinding是因为他拉取的文件名叫news_title_item.xml]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android设置页面PreferenceFragmentCompat]]></title>
<url>%2F2018%2F11%2F21%2Fandroid%E8%AE%BE%E7%BD%AE%E9%A1%B5%E9%9D%A2PreferenceFragmentCompat%2F</url>
<content type="text"><![CDATA[说在前面Android设置页面有通过SharedPreferences实现的,使用as自己创建的settingsactivity会报preferencefragment过时,但是使用PreferenceFragmentCompat又没法和创建好的页面使用,所以我只能通过创建一个activity通过引入fragment的方式创建PreferenceFragmentCompat 说在前面需要注意的是虽然这个方法可以让设置的值全局获取到,但是经过我测试,新安装的程序如果没有打开过你写的这个activity,那么此时获取设置的值是获取不到的 简单创建SettingActivity.java创建了一个activity并且同时写了一个settingfragment,引入xml下的setting.xml1234567891011121314151617181920212223package cn.xwmdream.boxuegu;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.support.v7.preference.Preference;import android.support.v7.preference.PreferenceFragmentCompat;public class SettingActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_setting); } public static class SettingFragment extends PreferenceFragmentCompat{ @Override public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.setting); } }} activity_setting.xml12345678910111213<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".SettingActivity"> <fragment android:id="@+id/settingfragment" android:name="cn.xwmdream.boxuegu.SettingActivity$SettingFragment" android:layout_width="match_parent" android:layout_height="match_parent"/></android.support.constraint.ConstraintLayout> setting.xml最外层是一个设置屏幕,然后是一个分组,里面有一个开关123456789101112<?xml version="1.0" encoding="utf-8"?><android.support.v7.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <android.support.v7.preference.PreferenceCategory android:title="第一个分组"> <android.support.v7.preference.SwitchPreferenceCompat android:defaultValue="true" android:key="firkey" android:summary="我是详情" android:title="我是标题"/> </android.support.v7.preference.PreferenceCategory></android.support.v7.preference.PreferenceScreen> 效果图: 介绍各个控件PreferenceCategoryPreferenceCategory是一个分组,把多个preference弄成一个区域,可以起一个小标题 属性表: 属性名 作用 title 分组的名称 SwitchPreferenceCompat(开关)1234567<android.support.v7.preference.SwitchPreferenceCompat android:defaultValue="true" android:key="firkey" android:summaryOff="关闭了" android:summaryOn="打开了" android:summary="我是详情" android:title="我是标题"/> 属性表: 属性名 作用 defaultValue 默认是开(true)还是关闭(false) key 在sp文件中存储的key summaryOff 当关闭时下面的小字 summaryOn 当开关打开时下面的小字 summary 设置下面的小字,优先级小于上面的两个属性 title 标题 效果 MultiSelectListPreference(多选弹出框)123456789101112131415161718192021222324<MultiSelectListPreference android:dialogTitle="弹出框显示标题" android:entries="@array/show1" android:entryValues="@array/array1" android:key="key_multi" android:summary="多选详情" android:title="多选标题" android:defaultValue="@array/ans1" /><!--在string.xml中指定相应的值--><string-array name="show1"> <item>选项1</item> <item>选项二</item> <item>选项三</item></string-array><string-array name="array1"> <item>one</item> <item>two</item> <item>three</item></string-array><string-array name="ans1"> <item>one</item> <item>two</item></string-array> 属性表: 属性名 作用 dialogTitle 弹出的标题 entries 多选框显示出来的字符串 entryValues 存储在sp中的值,entries中显示的值一一对应,比如选择了选项1,那么在sp中存一个值one key 在sp中存储的key summary 设置下面的小字 title 标题 defaultValue 默认值,是一个列表,存储的是entryValues,如果有值没有在entryValues中,那么这个值没有用 注意:虽然defaultValue如果设置了entryvalues没有的值时表面上什么都没选,但是在sp文件中已经记录了defaultValue的值,比如defaultValue记录了一个值onee,但是entryvalues没有这个值,则在弹出框中显示都没选,但是实际上在sp文件中已经记录了onee这个值比如entryvalues中有三个值one,two,three,此时defaultvalue是onee,two,此时如果还什么都没选,弹出框只会在two前面打勾,sp文件中记录的是onee,two,如果现在在弹出框中把三个值都选了,那么在sp文件中是四个值onee,one,two,three 效果图 ListPreference(单选弹出框)123456789<ListPreference android:dialogTitle="单选弹出框" android:entries="@array/show1" android:entryValues="@array/array1" android:key="key_list_pref" android:summary="单选项情" android:title="单选标题" android:defaultValue="two" /> 属性表: 属性名 作用 dialogTitle 弹出的标题 entries 多选框显示出来的字符串 entryValues 存储在sp中的值,entries中显示的值一一对应,比如选择了选项1,那么在sp中存一个值one key 在sp中存储的key summary 设置下面的小字 title 标题 defaultValue 默认值,是一个字符串,存储的是entryValues,如果有值没有在entryValues中或者没有这个属性,则什么都不默认选 注意:虽然defaultValue如果设置了entryvalues没有的值时表面上什么都没选,但是在sp文件中已经记录了defaultValue的值,比如defaultValue记录了一个值onee,但是entryvalues没有这个值,则在弹出框中显示都没选,但是实际上在sp文件中已经记录了onee这个值,但是如果从单选框中修改了值,那么sp文件中就会记录正常的entryvalues中的值,和上面多选的还是有区别的比如entryvalues中有三个值one,two,three,此时defaultvalue是onee,此时如果还什么都没选,弹出框都不选,sp文件中记录的是onee,如果现在在选择了一个值,比如two,那么在sp文件中是正常的two 效果图 EditTextPreference(文字选项)1234567<EditTextPreference android:defaultValue="我是默认值" android:dialogTitle="我是弹出框" android:key="key_edittext" android:summary="我是详情" android:title="我是标题" /> 属性表: 属性名 作用 defaultValue 默认值 dialogTitle 弹出的标题 key 在sp中存储的key summary 设置下面的小字 title 标题 效果图 CheckBoxPreference(选择开关)1234567<CheckBoxPreference android:defaultValue="false" android:key="key_checkbox" android:summaryOff="我关闭了" android:summaryOn="我打开了" android:title="我是标题" /> 属性表: 属性名 作用 defaultValue 默认是开(true)还是关闭(false) key 在sp文件中存储的key summaryOff 当关闭时下面的小字 summaryOn 当开关打开时下面的小字 summary 设置下面的小字,优先级小于上面的两个属性 title 标题 效果 SeekBarPreference(数值选择)123456<android.support.v7.preference.SeekBarPreference android:key="key_seekbar" android:title="我是标题" app:showSeekBarValue="false" android:defaultValue="50" /> 属性表: 属性名 作用 defaultValue 默认值 key 在sp文件中存储的key title 标题 showSeekBarValue 是否显示数字 效果 设置监听对于每一个preference有一个监听方法,监听他们发生改变后的值preference.setOnPreferenceChangeListener(Preference,Object);其中object就是修改后的值,对应不同的preference它的类型是以下 preference 修改后的类型 SwitchPreferenceCompat true或者false MultiSelectListPreference Set类型,是entryvalue的String集合 ListPreference String类型,是entryvalue中的String CheckBoxPreference true或者false SeekBarPreference 具体的数字 EditTextPreference 字符串 可以设置一个监听,当他们发生改变时在summery上修改成修改后的值1234567891011121314151617181920212223242526private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object value) { String stringValue = value.toString(); if (preference instanceof ListPreference) { ListPreference listPreference = (ListPreference) preference; int index = listPreference.findIndexOfValue(stringValue); preference.setSummary(index >= 0 ? listPreference.getEntries()[index] : null); }else if(preference instanceof MultiSelectListPreference){ StringBuffer summarys = new StringBuffer(); MultiSelectListPreference multiSelectListPreference = (MultiSelectListPreference)preference; Set<String> values = (Set<String>)value; for(String v : values){ int i = multiSelectListPreference.findIndexOfValue(v); if(i>=0){ summarys.append(multiSelectListPreference.getEntries()[i]+","); } } preference.setSummary(summarys.toString()); } else { preference.setSummary(stringValue); } return true; } }; 在创建preferencefragment的时候只需要绑定一下这个方法即可12345@Overridepublic void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.setting); findPreference("key").setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);} 通过findPreference(String)方法可以通过preference的key找到对应的preference 经过我测试CheckBoxPreference和SwitchPreferenceCompat只要设置了summaryOff/On的属性,那么以上方法不会修改他们的summary,还是按照属性上的Off/On显示 通过设置打开另外的程序打开一个网址1234567<PreferenceScreen android:summary="xwmdream.cn" android:title="打开官网"> <intent android:action="android.intent.action.VIEW" android:data="https://xwmdream.cn" /></PreferenceScreen> android:action要分配的操作(按照 setAction() 方法)。android:data要分配的数据(按照 setData() 方法)。android:mimeType要分配的 MIME 类型(按照 setType() 方法)。android:targetClass组件名称的类部分(按照 setComponent() 方法)。android:targetPackage组件名称的软件包部分(按照 setComponent() 方法)。 也可以通过设置data为 1android:data="market://details?id=cn.xwmdream.app" 打开应用商店 自定义设置]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[centos7最小安装版安装完操作]]></title>
<url>%2F2018%2F11%2F20%2Fcentos7%E6%9C%80%E5%B0%8F%E5%AE%89%E8%A3%85%E7%89%88%E5%AE%89%E8%A3%85%E5%AE%8C%E6%93%8D%E4%BD%9C%2F</url>
<content type="text"><![CDATA[新建用户并禁用root远程登陆 创建新用户1useradd newname 并为新用户创建密码1passwd newname 禁用root登陆打开/etc/ssh/sshd_config找到1#PermitRootLogin yes 把前面注释去掉,yes改成no1PermitRootLogin no 重启 连接wifi:1.先启动NetworkManager:启动:1systemctl start NetworkManager 重启:1systemctl restart NetworkManager 停止:1systemctl stop NetworkManager 2.打开关闭WIFI:打开wifi1nmcli r wifi on 关闭wifi:1nmcli r wifi off 3.连接wifi:1nmcli dev wifi con "wifi name" password "wifi password" #注意要加上引号 4.检查通过ip addr就能看到下面有一个网卡获得了ip地址 或者通过ping命令看看是否联网了 5.设置开机启动1chkconfig NetworkManager on 删除多余的引导打开/boot/grub2/grup.cfg 把两个timeout改成0123456789terminal_output consoleif [ x$feature_timeout_style = xy ] ; then set timeout_style=menu set timeout=0# Fallback normal timeout code in case the timeout_style feature is# unavailable.else set timeout=0fi 然后找到那个多余的引导名字,比如CentOS Linux (0-rescue-03969eee63114364b09ab63059c1df30) 7 (Core)整个menuentry删除1234567891011121314menuentry 'CentOS Linux (0-rescue-03969eee63114364b09ab63059c1df30) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-0-rescue-03969eee63114364b09ab63059c1df30-advanced-00373310-81e1-43ae-b6df-543747750465' { load_video insmod gzio insmod part_msdos insmod xfs set root='hd0,msdos1' if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root --hint-bios=hd1,msdos1 --hint-efi=hd1,msdos1 --hint-baremetal=ahci1,msdos1 --hint='hd0,msdos1' 100b9074-0e92-4202-9407-716c3aa019b1 else search --no-floppy --fs-uuid --set=root 100b9074-0e92-4202-9407-716c3aa019b1 fi linux16 /vmlinuz-0-rescue-03969eee63114364b09ab63059c1df30 root=/dev/mapper/centos-root ro crashkernel=auto rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet initrd16 /initramfs-0-rescue-03969eee63114364b09ab63059c1df30.img} 安装python3和pip3一定要先安装这些依赖,否则会报错1yum -y install make zlib zlib-devel gcc-c++ libtool openssl openssl-devel 进入下载页面,选择需要的版本,我选择的是Python-3.7.1.tgz,解压到服务器上给权限 控制台打开目录,编译安装1./configure --with-ssl --prefix=/usr/python3 执行 ./configure 时,如果报错:configure: error: no acceptable C compiler found in $PATH 说明没有安装合适的编译器。这时,需要安装/升级 gcc 及其它依赖包。1yum install make gcc gcc-c++ 完成之后,重新执行:1./configure --with-ssl --prefix=/usr/python3 控制台输入1make && make install 错误: can’t decompress data; zlib not available 解决办法:1yum -y install zlib* ModuleNotFoundError: No module named ‘_ctypes’ 解决办法:1yum install libffi-devel -y 设置 3.x 为默认版本在 /usr/bin 下面。可以看到 python 链接的是 python 2.7,所以,执行 python 就相当于执行 python 2.7。 备份原来的python1mv /usr/bin/python /usr/bin/python.bak 将 python 链接至 python3,创建pip链接:12ln -s /usr/python3/bin/python3 /usr/bin/pythonln -s /usr/python3/bin/pip3 /usr/bin/pip 这时,再查看 Python 的版本:1python -VPython 3.5.2 输出的是 3.x,说明已经使用的是 python3了。 配置 yum升级 Python 之后,由于将默认的 python 指向了 python3,yum 不能正常使用,需要编辑 yum 的配置文件:1vi /usr/bin/yum 同时修改:1vi /usr/libexec/urlgrabber-ext-down 将1#!/usr/bin/python 改为1#!/usr/bin/python2.7 保存退出即可。 安装mysql数据库 先下载对应的rpm文件,清华大学镜像站,我下载的是mysql57-community-release-el7-10.noarch.rpm,是mysql5.7的社区版本,不知为何清华大学镜像站没有了,移步官网下载 把上面的文件放到centos里,执行 1rpm -ivh mysql57-community-release-el7-10.noarch.rpm 执行安装 1yum install mysql-server -y 修改配置文件 配置到my.cnf,此文件一般会放在/etc/my.cnf,/etc/mysql/my.cnf,或者/usr/my.cnf可以使用find / -name my.cnf查找具体位置123456789101112131415161718#mysql服务[mysqld]character-set-client-handshake = FALSEcharacter-set-server = utf8mb4collation-server = utf8mb4_unicode_ciinit_connect='SET NAMES utf8mb4'#禁用密码强度检测插件validate_password=off#mysql字符集[mysql]default-character-set=utf8[mysql.server]default-character-set=utf8[mysql_safe]default-character-set=utf8#客户端[client]default-character-set=utf8 打开mysql服务 1systemctl start mysqld 查看mysql初始密码 1grep 'temporary password' /var/log/mysqld.log 登录mysql 1mysql -u root -p 修改初始密码 1ALTER USER 'root'@'localhost' IDENTIFIED BY '111'; 刷新权限 1FLUSH PRIVILEGES; 禁用mysql开机自启动 1chkconfig mysqld off 配置java环境 去Java官网下载对应的jdk包,我下载的是jdk-8u191-linux-x64.tar.gz将下载的文件解压后移动到/opt目录下 1sudo mv jdk1.8.0_181 /opt/ 修改环境变量 1sudo vi ~/.bashrc 在末尾添加如下配置12345#set Java environmentexport JAVA_HOME=/opt/jdk1.8.0_191export JRE_HOME=${JAVA_HOME}/jreexport CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/libexport PATH=${JAVA_HOME}/bin:$PATH 保存退出,使用source命令使其生效 1source ~/.bashrc 验证java环境是否配置成功 1java -vsersion 配置tomcat环境 去tomcat官网下载对应的压缩包,解压到服务器上/opt目录下,解压后我的路径是/opt/tomcat-9.0/,一定要给权限 打开.bashrc写下代码 123#set tomcat environmentexport CATALINA_HOME=/opt/tomcat-9.0export PATH=${CATALINA_HOME}/bin:$PATH 保存退出,使用source命令使其生效 1source ~/.bashrc 启动与关闭 1234#打开startup.sh#关闭shutdown.sh 配置项目把项目打包成war放到webapps目录下 安装nginx 参考自 以下内容有点问题,如需安装,参考我写的专门的nginx教程,也可参考官网安装教程:http://nginx.org/en/docs/install.html 首先安装基本环境 1yum -y install make zlib zlib-devel gcc-c++ libtool openssl openssl-devel 安装pcre pcre功能是让nginx有rewrite功能检查电脑上是否安装过 1pcre-config --version 如果安装过忽略此步骤,如果没安装过根据以下安装一下 下载PCRE:wget http://downloads.sourceforge.net/project/pcre/pcre/8.35/pcre-8.35.tar.gz 解压到服务器上 编译:./configure 安装:make && make install 查看安装版本:pcre-config –version 如果出现版本号,说明安装成功 检查系统里是否安装了pcre软件 rpm -qa pcre 如果没有显示说明没有安装 反之安装过 rpm -e –nodeps pcre 删除pcre 安装nginx去官网下载最新的nginx安装包,解压到服务器上并且cd到目录编译安装:./configure 默认地址 /usr/local/nginx可以用参数–prefix=/usr/nginx改变安装目录编译的时候会报一个openssl没有什么的错误,不用管,只要没出现error就行了安装:make&&make install 关闭防火墙参考 1、firewalld的基本使用启动: systemctl start firewalld关闭: systemctl stop firewalld查看状态: systemctl status firewalld开机禁用 : systemctl disable firewalld开机启用 : systemctl enable firewalld2.systemctl是CentOS7的服务管理工具中主要的工具,它融合之前service和chkconfig的功能于一体。启动一个服务:systemctl start firewalld.service关闭一个服务:systemctl stop firewalld.service重启一个服务:systemctl restart firewalld.service显示一个服务的状态:systemctl status firewalld.service在开机时启用一个服务:systemctl enable firewalld.service在开机时禁用一个服务:systemctl disable firewalld.service查看服务是否开机启动:systemctl is-enabled firewalld.service查看已启动的服务列表:systemctl list-unit-files|grep enabled查看启动失败的服务列表:systemctl –failed3.配置firewalld-cmd查看版本: firewall-cmd –version查看帮助: firewall-cmd –help显示状态: firewall-cmd –state查看所有打开的端口: firewall-cmd –zone=public –list-ports更新防火墙规则: firewall-cmd –reload查看区域信息: firewall-cmd –get-active-zones查看指定接口所属区域: firewall-cmd –get-zone-of-interface=eth0拒绝所有包:firewall-cmd –panic-on取消拒绝状态: firewall-cmd –panic-off查看是否拒绝: firewall-cmd –query-panic那怎么开启一个端口呢添加firewall-cmd –zone=public –add-port=80/tcp –permanent (–permanent永久生效,没有此参数重启后失效)重新载入firewall-cmd –reload查看firewall-cmd –zone= public –query-port=80/tcp删除firewall-cmd –zone= public –remove-port=80/tcp –permanent 操作nginx参考 运行的话到安装目录的sbin目录下,执行./nginx运行,或者相应的命令]]></content>
<tags>
<tag>mysql</tag>
<tag>linux</tag>
<tag>centos</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android使用ndk]]></title>
<url>%2F2018%2F11%2F12%2Fandroid%E4%BD%BF%E7%94%A8ndk%2F</url>
<content type="text"><![CDATA[慕课网 基本知识交叉编译简单地说,就是在一个平台上生成另一个平台上可执行的代码比如在windows生成Android的arm平台的可执行代码 jni是什么Java native interface(JNI)标准成为Java平台的一部分,它允许Java代码和其他语言写的代码进行交互 jni的实现流程 链接库 静态链接库 动态链接库静态链接库静态链接库就是把所有用到的依赖都包含进去,比如写一个c语言helloworld,他会把stdio.h的所有都包含进去 优点:这个库拿到各个地方都能用 缺点:库体积非常大,只需要一个print但是吧stdio的所有内容都包含进去了动态链接库动态链接库是只包含自己写的那部分东西,别的都不包含,在别的地方用的时候动态的去找需要的依赖 优点:文件体积小,只包含了printf 缺点:可能出现找不到依赖的情况ndk的链接库大多数是动态链接库 NDK知识android studio现在已经默认支持ndk,只需要创建项目的时候勾选include c++即可,运行的时候没有ndk和cmake就按照提示下载即可我使用的环境是Android studio3.1 默认生成的文件创建一个支持c++的项目以后会给一个cpp文件和一个默认的Mainactivity的Java文件 native-lib.cpp 12345678910111213#include <jni.h>#include <string>using namespace std;extern "C" JNIEXPORT jstringJNICALLJava_cn_xwmdream_ndktest_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str());} 看上面的那个方法名Java_cn_xwmdream_ndktest_MainActivity_stringFromJNI意思应该是Java语言,包名是cn.xwmdream.ndktest类名是MainActivity,方法名是stringFromJNI他有两个参数,还不懂什么意思,他创建了一个string类型的hello,赋值为’hello from c++’,然后调用NewStringUTF方法把c语言的字符串转化成Java能用的字符串,返回给Java程序上面的 extern “C” JNIEXPORT jstring应该是指定这个方法的返回值是一个jstring类型 MainActivity.java 12345678910111213141516public class MainActivity extends AppCompatActivity { //静态代码块,说明要用哪个文件,这里调用的是native-lib.cpp文件 static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = (TextView) findViewById(R.id.sample_text); //调用这个方法,输出hello from c++,写到那个textView上 tv.setText(stringFromJNI()); } //声明一个native类型的方法,静态代码块说的那个cpp文件里面的方法对应 public native String stringFromJNI();} JNI交互处理jni的一些基本知识基本类型基本类型和本地等效类型表: Java类型 对应的jni类型 说明 boolean jboolean 无符号,8位 byte jbyte 无符号,8位 char jchar 无符号,16位 short jshort 有符号,16位 int jint 有符号,32位 long jlong 有符号,64位 float jfloat 32位 double jdouble 64位 void void N/A String jstring 我自己加的,不知道 处理方式就是Java类型jni类型c语言类型上面的例子就是c语言的char *(string.c_str())类型通过NewStringUTF转化成jni的jstring类型,然后传递到Java代码里通过String类型输出出来 Java和native层进行字符串交互处理java向c语言传递一个字符串还是上面的那个例子改改 native-lib.cpp 123456789Java_cn_xwmdream_ndktest_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */jclas, jstring path) { const char *c = env->GetStringUTFChars(path, NULL); char d[50]; strcpy(d,c); d[0]='7'; //对指针进行释放 env->ReleaseStringUTFChars(path,c); return env->NewStringUTF(d);} 参数由原来的两个增加了一个jstring类型,这个值就是Java层传进来的一个String类型的参数,然后通过GetStringUTFChars(jstring,jboolean)把jstring类型的参数转化成一个char类型的指针,通过strcpy复制给d,然后改第一位字符为’7’然后通过上面的把d给返回回去,其中用ReleaseStringUTFChars方法释放资源c 在调用 GetStringUTFChars 函数从 JVM 内部获取一个字符串之后,JVM 内部会分配一块新的内存,用于存储源字符串的拷贝,以便本地代码访问和修改。即然有内存分配,用完之后马上释放是一个编程的好习惯。通过调用ReleaseStringUTFChars 函数通知 JVM 这块内存已经不使用了,你可以清除了。注意:这两个函数是配对使用的,用了 GetXXX 就必须调用 ReleaseXXX,而且这两个函数的命名也有规律,除了前面的 Get 和 Release 之外,后面的都一样。MainActivity.java 1234//声明方法中增加了一个String类型的参数,对应cpp中的jstringpublic native String stringFomJNI(String a);//调用函数tv.setText(stringFromJNI("hello")); 结果就是在屏幕上显示了”7ello” Java向jni传递一个数组第一种方法是生成native层数组的拷贝首先Java层先声明一个返回int[]的一个native的方法,然后调用它1234567//声明这个方法public native int[] updatearrayJNI(int[] a); //调用,并打印他的值 int[] a= updatearrayJNI(new int[]{1,2,3,4,5}); for(int i=0;i<a.length;i++) { Log.d(TAG, "onCreate: "+a[i]); } native层实现一个jintArray返回值的方法123456789101112extern "C" JNIEXPORT jintArrayJNICALLJava_cn_xwmdream_ndktest_MainActivity_updatearrayJNI(JNIEnv *env, jobject /* this */jclas, jintArray array) { jint nativeArray[5]; env->GetIntArrayRegion(array,0,5,nativeArray); for (int i = 0; i < 5; ++i) { nativeArray[i]+=6; } env->SetIntArrayRegion(array,0,5,nativeArray); return array;} 通过GetIntArrayRegion把从Java层传递进来的array的[0,5)赋值给nativeArray这个创建的数组,然后对nativeArray进行运算,最后通过SetIntArrayRegion把nativeArray的值放回array的[0,5)的位置,结果是Java收到的数组是7,8,9,10,11 第二种方法是直接调用数组指针操作java层和上面一样native层先获取一个int指针,获取他的长度,然后操作指针,最后释放指针,返回数组123456789101112131415extern "C" JNIEXPORT jintArrayJNICALLJava_cn_xwmdream_ndktest_MainActivity_updatearrayJNI(JNIEnv *env, jobject /* this */jclas, jintArray array) { //获取一个int型的指针 jint* data=env->GetIntArrayElements(array,NULL); jsize len = env->GetArrayLength(array); for(int i=0;i<len;i++){ data[i]+=3; } //释放data指针 env->ReleaseIntArrayElements(array,data,0); return array;} data是一个int类型的指针,通过GetIntArrayElements获取传入array的指针,jsize就是一个jint,通过GetArrayLength获取到传入数组的长度,然后操作指针,最后通过ReleaseIntArrayElements释放data指针,返回数组这样在Java层返回的结果是4,5,6,7,8 更多jni提供的方法在jni.h这个头文件里可以看到 自己创建一个cpp文件 在cpp文件夹下创建一个c或者cpp文件写上代码 test.cpp 123456789101112#include <jni.h>#include <string>using namespace std;extern "C" JNIEXPORT jstringJNICALLJava_cn_xwmdream_ndktest_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { return env->NewStringUTF("hello c++");} 这个是默认生成的一个代码,函数名不用说了 在app/CMakeLists.txt文件中添加链接库12345678add_library( # Sets the name of the library. testt # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/test.cpp) 第二行是动态链接的名称,也就是Java中 System.loadLibrary(“testt”)的名称最后那个是文件地址 android studio添加log首先先在CMakeLists.txt文件中添加123456target_link_libraries( # Specifies the target library. native-lib # Links the target library to the log library # included in the NDK. ${log-lib} ) 上面那个native-lib是add_library中指定的那个名字,下面是他引用的ndk链接库,这句话就是引入lib的链接库然后在要调用的cpp文件中引入android/log.h1#include <android/log.h> 然后就能调用log了,有五个级别的log,和Java层一样,写法是12345678__android_log_print(ANDROID_LOG_ERROR,"JNITag","content");//其他的等级ANDROID_LOG_VERBOSEANDROID_LOG_DEBUGANDROID_LOG_INFOANDROID_LOG_WARNANDROID_LOG_ERROR define优化1234//define指定#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR,"JNITag",__VA_ARGS__))//此时只用调用LOGE就行,tag是在define中指定LOGE("你好"); jni调用Java静态资源jni调用Java静态方法 对于JNI方法名,数据类型和方法签名的一些认识 首先写一个Java的静态方法123public static void h(String a){ Log.e(TAG, "h:"+a);} c++代码 123456789101112131415161718192021222324extern "C"JNIEXPORT void JNICALLJava_cn_xwmdream_ndktest_MainActivity_testStaticMethod(JNIEnv *env, jobject instance) { //先获取那个类名,就是包名.类名,只不过点都改成除号 jclass cls_hello = env->FindClass("cn/xwmdream/ndktest/MainActivity"); if(cls_hello==NULL){ LOGE("类获取失败"); } else{ //获取一个cls_hello指定那个类中一个名字为h,签名是(Ljava/lang/String;)V的静态方法,签名看前面的那个超链接 jmethodID mth_static_method=env->GetStaticMethodID(cls_hello,"h","(Ljava/lang/String;)V"); if(mth_static_method==NULL){ LOGE("静态方法获取错误"); }else{ //创建一个String类型的参数,用于传回Java层的h方法的string类型的参数 jstring ab=env->NewStringUTF("i m c"); //调用Java的静态方法,是cls_hello指定的类中的mth_static方法,参数是ab env->CallStaticVoidMethod(cls_hello,mth_static_method,ab); //释放资源ab env->DeleteLocalRef(ab); } } //资源回收 env->DeleteLocalRef(cls_hello);} 结果是 cn.xwmdream.ndktest E/JNITag: h:i m cc++代码中先使用findclass通过Java类的包名获取到类,然后通过getstaticmethodid的方法获取那个类中名字为h,签名为(Ljava/lang/String;)V的方法,签名的使用看上面引用的超链接,都获取到了以后,因为Java的h方法需要传递一个string类型的参数,所以要先创建一个jstring的对象,然后用env的callstaticvoidmethod方法调用Java层的那个静态没有返回值的方法,参数是刚创建的jstring对象。 jni修改Java静态属性定义一个Java静态属性1public static String name="aaa"; c++和上面调用静态方法类似,都是先获取类,然后通过签名获取静态实例123456789101112131415161718//先获取那个类名,就是包名.类名,只不过点都改成除号 jclass cls_hello = env->FindClass("cn/xwmdream/ndktest/MainActivity"); if(cls_hello==NULL){ LOGE("类获取失败"); } else{ //找到cls_hello中一个属性名为name的,类型签名是Ljava/lang/String;的一个静态成员属性 jfieldID field_name = env->GetStaticFieldID(cls_hello,"name","Ljava/lang/String;"); if(field_name==NULL) { LOGE("静态属性获取失败"); }else{ jstring n = env->NewStringUTF("bbb"); //设置cls_hello类的field_name的静态属性值为n(bbb) env->SetStaticObjectField(cls_hello,field_name,n); } } //资源回收 env->DeleteLocalRef(cls_hello); 以上代码就能通过c++把Java的那个静态属性name的值从aaa变成bbb jni调用Java实例资源jni调用Java实例方法先创建一个hello类,然后写一个sayhello方法123public void sayHello(String a){ Log.e(TAG, "sayHello: "+a);} c++ 123456789101112131415161718192021222324252627282930313233343536//先获取那个类名,就是包名.类名,只不过点都改成除号,获取hello类jclass cls_hello = env->FindClass("cn/xwmdream/ndktest/Hello");if (cls_hello == NULL) { LOGE("类获取失败"); return;}//获取sayhello方法jmethodID sayhello = env->GetMethodID(cls_hello, "sayHello", "(Ljava/lang/String;)V");if (sayhello == NULL) { LOGE("实例方法获取失败"); return;}//获取Hello的构造方法,第二个参数是构造方法的标识,第三个参数是构造方法的签名jmethodID method_gouzao = env->GetMethodID(cls_hello, "<init>", "()V");if(method_gouzao==NULL){ LOGE("构造方法获取失败"); return;}//通过类和构造方法创建出一个对象,第三个参数应该是构造方法传入的参数jobject hello_obj = env->NewObject(cls_hello, method_gouzao, NULL);if (hello_obj == NULL) { LOGE("new对象失败"); return;}//因为Java的sayhello中有一个参数,创建一个jstring当作传入的参数jstring message = env->NewStringUTF("hello abc");//调用hello_obj的sayhello方法,参数是上面的jstringenv->CallVoidMethod(hello_obj, sayhello, message);//回收资源env->DeleteLocalRef(message);env->DeleteLocalRef(hello_obj);env->DeleteLocalRef(cls_hello); 11-14 12:54:48.721 21647-21647/? E/JNITag: sayHello: hello abc 首先先获取类,然后获取sayhello这个实例方法,然后获取Hello的构造方法,通过构造方法构造一个对象,然后调用sayhello方法 jni修改实例属性Hello类增加一个属性1public String num="100"; 123456789101112jfieldID field = env->GetFieldID(cls_hello,"num","Ljava/lang/String;");if(field==NULL){ LOGE("成员属性获取失败");}//同上获取创建对象......//创建一个新的值jstring newvalue = env->NewStringUTF("50");//把hello_obj的field属性的值改成newvalueenv->SetObjectField(hello_obj, field, newvalue);//.....调用成员方法 以上就能修改实例的属性 jni异常处理 如果jni调用Java程序出现异常的话ndk还是可以继续执行下面的程序可以在调用可能出现异常Java方法的语句下面写一下方法12345678910///env->CallVoidMethod(hello_obj, sayhello, message);调用了可能出现异常的Java方法if(env->ExceptionCheck()){ env->ExceptionDescribe(); env->ExceptionClear(); LOGE("出现了异常"); //抛出一个异常 jclass cls_exception = env->FindClass("java/lang/Exception"); env->ThrowNew(cls_exception,"call java method ndk error"); return;}]]></content>
<tags>
<tag>android</tag>
<tag>java</tag>
<tag>ndk</tag>
<tag>c++</tag>
</tags>
</entry>
<entry>
<title><![CDATA[java小东西合集]]></title>
<url>%2F2018%2F08%2F19%2Fjava%E5%B0%8F%E4%B8%9C%E8%A5%BF%E5%90%88%E9%9B%86%2F</url>
<content type="text"><![CDATA[java UDP通信NOVEMBER 16, 2017UDP是一种面向无连接的协议,因此,在通信时发送端和接收端不用建立连接 InetAddress类网络通信少不了ip地址,jdk提供的InetAddress类,他封装了ip地址,提供了ip地址相关的方法 常用方法 方法作用 InetAddress getByName(String host) 参数host表示指定的主机,该方法用于在给定主机名的情况下确定主机的ip地址 InetAddress getLocalHost() 创建一个表示本地主机的InetAddress对象 String getHostName() 得到ip地址的主机名,如果是本机则是计算机名,不是本机则是主机名,如果没有域名则是ip地址 boolean isReachable(int timeout) 判断指定时间是否能到达 String getHostAddress() 得到字符串格式的原始ip地址 代码示例123456789101112131415import java.net.InetAddress;public class Example01 { public static void main(String[] args) throws Exception { InetAddress localAddress = InetAddress.getLocalHost(); InetAddress remoteAddress = InetAddress.getByName("xwmdream.cn"); System.out.println("本机的IP地址:" + localAddress.getHostAddress()); System.out.println("itcast的IP地址:" + remoteAddress.getHostAddress()); System.out.println("3秒是否可达:" + remoteAddress.isReachable(3000)); System.out.println("xwmdream.cn的主机名为:" + remoteAddress.getHostName()); }}/*本机的IP地址:192.168.0.101xwmdream.cn的IP地址:123.206.30.1393秒是否可达:truexwmdream.cn的主机名为:xwmdream.cn*/ UDP通信UDP通信过程就像是物流公司再发货一样,快递点发送和接受的时候都要用包裹来装载物品,UDP也是一样的,发送和接受数据时候也需要”集装箱”进行打包.为此,jdk提供了两个类:DatagramPacket(包裹),DatagramSocket(快递点) DatagramPacket类 构造方法 方法含义 DatagramPacket(byte[] buf,int length) 指定了封装数据的字节数组和数据的大小,没有指定ip地址和端口号.所以只能用于接受数据,不能用于发送数据 DatagramPacket(byte[] buf,int length,InetAddress addr,int port) 不仅制订了字节数组和数据大小,还制定了数据包的目标ip地址和端口号,可以用于发送,见最后补充 DatagramPacket(byte[] buf,int offset,int length) 这个构造方法和第一个构造方法类似,同样用于接收端,只不过多加了一个offset参数,用于置顶接收到数据放在buf缓冲数组是从offset开始的 DatagramPacket(byte[] buf,int offset,int length,InetAddress addr,int port) 和第二个类似,offset参数和第三个类似 补充,UDP发送数据就像快递发送包裹一样,发送的时候要注明收货人的地址电话,就像第2,4个构造方法一样,有InetAddress参数,是接收者的ip地址信息,所以第2,4构造方法用于发送端,第1,3用于接收端 方法声明 方法含义 InetAddress getAddress() 用于返回接收端或者发送端的ip地址,如果是发送端的对象,就返回接收端的ip地址,反之就返回发送端的ip地址 int getPort() 返回发送端或者接收端的端口号 byte[] getData() 返回用于发送或者要接收的数据 int getLength() 返回将要发送或者接收的数据的长度 DatagramSocket类要想发送出去数据光有”包裹”是不够的,DatagramSocket类相当于”快递点” 构造方法 方法含义 DatagramSocket() 创建一个发送端对象,系统会自动指定一个没有被使用的端口进行监听 DatagramSocket(int port) 创建一个对象,可以用于发送或者接受,port参数是监听的端口 DatagramSocket(int port,InetAddress addr) 不仅指定了端口号,还指定了ip地址,用于多块网卡的情况 基本方法 方法含义 void receive(DatagramPacket p) 能够把接收到的方法填充到DatagramPacket 数据包中,在接受到数据之前一直处于阻塞状态,接收到数据包才回返回 void send(DatagramPacket p) 用于发送DatagramPacket数据包,发送的数据包含将要发送的数据,数据的长度,接收者的ip地址和端口号 void close() 关闭当前Socket,释放资源 接收端程序:12345678910111213import java.net.*;public class Serve { public static void main(String[] args) throws Exception { byte[] b=new byte[1024];//创建一个长度1024的字节数组,用于接收数据 DatagramPacket dp=new DatagramPacket(b,1024);//创建一个DatagramPacket对象,用于接受数据 DatagramSocket ds = new DatagramSocket(6666);//创建一个DatagramSocket对象,并且监听6666端口 System.out.println("等待接受数据"); ds.receive(dp);//DatagramSocket对象的receive方法等待接受数据,如果没收到则处于阻塞状态 String str=new String(dp.getData(),0,dp.getLength())+"来自"+dp.getAddress().getHostAddress()+"的"+dp.getPort()+"端口";//打印接收到的数据 System.out.println(str);//打印接收到的数据 ds.close();//释放资源 }} 发送端程序:12345678910111213import java.net.*;import java.util.Scanner;public class Socker { public static void main(String[] args) throws Exception { String ans="你好世界";//要发送的数据 //创建一个datagramPacket的对象,也就是一个"包裹",包含发送数据的字节数组,发送数据的长度,发送目标的ip地址,和发送目标的端口号,6666端口号是接收端坚挺的 端口号 DatagramPacket dp=new DatagramPacket(ans.getBytes(),ans.getBytes().length,InetAddress.getByName("localhost"),6666); DatagramSocket ds = new DatagramSocket(7777);//创建DatagramSocket监听7777端口 ds.send(dp);//DatagramSocket将"包裹"发送出去 ds.close();//释放资源 }} 接收端运行结果:等待接受数据你好世界来自127.0.0.1的7777端口 java实现复制内容到剪贴板OCTOBER 11, 2017转自 http://blog.csdn.net/yangymy/article/details/711725891234567891011121314151617import java.awt.Toolkit;import java.awt.datatransfer.Clipboard;import java.awt.datatransfer.StringSelection;import java.awt.datatransfer.Transferable;public class ClipBoardUtil { public static void main(String[] args) { setSysClipboardText("复制的内容"); } /** * 将字符串复制到剪切板。 */ public static void setSysClipboardText(String writeMe) { Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); Transferable tText = new StringSelection(writeMe); clip.setContents(tText, null); }} java 实现拖动文件到窗口功能OCTOBER 11, 2017转自:java 实现拖动文件到窗口功能1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465package ads;import java.awt.BorderLayout;import java.awt.Color;import java.awt.datatransfer.DataFlavor;import java.awt.dnd.DnDConstants;import java.awt.dnd.DropTarget;import java.awt.dnd.DropTargetAdapter;import java.awt.dnd.DropTargetDropEvent;import java.io.File;import java.util.List;import javax.swing.JFrame;import javax.swing.JOptionPane;import javax.swing.JPanel;import javax.swing.UIManager;import javax.swing.UnsupportedLookAndFeelException;public class Tuo extends JFrame{ JPanel jp1; public Tuo() { jp1 =new JPanel(); jp1.setBackground(Color.yellow); getContentPane().add(jp1,BorderLayout.CENTER); setSize(500,200); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocation(400,200); setTitle("tuozhuai"); drag(); } public static void main(String[] args) { new Tuo().setVisible(true); } public void drag() { new DropTarget(jp1,DnDConstants.ACTION_COPY_OR_MOVE,new DropTargetAdapter() { @Override public void drop(DropTargetDropEvent dtde) { try{ if(dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); List<File>list=(List<File>)(dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)); String temp=""; for(File file:list) { temp+=file.getAbsolutePath()+";\n"; JOptionPane.showMessageDialog(null, temp); dtde.dropComplete(true); } } else { dtde.rejectDrop(); } }catch(Exception e){e.printStackTrace();} } }); }} 实现效果:]]></content>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[python合集]]></title>
<url>%2F2018%2F08%2F18%2Fpython%E5%90%88%E9%9B%86%2F</url>
<content type="text"><![CDATA[python爬虫入门JANUARY 23, 2018学习mooc:https://www.icourse163.org/learn/BIT-1001870001 第一天:requests库的基本使用方法requests有七个基本方法,分别是 方法 说明 requests.request() 构造一个请求,支撑以下各方法的基础方法 requests.get() 获取HTML网页的主要方法,对应于HTTP的GET requests.head() 获取HTML网页头信息的方法,对应于HTTP的HEAD requests.post() 向HTML网页提交POST请求的方法,对应于HTTP的POST requests.put() 向HTML网页提交PUT请求的方法,对应于HTTP的PUT requests.patch() 向HTML网页提交局部修改请求,对应于HTTP的PATCH requests.delete() 向HTML页面提交删除请求,对应于HTTP的DELETE 除了第一个另外六个是对应的http协议对资源的操作,但是看源码可以发现,第二个到底七个方法都是调用的第一个方法,也可以说requests只有一个request方法 返回值是一个Response对象 属性 说明 r.status_code http请求的返回状态码,200表示成功,其他表示失败 r.text HTTP响应内容的字符串形式,即,url对应的页面内容 r.encoding 从HTTP header中猜测的响应内容编码方式 r.apparent_encoding 从内容中分析出的响应内容编码方式(备选编码方式) r.content HTTP响应内容的二进制形式 其中r.encoding是从请求头中得到的编码信息,如果header中不存在charset,则认为编码为ISO‐8859‐1, r.text根据r.encoding显示网页内容,可能不准确,r.apparent_encoding是从具体内容中分析出来的编码信息,可能更加准确 Requests异常 异常 说明 requests.ConnectionError 网络连接错误异常,如DNS查询失败、拒绝连接等 requests.HTTPError HTTP错误异常 requests.URLRequired URL缺失异常 requests.TooManyRedirects 超过最大重定向次数,产生重定向异常 requests.ConnectTimeout 连接远程服务器超时异常 requests.Timeout 请求URL超时,产生超时异常 requests有一个专门处理异常的方法r.raise_for_status()如果不是200,产生异常 requests.HTTPError requests的爬虫基本框架123456789101112import requestsdef getHtmlText(url): try: r=requests.get(url) r.raise_for_status() r.encoding=r.apparent_encoding return r.text except: return '产生异常'if __name__=='__main__': url='http://www.baidu.com' print(getHtmlText(url)) requests方法详解requests.request(method, url, **kwargs)method : 请求方式,对应get/put/post等7种url : 拟获取页面的url链接**kwargs: 控制访问的参数,共13个 ∙ method : 请求方式r = requests.request(‘GET’, url, **kwargs)r = requests.request(‘HEAD’, url, **kwargs)r = requests.request(‘POST’, url,**kwargs)r = requests.request(‘PUT’, url, **kwargs)r = requests.request(‘PATCH’, url, **kwargs)r = requests.request(‘delete’, url, **kwargs)r = requests.request(‘OPTIONS’, url, **kwargs)∙ **kwargs: 控制访问的参数均为可选项params : 字典或字节序列,作为参数增加到url中 kv = {‘key1’: ‘value1’, ‘key2’: ‘value2’}r = requests.request(‘GET’, ‘http://python123.io/ws', params=kv)print(r.url)http://python123.io/ws?key1=value1&key2=value2 data : 字典、字节序列或文件对象,作为Request的内容 kv = {‘key1’: ‘value1’, ‘key2’: ‘value2’}r = requests.request(‘POST’, ‘http://python123.io/ws', data=kv)body = ‘主体内容’r = requests.request(‘POST’, ‘http://python123.io/ws', data=body) json : JSON格式的数据,作为Request的内容 kv = {‘key1’: ‘value1’}r = requests.request(‘POST’, ‘http://python123.io/ws', json=kv) headers : 字典,HTTP定制头 hd = {‘user‐agent’: ‘Chrome/10’}r = requests.request(‘POST’, ‘http://python123.io/ws', headers=hd) cookies : 字典或CookieJar,Request中的cookieauth : 元组,支持HTTP认证功能 files : 字典类型,传输文件 fs = {‘file’: open(‘data.xls’, ‘rb’)}r = requests.request(‘POST’, ‘http://python123.io/ws', files=fs) timeout : 设定超时时间,秒为单位 r = requests.request(‘GET’, ‘http://www.baidu.com', timeout=10) proxies : 字典类型,设定访问代理服务器,可以增加登录认证 pxs = { ‘http’: ‘http://user:[email protected]:1234','https': ‘https://10.10.10.1:4321' }r = requests.request(‘GET’, ‘http://www.baidu.com', proxies=pxs)allow_redirects : True/False,默认为True,重定向开关stream : True/False,默认为True,获取内容立即下载开关verify : True/False,默认为True,认证SSL证书开关cert : 本地SSL证书路径 python读取ini文件DECEMBER 19, 2017ini是一个保存程序设置的好东西123456789101112131415161718192021222324252627282930313233343536373839404142434445464748import configparsercfg=configparser.ConfigParser()cfg.read('test.ini')'''[database]sql_url=123.206.26sql_port=80sql_user=rootsql_pass=123[server]url=xwmdream.cn'''#读取所有的节cfg.sections()#['database', 'server']#列出节database下的元素cfg.items('database')#[('sql_url', '123.206.26'), ('sql_port', '80'), ('sql_user', 'root'), ('sql_pass', '123')]#获取database节下的sql_url的值cfg.get('database','sql_url')#'123.206.26'#设置database节下的test的值为456cfg.set('database','test','456')cfg.items('database')#[('sql_url', '123.206.26'), ('sql_port', '80'), ('sql_user', 'root'), ('sql_pass', '123'), ('test', '456')]#删除database节下的sql_pass的值cfg.remove_option('database','sql_pass')#删除database节cfg.remove_section('database')#保存到文件f=open('new.ini','w')cfg.write(f)f.close()#最后new.ini的结果'''[server]url = xwmdream.cn''' python发送邮件 大全汇总DECEMBER 18, 2017参考菜鸟教程 发送只有文字的邮件1234567891011121314151617181920212223242526import smtplibfrom email.mime.text import MIMETextfrom email.header import Headersender = '[email protected]'#发件人的邮件地址password='xxxxxx'#发件人的客户端密码host='smtp.xxx.com'#发件人用的邮件服务器host_port = 25#发件人用的服务器端口,默认是25receivers = ['[email protected]','[email protected]'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱# 三个参数:第一个为文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码message = MIMEText('文本内容~', 'plain', 'utf-8')message['From'] = Header("发件人哦~", 'utf-8')#内容中显示的发件人message['To'] = Header("收件人哦~", 'utf-8')#内容中显示的收件人message['Subject'] = Header('邮件内容的标题~', 'utf-8')#邮件的题目try: smtpObj = smtplib.SMTP_SSL(host,host_port)#这个点要注意 smtpObj.login(sender,password) #邮箱登录 smtpObj.sendmail(sender, receivers, message.as_string()) print ("邮件发送成功")except smtplib.SMTPException as e: print ("Error: 发送邮件产生错误") print(e)smtpObj.close() 发送网页邮件123456789101112131415161718192021222324252627282930import smtplibfrom email.mime.text import MIMETextfrom email.header import Headersender = '[email protected]'#发件人的邮件地址password='xxxxx'#发件人的客户端密码host='smtp.xxx.com'#发件人用的邮件服务器host_port = 25#发件人用的服务器端口,默认是25receivers = ['[email protected]'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱meg_text='''<h1>这个是大标题</h1><a href=https://xwmdream.cn>这个是链接</a>'''# 三个参数:第一个为文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码message = MIMEText(meg_text, 'html', 'utf-8')message['From'] = Header("发件人哦~", 'utf-8')#内容中显示的发件人message['To'] = Header("收件人哦~", 'utf-8')#内容中显示的收件人message['Subject'] = Header('邮件内容的标题~', 'utf-8')#邮件的题目try: smtpObj = smtplib.SMTP_SSL(host,host_port)#这个点要注意 smtpObj.login(sender,password) #邮箱登录 smtpObj.sendmail(sender, receivers, message.as_string()) print ("邮件发送成功")except smtplib.SMTPException as e: print ("Error: 发送邮件产生错误") print(e)smtpObj.close() 发送带有附件的邮件:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748import smtplibfrom email.mime.text import MIMETextfrom email.header import Headerfrom email.mime.text import MIMETextfrom email.mime.multipart import MIMEMultipartsender = '[email protected]'#发件人的邮件地址password='xxxxx'#发件人的客户端密码host='smtp.xxx.com'#发件人用的邮件服务器host_port = 25#发件人用的服务器端口,默认是25receivers = ['[email protected]'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱meg_text='''<h1>这个是大标题</h1><a href=https://xwmdream.cn>这个是链接</a><p>这个邮件有附件</p>'''# 三个参数:第一个为文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码message = MIMEMultipart()#邮件正文内容message.attach(MIMEText(meg_text, 'html', 'utf-8'))message['From'] = Header("发件人哦~", 'utf-8')#内容中显示的发件人message['To'] = Header("收件人哦~", 'utf-8')#内容中显示的收件人message['Subject'] = Header('邮件内容的标题~', 'utf-8')#邮件的题目# 构造附件1,传送当前目录下的 test.txt 文件att1 = MIMEText(open('test/test.txt', 'rb').read(), 'base64', 'utf-8')att1["Content-Type"] = 'application/octet-stream'# 这里的filename可以任意写,写什么名字,邮件中显示什么名字att1["Content-Disposition"] = 'attachment; filename="first.txt"'message.attach(att1)# 构造附件2,传送当前目录下的 runoob.txt 文件att2 = MIMEText(open('test/test.mp3', 'rb').read(), 'base64', 'utf-8')att2["Content-Type"] = 'application/octet-stream'att2["Content-Disposition"] = 'attachment; filename="two.mp3"'message.attach(att2)try: smtpObj = smtplib.SMTP_SSL(host,host_port)#这个点要注意 smtpObj.login(sender,password) #邮箱登录 smtpObj.sendmail(sender, receivers, message.as_string()) print ("邮件发送成功")except smtplib.SMTPException as e: print ("Error: 发送邮件产生错误") print(e)smtpObj.close() 发送网页中带有图片的邮件123456789101112131415161718192021222324252627282930313233343536373839404142434445import smtplibfrom email.mime.text import MIMETextfrom email.header import Headerfrom email.mime.text import MIMETextfrom email.mime.multipart import MIMEMultipartfrom email.mime.image import MIMEImagesender = '[email protected]'#发件人的邮件地址password='xxxxxx'#发件人的客户端密码host='smtp.xxx.com'#发件人用的邮件服务器host_port = 25#发件人用的服务器端口,默认是25receivers = ['[email protected]'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱meg_text='''<h1>这个是大标题</h1><a href=https://xwmdream.cn>这个是链接</a><p>这个邮件有附件</p><img src="cid:image1">'''# 三个参数:第一个为文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码message = MIMEMultipart()#邮件正文内容message.attach(MIMEText(meg_text, 'html', 'utf-8'))message['From'] = Header("发件人哦~", 'utf-8')#内容中显示的发件人message['To'] = Header("收件人哦~", 'utf-8')#内容中显示的收件人message['Subject'] = Header('邮件内容的标题~', 'utf-8')#邮件的题目# 指定图片为当前目录fp = open('test/test.jpg', 'rb')msgImage = MIMEImage(fp.read())fp.close()# 定义图片 ID,在 HTML 文本中引用msgImage.add_header('Content-ID', '<image1>')message.attach(msgImage)try: smtpObj = smtplib.SMTP_SSL(host,host_port)#这个点要注意 smtpObj.login(sender,password) #邮箱登录 smtpObj.sendmail(sender, receivers, message.as_string()) print ("邮件发送成功")except smtplib.SMTPException as e: print ("Error: 发送邮件产生错误") print(e)smtpObj.close() 补充上面的方法,发过去会显示由[email protected]代发 解决这个问题需要先引入1234567from email import encodersfrom email.utils import formataddr,parseaddr然后写入一个函数def _format_addr(s): name, addr = parseaddr(s) return formataddr((Header(name, 'utf-8').encode(), addr)) 然后将原本的12message['From'] = Header("发件人哦~", 'utf-8')#内容中显示的发件人message['To'] = Header("收件人哦~", 'utf-8')#内容中显示的收件人 换成12message['From'] = _format_addr('发件人 <%s>'%sender)message['To'] = _format_addr('收件人 <%s>'%receivers) 网页和python的握手DECEMBER 7, 2017使用websocket握手 网上找的例子,进行了优化 python代码:123456789101112131415161718192021222324252627282930313233343536import tornado.webimport tornado.websocketimport tornado.httpserverimport tornado.ioloopclass WebSocketHandler(tornado.websocket.WebSocketHandler): def check_origin(self, origin): return True def open(self): print('有人连接上我了') def on_message(self, message): print('接收到消息:'+message) self.sendhh(message) def on_close(self): print('有人下线了') def sendhh(self,mem): print('我发送了消息:'+mem) self.write_message(u""+mem)class Application(tornado.web.Application): def __init__(self): handlers = [ (r'/', WebSocketHandler) ] settings = { "template_path": "."} tornado.web.Application.__init__(self, handlers, **settings)if __name__ == '__main__': ws_app = Application() server = tornado.httpserver.HTTPServer(ws_app) server.listen(1234) tornado.ioloop.IOLoop.instance().start() html代码:123456789101112131415161718192021222324252627282930313233343536373839404142434445<!DOCTYPE HTML><html><head> <meta charset="UTF-8"> <title>Tornado Websocket</title></head><script type="text/javascript"> var ws=null; function setmem(hh){ document.getElementById('mem').innerHTML+=('<br/>'+hh); } function onLoad(){ ws = new WebSocket("ws://localhost:1234"); ws.onerror = function(e){ setmem('服务器连接失败'); ws.close(); ws=null; } ws.onmessage = function(e){ setmem('服务器说:'+e.data); } } function sendMsg(){ if(ws==null){ document.getElementById('mem').style.color='red'; return; } ha=document.getElementById('msg').value; ws.send(ha); setmem('我说:'+ha); } window.onunload =function(){ if(ws!=null){ ws.close(); } }</script><body onload='onLoad();'> Message to send: <input type="text" id="msg" /> <input type="button" onclick="sendMsg();" value="发送" /> <p id="mem"></p></body></html> python使用tcp通讯DECEMBER 5, 2017python通过tcp通讯, 使用了简单的多线程 一个接收端最多允许两个发送端同时连接 上代码: 接收端123456789101112131415161718192021222324252627282930313233343536import _threadimport socketnum=0def main(cona,acd): print('来自地址:',acd) global num conn.sendall('你好'.encode()) while True: try: data=cona.recv(1024) data=data.decode() except: print('有个人掉线了') break if not data: break print('接受到的数据是:',data) cona.sendall(str('你好'+data).encode()) conn.close() print('一个人走了',num) num-=1HOST=''PORT=5000s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.bind((HOST,PORT))s.listen(2)print('监听端口:'+str(PORT))while True: conn,addr=s.accept() if num>=2: conn.sendall('当前连接过多,稍后再试'.encode()) conn.close() continue; num+=1 _thread.start_new_thread(main,(conn,addr))s.close() 发送端:12345678910111213141516171819import socketHOST='127.0.0.1'PORT=5000s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)try: s.connect((HOST,PORT))except Exception as e: print('没有连接上') sys.exit()while True: data=s.recv(1024) data=data.decode() print('服务器说:'+data) c=input('输入:') if c.lower()=='bye': break s.sendall(c.encode())s.close() python正则表达式NOVEMBER 28, 2017python的re模块是用来匹配正则表达式的 re.compile(表达式)方法是根据正则表达式生成一个pattern对象, pattern.match(str)生成一个match对象,这个对象包含了匹配到的结果 也可以直接使用re.match(表达式,str),直接生成一个match结果对象 具体更多的方法,http://www.runoob.com/python/python-reg-expressions.html 常用正则表达式: [\u4e00-\u9fa5]匹配utf-8中文字符 ^-?\d+$匹配整数形式字符串 [0-9][1-9][0-9]匹配正整数形式字符串 附上一个自己写的爬取必应每日一图的代码123456789101112131415161718192021222324import timeimport scheduleimport reimport urllib.requestfrom time import strftimefrom time import localtimefrom time import timedef getbing(): req=urllib.request.urlopen('https://cn.bing.com') buf=req.read() an=re.findall(r'/az/hpr.+?.jpg',str(buf)) url='https://cn.bing.com'+an[0] req=urllib.request.urlopen(url) buf=req.read() f=open("/home/wwwroot/default/bing/bing.jpg",'wb') f.write(buf) f.close() print(strftime('%Y%m%d',localtime(time())),'获取成功')schedule.every().day.at('06:00').do(getbing)getbing()while True: schedule.run_pending() linux下搭建安装python3NOVEMBER 23, 2017转自:http://blog.csdn.net/liang19890820/article/details/51079633 简述CentOS 7 中默认安装了 Python,版本比较低(2.7.5),为了使用新版 3.x,需要对旧版本进行升级。 由于很多基本的命令、软件包都依赖旧版本,比如:yum。所以,在更新 Python 时,建议不要删除旧版本(新旧版本可以共存)。 版权所有:一去丶二三里,转载请注明出处:http://blog.csdn.net/liang19890820 查看 Python 版本号当 Linux 上安装 Python 后(默认安装),只需要输入简单的命令,就可以查看 Python 的版本号: python -V 写在前面:一定要先安装openssl!!!1yum install openssl-devel 下载新版本进入 Python下载页面,选择需要的版本。 这里,我选择的版本是 3.5.2 。1wget https://www.python.org/ftp/python/3.5.2/Python-3.5.2.tgz 下载完成之后,进行解压缩:1tar -zxvf Python-3.5.2.tgz 安装配置进入解压缩后的目录,安装配置: 12cd Python-3.5.2/./configure --with-ssl --prefix=/usr/python3 执行 ./configure 时,如果报错: configure: error: no acceptable C compiler found in $PATH 说明没有安装合适的编译器。这时,需要安装/升级 gcc 及其它依赖包。1yum install make gcc gcc-c++ 完成之后,重新执行:1./configure --with-ssl --prefix=/usr/python3 编译 & 安装配置完成之后,就可以编译了:1make 漫长的等待……完成后,安装:1make install 错误: can’t decompress data; zlib not available 解决办法:yum -y install zlib* ModuleNotFoundError: No module named ‘_ctypes’ 解决办法#yum install libffi-devel -y 设置 3.x 为默认版本查看 Python 的路径,在 /usr/bin 下面。可以看到 python 链接的是 python 2.7,所以,执行 python 就相当于执行 python 2.7。 备份原来的python1mv /usr/bin/python /usr/bin/python.bak 将 python 链接至 python3,创建pip链接:12ln -s /usr/python3/bin/python3 /usr/bin/pythonln -s /usr/python3/bin/pip3 /usr/bin/pip 这时,再查看 Python 的版本:1python -VPython 3.5.2 输出的是 3.x,说明已经使用的是 python3了。 配置 yum升级 Python 之后,由于将默认的 python 指向了 python3,yum 不能正常使用,需要编辑 yum 的配置文件: 1vi /usr/bin/yum 同时修改: 1vi /usr/libexec/urlgrabber-ext-down 将 #!/usr/bin/python 改为 #!/usr/bin/python2.7,保存退出即可。 版权声明:进步始于交流,收获源于分享!纯正开源之美,有趣、好玩、靠谱。。。作者:一去丶二三里 博客地址:http://blog.csdn.net/liang19890820 Python主要第三方模块和类(持续更新)NOVEMBER 12, 2017random模块123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051#random模块主要随机..import random#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#random.random()方法主要生成0-1的随机数print(random.random())#0.3245447 0-1的随机数#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#random.uniform(a,b)主要是生成在a-b区间的浮点数print(random.uniform(10,20))#13.1025687422363 10-20的浮点数print(random.uniform(20,10))#19.68574558#如果a>b 那么 b<=结果<=a#如果a<b 那么 a<=结果<=b#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#random.randint(a,b)主要生成随即整数 a<=结果<=b 其中a必须小于等于bprint (random.randint(10, 20))#17 10<=结果<=20print (random.randint(20, 20))#20 结果永远是20#print(random.randint(20,10))会报错 a必须小于等于b#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#random.choice(sequence) 从有序类型sequence中随机选出一个值 这个类型可以是列表,元组,字符串. 注意,字典和集合不能print(random.choice('abcd'))#c 在字符串abcd中随机选一个print(random.choice((1,2,3,4)))#4 在元组(1,2,3,4)中随机选出一个值#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#random.shuffle()的函数原型为:random.shuffle(x[, random]),用于将一个列表中的元素打乱。如:p = [1, 2, 3,4,'python']random.shuffle(p)print(p)#[2, 'python', 3, 4, 1] 会改变p的值#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#random.sample(sequence, k)从指定序列中随机获取指定长度的片断。sample函数不会修改原有序列。适用于集合,字符串,列表,元组.不适用于字典 生成的只有列表, 原类型不会改变list = (1,2,3,4,5,6,7,8,9,10)slice = random.sample(list, 5) #从list中随机获取5个元素,作为一个片断返回print(slice)#[7,10,6,2,9] 随机取了5个数字 生成的只是列表print(list)#(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)原有序列并没有改变 元组类型也不会改变 python的内置函数(持续更新)NOVEMBER 11, 2017 print函数print作为python的基本输出语句,在格式化输出中与c语言有很多相似的地方,但是要比c语言强大得多12345678910111213141516171819202122232425262728293031323334353637#格式化输出与c语言大差不差,只是前面在双引号里 用%进行占位,后面用%隔开,用元组进行解释age=12PI=3.1415926print("age is %d pi = %.3f"%(age,PI))#age is 12 pi = 3.142 用百分号隔开,后面是元组用于解释print("pi = %.*f"%(4,PI))#pi = 3.1416 动态设置保留小数位数'''d,i 带符号的十进制整数o 不带符号的八进制u 不带符号的十进制x 不带符号的十六进制(小写)X 不带符号的十六进制(大写)e 科学计数法表示的浮点数(小写)E 科学计数法表示的浮点数(大写)f,F 十进制浮点数g 如果指数大于-4或者小于精度值则和e相同,其他情况和f相同G 如果指数大于-4或者小于精度值则和E相同,其他情况和F相同C 单字符(接受整数或者单字符字符串)r 字符串(使用repr转换任意python对象)s 字符串(使用str转换任意python对象)'''a=1b=2c=3print(a,b,c)#1 2 3默认中间是一个空格,结尾一个换行print(a,b,c,end='ending',sep='*')#1*2*3ending sep可以设置元素中间隔字符 end设置结尾输出print("format输出:a={0} b={1} c={2}".format(a,b,c))#format输出:a=1 b=2 c=3 format输出用{0},{1}占位print("format输出:a={2} b={1} c={2}".format(a,b,c))#format输出:a=3 b=2 c=3 {}里面的数字是后面元组元素的序列,可以只用到部分元素,但是不能越界,注意序列从0开始print("{0:.3f}".format(1.2345))#1.235 count123456789101112131415161718#count可以判断列表,元组,字符串含有多少个要查询的元素,注意集合,字典不能使用,他们可以用in判断是否含有某元素a=[1,2,1]print(a.count(1))#2a=(1,2,1,'1')print(a.count(1))#2a='121'print(a.count('1'))#2a=[1,2,1]print(a.count(3))#0a={1,2,3}print(1 in a)#Trueprint(5 in a)#False 字符串相关函数strsplit:12345678910#split函数能够将一个字符串根据某部分字符串分成多个部分放在列表里a='123-456-789'print(a.split('-'))#['123', '456', '789']print(a.split('-4'))#['123', '56-789']print(a.split('hh'))#['123-456-789'] 如果字符串中没有该部分字符串,那么生成序列只有一个原字符串 map函数123456789101112131415161718192021#map() 会根据提供的函数对指定序列做映射。第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表def square(x) : # 计算平方数 return x ** 2map(square, [1,2,3,4,5]) # 计算列表和:1+2+3+4+5#[1, 4, 9, 16, 25] 注意函数参数的个数要和后面序列的个数一样map(lambda x: x ** 2, [1, 2, 3, 4, 5]) # 使用 lambda 匿名函数#[1, 4, 9, 16, 25]#map(square,[1,2,3],[4,5,6])#square接受一个参数,后面有两个序列,会报错# 提供了两个列表,对相同位置的列表数据进行相加map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])#[3, 7, 11, 15, 19]map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10,12])#[3, 7, 11, 15, 19] 如果多个列表个数不一样,按照少的算map(lambda x,y,z:x+y+z,{1,2,3},(4,5),{6:10})#[11] 元组集合字典都可以 isinstance()123456789101112131415#isinstance(object,class-or-type-or-tuple)-bool#能判断一个值是不是某个类,类型a=3print(isinstance(a,int))#Truea=3.3print(isinstance(a,int))#Falseclass Car: passcar=Car()print(isinstance(car,Car))#True car是Car类的对象 python汇总(持续更新)NOVEMBER 10, 2017 生成素数列:用列表推导式生成 :12 >>> [p for p in range(2,100) if 0 not in[p%d for d in range(2,int(sqrt(p))+1)]][2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73,79, 83, 89, 97] 用生成器推导式生成:1234567891011from math import sqrtdef primer(n=100): '''大于1小于等于n的素数推导式''' a=2 while a<=n: if 0 not in [a%d for d in range(2,int(sqrt(a)+1))]: yield a a+=1a=primer(100)>>> list(a)[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97] 序列解包(穿插print的用法):123456789101112131415161718192021222324252627282930#序列解包x,y,z=1,2,3print("x=%d y=%d z=%d" % (x,y,z),end='\thello end\n')#x=1 y=2 z=3 hello end#通过一个元组赋多个值t_tuple=(1,3.1415926,'i am string')a,b,c=t_tupleprint("a=%d b=%.*f c=%s" %(a,3,b,c))#a=1 b=3.142 c=i am string 其中b的值保留了三位小数#字典解包s={'x':1,'y':2,'z':3}a,b,c=sprint(a,b,c,sep='间隔')#x间隔y间隔z 只对字典解包等于字典的key sep是print函数输出元素中间的间隔字符a,b,c=s.items()print(a,b,c)#('x', 1) ('y', 2) ('z', 3) items()方法把字典的key和value当成一个元组#注意解包前后的变量数量要一致,否则报错#星号(*)解包,对元组()序列[]集合{}都可以a=(1,2,3,4)print(*a)#1 2 3 4print(*range(4),4,*{5,6,7,8})#0 1 2 3 4 5 6 7 8 函数参数的解包:1234567891011121314151617181920#函数中可变长度的参数#参数前加一个星号 那么参数会成为一个元组def demo(*p): print(p) for i in p: print(i,end=',')demo(1,2,3)#调用的时候不能有关键参数,也就是不能出现 x=1 的参数print()#(1, 2, 3)#1,2,3,#参数前两个星号 那么参数会成为一个字典def two(**p): print(p) for i in p.items(): print(i,end='\t')two(x=1,y=2,c=3)#参数必须是关键参数 就是 x=1这种格式的参数#{'x': 1, 'y': 2, 'c': 3}#('x', 1) ('y', 2) ('c', 3) 传递参数时的序列解包:123456789101112131415def demo(a,b,c): print(a+b+c)a=(1,2,3)demo(*a)#6dic={'a':1,'b':2,'c':3}demo(*dic)#abcdemo(*dic.items())#('a', 1, 'b', 2, 'c', 3)demo(*dic.values())#6 python3链接mysql数据库用mysqlconnector1pip install mysql-connector]]></content>
<tags>
<tag>python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[算法c语言合集]]></title>
<url>%2F2018%2F08%2F18%2F%E7%AE%97%E6%B3%95c%E8%AF%AD%E8%A8%80%E5%90%88%E9%9B%86%2F</url>
<content type="text"><![CDATA[位数很大的数的计算有时候需要算出的数据非常大,超出了可定义的范围。可以用这种方法,把数字的每一位都存到数组,然后数组逆序输出1234567891011121314151617181920#include<stdio.h>int main() { int a[1001]= {0},b,c,d,n,i; a[0]=1; while(a[5]==0) { for(i=0; a[i]!=0; i++) a[i]=a[i]*15; for(i=0; a[i]!=0; i++) if(a[i]>9) { d=a[i]/10; a[i]=a[i]%10; a[i+1]=a[i+1]+d; } } for(i=1000; a[i]==0; i--) i; for(i; i>=0; i--) printf("%d",a[i]); return 0;} c语言筛选法求素数FEBRUARY 4, 2017使用了动态内存分配,求给定区间里面所有的素数,123456789101112131415161718#include<stdlib.h>#include<stdio.h>int main() { int n,i,a,b; int *p; scanf("%d",&n); p=(int *)calloc(n,sizeof(int)); for(i=2; i<=n; i++) { if(*(p+i)==0) for(a=2; a*i<=n; a++) *(p+(a*i))=1; } for(i=2; i<=n; i++) { if(*(p+i)==0) printf("%d\n",i); } return 0;} 参考了网上的筛选法,自己添加的动态内存。因为不知道具体操作时候的内存有多大,所以使用了动态内存 c语言判断完全平方数FEBRUARY 4, 20171234567891011#include<stdio.h>#include<math.h>int main() { int n; scanf("%d",&n); if(sqrt(n)==(int)sqrt(n)) printf("是完全平方数"); else printf("不是完全平方数"); return 0;} c语言判断身份证号是否正确FEBRUARY 5, 2017今天写了一道判断身份证对错的题,才发现身份证也有算法。。怪不得我以前自己乱编身份证不能通过。。科普一下: 一个合法的身份证号码由17位地区、日期编号和顺序编号加1位校验码组成。校验码的计算规则如下: 首先对前17位数字加权求和,权重分配为:{7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2};然后将计算的和对11取模得到值Z;最后按照以下关系对应Z值与校验码M的值: Z:0 1 2 3 4 5 6 7 8 9 10M:1 0 X 9 8 7 6 5 4 3 2 1234567891011121314151617181920212223242526272829303132#include<stdio.h>char bai(int a) { char c; c = (12-a)%11+'0'; if(c>'9') c='X'; return c;}int main() { char a[19]; int c[100]= {0},b=0,n,i,j,q[]= {7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2},sum,z; printf("请输入18位身份证号码(X为大写):"); scanf("%s",a); sum=0; for(j=0; j<17; j++) { if(a[j]<'0'||a[j]>'9') { b=1; break; } z=(a[j]-'0')*q[j]; sum+=z; } sum=sum%11; if(bai(sum)!=a[17]) { b=1; } if(!b) printf("身份证号码正确"); else printf("身份证号码错误,最后一位应该是 : %c",bai(sum)); return 0;} 使用代码访问网页FEBRUARY 9, 20171234567891011#include<windows.h>int main() { system("explorer http://www.baidu.com"); return 0;}或者用指定浏览器打开#include<windows.h>int main() { system("start D:/世界之窗/TheWorld6/Application/TheWorld.exe http://www.baidu.com"); return 0;} 注意事项:在编译器里打开时”/“可能会变成”\”,需要更改成”/“,否则会错误 位数很大的阶层和FEBRUARY 18, 2017123456789101112131415161718192021222324252627282930313233343536373839#include<stdio.h>int main() { int a[1001],b,c,d,n,i,z[1001]; while(~scanf("%d",&n)) { for(i=0; i<1001; i++) z[i]=a[i]=-1; z[0]=1; for(b=1; b<=n; b++) { for(i=0; z[i]!=-1; i++) z[i]=z[i]*b; for(i=0; z[i]!=-1; i++) if(z[i]>9) { d=z[i]/10; z[i]=z[i]%10; if(z[i+1]==-1) z[i+1]=0; z[i+1]=z[i+1]+d; } for(i=0; z[i]!=-1||a[i]!=-1; i++) { if(a[i]==-1) a[i]=0; a[i]=a[i]+z[i]; } for(i=0; a[i]!=-1; i++) if(a[i]>9) { d=a[i]/10; a[i]=a[i]%10; if(a[i+1]==-1) a[i+1]=0; a[i+1]=a[i+1]+d; } } for(i=1000; a[i]==-1; i--) i; for(i; i>=0; i--) printf("%d",a[i]); } return 0;} 用链表写出来的超市管理系统FEBRUARY 27, 2017 写了个大概。功能还不完善。不过已经运用了单向链表的大部分功能了;下载 用c语言写的简陋的贪吃蛇MARCH 3, 2017写了一个贪吃蛇下载地址 用c语言写的高端贪吃蛇MARCH 6, 2017两个文件。可以更改颜色。可以自定义速度, 已知问题:极小概率出现食物出现时卡顿一秒的情况。这个bug暂时无法解决 其他的就没啥了。。有的win10可能编译出来不进入菜单直接进入游戏。。这个我也不知道为啥。。有毒。。下载地址 更新日志:取消手动改变速度。改为自动加速。每吃一个食物时间缩短2毫秒;修复了食物出现卡顿的情况,但是会偶先食物消失的情况。可能性极小,如果碰上了可以买彩票了。。不保证中奖。。 后缀表达式APRIL 7, 2017后缀表达式用到了数据结构中的堆栈,堆栈的意义就是一个数组先进后出,后缀表达式定义在百度百科 后缀表达式可以让电脑按照算数顺序进行计算 上午我尝试写了写代码,第一次写堆栈,写的好别扭。。不过还好写完了。。测试了几个数据都对。 上代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788#include<stdio.h>#include<stdlib.h>#define MAX 50struct BOX { char boxs[MAX]; int num;};typedef struct BOX * box;void out(box head) { int i; for(i=0; i<=head->num; i++) printf("%c",head->boxs[i]);}void add(box head,char c) { head->boxs[++head->num]=c;}char put(box head) { if(head->num==-1)return 'E'; return head->boxs[head->num--];}int pd(char c) { if(c>='0'&&c<='9') return 1; return 0;}int jibie(char c) { if(c=='+'||c=='-') return 1; if(c=='*'||c=='/') return 2; if(c=='(') return 0; if(c==')') return 4;}int main() { box head; head=(box)malloc(sizeof(box)); head->num=-1; int sum,s=0,i,flag,c,b=0; char ch; char ru[100]; gets(ru); for(i=0; ru[i]!='\0'; i++) { flag=pd(ru[i]); if(flag) { s=s*10+(ru[i]-'0'); } else { if(ru[i]!='('&&ru[i-1]!=')'&&b!=1) { printf("%d ",s); s=0; } b=0; if(head->num==-1||ru[i]=='(') { add(head,ru[i]); continue; } c=jibie(ru[i]); if(ru[i]==')') while(1) { ch=put(head); if(ch=='(') break; putchar(ch); } if(head->num==-1||ru[i]==')') continue; if(c<=jibie(head->boxs[head->num])&&head->boxs[head->num]!='(') { ch=put(head); putchar(ch); i--; b=1; continue; } add(head,ru[i]); } } printf("%d",s); while(head->num!=-1) putchar(put(head)); return 0;} 求两个数最大公约数的两种方法MAY 14, 2017上代码123456789int gcd1(int a, int b){ if (b) return gcd1(b,a%b); return a;}int gcd2(int a, int b){ while (b^=a^=b^=a%=b); return a;} c语言版2048AUGUST 8, 2017c语言版2048 可能有bug 欢迎反馈123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225#include <bits/stdc++.h>#include <conio.h>#define linemax 4#define empty -1#define randnum 10000#define victory 2048using namespace std;int flag,allnum,conflag,victoryflag;int form[linemax][linemax];int random() { return rand()%randnum;}void randomnum() { int i,j,num=allnum; for(i=0; i<linemax; i++) for(j=0; j<linemax; j++) { if(form[i][j]!=empty) continue; if(random()<=randnum/num) { form[i][j]=random()<(randnum/3)?4:2; allnum--; return ; } num--; } allnum--;}void welcome() { int i,j; for(i=0; i<linemax; i++) for(j=0; j<linemax; j++) form[i][j]=empty; randomnum(); randomnum();}void print() { int i,j; system("cls"); printf("%d\n",flag++); for(i=0; i<linemax; i++) { for(j=0; j<linemax; j++) { putchar('\t'); if(form[i][j]!=empty) printf("%d",form[i][j]); if(form[i][j]==victory) victoryflag=1; putchar('.'); } printf("\n\n"); } printf("%d\n",allnum);}void up() { int i,j,k; for(j=0; j<linemax; j++) for(i=0; i<linemax-1; i++) { if(form[i][j]==empty) { for(k=i+1; k<linemax; k++) { if(form[k][j]!=empty) { form[i][j]=form[k][j]; form[k][j]=empty; i--; conflag=1; break; } } } else { for(k=i+1; k<linemax; k++) { if(form[k][j]!=empty) { if(form[k][j]==form[i][j]) { form[i][j]*=2; form[k][j]=empty; allnum++; conflag=1; } break; } } } }}void down() { int i,j,k; for(j=0; j<linemax; j++) for(i=linemax-1; i>0; i--) { if(form[i][j]==empty) { for(k=i-1; k>=0; k--) { if(form[k][j]!=empty) { form[i][j]=form[k][j]; form[k][j]=empty; conflag=1; i++; break; } } } else { for(k=i-1; k>=0; k--) { if(form[k][j]!=empty) { if(form[k][j]==form[i][j]) { form[i][j]*=2; form[k][j]=empty; allnum++; conflag=1; } break; } } } }}void left() { int i,j,k; for(i=0; i<linemax; i++) for(j=0; j<linemax-1; j++) { if(form[i][j]==empty) { for(k=j+1; k<linemax; k++) { if(form[i][k]!=empty) { form[i][j]=form[i][k]; form[i][k]=empty; conflag=1; j--; break; } } } else { for(k=j+1; k<linemax; k++) { if(form[i][k]!=empty) { if(form[i][k]==form[i][j]) { form[i][j]+=form[i][k]; form[i][k]=empty; allnum++; conflag=1; } break; } } } }}void right() { int i,j,k; for(i=0; i<linemax; i++) for(j=linemax-1; j>0; j--) { if(form[i][j]==empty) { for(k=j-1; k>=0; k--) { if(form[i][k]!=empty) { form[i][j]=form[i][k]; form[i][k]=empty; j++; conflag=1; break; } } } else { for(k=j-1; k>=0; k--) { if(form[i][k]!=empty) { if(form[i][k]==form[i][j]) { form[i][j]+=form[i][k]; form[i][k]=empty; allnum++; conflag=1; } break; } } } }}int jiancha() { int i,j,e,k,i1; int ar[4][2]= {1,0,0,1,-1,0,0,-1}; for(i=0; i<linemax; i++) for(j=0; j<linemax; j++) { for(i1=0; i1<4; i1++) { e=ar[i1][0]+i; k=ar[i1][1]+j; if(e<0||k<0||e>=linemax||k>=linemax) continue; if(form[i][j]==form[e][k]) return 0; } } return 1;}void over() { printf("你失败了\n游戏结束");}int main() { flag=0; victoryflag=0; conflag=0; srand(time(NULL)); allnum=16; welcome(); print(); char get; while(1) { get=getch(); if(get=='w') up(); else if(get=='s') down(); else if(get=='a') left(); else if(get=='d') right(); if(conflag) { randomnum(); conflag=0; } print(); if(victoryflag) { printf("你赢了666"); break; } if(!allnum) { if(jiancha()) { over(); break; } } } return 0;} 星期计算APRIL 27, 2018给定一个2011年11月11日是星期五 然后给定一个别的日期,算算是星期几1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374#include<stdio.h>//判断是不是闰年int isrun(int year) { return (year%100!=0&&year%4==0||year%400==0)?1:0;}//判断一个年份有几天int num_year(int year) { return 365+isrun(year);}//到1月1日的天数int init_year(int year,int mon,int day) { int lis_mon[]= {31,28,31,30,31,30,31,31,30,31,30,31}; lis_mon[1]+=isrun(year); int i,ans=0; for(i=1; i<mon; i++) { ans+=lis_mon[i-1]; } ans=ans+day-1; return ans;}void swap(int &a,int &b) { int c; c=a; a=b; b=c;}//传入两个日期,前一天小于后一天返回1,否则返回-1int format(int year1,int mon1,int day1,int year2,int mon2,int day2) { int a1=year1*10000+mon1*100+day1; int a2=year2*10000+mon2*100+day2; int c; if(a1<=a2) return 1; return -1;}int abs(int a) { return a>0?a:-a;}//传入一个星期,再传入一个相隔几天,num负数表示星期we的前num天是星期几int getweek(int we,int num) { int ans=0; if(num>=0) ans=(we+num)%7; else { num=-num; if(we>num) ans=we-num; else ans=7-(abs(we-num)%7); } if(ans==0) ans=7; return ans;}int main() { int year,mon,day,myear=2011,mmon=11,mday=11,week=5; scanf("%d %d %d",&year,&mon,&day); int c=format(myear,mmon,mday,year,mon,day); if(c==-1){ swap(myear,year); swap(mmon,mon); swap(mday,day); } int c1=init_year(myear,mmon,mday),i; int c2=init_year(year,mon,day); int ans=0; for(i=myear; i<year; i++) { ans+=num_year(i); } ans=c*(ans-c1+c2); printf("%d",getweek(week,ans)); return 0;}]]></content>
<tags>
<tag>c语言</tag>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[android合集]]></title>
<url>%2F2018%2F08%2F09%2Fandroid%E5%90%88%E9%9B%86%2F</url>
<content type="text"><![CDATA[android多线程runOnUiThread这个可以直接在子线程中调用,并且修改ui12345678public void change(final String a){ runOnUiThread(new Runnable() { @Override public void run() { tv.setText(a); } });} 多线程调用1234567891011new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } change("aaaaaaaaaaaa"); }}).start(); 实现了五秒后更新 Handler现在主线程中创建一个handler123456789101112131415161718final Handler handler = new Handler(){ public void handleMessage(Message message){ tv.setText(""+message.what); }};然后在子线程中调用handler.sendEmptyMessage(1);就能调用上面的handlemessage方法,传入的1是传入message的what属性,handler.sendMessage(message)方法发送这个message到主线程中的那个回调new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } handler.sendEmptyMessage(1); }}).start(); android retrofit2的基本使用,搭配rxjava依赖12345678910implementation 'com.google.code.gson:gson:2.6.1'implementation 'com.orhanobut:logger:2.1.0'implementation 'com.squareup.okhttp3:okhttp:3.11.0'implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'implementation 'com.squareup.retrofit2:retrofit:2.4.0'implementation 'com.squareup.retrofit2:converter-gson:2.4.0'implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'implementation 'io.reactivex.rxjava2:rxjava:2.2.3'implementation 'com.squareup.retrofit2:adapter-rxjava:2.4.0'implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' 最简单的使用:先创建接口:12345678public interface WeatherService { @GET("s6/weather/now") Observable<WeatherEntity> getWeather(@Query("key") String key, @Query("location") String location); @GET("/") Call<ResponseBody> baidu();} 两个get 使用异步获取返回的原生字符串1234567891011121314151617181920212223242526272829OkHttpClient client = new OkHttpClient.Builder(). connectTimeout(1, TimeUnit.SECONDS). readTimeout(60, TimeUnit.SECONDS). writeTimeout(60, TimeUnit.SECONDS).build();Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://badu.com") .client(client) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();Call<ResponseBody> stringvalue = retrofit.create(WeatherService.class).baidu();stringvalue.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) { //访问成功时调用,调用方法是response.body.string() try { Log.d(TAG, "onResponse: "+response.body().string()); } catch (IOException e) { e.printStackTrace(); } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { //如果访问失败的调用 Log.d(TAG, "onFailure: "+"失败了"); }}); 使用rxjava123456789101112131415161718192021222324252627282930313233343536373839//创建一个连接对象,设置连接超时时间,读超时时间,写超时OkHttpClient.Builder client = new OkHttpClient.Builder(). connectTimeout(1, TimeUnit.SECONDS). readTimeout(60, TimeUnit.SECONDS). writeTimeout(60, TimeUnit.SECONDS);Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://free-api.heweather.com") .client(client.build()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();WeatherService weatherService = retrofit.create(WeatherService.class);weatherService.getWeather("xxxxx", "郑州") .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<WeatherEntity>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(WeatherEntity weatherEntity) { //响应解析的序列一个个进来 weatherEntities.add(weatherEntity); } @Override public void onError(Throwable e) { //出错了时候 mView.dataError(e); } @Override public void onComplete() { //响应解析的所有东西都完成了调用 mView.setData(weatherEntities); } }); 打印log12345678HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor();if(BuildConfig.DEBUG){ //显示日志 logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);}else { logInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE);}client.addInterceptor(logInterceptor); 让一个okhttp.builder对象client添加以下这个log解析器就ok 自定义结果解析器自己解析返回的结果GsonConverFactory.create,点进去这个方法,主要看responseBodyConverter方法,这个是处理返回请求的,他是调用了GsonResponseBodyConverter来处理请求,点进去这个类,public T convert(ResponseBody value)这个方法就是返回要构造的对象,参数value就是返回的response的body值,可以通过value.string();得到具体的字符串,在这个方法里解析可以通过throw的方式调用观察者的onError方法 所以重写结果解析器需要复制出来GsonConverFactory这个类,并且复制GsonResponseBodyConverter和GsonRequestBodyConverter这两个类,并且重新写一下GsonResponseBodyConverter这个类的convert方法对结果进行解析 123456789101112131415161718@Overridepublic T convert(ResponseBody value) throws IOException { String response = value.string(); Log.d("tag", "convert: " + response); //抛出异常,通过观察者的onError回掉接收结果 if (1 == 1) throw new JsonIOException("抛出异常"); JsonReader jsonReader = gson.newJsonReader(value.charStream()); try { T result = adapter.read(jsonReader); if (jsonReader.peek() != JsonToken.END_DOCUMENT) { throw new JsonIOException("JSON document was not fully consumed."); } return result; } finally { value.close(); }} rxjava和rxAndroidrxJavaRxJava 有四个基本概念:Observable (可观察者,即被观察者)、 Observer (观察者)、 subscribe (订阅)、事件。Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。 基本创建:12345678910111213141516171819202122232425262728Observable<String> oble = Observable.create(new ObservableOnSubscribe<String>() { @Override public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception { e.onNext("hello"); e.onComplete(); e.onNext("hello2"); }});Observer<String> oser = new Observer<String>() { @Override public void onSubscribe(@NonNull Disposable d) { Log.w("","onSubscribe"); } @Override public void onNext(@NonNull String s) { Log.w("","onNext = "+s); } @Override public void onError(@NonNull Throwable e) { Log.w("","onError" + e); } @Override public void onComplete() { Log.w("","onComplete"); }};Log.w("","subscribe");oble.subscribe(oser); subscribeonSubscribeonNext = helloonComplete 其中oble是一个被观察者,oser是一个观察者,被观察者可以调用onNext向观察者发送内容,此时观察者就能通过重写的onNext获取到数据,执行相应的操作 另外观察者还有oncomplete和onerror,如果执行了onComplete方法,那么就会断开联系,所以hello2没有显示出来,如果发生了错误会调用了onerror也会立马断开联系。 另一些使用方法简写被观察者上面的例子是create一个最基本的被观察者,当如果被观察者只有一个动作的时候就不需要那么复杂的操作,可以用一个just1Observable<String> observable = Observable.just("hello"); 这样就是只执行一个onNext(’hello’); 简写观察者当然对于观察者也是一样,如果不用考虑oncomplete和onerror也可以简写,创建一个consumer对象,重写accept方法就行,然后通过被观察者.subscribe(观察者)来建立联系12345678Observable<String> observable = Observable.just("hello");Consumer<String> consumer = new Consumer<String>() { @Override public void accept(String s) throws Exception { System.out.println(s); }};observable.subscribe(consumer); 创建完成或者错误的另一些方法可以创建一个action对象来处理oncomplete的事件,用一个consumer来处理onnext和onerror事件,最后重载subscribe的一些方法达到建立关系的目的1234567891011121314151617181920Observable<String> observable = Observable.just("hello");Action onCompleteAction = new Action() { @Override public void run() throws Exception { Log.i("kaelpu", "complete"); }};Consumer<String> onNextConsumer = new Consumer<String>() { @Override public void accept(String s) throws Exception { Log.i("kaelpu", s); }};Consumer<Throwable> onErrorConsumer = new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { Log.i("kaelpu", "error"); }};observable.subscribe(onNextConsumer, onErrorConsumer, onCompleteAction); public final Disposable subscribe() {}public final Disposable subscribe(Consumer<? super T> onNext) {}public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError) {}public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete) {}public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete, Consumer<? super Disposable> onSubscribe) {}public final void subscribe(Observer<? super T> observer) {} 上面是subscribe一些重载方法 线程调度rxJava最大的好处就是能够在多线程的情况下去实现,主要能应用在Android更新UI上。。 在建立关系subscribe的时候会有一些方法12345678910111213141516171819Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() { @Override public void subscribe(ObservableEmitter<Integer> emitter) throws Exception { Log.d("kaelpu", "Observable thread is : " + Thread.currentThread().getName()); Log.d("kaelpu", "emitter 1"); emitter.onNext(1); }});Consumer<Integer> consumer = new Consumer<Integer>() { @Override public void accept(Integer integer) throws Exception { Log.d("kaelpu", "Observer thread is :" + Thread.currentThread().getName()); Log.d("kaelpu", "onNext: " + integer); }};observable.subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(consumer); 最后那个建立关系的意思是让被监听者在Schedulers.newThread()这个新线程上,然后让观察者在AndroidSchedulers.mainThread()这个主线程上,就实现了主线程更新UI的操作,主要就是subscribeOn是让被观察者运行的线程,observeOn是观察者运行的线程 操作符的使用和Android的一些扩展可以看原文作者:蒲文辉链接:https://www.jianshu.com/p/7eb5ccf5ab1e來源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。 android中webview的交互webview中js调用Java代码大概思路是写一个Java类,然后通过webview的addJavascriptInterface方法把那个类传到页面中,然后页面就能直接通过指定的名字调用方法 布局就是上面一个webview下面一个textView mainactivity.Java123456789101112131415161718192021222324252627public class MainActivity extends Activity implements JsBridje{ private WebView mWebView; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWebView = (WebView)findViewById(R.id.wv_webview); mTextView = (TextView)findViewById(R.id.tv_result); //允许webview加载js mWebView.getSettings().setJavaScriptEnabled(true); //2.创建一个js接口类,jsinterface //3.创建一个就是接口类传递到webview中,第一个参数是Java接口类对象,第二个是传入到js中的名称 mWebView.addJavascriptInterface(new JsInterface(this),"javaslei"); //加载要显示的html mWebView.loadUrl("file:///android_asset/index.html"); } //重写一个jsbridje接口,让jsinterface类能够调用,改变UI @Override public void setvalue(String value) { mTextView.setText(value); }} 可以看到调用的addjavascriptinterface方法第一个参数是传入了一个对象,第二个参数是一个字符串,在js中直接调用字符串.方法即可 JsInterface.java1234567891011121314151617public class JsInterface { private static final String TAG = "JsInterface"; private JsBridje jsBridje; public JsInterface(JsBridje jsBridje) { this.jsBridje = jsBridje; } /** * 从js中调用的方法,这个方法不在主线程中执行,所以不能在这里改变UI * @param value */ @JavascriptInterface public void setvalue(String value){ jsBridje.setvalue(value); }} js调用的方法一定要加上@JavascriptInterface 因为要改变页面内容,所以引入了一个什么设计模式实现一jsbridje接口 JsBridje.java123public interface JsBridje { void setvalue(String value);} html12345678910111213141516171819202122232425<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>webview</title> <script type="text/javascript"> function oclick(){ var inputEle = document.getElementById('uservalue'); if(window.javaslei){ javaslei.setvalue(inputEle.value); }else{ alert('没找到Java对象'); } } </script></head><body> <h2>webview</h2> <div> <span>请输入要传递的值</span> <input type="text" id="uservalue" /> <p onclick="oclick()">提交</p> </div></body></html> 可以看到,在html中直接调用javaslei(mainactivity传入的名字).setvalue方法就能调用了jsinterface类中的方法,然后这个方法通过调用jsbridje的接口方法去调用mainactivity.java的setvalue方法改变textview的值 java中调用js代码其实就一行:mWebView.loadUrl(“javascript:要执行的命令”); 布局文件是webview和一个edittext和一个button mainactivity.java1234567891011121314151617181920212223public class MainActivity extends Activity{ private WebView mWebView; private EditText meditview; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWebView = (WebView)findViewById(R.id.wv_webview); meditview = (EditText) findViewById(R.id.tv_value); //允许webview加载js mWebView.getSettings().setJavaScriptEnabled(true); //加载要显示的html mWebView.loadUrl("file:///android_asset/index.html"); } public void onclick(View view) { String value = meditview.getText().toString(); mWebView.loadUrl("javascript:if(window.remote){window.remote('"+value+"')}"); }} 可以看到直接调用了js中的remote方法传入了一个字符串参数,注意那个字符串参数前后要加上单引号 html1234567891011121314<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>webview</title> <script type="text/javascript"> function remote(str){ document.write(str); } </script></head><body></body></html> html中就一个remote方法,就是把获取到的值写到界面中 使用chrome调试APP中的网页先启用调试mWebView.setWebContentsDebuggingEnabled(true); 然后chrome访问chrome://inspect/ 然后就能看到相应的inspect,点进去就能调试,不过肯定打不开,因为需要翻墙.或者下载离线包 解决了上面的内容就能够像调试网页的方式一样去调试APP中的html代码 一些常见的错误当js调用java 代码出现了throw,此时APP并不会崩溃,但是会在html的控制台中抛出一个错误 如果js没有判断是否有相应的方法就去调用会出现找不到方法的错误 参数类型错误,常见的有数组和对象里面的问题,因为js是弱类型语言,而Java是强类型,,所以如果类型有错误会导致程序出错 字符串为空值的时候会传入一个字符串类型的undefined,解决办法就是当要传入的对象为空值的时候,传一个””就行 android调用相机和相册android调用相机拍照实现点击按钮开始调用相机拍照,并且返回拍照的照片 mainactivity.java1234567891011121314151617181920212223242526272829303132333435363738394041424344454647public class MainActivity extends Activity { private ImageView imageView; private Uri imageuri; private static final int TAKE_PHOTO=1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView)findViewById(R.id.picture); } public void one(View view) { File outputImage = new File(getExternalCacheDir(),"output_image.jpg"); if(outputImage.exists()){ outputImage.delete(); } try { outputImage.createNewFile(); } catch (IOException e) { e.printStackTrace(); } if(Build.VERSION.SDK_INT>=24){ imageuri = FileProvider.getUriForFile(MainActivity.this,"com.example.cameraalbumtest.fileprovider",outputImage); }else{ imageuri = Uri.fromFile(outputImage); } Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); intent.putExtra(MediaStore.EXTRA_OUTPUT,imageuri); startActivityForResult(intent,TAKE_PHOTO); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch(requestCode){ case TAKE_PHOTO: if(resultCode==RESULT_OK){ try{ Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageuri)); imageView.setImageBitmap(bitmap); }catch(FileNotFoundException e){ e.printStackTrace(); } } } }} 布局文件activity_main.xml123456789101112131415161718<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:text="one" android:onClick="one" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:id="@+id/picture" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout> 在sdk24以后还需要需要一个内容提供器,现在清单文件下注册123456789101112131415161718192021<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.xwmdream.myapplication"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application ....... <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.exaample.cameraalibumtest.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/> </provider> </application></manifest> res/xml/file_paths.xml1234<?xml version="1.0" encoding="utf-8"?><pahts xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="my_images" path=""/></pahts> android调用相册 MainActivity.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104public class MainActivity extends Activity { private ImageView imageView; private Uri imageuri; private static final int CHOOSE_PHOTO = 2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView) findViewById(R.id.picture); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case CHOOSE_PHOTO: if(resultCode==RESULT_OK){ if(Build.VERSION.SDK_INT>=19){ handleImageOnKitKat(data); }else{ handleImageBeforeKitKat(data); } } } } private void handleImageBeforeKitKat(Intent data) { Uri uri = data.getData(); String imagePath = getImagePath(uri,null); displayImage(imagePath); } private void handleImageOnKitKat(Intent data) { String imagePath = null; Uri uri = data.getData(); if(DocumentsContract.isDocumentUri(this,uri)){ String docId = DocumentsContract.getDocumentId(uri); if("com.android.providers.media.documents".equals(uri.getAuthority())){ String id = docId.split(":")[1]; String selection = MediaStore.Images.Media._ID+"="+id; imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection); }else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){ Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId)); imagePath = getImagePath(contentUri,null); } }else if("content".equalsIgnoreCase(uri.getScheme())){ imagePath = getImagePath(uri,null); }else if("file".equalsIgnoreCase(uri.getScheme())){ imagePath = uri.getPath(); } displayImage(imagePath); } private void displayImage(String imagePath) { if(imagePath!=null){ Bitmap bitmap = BitmapFactory.decodeFile(imagePath); imageView.setImageBitmap(bitmap); }else{ Toast.makeText(this,"打开图片错误",Toast.LENGTH_SHORT).show(); } } private String getImagePath(Uri uri, String selection) { String path = null; Cursor cursor = getContentResolver().query(uri,null,selection,null,null); if(cursor != null){ if(cursor.moveToFirst()){ path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); } cursor.close(); } return path; } public void two(View view) { if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); } else { openAlbum(); } } private void openAlbum() { Intent intent = new Intent("android.intent.action.GET_CONTENT"); intent.setType("image/*"); startActivityForResult(intent, CHOOSE_PHOTO); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case 1: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openAlbum(); } else { Toast.makeText(this, "没有权限", Toast.LENGTH_SHORT).show(); } break; default: break; } }} 布局文件xml123456789101112131415161718<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:onClick="two" android:text="two" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:id="@+id/picture" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout> 其中涉及到针对sdk大于19的各个方法验证 还有动态申请权限等问题 当然如果项目中用到的话还需要对于图片文件压缩,否则会内存泄漏 android通知的使用普通通知123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475//创建通知管理NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);//构建一个通知NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"通知分组");//点击通知相应的intentIntent mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://xwmdream.cn"));PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0);builder.setContentIntent(pendingIntent);//在状态栏中的小图标,只显示alphabuilder.setSmallIcon(R.drawable.ic_launcher_background);//通知标题builder.setContentTitle("标题");//点击了此标题是否自动划掉此通知builder.setAutoCancel(true);//通知里是否显示时间builder.setShowWhen(true);//什么时候发送通知,系统毫秒数builder.setWhen(System.currentTimeMillis());//状态栏划过的摘要builder.setTicker("状态栏显示的摘要");//是否进行中,如果是进行中,则没法清除builder.setOngoing(false);//添加一些默认值/*NotificationCompat.DEFAULT_SOUND 添加默认声音提醒NotificationCompat.DEFAULT_VIBRATE 添加默认震动提醒NotificationCompat.DEFAULT_LIGHTS 添加默认呼吸灯提醒NotificationCompat.DEFAULT_ALL 同时添加以上三种默认提醒* */builder.setDefaults(NotificationCompat.DEFAULT_ALL);//设置震动停止1000,震100,停400,震500,如果设置了默认值,这个就不管用了builder.setVibrate(new long[]{1000, 100, 400, 500});/*优先级 描述NotificationCompat.PRIORITY_MAX 重要而紧急的通知,通知用户这个事件是时间上紧迫的或者需要立即处理的。NotificationCompat.PRIORITY_HIGH 高优先级用于重要的通信内容,例如短消息或者聊天,这些都是对用户来说比较有兴趣的NotificationCompat.PRIORITY_DEFAULT 默认优先级用于没有特殊优先级分类的通知NotificationCompat.PRIORITY_LOW 低优先级可以通知用户但又不是很紧急的事件。只显示状态栏图标NotificationCompat.PRIORITY_MIN 用于后台消息 (例如天气或者位置信息)。只有用户下拉通知抽屉才能看到内容,不会通知和响铃*/builder.setPriority(NotificationCompat.PRIORITY_HIGH);/*锁屏时显示,好像没用setVisibility() 方法共有三个选值:1.VISIBILITY_PRIVATE : 显示基本信息,如通知的图标,但隐藏通知的全部内容;2.VISIBILITY_PUBLIC : 显示通知的全部内容;3.VISIBILITY_SECRET : 不显示任何内容,包括图标。*/builder.setVisibility(NotificationCompat.VISIBILITY_SECRET);//下面是显示进度条int max = 100; // 进度最大值int progress = 50; // 当前进度boolean indeterminate = false; // 是否是不明确的进度条,为true就是模糊的进度条,就那种花纹一直动的进度条builder.setProgress(max, progress, indeterminate);//消息中的内容builder.setContentText("小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容");//设置长文本,默认的是一行,多余的是省略号,这个是有多少就显示多少,如果启用这个,就不会显示小内容builder.setStyle(new NotificationCompat.BigTextStyle().bigText("长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容"));//显示大图,如果设置这个,会显示一行小内容,然后显示大图片Bitmap aBigBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);builder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(aBigBitmap));//构建一个通知实体Notification notification = builder.build();//通过消息管理器发送,第一个参数是消息id参数,要不一样,如果一样就视为同一个通知不会重复发送notificationManager.notify(num++, notification);//管理器取消的通知,其中的id是上面发送里面的id//notificationManager.cancel(0);//清除所有通知//notificationManager.cancelAll(); 自定义通知先创建布局文件 message.xml12345678910111213141516171819202122232425262728<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <ImageView android:id="@+id/iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_launcher"/> <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:text="仗剑走天涯"/> <Button android:id="@+id/btn1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="播放"/> <Button android:id="@+id/btn2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下一首"/></LinearLayout> Java代码1234567891011121314151617181920NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"通知分组");builder.setSmallIcon(R.mipmap.ic_launcher);//加载布局RemoteViews rv = new RemoteViews(getPackageName(), R.layout.message);rv.setTextViewText(R.id.tv, "泡沫");//修改自定义View中的歌名//通过pendingIntent发送广播的方式来设置监听事件Intent button1I = new Intent("btn1");PendingIntent button1PI = PendingIntent.getBroadcast(this, 0, button1I, 0);rv.setOnClickPendingIntent(R.id.btn1,button1PI);//修改自定义View中的图片(两种方法)// rv.setImageViewResource(R.id.iv,R.mipmap.ic_launcher);rv.setImageViewBitmap(R.id.iv, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));builder.setContent(rv);Notification notification = builder.build();notificationManager.notify(num++, notification); android内容提供者以及观察者内容提供者内容提供器作为Android四大组建之一.感觉没啥太大用途. 感觉就是一个应用程序创建一个可以被别的程序访问的数据库 访问其他程序中的数据如果想要访问别的内容提供器中的共享数据,就要借助ContentResolver类,可以通过Context中的getContentResolver方法得到,它提供了一些列对数据的crud操作,操作和sqlitedatabase很相似,但是ContentResolver没有库和表,而是用一个uri代替,如’content://com.xxx.xxxx.xxx/aaa’表示访问包名是com.xxx.xxxx.xxx程序的aaa表,然后进行curd操作 查询:123456getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);//uri:看上面//projection 指定的列名,相当于select的列名//selection 查询条件,相当于where语句//selectionArgs 查询条件中?的参数//sortOrder 排序条件 返回的是一个cursor结果集,和sqlite用法一样 查询手机上所有的联系人1234567cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);if(cursor!=null){ while(cursor.moveToNext()){ Log.d(TAG, "onCreate: "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))); Log.d(TAG, "onCreate: "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))); }} 其中的参数都是系统预设的 查询手机上电话为10086的联系人的名字 1234567891011121314Cursor cursor = null;try{ cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,new String[]{ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME},ContactsContract.CommonDataKinds.Phone.NUMBER+" = ?",new String[]{"10086"},null); if(cursor!=null){ //找到了这个联系人 while(cursor.moveToNext()){ Log.d(TAG, "onCreate: "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))); } }else{ //没找到这个联系人 }}catch (Exception e){ e.printStackTrace();} 插入联系人(参考) 1234567891011121314151617181920212223242526272829303132333435363738394041addContact("zhangphil", "12345678901","[email protected]");// 一个添加联系人信息的例子public void addContact(String name, String phoneNumber,String email) { // 创建一个空的ContentValues ContentValues values = new ContentValues(); // 向RawContacts.CONTENT_URI空值插入, // 先获取Android系统返回的rawContactId // 后面要基于此id插入值 Uri rawContactUri = getContentResolver().insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); values.clear(); values.put(Data.RAW_CONTACT_ID, rawContactId); // 内容类型 values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); // 联系人名字 values.put(StructuredName.GIVEN_NAME, name); // 向联系人URI添加联系人名字 getContentResolver().insert(Data.CONTENT_URI, values); values.clear(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); // 联系人的电话号码 values.put(Phone.NUMBER, phoneNumber); // 电话类型 values.put(Phone.TYPE, Phone.TYPE_MOBILE); // 向联系人电话号码URI添加电话号码 getContentResolver().insert(Data.CONTENT_URI, values); values.clear(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); // 联系人的Email地址 values.put(Email.DATA, email); // 电子邮件的类型 values.put(Email.TYPE, Email.TYPE_HOME); // 向联系人Email URI添加Email数据 getContentResolver().insert(Data.CONTENT_URI, values);} 内容观察者内容观察者就是要观察一个uri,当这个uri下内容发生改变的时候执行的操作,类似于数据库的触发器内容观察者要继承ContentObserver类,有一个handler参数用于多线程更新UI主要方法是onChange(boolean self) 这个方法是当观察的uri发生改变的时候调用的方法 注册内容观察者1getContentResolver().registerContentObserver(uri,notifyForDescendents,ContentResolver); uri是要监听的地址uri notifyForDescendents表示是否模糊匹配,例如一个uri为aa.bb.cc/dd,当notifyForDescendents为true的时候即模糊匹配,此时aa.bb.cc/ff发生变化时也会触发此观察者 为false时只有aa.bb.cc/dd发生变化时触发 ContentResolver是内容观察者对象 注销内容观察者1getContentResolver().unregisterContentObserver(ContentResolver); 创建内容观察者1234567891011public class MyContentResolver extends ContentObserver { public MyContentResolver(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); Log.d("onChange: ","发生了变化"); }} android的fragment(碎片)OCTOBER 1, 2018个人感觉碎片就是把activity分成一片一片的.和activity一样有生命周期 静态创建碎片效果图: 整个界面分为上下两部分,是两个fragment 上面的fragment(title_fragment)先创建布局 fragment_title.xml123456789101112131415161718192021<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="horizontal" android:background="@color/colorAccent" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_back" android:text="返回" android:layout_centerVertical="true" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_title" android:text="我是标题" android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" /></RelativeLayout> 创建titlefragment类继承v4的Fragment12345678910111213141516171819202122232425262728293031import android.support.v4.app.Fragment;public class TitleFragment extends Fragment { /** * 表示fragment第一次创建绘制用户界面时系统回调的方法 * @param inflater 表示布局填充器或者加载器,将xml文件转化成view对象 * @param container 表示当前fragment出入activity的布局视图对象 * @param savedInstanceState 表示存储上一个fragment的信息 * @return view表示当前加载的fragment视图,如果fragment不 提供视图可以返回null */ @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_title,container,false); TextView tv1 = view.findViewById(R.id.tv_back); tv1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getActivity(),"返回",Toast.LENGTH_SHORT).show(); } }); TextView tv2 = view.findViewById(R.id.tv_title); tv2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getActivity(),"标题",Toast.LENGTH_SHORT).show(); } }); return view; }} 重写了oncreateView方法,是第一次绘制用户界面调用的方法,里面使用布局填充器加载了xml布局,得到一个View对象,然后通过view.findviewbyid方法获取到布局上的空间,然后注册点击事件 下面的ContentFragmentxml布局 fragment_content.xml123456789101112<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="我是内容哈哈哈哈" /></LinearLayout> 布局很简单,一个tv,一句话 同样创建他的Fragment类 ContentFragment.java12345678910import android.support.v4.app.Fragment;public class ContentFragment extends Fragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_content,container,false); return view; }} activity加载fragment activity_main.xml12345678910111213141516171819202122<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <fragment android:id="@+id/fragment_title" android:name="cn.xwmdream.fragment.TitleFragment" android:layout_width="match_parent" android:layout_weight="1" android:layout_height="0dp"/> <fragment android:id="@+id/fragment_content" android:name="cn.xwmdream.fragment.ContentFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="9" /></LinearLayout> 线性布局,上下两个fragment,name是要加载fragment的路径,注意,一定要给每一个fragment创建id,否则报错 MainActivity.java还是常规操作,不解释动态引入fragment在activity中 创建fragment的管理器对象 获取fragment的事务对象并开启(事务具有一致性) 调用事务的动态方法,动态添加移除替换fragment 提交事务 修改activity_main.xml 把原来的fragment换成linearlayout容器,这些容器一会动态添加fragment123456789101112131415161718192021<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <LinearLayout android:orientation="horizontal" android:id="@+id/ll_title" android:layout_weight="1" android:layout_width="match_parent" android:layout_height="0dp"></LinearLayout> <LinearLayout android:orientation="horizontal" android:id="@+id/ll_content" android:layout_weight="1" android:layout_width="match_parent" android:layout_height="0dp"></LinearLayout></LinearLayout> MainActivity.java1234567891011121314151617181920//1.创建fragment管理器对象FragmentManager manager = getSupportFragmentManager();//2.获取fragment的事务对象并且开启事务FragmentTransaction transaction = manager.beginTransaction();//3.调用事务操作fragment,//add方法,第一个参数是把碎片添加到哪个布局,第二个是碎片对象TitleFragment titleFragment = new TitleFragment();transaction.add(R.id.ll_title,titleFragment);transaction.add(R.id.ll_content,new ContentFragment());//remove(Fragment)删除一个fragment对象,//transaction.remove(titleFragment);//replace(布局id,新的fragment对象) 把某个布局中的fragment替换成新的//transaction.replace(R.id.ll_content,new TitleFragment());//4.提交事务transaction.commit(); 注意动态引用的viewgroup里面不能有子元素 引用过add后如果不引用remove是不能再次add replace相当于remove+add 生命周期生命周期主要函数onattach当碎片和活动建立起关联的时候调用 onCreateView()第一次为碎片创建视图(加载布局)的时候调用,控件关联写在这里 onActivityCreated()确保碎片与相关活动一定创建完毕的时候调用 onDestoryView当碎片与关联的试图移除的时候调用 onDetach当碎片和活动解除关联的时候调用 静态生命周期(f代表fragment,a代表activity) 启动:f.onAttach->f.onCreate->f.onCreateView->a.onCreate->f.onActivityCreated->f.onStart->a.onStart->a.onResume->f.onResume 暂停:f.onPause->a.onPause->f.onStop->a.onStop 重新开始:a.onRestart->f.onStart->a.onStart->a.onResume->f.onResume 销毁:(暂停)->f.onDestroyView->f.onDestroy->f.onDetach->a.onDestroy 动态添加生命周期在oncreate函数中添加:a.onCreate>f.onAttach->f.onCreate->f.onCreateView->f.onActivityCreated->f.onStart->a.onStart->a.onResume->f.onResume 在事件中添加:f.onAttach->f.onCreate->f.onCreateView->f.onActivityCreated->f.onStart->f.onResume 暂停,重新开始,销毁同上 心得:感觉注册就是静态注册,看在哪个方法中注册,如果在系统回调函数中注册,会先打印activity回掉前的方法,和静态的相互顺序一样 fragment交互中的生命周期:函数中的生命周期add:当使用add函数启动另一个fragment,只执行第二个fragment的启动周期 此时的销毁周期f同时表示两个fragment的生命周期,并且老的fragment会先执行 replace:(1.表示原来的fragment,2表示新的fragment):2.onAttach->2.onCreate->1.onPause->1.onStop->1.onDestroyView->1.onDestroy->1.onDetach->2.onCreateView->2.onActivityCreated->2.onStart->2.onResume remove:销毁中所有fragment的周期 返回栈中的生命周期:压入栈:2.onAttach->2.onCreate->1.onPause->1.onStop->1.onDestroyView->2.onCreateView->2.onActivityCreated->2.onStart->2.onResume 类似replace,,区别:就是第一个销毁状态只执行到onDestroyView,不执行onDestroy和onDetach 弹出栈:(2表示栈顶对象,1表示底下对象):2.onPause->2.onStop->2.onDestroyView->2.onDestroy->2.onDetach->1.onCreateView->1.onActivityCreated->1.onActivityCreated->1.onResume 传值activity向fragment传值:activity通过id获取fragment对象:1xFragment xfragment = (xFragment)getFragmentManager().findFragmentById(R.id.xfragment); activity.java 12345678910111213141516//创建管理器FragmentManager manager = getSupportFragmentManager();//开启事务FragmentTransaction transaction = manager.beginTransaction();//创建碎片对象TitleFragment1 titleFragment = new TitleFragment1();//创建Bundle对象用于保存值Bundle bundle = new Bundle();//保存值bundle.putString("hello","world");//把值保存到fragment对象中titleFragment.setArguments(bundle);//添加transaction.add(R.id.fl, titleFragment);//提交transaction.commit(); fragment.java 12345Bundle bundle = getArguments();//不为空获取值if(bundle!=null){ bundle.getString("hello");} fragment向activity传值获取activity对象:1getActivity(); 传值思想: 创建一个接口,接口里面实现一个有参数值的方法,让activity实现这个接口,并重写接口那个方法 在fragment的onAttach方法中实例化那个接口,实例化对象就是getActivity 然后再需要传值的时候调用接口的那个方法就能把值传到activity重写的方法里 fragment向fragment传值1、在AFragment中通过getFragmentManager.findFragmentById(int id)获取BFragment实例,调用BFragment的方法实现传值 2、在AFragment中通过getFragmentManager.findFragmentById(int id).getView().findViewById(int id)获取到BFragment中的view对象,对控件直接进行传值 3、在AFragment中直接getActivity().findViewById(int id)获取属于当前Activity的BFragment中的view对象 自适应(平板和手机的适配)Android的限定符: 名称 像素密度范围 图片大小 mdpi 120dpi~160dpi 48×48px hdpi 160dpi~240dpi 72×72px xhdpi 240dpi~320dpi 96×96px xxhdpi 320dpi~480dpi 144×144px xxxhdpi 480dpi~640dpi 192×192px small 小屏幕 normal 基准屏幕 large 大屏幕 xlarge 超大屏幕 land 横向屏幕 port 纵向屏幕 long 比标准屏幕宽高比明显的高或者宽的这样屏幕 notlong 和标准屏幕配置一样的屏幕宽高比 可以创建不同屏幕的布局进行写一些逻辑.比如layout-xhdpi,表示像素密度在240dpi-320dpi使用的布局文件 在Java代码中通过fandviewbyid!=null判断是否加载某些不同布局的控件,写不同的逻辑 还可以使用最小宽度限定符,layout-sw600dp sw600dp表示small width 600dp,表示最小600dp时候使用的布局,及宽度大于等于600dp调用的布局.这里的宽度指的是长宽比较小的那个值 android滑动列表RecyclerView简介RecyclerView是Android官方推荐使用的滚动控件 我认为的recyclerview是通过一个布局实现整体的布局,然后通过一个适配器实现子项布局点击等一些操作 写一个水果列表. 首先添加依赖 1'com.android.support:recyclerview-v7:28.0.0' 创建一个主activity,并编写布局,就是创建一个recyclerview,然后长宽match_parentmain_layout.xml 123456789101112131415<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"/></LinearLayout> 创建一个水果列表的子布局,就是列表中单个元素的布局方式fruit_item.xml 123456789101112131415<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/fruit_image" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/fruit_name" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout> 写一个水果列表子布局的类Fruit.java 12345678910111213141516171819202122232425public class Fruit { private String name; private int imageId; public Fruit(String name, int imageId) { this.name = name; this.imageId = imageId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getImageId() { return imageId; } public void setImageId(int imageId) { this.imageId = imageId; }} 开始写适配器,并创建点击事件 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556import android.support.annotation.NonNull;import android.support.v7.widget.RecyclerView;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.TextView;import android.widget.Toast;import java.util.List;public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{ private List<Fruit> mFruitList; @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int type) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.fruit_item,viewGroup,false); final ViewHolder holder = new ViewHolder(view); holder.fruitView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int position = holder.getAdapterPosition(); Fruit fruit = mFruitList.get(position); Toast.makeText(v.getContext(),"you click view "+fruit.getName(),Toast.LENGTH_SHORT ).show(); } }); return holder; } @Override public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) { Fruit fruit = mFruitList.get(i); viewHolder.fruitImage.setImageResource(fruit.getImageId()); viewHolder.fruitName.setText(fruit.getName()); } @Override public int getItemCount() { return mFruitList.size(); } static class ViewHolder extends RecyclerView.ViewHolder{ View fruitView; ImageView fruitImage; TextView fruitName; public ViewHolder(@NonNull View itemView) { super(itemView); fruitView = itemView; fruitImage = (ImageView)itemView.findViewById(R.id.fruit_image); fruitName = (TextView)itemView.findViewById(R.id.fruit_name); } } public FruitAdapter(List<Fruit> fruitList){ mFruitList = fruitList; }} 其中适配器实现了一个ViewHolder的内部类,里面是适配器对应的子控件,view是整个子控件,imageview是图片控件,textview是后面的文字控件,可以在适配器的onCreateViewHolder方法中为他们创建对应的点击事件. mfruitList是对应整个滑动列表的元素列表 MainActivity.java,自备图片MainActivity.java 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity { //水果元素列表 private List<Fruit> fruitList = new ArrayList<Fruit>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化水果列表 initFruits(); //绑定recycleview RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); //加载一个布局 LinearLayoutManager layoutManager = new LinearLayoutManager(this); //把布局弄成横向滑动的 //layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); //把布局弄成纵向3列瀑布流形式 //StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL); //让recycleview加载创建的布局 recyclerView.setLayoutManager(layoutManager); //创建适配器,传入水果元素列表 FruitAdapter adapter = new FruitAdapter(fruitList); //加载适配器 recyclerView.setAdapter(adapter); } //初始化水果列表,传入水果名字和图片id private void initFruits() { for (int i = 0; i < 2; i++) { fruitList.add(new Fruit("菠萝", R.drawable.boluo)); fruitList.add(new Fruit("草莓", R.drawable.caomei)); fruitList.add(new Fruit("橙子", R.drawable.chengzi)); fruitList.add(new Fruit("荔枝", R.drawable.lizhi)); fruitList.add(new Fruit("龙眼", R.drawable.longyan)); fruitList.add(new Fruit("芒果", R.drawable.mangguo)); fruitList.add(new Fruit("猕猴桃", R.drawable.mihoutao)); fruitList.add(new Fruit("苹果", R.drawable.pingguo)); fruitList.add(new Fruit("葡萄", R.drawable.puto)); fruitList.add(new Fruit("圣女果", R.drawable.shengnvguo)); fruitList.add(new Fruit("水蜜桃", R.drawable.shuimitao)); fruitList.add(new Fruit("提子", R.drawable.tizi)); fruitList.add(new Fruit("香蕉", R.drawable.xiangjiao)); fruitList.add(new Fruit("西瓜", R.drawable.xigua)); fruitList.add(new Fruit("西红柿", R.drawable.xihongshi)); fruitList.add(new Fruit("杨桃", R.drawable.yangtao)); fruitList.add(new Fruit("樱桃", R.drawable.yingtao)); } }} 加载的时候要加载一个布局,上面写的是默认纵向列表布局,可以设置成横向,还有瀑布流形式,不过设置的时候一定要注意fruit_item.xml中间的布局变化 如果设置成横向滑动就不要把宽度设置成父类最大 如果设置成纵向瀑布流形式,要把最外层的width设置成父类最宽 更新数据更新数据的方法是更新fruitList这个数组,然后再执行对应的适配器方法,可以把适配器弄成成员变量 刷新全部可见的item,notifyDataSetChanged() 刷新指定item,notifyItemChanged(int) 从指定位置开始刷新指定个item,notifyItemRangeChanged(int,int) 插入、移动或者删除一个并自动刷新,notifyItemInserted(int)、notifyItemMoved(int)、notifyItemRemoved(int) 局部刷新,notifyItemChanged(int, Object) 列表滚动到制定项recyclerView.scrollToPosition(int); 会让第指定个项目出现在屏幕上,只是完全出现在屏幕上,不是屏幕第一个 高级用法RecyclerView实现滑动删除和拖拽功能总结和分析几种判断RecyclerView到达底部的方法 Android使用SharePreferences存储数据SEPTEMBER 25, 2018 简介SharedPreferences存储类用来存储一些键值对,比如用户的设置信息 可以存储五大基本类型String,Float,Long,int,boolean和字符串集合StringSet,其中StringSet是一个字符串的集合,Set 创建12345678910111213Set set = new HashSet<String>();set.add("one");set.add("two");SharedPreferences sp = getSharedPreferences("data",MODE_PRIVATE);SharedPreferences.Editor editor= sp.edit();editor.putString("name","xwm");editor.putInt("int",500);editor.putBoolean("bool",true);editor.putLong("long",12l);editor.putFloat("float", (float) 3.1415926);editor.putStringSet("StringSet",set);editor.commit(); 先创建一个sp对象,然后获取到编辑器editor,在editor中保存数据,保存完以后要进行commit,否则不能保存; 他会把数据保存到data/data//shared_prefs文件夹中的xml文件里,如以上代码会保存成123456789101112<?xml version='1.0' encoding='utf-8' standalone='yes' ?><map> <string name="name">xwm</string> <float name="float" value="3.1415925" /> <boolean name="bool" value="true" /> <long name="long" value="12" /> <set name="StringSet"> <string>one</string> <string>two</string> </set> <int name="int" value="500" /></map> 获取数据 123456789SharedPreferences spa = getSharedPreferences("data", MODE_PRIVATE);//获取五大基本类型Log.d(TAG, "String: "+spa.getString("name","空"));Log.d(TAG, "int"+spa.getInt("int",-1));Log.d(TAG, "bool"+spa.getBoolean("bool",false));Log.d(TAG, "Long"+spa.getLong("long",0l));Log.d(TAG, "float"+spa.getFloat("float",(float)0.0));//获取StringSet对象Log.d(TAG, "StringSet"+spa.getStringSet("StringSet",null)); String: xwmint500booltrueLong12float3.1415925StringSet[two, one] 先创建一个SharePreferences对象,然后可以通过get方法通过key获取到各类型的数据,get方法有两个参数,第一个是要取出值的key,第二个值是如果这个key没有对应的值要返回的默认值 取出方法还有一个getAll方法,是将所有的键值对弄成一个map返回回来 提交方法sharepreferences有两个提交方法,一个是上面用到的commit,还有一个apply这两个方法的区别在于: apply没有返回值而commit返回boolean表明修改是否提交成功 apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。 apply方法不会提示任何失败的提示。由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。 参考:https://blog.csdn.net/jake9602/article/details/18414841 android广播参考:Android四大组件:BroadcastReceiver史上最全面解析安卓独有的广播机制,就像生活中的广播一样,一个发射广播,很多人能同时接受到广播内容 实现原理采用的模型 Android中的广播使用了设计模式中的观察者模式:基于消息的发布 / 订阅事件模型 因此,Android将广播的发送者 和 接收者 解耦,使得系统方便集成,更易扩展 模型讲解 模型中有3个角色: 消息订阅者(广播接收者) 消息发布者(广播发布者) 消息中心(AMS,即Activity Manager Service) 广播接收者静态注册 静态注册是指在清单文件中注册 在as中new一个other选择Broadcast Receiver就能创建一个广播接收者,两个复选框,代表能不能由系统实例化和接收程序之外的广播 as会自动在清单文件注册,打开清单文件可以看到12345678910111213141516171819202122<receiver android:enabled=["true" | "false"]//此broadcastReceiver能否接收其他App的发出的广播//默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false android:exported=["true" | "false"] android:icon="drawable resource" android:label="string resource"//继承BroadcastReceiver子类的类名 android:name=".mBroadcastReceiver"//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收; android:permission="string"//BroadcastReceiver运行所处的进程//默认为app的进程,可以指定独立的进程//注:Android四大基本组件都可以通过此属性指定自己的独立进程 android:process="string" >//用于指定此广播接收器将接收的广播类型//本示例中给出的是用于接收网络状态改变时发出的广播 <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> </intent-filter></receiver> 注册实例12345678<receiver //此广播接收者类是mBroadcastReceiver android:name=".mBroadcastReceiver" > //用于接收网络状态改变时发出的广播 <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> </intent-filter></receiver> +action中的值是要接收的广播名字,就像现实广播中的’fm100.8’一样,是要接收广播的名字,当然可以是系统广播的名字,如android.intent.action.NEW_OUTGOING_CALL是系统打电话时发送的广播的名字 当此 App首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中。 动态注册动态注册是只在Java代码中进行注册,1234567891011121314@Overrideprotected void onResume(){ TwoReceiver twoReceiver = new TwoReceiver(); //设置要广播接收的名字 IntentFilter intentFilter = new IntentFilter(android.net.conn.CONNECTIVITY_CHANGE); registerReceiver(twoReceiver,intentFilter);}//记得要在onPause方法中释放注册的广播接收者@Overrideprotected void onPause() { super.onPause(); //释放注册的广播接收者 unregisterReceiver(twoReceiver);} 动态广播最好在Activity 的 onResume()注册、onPause()注销。原因: 对于动态广播,有注册就必然得有注销,否则会导致内存泄露 在onResume()注册、onPause()注销是因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。 广播接收者重写了onReceive方法实现接收广播的逻辑 可以通过getResultData方法获取有序广播中的数据,通过setResult设置回数据12345@Overridepublic void onReceive(Context context, Intent intent) { String result = getResultData(); Log.d("TwoReceiver","onReceivee: "+result);} 发送广播 广播分为有序广播和无序广播 无序广播一次发送,所有人同时接收 特点: 所有接收器没有先后顺序,接受顺序不确定,但是都能接受到 通过sendBroadcast(intent)方法来发送,它是完全异步的。 效率高 无法使用setResult系列、getResult系列及abort(中止)系列API 有序广播一次发送,接收有顺序 特点: 有接收先后顺序,根据priority的值来判定先后顺序,值越大优先级越高 静态注册接收器:12345678<receiver android:name=".OneReceiver" android:enabled="true" android:exported="true"> <intent-filter android:priority="1000"> <action android:name="bmbm" /> </intent-filter></receiver> 动态注册接收器:12345TwoReceiver twoReceiver = new TwoReceiver();IntentFilter intentFilter = new IntentFilter("bmbm");//设置代表优先级顺序的priority值intentFilter.setPriority(1000);registerReceiver(twoReceiver,intentFilter); 当两个接收者优先级一样时或者都没有优先级的时候,那么按照清单文件上注册的先后顺序先后收到广播 当一个静态注册和另一个动态注册的接受者优先级相同或者都没有优先级的时候,那么动态注册会先收到广播 通过sendOrderedBroadcast(intent,null);发送有序广播 可以使用setResult系列、getResult系列及abort(中止)系列API 当优先级高的接受者使用abortBroadcast()方法后,那么比他优先级低的广播接收者则接收不到广播 发送一个广播123456789Intent intent = new Intent();//指定广播的名字,类比现实中广播的名字,如'fm100.8'intent.setAction("hello");//发送一个有序广播sendOrderedBroadcast(intent,null);//此时接收名为'hello'的广播接受者会按照优先级顺序接受到有序广播sendBroadcast(intent)//此时接收名为'hello'的广播都会同时无序收到广播 使用本地广播本地广播,就是自己程序发的只有自己能收到,别的收不到 先创建广播接收器Myreceiver.java1234567public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "收到了", Toast.LENGTH_SHORT).show(); }} MainActivity.java123456789101112131415161718192021//注册应用内广播接收器//步骤1:实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceivermBroadcastReceiver = new mBroadcastReceiver();IntentFilter intentFilter = new IntentFilter();//步骤2:实例化LocalBroadcastManager的实例localBroadcastManager = LocalBroadcastManager.getInstance(this);//步骤3:设置接收广播的类型intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);//步骤4:调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);//取消注册应用内广播接收器localBroadcastManager.unregisterReceiver(mBroadcastReceiver);//发送应用内广播Intent intent = new Intent();intent.setAction(BROADCAST_ACTION);localBroadcastManager.sendBroadcast(intent); 特别注意 对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的: 对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context;对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context。对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context; 作者:Carson_Ho链接:https://www.jianshu.com/p/ca3d87a4cdf3來源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。 android activity的生命周期和启动模式SEPTEMBER 23, 2018 简介:Android生命周期分为七个函数:onCreate,onStart,onResume,onPause,onStop,onRestart,onDestory 单个activity的生命周期Android官方文档给的生命周期示例图: 1、Activity的启动 onCreate->onStart->onResume->处于可见状态 2、Activity的不可见 onPause->onStop 3、Activity的重新可见 onRestart->onStart->onResume 4、Activity的销毁 onPause->onStop->onDestory 一个Activity在创建和显示的时候,先调用的是onCreate, onStart, onResume,方法 按下back键的时候会调用:onPause,onStop,onDestory方法 按下home键会调用:onPause onStop 按下home键后再打开程序:onrestart onstart onresume 方法 说明 是否能事后终止? 后接 onCreate() 首次创建 Activity 时调用。 您应该在此方法中执行所有正常的静态设置 — 创建视图、将数据绑定到列表等等。 系统向此方法传递一个 Bundle 对象,其中包含 Activity 的上一状态,不过前提是捕获了该状态(请参阅后文的保存 Activity 状态)。始终后接 onStart()。 否 onStart() onRestart|在 Activity 已停止并即将再次启动前调用。始终后接 onStart()|否|onStart()onStart()|在 Activity 即将对用户可见之前调用。如果 Activity 转入前台,则后接 onResume(),如果 Activity 转入隐藏状态,则后接 onStop()。|否|onResume()或onStop()onResume()|在 Activity 即将开始与用户进行交互之前调用。 此时,Activity 处于 Activity 堆栈的顶层,并具有用户输入焦点。始终后接 onPause()。|否|onPause()onPause()|当系统即将开始继续另一个 Activity 时调用。 此方法通常用于确认对持久性数据的未保存更改、停止动画以及其他可能消耗 CPU 的内容,诸如此类。 它应该非常迅速地执行所需操作,因为它返回后,下一个 Activity 才能继续执行。如果 Activity 返回前台,则后接 onResume(),如果 Activity 转入对用户不可见状态,则后接 onStop()。|是|onResume()或onStop()onStop()|在 Activity 对用户不再可见时调用。如果 Activity 被销毁,或另一个 Activity(一个现有 Activity 或新 Activity)继续执行并将其覆盖,就可能发生这种情况。如果 Activity 恢复与用户的交互,则后接 onRestart(),如果 Activity 被销毁,则后接 onDestroy()。|是|onRestart()或onDestroy()onDestroy()|在 Activity 被销毁前调用。这是 Activity 将收到的最后调用。 当 Activity 结束(有人对 Activity 调用了 finish()),或系统为节省空间而暂时销毁该 Activity 实例时,可能会调用它。 您可以通过 isFinishing() 方法区分这两种情形。|是|无 两个activity跳转时候的生命周期:当第1个activity去访问第二个activity的时候会先调用1.onPause->2.onCreate->2.onStart->2.onResume->1.onStop 当在第二个activity结束的时候会调用:2.onPause->1.onRestart->1.onStart->onResume->2.onStop->2.Destory 注意如果第二个activity类型是一个dialog类型的activity,那么第一个activity只会执行onPause,启动时候直接启动onResume,因为dialog类型的activity没有完全遮住下面的activity,所以只是暂停,并没有停止 规律每次跳转到第二个activity的时候会先调用第一个activity的onpause,然后会调用即将显示activity的要显示的周期,然后再执行第一个activity的剩下的操作..原因是因为假设要访问的activity不能正确执行发生崩溃,能及时回到之前的activity,不至于之前界面的也被销毁了而执行不了,所以将之前的stop和destory方法第2个activity显示之后. 横竖屏的生命周期基本生命周期:竖.Pause->竖.Stop->竖.Destroy【竖屏销毁,并开始创建横屏】 横.Create->横.Start->横.Resume【横屏显示】 发生横竖屏切换的时候会先销毁之前的activity,然后重新创建横屏的activity,所以有一些数据要保存,此时可以重写onSaveInstanceState方法保存数据12345@Overridepublic void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("name","value");} 然后在重写onRestoreInstanceState方法取出保存的数据1234567@Overrideprotected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if(savedInstanceState!=null){ text.setText(savedInstanceState.getString("name")); }} 具体生命周期:竖屏销毁时候:onPause->onSaveInstanceState->onStop->onDestroy 横屏创建的时候onCreate->onStart->onRestoreInstanceState->onResume 另外: 当重写了onSaveInstanceState发现,如果是要打开别的界面或者home暂停的时候也会调用该方法,此时如果返回此activity不会调用onRestoreInstanceState方法如果是单击back返回,也就是界面要销毁的时候则不会调用. 横屏可以重写布局,在res下新建一个layout-land的资源文件夹,里面创建同名的布局文件即可 我测试用的Android5.0手机,创建了个edittext,发现横屏的时候系统会给保存之前的数据,如果想要横屏修改这个数据,在oncreate中是不能修改的,必须在onRestoreInstanceState中修改,因为系统会在onRestoreInstanceState方法中把之前的数据写回来,,所以要在它之后再修改值才会生效. oncreate方法中也有个Bundle方法,也是保存之前的数据的,和onRestoreInstanceState中的用法一样 当然也可以设置禁止横屏,在清单文件中加入 android:screenOrientation=”portrait”是可以让屏幕保持竖屏,不横屏, 如果是想保持横屏不竖屏,可以添加1android:screenOrientation="landscape" activity启动模式Android的活动在内存里是以栈的形式存储 activity一共有四种启动模式:standard,,singleTop,,singleTask和singleInstance,可以通过activity标签指定android:launchMode属性来选择启动模式 standard:standard是默认的启动模式,在不指定别的启动模式时,所有活动都会自动使用这种启动模式.当有一个新的活动时,系统不会在乎此活动是否在堆栈中存在,每次启动都会为活动创建新的实例放入栈顶,简单地说就是你启动一个活动,他就往栈顶放一个 singleTop很多情况下第一种启动模式有些不合理,有的活动已经在栈顶了,为什么还要再次启动?singleTop就解决了这个问题,当要启动的活动已经在栈顶时候(我觉得就是自己启动自己的时候),那么就不创建新的活动实例 singleTasksingleTop很好的解决重复创建栈顶活动的问题,但是如果要启动的活动已经在栈内,并没有在栈顶,还是会创建多个活动实例,singleTask是能让整个堆栈只存在一个相同的活动实例比如说当前a活动是栈顶,b活动在a活动的下面,此时要启动b活动,那么会先将b活动弹出栈,此时使得a活动进入栈顶.整个过程是要把a活动以上的活动都销毁(destroy),直到a活动处于栈顶为止 singleInstance这个启动模式简单来说就是又创建了一个新的堆栈,是APP中LAUNCHER的activity所处在的栈(暂且叫他主栈)旁边又创建了一个堆栈(暂且叫他副栈)第一个例子:有abc三个活动,比如b的启动模式是singleInstance,a启动了b,b启动了c,此时在c界面单击back,按理说应该是返回到b,其实是返回到a.因为b是生成到副栈,此时是两个堆栈,主栈是a->c,副栈里面有b,当在c中单击返回键,是会将c下面的a放到栈顶,如果再a中再次back,那么才会显示b,因为是先将主栈的显示,主栈都销毁了再启动副栈的活动第二个例子:有ab两个活动,假设b的启动模式是singleInstance,在a中启动b,此时单击home键退出,然后再点进来,按理说应该还是显示b的界面,其实是显示的a,因为当重新启动了以后会先显示主栈的活动,没有的话才显示副栈 android弹出框dialog内置dialog普通对话框 123456789101112AlertDialog dialog = new AlertDialog.Builder(this).setTitle("我是标题").setMessage("我是内容").setIcon(R.mipmap.ic_launcher).setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "yes", Toast.LENGTH_SHORT).show(); } }).setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "no", Toast.LENGTH_SHORT).show(); } }).create();dialog.show(); 普通对话框可以设置标题,设置提示内容,以及设置确定和取消的点击事件 点旁边空白处也可以关闭dialog,但是不会调用取消的点击事件 单选对话框1234567891011121314151617AlertDialog dialog = new AlertDialog.Builder(this).setTitle("我是标题").setIcon(R.mipmap.ic_launcher).setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "yes", Toast.LENGTH_SHORT).show(); }}).setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "no", Toast.LENGTH_SHORT).show(); }}).setSingleChoiceItems(new String[]{"选项1", "选项2"}, 0, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, ""+which, Toast.LENGTH_SHORT).show(); }}).create();dialog.show(); 如果设置了setMessage,那么就只显示内容,不会显示这个单选 setSingleChoiceItems,第一个参数是一个字符串数组,第二个参数是默认选第几个,从0开始,第三个选项是一个点击事件,which是点击了第几个,从0开始 多选对话框1234567891011121314151617AlertDialog dialog = new AlertDialog.Builder(this).setTitle("我是标题").setIcon(R.mipmap.ic_launcher).setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "yes", Toast.LENGTH_SHORT).show(); }}).setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "no", Toast.LENGTH_SHORT).show(); }}).setMultiChoiceItems(new String[]{"选项1", "选项2"}, new boolean[]{true, false}, new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { Toast.makeText(MainActivity.this, ""+which+isChecked, Toast.LENGTH_SHORT).show(); }}).create();dialog.show(); setMultiChoiceItems第一个参数是字符串数组,第二个参数是数组里的值默认选不选,第三个参数是点击事件,which是第几个选项发生了变化,第二个参数isChecked表示变化是点击了还是没点击 自定义对话框 先创建一个布局my_dialog.xml1234567891011121314151617181920212223242526<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="Button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" /></android.support.constraint.ConstraintLayout> 包含了上面一个textView和下面一个button Mydialog.java12345678910111213141516171819202122232425262728293031323334353637public class MyDialog extends Dialog { private boolean isCreate; private String message; private TextView textView; private Button button; public MyDialog(Context context) { super(context); isCreate=false; message=null; } public void setTextView(String text){ if(isCreate){ textView.setText(text); }else{ message = text; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.my_dialog); isCreate = true; textView = (TextView)findViewById(R.id.textView); if(message!=null){ textView.setText(message); } button = (Button)findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(v.getContext(), "button", Toast.LENGTH_SHORT).show(); dismiss(); } }); }} 代码中重写了oncreate方法加载布局 当前对象如果还没有显示过,那么其内部子元素还没有关联资源(findviewbyid),此时如果调用一些方法,比如settext会报空值错误,参考setTextView的方法 给button设置了一个点击事件,显示一个”button”并且调用dismiss关闭当前弹出框 调用MainActivity.java123MyDialog myDialog = new MyDialog(this);myDialog.setTextView("你好啊");myDialog.show(); dialog生命周期 Dialog的生命周期一共用以下6个方法:onCreate(),show(),onStart() ,cancel(),onDismiss(),Stop() Dialog仅在在第一次启动时候会执行onCreate()方法(之后无论该Dialog执行Dismiss(),cancel(),stop(),Dialog都不会再执行onCreate()方法). show() 和 onStart()在每次Dialog显示时都会依次执行. onDismiss() 和 stop() 在每次Dialog消失的时候都会依次执行. cancel() 是在点击BACK按钮或者Dialog外部时触发,然后依次执行onDismiss() 和 stop(). 举例:点击显示按钮,第一次显示Dialog,然后按BACK键返回 onCreate() —> onStart() —>show() ;Stop() —> onDismiss() —> cancel(); 再次点击显示按钮,然后点击Dialog外部. onStart() —> show();Stop() —> onDismiss() —> cancel(); 再次点击显示按钮,然后执行Dialog.dismiss() 方法. onStart() —> show();Stop() —> onDismiss(); 自定义dialog需要注意:当前对象如果还没有显示过,那么其内部子元素还没有关联资源(findviewbyid),此时如果调用一些方法,比如settext会报空值错误 其他操作dialog上的edittext获得交点并弹出键盘:1234567891011121314151617/** * 让一个edittext获得焦点并弹出键盘 * @param et 要获得焦点的edittext对象 */private void showKeyBoard(EditText et) { if (et != null) { //设置可获得焦点 et.setFocusable(true); et.setFocusableInTouchMode(true); //请求获得焦点 et.requestFocus(); //调用系统输入法 InputMethodManager inputManager = (InputMethodManager) et .getContext().getSystemService(Context.INPUT_METHOD_SERVICE); inputManager.showSoftInput(et, 0); }} 然后在dialog外部调用的时候要先调用show,并且在多线程中调用此方法,如果不show就调用获得焦点会报错, 示例(runOnUiThread)12345678910111213myDialog.show();//多线程调用弹出键盘操作new Thread(){ public void run() { runOnUiThread(new Runnable(){ @Override public void run() { myDialog.showNameKeyBoard(); } }); }}.start(); android操作sqllite的工具类继承安卓自带的SQLiteOpenHelper,其中使用了代理模式(IResultSetUtil)操作结果集 需要注意的是,代理模式传入了cs(结果集对象)和db对象(连接对象),记得用完close. sqllite中间传入的参数值都是字符串类型 创建工具类SqlLiteHelper.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104import android.content.ContentValues;import android.content.Context;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;public class SqlLiteHelper extends SQLiteOpenHelper { private String firsql; /** * 构造方法 * @param context 上下文 * @param name 创建的文件名字,如database.db * @param factory 不知道,父类要的,看样子像是结果集操作工厂 * @param version 定义的当前数据库的版本 * @param firsql 第一次运行执行的sql语句,一般是用来创建表 */ public SqlLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, String firsql) { super(context, name, factory, version); this.firsql = firsql; } /** * 创建时候调用的方法 * 只有在构造方法中的数据库名称那个文件不存在的时候才会调用这个方法 * @param db */ @Override public void onCreate(SQLiteDatabase db) { db.execSQL(firsql); } /** * 当数据库版本变更的时候调用的方法,构造方法里的版本号 * @param db * @param oldVersion * @param newVersion */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } /** * 插入数据的方法 * @param tbname 插入数据的表名 * @param cols 要插入数据的列名 * @param values 要插入数据的值,和列名一一对应的值 */ public void insert(String tbname, String[] cols, Object... values) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues contentValues = new ContentValues(); for (int i = 0; i < cols.length && i < values.length; i++){ contentValues.put(cols[i], values[i].toString()); } Long id = db.insert(tbname, null, contentValues);//返回插入数据的行号,与id无关 db.close(); } /** * 修改数据库的方法 * @param tablename 要修改数据表的表名 * @param cols 要修改数据的列名 * @param newvalue 修改后对应的值 * @param require 修改的条件,如'id=?' * @param requirevalue 条件中问号对应的值 * @return 返回修改的行数 */ public int update(String tablename, String[] cols, String[] newvalue, String require, String[] requirevalue) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); for (int i = 0; i < cols.length && i < newvalue.length; i++) { values.put(cols[i], newvalue[i]); } int number = db.update(tablename, values, require, requirevalue); db.close(); return number; } /** * 删除数据 * @param tbname 要删除数据所在的表名 * @param require 删除的条件,如'id=?' * @param values 条件中问号对应的值 * @return 返回删除的行数 */ public int delete(String tbname,String require,String[] values){ SQLiteDatabase db = this.getWritableDatabase(); int number = db.delete(tbname,require,values); db.close(); return number; } /** * 执行传入的sql语句 * @param sql 要执行的语句 * @param iResultSetUtil 代理模式操作返回的数据库 * @param values 语句中问号对应的值 * @return 返回代理要的值 */ public Object executeQuery(String sql,IResultSetUtil iResultSetUtil,String [] values){ SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.rawQuery(sql,values); return iResultSetUtil.doHandler(cursor,db); }} IResultSetUtil.java123456import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;public interface IResultSetUtil { public Object doHandler(Cursor cs, SQLiteDatabase db);} 使用:1234567891011121314151617181920//创建对象SqlLiteHelper sqlLiteHelper = new SqlLiteHelper(this,"data.db",null,1,"create table persion(id integer PRIMARY KEY autoincrement,name verchar(20));");//插入sqlLiteHelper.insert("persion",new String[]{"name"},"xxxx");//查询String result = (String) sqlLiteHelper.executeQuery("select * from persion where id=?", new IResultSetUtil() { @Override public Object doHandler(Cursor cs, SQLiteDatabase db) { String result=null; Log.d("doHandler: ",""+cs.getCount()); cs.moveToFirst(); if(cs!=null&&cs.getCount()>0){ result = cs.getString(cs.getColumnIndex("name")); } cs.close(); db.close(); return result; }}, new String[]{"0"});Toast.makeText(this,result,Toast.LENGTH_SHORT).show(); 事务:事务分三步 123456789101112131415161718//开启事务:db.beginTransaction();//提交事务:db.setTransactionSuccessful();//关闭事务:db.endTransaction();//样例db.beginTransaction();/*进行一系列操作* */db.setTransactionSuccessful();db.endTransaction(); Android ADB命令大全JANUARY 26, 2018转自:https://www.jianshu.com/p/860bc2bf1a6a作者:张明云链接:https://www.jianshu.com/p/860bc2bf1a6a來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 另外下载adbADB和Fastboot最新版的谷歌官方下载链接ADB和Fastboot for Windowshttps://dl.google.com/android/repository/platform-tools-latest-windows.zipADB和Fastboot for Machttps://dl.google.com/android/repository/platform-tools-latest-darwin.zipADB和Fastboot for Linuxhttps://dl.google.com/android/repository/platform-tools-latest-linux.zip]]></content>
<tags>
<tag>android</tag>
</tags>
</entry>
<entry>
<title><![CDATA[java操作数据库以及使用c3p0]]></title>
<url>%2F2018%2F06%2F28%2Fjava%E6%93%8D%E4%BD%9C%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BB%A5%E5%8F%8A%E4%BD%BF%E7%94%A8c3p0%2F</url>
<content type="text"><![CDATA[java操作mysql数据库环境jdbc驱动下载下载与平台无关的Platform Independent,下载zip格式(注意版本问题,点Looking for previous GA versions?是以前版本) jdbc操作jdbc编程五个步骤:1,加载驱动;2,打开链接;3,执行查询;4,处理结果;5,清理环境 链接并查询:包都引用自java.sql.*123456789101112String sql = "SELECT * FROM tbl_user";Class.forName("com.mysql.jdbc.Driver");Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/temp","root","6");Statement st = conn.createStatement();ResultSet rs = st.executeQuery(sql);while(rs.next()) { System.out.println(rs.getInt("id")); System.out.println(rs.getString("name")); System.out.println(rs.getString("password")); System.out.println(rs.getString("email")); System.out.println();} 弄完了把rs,st,conn按顺序关闭 插入语句: 123456String sql = "insert into tbl_user(name,password,email) values('tom','tom123','[email protected]'),('jack','jack123','[email protected]')" ;Class.forName("com.mysql.jdbc.Driver");conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/temp","root","6");st = conn.createStatement();int count = st.executeUpdate(sql);System.out.println(count); count是更新记录的条数 更新语句:和插入一样,sql语句变一下 1sql="update tbl_user SET email='[email protected]' where name='jack'" 删除语句:和上面一样,sql语句变一下 1sql="delete from tbl_user where name='tom'"; 事务: 执行多个语句,都要用同一个Connection链接 conn.setAutoCommit(false)禁止事务自动提交 然后最后执行一下conn.commit()就行了 在catch语句中调用conn.rollback()回滚事务 java读取propertiesjava有properties类专门读取.properties文件123456789InputStream in = this.getClassLoader().getResourctAsStream(filename)Properties prop = new Properties()prop.load(in);//获取数据:prop.getproperty(name)//静态变量可以在静态代码块中赋值,只会在加载类的时候执行一次static{ //静态变量的赋值语句} 优化: 单例模式创建链接管理类 可以使用PrepareStatement类管理修改信息123456789101112131415//插入:PreparedStatement ps = conn.prepareCall("INSERT INTO tbl_name(name,password,email) values(?,?,?)")ps.setString(1,"username")ps.setString(2,"password")ps.setString(3,"email")ps.execute();//修改:String updateSql = "UPDATE tbl_name SET name=? ,password=?,email=? where id=?"PrepareStatement ps = conn.prepareStatement(updateSql)ps.setString(1,"name")ps.setString(2,"password")ps.setLong(4,1)ps.execute()//删除和修改一样,修改相应的sql语句即可注意编号是从1开始,填补上面语句中的问号 java使用c3p0操作数据库java的数据库池管理工具百度百科下载地址使用lib目录下的jar 首先在src目录创建一个c3p0-config.xml(不能错),所有的参数请点这里 12345678910111213141516171819202122232425262728293031323334353637383940414243<?xml version="1.0" encoding="UTF-8"?><c3p0-config> <named-config name = "mysql"> <!-- 配置数据库用户名 --> <property name="user">root</property> <!-- 配置数据库密码 --> <property name="password">password</property> <!-- 配置数据库链接地址 --> <property name="jdbcUrl">jdbc:mysql://localhost:3306/temp?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=true</property> <!-- 数据库驱动 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <!-- 连接池在无空闲连接可用时一次性创建的新数据库连接数,default : 3 --> <property name="acquireIncrement">5</property> <!-- 初始化连接数 --> <property name="initialPoolSize">10</property> <!-- 最小连接数 --> <property name="minPoolSize">5</property> <!-- 连接池中保留的最大连接数.default: 15 --> <property name="maxPoolSize">30</property> <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0--> <property name="maxStatements">100</property> <!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 --> <property name="maxStatementsPerConnection">10</property> <!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default: 3--> <property name="numHelperThreads">3</property> <!--用户修改系统配置参数执行前最多等待300秒。Default: 300 --> <property name="propertyCycle">10</property> <!--当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出 SQLException,如设为0则无限期等待。单位毫秒。Default: 0 --> <property name="checkoutTimeout">2000</property> <!--每60秒检查所有连接池中的空闲连接。Default: 0 --> <property name="idleConnectionTestPeriod">10</property> <!--最大空闲时间,20秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --> <property name="maxIdleTime">20</property> <!-- 配置链接的生存时间,超过这个时间的链接将由连接池自动断开丢弃掉,当然正在使用的链接不会马上断开,而是等待他close再断开 --> <property name="maxIdleTimeExcessConnections">5</property> <!--两次连接中间隔时间,单位毫秒。Default: 1000 --> <property name="acquireRetryDelay">1000</property> <!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试使用。Default: null--> <property name="automaticTestTable">Test</property> <!--如果设为true那么在取得连接的同时将校验连接的有效性。Default: false --> <property name="testConnectionOnCheckin">true</property> </named-config></c3p0-config> 创建一个链接管理类ConnectionManager.java 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748package cn.xwmdream.db;import java.sql.Connection;import java.sql.SQLException;import com.mchange.v2.c3p0.ComboPooledDataSource;import com.mchange.v2.c3p0.DataSources;public class ConnectionManager { private static ConnectionManager instance; private ComboPooledDataSource ds; private ConnectionManager() throws Exception{ ds = new ComboPooledDataSource("mysql"); } /** * 单例模式获取数据库管理连接对象 * */ public static final ConnectionManager getInstance() { if(instance==null) { try { instance = new ConnectionManager(); } catch (Exception e) { e.printStackTrace(); } } return instance; } /** * 为了线程安全,同步 * */ public synchronized final Connection getConnection() { try { return ds.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return null; } @Override protected void finalize() throws Throwable { DataSources.destroy(ds);//关闭datasource super.finalize(); }} 使用代理模式创建一个结果集处理接口IResultSetUtil.java 12345678910package cn.xwmdream.db;import java.sql.ResultSet;import java.sql.SQLException;/** * 处理ResultSet的接口 */public interface IResultSetUtil { public Object doHandler(ResultSet rs)throws SQLException;} 创建一个数据库操作类DBUtil.java 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111package cn.xwmdream.db;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;public class DBUtil { /** * 从c3p0连接池中获取数据库连接对象 */ public static Connection getConnection() { Connection conn = null; conn = ConnectionManager.getInstance().getConnection(); return conn; } /** * 释放资源 */ public static void close(Connection conn, PreparedStatement psmtStatement, ResultSet resultSet) { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (psmtStatement != null) { try { psmtStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 增删改的通用方法 */ public static int executeUpdate(String sql,Object...objects) { int result = 0; Connection conn = null; PreparedStatement psmt = null; try { conn = getConnection(); psmt = conn.prepareStatement(sql); if(objects!=null) { for(int i=0;i<objects.length;i++) { psmt.setObject(i+1, objects[i]);//下标从1开始 } } result = psmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { close(conn,psmt,null); } return result; } /** * 查询通用方法 */ public static Object executeQuery(String sql,IResultSetUtil rsHandler,Object...objects) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { connection = getConnection(); preparedStatement = connection.prepareStatement(sql); if (objects != null) { for(int i=0;i<objects.length;i++) { preparedStatement.setObject(i+1, objects[i]); } } resultSet = preparedStatement.executeQuery(); return rsHandler.doHandler(resultSet); } catch (SQLException e) { e.printStackTrace(); }finally { close(connection,preparedStatement,resultSet); } return null; } /** * 查询单个字段值的通用的方法 * */ public static Object executeQuery(String sql,Object...objects) { return executeQuery(sql,new IResultSetUtil() { @Override public Object doHandler(ResultSet rs) throws SQLException { Object object = null; if(rs.next()) { object = rs.getObject(1);//第一列的值就是1,第二列的值,就是2 } return object; } },objects); }} 测试 123456789101112131415//获得一个链接对象System.out.println(DBUtil.getConnection());//获取user表id等于1的第一个值的name字段System.out.println(DBUtil.executeQuery("select name from user where id=?", 1));//获取user表id等于1的结果集System.out.println(DBUtil.executeQuery("select * from user where id=?", new IResultSetUtil() { @Override public Object doHandler(ResultSet rs) throws SQLException { return rs; }}, 1));//插入一个id等于2,name为haha的值System.out.println(DBUtil.executeUpdate("insert into user (id,name)values(?,?)",2, "haha"));]]></content>
<tags>
<tag>java</tag>
<tag>mysql</tag>
</tags>
</entry>
<entry>
<title><![CDATA[hello_new_word_hahaha]]></title>
<url>%2F2018%2F05%2F19%2Fhello-new-word-hahaha%2F</url>
<content type="text"><![CDATA[]]></content>
<tags>
<tag>经历</tag>
</tags>
</entry>
</search>