黑马程序员——Java高新_内省、注解、类加载器

时间:2023-02-17 17:09:39

 ----------------------Android培训Java培训、期待与您交流! ----------------------

内省(IntroSpector)

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

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

setId()的属性名idsetCPU的属性名是什么?CPU、getUPS的属性名是什么?UPS

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

          一个符合JavaBean特点的类可以当作普通类一样进行使用,但普通类不一定可以当做JavaBean来使用。把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:

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

JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套api操作JavaBean比用普通类的方式更方便。

个人理解:对于某种符合JavaBean规则的类,使用内省来操作比直接用反射要容易有效率的多,这就是它的特点。

内省的示例代码如下(以get为例):

private static Object getProperty(Object p1, 
String propertyName) throws Exception {
// 简易内省
PropertyDescriptor pd = new PropertyDescriptor(propertyName,p1.getClass());
Method methodGet = pd.getReadMethod();
Object retVal = methodGet.invoke(p1);
//复杂内省,先获得所有的PropertiesDescriptor,再逐个检查判断
/*BeanInfo beanInfo = Introspector.getBeanInfo(p1.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
Object retVal = null;
for(PropertyDescriptor pd: pds){
if(pd.getName().equals(propertyName)){
Method methodGet =pd.getReadMethod();
retVal = methodGet.invoke(p1);
break;
}
}
*/
return retVal;
}

-----------------------------分割线-----------------------------

2 Beanutils工具包

它是针对javabean的操作属性的工具类,需要先导入BeanUtils,把下载好的beanUtils包导入进去,还要再导入logging包,不然会报异常

操作BeanUtils

ReflectPoint pt1 = new ReflectPoint(2,6);  //自定义类
BeanUtils.setProperty(pt1, "x", "9");
//对pt1类对象X属性赋值,"9"是要赋的值,这里注意不管类的属性是什么类型,都要用String来输入
BeanUtils.setProperty(pt1, "birthday.time", "111");
//支持级联的操作,birthday是Date的类型,这个属性也是对象的方法time,进行赋值
PropertyUtils.setProperty(pt1, "x", 9);
//这也是对javabean类的属性操作

需要注意的是:PropertyUtilsBeanUtils不同的地方:PropertyUtils设置属性时,原属性是什么类型就什么类型不能转换的,而BeanUtils只要输入String类型,对内部可以自动转换

-----------------------------分割线-----------------------------

注解

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

注解应用图

 黑马程序员——Java高新_内省、注解、类加载器

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

 -----------------------------分割线-----------------------------

自定义注解

定义一个最简单的注解:public @interface MyAnnotation {}

把它加在某个类上:

          @MyAnnotation public class AnnotationTest{}

          @Retention(RetentionPolicy.RUNTIME) //指明注解生存的时间

          @Target({ElementType.METHOD,ElementType.TYPE})

/          /指明注解适用的场合,指明它可以用于哪里 类 域 方法...

思考:@Override@SuppressWarnings@Deprecated这三个注解的属性值分别是什么?

          java.lang包中 有三个注解分别是DeprecatedSuppressWarningOverride   

          在使用注解前必须要在注解类前面加上@每增加一个注解就意味着产生了一个注解对象
          @Deprecated过时API注解我们在用到JDK提供的API的时候,在编译中遇到这样的提示提示用到过时的API那么这个API就被这个@Deprecated注解所标识,在javac进行编译的时候发现了注解便做出相应的提示。

          @Override覆盖注解也就是在继承中进行覆盖父类的某个方法的时候可以加上这个注解,加上这个注解之后如果我们的覆盖方法出错了IDE会提示我们错误

          @SuppressWarnings批注允许选择性地取消特定代码段(即,类或方法)中的警告。其中的想法是当看到警告时,开发人员将调查它,如果确定它不是问题,就可以添加批注,便不会再看到警告。虽然听起来似乎会屏蔽潜在的错误,但实际上它将提高代码安全性,因为它将防止开发人员对警告无动于衷,开发人员看到的每一个警告都将值得注意。

在定义注解时,我们可以给它添加基本属性、高级属性。

什么是注解的属性?

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

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

          这样在实例化注解对象时,可以这样赋值:@MyAnnotation(color="red")

          我们也可以为属性指定缺省值:String color() default "yellow";

          特别的value属性:String value() default "lx"; 

PS:如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@MyAnnotation("lx")。

定义高级属性:数组类型属性、枚举类型属性、注解类型的属性(元注解MetaAnnotation

PS:枚举和注解都是特殊的类,不能用new 创建它们的实例对象,创建枚举的实例对象就是在其中增加元素。在程序中如何创建出一个注解的实例对象啊?直接用@放上一个标记即可。

自定义注解示例代码如下:

import java.lang.annotation.*;
import com.itheima.day01.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface AnnotationDemo {
String color() default "blue";
String value();
int[] arrayAttr() default{3,4,4};
EnumTest.TrafficLight lamp() default EnumTest.TrafficLight.Red;
MetaAnnotation annotationAttr() default @MetaAnnotation("lx");
}

其中写了一个简易的元注解MetaAnnotation的代码如下:

public @interface MetaAnnotation {
String value();
}

调用自定义注解的示例代码如下:

import java.lang.reflect.Method;
import java.text.Annotation;

@AnnotationDemo(annotationAttr=@MetaAnnotation("lx"),
color="red",value="abc",arrayAttr=1)
public class AnnotationTest {
@SuppressWarnings("deprecation")
@AnnotationDemo("xxx")
public static void main(String[] args) throws SecurityException, NoSuchMethodException {
// TODO Auto-generated method stub
System.runFinalizersOnExit(true);
if(AnnotationTest.class.isAnnotationPresent(AnnotationDemo.class)){
AnnotationDemo ann =
(AnnotationDemo)AnnotationTest.class.getAnnotation(AnnotationDemo.class);
System.out.println(ann.color());
System.out.println(ann.value());
System.out.println(ann.arrayAttr().length);
System.out.println(ann.lamp().nextLamp().name());
System.out.println(ann.annotationAttr().value());
}
Method mainMethod = AnnotationTest.class.getMethod("main", String[].class);
AnnotationDemo ann2 =
(AnnotationDemo)mainMethod.getAnnotation(AnnotationDemo.class);
System.out.println(ann2.value());
}
}

-----------------------------分割线-----------------------------

5 类加载器

什么是类加载器?类加载器就是一个专门负责加载类的对象。

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

Java虚拟机中可以安装多个类加载器,系统默认有3个主要的类加载器,它们之间的关系从上往下分别是:

          BootStrap 只加载JRE/lib/rt.jar下的类

          extClassLoader加载JRE/lib/ext/*.jar(jre内库中扩展的jar包)

          AppClassLoader 只能加载到ClassPath指定的所有jar或目录

特别的,BootStrap是不会被JVM打印出来的,在JVM里,它被赋予null值。

类加载器之间的父子关系和管辖范围图如下:

 黑马程序员——Java高新_内省、注解、类加载器

类加载器的委托机制:

          1 每个类加载器加载类时,会先委托给其上级类加载器:进行加载时,发起加载的请求者会先要求上级去找,一路上去找到*加载器BootStrap还是找不到时,返回给请求者自己去找,还是找不到就抛出异常。(因为没有getChild()方法,没办法往下面找了)

如:AppClassLoader会先让extClassLoader去找,找不到再委托给BootStrap ,若还找不到,就返回给AppClassLoader自己去加载,还是找不到就抛出异常。

          首先当前线程的类加载器去加载线程中的第一个类;如果类A中引用了类BJava虚拟机将使用加载类A的类装载器来加载类B

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

关于类加载器的委托机制的一道面试题:

能不能自己写个类叫java.lang.System

          答:通常来说,不能。写了也是白写。因为java为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System但是可以自己自定义一个类加载器来进行加载。

          那么,可以把先前编写的类加入到jdkrt.jarBootStrap去加载中吗?会有怎样的效果呢?

                    不行!!!不能随意将自己的class文件加入进rt.jar文件中。

-----------------------------分割线-----------------------------

自定义类加载器

需求如下:

对某个类class文件进行加密并放入classPathAppClassLoader去加载,会怎么样?肯定会报错。

          这个时候需要用到自定义类加载器解密进行加载。

编写一个自定义类加载器。

          原理:定义一个类,继承ClassLoader抽象类

          defineClass()方法,传进来一个class文件的二进制数据,返回该文件的字节码

          重写findClass()方法,不重写loadClass()方法,若重写loadClass(),就会破坏委托机制,它不会先去找父类了,重写findClass是当找父类区加载时,细节上若有问题,就会跑来找子类这个方法。

          那么,编写好自定义加载器后,如何使用这个自定义加载器呢?

          假设自定义的类的类名为ClassLoaderAttachment,它继承了Date类。

                    Class clazz=new MyClassLoader().loadClass()

                    //先用自定义加载器得到需要自定义加载的这个类ClassLoaderAttachment的字节码

          然后呢?通过newInstance建立类实例。

                    ClassLoaderAttachment c = (ClassLoaderAttachment)clazz.newInstance();

          这样写不对!编译器里面根本就不认识ClassLoaderAttachment,编译都不能通过!

          那应该怎么办?使用它的父类来进行调用

                    Date c = (Date)clazz.newInstance();

          最后一步,把classPath里的加密class文件删掉,原因(类加载机制使得编译时虚拟机根本不会去找自定义加载器!)。

自定义类加载器示例代码如下:

          要被自定义类加载器加载的类ClassLoaderAttachment:

import java.util.Date;
public class ClassLoaderAttachmentextends Date {
public String toString(){
return "hello,world";
}
}

定义了加密解密的自定义类加载器MyClassLoader:

import java.io.*;
public class MyClassLoader extends ClassLoader {
public static void main(String[] args) throws IOException {
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();
}
//进行加密解密的算法,将传进来的每个字节都^0xff再输出
private static void cypher(InputStream ips, OutputStream ops) throws IOException {
// TODO Auto-generated method stub
int b = -1;
while((b=ips.read())!=-1){
ops.write(b^0xff);
}
}

private String classDir;
public MyClassLoader(){}

public MyClassLoader(String classDir){
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String name){
String classFileName =
classDir+ "\\" + name.substring(name.lastIndexOf('.')+1)+".class";
try{
FileInputStream fis = new FileInputStream(classFileName);
//定义一个数组输出流方便定义class
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
System.out.println("正在使用自定义类加载器");
byte[] bytes = bos.toByteArray();
return defineClass(null,bytes,0,bytes.length);
}
catch(Exception e){
e.printStackTrace();
}
return null;
}
}

对自定义类加载器进行调用测试的类ClassLoaderTest:

import java.util.Date;

public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader!=null){
System.out.println(loader.getClass().getName());
loader=loader.getParent();
}
Class clazz = new MyClassLoader("testlib").loadClass
("com.itheima.day02.ClassLoaderAttachment");
Date date = (Date)clazz.newInstance();
System.out.println(date);
}

}

-----------------------------分割线-----------------------------

关于类加载器的一个高级问题:

javaWeb项目中servlet由什么类加载器加载?它上面有父类加载器吗?

          这些类加载器从子类往父类,如下:

                    org.apache.catalina.loader.WebappClassLoader

                    org.apache.catalina.loader.StandardClassLoader

                    sun.misc.Launcher$AppClassLoader

                    sun.misc.Launcher$ExtClassLoader

                    BootStrap

问题:编写一个Myservlet,把Myservlet.class文件大jar包,放到ext目录下,重启TomCat,发现找不到HttpServlet的错误。因为ExtClassLoader加载不了HttpServlet

          怎么办?把servlet.jar也放到ext目录下,问题解决。这个时候就可以由ExtClassLoader进行类加载了。

总结:父级类加载器加载的类无法引用只能被子类加载器的类。

原理图:

 黑马程序员——Java高新_内省、注解、类加载器

 

----------------------Android培训Java培训、期待与您交流! ----------------------