-
Notifications
You must be signed in to change notification settings - Fork 0
/
学spring记录的笔记.txt
executable file
·6688 lines (5362 loc) · 232 KB
/
学spring记录的笔记.txt
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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
spring是什么?
spring是一个开源框架,spring是一个IOC(DI)和AOP容器框架
spring是一个容器,因为它包含并且管理应用对象的生命周期
spring是框架,spring实现了使用简单的组件配置组合成一个复杂的应用。在spring中可以使用XML和Java注解组合这些对象
spring jar包的作用:
spring-core-4.0.0.RELEASE.jar 这个jar包包含spring框架基本的核心工具类,spring其他组件都要用到这个包里的类。
spring-context.jar 这个jar包为spring核心提供了大量的扩展。
面试题:什么是JDBC?
JDBC就是用Java语言来操作数据库的一门技术。
接口可以解除耦合关系,什么意思?
没有接口,原来类和类之间是继承的关系,子类要想有父类的方法必须通过继承才能有父类的方法,但是有了接口之后就可以在接口
中定义方法,然后实现这个接口,类和类之间的关系就解开了。
工厂方法设计模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。FactoryMethod使一个类的实例化延迟到其子类。
public class FactoryMethod {
public static void main(String[] args) {
IWorkFactory i1 = new StudentWorkFactory();
i1.getWork().doWork();
IWorkFactory i2 = new TeacherWorkFactory();
i2.getWork().doWork();
}
}
interface IWorkFactory{
Work getWork();
}
class StudentWorkFactory implements IWorkFactory{
@Override
public Work getWork() {
return new StudentWork();
}
}
class TeacherWorkFactory implements IWorkFactory{
@Override
public Work getWork() {
return new TeacherWork();
}
}
interface Work{
void doWork();
}
class StudentWork implements Work{
@Override
public void doWork() {
System.out.println("学生写作业");
}
}
class TeacherWork implements Work{
@Override
public void doWork() {
System.out.println("老师批改作业");
}
}
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
代理模式的一个好处就是对外部提供统一的接口方法,而代理类在接口中实现对真实类的附加操作行为,从而可以在不影响外部调用
情况下,进行系统扩展。也就是说,我要修改真实角色的操作的时候,尽量不要修改他,而是在外部在“包”一层进行附加行为,即
代理类。例如:接口A有一个接口方法operator(),真实角色:RealA实现接口A,则必须实现接口方法operator()。客户端Client
调用接口A的接方法operator()。现在新需求来了,需要修改RealA中的operator()的操作行为。怎么办呢?如果修改RealA就会影响
原有系统的稳定性,还要重新测试。这是就需要代理类实现附加行为操作。创建代理ProxyA实现接口A,并将真实对象RealA注入进来
。ProxyA实现接口方法operator(),另外还可以增加附加行为,然后调用真实对象的operator()。从而达到了“对修改关闭,对扩展
开放”,保证了系统的稳定性。我们看客户端Client调用仍是接口A的接口方法operator(),只不过实例变为了ProxyA类了而已。也
就是说代理模式实现了ocp原则。
静态代理
public class Proxy {
public static void main(String[] args) {
Object o = new ProxyObject();
o.action();
}
}
interface Object{
void action();
}
class ProxyObject implements Object{
Object obj;
public void action() {
System.out.println("代理类开始执行");
obj.action();
System.out.println("代理方法执行完毕");
}
public ProxyObject() {
System.out.println("代理方法创建成功");
obj = new ObjectImp();
}
}
class ObjectImp implements Object{
public void action() {
System.out.println("=====被代理类======");
System.out.println("=====被代理类的操作==");
}
}
利用反射可以实现动态代理
静态代码块随着类的加载而加载,而且只被加载一次。
非静态代码块是随着对象的加载而加载,在构造器之前加载。
反射:
反射允许程序在执行期间借助ReflectionAPI取得任何类的内部信息,并能直接操作对象的内部属性及方法。
反射相关的API:
java.lang.Class 代表一个类
java.lang.reflect.Method 代表类的方法
java.lang.reflect.Field 代表类的成员变量
java.lang.reflect.Constructor 代表类的构造方法
通过反射调用对象的属性和方法:
Class<Person> clazz = Person.class;
Person p1 = clazz.newInstance();//创建运行时类的对象
下面的这行代码我想去调用Person里面的name属性(name属性是私有的,也可以通过反射去调用)
Field f1 = clazz.getDeclaredField("name"); person里面有一个属性叫name 通过clazz.getDeclaredField("name")去拿到这个属性,因为name声明成了private,所有要加Declared,如果name是public的就可以直接getField()
f1.setAccessible(true); 因为name是私有的所以要加这句代码让他可以访问,否则会报访问权限异常。
f1.set(p1, "zhangsan"); p1就是那个运行时类的对象,这句代码就是说设置p1这个运行时类的对象的name属性为zhangsan
System.out.println(p1);
Method m1 = clazz.getMethod("show");调属性用getField()调方法就用getMethod()
m1.invoke(p1); 属性是用set去设置属性的值,而方法则是直接调invoke()方法去执行方法。p1是哪一个对象,有的方法需要传入参数,就在后面写。
Method m2 = clazz.getMethod("display", String.class);第一个参数是方法名,第二个参数是这个方法传入参数的类型。
m2.invoke(p1, "China");
java.lang.Class:是反射的源头
我们创建一个类是通过编译(javac.exe),生成对应的.class文件。之后我们使用java.exe加载(JVM的类加载器)此.class文件
此.class文件加载到内存以后就是一个运行时类,存在缓存区。那么这个运行时类本身就是一个Class!
有了Class实例之后,可以进行如下操作:
1).创建对应的运行时类的对象
2).可以获取到对应的运行时类的完整结构(属性、方法、构造器、内部类、父类、所在的包、异常、注解等)
3).调用对应的运行时类的指定结构
如何去获取Class的实例:(3种)
1.调用运行时类本身的.class属性
Class clazz = Person.class;
System.out.println(clazz);
2.通过运行时类的对象获取
Person p = new Person();
Class clazz2 = p.getClass();
System.out.println(clazz2);
3.通过Class的静态方法获取
String className = "com.iflytek.factory.test.Person";
Class clazz = Class.forName(className);
Person p = (Person) clazz.newInstance();
System.out.println(p);
有了Class实例能做什么?
1.创建类的对象:调用Class对象的newInstance()方法
newInstance()方法默认调用类的无参构造器,如果类没有无参构造器,则报初始化错误
Class实例获取运行时类的属性:
Class clazz = Person.class;
Field[] fields = clazz.getFields(); getFields()方法只能获取到声明为public修饰符的属性 (可以获取到运行时类及其父类声明为public的属性)
for(int i = 0;i<fields.length;i++){
System.out.println(fields[i]);
}
要想获取到所有的属性,必须使用getDeclaredFields(); (只能获取运行时类的本身属性)
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f :declaredFields){
System.out.println(f.getName());
}
Class实例获取运行时类的方法:
Class clazz = Person.class;
Method[] methods = clazz.getMethods(); getMethods()方法可以获取修饰符为public的直接或间接父类的方法和本身修饰符public的方法
for(Method m :methods){
System.out.println(m);
}
要想获取到本身类的所有方法,需使用clazz.getDeclaredMethods()
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m1 :declaredMethods){
System.out.print(m1);
}
**********************************************必须会************************************************************
调用运行时类的指定属性:
Class clazz = Person.class;
//获取指定的属性
Field name = clazz.getField("name");
Person p = (Person) clazz.newInstance(); 创建运行时类的对象
name.set(p, "Jimmy");
System.out.println(p);
Field age = clazz.getDeclaredField("age"); 这里的age属性是private的所以要用getDeclaredField
age.setAccessible(true); 还要设置它的访问权限是true
age.set(p, 12);
System.out.println(p);
调用运行时类中指定的方法:
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Method m = clazz.getMethod("show", null); 如果调用声明为private的方法就要用getDeclaredMethod()
m.invoke(p, null);
Method m1 = clazz.getMethod("display", String.class);
m1.invoke(p, "美国"); 这个invoke方法是有返回值的,
Method m2 = clazz.getMethod("toString", null);
java.lang.Object returnVal = m2.invoke(p, null);
System.out.println(returnVal);
调用运行时类中指定的构造器:
String className = "com.iflytek.factory.test.Person";
Class clazz = Class.forName(className);
Constructor con = clazz.getDeclaredConstructor(String.class,int.class);
con.setAccessible(true);
Person p = (Person)con.newInstance("Jimmy",41); newInstance方法也是有返回值的
System.out.println(p);
**********************************************必须会************************************************************
java动态代理:
动态代理是指客户通过代理类来调用其他对象的方法,并且在程序运行时根据需要动态创建目标类的代理对象
interface Subject{
void action();
}
//被代理类
class RealSubject implements Subject{
@Override
public void action() {
System.out.println("我是一个代理类");
}
}
//凡是和动态代理相关的都要用到InvocationHandler这样一个接口
class MyInvocationHandler implements InvocationHandler{
java.lang.Object obj;
//下面的这个blind方法有两个作用,1.给被代理的对象实例化 2.返回一个代理对象
public java.lang.Object blind(java.lang.Object obj){
this.obj = obj;
//既然被代理类实现了接口并重写了抽象方法,那么代理类也要重写这个抽象方法
//最后一个参数就是实现了InvocationHandler这个接口的类对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(), this);
}
//当代理类对象调用重写方法的时候,都会自动转为对invoke方法的调用。
@Override
public java.lang.Object invoke(java.lang.Object proxy, Method method,
java.lang.Object[] args) throws Throwable {
Object returnVal = method.invoke(obj, args);
return returnVal;
}
}
public class TestProxy {
public static void main(String[] args) {
//首先创建被代理类的对象
RealSubject rs = new RealSubject();
MyInvocationHandler mih = new MyInvocationHandler();
java.lang.Object obj = mih.blind(rs);
Subject sub = (Subject) obj;
sub.action();
}
}
动态代理与AOP:
interface Human{
void info();
void fly();
}
//接口的实现类,被代理类。
class SuperMan implements Human{
public void info() {
System.out.println("我是超人");
}
public void fly() {
System.out.println("我可以飞的更高");
}
}
class HunmanUtil{
public void method1(){
System.out.println("*********方法一**********");
}
public void method2(){
System.out.println("*********方法二**********");
}
}
//要想实现动态代理的创建,设计到两个东西,InvocationHandler的实现类,还有一个Proxy代理类实例的创建
class MyInvocationHandler implements InvocationHandler{
Object obj; //被代理对象的声明
public void setObject(Object obj){ //这个方法就是实例化被代理对象
this.obj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
HunmanUtil h = new HunmanUtil();
h.method1();
Object returnVal = method.invoke(obj, args);//在两个写死的方法中间插入(中间这个方法时动态的)
h.method2();
return returnVal;
}
}
class MyProxy{
//动态的创建一个代理类的对象
//要想创建一个代理类的对象,我要知道被代理类是什么,所以传入一个被代理类的参数,obj就是被代理类对象
public static Object getProxyInstance(Object obj){
MyInvocationHandler handler = new MyInvocationHandler();
handler.setObject(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
//上面一行代码就是返回一个代理类的对象
}
}
public class TestAOP {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Object obj = MyProxy.getProxyInstance(superMan);//这行代码返回的是一个代理类的对象
Human h = (Human) obj;
h.fly();
}
}
包装类(Wrapper):
针对8种基本数据类型定义的引用类型--包装类
包装类主要掌握,基本数据类型和包装类和String三者之间的转换。
基本数据类型和包装类之间的转换:
int i = 10;
Integer i1 = new Integer(i);
System.out.println(i1);
方法:调用包装类的构造器
Float f = new Float(23.1f);
System.out.println(f);
包装类转化为基本的数据类型:
int i2 = i1.intValue();
System.out.println(i2); 方法:调用xxxvalue方法
float f2 = f.floatValue();
System.out.println(f2);
JDK5.0以后,自动装箱拆箱。以为着上面的就不用了
Integer i = 12;(自动装箱) 左边是一个包装类,是一个类,右边是一个基本数据类型
int i1 = i; (自动拆箱)i是Integer这个包装类的对象,将i直接赋值给i1这个基本数据类型
基本数据类型、包装类转化为String:调用String的valueOf方法
int i = 10;
String str = String.valueOf(i);
System.out.println(str.getClass());
String 转化为基本数据类型、包装类:调用包装类.parsexxx方法
int i1 = Integer.parseInt(str);
System.out.println(i1);
接口:
接口里面只能有常量和抽象方法,常量默认是public static final 修饰的,抽象方法默认是public abstract修饰的
接口和接口之间是继承关系,而且可以实现多继承。
枚举类:
什么是枚举类,根据字面意思就是你这个类里面的对象是有限个的可以给你枚举出来,对象是有限个的,如果只有一个,那就是单例。
若枚举只有一个成员,则可以作为一种单例模式的实现。
自定义枚举类
class Season{
private final String seasonName;
private final String seasonDesc;
//私有化构造器,外部调不了。上面声明的属性在构造器中初始化,一旦初始化不能被修改
private Season(String seasonName,String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
public static final Season SPRING = new Season("春天", "春暖花开");
public static final Season AUTUMN = new Season("秋天", "好多落叶");
public static final Season SUMMEWR = new Season("夏天", "太阳");
public static final Season WINTER = new Season("冬天", "大雪纷飞");
@Override
public String toString() {
return "Season [seasonName=" + seasonName + ", seasonDesc="
+ seasonDesc + "]";
}
public void show(){
System.out.println("这是一个季节");
}
}
如何使用enum关键字定义枚举类
enum Season1{
SPRING("春天", "开花"), 这四个都是对象 SPRING SUMMER AUTUMN WINTER
SUMMER("夏天", "太阳"),
AUTUMN("秋天", "落叶"),
WINTER("冬天", "大雪");
private final String seasonName;
private final String seasonDesc;
private Season1(String seasonName,String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season1 [seasonName=" + seasonName + ", seasonDesc="
+ seasonDesc + "]";
}
}
用enum定义的枚举类Season1下面有一个方法.values(),这个方法可以将枚举类下面的所有对象以一个数组的形式返回
public static void main(String[] args) {
Season1 spring = Season1.SPRING;
System.out.println(spring);
System.out.println(spring.getSeasonName());
Season1[] values = Season1.values();
for(Season1 val : values){
System.out.println(val);
}
}
让枚举类实现接口:
枚举类实现接口后要重写这个接口的抽象方法,然后你可以通过枚举类的对象来调这个重写的抽象方法,但是你不管通过哪一个枚举类对象调用
这个方法,执行效果都一样,要是想不同的枚举类对象调这个方法会有不同的效果就要在这个里面重写接口的抽象方法
SPRING("春天", "开花"){
public void show(){
System.out.println("春天好");
}
},
SUMMER("夏天", "太阳"){
public void show(){
System.out.println("夏天好");
}
},
AUTUMN("秋天", "落叶"){
public void show(){
System.out.println("秋天好");
}
},
WINTER("冬天", "大雪"){
public void show(){
System.out.println("冬天好");
}
};
StringBuffer 和 StringBuider:
java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删。
StringBuffer(JDK1.0的时候就有了)的方法:
append() 在后面追加
insert(int index,String str) 在指定位置插入一个字符串
reverse() 反转此StringBuffer
replace(int startIndex,int endIndex,String str) 替换
delete(int startIndex,int endIndex) 删除
String substring(int start,int end) 截取字符串
StringBuider:JDK1.5之后新增的
涉及到线程安全问题的话,StringBuider线程不安全。但是如果不考虑线程安全问题的话,StringBuider的执行效率要比StringBuffer高
日期类:
Date类
Date date = new Date();
System.out.println(date);
System.out.println(date.getTime());//1462084125348 获取时间戳long型
Date date2 = new Date(1462084125348L); //将时间戳变为Date类型
System.out.println(date2);
SimpleDateFormat类:
SimpleDateFormat sdf = new SimpleDateFormat();
String str = sdf.format(new Date());
System.out.println(str); //默认的16-5-1 下午3:13
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm");
str = sdf1.format(new Date());
System.out.println(str); //2016-05-01 03:13
//返回date1和date2之间的天数
public int getDays(String date1,String date2) throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d1 = sdf.parse(date1);
Date d2 = sdf.parse(date2);
long time = d2.getTime()-d1.getTime();
return (int)(time/1000/3600/24+1); 传入的date1和date2均为String类型的字符串通过sdf.parse(date1)将字符串解析为Date类型
}
Filter过滤器:
Filter的基本功能是对Servlet容器调用Servlet的过程进行拦截,从而在Servlet进行响应处理的前后实现一些特殊的功能。
在Servlet API中定义了三个接口类来供开发人员编写Filter程序:Filter FilterChain FilterConfig
Filter是什么?
Filter是一个实现了Filter接口的Java类,可以对发送到Servlet的请求进行拦截,并对响应也可以进行拦截。
Filter对象是在Servlet容器加载当前web应用的时候就被创建
public void init(FilterConfig arg0) throws ServletException {
//init方法在创建Filter对象后就立即被调用,且只被调用一次
该方法主要用于对当前Filter进行初始化操作
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
真正Filter的逻辑代码需要编写在该方法中,每次拦截都会调用该方法。
FilterChain: filter链,多个Filter可以构成一个Filter链
chain.doFilter(request, response); 把请求传给Filter链的下一个Filter,若当前的Filter是Filter链的最后一个Filter
则把请求传给目标资源(Servlet或者jsp)。
多个Filter的拦截顺序和<filter-mapping>配置顺序有关,配置靠前的先被调用。
上面那句话另一种表述方式:Filter链中各个Filter的拦截顺序与他们在应用程序的web.xml中映射的顺序一致。
}
public void destroy() {
在Filter销毁之前被调用,且只被调用一次。
}
<filter-mapping>里面的<dispatcher>指定过滤器所拦截的资源被Servlet容器所调用的方式
如果<dispatcher>的子元素是REQUEST(默认是REQUEST)当用户直接访问页面时,web容器会调用过滤器。如果目标资源是通过转发的方式访问页面,那么过滤器不会被调用。
如果<dispatcher>的子元素是FORWARD那么目标资源如果哦是通过转发的方式来访问页面那么过滤器将被调用。
监听器:
专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时立即采取行动。
servlet监听器:用于监听web应用程序中的ServletContext,HttpSession和ServletRequest等域对象的创建和销毁事件,以及监听这些域对象中属性发生修改的事件。
按监听事件类型Servlet监听器可分为如下三种类型:
---监听域对象自身创建和销毁的事件监听器
---监听域对象中属性的增加和删除的事件监听器
---监听绑定到HttpSession域中某个对象的状态的事件监听器
下面来说说第一种监听域对象自身创建和销毁的事件监听器,以ServletContext为例:
我们都知道ServletContext这个域对象是在当前WEB应用被加载的时候创建当前WEB应用被卸载的时候销毁。
你现在要写的是监听域对象创建和销毁事件的监听器,那么你就要去实现ServletContextListener这个接口。然后重写
这个接口里面的抽象方法。假如我要监听HttpSession这个对象的创建和销毁,那么我就要去实现HttpSessionListener这个接口。
还是以ServletContext为例,代码说明:
public class HelloServletContext implements ServletContextListener {
public void contextDestroyed(ServletContextEvent sce) { 这个参数的作用就是,我可以通过这个参数来获取ServletContext,使用sce.getServletContext()
System.out.println("ServletContext对象被销毁");
}
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext对象被创建");
}
}
除了上面的代码以外,我还要在web.xml中配置一下:
<listener>
<listener-class>com.iflytek.listener.test.HelloServletContext</listener-class>
</listener>
这样写完以后,当我项目一启动的时候,控制台就会打印"ServletContext对象被创建",当我项目一停止就会打印
"ServletContext对象被销毁"。
其实准确的说应该是当前WEB应用被加载的时候打印"ServletContext对象被创建",当前WEB应用被卸载的时候打印"ServletContext对象被销毁"
当前WEB应用被加载和卸载与项目启动和停止还是有区别的,比如说你把上面写的Servlet的内容在项目运行的
时候随便改一下,那么eclipse就会自动帮你编译,卸载修改前的WEB应用并重新加载修改后的WEB应用。即这时候
控制台就会打印"ServletContext对象被销毁"和"ServletContext对象被创建"。而我的项目并没有手动的去
停止再重启。
我们说ServletContextListener是最常用的一个Listener,要是实现了这个接口我就可以对ServletContext这个
域对象进行监听,当前WEB应用被加载的时候就会调contextInitialized(ServletContextEvent sce)这个方法,
所以我就可以对相关资源进行初始化操作。
域对象的创建和销毁的事件监听器就是用来监听ServletContext,HttpSession和ServletRequest这三个对象的创建和销毁事件的监听器。
ServletContext创建时机:web服务器启动时为每个web应用程序创建相应的ServletContext对象
ServletContext销毁时机:web服务器关闭时为每个web应用程序销毁相应的ServletContext对象
HttpSession创建时机:浏览器开始与服务器会话时创建
HttpSession销毁时机:调用HttpSession.invalidate();超过了Session的最大有效时间间隔;服务器的进程被停止。
ServletRequest创建时机:每次请求的时候开始创建。
ServletRequest销毁时机:每次访问结束后销毁。
ServletContextListener:
监听ServletContext对象被创建或销毁的Servlet监听器
ServletContextListener是最常用的Listener,可以在当前web应用被加载的时候对当前web应用的相关资源进行初始化操作:
创建数据库连接池,创建Spring的IOC容器,读取当前web应用的初始化参数。
ServletContext对象什么时候被创建?当前web应用被加载的时候
request发送请求的时候被创建,请求结束的时候被销毁。比如说你在A页面写了一个<%requset.setAttribute("requestKey","requestValue") %>
和<a href = "B"><a/>你点击超链接去B页面通过getAttribute去拿,这时候拿不到,因为你点击超链接又是一个请求,两个请求不一样自然拿不到
但是如果你在A页面通过转发的方式去B页面拿,那么就可以拿的到,因为转发是同一个请求。
session:当第一次访问web应用的一个jsp或者Servlet的时候,且该jsp或者Servlet还需要创建session的时候创建session对象
关闭浏览器,并不意味着session被销毁,还可以通过sessionId找到服务器中的session对象
******************************************************************************************************************
springMVC部分
******************************************************************************************************************
RESTful风格的SpringMVC CRUD
springMVC的form标签:可以更快速的开发页面,可以进行表单值回显。
报这个错误 Neither BindingResult nor plain target object for bean name 'command' available as request attribute
使用了form标签,你想来显示这个页面就是让页面正常显示,必须在域对象中有这个bean,这个bean有跟表单path对应的属性
可以通过modelAttribute属性指定绑定的模型属性,如果没有该属性,则默认从request域对象中读取command的表单bean,如果该属性值也不存在则会报错。
springmvc认为表单值一定是要进行回显的
springNVC静态资源问题:
为什么会有这样的问题?因为优雅的REST风格的资源的URL不希望带.html和.do等后缀。若将DispatcherServlet的请求映射配置为/,
则springMVC将捕获WEB容器的所有请求,包括静态资源(图片、js、css)请求。SpringMVC会把他们当成一个普通的请求处理(因为它们没有被映射)
所有找不到对应的处理器而出错。
解决办法:在spring的配置文件中写上<mvc:default-servlet-handler/>
写上它的作用就是default-servlet-handler将在springMVC上下文定义一个DefaultServletHttpRequestHandler,他会对进入DispatcherServlet
的请求进行筛查,如果发现是没有经过映射的请求,将该请求交给web应用服务器默认的Servlet处理。如果不是静态资源请求,才交由
DispatcherServlet处理。
springMVC概述:
springMVC是spring为展现层提供的基于MVC设计理念的优秀web框架,是目前主流的MVC框架之一。
spring3.0以后全面超越Struts2,成为最优秀的MVC框架。
springMVC通过一套MVC注解,让pojo成为处理请求的控制器,而无需实现任何接口。
@RequestMapping:
---DispatcherServlet截获请求后,就通过控制器上的@RequestMapping提供的映射信息确定请求所对应的处理方法。
@RequestMapping(value = "/testParam",params = {"userName","age!=10"})
<a href="testParam?userName=Jimmy&age=11">TestParam</a>
@RequestMapping里有一个参数叫params,他是一个String类型的数组。意思是要求请求的url里必须包含参数userName 和age,并且age的值不能等于10。
@RequestMapping的url支持通配符。 例如:@RequestMapping("/testAntPath/*/abc")
@PathVariable注解:
可以映射URL中的占位符到目标方法的参数中。(正是因为这样的一个特性,才使得Springmvc支持RESTFUL风格的URL)
通过@PathVariable可以将URL中占位符参数绑定到控制器处理方法的入参当中。
REST:
(资源)表现层状态转化。是目前最流行的一种互联网软件架构。
资源:网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本,一张图片,一首歌曲,总之就是一个具体的存在。
可以用一个URI指向他,每种资源对应一个URI。要获取这个资源,访问它的uri就可以了,因此URI即为每一个资源的独一无二的标识符。
表现层:把资源具体呈现出来的形式,叫做它的表现层。比如:文本可以用txt格式表现、也可以用HTML格式、XML格式、JSON格式、
甚至可以采用二进制格式。
状态转化:每发出一个请求,就代表客户端和服务器的一次交互过程。HTTP协议,是一个无状态的协议,即所有的状态都保存在服务器
端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生状态转化。而这种转化是建立在表现层之上的,所以就是
“表现层状态转化”。具体的说,就是HTTP协议里面,四个表示操作方式的动词,GET,POST,PUT,DELETE。他们分别对应四种基本操作,
GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
Rest风格的URL
以CRUD为例:
新增:/order POST
修改:/order/1 PUT update?id=1
获取: /order/1 GET get?id=1
删除:/order/1 DELETE delete?id=1
如何发送PUT请求和DELETE请求?
1.首先需要在web.xml中配置过滤器。
2.然后发送post请求
3.需要在发送post请求的时候携带一个name="_method"的隐藏域,value值为DELETE或PUT
@RequestParam:
例子:
<a href="testRequestParam?userName=Jimmy&age=11">TestRequestParam</a>
@RequestParam(value = "userName")String userName,
@RequestParam(value = "age",required=false,defaultValue="0") int age
可以使用@RequestParam的value属性值来映射来映射那个请求参数。
required:该参数是否必须。
defaultValue:请求参数的默认值。
springMVC会按请求参数名和POJO属性名自动进行匹配。
自动为该对象填充属性值,支持级联属性
----使用servlete原生的api作为入参:
MVC的handler方法里可以接受哪些ServletAPI类型的参数?
HttpServletRequest、HttpServletResponse
HttpSession、Local、
InputStream、OutputStream、
Reader、Writer
public void testServletApI(HttpServletRequest request,
HttpServletResponse response,Writer writer) throws Exception{
System.out.println("request:"+request+"response:"+response);
writer.write("helloSpringMVC"); 直接在页面上打出helloSpringMVC
// return SUCCESS;
}
-----处理模型数据
我们知道MVC设计模式是我们发一个请求到目标处理器,目标处理器去调业务方法,业务方法可能会有返回值,比如说一个对象
或者说是一个集合然后转发到页面,转发到页面时需要把那个返回值(对象或者集合)在页面上显示出来。
springMVC提供了一下几种途径输出模型数据
--ModelAndView
--Map
--@SessionAttributes
--@ModelAttribute
1.ModelAndView
控制器处理方法的返回值如果是ModelAndView,则其既包含视图信息,也包含模型数据信息。
public ModelAndView testModelAndView(){
String viewName = SUCCESS;
ModelAndView modelAndView = new ModelAndView(viewName);
modelAndView.addObject("time", new Date());
return modelAndView;
}
springmvc会把ModelAndView的model数据放到request域对象中
2.Map
目标方法的入参可以加一个Map或者Model类型的参数
public String testMap(Map<String,Object>map){
map.put("names", Arrays.asList("tom","kelly"));
return SUCCESS;
}
3.@SessionAttributes
若希望在多个请求之间公用某个模型属性数据,则可以在控制器类上标注一个@SessionAttributes
public String testSessionAttribute(Map<String,Object>map){
User user = new User("jimmy", "123", "[email protected]", 12, null);
map.put("user", user);
map.put("company", "iflytek");
return SUCCESS;
}
除了上面这段代码还要在控制器类上写@SessionAttributes(value = {"user"},types = String.class)
上面的value属性和types属性表示:
@SessionAttributes除了可以通过属性名指定需要放到会话中的属性外(用的就是value属性值)
还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中(用的就是types属性)
如果@SessionAttributes(value = {"user"},types = String.class)和map.put("user", user);
@SessionAttributes的value值和map里面的键的值一样,那么map里面的东西就会既放在request域里面也会放在session域里面
4.@ModelAttribute
这个注解的使用场景:现在需要完成一个修改操作,但是修改有一个条件就是有一个字段不能被修改,比如说录入时间就不能修改
假设数据表有三个字段,有一个字段不能被修改。你要修改的话,(页面的form表单传给controller)表单传入的就只能是两个字段,然后在目标方法里有一个对象(controller方法对象作为参数)
这个对象是new的,然后表单把值传过来赋给对象的对应属性,但是只赋了两个属性有一个属性是空。然后拿着这样一个对象
去更新的话显然是有问题的。怎么解决这个问题?
就是form表单传更新参数给controller里的对应对象,但是这个对象不是new的而是从数据库中获取的,这样你
更新两个字段,还有一个字段没更新,但这个字段有值。还是原来从数据库中获取的值。
@ModelAttribute
public void getUser(@RequestParam("id")Integer id,Map<String,Object> map){
if(id != null){
User user = new User(1, "tom", "123", "[email protected]", 12);
map.put("user", user);
}
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user){ 这里的user值是新创建的,form表单传过来的参数,有一个password值在form表单没传
System.out.println("修改:"+user); 所以这里打出来的user值password为空
return SUCCESS;
}
但是加了上面ModelAttribute注解修饰的方法里初始化了一个user,然后放到map里。上面的user会传给下面的user
这样下面的user就有值了,不是新创建的了
注意:在@ModelAttribute修饰的方法中,放入到map时的键需要和目标方法入参类型的第一个字母的小写字符串一致。
如果不一致,报错。
有@ModelAttribute修饰的方法,会在每个目标方法执行之前被springMVC调用。
@ModelAttribute也可以来修饰目标方法POJO类型的入参,其value属性值有如下作用:
1).springMVC会使用value属性值在implicitModel中查找对应的对象,若存在则会直接传到目标方法的入参中。
2).springMVC会以value为key,POJO类型的对象为value,存到request中。
比如说:
map.put("abc", user);
public String testModelAttribute(@ModelAttribute("abc")User user){} springMVC会使用abc来查找对应的对象,因为上一行map里面有abc,所以直接把map里面的user传进来
sessionattribute引发的一个异常:
org.springframework.web.HttpSessionRequiredException: Session attribute 'user' required - not found in session
public String testModelAttribute(User user){
System.out.println("修改:"+user);
return SUCCESS;
}
他先会去implicitModel中去找user对象(没用@ModelAttribute("abc")这个东西修饰,默认就是类名第一个字母小写)
如果用了这个东西修饰就是去implicitModel找abc这个对象。如果implicitModel里没有的话他会看当前的handler
是否标识了@SessionAttributes(value = {"user"}),然后看里面的value值是否和User user 一样,如果一样就会
强制去session里找,找不着就抛异常了。
@SessionAttributes(value = {"user"},types = String.class)
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
// @ModelAttribute
// public void getUser(@RequestParam("id")Integer id,Map<String,Object> map){
// if(id != null){
// User user = new User(1, "tom", "123", "[email protected]", 12);
// map.put("user", user);
// }
// }
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user){
System.out.println("修改:"+user);
return SUCCESS;
}
}
上述代码会抛异常,是sessionattribute引发的一个异常。因为它回去session域里面找,没找到就会抛异常。
但是如果把屏蔽的代码打开就不会抛异常。因为map.put("user", user) user被放到请求域里面
然后@SessionAttributes(value = {"user"},types = String.class)里面的value值和map的键一样,
如果他们俩一样user就会既放在请求域也会放在session域中。 这样去session域里面找就会找到了。
总结描述:若implicitModel中不存在key对应的对象,则检查当前handler是否使用了@SessionAttributes注解修饰
若使用了该注解,且@SessionAttributes注解的value属性值中包含了key,则会从Httpsession中获取key所对应的value值
,若存在,则直接传入到目标方法的入参中,若不存在则抛出异常。
若handler没有标识@SessionAttributes注解或@SessionAttributes注解的value值不包含key,则会通过反射来创建POJO
类型的参数,作为目标方法的参数。
-----可以直接响应转发的页面,而无需经过handler的方法
<mvc:view-controller path="/success" view-name="success"/>
在实际开发中还需要配置下面的注解,不然其他的映射就不好用了。
<mvc:annotation-driven></mvc:annotation-driven>
为什么要加<mvc:annotation-driven></mvc:annotation-driven>这个配置?
<mvc:annotation-driven />会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter
ExceptionHandlerExceptionResolver这三个bean。
还将提供以下支持:
--支持使用ConversionService实例对表单参数进行类型转化。
--支持使用@NumberFormate @DateTimeFormate注解完成数据类型的格式化。
--支持使用@valid实例对JavaBean实例进行Jsr303验证。
--支持使用@RequestBody @ResponseBody注解
@InitBinder:
由@InitBinder标识的方法,可以对WebDateBinder对象进行初始化。WebDateBinder是DateBinder的子类,用于完成
表单字段到JavaBean属性的绑定。
@InitBinder方法不能有返回值,他必须声明为void
@InitBinder方法的参数通常是WebDateBinder
@InitBinder
public void initBinder(WebDateBinder binder){
binder.setDisallowedFields("name") 表单到JavaBean赋值的过程中,哪一个值不进行赋值。
}
数据类型转换与格式化:
比如说你现在添加一个用户,这个用户有一个属性是生日。你在添加的时候需要将你写在input框里面的字符串日期
转换为date类型的日期。
方法:首先要配<mvc:annotation-driven/>其次直接在类的属性上加@DateTimeFormate("pattern=yyyy-MM-dd")
比如说:
@DateTimeFormate("pattern=yyyy-MM-dd")
private Date birth
数据校验:
---如何校验?
①使用JSR303验证标准
②加入hibernate validator 验证框架的jar包
③在springMVC配置文件中添加<mvc:annotation-driven/>
④需要在bean的属性上添加相应注解
⑤在目标方法bean类型的前面添加@Valid注解
JSR 303是Java为Bean数据合法性校验提供的标准框架,他已经包含在JavaEE6.0中
JSR 303通过在bean属性上标注类似于@NotNull、@Max等标准的注解指定校验的规则,并通过标准的验证接口对Bean进行验证。
Hibernate Validator是JSR 303的一个参考实现,除支持所有的标准检验的注解外,它还支持以下的扩展注解:
@Email、@Length、@NotEmpth、@Range
SpringMVC数据校验:
Spring4.0拥有自己独立的数据校验框架,同时支持JSR 303标准的校验框架。
Spring本身并没有提供JSR 303的实现,所以必须将JSR 303实现者(Hibernate Validator)的jar包放到类路径下。
Spring的LocalValidatorFactoryBean既实现了Spring的valiator接口,也实现了JSR303的valiator接口,所以只要在
Spring容器中定义一个LocalValidatorFactoryBean,即可将其注入到需要校验的bean中。
怎么去定义或配LocalValidatorFactoryBean?
<mvc:annotation-driven></mvc:annotation-driven>会默认装配好一个LocalValidatorFactoryBean,通过在处理方法
的入参上标注@valid注解即可让SpringMVC在完成数据绑定后执行数据校验工作。
显示错误消息:
在form表单里的每个字段属性的下面加上
<form:errors path="lastName"></form:errors>
简单的说就是在JSP页面上可通过<form:errors path="lastName"></form:errors>显示错误消息
springMVC返回JSON
1、加入jackson相关的jar包
2、目标方法(controller里)直接返回你需要的对象或者集合
@RequestMapping("/testJSON")
@RequestBody
public Collection<Employee>testJSON(){
return employeeDao.getAll();
}
3、加一个@RequestBody注解
这样我们就可以完成发一个请求然后SpringMVC给我们返回一个json对象或者数组
springMVC返回JSON的背后原理:
其实它是需要HttpMessageConverter这样一个接口。
HttpMessageConverter<T>是Spring3.0新添加的一个接口,负责将请求信息转换为一个对象(类型是T),将对象(类型是T)输出
为响应信息
HttpMessageConverter的工作原理:
java对象
SpringMVC<----------HttpMessageConverter<-------HttpInputMessage<--------请求报文
SpringMVC---------->HttpMessageConverter------->HttpOutputMessage-------->响应报文
java对象
你发一个请求先转为HttpInputMessage再由HttpMessageConverter转为SpringMVC需要的对象,对应的SpringMVC返回一个
java对象,它通过HttpMessageConverter转为HttpOutputMessage对象,然后给出响应。
HttpInputMessage:
public interface HttpInputMessage extend HttpMessage{
InputStream getBody() throws IOException;
}
它返回的是一个输入流,响应的HttpOutputMessage得到的第一个输出流,输出流往外一写不就是响应信息了嘛。
使用HttpMessageConverter:
我们用他干什么?用它把请求信息转化为我们需要的对象传入到目标方法的入参或者把目标方法的返回值转化为
我们想返回给客户的类型返回给客户。
官方一点的说法:使用HttpMessageConverter将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型
的响应信息。
@RequestBody用来修饰目标方法的入参的。@ResponseBod用来修饰目标方法的。
例:
public String testHttpMessageConverter(@RequestBody String body){
比如说这个目标方法的入参使用了@RequestBody来修饰,这个参数是一个String类型
它就会把请求信息转化为String类型的字符串传到目标方法
}
关于国际化:
1、在页面上能够根据浏览器语言设置的情况对文本、时间、数值进行本地化处理
2、可以在bean中获取国际化资源文件Locale对应的消息。
3、可以通过超链接切换Locale,而不再依赖浏览器的语言设置情况。
解决:
1、使用JSTL的fmt标签
文件上传:
springMVC为文件上传提供了直接的支持,这种支持是通过即插即用的MultipartResolver(他其实是一个接口)实现的。
spring用Jakarta Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。
springMVC的上下文默认没有装配MultipartResolver,因此默认情况下不能处理文件的上传工作,如果想使用spring的文件
上传功能,需先在上下文配置MultipartResolver。
在springMVC的配置文件中配置MultipartResolver:
<bean id="commonsMultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="1024000"></property>
</bean>
拦截器:
首先想搞一个自定义的拦截器,需要先建一个类,然后实现HandlerInterceptor接口。重写里面的抽象方法
例:public class FirstInterceptor implements HandlerInterceptor{
public void afterCompletion(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, Exception arg3)throws Exception {
渲染视图之后被调用(在DispatcherServlet完全处理完请求后被调用)
可以在该方法中进行一些资源清理的操作
}
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2, ModelAndView arg3) throws Exception {
调用目标方法之后(业务处理器处理完请求之后)
渲染视图之前被调用 (DispatcherServlet向客户端返回响应之前被调用)
}
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2) throws Exception {
该方法在目标方法之前被调用。
若返回值为true,则继续调用后续的拦截器和目标方法
若返回值为false,则不会调用后续的拦截器和目标方法。
return true;
}
}
然后还需要在springMVC的配置文件中配置一下
<mvc:interceptors>
<bean class="com.iflytek.springmvc.intercept.FirstInterceptor"></bean>配置bean节点
</mvc:interceptors>
如果某一个拦截器的的preHandle方法返回一个false,那么后面的拦截器就不会被调用,而且目标方法也不会被调用。
知道了这三个方法的调用顺序,这三个方法具体有什么用呢?
----preHandle:
它在调目标方法之前被调用,可以考虑做权限(如果你没有权限就不让你调用目标方法)或者是日志、事务等
----postHandle
可以对请求域中的属性或视图做出修改。
----afterCompletion
可以做释放资源使用
<mvc:interceptors>
<bean class="com.iflytek.springmvc.intercept.FirstInterceptor"></bean>
<!-- 配置拦截器(不)作用的路径 -->
<mvc:interceptor>
<mvc:mapping path="/testParam"/>
<bean class="com.iflytek.springmvc.intercept.SecondInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
在<mvc:interceptors>里面可以在配一个<mvc:interceptor>指定拦截器作用于哪些路径和不作用于哪些路径
像上面的这个例子就是FirstInterceptor这个拦截器作用于所有的,而SecondInterceptor这个拦截器只作用于
testParam这个路径的请求。
多个拦截器的执行顺序:
对于preHandle按springMVC配置文件中配置的顺序执行,
对于postHandle按springMVC配置文件中配置的反序执行,
对于afterCompletion按springMVC配置文件中配置的反序执行。
FirstInterceptor#preHandle------->SecondInterceptor#preHandle
↓
↓
↓
HandlerAdapter#handle(目标方法)
↓
↓
↓
FirstInterceptor#postHandle←←←←←←SecondInterceptor#postHandle
↓
↓→→→→→→→→→→→→DispatcherServlet#render(渲染视图)
↓
↓
↓
FirstInterceptor#afterCompletion←←←←←←SecondInterceptor#afterCompletion
如果一个拦截器没有任何问题执行完了,那么是一定执行释放资源的这个afterCompletion方法。
如果某一个拦截器的preHandle方法已经返回false了,
我就不需要去释放资源了就不需要执行这个拦截器的afterCompletion方法了
异常处理: