黑马程序员_高新技术之javaBean,注解,类加载器

时间:2023-07-13 15:48:50

----------- android培训java培训、java学习型技术博客、期待与您交流! ----------

第一部分 javaBean

一,由内省引出javaBean
1,内省:
内省对应的英文单词为IntroSpector,它主要用于对JavaBean进行操作。

2,javaBean
定义:JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果一个Java类中的一些方法符合某种命名规则,则可以把它当作JavaBean来使用。

3,javaBean的属性
JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,如果方法名为getId,中文意思即为获取id,去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
setId()的属性名id
isLast()的属性名last
setCPU的属性名是什么?CPU
getUPS的属性名是什么?UPS
总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。

4,javaBean的作用
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。

5,javaBean的好处
一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:
1)在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那就没什么挑选的余地!
2)JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要自己去通过getX方法来访问私有的x,可以用内省这套api操作JavaBean比用普通类的方式更方便。

二,对javaBean的内省操作-内省综合案例
1,简单内省操作
首先将要操作的类(比如ReflectPoint)生产settr和getter方法

public int getX() {
return x;
} public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;

然后创建一个IntroSpector类来设置获取某个对象上的属性的值

public class IntroSpectorTest {

	/**
* @param args
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//创建ReflectPoint对象
ReflectPoint pt1 = new ReflectPoint(3,5); String propertyName = "x";
//获取pt1对象上的属性值
Object retVal = getProperty(pt1, propertyName);
System.out.println(retVal); Object value = 7;
//设置pt1对象上属性x的值
setProperties(pt1, propertyName, value);
System.out.println(pt1.getX()); }
//对某个对象身上的属性进行设置
private static void setProperties(Object pt1, String propertyName,
Object value) throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
PropertyDescriptor pd2 = new PropertyDescriptor(propertyName,pt1.getClass());
Method methodSetX = pd2.getWriteMethod();
methodSetX.invoke(pt1,value);
}
//对某个对象身上的属性进行获取
private static Object getProperty(Object pt1, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
PropertyDescriptor pd = new PropertyDescriptor(propertyName,pt1.getClass());
Method methodGetX = pd.getReadMethod();
Object retVal = methodGetX.invoke(pt1); } }

2,复杂内省操作
采用遍历BeanInfo的所有属性方式来查找和设置某个RefectPoint对象的x属性。在程序中把一个类当作JavaBean来看,就是调用IntroSpector.getBeanInfo方法, 得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息。

代码如下:

//把一个java类当做一个javaBean来看 用BeanInfo来表示
BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());
//获取到所有的属性
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
Object retVal = null;
for(PropertyDescriptor pd : pds){
//如果属性名等于要操作的属性名 则
if(pd.getName().equals(propertyName))
{
//通过属性找到读取的方法 作用于对象上 获取该属性上的值
Method methodGetX = pd.getReadMethod();
retVal = methodGetX.invoke(pt1);
break;
}
}

三,BeanUtils工具包
BeanUtils工具包由Apache提供,可以在apache.org 上面下载commons-BeanUtils工具包,同时也要下载Logging也就是日志工具包 。

BeanUtils.setProperty(pt1, "x", "9");//设置x属性   ,利用BeanUtils设置值的时候 值可以是任意原始类型 也可以是String类型  在web开发中我们用到的都是String类型 因为网页间传递的是字串 如果我们不想进行字符串转换 那个就用PropertyUtils类  和BeanUtils类有着同样的作用只不过PropertyUtils 不进行类型的转换

System.out.println(pt1.getX());//返回x属性

下面是BeanUtils和PropertyUtils的区别:

//BeanUtils是以字符串的形式对javaBean进行操作
BeanUtils.setProperty(pt1, "birthday.time", "111");//设置birthday的time属性 因为birthday是个复合属性
System.out.println(BeanUtils.getProperty(pt1, "birthday.time"));//获取time的值
//PropertyUtils是以属性本身的类型进行操作。
/*
get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型。
*/
PropertyUtils.setProperty(pt1, "x", 9);//这只x的值为9
System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName());

java7的新特性  Map集合在BeanUtils中的应用:

Map map = {name:"zxx",age:18};
BeanUtils.setProperty(map, "name", "lhm");

第二部分 注解

1,定义:
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。一个注解也是一个类
2,
几个常用注释类型:
1) @SuppressWarnings:压缩警告
指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。

2) @Deprecated 已过时
用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告。

3) @Override 重写
表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息。

@Retention 指示注释类型的注释要保留多久。
@Retention的作用:
当在一个java源程序身上加了一个注解,源程序由javac编译成class文件的时候可能会把注解给去掉。如果javac编译器把这个注解留在了class文件,但是我们的程序在用这个class的时候 ,用类加载器把这个class调到内存里来,(注意:class文件里面的东西不是字节码,只有把class里面的东西由类加载器加载到内存里面来,对它进行一个处理,处理完后最终在内存里的二进制码才是字节码)内加载器把这个class加载到内存中的过程中也有转换。转换的时候是否把class文件里面的注解保留下来这也不清楚。所以一个注解的生命周期有三个阶段:
java源文件-->class文件-->内存中的字节码。
所以设计注解的时候加上一个 @Retention这个说明可以表明这个注解是在哪个生命阶段。Retention的默认值是在class阶段
@Retention其三种取值:RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME;分别对应:java源文件-->class文件-->内存中的字节码。

同理 @Override对应的Retention是在RetetionPolicy.SOURCE 源程序阶段 给编译器看的。
@SuppressWarnings也是给编译器看的,在RetetionPolicy.SOURCE 源程序阶段
@Deprecated  当某个类的方法过时时,编译器要从该类的二进制代码上才能看到过时 所以它保留的是在运行期间 就是在内存中的二进制里面。RetetionPolicy.RUNTIME 内存中的字节码

Retention里面填的值是一个枚举:RetentionPolicy  具有三种取值:
CLASS   编译器将把注释记录在类文件中,但在运行时 JVM 不需要保留注释。
RUNTIME 编译器将把注释记录在类文件中,在运行时 JVM 将保留注释,因此可以反射性地读取。
SOURCE  编译器要丢弃的注释。

4) @Target
指示注释类型所适用的程序元素的种类。如果注释类型声明中不存在 Target 元注释,则声明的类型可以用在任一程序元素上。如果存在这样的元注释,则编译器强制实施指定的使用限制。
Target里面填的值是一个枚举:ElementType程序元素类型。此枚举类型的常量提供了 Java 程序中声明的元素的简单分类。
下面这些常量与 Target 元注释类型一起使用,以指定在什么情况下使用注释类型是合法的。
ANNOTATION_TYPE
          注释类型声明
CONSTRUCTOR
          构造方法声明
FIELD
          字段声明(包括枚举常量)
LOCAL_VARIABLE
          局部变量声明
METHOD
          方法声明
PACKAGE
          包声明
PARAMETER
          参数声明
TYPE
          类、接口(包括注释类型)或枚举声明

3,注解的应用结构图
注解就相当于一个你的源程序中要调用的一个类,要在源程序中应用某个注解,得先准备好了这个注解类。就像你要调用某个类,得先有开发好这个类。
结构图:

黑马程序员_高新技术之javaBean,注解,类加载器

4,为注解增加基本属性
1)什么是注解的属性
 一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是黑马的学生,否则,就不是。如果还想区分出是传智播客哪个班的学生,这时候可以为胸牌在增加一个属性来进行区分。加了属性的标记效果为: @MyAnnotation(color="red")

2)定义基本类型的属性和应用属性:
在注解类中增加String color();
@MyAnnotation(color="red")

3)用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
MyAnnotation a = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
System.out.println(a.color());
可以认为上面这个 @MyAnnotation是MyAnnotaion类的一个实例对象

4)为属性指定缺省值:
String color() default "yellow";
value属性:
String value() default "huan";
如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如: @MyAnnotation("heima")。

5,为注解增加高级属性
1)数组类型的属性
int [] arrayAttr() default {1,2,3};
@MyAnnotation(arrayAttr={2,3,4})
如果数组属性中只有一个元素,这时候属性值部分可以省略大括号

2)枚举类型的属性
EnumTest.TrafficLamp lamp() ;
@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)

3)注解类型的属性:
MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");
@MyAnnotation(annotationAttr = @MetaAnnotation(“yyy”) )
可以认为上面这个 @MyAnnotation是MyAnnotaion类的一个实例对象,同样的道理,可以认为上面这个 @MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下:
 MetaAnnotation ma =  myAnnotation.annotationAttr();
 System.out.println(ma.value());

代码演示:
自定义的注解类:

@Retention(RetentionPolicy.RUNTIME) //在注解类身上加的注解  这种称为元注解
@Target({ElementType.METHOD,ElementType.TYPE})//表明@ItcastAnnotation只能放在方法或者类上
public @interface ItcastAnnotation {
String color() default "blue";//颜色属性的默认值
String value();
int[] arrayAttr() default {3,4,4};//数组属性的默认值
EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED;
MetaAnnotation annotationAttr() default @MetaAnnotation("lhm");
}

注解的测试类:

@ItcastAnnotation(annotationAttr=@MetaAnnotation("flx"),color="red",value="abc",arrayAttr=1)//设置属性值
public class AnnotationTest { /**
* @param args
*/
@SuppressWarnings("deprecation")
@ItcastAnnotation("xyz")
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
System.runFinalizersOnExit(true);
//判断ItcastAnnotation这个注解是否存在
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)){
//通过反射获取ItcastAnnotation的实例对象
ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
System.out.println(annotation.color());
System.out.println(annotation.value());
System.out.println(annotation.arrayAttr().length);
System.out.println(annotation.lamp().nextLamp().name());
System.out.println(annotation.annotationAttr().value());
} Method mainMethod = AnnotationTest.class.getMethod("main", String[].class);
ItcastAnnotation annotation2 = (ItcastAnnotation)mainMethod.getAnnotation(ItcastAnnotation.class);
System.out.println(annotation2.value());
} @Deprecated
public static void sayHello(){
System.out.println("hi,黑马");
}
}

第三部分 类加载器

1,什么是类加载器和类加载器的作用
 类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。
 类加载器的作用就是当程序需要用到某个类时,通过类加载器把这个类的二进制加载到内存。

2,java.lang.ClassLoader类介绍

java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。为了完成加载类的这个职责,ClassLoader提供了一系列的方法,比较重要的方法如下:

方法       说明
getParent()      返回该类加载器的父类加载器。
loadClass(String name)     加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name)     查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name)       查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
defineClass(String name, byte[] b, 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。
 int off, int len)     这个方法被声明为 final的。
resolveClass(Class<?> c)    链接指定的 Java 类。

2,类加载器的树状组织结构

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

说明:
类加载器也是Java类,因为他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器是不是java类,这正是BootStrap。
BootStrap类加载器:不是java类 是嵌套在java虚拟机内核里的,是用c++语言写的一段二进制代码

除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过getParent()方法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。

下面的代码用来查看类加载器的层次结构关系:

ClassLoader loader = ClassLoaderTest.class.getClassLoader();
////打印出当前的类装载器,及该类装载器的各级父类装载器
while(loader != null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);

打印结果:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null

用一个图来形象的表示类加载器的树状组织结构图:

黑马程序员_高新技术之javaBean,注解,类加载器

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

每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。

5,编写自己的类加载器
虽然在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,还是需要为应用开发出自己的类加载器。比如应用通过网络来传输 Java 类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来。下面将通过两个具体的实例来说明类加载器的开发。

5.1文件系统类加载器

第一个类加载器用来加载存储在文件系统上的 Java 字节代码。
代码如下:

public class MyClassLoader extends ClassLoader{

	/**
* @param args
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//源文件路径
String srcPath = args[0];
//目的文件目录
String destDir = args[1];
//通过输入流关联源文件
FileInputStream fis = new FileInputStream(srcPath);
//获取源文件路径中的文件名
String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);
String destPath = destDir + "\\" + destFileName;
//输出流关联目的路径
FileOutputStream fos = new FileOutputStream(destPath);
cypher(fis,fos);
fis.close();
fos.close();
}
//该功能将文件进行加密
private static void cypher(InputStream ips ,OutputStream ops) throws Exception{
int b = -1;
while((b=ips.read())!=-1){
ops.write(b ^ 0xff);//将0变成1 1变成0
}
} private String classDir; @Override
//覆写findClass()方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class";
try {
FileInputStream fis = new FileInputStream(classFileName);
//创建一个字节数组输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
System.out.println("aaa");
//将获取到的数据复制到一个新分配的数组中
byte[] bytes = bos.toByteArray();
//将一个 byte 数组转换为 Class 类的实例并返回
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
//无参构造函数
public MyClassLoader(){ }
//有参构造函数
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}

类 MyClassLoader继承自类 java.lang.ClassLoader。java.lang.ClassLoader类的常用方法中,一般来说,自己开发的类加载器只需要覆写 findClass(String name)方法即可。java.lang.ClassLoader类的方法 loadClass()封装了前面提到的代理模式的实现。该方法会首先调用 findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的 loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用 findClass()方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写 loadClass()方法,而是覆写 findClass()方法。

类 MyClassLoader的 findClass()方法首先根据类的全名在硬盘上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass()方法来把这些字节代码转换成 java.lang.Class类的实例。

5.2类加载器与 Web 容器

对于运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。

绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:

每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes和 WEB-INF/lib目录下面。
多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。
当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。