七:泛型
1:Jdk 1.5以前的集合类中存在什么问题
ArrayList collection = new ArrayList();
collection.add(1);
collection.add(1L);
collection.add("abc");
int i = (Integer) collection.get(1);//编译要强制类型转换且运行时出错!
2:Jdk 1.5的集合类希望你在定义集合时,明确表示你要向集合中装哪种类型的数据,无法加入指定类型以外的数据
ArrayList<Integer> collection2 = new ArrayList<Integer>();
collection2.add(1);
/*collection2.add(1L);
collection2.add(“abc”);*///这两行代码编译时就报告了语法错误
int i2 = collection2.get(0);//不需要再进行类型转换
3:泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,
编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,
getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会擦除泛型的类型信息,只要能跳
过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
4:在JDK 1.5中引入范型后,Object.getClass()方法的定义如下:
public final Class<? extends Object> getClass()
这说明((Test)obj).getClass()语句返回的对象类型为Class<? extends Test>,
而Class<T>的newInstance()方法的定义如下:
public T newInstance() throws InstantiationException,IllegalAccessException即对于编译器看来,
Class<Test>的newInstance()方法的对象类型为Test,而((Test)obj).getClass()返回的为对象类型为Class<? extends Test>
,
所以,编译器认为((Test)obj).getClass().newInstance()返回的对象类型为Test。
泛型和反射结合,可以往<Integer>类型的集合中避开编译器,添加其他类型的数据.
5:了解泛型
(1)ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为原始类型
(2)参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译报告警告,例如, Collection<String> c = new Vector();//可不可
以,不就是编译器一句话的事吗?
原始类型可以引用一个参数化类型的对象,编译报告警告,例如, Collection c = new Vector<String>();//原来的
方法接受一个集合参数,新的类型也要能传进去
(3)参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
(4)编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,
例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
思考题:下面的代码会报错误吗?
Vector v1 = new Vector<String>();
Vector<Object> v = v1;
此代码不会出错,因为编译器在编译代码时是一行一行的编译,不会把两行嗲吗结合在一块.
6:泛型的通配符
问题:
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
错误方式:
public static void printCollection(Collection<Object> cols) {
for(Object obj:cols) {
System.out.println(obj);
}
/* cols.add("string");//没错
cols = new HashSet<Date>();//会报告错误!*/
}
正确方式:
public static void printCollection(Collection<?> cols) {
for(Object obj:cols) {
System.out.println(obj);
}
//cols.add("string");//错误,因为它不知自己未来匹配就一定是String
cols.size();//没错,此方法与类型参数没有关系
cols = new HashSet<Date>();
}
总结:
使用"?"通配符可以引用其他各种参数化的类型,"?"通配符定义的变量主要用作引用,
可以调用与参数化无关的方法(如:size),不能调用与参数化有关的方法(如:add())。
7: 泛型中的 ? 通配符的扩展
(1)限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
(2)限定通配符的下边界:
正确: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>();相似,
只能通过强制类型转换方式来赋值。
8:自定义泛型
(1)泛型放在方法上:
权限修饰符 [static] <T> T 名称(T x,T y){return XXX}
例1:public static <T> T add(T x,T y){
return x
//return x+y 此处不能使用 "+" 因为此处类型不确定,并不是所有的类型都可以使用"+"的
}
例2:public static <T> void swap(T x,T y){}//无返回值
泛型的实际类型只能是引用类型,不能是基本类型.
9:泛型方法的定义和使用
(1)Java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:
<T> T add(T x,T y) {
return (T) (x+y);
//return null;
}
用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,
也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。
(2)交换数组中的两个元素的位置的泛型方法语法定义如下:
static <E> void swap(E[] a, int i, int j) {
E t = a[i];
a[i] = a[j];
a[j] = t;
}//或用一个面试题讲:把一个数组中的元素的顺序颠倒一下
(3)只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误。
(4)除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,
例如,Class.getAnnotation()方法的定义。并且可以用"&"来指定多个边界,
如<V extends Serializable & cloneable> void method(){}
(5)普通方法、构造方法和静态方法中都可以使用泛型。
(6)也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
在catch语句中必须确定类型.
(7)在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) { return map.get(key);}
例1:
class Demo
{
public static void main(String[] args)
{
String abc = "abc";
autoConvert(abc);
}
//将Object类型转换成T类型.
public static <T> T autoConvert(Object obj){
return (T)obj;
}
//定义一个方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象.
private static <T> void fillArray(T[] a,T obj){
for(int i=0;i<a.length;i++){
a[i] = obj;
}
}
}
10:类型参数的类型推断
(1)编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,
其实现方法是一种非常复杂的过程。
(2)根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
(a)当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,
那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,
即直接根据调用方法时传递的参数类型或返回值7来决定泛型参数的类型,例如:
swap(new String[3],3,4)----->static <E> void swap(E[] a, int i, int j)
(b)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时
这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5) ---> static <T> T add(T a, T b)
(c)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时
这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大
交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f)---> static <T> void fill(T[] a, T v)
(d)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时
这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,
例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,
对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f) ---> static <T> T add(T a, T b)
(e)参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,
而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5])--->static <T> void copy(T[] a,T[] b);
copy(new Vector<String>(), new Integer[5])--->static <T> void copy(Collection<T> a , T
[] b);
例:在自定义方法copy中把集合中的元素复制到数组中.
集合内容拷贝进数组中时:
public static <T> void copy(Vector<T> v,T[] a){
}
copy1(new Date[10],new String[10]); //不会出错
copy2(new Vector<String>(),new String[10]); //不会报错
copy2(new Vector<Date>(),new String[10]); //会报错
crud 数据库的增删改查.
11:泛型类的定义和使用.
(1)如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采
用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
public class GenericDao<T> {
private T field1;
public void save(T obj){}
public T getById(int id){}
}
(2)类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
GenericDao<String> dao = null;
new genericDao<String>();
(3)注意:
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。
因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
问题:类中只有一个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?
使用方法级别的泛型.如果类中的多个方法使用泛型,则使用类级别的泛型.
12:通过反射获得泛型的实际类型参数.
例1:
Class GenericalReflection {
private Vector<Date> dates = new Vector<Date>();
public void setDates(Vector<Date> dates) {
this.dates = dates;
}
public static void main(String[] args) {
Method methodApply = GenericalReflection.class.getDeclaredMethod("applyGeneric",
Vector.class);
ParameterizedType pType = (ParameterizedType)
(methodApply .getGenericParameterTypes())[0];
System.out.println("setDates("
+ ((Class) pType.getRawType()).getName() + "<"
+ ((Class) (pType.getActualTypeArguments()[0])).getName()
+ ">)" );
}
}
例2:泛型DAO的应用:
public abstract class DaoBaseImpl<T> implements DaoBase<T> {
protected Class<T> clazz;
public DaoBaseImpl() {
Type type = this.getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) type;
this.clazz = (Class) pt.getActualTypeArguments()[0];
System.out.println("clazz = " + this.clazz);
}
}
public class ArticleDaoImpl extends DaoBaseImpl<Article> implements ArticleDao {}
八:类加载器 ClassLoader
1:简要介绍什么是类加载器和类加载器的作用
2:Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,
每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
3:类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,
显然必须有第一个类加载器不是java类,这正是BootStrap(BootStrap随着java虚拟机内核的启动而加载,是用C++写的一段代
码)。
4:Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,
需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
例:
class Demo
{
public static void main(String[] args)
{
ClassLoader loader = Demo.class.getClassLoader();//得到本类的类加载器对象.
while(loader!=null){
System.out.println(loader.getClass().getName()); //打印类加载器对象的名字.
loader = loader.getParent(); //得到此类加载器的父类加载器对象.
}
System.out.println(loader); //打印出最终的类加载器对象.
}
}
5:类加载器的委托机制
(1)每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,
这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要
加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它
自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
(2)当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
a:首先当前线程的类加载器去加载线程中的第一个类。
b:如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
c:还可以直接手动调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
(3)每个类加载器加载类时,又先委托给其上级类加载器。
a:当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,
不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
b:对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下
的itcast.jar包中后,运行结果为ExtClassLoader的原因。
面试题:,能不能自己写个类叫java.lang.System,为了不让我们写System类,类加载采用委托机制,
这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System。
一般不可以,因为类加载器的委托机制,在BootStrap中就会找到System类,所以不会执行自己写的System类的,如果抛开
委托机制的话就可以使用自己写的System类.
6:自定义类加载器
(1)自定义的类加载器必须继承抽象类ClassLoader
不复写Class<?> loadClass(String name)方法
复写findClass(String name)方法
protected Class<?> findClass(String name) 使用指定的二进制名称查找类。
protected Class<?> defineClass(String name, byte[] b, int off, int len) 将一个 byte 数组转换为 Class
类的实例。
例:class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the class data from the connection
. . .
}
}
(2)编程步骤:
a:编写一个对文件内容进行简单加密的程序。
b:编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
c:编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。
程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,
然后再使用Class.forName。
例;
import java.io.*;
import java.lang.reflect.*;
public class MyClassLoader extends ClassLoader
{
private String path = null;
public MyClassLoader(String path) throws Exception//检查文件是否存在
{
File f = new File(path);
if(!f.isDirectory())
{
throw new RuntimeException(path + " is not a directory");
}
this.path = path;
}
public Class findClass(String name) //throws Exception //为什么不能抛出
{
try
{
File f = new File(path,name.substring(name.lastIndexOf('.')+1) + ".class");
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
byte [] buf = bos.toByteArray();
fis.close();
bos.close();
return defineClass(name,buf,0,buf.length);
}catch(Exception e)
{
throw new ClassNotFoundException(name + " is not found!");
}
return null;
}
public static void cypher(InputStream istream,OutputStream ostream) throws Exception
{
//下面这段代码可能遇到255的字节,当成byte就成了-1
/*byte b = 0;
while((b = (byte)istream.read()) != -1)
{
ostream.write(b ^ 0xff);
}*/
int b = 0;
while((b = istream.read()) != -1)
{
ostream.write(((byte)b) ^ 0xff);
}
}
public static void main(String [] args) throws Exception
{
//下面省略了错误检查
if(!args[0].endsWith("class"))
{
ClassLoader loader = new MyClassLoader(args[1]);
Class cls = loader.loadClass(args[0]);
/*
让自定义类继承Date类
System.out.println(cls.getClassLoader().getClass().getName());
java.util.Date d = (java.util.Date)cls.newInstance();
System.out.println(d.toString());
*/
//Method m = cls.getMethod("test",null);//在jdk1.5中报警告,为什么?
Method m = cls.getMethod("test");
//m.invoke(cls.newInstance(),null);
m.invoke(cls.newInstance());
//((Test)cls.newInstance()).test();
return;
}
else
{
FileInputStream fis = new FileInputStream(args[0]);
File f = new File(args[1], new File(args[0]).getName());//不用检查目录最后是否有目录分
割符
FileOutputStream fos = new FileOutputStream(f);
cypher(fis,fos);
fis.close();
fos.close();
}
}
}
九:代理
1:代理的概念与作用
(1)生活中的代理
武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,
你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点
区别都没有吗?从代理商那里买真的一点好处都没有吗?
(2)程序中的代理
a:要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,
例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?
b:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,
并在调用方法时加上系统功能的代码。
c:如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,
在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,
想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,
以后运行一段时间后,又想去掉系统功能也很容易。
2:面向方面的编程(AOP)
(1)系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ------|----------|------------|-------------
CourseService ------|----------|------------|-------------
MiscService ------|----------|------------|-------------
(2)用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
(3)交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。
可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
(4)使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
3:动态代理技术
(1)要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,
将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
(2)JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
(3)JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
(4)CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,
如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
(5)代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,
还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
例;
Class proxy{
void sayHello(){
……….
try{
target.sayHello();
}catch(Exception e){
………..
}
………….
}
}
4:使用JVM动态生成类.
Proxy类提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类.
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。
第一个参数是一个类加载器对象,第二个参数是所实现的接口.class
第一个参数通常使用第二个参数所实现接口的类加载器,
例:Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
(1)创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
(2)编码列出动态类中的所有构造方法和参数签名
(3)编码列出动态类中的所有方法和参数签名
(4)创建动态类的实例对象
用反射获得构造方法
编写一个最简单的InvocationHandler类
调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。
将创建动态类的实例对象的代理改成匿名内部类的形式编写,锻炼大家习惯匿名内部类。
(5)总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
a:生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
b:产生的类字节码必须有个一个关联的类加载器对象;
c:生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,
把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个
InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。
在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
(6)用Proxy.newInstance方法直接一步就创建出代理对象。
例:
import java.lang.reflect.Proxy;
import java.util.Collection;
class ProxyDemo
{
public static void main(String[] args)
{
//返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。
Class classProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
System.out.println(classProxy1.getName());
Method[] methods = clazz.getMethods();
for(Method m:methods)
{
System.out.println(m.getName());
}
System.out.println("---------------------");
Constructor[] contructors = clazz.getConstructors();
for(Constructor c:contructors)
{
System.out.print(c.getName());
StringBuilder sBuilder = new StringBuilder();
sBuilder.append('(');
Class[] paramTypes = c.getParameterTypes();
for(Class paramType:paramTypes)
{
sBuilder.append(paramType.getName());
sBuilder.append(',');
}
if(paramTypes.length>1)
sBuilder.deleteCharAt(sbf.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
}
// 用下面的代码是不可以的,因为没有无参构造.
//clazz.newInstance() ;
//clazz.getConstructor().newInstance();
Constructor constructor = clazz.getConstructor(InvocationHandler.class); //使用有参构造,并传一个
InvacationHandler.class对象.
//使用内部类.
private class MyInvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
//这么做而不是直接用4.5步,是担心基础差点的同学看不懂
System.out.println(obj);//注意,结果为null,其实obj部位null,是其toString方法搞的鬼!
System.out.println(obj.getClass().getName());
obj.clear();//这里为什么要选择一个没有返回值的方法呢?
//proxy.add(“xyz”);//调用有基本类型返回值的方法会抛出异常
//System.out.println(proxy.size());//
Collection proxy= (Collection)constructor.newInstance(new InvocationHandler(){
@Override
//这里的三个参数正好就是proxy.add(obj)中的三部分 。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
}
}
5:让动态生成的类变成目标类的代理
(1)怎样将目标类传进去?
a:直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。
b:为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
c:让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
(2)将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象,
让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。
(3)将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供?
a:把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,
接收者只要调用这个对象的方法,即等于执行了外界提供的代码!
b:为bind方法增加一个Advice参数。
6:编写可生成代理和插入通告的通用方法
例:完整代码
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
public class ProxyTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazz.getName());
Constructor[] constructors = clazz.getConstructors();
for(Constructor constructor : constructors){
System.out.print(constructor.getName());
Class[] params = constructor.getParameterTypes();
StringBuilder sBuilder = new StringBuilder();
sBuilder.append('(');
for(Class param : params){
sBuilder.append(param.getName()).append(',');
}
if(sBuilder.length()>1) {
sBuilder.deleteCharAt(sBuilder.length()-1);
}
sBuilder.append(')');
System.out.println(sBuilder);
}
System.out.println("----------method list------------");
Method[] methods = clazz.getMethods();
for(Method method : methods){
System.out.print(method.getName());
Class[] params = method.getParameterTypes();
StringBuilder sBuilder = new StringBuilder();
sBuilder.append('(');
for(Class param : params){
sBuilder.append(param.getName()).append(',');
}
if(sBuilder.length()>1) {
sBuilder.deleteCharAt(sBuilder.length()-1);
}
sBuilder.append(')');
System.out.println(sBuilder);
}
final Collection target = new ArrayList();
Collection proxy = getProxy(target,new Advice(){
public void afterMethod() {
System.out.println("来传智学习啦");
}
public void beforeMethod() {
// TODO Auto-generated method stub
System.out.println("从传智毕业习啦");
}
});
proxy.add("aa");
proxy.add("bb");
System.out.println(proxy.size());
//proxy.clear();
System.out.println(proxy.toString());
System.out.println(((ArrayList)target).get(0));
}
private static Collection getProxy(final Collection target ,final Advice advice)
throws NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException {
Class clazz1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
class MyInvocationHander implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
advice.beforeMethod();
Object retVal = method.invoke(target, args);
advice.afterMethod();
return retVal;
}
}
//Collection proxy = (Collection)clazz.newInstance();
Constructor constructor = clazz1.getConstructor(InvocationHandler.class);
Collection proxy = (Collection)constructor.newInstance(new MyInvocationHander());
return proxy;
}
}
7:实现AOP功能的封装与配置
(1)工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。
其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置
文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该
类实例对象的getProxy方法返回的对象。
(2)BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice
(3)ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
目标
通知
(4)编写客户端应用:
编写实现Advice接口的类和在配置文件中进行配置
调用BeanFactory获取对象
例:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
InputStream ips = BeanFactory.class.getResourceAsStream("config.properties");
BeanFactory beanFactory = new BeanFactory(ips);
Object obj = beanFactory.getBean("xxx");
System.out.println(obj.getClass().getName());
System.out.println(obj.toString());
}
private InputStream ips = null;
private Properties props = new Properties();
public BeanFactory(InputStream ips){
this.ips = ips;
try {
props.load(ips);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Object getBean(String beanName){
Object obj = null;
try {
String className = props.getProperty(beanName);
Class clazz = Class.forName(className);
obj = clazz.newInstance();
if(obj instanceof ProxyFactoryBean){
ProxyFactoryBean factoryBean = (ProxyFactoryBean)obj;
String adviceName = props.getProperty(beanName + ".advice");
String targetName = props.getProperty(beanName + ".target");
factoryBean.setAdvice((Advice)Class.forName(adviceName).newInstance());
factoryBean.setTarget(Class.forName(targetName).newInstance());
obj = factoryBean.getProxy();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}
}
public class ProxyFactoryBean {
Advice advice = null;
Object target = null;
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(){
Class clazz1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
class MyInvocationHander implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
advice.beforeMethod();
Object retVal = method.invoke(target, args);
advice.afterMethod();
return retVal;
}
}
Object proxy = null;
//Collection proxy = (Collection)clazz.newInstance();
try {
Constructor constructor = clazz1.getConstructor(InvocationHandler.class);
proxy = constructor.newInstance(new MyInvocationHander());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return proxy;
}
}
public class MyAdvice implements Advice
{
public void afterMethod() {
System.out.println("从毕业啦");
}
public void beforeMethod() {
// TODO Auto-generated method stub
System.out.println("来要去学习啦");
}
}