本节内容:
- 类加载器
- 反射
- 注解 @xxx
- 动态代理
一、类加载器
1. 什么是类加载器,作用是什么?
类加载器(ClassLoader)就是加载字节码文件(.class)。站在累加载器的角度上看CodeSegment,就是一个一个Class对象。
站在ClassLoader角度来讲,每个class就是一个class对象。而在每个class类对象里,又有属性、方法。
2. ClassLoader的类加载机制
并非一次性加载,需要的时候加载(运行期间动态加载)。不是从头到尾扫描,有多少类就加载进多少类,而是用到的时候才会去加载。
【示例】:TestDynamicLoading.java
package com.itheima.reflection;
public class TestDynamicLoading {
public static void main(String[] args) {
new A();
System.out.println("=========");
new B();
new C();
new C();
new D();
new D();
}
}
class A {
}
class B {
}
class C {
static {
System.out.println("cccccccc");
}
}
class D {
{
System.out.println("dddddd");
}
}
如果A.class和B.class同时加载进来,那么“======”会在它们加载完成后打印出来;
如果是看到A加载A,看到B才加载B,那么“======”会在它们中间。
下面设置下运行配置,鼠标右击选择“Run As”,“Run Configuration”,(x)= Arguments,在“VM arguments”里输入:
-verbose:class
所以这叫做动态加载。
另外,注意看类C中的static语句块,static语句块中的内容会在类加载进内存后被调用一次,注意无论你生成多少个C的对象,只会调用一次。
而类D中的代码叫动态语句块,每次new出来一个对象的时候就会去执行动态语句块中的内容。动态语句块用的不多。
3. 类加载器的种类
类加载器有三种,不同类加载器加载不同的字节码:
- BootStrap:引导类加载器:加载都是最基础的文件。这是最核心的类加载器。
- 这里的BootStrap和前端学习的BootStrap不是一个东西,前端学习的BootStrap是一个前端框架。这里的BootStrap底层是C语言写的,Java是个高级语言,它是操作不了硬件的。它主要加载的是JVM运行时的最基础的一些jar包。其他的类加载器都是Java写的。其他的类加载器要想工作的话,类加载器本身也是要加载到内存,下面的类加载器都是被BootStrap类加载器加载到内存中,然后其他的类加载器在加载其他的需要的class进内存。
- ExtClassLoader:扩展类加载器:加载都是基础的文件,加载JVM的一些基础性jar包中的类。
- AppClassLoader:应用类加载器:第三方jar包和自己编写java文件。
- 第三方jar包和我们自己编写的jar文件是没区别的。我们自己写的java文件编译后也可以打成一个jar包。
【注意】:上面的关系不是继承,是加载器对象中有一个引用指向了上一层的加载器对象。
怎么获得类加载器?(重点)
ClassLoader 字节码对象.getClassLoader();
获得类加载器之后,可以获得classes下任何资源:
package com.itheima.classloader;
public class Demo {
public static void main(String[] args) {
//获得Demo字节码文件的类加载器
Class clazz = Demo.class;//获得Demo的字节码对象
ClassLoader classLoader = clazz.getClassLoader();//获得类加载器
//getResource()中的的参数路径是相对于classes(src)
//获得classes(src)下的任何的资源,这个jdbc.properties被放在了和Demo.java在一起
String path = classLoader.getResource("com/itheima/classloader/jdbc.properties").getPath();
//classLoader.getResourceAsStream("");
System.out.println(path);
}
}
二、反射
上面的字节码对象(Class对象)怎么获取?3种方式,上面的示例中只是这3种中第一种方式。
方式一,使用类的class属性: Class<java.util.Date> clz1 = java.util.Date.class; 方式二,通过Class类中的静态方法forName(String className),传入类的全限定名(必须添加完整包名)。 Class<?> clz2 = Class.forName(“java.util.Date”); //把名字为java.util.Date的类加载进内存 方式三,通过对象的getClass方法来实现,其中,getClass()是Object类中的方法,所有的对象都可以调用该方法 java.util.Date str = new java.util.Date(); Class<?> clz3 = str.getClass();
反射是在运行期间动态地加载一个类进来,动态地new一个对象出来,动态地去了解这个对象的内部结构,动态地去调用这个对象的某一些方法。在学习框架时,很多的类的名字是写在配置文件中的。这时候你就会想它们是怎么new出来的,就是用反射机制new出来的。
【示例】:建立一个properties文件,把需要动态加载的类名写进去
test.properties
com.itheima.reflection.T
接着写一个程序从properties中把这个类名读出来,然后生成它的一个对象。
TestFlection.java
package com.itheima.reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestFlection {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
String str = "com.itheima.reflection.T"; //必须是类全名,这块从properties文件中读取出来
Class c = Class.forName(str);
Object o = c.newInstance();
Method[] methods = c.getMethods(); //获取类里面有哪些方法,这些方法有哪些信息,放到method对象里
for (Method m : methods) {
//System.out.println(m.getName());
if(m.getName().equals("mm")) {
m.invoke(o); //m.invoke(obj, args) obj:方法属于哪个对象的,args:方法的参数。参数可以有多个,也可以不传,属于可变参数的方法
}
if(m.getName().equals("m1")) {
for (Class paramType : m.getParameterTypes()) {
System.out.println(paramType.getName());
}
m.invoke(o, 1, 2);
}
if(m.getName().equals("getS")) {
Class returnType = m.getReturnType();
System.out.println(returnType);
System.out.println(returnType.getName());
}
}
}
}
class T {
int i;
String s;
static { //可以写个static代码块看看是否被加载成功
System.out.print("T loaded!");
}
public T() {
System.out.println("T constructed!");
}
public void m1(int i, int j) {
this.i = i + j;
System.out.println(this.i);
}
public void mm() {
System.out.println("m invoked");
}
public String getS() {
return s;
}
}
三、注解 @xxx
1. 什么是注解,注解作用
注解就是符合一定格式的语法 @xxxx。比如使用Junit是单元测试的工具,在一个类中使用 @Test 对程序中的方法进行测试。
注解是一种代码级别的说明。它是jdk1.5及以后版本引入的一个特性,与类、接口、枚举是同一个层次。
注解和注释区别:
- 注释:在阅读程序时清楚 --给程序员看的
- 注解:给jvm看的,给机器看的
注解在目前而言最主流的应用:代替配置文件。
- 比如我们在用Eclipse创建动态web工程时,把"Dynamic web module version"选择2.5,这样创建完成后会有web.xml。如果选择3.0的版本,创建完成后会少个web.xml。因为注解可以代替webxml
在src下新建一个Servlet
关于配置文件与注解开发的优缺点:
- 注解优点:开发效率高、成本低
- 注解缺点:耦合性大、并且不利于后期维护
企业中一般是注解和xml混用。基本不改的地方用注解,有可能要改的地方用xml配置文件。
2. jdk5提供的注解
- @Override:JDK5表示告知编译器此方法是覆盖父类的,JDK6还可以表示实现接口的方法。
- @Deprecated:表示被修饰的方法已经过时。过时的方法不建议使用,但仍可以使用。一般被标记为过时的方法都存在不同的缺陷:(1)安全问题;(2)新的API取代。
- @SuppressWarnings:压制警告,被修饰的类或方法如果存在编译警告,将被编译器忽略。
- deprecation:忽略过时
- rawtypes:忽略类型安全
- unused:忽略不使用
- unchecked:忽略安全检查
- null:忽略空指针
- all:忽略所有。如果要忽略多个,可以直接all
import java.util.ArrayList; import java.util.List; /** * 测试JDK5提供的注解。 * @author jkzhao * */ public class AnnoDemo { public static void main(String[] args) { //压制警告 @SuppressWarnings({ "unused", "rawtypes" }) //如果里面要压制的太多,直接"all" List list = new ArrayList(); //这句话会报警告,因为没加泛型,集合中什么都可以存。取的时候自己不知道里面存的什么,可能存在转换错误的隐患。rawtypes //list未使用,也会出警告。unused show(); } //定义方法过时 @Deprecated public static void show(){ } public static void show(String xx){ } //帮助开发人间检查是否覆盖父类的方法正确。比如你把下面的方法改为public String toStringXX(),加上这个注解,就会报错了。帮你做了检查,你写错了。 //注解是给编译器用的,当你写错代码导致错误时,Eclipse会报红线,编译出错了。javac是编译,现在使用IDE工具,不需要自己手动编译,点击菜单栏的"Project",可以看到"Build Automatically" @Override public String toString() { return super.toString(); } }