第04天 java基础加强
今日内容介绍
u Xml的综合案例
u 注解
u 类的加载
u 动态代理
第1章 注解
1.1 注解概述
l 什么是注解:Annotation注解,是一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次
n 对比注释:注释是给开发人员阅读的,注解是给计算机提供相应信息的。
l 注解的作用:
- 编译检查:通过代码里标识注解,让编译器能够实现基本的编译检查。例如:@Override
- 代码分析:通过代码里标识注解,对代码进行分析,从而达到取代xml目的。
- 编写文档:通过代码里标识注解,辅助生成帮助文档对应的内容
- @Deprecated 表示被修饰的方法已经过时。过时的方法不建议使用,但仍可以使用。
1.2 JDK常见注解
1.2.1 常见注解
n 一般被标记位过时的方法都存在不同的缺陷:1安全问题;2新的API取代
- @Override JDK5.0表示复写父类的方法;jdk6.0 还可以表示实现接口的方法
- @SuppressWarnings 表示抑制警告,被修饰的类或方法如果存在编译警告,将被编译器忽略
deprecation ,或略过时
rawtypes ,忽略类型安全
unused , 忽略不使用
unchecked ,忽略安全检查
null,忽略空指针
all,忽略所有
1.2.2 案例代码一
AnnotationDemo_01
public class AnnotationDemo_01 {
@Deprecated
public void init(){
}
}
AnnotationDemo_02
public class AnnotationDemo_02 {
}
// 情况1: jdk1.5表示复写父类的方法
class Parent_2_1{
public void init(){
}
}
class Son_2_1 extends Parent_2_1{
@Override
public void init() {
}
}
// 情况2:jdk1.6 表示方法实现接口声明的方法
interface Parent_2_2{
public void init();
}
class Son_2_2 implements Parent_2_2 {
@Override
public void init() {
}
}
AnnotationDemo_03
@SuppressWarnings("serial")
public class AnnotationDemo_03 implements java.io.Serializable {
@SuppressWarnings({ "unused", "null", "deprecation", "rawtypes" })
public static void main(String[] args) {
List list = new ArrayList();
String str = null;
str.toString();
new Thread().stop();
}
}
1.3 自定义注解
1.3.1 自定义注解--定义与使用
l 定义注解使用关键字: @interface
- 定义类: class
- 定义接口:interface
- 定义枚举:enum
// #1 定义注解
@interface MyAnno1{
}
l 定义带有属性的注解
//#2 定义含有属性的注解
@interface MyAnno2{
public String username() default "jack";
}
l 属性格式:修饰符 返回值类型 属性名() [default 默认值]
- 修饰符:默认值 public abstract ,且只能是public abstract。
- 返回值类型:基本类型、字符串String、Class、注解、枚举,以及以上类型的一维数组
- 属性名:自定义
- default 默认值:可以省略
l 注解使用的注意事项:
- 注解可以没有属性,如果有属性需要使用小括号括住。例如:@MyAnno1 或 @MyAnno1()
- 属性格式:属性名=属性值,多个属性使用逗号分隔。例如:@MyAnno2(username="rose")
- 如果属性名为value,且当前只有一个属性,value可以省略。
- 如果使用多个属性时,k的名称为value不能省略
- 如果属性类型为数组,设置内容格式为:{ 1,2,3 }。例如:arrs = {"itcast","itheima"}
- 如果属性类型为数组,值只有一个{} 可以省略的。例如:arrs = "itcast"
- 一个对象上,注解只能使用一次,不能重复使用。
1.3.2 代码案例二
Anno_01.java
public @interface Anno_01 {
}
Anno_02.java
public @interface Anno_02 {
/* 格式:修饰符 返回值类型 属性名() default 默认值
* * 修饰符:public abstract
* * 返回值类型:基本类型、String、Class、注解、枚举,以及以上类型的一位数组
* * 属性名:自定义
* * 默认值:可以省略
*/
public abstract String username() default "默认值";
public int age() ;
public Class clazz();
public Anno_01 anno();
public Color color();
}
//枚举:相当于多例。Color.RED
enum Color{
RED,BLUE;
}
//自定义多例
class Color2{
private Color2(){
}
public static final Color2 RED = new Color2();
public static final Color2 BLUE = new Color2();
}
Anno_03.java
public @interface Anno_03 {
public String[] hobbies();
}
Anno_04.java
public @interface Anno_04 {
public String[] value();
}
TestAnnotation.java
@Anno_01
@Anno_02(username="jack", age=18,clazz=Date.class , anno=@Anno_01 , color = Color.RED )
//@Anno_03(hobbies={"抽烟","喝酒","烫头"}) //数组表示使用{}括住
@Anno_03(hobbies="抽烟") //如果只有一个值,{}可以省略
//@Anno_04(value={"a","b"})
//@Anno_04(value="a")
@Anno_04("a") //只有一对属性时,属性名为value,可以省略属性名
//同一个对象中,同一个注解只能使用一次。
public class TestAnnotation {
}
1.3.3 自定义注解--解析和元注解
1.3.3.1 解析
如果给类、方法等添加注解,如果需要获得注解上设置的数据,那么我们就必须对注解进行解析,JDK提供java.lang.reflect.AnnotatedElement接口允许在运行时通过反射获得注解。
常用方法:
boolean isAnnotationPresent(Class annotationClass) 当前对象是否有注解
T getAnnotation(Class<T> annotationClass) 获得当前对象上指定的注解
Annotation[] getAnnotations() 获得当前对象及其从父类上继承的,所有的注解
Annotation[] getDeclaredAnnotations() 获得当前对象上所有的注解
测试
public class MyAnnoTest_5 {
public static void main(String[] args) {
boolean b = MyAnnoTest_4.class.isAnnotationPresent(MyAnno_1.class);
System.out.println(b); //false
}
}
当运行上面程序后,我们希望输出结果是true,但实际是false。TestAnno2类上有@MyAnno1注解,但运行后不能获得,因为每一个自定义注解,需要使用JDK提供的元注解进行修饰才可以真正的使用。
1.3.3.2 元注解
元注解:用于修饰注解的注解。(用于修饰自定义注解的JDK提供的注解)
JDK提供4种元注解:
@Retention 用于确定被修饰的自定义注解生命周期
RetentionPolicy.SOURCE 被修饰的注解只能存在源码中,字节码class没有。用途:提供给编译器使用。
RetentionPolicy.CLASS 被修饰的注解只能存在源码和字节码中,运行时内存中没有。用途:JVM java虚拟机使用
RetentionPolicy.RUNTIME 被修饰的注解存在源码、字节码、内存(运行时)。用途:取代xml配置
@Target 用于确定被修饰的自定义注解 使用位置
ElementType.TYPE 修饰 类、接口
ElementType.CONSTRUCTOR 修饰构造
ElementType.METHOD 修饰方法
ElementType.FIELD 修饰字段
@Documented 使用javaDoc生成 api文档时,是否包含此注解 (了解)
@Inherited 如果父类使用被修饰的注解,子类是否继承。(了解)
修改注解类,在运行测试实例,输出结果为:true。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno1{
}
1.3.4 案例代码三
AnnoParse_01.java
@MyAnno("测试数据")
public class AnnoParse_01 {
public static void main(String[] args) {
//运行时,获得指定类上面的注解,从而获得对应数据
boolean b = AnnoParse_01.class.isAnnotationPresent(MyAnno.class);
if(b){
MyAnno myAnno = AnnoParse_01.class.getAnnotation(MyAnno.class);
String value = myAnno.value();
System.out.println(value);
} else {
System.out.println("没有注解");
}
}
@MyAnno
public AnnoParse_01(){
}
@MyAnno
public void init(){
}
}
MyAnno.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/* 元注解:jdk提供一套注解,元注解用于修饰自定义注解。
* 用于控制自定义注解 使用位置、生命周期等基本信息。
*
* @Target 用于限定自定义注解使用位置
* @Target(ElementType.CONSTRUCTOR) 只能在构造方法使用
* @Target(ElementType.METHOD) 只能在普通方法使用
* @Target(ElementType.FIELD) 只能在字段使用
* @Target(ElementType.TYPE) 只能在类、接口使用
* @Retention 用于确定自定义注解生命周期
* @Retention(RetentionPolicy.SOURCE) 自定义注解只在源码有效,编译之后将删除(class文件没有)。提供编译器使用
* @Retention(RetentionPolicy.CLASS) 自定义注解只在源码和字节码有效,编译之后有,运行时内存没有。提供JVM使用
* @Retention(RetentionPolicy.RUNTIME) 自定义注解在源码、字节码和内存都有效。【】提供程序使用,用于取代xml配置文件
*
*/
@Target({ElementType.TYPE,ElementType.CONSTRUCTOR,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
public String value() default "默认值";
}
1.4 综合案例
1.4.1 案例分析
l 模拟Junit测试,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修改方法,且在运行时可以获得。
l 其次编写目标类(测试类),然后给目标方法(测试方法)使用@MyTest注解
l 最后编写测试类,使用main方法模拟Junit的右键运行。
1.4.2 案例代码四
MyTest.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
Demo.java
public class Demo {
@MyTest
public void demo01(){
System.out.println("demo01");
}
//@MyTest
public void demo02(){
System.out.println("demo02");
}
@MyTest
public void demo03(){
System.out.println("demo03");
}
}
TestApp.java
import java.lang.reflect.Method;
public class TestApp {
public static void main(String[] args) throws Exception{
//模拟 右键运行
// 当前指定类,所有的方法,是否有@MyTest注解,如果有将运行
//1 当前类
Class clazz = Demo.class;
// * new 实例
Object obj = clazz.newInstance();
//2 获得所有的方法
Method[] allMethod = clazz.getMethods();
for(Method method : allMethod){
//3 判断方法上是否有指定的注解
boolean b = method.isAnnotationPresent(MyTest.class);
if(b){
// 4 如果有,运行类
method.invoke(obj);
}
}
}
}
第2章 类的加载
2.1 类加载--理论
l 类加载器:类加载器是负责加载类的对象。将class文件(硬盘)加载到内存生成Class对象。
所有的类加载器 都是 java.lang.ClassLoader 的子类
l 使用类.class.getClassLoader() 获得加载自己的类加载器
l 类加载器加载机制:全盘负责委托机制
全盘负责:A类如果要使用B类(不存在),A类加载器C必须负责加载B类。
委托机制:A类加载器如果要加载资源B,必须询问父类加载是否加载。
如果加载,将直接使用。
如果没有机制,自己再加载。
l 采用 全盘负责委托机制 保证 一个class文件 只会被加载一次,形成一个Class对象。
l 注意:
如果一个class文件,被两个类加载器加载,将是两个对象。
提示 com.itheima.Hello 不能强制成 com.itheima.Hello
h.getClass() -->A h.getClass() -->B
自定义类加载,可以将一个class文件加载多次。
2.2 类加载--演示
2.2.1 案例代码五
ClassLoaderDemo_01
import org.junit.Test;
public class ClassLoaderDemo_01 {
@Test
public void demo01(){
// 确定引导类加载器,加载内容:rt.jar (Runtime )
// * JDK固定配置信息,sun.boot.class.path 用于表示 引导类加载器所加载的内容
String paths = System.getProperty("sun.boot.class.path");
String[] allPath = paths.split(";");
for(String p : allPath){
System.out.println(p);
}
}
@Test
public void demo02(){
// 确定引导类加载器,类型:null
ClassLoader cl = String.class.getClassLoader();
System.out.println(cl);
}
}
ClassLoaderDemo_02.java
import org.junit.Test;
import sun.net.spi.nameservice.dns.DNSNameService;
public class ClassLoaderDemo_02 {
@Test
public void demo01(){
// 确定扩展类加载器,加载内容: jre/lib/ext
// * JDK固定配置信息,java.ext.dirs 用于表示 扩展类加载器所加载的内容
String paths = System.getProperty("java.ext.dirs");
String[] allPath = paths.split(";");
for(String p : allPath){
System.out.println(p);
}
}
@Test
public void demo02(){
// 确定扩展类加载器,类型:Launcher$ExtClassLoader
ClassLoader cl = DNSNameService.class.getClassLoader();
System.out.println(cl);
}
}
ClassLoaderDemo_03.java
import org.junit.Test;
import sun.net.spi.nameservice.dns.DNSNameService;
public class ClassLoaderDemo_03 {
@Test
public void demo01(){
// 确定应用类加载器,加载内容: 项目/bin (编译后内容) ,自己编写类由应用类加载加载
// * JDK固定配置信息,java.class.path 用于表示应用类加载器所加载的内容
String paths = System.getProperty("java.class.path");
String[] allPath = paths.split(";");
for(String p : allPath){
System.out.println(p);
}
}
@Test
public void demo02(){
// 确定应用类加载器,类型:Launcher$AppClassLoader
ClassLoader cl = ClassLoaderDemo_03.class.getClassLoader();
System.out.println(cl);
}
}
ClassLoaderDemo_04.java
import org.junit.Test;
import sun.net.spi.nameservice.dns.DNSNameService;
public class ClassLoaderDemo_04 {
@Test
public void demo01(){
//3个类加载的关系
ClassLoader c1 = ClassLoaderDemo_04.class.getClassLoader();
System.out.println(c1); //应用 (AppClassLoader)
ClassLoader c2 = c1.getParent();
System.out.println(c2); //扩展(ExtClassLoader)
ClassLoader c3 = c2.getParent();
System.out.println(c3); //引导(null)
}
}
第3章 动态代理
3.1 动态代理概述
java代理有jdk动态代理、cglib代理,这里只说下jdk动态代理,jdk动态代理主要使用的是java反射机制(既java.lang.reflect包)
3.2 Proxy类
动态代理:程序运行时,使用JDK提供工具类(Proxy),动态创建一个类,此类一般用于代理。
代理:你 -- 代理(增强) -- 厂商
代理类: 目标类:被代理的
动态代理使用前提:必须有接口
Object proxyObj = Proxy.newProxyInstance(参数1,参数2,参数3);
参数1:ClassLoader,负责将动态创建类,加载到内存。 当前类.class.getClassLoader();
参数2:Class[] interfaces ,代理类需要实现的所有接口(确定方法),被代理类实例.getClass().getInterfaces();
参数3:InvocationHandler, 请求处理类,代理类不具有任何功能,代理类的每一个方法执行时,调用处理类invoke方法。
invoke(Object proxy ,Method ,Object[] args)
参数1:代理实例
参数2:当前执行的方法
参数3:方法实际参数。
3.3
动态代理原理
3.4 动态代理案例
3.4.1 需求分析
自定一个MyCollections,在该类中定义一个unmodifiableList方法实现,使用动态代理对该方法增强,使list对象不能再加入元素。
3.4.2 案例代码六
MyCollections.java
public class MyCollections {
@SuppressWarnings({ "unchecked", "rawtypes" })
public static List<String> unmodifiableList(final List<String> list) {
//list 所有功能都有 (目标类)
//proxyList 希望不能进行增删改,只能查询 (代理类)
// 参数1:ClassLoader ,动态代理需要一个类加载器
ClassLoader loader = MyCollections.class.getClassLoader();
// 参数2:Class[] interfaces 需要与目标类接口保持一致
Class[] interfaces = list.getClass().getInterfaces();
List<String> proxyList = (List<String>)Proxy.newProxyInstance(loader, interfaces, new InvocationHandler(){
//代理类的每一个方法调用一次,处理类invoke方法都执行一次
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//处理(增强)--不允许添加
//1 获得方法名
String methodName = method.getName();
//2 eq 不同方法的处理
if("add".equals(methodName)){
throw new UnsupportedOperationException("操作不允许");
}
//处理类中直接执行目标类对应的方法
return method.invoke(list, args);
}
});
return proxyList;
}
}
ProxyDemo_01.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
public class ProxyDemo_01 {
@Test
public void demo01(){
List<String> list = new ArrayList<>();
list.add("abc");
String s = list.get(0);
System.out.println(s);
List<String> list2 = Collections.unmodifiableList(list);
String s2 = list2.get(0);
System.out.println(s2);
list2.add("123");
//不支持操作异常,当前list2不允许修改。
//java.lang.UnsupportedOperationException
}
@Test
public void demo02(){
List<String> list = new ArrayList<>();
list.add("abc");
String s = list.get(0);
System.out.println(s);
List<String> list2 = MyCollections.unmodifiableList(list);
String s2 = list2.get(0);
System.out.println(s2);
list2.add("123"); //不允许
System.out.println(list2.size());
//不支持操作异常,当前list2不允许修改。
//java.lang.UnsupportedOperationException
}
}