黑马程序员-张孝祥Java基础加强(PART2)

本文深入探讨Java泛型的基础知识及其应用场景,包括泛型的使用、类型推断及泛型方法等。此外,还介绍了类加载器的工作原理以及如何自定义类加载器。最后,文章详细讲解了代理的概念、动态代理技术及其在AOP中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

---------------------- ASP.Net+Unity开发.Net培训、期待与您交流! ----------------------


知识点:

1.泛型

2.类加载器

3.代理


一、泛型

泛型也是jdk1.5的新特性

1.体验泛型

在jdk1.5之前向集合类中添加元素是这样的:

ArrayList collection1 = new ArrayList();
collection1.add(1);
collection1.add(1L);
collection1.add("abc");
int i = (Integer) collection1.get(1);//编译时要强制类型转换且运行出错
jdk1.5希望在定义集合时,明确表示要向集合中装那种类型的数据,不能加入指定类型以外的数据

ArrayList
  
    collection2 = new ArrayList
   
    ();
//下面两行在编译时就报告语法错误
//collection2.add(1);
//collection2.add(1L);
collection2.add("abc");
String s = collection2.get(0);//不需要再进行类型转换

   
  
泛型是提供给javac编译器使用的,可以限定集合中元素的类型,让编译器挡住源程序中的非法输入。编译器编译带类型说明的集合时会去除掉”类型“信息,使程序运行效率不受影响。对于参数化的类型, getClass()方法的返回值和原始类型完全相同。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其他类型的数据。如:

ArrayList
  
    collection3 = new ArrayList
   
    ();
System.out.println(collection3.getClass()==collection2.getClass());//true
collection3.getClass().getMethod("add", Object.class).invoke(collection3, "tiantian");
System.out.println(collection3.get(0));

   
  

2.了解泛型

(1)一些术语,拿ArrayList<E>ArrayList<Integer>举例

整个ArrayList<E>称为泛型类型,其中E称为类型变量或类型参数。整个ArrayList<Integer>称为参数化的类型,其中Integer称为类型参数的实例或实际参数类型。<>念做typeofArrayList称为原始类型。

(2)参数化类型和原始类型的兼容性

参数化类型可以引用一个原始类型的对象,编译报告警告,如Collection<String> c = new Vector();

原始类型可以引用一个参数化类型的对象,编译报告警告,如Collection c = new Vector<String>();

但下面的语句不会报错

Vector v = new Vector<String>();

Vector<Object> v1 = v;

(3)参数化类型不考虑类型参数的继承关系,下面两种写法都是错误的

Vector<String> v = new Vector<Object>();

Vector<Object> v = new Vector<String>();

(4)编译器不允许创建泛型变量的数组,即在创建数组实例时,数组的元素不能使用参数化的类型。例如,下面是错误的

Vector<Integer>[] vList = new Vector<Integer>[10];


3.泛型中的?通配符

如果想要定义一个方法,用于打印任意参数化类型的集合中的所有数据,该怎么定义呢?

错误方式:

public static void printCollection(Collection cols) {
		for(Object obj:cols) {
			System.out.println(obj);
		}
		 cols.add("string");//没错
		 cols = new HashSet
   
    ();//会报告错误!
}


    

正确方式:

public static void printCollection(Collection
       cols) {
		for(Object obj:cols) {
			System.out.println(obj);
		}
		cols.add("string");//错误,因为它不知自己未来匹配就一定是String
		cols.size();//没错,此方法与类型参数没有关系
	    cols = new HashSet
      
       ();//没错
	}

      
总结:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。


4.泛型中?通配符的扩展

限定通配符的上边界:

正确:Vector<? extends Number> v = new Vector<Integer>();

错误:Vector<? extends Number> v = new Vector<String>();

限定通配符的下边界:

正确:Vector<? super Integer> v = new Vector<Number>();

错误:Vector<? super Integer> v = new Vector<Byte>();

注意:

限定通配符总是包括自己;

?只能做引用,不能用它去给其他变量赋值


5.泛型集合类的综合案例

HashMap
      
        hm = new HashMap
       
        ();
hm.put("liwei", 34);
hm.put("minfangqun", 23);
hm.put("limuchen", 2);
Set
        
         
          > entrySet = hm.entrySet();
for(Map.Entry
          
            entry : entrySet)
	System.out.println(entry.getKey()+":"+entry.getValue());

          
         
        
       
      

6.定义泛型方法

用于表示泛型的类型参数应该出现在方法的其他所有修饰符之后和返回类型之前,按照惯例,类型参数用单个大写字母表示。

交换数组中的两个元素位置的泛型方法语法定义入下:

static 
      
        void swap(E[] a, int i, int j) {
	E t = a[i];
	a[i] = a[j];
	a[j] = t;
}

      

只有引用类型才能作为泛型方法的实际参数,swap(new int[],3,5);会报告编译错误。

除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用,并且可以用&来指定多个边界,如<V extends Serializable&Clonable>

普通方法、构造方法和静态方法中都可以使用泛型。

也可以使用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但不能用于catch子句中。如:

private static 
      
        sayHello() throws T
{
    try{
 
    }catch(Exception e){
        throw (T)e;
    }
}

      
在泛型中可以同时有多个类型参数,在定义他们的<>中用逗号分隔,如:

public static <K,V> V getValue(K key) { return map.get(key);}


7.几个泛型方法示例

(1)自动将Object类型的对象转换成其他类型的泛型方法

private static 
      
        T autoConvert(Object obj) {
    return (T)obj;
}

      
(2)将任意类型的数组中的所有元素填充为相应类型的某个对象

private static 
      
        void fillArray(T[] a,T obj) {
	for(int i=0;i
       
      
(3)打印出任意参数化类型的集合中的所有内容

public static 
      
        void printCollection2(Collection
       
         collection,T t) {
	System.out.println(collection.size());
	for(T obj : collection) 
		System.out.println(obj);
	
	collection.add(t);
}

       
      
在这种情况下,前面的通配符方案要比泛型更加有效。当一个类型变量用来表达两个参数之间活着参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候用,才需要使用泛型方法。

(4)把任意参数类型的集合中的数据安全的复制到相应类型的数组中

private static 
      
        void copy1(Vector
       
         src, T[] dest) {
	int size = src.size();
	for(int i = 0; i < size; i ++) {
		dest[i] = src.get(i);
	}
}

       
      

(5)把任意参数类型的一个数组中的数组安全的复制到相应类型的另一个数组中

private static 
      
        void copy2(T[] src, T[] dest) {
	for(int i=0;i
       
      

8.类型参数的类型推断

编译器判断泛型方法的实际类型参数的过程称为类型推断。

根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:

(1)当某个类型变量只在所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型。如:

swap(new String[3],1,2)-->static <E> void swap(E[],int i,int j),可以推出E为String

(2)当某个类型变量在所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都是同一种类型,很容易推出来。如:

add(3,5)-->static <T> add(T a,T b),可以推出T为Integer

(3)当某个类型变量在所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这个时候取多个参数的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没有问题,只是运行时出问题

fill(new Integer[3],3.5f)-->static <T> void fill(T[],T t)

(4)当某个类型变量在所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且使用返回值,这时优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x的类型改为Number,则没了错误

int x = add(3,3.5f)-->static <T> T add(T a,T b)

(5)参数类型的类型推断具有传递性,下面第一种情况判断实际类型参数为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:

copy(new Integer[5],new String[5])-->static <T> void copy(T[] a,T[] b);

copy(new Vector<String>(),new Integer[3])-->static <T> void copy(Collection<T> a,T[] b);


9.定义泛型类型

如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持一致时,就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:

package com.itheima.day2;

import java.util.Set;

//dao-->data access object  增删改查crud
public class GenericDao
      
        {
	public void add(T obj) {
		
	}
	
	public void delete(T obj) {}
	
	public void deleteById(int id) {}
	
	public void update(T obj) {}
	
	public T findById(int id) {
		return null;
	}
	
	public Set
       
         findByConditions(String where) {
		return null;
	}
	
	public T findByUsername(String name) {
		return null;
	}
	
	public static 
        
          void staticMethod(T obj){}
}

        
       
      
类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如下面两种方式都可以:

GenericDao<String> dao = null;

new GenericDao<String>();

注意:

在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型;

当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。


10.通过反射获得泛型的参数化类型

Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);
Type[] types = applyMethod.getGenericParameterTypes();
ParameterizedType pType = (ParameterizedType)types[0];
System.out.println(pType.getActualTypeArguments()[0]);
System.out.println(pType.getRawType());

二、类加载器
1.了解类加载器
类加载器是负责加载类的对象。

Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,BootStrapExtClassLoadAppClassLoader,每个类负责加载特定位置的类。

类加载器也是Java类,因为其他是Java类的类加载器本身也要被类加载器加载,显然必须有一个不是Java类的类加载器,这就是BootStrap

Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其之指定一个父级类加载器对象或默认采用系统类加载器为其父级类加载器。


package com.itheima.day2;

public class ClassloaderTest {

	/**
	 * @param args
	 * @throws ClassNotFoundException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 */
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		
		ClassLoader cl = ClassloaderTest.class.getClassLoader();
		while(cl!=null) {
			System.out.println(cl.getClass().getName());
			cl = cl.getParent();
		}
		System.out.println(cl);

	}

}
上面代码的输出结果为 sun.misc.Launcher$AppClassLoader,sun.misc.Launcher$ExtClassLoader,null。最后的加载器其实是 BootStrap,但因为它不是Java类,所以为空。

如果用eclipse的打包工具将ClassLoaderTest输出到jre/lib/ext目录下的itheima.jar包,再运行这个类时,输出结果为sun.misc.Launcher$ExtClassLoader,null。这是为什么呢?这时classpath目录下有ClassLoaderTest.classjre/lib/ext/itheima.jar中也有ClassLoaderTest.class,我们需要了解类加载的具体过程和原理。


2.类加载器的委托机制

当Java虚拟机要加载一个类时,到底派哪个类加载器去加载呢?首先用当前线程的类加载器去加载线程中的第一个类;如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B;也可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托机制。类加载器一级级委托到BootStrap类加载器,如果BootStrap在它对应的目录下找不到要加载的类,就一级级回退到子孙类加载器去找要加载的类。当回退到最初的类加载器还无法找到时,就抛出ClassNotFoundException。这就说明了上面的问题。


3.编写自己的类加载器

自定义的类加载器必须继承ClassLoader,并覆写findClass方法。该类加载器还实现简单的加密类功能。参考文档中的示例代码。

先创建一个被操作的类ClassLoaderAttachment

package com.itheima.day2;

import java.util.Date;

public class ClassLoaderAttachment extends Date {
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "hello,beijing";
	}
}
下面是自定义的类加载器, MyClassLoader

package com.itheima.day2;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MyClassLoader extends ClassLoader {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		String srcPath = args[0];//要加密的文件路径
		String destDir = args[1];//加密后的文件存储目录
		String destFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);?//获取原文件的文件名
		String destPath = destDir+"\\"+destFileName;//加密后文件的完整路径
		FileInputStream fis = new FileInputStream(srcPath);//读取流
		FileOutputStream fos = new FileOutputStream(destPath);//写入流
		cypher(fis, fos);//加密
		fis.close();
		fos.close();

	}
	
	//简单的加密方法
	//将原文件中的数据取出,加密后输出到目的文件中
	private static void cypher(InputStream ips, OutputStream ops) throws IOException {
		int b = -1;
		while((b=ips.read())!=-1)
			ops.write(b ^ 0xff);
	}
	
	private String classDir;
	
	public MyClassLoader(String classDir) {
		super();
		this.classDir = classDir;
	}

	public MyClassLoader() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	

	@Override
	protected Class
       findClass(String name) throws ClassNotFoundException {
		// TODO Auto-generated method stub
		System.out.println(name);
		byte[] b = null;
		try {
			b = loadClassData(name);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return defineClass(null, b, 0, b.length);
	}
	
	private byte[] loadClassData(String name) throws IOException {
        // load the class data from the connection
         String classFilePath = this.classDir+"\\"+name.substring(name.lastIndexOf('.')+1)+".class";
         FileInputStream fis = new FileInputStream(classFilePath);
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         cypher(fis,bos);
         fis.close();
         return bos.toByteArray();
    }


}
原文件路径即arg[0]为 ClassLoaderAttachment.class的绝对路径,目标目录即arg[1]为工程根目录下的 itheimalibs目录,运行 MyClassLoader之后在该目录下会有一个加密后的 ClassLoaderAttachment.class文件。用加密后的文件去覆盖原文件,在 ClassLoaderTest中加载 ClassLoaderAttachment
Class clazz = new MyClassLoader("itheimalibs").loadClass("com.itheima.day2.ClassLoaderAttachment");
Date d1 = (Date)clazz.newInstance();
System.out.println(d1);
ClassLoaderTest中运行以上代码,会出现 “java.lang.ClassFormatError: Incompatible magic value xxx”的错误,这是因为加载类的时候会先去父加载器中寻找,在classpath下有 CalssLoaderAttachment.class,但是加过密的,父加载器无法解析,就出现了上面错误。把 classpath下的 CalssLoaderAttachment.class删掉,最后就会通过 MyClassLoader加载 itheimalibs目录下的 class文件,并输出结果 “hello,beijing”

4.一个类加载器的高级问题分析
编写一个能打印自己的类加载器名称和当前类加载器的父子结构关系链的 MyServlet,正常发布后,看到打印结果为 WebAppClassloader
package com.itheima.itheimaweb.web.servlets;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyServlet extends HttpServlet {

	/**
	 * The doGet method of the servlet. 
* * This method is called when a form has its tag value method equals to get. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); ClassLoader loader = this.getClass().getClassLoader(); while(loader != null) { out.println(loader.getClass().getName()+"
"); loader = loader.getParent(); } out.close(); } }
把M yServlet.class打包成jar包,放到jre/lib/ext目录下,重启tomcat,发现有找不到 HttpServlet的错误。把servlet-api.jar也放到ext目录下,问题解决了,打印结果为 ExtClassLoader。这是因为父级类加载器加载的类无法引用只能被子类加载器加载的类。原理图如下:


三、代理

1.代理的概念和作用

生活中的代理:从代理商手里买电脑和直接从厂家买电脑,主体义务目标是相同的。

程序中的代理:

为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如异常处理、日志、计算方法的运行时间、事物管理等等,编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。



2.AOP和动态代理技术

AOP就是面向方面的编程,AOP的目标就是要使交叉业务模块化。使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类。JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在理方法中的如下四个位置加上系统功能代码:

(1)调用目标方法之前

(2)调用目标方法之后

(3)调用目标方法的前后

(4)在处理目标方法异常的catch块中


3.分析JVM动态生成的类

package com.itheima.day3;

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
	 * @throws NoSuchMethodException 
	 * @throws SecurityException 
	 * @throws InvocationTargetException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * @throws IllegalArgumentException 
	 */
	public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
		// 动态生成实现了Collection接口(可以实现若干接口)的类
		Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		System.out.println(clazzProxy.getName());//打印动态类的名字,结果为$Proxy0
		
		// 打印动态类的所有构造方法,输出打印格式为 构造方法(参数1, 参数2, ...,参数n)
		// 从结果可以看到动态类只有一个接收InvocationHandler参数的构造方法,$Proxy0(java.lang.reflect.InvocationHandler)
		System.out.println("-----------------begin constructor list------------------");
		//获取动态类的所有构造方法
		Constructor[] constructors = clazzProxy.getConstructors();
		for(Constructor constructor : constructors) {
			//使用StringBuilder来构造打印格式
			StringBuilder sb = new StringBuilder();
			//获得构造方法名
			String name = constructor.getName();
			sb.append(name);
			sb.append('(');
			//获得所有参数类型
			Class[] paramTypes = constructor.getParameterTypes();
			for(Class paramType : paramTypes) {
				sb.append(paramType.getName()).append(',');
			}
			//如果参数个数不为0,则将最后的逗号删除
			if (paramTypes.length>0)
				sb.deleteCharAt(sb.length()-1);
			sb.append(')');
			System.out.println(sb.toString());
		}
		
		// 打印动态类的所有构造方法,格式同上
		// 可以看到动态类中有Collection接口中的所有方法
		System.out.println("------------------------begin method list-----------------------");
		Method[] methods = clazzProxy.getMethods();
		for(Method method : methods) {
			StringBuilder sb = new StringBuilder();
			sb.append(method.getName());
			sb.append('(');
			Class[] paramTypes = method.getParameterTypes();
			for(Class paramType : paramTypes) {
				sb.append(paramType.getName()).append(',');
			}
			if (paramTypes.length>0)
				sb.deleteCharAt(sb.length()-1);
			sb.append(')');
			System.out.println(sb.toString());
		}
		
		// 创建动态类的实例对象
		System.out.println("------------------------begin create new instance-----------------------");
		// 通过反射获得动态类的构造函数
		Constructor cons =  clazzProxy.getConstructor(InvocationHandler.class);
		// 用构造方法的newInstance方法创建动态类的实例对象
		// 参数为InvocationHandler的匿名内部类
		Collection proxy1 = (Collection)cons.newInstance(new InvocationHandler() {
			
			//覆写父类的invoke方法
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				// TODO Auto-generated method stub
				return null;
			}
		});
		//调用下面的方法,最终就是调用匿名内部类的invoke方法
		// 打印创建的对象,结果为null
		System.out.println(proxy1.toString());
		proxy1.clear();
		// 调用size()方法,会报告空指针异常,因为返回的是null,而需要的是int
//		proxy1.size();
		
		
		

		// 直接调用Proxy.newInstance方法创建动态代理对象
		Collection proxy2 = (Collection)Proxy.newProxyInstance(
				Collection.class.getClassLoader(),//类加载器
				new Class[]{Collection.class},//要实现的接口数组
				new InvocationHandler() {//InvocationHandler对象,执行业务代码
					// 目标类,执行业务的主体
					ArrayList target = new ArrayList();
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
						// TODO Auto-generated method stub
						long beginTime = System.currentTimeMillis();//增加的系统功能代码
						Object retVal = method.invoke(target, args);//调用目标类的方法
						long endTime = System.currentTimeMillis();//增加的系统功能代码
						System.out.println(method.getName()+" running time:"+(endTime-beginTime));//增加的系统功能代码
						return retVal;
					}
				});
		proxy2.add("zxx");
		proxy2.add("lhm");
		proxy2.add("bxd");
		System.out.println(proxy2.size());// 打印结果为3
		System.out.println(proxy2.getClass().getName());// 打印结果为$Proxy0
		
		// 封装获得动态代理类的代码到getProxy方法中,接收两个参数,一个是目标类
		// 另一个是实现了Advice接口的类
		// Advice接口中可以封装所有要实现的系统功能的方法
		final ArrayList target = new ArrayList();
		Collection proxy3 = (Collection)getProxy(target, new MyAdvice());
		proxy3.add("zxx");
		proxy3.add("lhm");
		proxy3.add("bxd");
		System.out.println(proxy3.size());// 打印结果为3
		System.out.println(proxy3.getClass().getName());// 打印结果为$Proxy1
	}

	
	private static Object getProxy(final Object target, final Advice advice) {
		Object proxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),//传入目标类的类加载器
				target.getClass().getInterfaces(),//传入目标类的接口数组
				new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						// TODO Auto-generated method stub
						advice.beforeMethod(method);// 执行系统功能代码
						Object retVal = method.invoke(target, args);// 执行业务主体
						advice.afterMethod(method);// 执行系统功能代码
						return retVal;
					}
				});
		return proxy;
	}

}
动态代理的工作原理图


4.实现AOP功能的封装与配置

(1)工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件进行切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该实例对象的getProxy方法返回的对象。

package com.itheima.day3.aopframework;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import com.itheima.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();
		}
	}
	
	public Object getBean(String name) {
		String className = props.getProperty(name);
		Object bean = null;
		try {
			Class clazz = Class.forName(className);
			bean = clazz.newInstance();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(bean instanceof ProxyFactoryBean) {
			Object proxy = null;
			ProxyFactoryBean proxyFacotryBean = (ProxyFactoryBean)bean;
			try {
				Object target = Class.forName(props.getProperty(name+".target")).newInstance();
				Advice advice = (Advice)Class.forName(props.getProperty(name+".advice")).newInstance();
				proxyFacotryBean.setTarget(target);
				proxyFacotryBean.setAdvice(advice);
				proxy = proxyFacotryBean.getProxy();
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
			
			return proxy;
		}
		
		return bean;
	}
}

(2)BeanFacotry的构造方法接收代表配置文件的输入流对象,配置文件格式如下:

#xxx=java.util.ArrayList
xxx=com.itheima.day3.aopframework.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=com.itheima.day3.MyAdvice

(3)ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供目标和通知(Advice)两个参数。

package com.itheima.day3.aopframework;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.itheima.day3.Advice;

public class ProxyFactoryBean {
	private Object target;
	private Advice advice;

	public Object getTarget() {
		return target;
	}

	public void setTarget(Object target) {
		this.target = target;
	}

	public Advice getAdvice() {
		return advice;
	}

	public void setAdvice(Advice advice) {
		this.advice = advice;
	}

	public Object getProxy() {
		// TODO Auto-generated method stub
		Object proxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				target.getClass().getInterfaces(),
				new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						// TODO Auto-generated method stub
						advice.beforeMethod(method);
						Object retVal = method.invoke(target, args);
						advice.afterMethod(method);
						return retVal;
					}
				});
		return proxy;
	}
	
}

(4)编写客户端应用,编写实现Advice接口的类和在配置文件中进行配置,调用BeanFactory获取对象。

package com.itheima.day3.aopframework;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Collection;

public class AopFrameworkTest {

	/**
	 * @param args
	 * @throws FileNotFoundException 
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
		Object bean = new BeanFactory(ips).getBean("xxx");
		System.out.println(bean.getClass().getName());
		
		((Collection)bean).add("nimei");
		System.out.println(((Collection) bean).size());
	}

}


---------------------- ASP.Net+Unity开发.Net培训、期待与您交流! ----------------------

详细请查看:http://edu.youkuaiyun.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值