内省、注解、类加载器

一、由内省引出jababean的讲解

1、概述

        内省对应的英文单词为IntroSpector,它主要用于对JavaBean进行操作。JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法符合某种命名规则。

        如果要在两个模块之间传递多个in西,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法叫什么好呢?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setld。中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。

setId()的属性名—>id

isLast()的属性名—>last

setCPU的属性名是什么?—>CPU

getUPS的属性名是什么?—>UPS

总之,一个类被当做javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。

一个符合JavaBean特点的类可以当做普通类一样进行使用,但把它当做JavaBean用肯定需要带来一些额外的好处。

好处:1)在JavaEE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地。

            2)JDK中提供了对JavaBean进行操作的PI,这套API称为内省,若要自己通过getX的方式来访问私有x,空内省这套API,操作JavaBean要比使用普通的方式更方便。

例1

import java.beans.*;
import java.lang.reflect.*;

class IntroSpectorDemo
{
	public static void main(String[] args)throws Exception
	{
		HashCodeTest hct=new HashCodeTest(2,3);//创建坐标
		String propertyName="x";
		//"x"->"X"->"getX"->MethodGetX->
		//用内省的方式
		//获取并getX方法
		Object retval=getProperty1(hct,propertyName);//关联,获取x的值
		System.out.println(retval);

		Object value=5;//设置为变量
		//获取并调用setX方法
		setProperty(hct,propertyName,value);

		System.out.println(hct.getX());
	}

	//获取并调用setX方法
	private static void setProperty(HashCodeTest hct,String propertyName,Object value)throws Exception
	{
		PropertyDescriptor pd=new PropertyDescriptor(propertyName,hct.getClass());//创建对象关联
		Method methodSetX=pd.getWriteMethod();//获取JavaBean类中的setX方法
		methodSetX.invoke(hct,value);//调用setX方法
	}
	//获取并getX方法
	private static Object getProperty1(HashCodeTest hct,String propertyName)throws Exception
	{
		propertyDescriptor pd=new PropertyDescriptor(propertyName,hct.getClass());//创建对象关联
		Method mehodGetX=pd.getReadMethod();//获取JavaBean类中的getX方法
		Object retval=metthodGetX.invoke(hct);//调用getX方法
		return retval;
		
		/*
		另一种方法:
		采用遍历BeanInfo的所有属性方式来查找和设置某个Refectpoint对象的X属性。在程序中把一个类当作JavaBean来看,就是
		调用IntroSpector.getBeanInfo方法,得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息。

		BeanInfo beanInfo=Introspector.getBeanInfo(pt1.getClass())//得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息
		PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();//把所有结果装入数组
		Object reVal=null;
		for(PropertyDescriptor pd:pds)
		{
			if(pd.getName().equals(propertyName))//如果传入的与数组中的值相等
			{
				Method methodGetX=pd.getReadMethod();//获取JavaBean类中的getX方法
				retVal=methodGetX.invoke(pt1);
				break;
			}
		}
		return retVal;
		*/
	}
}

Eclipse小技巧:选择要变为方法的代码,右键——>Refactor——>Extract Method,然后就会生成一个方法了。

二、BeanUtil工具包

1、BeanUtils等工具包都是由阿帕奇提供的,为了便与开发。
2、BeanUtils可以将8种基本数据类型进行自动的转换,因此对于非基本数据类型,就需要注册转换器Converter,这就需要ConverUtil包。
3、好处:
1)提供的set或get方法中,传入的是字符串,返回的还是字符串,因为在浏览器中,用户输入到文本框的都是字符串的形式发送至服务器上的,
所以操作的都是字符串。也就是说这个工具包的内部有自动将证书转换为字符串的操作。
2)支持属性的级联操作,即支持属性链。如可以设置:认得脑袋上的眼睛的眼珠的颜色。这种级联属性如果自己用反射,那就很困难了,通过这个工具包就可以轻松调用
4、可以和Map集合进行相互转换:可将属性信息通过键值对的形式作为Map集合存储(通过static java.util.Maodescribe(java.lang.Object bean)的方法)。
也可以将Map集合转换为JavaBean中的属性信息。(通过static void populate(java.lang.Objectbean,java.util.Map.properties)的方法)
注:要正常使用BeanUtils工具,还要将Apache公司的logging(日志)的jar包也添加进Build Path。

Ecplipse小知识:
 在工程中导入工具jar包。
两种方式:
1、右键项目——选择Properties——Java Build Path——选择Liberiers标签。AddExternal Jars——选择要导入的jar包。即可。
这样做有个问题就是如果jar路径发生变化。项目就不能使用到这个jar包。
2、在项目中建立一个lib目录,专门用于存放项目所使用到的jar工具包。将要使用到jar包复制粘贴进来,并在jar上点右键——选择Builder Path——Add toBiuldPath,即可、
这时jar包中的对象,就可以使用了。这样做的好处:项目移动,jar随项目移动。

例1

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
class IntroSpectorDemo
{
	public static void main(String[] args)throws Exception
	{
		HashCodeTest hct=new HashCodeTest(2,3);//创建坐标
		String propertyName="x";
		//"x"->"X"->"getX"->MethodGetX->
		
		//用BeanUtils工具包的方法
		System.out.println(BeanUtils.getProperty(hct,propertyName));//get
		BeanUtils.setProperty(hct,propertyName,"9");//set

		System.out.println(hct.getX());

		//对于JavaBean中的属性是对象的操作
		BeanUtils.setProperty(hct,"birthday.time","10");//set
		System.out.println(BeanUtils.getProperty(hct,"birthday.time"));//get
	}
}


5、BeanUtils工具包中海油一个工具PropertyUtils,用法跟BeanUtils一样。区别:
1)BeanUtil会对JavaBean的属性的类型进行转换,如属性本身是integer,会转换成String。
2)PropertyUtils以属性本身的类型进行操作。

例2

//用BeanUtils工具包的工具类BeanUtils方法  
    BeanUtils.setProperty(hct, propertyName, "9");//set,9是String  
    System.out.println(BeanUtils.getProperty(hct, propertyName));//get  
    System.out.println(BeanUtils.getProperty(hct, propertyName).getClass().getName());//java.lang.String  
          
//BeanUtils工具包中的PropertyUtils的操作  
    PropertyUtils.setProperty(hct, propertyName, 9);//9是Integer  
    System.out.println(PropertyUtils.getProperty(hct, propertyName));  
System.out.println(PropertyUtils.getProperty(hct, propertyName).getClass().getName());//java.lang.Integer 

三、注解

1、了解注解及java提供的几个毕本注解

1)先通过@SuppressWarnings(压制过时警告)的应用让大家直观地了解注解:

通过System.runFinalizersOnExit(true)的编译警告引出

@SuppressWarnings("deprecation")

2) @Deprecated(过时警告)

直接在刚才的类中增加一个方法,并加上@Deprecater标注,在另外一个类中调用这个方法

3)Override(表示下面是在覆盖方法)

public  boolean  equals(Reffect  oeher)方法与HashSet结合讲解

4)总结:

        注解相当于一种标记,在程序中加了注解就等于打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

       看java.lang包,可看到JDK中提供的最基本的annotation

2、注解的应用结构图


3、自定义注解及其应用

1)定义一个最简单的注解:public @interface MyAnnotation{}
2)把它加在某个类上:@MyAnnotation public class AnnotationTest{}
3)用反射进行测试AnnotationTest的定义上是否有@MyAnnotation
4)根据发射测试的问题,引出@Retention元注解的讲解,其三中取值:
 RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME:
 分别对应:java源文件-->class文件-->内存中的字节码
5)演示和讲解@Target元注解
 Target的默认值为任何元素,设置Target等于ElementType。METHOD,ElementType.METHOD,ElementType,TYPE)就可以了
6)元注解以及其枚举属性值不用记,只要会看jdk提供那几个基本注解的API帮助文档的定义或其源代码,按图索骥即可查到,
 或者直接看java。lang。annotation包下面的类。

例3

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)//元注释
//注解类
public @interface MyAnnotation{
}

@MyAnnotation
//应用类
public class AnnotationDemo{
	@SuppressWarnings("deprecation")//压制过时信息
	public static void main(String[] args) {

		System.runFinalizersOnExit(true);//这是一个过时了的方法,如果没有注解就会有警告提示
		//判断此类是否有MyAnnotation注解
		if(AnnotationDemo.class.isAnnotationPresent(MyAnnotation.class)){
			//如果有,则获取该注释
			MyAnnotation annotation=AnnotationDemo.class.getAnnotation(MyAnnotation.class);
			System.out.println(annotation);
		}
	}
}

4、为注解增加同级属性

1)数组类型的属性

int[ ]  arrayAttr()  default{1,2,3}

@MyAnnotation(arratAttr={2,3,4})

如果数组属性中只有一个元素,这时候属性值部分可以省略大括号

2)枚举类型属性:

EnumTest.TracffiLamp  lamp()

@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)

3)注解类型的属性:

MetaAnnotation  annotationAttr() default@MetaAnnotation("xxxx")

@MyAnnotation(annotationAttr=@MetaAnnotation("yyy"))

可以认为上面那么@MyAnnotation是MyAnnotation类的一个实例对象,同样的道理,可以认为上面这个@MetaAnnotation是MetaAnnotation类的一个实例对象,

调用代码如下:MetaAnnotation  ma=myAnnotation.annotationAttr()

System.out.println(ma.value())

4)注解的详细语法可以通过看java语言规范了解,即看java的languagespecification

四、类加载器

1、定义:

        类加载器就是加载器的工具。在java程序中用到一个类,出现了这个类的名字。java虚拟机首先将这个类的字节码加载到内存中,通常这个类的字节码的原始信息放在硬盘上的classpath指定的目录下,把class文件的内容加载到内存里面来,再对它进行处理,处理之后的结果就是字节码。这些工作就是类加载器在操起。

2、类加载器的作用

将.class文件中的内容变为字节码架子啊进内存。

3、默认类加载器

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

类加载器也是java类,因为其他事java类的类加载器本身也有被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。

4、类加载器之间的关系

Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,

需要为其制定一个父类加载器对象或者默认采用系统类加载器为其父类加载器

如图:

例1

/**
 * @author dashu
 *
 */
public class ClassLoaderTest {

	/**
	 * @param args
	 */
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println(
				ClassLoaderTest.class.getClassLoader().getClass().getName()
				);//AppClassLoader
		System.out.println(
				System.class.getClassLoader() ); //元父类加载器RootStrap,null
		
		ClassLoader loader=ClassLoaderTest.class.getClassLoader();
		while(loader!=null){
			System.out.println(loader.getClass().getName());//由类加载器ExtClassLoader加载
			loader=loader.getParent();//找父级
		}
		System.out.println(loader);
	}
}

5、类加载器的委托机制

(1)当Java虚拟机要加载一个类时,到底哪个类加载器去加载呢?

每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托模式。

类加载器一级级委托BootStrap类加载器,当BootStrap无法加载当前索要加载的类时,然后才一级级回退到子孙类加载器去进行真正的加载。

当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。

1)首先当前线程的类加载器去加载线程中的第一个类。

2)如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。

3)还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类

(2)每个类加载器加载类时,又先委托给上级类加载器

1)当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?

2)对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。

有一道面试题,能不能自己写个类叫java.lang.System,为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System。

6、编写自己的类加载器

(1)知识讲解:

1)自定义的类加载器必须继承ClassLoader,复写其中的findClass(String  name)方法,而不用覆写loadClass()方法

2)loadClass方法与findClass方法

        在loadClass()内部是会先委托给父级,当父级找不到后返回,再调用findClass(String  name)方法,也就是你自定义的类加载器去找。所以只需要覆写findClass方法,就能实现用自定义的类加载器加载类的目的。

        因为,一般自定义类加载器,会把需要加载的类放在自己指I定的目录中,而java中已有的类加载器是不知道你这个目录的,所以会找不到。这样才会调用你覆写的findClass()方法,用你自定义的类加载器去制定的目录加载类。

       这是一种模板方法设计模式。这样就保留了lodaClass()方法中的流程(这个流程就是先找父级,找不到再调用自定义的类加载器),而我们只需覆写findClass方法,实现局部细节就行了。

        ClassLoader提供了一个protected  Class<?>defineClass(String  name,byte[]  b,int  off,int  len)方法,只需要将类对应的class文件传入,就可以将其变为字节码。

3)defineClass方法

(2)编程步骤:

1)编写一个队文件内容进行简单加密的程序

2)编写一个自己的类装载器,可实现对加密过的类进行装载和解密

3)编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类,

程序中可以除了使用ClassLoaderload方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用ClassforName

(3)实验步骤:

1)对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如,java MyClassLoader MyTest.class  F:\itcast

2)运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader,java  MyClassLoader  My Test  F:itcast

3)用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类加载器装载失败。

4)删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了 

例2

package cn.itcast.demo;

import java.util.*;
//定义一个测试类,继承Date,
public class ClassLoaderAttachment extends Date{
	public String toString(){
		return "hi,传智播客";
	}
}

import java.io.*;;

public class MyClassLoader {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String srcPath=arg[0];//文件源
		String destDir=args[1];//文件目的
		InputStream ips=new FileInputStream(srcPath);//读取流
		String destFileName=srcPath.substring(srcPath.lastIndexOf("\\"+1))//获取子字符串源文件名
		String destFilePath=destDir+"\\"+destFileName;//目的路径
		OutputStream ops=new FileOutputStream(destFilePath);//输出流
		cypher(ips,ops);//加密class字节码
		ips.close();//关流
		ops.close();	
	}
	//加密方法
	private static void cypher(InputStream ips,OutputStream ops)throws Exception{
		int b=-1;
		while((b=ips.read())!=-1){
			ops.write(b^0xff);
		}
	}
	
	@Override
	//覆盖ClassLoader的findClass方法
	protected Class<?> findClasss(String name) throws ClassNotFoundException{
		name=name.substring(name.lastIndexOf(".")+1);
		String classFileName=classDir+"\\"+name+".class";//获取class文件
		InputStream ips=null;
		try {
			ips=new FileInputStream(classFileName);
			ByteArrayOutputStream bos=new ByteArrayOutputStream();//定义字节流数组
			cypher(ips,bos);//解码
			ips.close();
			byte[] buf=bos.toByteArray();//取出字节数组流中的数据
			return defineClass(null,buf,0,buf.length);//加载进内存
			
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return null;
	}
	
	private String classDir;
	public MyClassLoader(){}
	//带参数的构造函数
	public MyClasLoader(String classDir){
		this.classDir=classDir;
	}
}

import java.util.Date;

public class ClassLoaderDemo{
	public static void main(String[] args)throws Exception{
		//将用自定义的类加载器加载class文件
		Class clazz=new MyClassLoader("itcast").loadClass("cn.itcast.demo.ClassLoaderAttachment");
		Date d1=(Date)clazz.newInstance();//获取Class类的实例对象
		System.out.println(d1);
	}
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值