一、基础加强
1、Myeclipse和eclipse
1)Myeclipse:是elipse的插件,用于开发javaEE的平台,为其打补丁,扩展其功能,可以开发JavaEE程序即web项目,但是现在Myeclipse也集成了eclipse的功能,可直接使用Myeclipse。
2)eclipse:是开发java的一款专业的IDE(集成开发环境),以前需要Myeclipse才能开发JavaEE程序,现在的eclipse的版本也可以不用再安装Myeclipse插件,直接可以开发JavaEE项目。
2、Myeclipse介绍
(1)project(工程):一个工程包含有:包,jar包,资源文件,java源文件等内容。将一个工程的所有源文件用一个工程来组织,开发工具能对所有源文件集中管理,记住每个源文件的位置和相互关系,配置信息等都在工程中有记录。
(2)preferences(首选项):在workspace(工作间)中配置,将会影响到它下面所有的工程,一个工作间包含多个工程,不同透视图就是几个小窗口的集合。
(3)调试:设置断点,在行的开头双击即可。 运行程序,用debug as -- 程序
打开debug界面
(4)perspective(透视图)和view(视图)的关系:
view:每一个小窗口就是一个视图。可通过window下的show view调出相应的视图。
perspective:透视图就是若干个小窗口的集合。
(5)将其他工作间中的工程拷贝到当前工作间所在的文件夹下。在eclipse中右击工程--->导入--->浏览选择当前工作间目录下拷贝过来的工程。
(6)配置JDK环境:右击工程--->BuildPath --->Libraries(库)--->移除导入的工程的JDK库,增加自己的库。
注:Add JARs是增加打个Jar包,
如果Jar包不在当前工程文件夹中,用Add Exernd JARs
如果Jar包在当前工程文件夹中,用Add JARs
增加一个库相当于增加很多Jar,这是一个打包的过程。
3、静态导入
当类名重名时,需要指定具体的包名。
当方法重名时,指定具体所属的对象或者类。
import语句:是导入一个类或某个包中的所有类。
import static语句:导入一个类中的某个静态方法或所有静态方法。
可参见 黑马程序员---集合框架 七、
4、增强 for循环
增强for循环(高级for循环)其实就是for循环的简写形式。并不是所有的for循环都可以简写为foreach,高级for循环只适用于集合或数组的遍历操作。
高级for循环格式:
for(数据类型 变量名: 被遍历的集合或数组) {循环体}
在对集合或数组的操作中,集合一般的遍历方式是使用迭代器遍历,而且可以做删除操作,ListIterator在遍历时还可以进行增删改查操作,但如果用高级for循环,就不能做这些操作。
高级for循环的局限性: 只能获取集合元素,但不能对集合进行操作。
参见 可参见 黑马程序员---集合框架 七、
5、可变参数
简化代码、提高效率。
在定义可变参数时,一定要把参数定义在参数列表的最后面。如果定义在了开始或中间,虚拟机就会自动的将该参数后面的不同类型的参数纳入该参数列表
import java.util.*;
class ParamDemo {
public static void main(String[] args) {
/*
int[] arr = {1,25,7};
show(arr);
*/
show(1,2,4,23);
}
/*
当将方法定义为这中形式时,在主函数中调用该方法时,
要先新建一个数组对象,才能调用方法,
而将方法定义为这中形式,在调用该方法时,就不需要新建数组对象,直接调用方法即可
JDK1.5新特性:方法的可变参数
简化代码、提高效率
在定义可变参数时,一定要把参数定义在参数列表的最后面。
如果定义在了开始或中间,虚拟机就会自动的将该参数后面的不同类型的参数纳入该参数列表
*/
public static void show(int... arr){
System.out.println(Arrays.toString(arr));
}
/*
当将方法定义为这中形式时,在主函数中调用该方法时,
要先新建一个数组对象,才能调用方法
public static void show(int[] arr){
System.out.println(Arrays.toString(arr));
}
*/
}
6、基本数据类型的拆装箱
Integer x1= 4;
System.out.println(x1+2);
自动装箱:x1代表的是一个对象,是引用数据类型,由于自动装箱功能,x1可以直接这样写。
自动拆箱:x1+2 ,将类类型变量直接作为值进行运算。
7、享元模式
我们知道在word中可以输入任意的26个字母,这些字母可以说视为26个对象。但是这26个字母有很多相同的属性,而且每个对象都十分小,那么,这时就可以将这26个字母最为一个对象使用。除了相同属性之外的不同属性可以作为参数传递给对象。这种对很多很小的对象创建为一个对象的模式就叫做享元模式。
二、枚举
枚举是一个特殊的类,而且是一个不可被继承的final类,其中的元素都是类静态常量,它的出现可以将程序的错误在编译时期发现。
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器会报错。枚举可以让编译器在编译时既可以控制源程序中填写的非法值,普通的变量的方式在开发阶段无法实现这一目标。
用枚举类规定值,如WeekDay1类。以后用此类型定义的值只能是这个类中规定好的那些值,若不是这些值,编译器不会通过。
如果想在一个类中编写完每个枚举类和测试调用类,那么可将枚举类定义成调用类的内部类。
import java.util.Date;
public class EnumTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//1、赋的值只能在类WeekDay中定义好的某个常量
WeekDay1 weekDay = WeekDay1.MON;
System.out.println(weekDay.nextDay());//SUN
//2、当定义一个WeekDay枚举类后,只能调用规定的值
WeekDay weekDay2 = WeekDay.FRI;
//自动将字符串打印
System.out.println(weekDay2);
System.out.println(weekDay2.name());
//排行
System.out.println(weekDay2.ordinal());
//静态方法,传递一个字符串,可将该字符串转换为对应的
System.out.println(WeekDay.valueOf("SUN").toString());
//返回数组
System.out.println(WeekDay.values().length);
//4、调用父类带参的构造方法
new Date(300){};
}
//2、定义枚举(枚举的基本应用)
public enum WeekDay{
//调用带参、无参的构造函数
SUN(1),MON(),TUE,WED,THI,FRI,SAT;
//3、构造方法必须定义在元素列表之后,若元素后还有内容,则用;
//构造方法必须私有修饰
//无参的构造函数
private WeekDay(){System.out.println("first");}
private WeekDay(int day){System.out.println("second");}
}
//4、实现带有抽象方法的枚举
public enum TrafficLamp{
//RED元素{}都是TrafficLamp的每个实例对象
RED(30){
public TrafficLamp nextLamp(){
return GREEN;
}
},
GREEN(45){
public TrafficLamp nextLamp(){
return YELLOW;
}
},
YELLOW(5){
public TrafficLamp nextLamp(){
return RED;
}
};
//返回值类型仍为该类类型
public abstract TrafficLamp nextLamp();
private int time;
//带参的构造函数
private TrafficLamp(int time){this.time = time;}
}
}
WeekDay1.java
public abstract class WeekDay1 {
private WeekDay1(){}
//常量 SUN是对象类型的值
public final static WeekDay1 SUN = new WeekDay1(){
//将抽象方法nextDay定义到每个SUN变量的内部
//这样就将大量的if else语句转移成了一个个独立的类
public WeekDay1 nextDay() {
return MON;
}
/*
* 采用抽象方法定义的nextDay可将大量的if else语句转换成了一个个独立的类。
public WeekDay1 nextDay() {
if(this == SUN){
return MON;
}else {
return SUN;
}
//return MON;
}
*/
//复写方法,将字符串打印
public String toString(){
return this == SUN?"SUN":"MON";
}
};
//实现枚举的抽象方法
public final static WeekDay1 MON = new WeekDay1(){
public WeekDay1 nextDay() {
// TODO Auto-generated method stub
return SUN;
}
};
//带有抽象方法的枚举
public abstract WeekDay1 nextDay();
/* public WeekDay nextDay(){
if(this == SUN){
return MON;
}else{
return SUN;
}
}
*/
public String toString(){
return this==SUN?"SUN":"MON";
}
}
若枚举中只有一个成员时,可用单例模式实现(银行调度)。
三、反射
1、概述:
我们知道,用类来描述具有相同属性和行为的事物,而类的实例(也就是这类事物实实在在的个体)则是该类在现实世界中真实存在的对象。比如,用Person来描述人,而世界上的每个实实在在的个体就是Person的实例对象。
而,反射,简单地说,就是用来描述这些类中的成分的。用毕老师的话说,万物皆对象。而描述万物,就用类来描述,而这些类也有一些共性:比如说,这些类都依存在自己所属的包中,这些类也都有他们自己的类名、属性和方法;而对于任意一个对象,都可以调用它的任意一个方法和属性。
那么,如何获得这些类中的内容呢?
2、Class类
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
简单地说,Class类映射的是该对象所在的类(即该字节码对应的实例对象)。
我们一般描述类用关键字class来标识,如:
class Person{
。。。。。
//创建对象
Person p1 = new Person();
Person p2 = new Person();
}
那么,如何获得该字节码对应的实例对象呢?根据我们以往的思考方式,类可以用class来标识,那么Class类该怎么创建呢?
是这样吗?Class cls = new Class(); 其实,这样是不对的。虽然Class是一个类,但他有点特殊。Class类的作用主要是获得该字节码所属的实例对象。
获得各个字节码对应的实例对象的方式:
类名.class
对象.getClass();
Class.forName(“类名”); 这是一个静态方法,用于接收字符串类型的变量
还有TYPE 语法
Class c1 = Boolean.TYPE;
Class c2 = Byte.TYPE;
Class c3 = Character.TYPE;
Class c4 = Short.TYPE;
Class c5 = Integer.TYPE;
Class c6 = Long.TYPE;
Class c7 = Float.TYPE;
Class c8 = Double.TYPE;
Class c9 = Void.TYPE;
下面举个例子:
用这三种方式验证”wangting”这个实例对象是否同属于一个字节码
//定义一个字符串对象
String str1 = "wangting";
Class cls1= String.class;
Class cls2 = new String().getClass();
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1==cls2); //true
System.out.println(cls1==cls3); //true
也可以通过Class类的isPrimitive方法判断该类是否是基本数据类型。
//是否是原始数据类型
System.out.println(cls1.isPrimitive());//false
System.out.println(int.class.isPrimitive()); //true
System.out.println(int.class == Integer.class); //false 基本数据类型和基本数据类型包装类
System.out.println(int.class==Integer.TYPE);//true
System.out.println(int[].class.isPrimitive());//false
//数组类型的Class实例对象 Class.isArray()
//只要在源程序中出现的类型,都有各自的Class实例对象例如,int[]、void等
System.out.println(int[].class.isArray());
3、Constructor类
Constructor类是用来获得某类中的构造方法。当然,这是在使用字节码的前提下使用的。
//1、Constructor类(构造方法)
System.out.println(new String(new StringBuffer("abc")));
//用反射的方法,实现new String(new StringBuffer("abc"));
//使用Constructor的getConstructor方法 返回类型仍为Constructor
Constructor cons =String.class.getConstructor(StringBuffer.class);
//再通过newInstance创建对象
cons.newInstance(new StringBuffer("abc")); //abc
//不能从Object类型转换为String类型
//String str = cons.newInstance(new StringBuffer("abc"));
//所以要将新建对象强转
String str2 = (String)cons.newInstance(new StringBuffer("wangting"));
//测试代码的功能是否实现,最简单的就是将结果打印看出现的问题。当然,实际开发不可打印
System.out.println(str2);
//2、Class.newInstance(); 如何实现????
// String.class.getConstructor(StringBuffer.class).newInstance("wangshan");
// String str3 =(String)String.class.newInstance(new StringBuffer("wangshan"));
4、Field类
public class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
ReflectPoint rp = new ReflectPoint(3,5);
//获得rp对象的字节码和ReflectPoint类上的字段。但这样并不能得到
Field fieldY = rp.getClass().getField("y");
System.out.println(fieldY.get(rp));
Field fieldX = rp.getClass().getField("x");
//因为x是私有的,所以报异常
//java.lang.NoSuchFieldException: x 没有找到这个字段
fieldX.setAccessible(true);
System.out.println(fieldX.get(rp));
成员变量反射的综合案例:
ReflectPoint.java
public class ReflectPoint {
private int x;
public int y;
public String str1 = "ball";
public String str2 = "baseketball";
public String str3 = "wangting";
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
//复写Object类中的toString方法
@Override
public String toString(){
return str1+"---"+str2+"---"+str3;
}
}
ReflectTest.java
//综合案例:
changeStringValue(rp);
System.out.println(rp);
public static void changeStringValue(Object obj) throws Exception{
//首先获得该类的字节码 文件,再获得该类中的字段
Field[] fields = obj.getClass().getFields();
for (Field field : fields){
//if (field.getType().equals(String.class)){}
//一般比较字节码用==
//如果传入的类型和该类型字节码恒等
if(field.getType() == String.class){
String oldValue = (String)field.get(obj);
String newValue = oldValue.replace('b', 't');
field.set(obj, newValue);
}
}
}
5、Method类
Method类表示的是类中的成员方法(字节码中的方法)。所以,先要获得字节码方法。
我们知道,不同的对象可以调用同一个类中的方法:
str1.charAt();
str2.charAt();
那么,如何使用反射做同样的操作呢?
String str1 = "wangting";
//一般方法调用charAt()
System.out.println(str1.charAt(3));
//反射
//获得字节码方法,再用字节码方法调用getMethod方法,最后methodCharAt方法对象再调用invoke方法
Method methodCharAt = String.class.getMethod("charAt", int.class);
//注意,字节码方法中都为字节码类型的参数列表
System.out.println(methodCharAt.invoke(str1, 3));
这是JDK1.5版本之后的新特性
6、用反射方式执行某个类中的main方法:
对接收数组参数的成员方法进行反射.
//TestArguments.main(new String[]{"111","222","333"});
String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
//mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}});
mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
7、数组与Object的关系及其反射类型。
int [] a1 = new int[]{1,2,3};
int [] a2 = new int[4];
int[][] a3 = new int[2][3];
String [] a4 = new String[]{"a","b","c"};
System.out.println(a1.getClass() == a2.getClass());
//System.out.println(a1.getClass() == a4.getClass());
//System.out.println(a1.getClass() == a3.getClass());
//获得数组a1的字节码及名称
System.out.println(a1.getClass().getName());
//获得a1的字节码,并通过该字节码获得其父类字节码及名称
System.out.println(a1.getClass().getSuperclass().getName());
//获得a4的字节码,并通过该字节码获得其父类字节码及名称
System.out.println(a4.getClass().getSuperclass().getName());
//Objeect的反射
Object aObj1 = a1;
Object aObj2 = a4;
//Object[] aObj3 = a1;
Object[] aObj4 = a3;
Object[] aObj5 = a4;
System.out.println(a1);
System.out.println(a4);
//将数组变成List集合
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a4));
printObject(a4);
printObject("xyz");
8、ArrayList和HashSet比较
ArrayList集合的底层数据结构是数组结构,且是有序集合,每个元素有对应的索引(角标),如果两个元素相同,则按照顺序存入,
HashSet底层是哈希表结构,存入或取出没有顺序,如果存入的两个元素相同,先判断该元素的hashCode是否相同,若相同,则再比较两个元素是否是同一对象,若是,第二个元素不再存入。
四、JavaBean
JavaBean是一种特殊的java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。方法中不接收参数。
1、javaBean的属性:根据方法确定,这里属性是Age
如果第二个字母是小写的,则把第一个字母变成小写(去除get或set,剩下的是属性名)。如:
方法 属性名
gettime--->time
setTime--->time
getCPU--->CPU
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个javaBean中,这种javaBean的实例对象通常称之为值对象(Value-Object)。这些信息在类中用私有字段来存储,如果速去或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?javaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,
2、对javaBean的内省操作
JDK中提供了对javaBean进行操作的API,这套API成为内省(IntroSpector)。
private static void setProperties(Object pt1, String propertyName,
Object value) throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
//1、属性描述符,接收属性名和所对应的自己吗,通过获得只读方法获取(返回值类型为Method)
//再调用invoke方法
PropertyDescriptor pd2 = new PropertyDescriptor(propertyName,pt1.getClass());
Method methodSetX = pd2.getWriteMethod();
methodSetX.invoke(pt1,value);
}
Map map = {name:"zxx",age:18};
BeanUtils.setProperty(map, "name", "lhm");
3、BeanUtils工具包操作javaBean:和上面的抽取的方法差不多,就是别人已经定义好了的内省方法。只需要导入别人的jar包使用就可以了。
五、注解
一个注解就是一个类(实例对象)。注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记。javac编译器,开发工具和其他程序 可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记, 就去干相应的事。
注意:标记可以加在包,类,字段,方法,方法的参数以及局部变量上,还可以加在注解上,这种注解为元注解。
@SuppressWarnings("deprecation"):压缩警告
@Deprecated :过时警告
@Override:重写方法注解
注解的定义格式:@ interface 注解名 {}
为注解添加属性:
为注解添加属性类似于为接口添加方法一样,例String color();这个语句就表示为注解添加一个字符串类型的属性color,那么在为别的类加上这个注解的时候就必须以@ 注解名(属性 = 值)的方式为每个属性赋值
属性的默认值:也可以在定义注解属性的时候就为这个属性设定一个默认值:String color() default "值";这样的话在添加注解的时候就可以不再指定这个属性的值,系统会自动将其设置为这个默认值;
value属性:当一个注解在被添加时只有一个名为value的属性需要设置值的时候就可以用@ 注解名(值)的方式直接填写值,而省略"value ="这一部分;
为注解添加其他高级属性:
数组属性:如int[] arrayAttr() default {1,2,3}; 如果数组属性的值只有一个元素,那么在赋值的时候可以省略大括号;即类似于@ 注解名(arrayAttr = 3)
枚举属性:如TrafficLamp lamp() default TrafficLamp.RED;
注解属性:如Annotation annotationAttr() default @ 注解名(属性名=属性值...)
六、泛型
JDK1.5版本以后出现新特性。用于解决安全问题,是一个类型安全机制。
1、ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
2、ArrayList称为原始类型,
参数化类型与原始类型的兼容性:
(1)参数化类型可以引用一个原始类型的对象,编译报告警告,
Collection<String> c = new Vector();
(2)原始类型可以引用一个参数化类型的对象,编译报告警告。
Collection c = new Vector<String>();
(2)参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错
Vector<Object> v = new Vector<String>(); //也错误!编译器不允许创建泛型变量的数组。
在类中String extends Object是可以的,但在泛型中不可以。
(3)即在创建数组实例时,数组的元素不能使用参数化的类型,下面语句有错误: Vector<Integer> vectorList[] = new Vector<Integer>[10];
3、好处:
将运行时期出现问题ClassCastException,转移到了编译时期。, 方便于程序员解决问题。让运行时问题减少,安全。
避免了强制转换麻烦。
4、泛型格式:
通过<>来定义要操作的引用数据类型。
ArrayList<String> al = new ArrayList<String>();
在使用java提供的对象时,什么时候写泛型呢?
通常在集合框架中很常见,只要见到<>就要定义泛型。可以使得存取的数据类型一致,避免强转的麻烦。
其实<> 就是用来接收类型的。当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
什么时候定义泛型类?当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展。现在定义泛型来完成扩展
泛型定义在接口上。
interface Inter<T>{ void show(T t);}
5、
? 通配符。也可以理解为占位符。
泛型的限定;
? extends E: 可以接收E类型或者E的子类型。上限。
? super E: 可以接收E类型或者E的父类型。
下限限定通配符的上边界:正确:
Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下边界:正确:
Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
提示:限定通配符总是包括自己。
?只能用作引用,不能用它去给其他变量赋值
Vector<? extends Number> y = new Vector<Integer>();
Vector<Number> x = y; 上面的代码错误,原理与Vector<Object > x11 = new Vector<String>();相似, 只能通过强制类型转换方式来赋值。
反射在泛型中的应用:反射方式可以后期往里面加入别的元素。因为编译完后是去类型化的
七、类加载器
1、概述
简单的说,类加载器就是加载类的工具。我们编写的每个类由JVM解析为字节码加载并存储在硬盘上(classpath路径下),并经过一系列的处理,返回处理好的结果给程序员。
Java虚拟机中可以安装多个类加载器,有三个是系统默认的:BootStrap、ExtClassLoader、AppClassLoader。他们分别负责加载特定位置的类。
BootStrap--顶级类加载器:类加载器本身也是Java类,因为它是Java类,本身也需要加载器加载,显然必须有第一个类加载器而不是java类的,这正是 BootStrap。它是嵌套在Java虚拟机内核中的,已启动即出现在虚拟机中,是用c++写的一段二进制代码。所以不能通过java程序获取其名字, 获得的只能是null。
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
/*
* 打印ClassLoaderTest类的加载器 sun.misc.Launcher$AppClassLoader
* 通过类名.class获得该类字节码对应的实例对象,该实例对象调用getClassLoader方法(该方法
* 返回的也是一个实例对象),再获得此加载器对象的字节码,获得此加载器名称
* */
System.out.println(
ClassLoaderTest.class.getClassLoader().getClass().getName()
);
//System类没有加载器,打印null。因为该类的加载器是根加载器(BootStrap),即JVM自带的加载器
System.out.println(
System.class.getClassLoader()
);
System.out.println("xxx");
//通过类名.class获得该类的实例对象,根据对象获得该类的加载器,返回值仍为一个加载器
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
//如果该加载器不为null
while(loader != null){
//打印该类的加载器名称 sun.misc.Launcher$AppClassLoader
System.out.println(loader.getClass().getName());
//获得该类的父类类加载器 sun.misc.Launcher$ExtClassLoader
loader = loader.getParent();
}
//null 父类的父类加载器BootStrap
System.out.println(loader);
}
}
2、类加载器的委托机制
加载类的方式:
首先,当前线程的类加载器去加载线程中的第一个类。
通过查阅API文档可以知道,在线程的方法中可以获得该线程的加载器。如果未设定,则默认为父线程的 ClassLoader 上下文。原始线程的上下文 ClassLoader 通常设定为用于加载应用程序的类加载器。
2)若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B。也就是说,java虚拟机在加载A类时,会使用B类的类加载器加载A。
3)还可直接调用ClassLoader的LoaderClass()方法,来制定某个类加载器去加载某个类。
加载器的委托机制:每个类加载器加载类时,优先委托给上级类加载器。
每个ClassLoader本身只能分别加载特定位置和目录中的类,但他们可以委托其他类的加载器去加载,这就是类加载器的委托模式,类加载器一级级委托到BootStrap类加载器,当BootStrap在指定目录中没有找到要加载的类时,无法加载当前所要加载的类,就会一级级返回子孙类加载器,进行真正的加载,每级都会先到自己相应指定的目录中去找,有没有当前的类;直到退回到最初的类装载器的发起者时,如果它自身还未找到,未完成类的加载,那就报告ClassNoFoundException的异常。
3、委托机制的优点:可以集中管理,不会产生多字节码重复的现象。
可不可以自己写个类为:java.lang.System呢?
通常是不可以的,由于类加载器的委托机制,会先将System这个类一级级委托给最顶级的BootStrap,由于BootStrap 在其指定的目录中加载的是rt.jar中的类,且其中有System这个类,那么就会直接加载自己目录中的,也就是Java已经定义好的System这个 类,而不会加载自定义的这个System。
但是还是有办法加载这个自定义的System类的,此时就不能交给上级加载了,需要用自定义的类加载器加载,但是要抛开类加载器的委托机制,这就需要有特殊的写法才能去加载这个自定义的System类的。
4、自定义类加载器
类加载器是负责加载类的对象。如果要自定义类加载器就要让该自定义加载器类继承ClassLoader 。ClassLoader 类是一个抽象类,里面的方法时抽象的,这些抽象方法只是提供的一种功能,具体该功能是什么,就需要子类定义自己的特殊内容。
父级-->loadClass-->findClass-->得到Class文件后转化成字节码-->defineClass()。
具体步骤:自定义子类继承ClassLoader 抽象类,复写findClass()方法,自定义自己的特殊内容。这里不需要复写loadClass()方法,该方法默认的实现方式为:内部调用findLoadedClass(String)方法来检查是否已经加载类,在父类加载器上调用loadClass方法,如果父类加载器为null,则使用虚拟机的内置加载器,然后再调用findClass方法查找类。
八、代理类及AOP概念
1、概述
在我们日常的生活中,有很多代理的例子。比如,某个服装品牌的代理商,他们根据总部的安排负责某个地区该品牌的销售业务。那么我们就可以在该代理商的手里买衣服。就不用很麻烦的去总部购买。
程序中的代理:
要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如异常处理、日志、计算方法的运行时间、事物管理等等。
简单地说,就是将一些额外的系统功能单独的定义在某些类中,然后在原系统中只要调用该类的这些功能方法就可以了。当然类中的方法应该是静态的才可以直接用类名调用。
但是,要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,这时就不能采用静态代理方式,需用动态代理技术。
2、动态代理技术
JVM可在运行时,动态生成类的字节码,这种动态(不是代理,只是拿出来作为代理类)生成的类往往被用作代理类,即动态代理类。
注:JVM生成的动态类必须实现一或多个接口,所以JVM生成的动态代理类只能用作具有相同接口的目标类代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以作为该类的代理,所以,如果要为一个没有实现接口的类生成动态代理,那么可以使用CGLIB库。
代理类各个方法通常除了调用目标相应方法和对外返回目标返回的结果外,还可以再代理方法中的如下位置上加上系统功能代码:
1)在调用目标方法之前
2)在调用目标方法之后
3)在调用目标方法前后
4)在处理目标方法异常的catch块中。
3、实现类似spring的可配置的AOP框架
(1)工厂类BeanFactory:
工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。
接受一个配置文件,通过Properties对象加载InputStream流对象获得。
创建getBean(String name)方法,接收Bean的名字,从上面加载后的对象获得。
通过其字节码对象创建实例对象bean。
判断bean是否是特殊的Bean即ProxyFactoryBean,如果是,就要创建代理类,并设置目标和通告,分别得到各自的实例对象,并返回代理类实例对象。如果不是在返回普通类的实例对象。
BeanFactory的构造方法接收代表配置文件的输入流对象的配置文件格式如下:
新建文件:cofig.properties
#xxx=java.util.ArrayList
xxx=cn.itcast.test3.aopframework.ProxyFactoryBean
xxx.advice=cn.itcast.test3.MyAdvice
xxx.target=java.util. ArrayList
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import cn.itcast.day3.Advice;
public class BeanFactory {
Properties props = new Properties();
//创建对象时需要传入一个配置文件中的数据,所以需要在构造方法中接受一个参数
public BeanFactory(InputStream ips){
try {
//将配置文件加载进来
props.load(ips);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//创建getBean方法,通过配置文件中的名字获取bean对象
public Object getBean(String name){
//从配置文件中读取类名
String className = props.getProperty(name);
Object bean = null;
try {
//由类的字节码获取对象
Class clazz = Class.forName(className);
bean = clazz.newInstance();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//判断bean是特殊的bean即ProxyFactoryBean还是普通的bean
if(bean instanceof ProxyFactoryBean){
Object proxy = null;
//是ProxyFactoryBean的话,强转,并获取目标和通告
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
try {
//获取advice和target
Advice advice = (Advice)Class.forName(props.getProperty(name + ".advice")).newInstance();
Object target = Class.forName(props.getProperty(name + ".target")).newInstance();
//设置目标和通告
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
//通过类ProxyFactoryBean(开发中是作为接口存在)中获得proxy对象
proxy = proxyFactoryBean.getProxy();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//是ProxyFactoryBean的话,返回proxy对象
return proxy;
}
//否则返回普通bean对象
return bean;
}
}
(2)创建ProxyFactoryBean(接口),此处用类做测试,其中有一个getProxy方法,用于获得代理类对象。ProxyFactoryBean充当封装成动态的工厂,需为工厂提供的配置参数信息包括:
目标(target)
通告(advice)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactoryBean {
//定义全局变量
private Advice advice;
private Object target;
//生成get和set方法
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy() {
// TODO Auto-generated method stub
Object proxy3 = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
/*new Class[]{Collection.class},*/
//这里的接口要和target实现相同的接口
target.getClass().getInterfaces(),
new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*long beginTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + " running time of " + (endTime - beginTime));
return retVal;*/
//通过契约,使用其方法--before和after方法
advice.beforeMethod(method);
Object retVal = method.invoke(target, args);
advice.afterMethod(method);
return retVal;
}
}
);
return proxy3;
}
}
BeanFactory和ProxyFactoryBean:
BeanFactory是一个纯粹的bean工程,就是创建bean即相应的对象的工厂。
ProxyfactoryBean是BeanFactory中的一个特殊的Bean,是创建代理的工厂。
对配置文件进行配置,
#xxx=java.util.ArrayList
xxx=cn.itcast.test3.aopframework.ProxyFactoryBean
xxx.advice=cn.itcast.test3.MyAdvice
xxx.target=java.util. ArrayList
注意:其中的#代表注释当前行
此时,如果传入的是java.util.ArrayList的话,返回的结果就是这个对象的名字
否则,如果是特殊的bean对象,那么返回的就是$Proxy这个名字,是一个ProxyFactoryBean对象。
(3)作一个测试类:AopFrameworkTest进行测试。
import java.io.InputStream;
import java.util.Collection;
public class AopFrameworkTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//读取配置文件的数据
InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
//获取bean对象
Object bean = new BeanFactory(ips).getBean("xxx");
System.out.println(bean.getClass().getName());
((Collection)bean).clear();
}
}