注解、类加载器、动态代理
1.1 注解
1.1.1 注解概念和作用
- 什么是注解
Annotation注解是类的组成部分,给类携带一些额外的信息,是一种代码级别的说明,是JDK1.5之后的新特性
注释是给开发人员阅读的,注解是给程序提供相应信息的
- 注解的作用
2.1编译检查:通过代码里标识注解,让编译器能够实现基本的编译检查。例如:@Override
2.2编写文档:通过代码里标识注解,辅助生成帮助文档对应的内容
2.3代码分析:通过代码里标识注解,对代码进行分析,从而达到取代xml目的
1.1.2 内置注解的使用
1. 三个基本的Annotation:
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示某个程序元素(类, 方法等)已过时
@SuppressWarnings: 抑制编译器警告
1) rawtypes 忽略类型安全
2) unused 忽略不使用
3) deprecation 忽略过时
4) unchecked 忽略安全检查
5) null 忽略空指针
6) all 忽略所有
案例代码:
public class Demo01 {
@SuppressWarnings({ "rawtypes", "unused", "deprecation", "null", "unchecked"})
public static void main(String[] args) {
Student s = new Student();
s.sleep();
List list = new ArrayList<>();
List list2 = new ArrayList<>();
list.add("abc");
new Thread().stop();
String str = null;
str.length();
}
}
class Person {
public void sleep() {
System.out.println("人睡觉");
}
}
class Student extends Person {
@Override
public void sleep() {
System.out.println("学生学习后睡觉");
}
@Deprecated
public void playLol() {
System.out.println("完LOL");
}
}
1.2 自定义注解
1.2.1 自定义注解的语法
修饰符 @interface 注解名 {
}
如:定义一个名为Student的注解
public @interface StudentAnno {
}
1.2.2 注解的使用
@注解名
如:
@StudentAnno
public class Demo02 {
}
1.2.3 3.注解的属性
1) 注解属性的作用:可以给每个注解加上多个不同的属性,用户使用注解的时候,可以传递参数给属性,让注解的功能更加强大。
2) 属性声明方式: 属性类型 属性名();
public @interface StudentAnno {
// 给注解添加属性
String name(); //字符串
int age(); //整型
String[] hobby(); //数组类型
}
注:如果定义了属性,那么在使用注解的时候,就一定要给属性赋值
@StudentAnno(name="悟空", age=500, hobby={"吃桃子", "打妖怪"})
public class Demo02 {
}
3) 属性默认值:
String name() default "默认值";
如果属性有默认值,则使用注解的时候,这个属性就可以不赋值。也可以重新赋值,覆盖原有的默认值。
如定义注解:
public @interface StudentAnno {
// 3.给注解添加属性
String name(); // 字符串
int age() default 18; // 整型
String[] hobby(); // 数组类型
}
使用注解时不赋值,使用默认值:
@StudentAnno(name="悟空", hobby={"吃桃子", "打妖怪"})
public class Demo02 {
}
4) 特殊属性名value:
如果注解中只有一个名称value的属性,那么使用注解时可以省略value=部分,只写属性值即可。无论这个value是单个元素还是数组,都可以省略。但如果还有其它属性需要赋值,则调用时value名字不能省略。
public @interface EmoplyeeAnno {
// 注解只有一个属性,且属性名是value,给value属性赋值可以写成 @EmoplyeeAnno("张三")
String value();
}
使用:
@EmoplyeeAnno("abc")
// @EmoplyeeAnno({"abc", "cba"})
public static void main(String[] args) {
}
1.2.4 自定义注解练习
1) 注解名为BookAnno
2) 包含属性:String value(); //书名
3) 包含属性:double price(); //价格,默认值为 100
4) 包含属性:String[] authors(); //多位作者
案例代码:
public @interface BookAnno {
String value(); // 书名
double price() default 100; // 价格
String[] authors(); // 作者
}
使用注解:
@BookAnno(value="西游记", price=20, authors={"吴承恩", "金正恩"})
public class Demo03 {
}
1.3 元注解
元注解概念:在自定义注解时使用的注解,给自定义注解添加约束,称为元注解。查看@Override的源码,即可看到元注解,任何一个注解都有元注解
1.3.1 @Target元注解
@Target作用:指明此注解用在哪个位置,如果不写默认是全部位置
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE})
ElementType.TYPE: 用在类和接口上
ElementType.FIELD:用在成员变量上
ElementType.METHOD: 用在方法上
ElementType.PARAMETER:用在参数上
ElementType.CONSTRUCTOR:用在构造方法上
ElementType.LOCAL_VARIABLE:用在局部变量
案例代码:
@Target(ElementType.TYPE)
public @interface BookAnno {
String value(); // 书名
double price() default 100; // 价格
String[] authors(); // 作者
}
自定义注解使用:
@BookAnno(value="西游记", price=20, authors={"吴承恩", "金正恩"})
public class Demo04 {
// @Target(ElementType.TYPE) 明确只能用在类上面
// @BookAnno(value="西游记", price=20, authors={"吴承恩", "金正恩"})
public static void main(String[] args) {
}
}
1.3.2 @Retention元注解
@Retention功能:限制自定义注解的作用范围
枚举值 |
作用 |
RetentionPolicy.SOURCE |
注解只存在于Java源代码中,编译成字节码文件以后删除。 |
RetentionPolicy.CLASS |
注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。 |
RetentionPolicy.RUNTIME |
注解存在于Java源代码中、编译以后的字节码文件中、运行时的内存中,程序可以通过反射获取该注解。 |
如:Override就限定只能用在源代码上:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
注意:如果要通过反射拿到注解,这个自定义的注解必须设置为: RetentionPolicy.RUNTIME
1.4 解析注解
AnnotatedElement是一个接口,只要实现这个接口的类都可以获取注解.
Class,Constructor,Field,Method都实现这个接口
1.4.1 AnnotatedElement接口中的方法:
|
getAnnotation |
|
getAnnotations |
||
getDeclaredAnnotations |
||
|
isAnnotationPresent |
1.4.2 获得注解
public final class Class<T> implements AnnotatedElement
得到注解类对象的原则:这个注解在哪个成员上,就通过哪个对象来得到它的注解。
如:注解用在类上,就通过类对象得到它的注解。注解用在方法上,就通过方法对象得到它的注解
@BookAnno(value="西游记", price=20, authors={"吴承恩", "金正恩"})
public class Demo04 {
public static void main(String[] args) throws Exception {
// 获取类上面的注解
boolean b = Demo04.class.isAnnotationPresent(BookAnno.class);
if (b) {
BookAnno anno = Demo04.class.getAnnotation(BookAnno.class);
System.out.println(anno.value());
System.out.println(anno.price());
System.out.println(Arrays.toString(anno.authors()));
} else {
System.out.println("类上面没有对应注解");
}
System.out.println("------------------");
// 获取方法上面的注解
Method method = Demo04.class.getMethod("test");
boolean b2 = method.isAnnotationPresent(BookAnno.class);
if (b2) {
BookAnno anno = method.getAnnotation(BookAnno.class);
System.out.println(anno.value());
System.out.println(anno.price());
System.out.println(Arrays.toString(anno.authors()));
} else {
System.out.println("方法上面没有对应注解");
}
}
@BookAnno(value="水浒传", price=10, authors={"施耐庵", "施主"})
public static void test() {
}
}
1.1 注解案例-模拟@Test注解
案例需求:模拟系统自带的@Test注解,自动运行带@Test注解的方法
1) 模拟JUnit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
2) 其次编写目标类,然后给目标方法使用@MyTest注解,编写三个方法,其中两个加上@MyTest注解。
3) 最后编写调用类,使用main方法调用目标类,模拟JUnit的运行,只要有@MyTest注释的方法都会运行。
案例实现:
1) 步骤1:编写自定义注解类@MyTest
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
2) 步骤2:编写目标类MyTestDemo
public class MyTestDemo {
@MyTest
public void test1(){
System.out.println("test1执行了...");
}
@MyTest
public void test2(){
System.out.println("test2执行了...");
}
public void test3(){
System.out.println("test3执行了...");
}
}
3) 步骤3:编写测试方法
3.1) 得到使用注解的类对象
3.2) 创建类的实例
3.3) 得到类中所有公有的方法
3.4) 遍历所有的方法,如果方法上有注解,则运行此方法
public class Demo05 {
public static void main(String[] args) throws Exception {
// 1.1 反射:获得类的字节码对象.Class
Class clazz = MyTestDemo.class;
// 1.2 获得实例对象
Object obj = clazz.newInstance();
// 2 获得目标类所有的方法
Method[] allMethod = clazz.getMethods();
// 3 遍历所有的方法
for (Method method : allMethod) {
// 3.1 判断方法是否有MyTest注解
if (method.isAnnotationPresent(MyTest.class)) {
// 4 如果有注解运行指定的类
method.invoke(obj);
}
}
}
}
第2章 类加载器
2.1 类加载器的作用
类加载器是负责将class字节码文件,从硬盘加载到内存中,并且生成Class类对象
类加载器的共同父类:java.lang.ClassLoader
2.2 三种类加载器
1) 引导类加载器:
BootstrapClassLoader 最底层的加载器,由C和C++编写,不是Java中的类。
作用:加载JRE最基础的Java类,如:rt.jar
2) 扩展类加载器:
ExtClassLoader 由Java程序编写,是一个Java类。作用:加载JRE中的扩展类
3) 应用类加载器:
AppClassLoader 由Java程序编写,是一个Java内部类。作用:加载CLASSPATH指定的jar(包括第三方的库)和自己编写的类。
4. 如何得到类加载器:
类名.class.getClassLoader()
2.3 类加载器的示例
1. 示例:创建三个测试类,每个测试类写2个测试方法。
l 预备知识:
1) 使用System.getProperties()查看所有JVM加载的系统环境配置信息
通过System.getProperty(常量名),指定常量的名字,得到一个常量的值。
public class Demo06 {
public static void main(String[] args) {
Properties properties = System.getProperties();
Set<String> names = properties.stringPropertyNames();
for (String name : names) {
System.out.println(name + "=" + System.getProperty(name));
}
}
}
2) 输出每种类加载器加载的内容
其实是一些加载路径,使用分号分隔,我们可以使用split()方法拆分以后输出。
public class Demo06 {
public static void main(String[] args) {
/*Properties properties = System.getProperties();
Set<String> names = properties.stringPropertyNames();
for (String name : names) {
System.out.println(name + "=" + System.getProperty(name));
}*/
String boots = System.getProperty("sun.boot.class.path");
String[] strings = boots.split(";");
for (String string : strings) {
System.out.println(string);
}
System.out.println("-------------------");
String exts = System.getProperty("java.ext.dirs");
String[] strings2 = exts.split(";");
for (String string : strings2) {
System.out.println(string);
}
}
}
3) 输出每种类加载器的类型,使用方法getClassLoader()。
2. 引导类加载器
● 常量:sun.boot.class.path
1) 代码:使用String类,因为String类在java.lang包中,由引导类加载器加载。
// 测试引导类加载器
@Test
public void test2() {
System.out.println("引导类加载器加载路径");
String boots = System.getProperty("sun.boot.class.path");
String[] strings = boots.split(";");
for (String string : strings) {
System.out.println(string);
}
ClassLoader loader = String.class.getClassLoader();
System.out.println("引导类加载器 " + loader);
}
2) 加载的内容:
C:developJavajdk1.7.0_72jrelib esources.jar
C:developJavajdk1.7.0_72jrelib t.jar
C:developJavajdk1.7.0_72jrelibsunrsasign.jar
C:developJavajdk1.7.0_72jrelibjsse.jar
C:developJavajdk1.7.0_72jrelibjce.jar
C:developJavajdk1.7.0_72jrelibcharsets.jar
C:developJavajdk1.7.0_72jrelibjfr.jar
C:developJavajdk1.7.0_72jreclasses
3) 加载器的类型:
因为引导类加载器不是类,所以返回null。
3. 扩展类加载器
● 常量:java.ext.dirs
注:如果要使用lib/ext包中的类,要在eclipse中要进行如下设置。
在“Project Properties-->Java Build Path”中的指定JRE包的访问规则,Edit规则。
Accessible,指定为sun/**,指定可以在eclipse中访问sun开头的包。
1) 代码:使用任何一个在ext包中的类
// 测试扩展类加载器
@Test
public void test3() {
System.out.println("扩展类加载器加载路径");
String boots = System.getProperty("java.ext.dirs");
String[] strings = boots.split(";");
for (String string : strings) {
System.out.println(string);
}
ClassLoader loader = DNSNameService.class.getClassLoader();
System.out.println("扩展类加载器 " + loader);
}
2) 加载的内容:
C:developJavajdk1.7.0_72jrelibext
C:WindowsSunJavalibext
3) 加载器的类型:
sun.misc.Launcher$ExtClassLoader@153d05b
4. 应用类加载器
● 常量:java.class.path
1) 代码:自己编写的类,由应用类加载器加载
// 测试应用类加载器
@Test
public void test4() {
System.out.println("应用类加载器加载路径");
String boots = System.getProperty("java.class.path");
String[] strings = boots.split(";");
for (String string : strings) {
System.out.println(string);
}
ClassLoader loader = Demo06.class.getClassLoader();
System.out.println("应用类加载器 " + loader);
}
2) 加载的内容:
C:Userszhangpingworkin
C:developeclipsepluginsorg.junit_4.12.0.v201504281640junit.jar
C:developeclipsepluginsorg.hamcrest.core_1.3.0.v201303031735.jar
/C:/develop/eclipse/configuration/org.eclipse.osgi/383/0/.cp/
/C:/develop/eclipse/configuration/org.eclipse.osgi/382/0/.cp/
3) 加载器的类型:
sun.misc.Launcher$AppClassLoader@7692ed85
1.2 三个类加载器的关系
1. 示例:得到当前类的类加载器,并输出每个类加载器的父类加载器。
// 测试三个类加载器关系
@Test
public void test5() {
ClassLoader loader = Demo06.class.getClassLoader();
// sun.misc.Launcher$AppClassLoader@a53948
System.out.println("应用类加载器" + loader);
ClassLoader parent = loader.getParent();
// sun.misc.Launcher$ExtClassLoader@153d05b
System.out.println("上一层类加载器" + parent);
ClassLoader parent2 = parent.getParent();
// null表示引导类加载器
System.out.println("上一层的上一层类加载器" + parent2);
}
● 输出结果
应用类加载器 sun.misc.Launcher$AppClassLoader@a53948
上一层类加载器sun.misc.Launcher$ExtClassLoader@153d05b
上一层的上一层类加载器null
2. 结论:
1) 应用类加载器AppClassLoader的父加载器是扩展类加载器ExtClassLoader
2) 扩展类加载器ExtClassLoader的父加载器是引导类加载器BootstrapClassLoader
3) 注:不存在父类与子类的关系,它们都是Launcher的内部类
1.3 类加载器中的方法
1. URL getResource() 查找具有给定名称的资源,返回URL。
一般使用URL类中的getPath()方法,得到具体的路径。
2. InputStream getResourceAsStream(String name) 返回读取指定资源的输入流
3. 示例:在src目录下创建一个bean.xml文件,使用类加载器的方法
1) 得到文件的路径
2) 得到文件的输入流
public class Demo07 {
public static void main(String[] args) throws Exception {
ClassLoader loader = Demo07.class.getClassLoader();
// 得到类路径下指定的文件的路径
URL url = loader.getResource("Info.txt");
System.out.println(url.getPath());
// 得到类路径下指定的文件,并且转成相应的输入流
InputStream is = loader.getResourceAsStream("Info.txt");
byte[] b = new byte[1024];
int len = 0;
while ((len = is.read(b)) != -1) {
System.out.println(new String(b, 0, len));
}
}
}
第3章 代理模式
3.1 代理模式的分类
代理模式分成静态代理和动态代理
3.2 代理模式涉及到的对象
1. 抽象对象:是真实对象与代理对象的共同接口
2. 真实对象:对象本身,如:明星
3. 代理对象:相当于真实对象的一个代理对象如:经纪人
1) 拥有真实对象的成员变量
2) 通过构造方法传入真实对象
3) 可以改写真实对象的方法或对真实对象的方法进行拦截
4. 调用者:使用真实对象的消费者,如:明星的粉丝
3.3 静态代理的实现
1. 抽象对象:明星
public interface Star {
// 唱歌方法
public abstract void sing();
// 跳舞方法
public abstract void dance();
}
2. 真实对象:
public class BabyStrong implements Star {
@Override
public void sing() {
System.out.println("王宝强: 唱天下无贼...");
}
@Override
public void dance() {
System.out.println("王宝强: 跳一个人的武林...");
}
}
3. 代理对象:
public class StarProxy implements Star {
private BabyStrong bs;
public StarProxy(BabyStrong bs) {
this.bs = bs;
}
@Override
public void sing() {
System.out.println("经纪人: 弹出场费");
bs.sing();
System.out.println("经纪人: 收取出场费,和宝强分成");
}
@Override
public void dance() {
System.out.println("经纪人: 出场费不够,谈崩了...没有下文");
}
}
4. 调用者:
public class Fans {
public static void main(String[] args) {
// 创建真实对象
BabyStrong bs = new BabyStrong();
// 创建代理对象
StarProxy sp = new StarProxy(bs);
// 调用代理对象的方法
sp.sing();
sp.dance();
}
}
3.4 静态代理模式的特点
1. 优点:
在不修改现有类的前提下,对现有类的功能进行扩展和修改
可以拦截真实对象的方法
2. 缺点:
一个真实对象必须对应一个代理对象,如果大量使用会导致类的急剧膨胀。
如果抽象对象中方法很多,则代理对象也要编写大量的代码。
3.5 动态代理模式
1. 特点:
1) 动态生成代理对象,不用手动编写代理对象
2) 不需要编写目标对象中所有同名的方法
2. 动态代理类相应的API:
1. Proxy类:
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
² newProxyInstance()作用:生成动态代理对象
² 参数说明:
loader参数:真实对象的类加载器
interfaces参数:真实对象所有实现的接口数组
h参数: 具体的代理操作,InvocationHandler是一个接口,需要传入一个实现了此接口的实现类。
Object返回值:生成的代理对象
2.InvocationHandler接口:
Object invoke(Object proxy, Method method, Object[] args)
² invoke作用:每次调用代理对象的方法,都会走到这个方法来.在这个方法中实现对真实方法的增强或拦截
² 参数说明:
proxy:即方法newProxyInstance()方法返回的代理对象,该对象一般不要在invoke方法中使用,容易出现递归调用。
method: 真实对象的方法对象,会进行多次调用,每次调用method对象都不同。
args:代理对象调用方法时传递的参数
返回值:是真实对象方法的返回值
3.6 动态代理模式的开发步骤
- 定义接口
- 定义真实类
- 直接创建真实对象
- 通过Proxy类,创建代理对象
- 调用代理方法,其实是调用InvocationHandler接口中的invoke()方法
3.7 动态代理模式代码
Work接口:
public interface Work {
// 搬砖方法
public abstract void moveBricks(int count);
// 敲代码方法
public abstract void coding();
}
LaoWang真实对象类:
public class LaoWang implements Work {
@Override
public void moveBricks(int count) {
System.out.println("王宝强: 一次搬" + count + "块转");
}
@Override
public void coding() {
System.out.println("王宝强: 疯狂的敲代码");
}
}
Boss调用类:
public class Boss {
public static void main(String[] args) {
// 创建真实对象
final LaoWang laoW = new LaoWang();
// 创建动态代理,这个代理实现Work接口
// loader:真实对象的类加载器 LaoWang.class
// interfaces: 真实对象的类所有实现的接口数组Work
// h: 具体怎么代理
Work obj = (Work)Proxy.newProxyInstance(
// 真实对象的类加载器 LaoWang.class
LaoWang.class.getClassLoader(),
// 真实对象的类所有实现的接口数组
// new Class[]{Work.class}
LaoWang.class.getInterfaces(),
// 具体的代理操作
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理对象
// System.out.println(proxy);
// 被代理对象调用的方法
// System.out.println(method.getName());
// 被调用方法的参数
// System.out.println(Arrays.toString(args));
System.out.println("先执行代理,代理类再调用真实类的方法: " + method.getName());
method.invoke(laoW, args);
return null;
}
});
// 使用代理对象调用方法
obj.moveBricks(10);
obj.coding();
}
}
3.8 动态代理练习
需求:
有一个ArrayList<String> arrayList = new ArrayList<>();使用动态代理增强ArrayList的add方法,每个add到ArrayList中的元素都添加”代理 :”字符串.如arrayList.add(“张三”);实际添加到ArrayList中的内容为”代理 :张三”
案例代码:
public class Demo10 {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
// 真实对象
final ArrayList<String> arrayList = new ArrayList<>();
// 创建动态代理
List<String> listProxy = (List<String>)Proxy.newProxyInstance(
arrayList.getClass().getClassLoader(),
arrayList.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("add")) {
args[0] = "代理 : " + args[0];
}
Object result = method.invoke(arrayList, args);
return result;
}
});
listProxy.add("张三");
listProxy.add("李四");
listProxy.add("王五");
for (String obj : arrayList) {
System.out.println(obj);
}
}
}