首页 > 代码库 > 第17天(基础加强_注解_类加载器_动态代理)_学习目标版本
第17天(基础加强_注解_类加载器_动态代理)_学习目标版本
学习目标
- 能够使用Junit进行单元测试
- 能够说出注解的作用
- 能够使用JDK提供的3个注解
- 能够根据基本语法编写自定义注解实现类
- 能够了解自定义注解解析
- 能够了解元注解使用
- 能够根据上课案例分析,编写模拟@Test案例
- 能够理解动态代理原理
- 能够使用动态代理Proxy编写代理类
Junit单元测试
Junit介绍
JUnit是一个Java语言的单元测试框架,简单理解为可以用于取代java的main方法。Junit属于第三方工具,一般情况下需要导入jar包,不过,多数Java开发环境已经集成了JUnit作为单元测试工具。
Junit的使用
创建"MyJunit"java项目,并创建"com.itheima_00_Junit"包
- 编写测试类,简单理解Junit可以用于取代java的main方法
- 在测试类方法上添加注解 @Test
- @Test修饰的方法要求:public void 方法名() {…} ,方法名自定义建议test开头,没有参数。
- 添加Eclipse中集成的Junit库,鼠标点击"@Test",使用快捷键"ctrl + 1",点击"Add Junit…"
结果
- 使用:选中方法右键,执行当前方法;选中类名右键,执行类中所有方法(方法必须标记@Test)
- 常用注解
@Test,用于修饰需要执行的方法
@Before,测试方法前执行的方法
@After,测试方法后执行的方法
publicclass JunitDemo_1 {
@Test
publicvoidmyTest(){
System.out.println("测试 test");
}
@Before
publicvoidmyBefore(){
System.out.println("方法前");
}
@After
publicvoidmyAfter(){
System.out.println("方法后");
}
/*运行结果:
* 方法前
* 测试 test
* 方法后
*/
}
- 常见使用错误,如果没有添加"@Test",使用"Junit Test"进行运行,将抛异常
注解
注解概述
- 什么是注解:Annotation注解,是一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次
- 对比注释:注释是给开发人员阅读的,注解是给计算机提供相应信息的。
- 注解的作用:
- 编译检查:通过代码里标识注解,让编译器能够实现基本的编译检查。例如:@Override
- 代码分析:通过代码里标识注解,对代码进行分析,从而达到取代xml目的。
- 编写文档:通过代码里标识注解,辅助生成帮助文档对应的内容
JDK提供的注解
- @Deprecated 表示被修饰的方法已经过时。过时的方法不建议使用,但仍可以使用。
- 一般被标记位过时的方法都存在不同的缺陷:1安全问题;2新的API取代
- @Override JDK5.0表示复写父类的方法;jdk6.0 还可以表示实现接口的方法
- @SuppressWarnings 表示抑制警告,被修饰的类或方法如果存在编译警告,将被编译器忽略
deprecation ,或略过时
rawtypes ,忽略类型安全
unused ,忽略不使用
unchecked ,忽略安全检查
null,忽略空指针
serial, 忽略序列号
all,忽略所有
- @Deprecated
//#1 方法过期
class Parent1_1{
@Deprecated
public void init(){
}
}
- @Override 复写父类方法
//#2.1 JDK5.0 复写父类方法
class Parent1_2{
public void init(){
}
}
class Son1_2 extends Parent1_2{
@Override
public void init() {
}
}
- @Override 实现接口方法
//#2.2 JDK6.0 实现父接口方法
interface Parent1_3{
public void init();
}
class Son1_3 implements Parent1_3{
@Override
public void init() {
}
}
- @SupperssWarings
//#3 抑制警告
// serial : 实现序列号接口,但没有生产序列号
@SuppressWarnings("serial")
class Parent1_4 implements java.io.Serializable{
//null :空指针
@SuppressWarnings("null")
public void init(){
//rawtypes :类型安全,没有使用泛型
//unused : 不使用
@SuppressWarnings({ "rawtypes", "unused" })
List list = new ArrayList();
String str = null;
str.toString();
}
}
- 没有抑制警告
自定义注解:定义—基本语法
- 定义注解使用关键字: @interface
- 定义类: class
- 定义接口:interface
- 定义枚举:enum
// #1 定义注解
@interface MyAnno1{
}
- 定义带有属性的注解
//#2 定义含有属性的注解
@interface MyAnno2{
public String username() default "jack";
}
- 属性格式:修饰符返回值类型属性名() [default 默认值]
- 修饰符:默认值 public abstract ,且只能是public abstract。
- 返回值类型:基本类型、字符串String、Class、注解、枚举,以及以上类型的一维数组
- 属性名:自定义
- default 默认值:可以省略
- 完整案例
//#3 完整含属性注解
@interface MyAnno3{
int age() default 1;
String password();
Class clazz();
MyAnno2 myAnno(); // 注解
Color color(); // 枚举
String[] arrs();
}
enum Color{
BLUE,RED;
}
自定义注解:使用
- 使用格式:@注解类名( 属性名= 值 , 属性名 = 值 , .....)
@MyAnno_1
@MyAnno_2(username="rose")
@MyAnno_3(
age=18 ,
password="1234" ,
clazz=String.class ,
myAnno=@MyAnno_2 ,
color = Color.RED ,
arrs = {"itcast","itheima"}
)
publicclass MyAnnoTest_4 {
}
- 注解使用的注意事项:
- 注解可以没有属性,如果有属性需要使用小括号括住。例如:@MyAnno1或@MyAnno1()
- 属性格式:属性名=属性值,多个属性使用逗号分隔。例如:@MyAnno2(username="rose")
- 如果属性名为value,且当前只有一个属性,value可以省略。
- 如果使用多个属性时,k的名称为value不能省略
- 如果属性类型为数组,设置内容格式为:{ 1,2,3 }。例如:arrs = {"itcast","itheima"}
- 如果属性类型为数组,值只有一个{} 可以省略的。例如:arrs = "itcast"
- 一个对象上,注解只能使用一次,不能重复使用。
自定义注解:解析
- 如果给类、方法等添加注解,如果需要获得注解上设置的数据,那么我们就必须对注解进行解析,JDK提供java.lang.reflect.AnnotatedElement接口允许在运行时通过反射获得注解。
- 常用方法:
- boolean isAnnotationPresent(Class annotationClass) 当前对象是否有注解
- T getAnnotation(Class<T> annotationClass) 获得当前对象上指定的注解
- Annotation[] getAnnotations() 获得当前对象及其从父类上继承的,所有的注解
- Annotation[] getDeclaredAnnotations() 获得当前对象上所有的注解
- 测试
publicclass MyAnnoTest_5 {
publicstaticvoid main(String[] args) {
booleanb = MyAnnoTest_4.class.isAnnotationPresent(MyAnno_1.class);
System.out.println(b); //false
}
}
当运行上面程序后,我们希望输出结果是true,但实际是false。TestAnno2类上有@MyAnno1注解,但运行后不能获得,因为每一个自定义注解,需要使用JDK提供的元注解进行修饰才可以真正的使用。
自定义注解:定义—元注解
- 元注解:用于修饰注解的注解。(用于修饰自定义注解的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{
}
案例:自定义@Test
案例分析
- 模拟Junit测试,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修改方法,且在运行时可以获得。
- 其次编写目标类(测试类),然后给目标方法(测试方法)使用@MyTest注解
- 最后编写测试类,使用main方法模拟Junit的右键运行。
案例实现
- 步骤1:编写自定义注解类@MyAnno
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public@interfaceMyAnno {
}
- 步骤2:编写目标类AnnotationDemo
publicclass AnnotationDemo_0 {
@MyAnno
publicvoid demo1(){
System.out.println("demo1执行了...");
}
@MyAnno
publicvoid demo2(){
System.out.println("demo2执行了...");
}
publicvoid demo3(){
System.out.println("demo3执行了...");
}
}
- 步骤3:编写测试方法
publicclass AnnotationTest_1 {
publicstaticvoid main(String[] args) {
try {
//1.1 反射:获得类的字节码对象.Class
Classclazz = AnnotationDemo_0.class;
//1.2 获得实例对象
Object obj = clazz.newInstance();
//2 获得目标类所有的方法
Method[] allMethod = clazz.getMethods();
//3 遍历所有的方法
for (Method method : allMethod) {
//3.1 判断方法是否有MyTest注解
booleanflag = method.isAnnotationPresent(MyAnno.class);
if (flag) {
//4 如果有注解运行指定的类
method.invoke(obj, null);
}
}
} catch (Exception e) {
e.printStackTrace();
}
/* 输出结果:
* demo1执行了...
* demo2执行了...
*/
}
}
类加载器
- 类加载器:类加载器是负责加载类的对象。将class文件(硬盘)加载到内存生成Class对象。
所有的类加载器都是 java.lang.ClassLoader 的子类
- 使用 类.class.getClassLoader() 获得加载自己的类加载器
- 类加载器加载机制:全盘负责委托机制
全盘负责:A类如果要使用B类(不存在),A类加载器C必须负责加载B类。
委托机制:A类加载器如果要加载资源B,必须询问父类加载是否加载。
如果加载,将直接使用。
如果没有机制,自己再加载。
- 采用全盘负责委托机制保证一个class文件只会被加载一次,形成一个Class对象。
动态代理
java代理有jdk动态代理、cglib代理,这里只说下jdk动态代理,jdk动态代理主要使用的是java反射机制(既java.lang.reflect包)
Proxy类
原理是(歌手、经纪人做例子):
- 建立一个公共的接口,比如:歌手public interface Singer;
- 用具体的类实现接口,比如:周杰伦,他是歌手所以实现Singer这个类,class MySinger implements Singer,重写singer方法.
- 建立代理类,这里也就是经纪人,他需要实现InvocationHandler接口,并重写invoke方法
- 这样当有什么事情,要找周杰伦(具体类)唱歌的时候,就必须先到经纪人(代理类)那里处理,代理人在决定要不要与你见面(该方法要不要执行),找到经纪人方法invoke,经纪人方法invoke来找周杰伦的singer方法
动态代理:程序运行时,使用JDK提供工具类(Proxy),动态创建一个类,此类一般用于代理。
代理:你 -- 代理(增强) -- 厂商
代理类:目标类:被代理的
动态代理使用前提:必须有接口
Object proxyObj = Proxy.newProxyInstance(参数1,参数2,参数3);
参数1:ClassLoader,负责将动态创建类,加载到内存。当前类.class.getClassLoader();
参数2:Class[] interfaces ,代理类需要实现的所有接口(确定方法),被代理类实例.getClass().getInterfaces();
参数3:InvocationHandler, 请求处理类,代理类不具有任何功能,代理类的每一个方法执行时,调用处理类invoke方法。
voke(Object proxy ,Method ,Object[] args)
参数1:代理实例
参数2:当前执行的方法
参数3:方法实际参数。
动态代理案例: 模拟Collections工具类的静态方法
@Test
publicvoid test_2() {
List<String>list = new ArrayList<String>();
list.add("123");
System.out.println(list.size());
list = myProxy(list);
list.set(0, "1");
System.out.println(list.size());
}
publicstatic List<String> myProxy(List<String>list) {
List<String>listProxy = (List) Proxy.newProxyInstance(
ReflectTest.class.getClassLoader(), list.getClass()
.getInterfaces(), new MyPro(list));
returnlistProxy;
publicclass MyPro implements InvocationHandler{
private List<String>list ;
public MyPro(List<String>list){
this.list =list;
}
public Object invoke(Object porxy,Method method,Object[] arge) throws Exception{
if("add".equals(method.getName()))
thrownew UnsupportedOperationException("add NO");
if("set".equals(method.getName()))
thrownew UnsupportedOperationException("set NO");
if("remove".equals(method.getName()))
thrownew UnsupportedOperationException("remove NO");
returnmethod.invoke(list, arge);
}
}
@Test
publicvoid test_2() {
List<String>list = new ArrayList<String>();
list.add("123");
System.out.println(list.size());
list = myProxy(list);
list.set(0, "1");
System.out.println(list.size());
}
publicstatic List<String> myProxy(final List<String>list) {
List<String>list2= (List) Proxy.newProxyInstance(ReflectTest.class
.getClassLoader(), list.getClass().getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
if ("add".equals(method.getName()))
thrownew UnsupportedOperationException("add NO");
if ("set".equals(method.getName()))
thrownew UnsupportedOperationException("set NO");
if ("remove".equals(method.getName()))
thrownew UnsupportedOperationException("r NO");
returnmethod.invoke(list, args);
}
});
returnlist2;
}
第17天(基础加强_注解_类加载器_动态代理)_学习目标版本