JavaSE(11):java高新技术


十一、 Java高新技术

 

1、Eclipse的使用

 

简介:Eclipse是一个javaIDE,集成开发环境,myEclipse是一个Eclipse的功能插件,为了方便,该插件将原本的Eclipse软件也集成在内。

 

常用专业简写:IDE-Integrated Development Environment(集成开发环境)

JavaEE-Java PlatformEnterprise Editionjava企业级应用程序版本) JMS-Java Message Servicejava消息服务)  JME- Java Management Extensionsjava管理扩展) JNDI - Java Naming and Directory InterfaceJava命名和目录接口)

 

workspaceproject:File/SwitchWorkspace,可切换工作区间。Window/Preferences下的全局配置对指定workspace有效。工程名/Properties下的局部配置对指定工程有效。修改全局配置会自动修改局部配置,单独修改局部配置会覆盖全局配置。

 

Perspectiveview:Perspective指透视图,如javadebug等,每个透视图默认会打开指定viewWindow/Show View可配置某个透视图中打开的view

 

快捷键的绑定:Window/General/Keys,可配置各个功能快捷键。如Content AssistbindingAlt+/。修改快捷键时,应先Unbind Command解除绑定,再在Binding输入框中按指定快捷键,而不是拼写快捷键的字母。

注意:快捷键冲突时将不起效果,应该先将某个快捷键解除绑定另一个快捷键才会生效。

 

设置代码模板:Window/Java/Editor/Templates以新建模板tryfinally为例NametryFinallyPattern为:
 try{
  ${line_selection}
 }
 finally{
  ${cursor}
 }
  编写java代码时,选中需要try的代码,右键Surround With/tryFinally,就可以为选中代码添加模板,并将光标移动到指定位置。

 

导入工程:1右键/Import/Existing Projects into WorkSpace,选中某工程,即可导入。2替换工程引用库:右键/Properties/Java Build Path/Library3)自定义用户库:右键/Properties/Java Build Path/Library/Add Library/User Library/next/User Library/New/Add JARs。配置好之后,就可以一次为工程导入多个jar文件。

 

 

2、jdk1.5新特性

 

1、静态导入:import导入一个类或者某个包中的所有类。静态导入import static导入一个类中某个静态方法或所有静态方法。例如:import static java.lang.Math.maximport static java.lang.Math.*(导入所有的静态方法);

 

2、可变参数:同一个方法,若有许多种参数情况,且参数只是数量不同,则可以使用可变参数,避免了多次overload方法,此时将传入的可变参数看做数组传入方法中。例如:public static void max(int x,int...args);(注:可变参数只能出现在参数列表最后,一个方法只能有一个可变参数,可变参数视为数组处理)

 

3、增强for循环:for( type 迭代变量名集合变量名) { ···· 注意:集合变量名可以是数组或实现了Iterator接口的集合类。

 

4、基本数据类型的自动装箱和拆箱:自动装箱:Integer i = 12;将基本数据类型自动装成一个封装类对象Integer对象。自动拆箱:system.out.print( i + 12);此时将Integer对象自动拆成基本数据类型int,完成加法。

注:当int-128~128范围内,及一个字节8位内,自动装箱时,会将int装箱对象放入缓冲池,供下一次需要时直接取用,如:Integer i1 = 12Integer i2 = 12;此时 i1==i2;但是若:Integer i3 = 129Integer i4 = 129;此时 i3 = i4这就是享元模式:将经常用到的数据缓冲起来,而不是每次都创建一个新的对象,以节省空间

 

5、枚举:让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器报错。枚举让程序在编译阶段就控制源程序中填写的非法值。枚举是一个特殊的类,其中也可以定义构造方法、成员变量,普通方法、抽象方法。

普通类实现枚举:定义一个类,私有化其构造器,在类中定义若干个该类类型的成员变量。

枚举类:enum关键字。枚举元素必须放在类中最开头,默认调用无参构造器,同时可以自定义并指定其他构造器,但构造器必须私有化,枚举元素列表需要和其他的类的内容以分号隔开。当枚举类只有一个枚举元素时,实现了单例

testEnum.java(两种普通类实现枚举的方法,两种枚举类的实现)

package blog11;

public class TestEnum {
	//四种实现枚举的方法
	public static void main(String[] args) {
		
	}
}

//普通类实现枚举
class Enum1{
	private Enum1(){} // private the constructor
	//create 3 enum elements for user
	public final static Enum1 e1 = new Enum1();
	public final static Enum1 e2 = new Enum1();
	public final static Enum1 e3 = new Enum1();
	
	public Enum1 nextElement(){
		if(this == e1){
			return e2;
		}else if(this == e2){
			return e3;
		}else if(this == e3){
			return e1;
		}else{
			System.out.println("这出错了吧...");
			return null;
		}
	}
}

//普通抽象类通过匿名内部类实现
abstract class  Enum2{
	
	private Enum2(){}
	
	public final static Enum2 e1 = new Enum2(){ /*此时不能再new抽象类的对象了,而是new了一个匿名内部类,
												该匿名内实际是Enum2的一个匿名子类,实现了其抽象方法*/
		@Override
		public Enum2 nextElements() {
			return e2;
		}};
	public final static Enum2 e2 = new Enum2(){
		@Override
		public Enum2 nextElements() {
			return e1;
		}};
	
	public abstract Enum2 nextElements(); //有了抽象方法,类成了抽象类
}

//无构造器的枚举类
enum Enum3{ // 枚举也是一个类,可以有方法,属性
	blue,red,green{/*甚至可以这样,为单独的元素定义方法*/ @SuppressWarnings("unused")public Enum3 next(){return blue;}};
	
	public Enum3 nextElement(){ //这个方法属于Enum3,没个枚举元素都有
		return null; //此出参照Enum1使用if···else实现
	}
}

//带构造器的枚举类
enum Enum4{
	sat() {
		@Override
		public Enum4 nextDay() {
			return sun;
		}
	},sun(2) {
		@Override
		public Enum4 nextDay() {
			return mon;
		}
	},mon {
		@Override
		public Enum4 nextDay() {
			return sat;
		}
	}; //可以用()的形式通过参数指定构造器
	
	//指定了两个构造器,枚举元素必须放在内容的最前列。
	private Enum4(){}
	private Enum4(int a){}
	
	public abstract Enum4 nextDay();//此时元素除了指定构造方法之外,还必须实现该方法,且元素作为匿名内部子类对象处理
}


 

3、反射及框架

 

反射执行main方法:在框架中,通常通过反射去执行另一个class,需要反射调用其main方法,若传入需要执行的classpublic类名,则通常如下处理:Class.forName(“className”).getMethod(“main”,String[].class).invoke(null,(object)new String[]{“A”,”B”,”C”});ABC为传入main方法的参数,此处注意两点:1maininvoke时因为是静态方法,第一个参数对象为空null2)第二个参数为main的参数列表,常规的主动调用时这个形式string[] args,但是在反射时,invoke的第二个参数只能是一个对象,而一维数组可以看做是一个Object对象,因为String[].class.getSuperClass() == Object.class,所以第二个参数传入Object对象,但是实际在传入的是一个数组时,会被自动拆分成一个一个的数组元素。如invokenullnew String[]{“A”,”B”,”C”}),此时会自动将ABC拆分成三个元素作为invoke的参数,则编译错误(因为invoke只有两个参数)。有两种解决办法:1)在拆分前多进行一次数组包装:new Object[]{new String[]{“A”,”B”,”B”}},则传入的是一个Object数组,进行拆分元素,它只有一个元素,就是一个String数组,已知String数组是一个Object子类对象,逻辑满足,同时满足invoke参数需求,此时invoke已将String数组看做了一个Object对象(因为这是Object数组),所以不会在拆分了。2)在传入参数的同时,告诉编译器这就是一个Object对象,你别把它当数组拆分了。这样表达:(Objectnew String[]{“A”,”B”,”C”},子类强转为父类,完全可以,同时满足invoke参数需求。

ReflectMain.java java反射调用一个类的Main方法)

package blog11;

import java.lang.reflect.Method;

public class ReflectMain {
	public static void main(String[] args) {
		System.out.println("lalala");
	}
}

class realTest{
	//在该类中,反射调用ReflctMain中的main方法。
	public static void method() throws Exception{
		Method m = ReflectMain.class.getMethod("main",String[].class);
		m.invoke(null, new Object[]{new String[]{"A","B","C"}});
		//m.invoke(null, (Object)new String[]{"A","B","C"});两种方法都可以
	}
}


 

数组的反射int[] a = new int[10]{}; a.getClass().getSuperClass() == Object.class;数组的类是Object类的子类,即String[].class.getSuperClass() == Object.class;按此推论则有:int[][n].class = Object[].class,此时这个Object的一维数组里,每一个元素都是一个int数组。而且int[][n] a = new int[n]{};只有变量a,且a.getClass().isArray() == true;知道变量,知道是数组,但是不能反射得到类型int,只能知道a中的某个元素的类型如a[0].getClass().

 

hashCodehashCode可近似看做一种根据对象内存地址计算得到的对象独有的数据,根据这个数据进行对象的保存、查找可以避免重复对象的同时保证了效率(通常是查找,存储效率不高),使用在一些根据哈希值进行处理的集合类如HashMapHashSet中,所以这些集合中存储的对象需要重写hashCodeequals方法,但是不进行哈希处理的集合元素就无需重写hashCode方法了。相当于程序会根据hashCode 将对象划分成一个个的区间,然后在区间内保存、查找。而hashCode通常和对象的属性有关,所以一旦进行Hash操作后,对象不要修改属性值,会造成hashCode值发生改变,从而对该对象操作时,会到另一个Hash值区间去操作,原来那个就会丢失,游离在这篇内存中无法操作了,这就造成了内存泄露。

 

反射实现框架:工具类是用户的类去调用工具类实现某个功能,而框架是已经写好的一个模板,它调用用户写好的类来组织完成一系列功能,就想一个毛培房,每个人有其自己不同的装修喜好,然后寄托在这个框架之上,形成了每个人不同的房子。通常需要注意:框架需要配置文件,相当于在毛培房里列一张装修清单,然后根据这个清单去装修。框架需要一个配置文件,然后根据配置的内容去做相应处理,因为不知道未来需要调用的类,不能new对象,只能通过配置文件传入需要调用的类的信息,然后反射创建对象,这样只要在运行期间能创造出需要的对象就好了,注意配置文件的读取路径问题。框架主要就是反射运行其他的类,或反射创建其他未知类对象。

 

类加载器的方式管理资源和配置文件:类加载器可以作为一种资源加载的方式,但是不具备保存的功能,使用通常的数据流来加载配置文件可以保存配置的改变。
InputStream ips1 = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/resource/config.properties");//绝对路径获取文件,路径前面不能加“/”
InputStream ips2 = ReflectTest2.class.getResourceAsStream("resource/config.properties");//相对路径获取文件
InputStream ips3 = ReflectTest2.class.getResourceAsStream("cn/itcast/day1/resource/config.properties");//绝对路径获取文件,路径前可加也可不加“/”

 

 

4、内省及JavaBean

Introspector内省

JavaBean:一个类中属性值具有特定形式的getset方法操作其属性。这样的类就是JavaBean,对于JavaBean类,有其特有的一些类来操作类中的属性,也提供了很多这样的操作工具类,比如常用的:BeanUtils工具包

 

内省访问JavaBean属性的两种方式: 
1通过PropertyDescriptor类操作Bean的属性。

2)通过Introspector类获得Bean对象的BeanInfo(该类表示某个类作为JavaBean来看,得到的JavaBean信息),然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后通过反射机制来调用这些方法。
 

javabean的简单内省操作:
MyBean m = new MyBean(3,3);
PropertyDescriptor pd = new PropertyDescriptor("x", m.getClass());//获取属性描述对象
Method methodGetX = pd.getReadMethod();//获取get方法
System.out.println(methodGetX.invoke(rp));//输出3
Method methodSetX = pd.getWriteMethod();//获取set方法
methodSetX.invoke(m, 7);
System.out.println(rp.getX());//输出7

 

采用BeanInfo方式操作:

BeanInfo beanInfo = Introspector.getBeanInfo(m.getClass());

PropertyDescripor[] pds = beanInfo.getPropertyDescriptors();//缺点是只能获取全部的属性描述

Object retValue = null;

for(PropertyDescriptor p:pds){

If(p.getName().equals(propertyName)){

Method mehtodGetX = p.getReadMethod();

retValue = methodGetX.invoke(m);

Break;

}

}

 

附:学会在工程中导入第三方扩展jar包,并加入到classPath中。

 

5、注解

详见本blog系列博文《【黑马程序员】10 java反射》中。

 

 

6、泛型

详见本blog系列博文《【黑马程序员】9 java集合与泛型》中。

注:因为泛型只是源码级的,而不会保存到class字节码中,所以可以反射暴力存储不用类型数据到一个集合中。所以泛型不能绝对保证存入的数据就是其泛型规定的类型,反射操作可以打破这个规定。

如:

ArrayList<Integer> list = new ArrayList<>();

ArrayList<String > list2 = new ArrayList<>();

System.out.println( list.getClass() == list2.getClass()); //true,说明是同一份字节码,和泛型类型无关

List.add(你好);//报错,只能放integer类型 

list.getClass().getMethod(add,Object).invoke(list,你好);//通过

System.out.println(list[0]) ;// 你好证明保存成功了。

 

7、类加载器

定义:类加载器就是加载类的一个类,类加载器本身也是一个java类(所以可以用户自定义类加载器),这就诞生了一个最开始是谁加载类加载器这个类的问题了,事实上是由一个内嵌到jvm内核的c++编写的一个非java类的类加载器BootStrap完成的,这个最顶级的加载器逐级加载其他的加载器,形成一个树状结构,其他的下层加载器按照其使用范围分别加载不同的java类。类加载的本质就是将存储在硬盘上的二进制的字节码,也就是.class文件读取到内存中(事实上,还有一个中间处理过程,.class文件读取到内存,进行处理,处理之后才是字节码),供程序直接使用创建对象

默认的类加载器Jvm中可以安装多个类加载器,系统默认的有三个类加载器。其加载范围不同:

① BootStrap|:(c++编写,非java类)顶层加载器,随jvm而启动,加载固定的一个jar:JRE/lib/rt.jar,这个rt.jar中包含了常用了一些类,如System类,也能加载他的下层类加载器ExtClassLoader,但是因为这个加载器是c++写的,所以在进行对象返回时,并不会返回得到这个加载器类的对象信息,返回一个null,如:System.class.getClassLoader()得到的是null,并不是说System这个类没有类加载器,而是它的类加载器是顶级加载器BootStrap

② ExtClassLoader:扩展jar包加载器,由BootStrap加载,本身能指定目录加载:JRE/lib/ext/*.jar

③ AppClassLoader:程序类加载器,加载classPath目录下的类,本身由ExtClassLoader加载。

④ MyClassLoader:用户自定义的类加载,需要指定其父类加载器或采用系统默认的加载器,即指定上层加载器,用来加载这个类本身,需要继承classloader这个抽象类。

 

类加载的委托机制:记住两句话:

1)A类引出了B类(继承或是类中定义,引用等等),则由A类的加载器作为B类的初始底层加载器(可以向上委托查找加载)。

2)一个类加载器(记为O)在加载一个类的时候,会首先调用其上层类加载器(可以说父加载器)去在该上层加载器的加载范围内寻找该需要被加载的类的字节码文件,依次向上委托,若找到,则加载,若找不到,逐层回到相应子类加载器,直到回溯到原本的这个最原始的底层类加载器(O),若还没找到,则抛出异常,若在子类和父类加载器的加载范围中同时存在需要被加载的类的字节码,则显然由父类加载。这样做的好处是,防止不用的子类加载同一个类,在内存中加载出两份字节码,若都由他们的父类向上委托,则只会加载一份字节码,第二次不用再加载,直接从内存中调用。

 

自定义类加载器:实质是读取本地磁盘上的class文件成字节数组形式,然后返回class类型对象。

关键API1)、ClassLoader类的成员方法protected Class defineClass(byte[] b,int off, int len),本身就是protected的,供子类调用,将字节数组b转成class对象。2)、ClassLoader类的成员方法Class loadClass(String name)方法,用指定的ClassLoader对象加载name这个类。

MyClassLoader.java(包含一个测试类,和一个自定义的类加载器类)

package blog11;
//自定义类加载器测试类
import java.util.Date;

@SuppressWarnings("serial")
public class TestClass extends Date{/*这里继承一个父类是因为,在测试的时候,需要用类加载加载该类
 	然后创建该类对象,那么该类的对象不能这样 T t = tc.newInstance(),这样编译器会加载出现的T类型,只能用
 	父类的引用,使用多态的方式创建子类TestClass的对象用于测试:Date d = tc.newInstance(),从头到尾本类不出现。
 	注:tc是类加载得到的TestClass类的字节码相应的class对象*/
	public static void main(String[] args) {
		System.out.println("测试类,该类要通过自定义的类加载器加载....");
	}
	
	@Override
		public String toString() {
			return "加载成功了吧";
		}
}


package blog11;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
 
//本测试时,需要将TestClass先编译运行,然后将得到的src\bin目录下的TestClass.class文件移动到Test文件夹下,否则委托机制直接会让父类加载器加载TestClass类的,测试则失败
public class MyClassLoader extends ClassLoader{
	//测试使用该类加载器加载TestClass类
	@SuppressWarnings("rawtypes")
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		MyClassLoader myLoader = new MyClassLoader(".\\test"); //加载该工程文件夹下的test目录
		Class clazz = myLoader.loadClass("TestClass"); //加载该类,获取到TestClass类的字节码对应的Class对象
		Date tc = (Date) clazz.newInstance();
		System.out.println(tc.toString()); //多态调用子类对象的toString方法,虽然定义的是父类类型的变量
		System.out.println(clazz.getClassLoader()); // MyClassLoader
		System.out.println(clazz.getClassLoader().getClass().getClassLoader());//AppClassLoader
	}
	
	private String loadDir = "."; //这个加载器默认的加载目录为工程目录
	
	public MyClassLoader() {
	}

	public MyClassLoader(String dir) {
		loadDir = dir;
	}
	
	@SuppressWarnings("deprecation")
	@Override /*寻找该类,传入类的名称,加载该类,得到一个字符数组,在通过字节数组返回class对象*/
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		try {
			InputStream in = new FileInputStream(new File(loadDir + File.separator + name + ".class"));
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			int b = -1;
			while( (b = in.read()) != -1 ){
				out.write((byte)b);
			}
			byte [] buffer = out.toByteArray(); //读取的数据,保存成了字节数组了
			in.close();
			out.close();
			return defineClass(buffer, 0, buffer.length); //将字节数组返回成相应的class对象
		} catch (FileNotFoundException e) {
		} catch (IOException e) {
		}
		
		return super.findClass(name); //原方法体,直接调用委托调用父类的方法,子类加载器不关心这其中的转换读取过程,模板设计方法
	}
}


 

 

8、代理

 

代理的引入及特点:当需要为一些实现了相同接口的类的各个方法添加一些日志、事务管理、运行时间计算等等功能时,为了统一代码,简化代码,编写一个与这些所有的目标类实现了相同接口的代理类,则该代理类也具有目标类的方法,但是在代理类中的各个方法中添加额外事务功能代码,再在调用代理类的某个方法时,传入实际的目标类和方法参数,调用到实际类的方法,则简化得完成了功能,这就是代理设计。

 

成分:根据上述分析,所以代理类具有以下成员:

① 抽象角色:声明真实对象和代理对象的共同接口;

② 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。 

③ 真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。

 

优点:使用代理可以很方便管理程序,只需要在配置文件中配置使用目标类还是代理类,就可以切换需要实现额外功能还是基本功能,并且很容易在所有的目标类的方法的切面上添加一个统一的代码模板。

AOP:面向方面编程Aspect Oriented Program(面向对象Object Oriented)。在众多方法中产生的交叉业务,就是这许多方法的相同位置(如首末)需要添加相同的业务,因为是都有的,所以是交叉业务,这个时候就可以使用代理实现。

若采用静态代理的方式,则需要为每个接口都写一个代理类,太麻烦,我们只需要为每一个程序现在调用的类写代理就可以了。这个时候可以使用动态编程,在程序运行过程中,动态地创建这个代理类,实际就是动态产生出这个代理类的字节码。

 

动态代理:让jvm动态创建代理类只需关注三件事:

1、原来的目标类实现了哪些方法,即它实现了哪个接口,那么代理类也需要实现这些接口,才能去代理这个类完成类中的方法。

2、既然产生的是一个类,以类的字节码形式,那么类都有类加载器,所以需要指定一个类加载器,通常和目标类的加载器相同。

3、生成的类中的代码是咋样的呢?由程序员提供。执行代码的对象就是InvocationHandler对象,它在动态代理类对象创建的时候被传递进去,然后调用其invoke方法。

TestProxy.java (三个代理实例,两种实现代理的方式和一种实现封装代理代码的动态代理实现)

 

package blog11;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

//两种实现代理的方式:主要完成两个任务
//1、动态创建代理类
//2、用代理类去产生实例
public class MyProxy {
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void main(String[] args) throws Exception {
		//此处假设我们代理的那个目标类实现了Collection接口(所以目标类有可能是任何一种Collection集合)
		//此处动态创建目标类的代理类,需要指定该代理类的类加载器(和目标类相同,此处用了目标类的父类的加载器),实现的接口,就是目标类的接口Collection
		Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		System.out.println(clazz);//class com.sun.proxy.$Proxy0  可见确实内存中创建了一份字节码,是一个代理类
		//用获得的代理类clazz对象创建代理类实例
		// Method[] ms = clazz.getMethods();  可获取方法,实际会获取到都是Collection接口中提供的方法
		// Object objProxy = clazz.newInstance(); 不能这样,抛出异常:InstantiationException,实例化异常
		Constructor constructor = clazz.getConstructor(InvocationHandler.class); //获取制定的构造器,参数表示了代理目标的实例
		Collection proxy = (Collection)/*代理类也实现了Collection接口,所以可以这样写*/constructor.newInstance(new InvocationHandler(){
			//此处通过获取的构造器创建代理类实例。但是需要传入实参,所以需要实例化InvocationHandler,采用匿名内部类的方式,也可以定义一个普通内部类
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				return null;
			}});
		System.out.println(proxy); //null,并不是没创建,而是其tostring方法返回的是null
		System.out.println(proxy.toString());//null,若没创建,此处空指针异常。
		//注意:代理类从Object继承了许多方法,其中只有hashCode、equals和toString方法会委托
		//目标类去实现,其他的方法都不会交给代理的目标类,而是自身作为一个普通的Object子类去实现,所以如下:
		// proxy.add("aa"); //调用的是代理目标类的add方法,此时运行空指针异常,没有传入实际实现类
		System.out.println(proxy.getClass()); //输出的是Proxy类,而不是Collection类,调用其自身的方法,而不是代理目标类的
	
		//第二种方法,获取代理类和创建代理类对象一步到位
		final ArrayList alist = new ArrayList();
		Collection colProxy = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), 
				new Class[]{Collection.class},/*第二个参数是实现的接口类型,是数组,因为实现可能多个接口,但是此处知道就一个Collection接口*/
				new InvocationHandler(){
					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						//可以看到内部方法中代理对象proxy,方法method,参数args
						//分别表示,实现代理的对象proxy,代理此时调用的方法method,方法传入的参数
						//还缺少一个实际的代理目标类对象,因为最终实际还是目标类执行的, 代理类只是中间组织、过渡、稍微修改业务的作用
						System.out.println(String.valueOf(System.currentTimeMillis())+":我是代理类,目标类你快来实现方法:" + method.getName());
						Object obj = method.invoke(alist, args);
						System.out.println(String.valueOf(System.currentTimeMillis())+":实现完毕啦...");
						//此时外部定义了一个arrayList作为目标类,传入类代理类内部实现,这样,和方法、参数、实际目标都联系起来了
						return obj;
					}}
		);
		colProxy.add("aa");
		colProxy.add(15);
		System.out.println(colProxy); //[aa,15],且每一次调用add包括println中的默认的toString方法,都打印出了日志:我是代理类,目标类你快来实现方法、实现完毕啦...
	}
}

接下来是一个代理的使用实例:包含动态代理的三个元素:接口、目标类、代理类三个文件:

 

package blog11;
//  目标类实现的接口
public interface ArithClac {
	
	int add(int i,int j);
	int sub(int i,int j);
	void mul(int i,int j);
	void div(int i,int j); 
}


package blog11;

public class ArithClacImp implements ArithClac{

	@Override
	public int add(int i, int j) {
		int result = i + j;
		return result;
	}

	@Override
	public int sub(int i, int j) {
		int result  = i-j;
		return result;
	}

	@Override
	public void mul(int i, int j) {
		int result = i*j;
		System.out.println("result:"+result);
	}

	@Override
	public void div(int i, int j) {
		int result = i/j;
		System.out.println("result:"+result);
		
	}
	
	
}



package blog11;

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

import org.junit.Test;

public class TestProxy {
	//动态代理:面向切面编程 (Aspect Orient Program)
	//此时想为ArithClacImp中四个方法在调用时都添加前置和后置的一些日志。若没个方法都手动添加,则代码膨胀严重,且维护困难
	//此时可以用一个模板,将所有的方法嵌套到这个模板中,任何一个方法被调用时都会调用这个模板
	//此时就用到了动态代理:每个方法都需要添加的代码又叫做横切关注点:日志、验证、事务等等
	
	//代理设计模式原理:使用一个代理将对象包装起来,然后用这个代理代替原来的对象,任何对原始对象的操作都通过代理
	//代理对象决定是否及何时将方法调用转到原来的对象上 
	//实现:
	@Test
	public void testProxy(){
		final ArithClac arithClac = new ArithClacImp();
		ArithClac proxy = 
			(ArithClac)Proxy.newProxyInstance(arithClac.getClass().getClassLoader(),
												 new Class[]{ArithClac.class}, //此句最好是:arithClac.getClass().getInterfaces()表示动态代理对象实现的接口和原对象实现的接口相同
												 new InvocationHandler(){
			//通过Proxy代理建立一个代理的对象。传入三个参数
			//1.这个代理对象(由动态代理根据原始对象处理创建的对象)的类加载器,通常与被代理的对象的加载器相同。
			//2.由动态代理产生的对象必须实现的所有接口(的类型),因为不唯一,可以实现多接口,所以是Class数组类型
			//3.调用代理对象的时候,应该产生的行为:匿名内部类InvocationHandle接口的实现
					public Object invoke(Object proxy,Method method,Object[] args)
					//invoke三个参数:proxy:被代理的对象,必须是final修饰的对象
					//				Method:正在被调用的方法
					//				args:调用方法时被传入的参数
							throws Throwable {
								//调用方法时,添加前置日志:
								System.out.println("the method " + method.getName() + " begins with : "+ Arrays.asList(args));
								Object result = method.invoke(arithClac, args);
								System.out.println("the method " + method.getName() + " ends with :" + result);
								return result;
						}		
					});
		
		//测试:
		int result = proxy.add(10, 2); //虽然是代理类在调用方法,实际是代理类过渡,还是调用了目标类对象去完成方法的
		System.out.println(result); //但是在代理类调用方法时,添加了工作日志。虽然调用方法上繁琐了,但是添加日志上统一简化了。
		
		result =  proxy.sub(8, 4);
		System.out.println(result);
		
		//关键的两个语句:1). ArithClac proxy = 
//				(ArithClac)Proxy.newProxyInstance(arithClac.getClass().getClassLoader(),
//				 new Class[]{ArithClac.class}, 
//				 new InvocationHandler(){} );
		//意为:ArithClac proxy创建一个动态代理对象(源目标对象声明为ArithClac类型的,定义代理类对象也是该类型,建议最好用共同实现的接口做类型声明)
		//newProxyInstance产生一个对象,该对象由arithClac.getClass().getClassLoader()加载,实现Class[]{ArithClac.class}这些接口
		//且调用该动态代理产生的新的代理对象的方法时做出的动作:InvocationHandler(){} ,函数体写出具体实现
		
		//2). public Object invoke(Object proxy,Method method,Object[] args)
		//调用方法是的动作:proxy为该动态代理对象对应的原来的对象,method为调用的方法,args为方法的参数 
		
		//注:20行的final的语义:若无final,当testProxy方法调用完之后,有可能arithClac会被变成垃圾回收,而ArithClac proxy这个动态代理对象
		//还需要继续使用,这个时候,调用proxy方法时就会在34行invoke方法中访问到不存在的arithClac,而出错,所以需要让proxy延长生命,使用final。
	}
	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值