1 Junit测试
Junit测试单元是由第三方提供的测试工具,可以使用main方法,让指定的方法直接执行起来;
在junit测试单元中有3个注解:
1: @Test 表示要测试这个方法
2: @Before 表示在执行被测试的方法(含有@Test的方法)之前,需要执行含有@Before的方法
3: @After 表示在执行被测试的方法(含有@Test的方法)之后,需要执行含有@After的方法
junit的使用步骤:
1:导入junit.jar包(在eclipse中已经内置了,直接引入即可)
2:直接在被测试的方法上面添加相应的注解即可;
使用前提:
被测试的方法要求:
1:必须是public权限
2:必须是void返回值
3:必须是空参数的列表
1 注解
1.1 概述
什么是注解:Annotation注解,是一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次
对比注释:注释是给开发人员阅读的,注解是给计算机提供相应信息的。
注解的作用:
1. 编译检查:通过代码里标识注解,让编译器能够实现基本的编译检查。例如:@Override
2. 代码分析:通过代码里标识注解,对代码进行分析,从而达到取代xml目的。
3. 编写文档:通过代码里标识注解,辅助生成帮助文档对应的内容
1.2 JDK提供的注解
@Deprecated 表示被修饰的方法已经过时。过时的方法不建议使用,但仍可以使用。(以后使用被这个修饰的方法时候会多一条线)
一般被标记位过时的方法都存在不同的缺陷:1安全问题;2新的API取代
2. @Override JDK5.0表示复写父类的方法;jdk6.0 还可以表示实现接口的方法
3. @SuppressWarnings 表示抑制警告,被修饰的类或方法如果存在编译警告,将被编译器忽略(里面可以传字符串的值)
可传的值为:
deprecation ,或略过时
rawtypes ,忽略类型安全
unused , 忽略不使用
unchecked ,忽略安全检查
null,忽略空指针
all,忽略所有
@Deprecated ```java //#1 方法过期 class Parent1_1{ @Deprecated public void init(){} }
@Override 复写父类方法
```java
//#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();
}
}
1.3 自定义注解:定义—基本语法
一般抽取工具或者优化的时候会使用。
关键字: @interface
只能写属性:
属性的格式:
public abstract 数据类型 属性名() default 属性值;
支持的数据类型:
基本类型、字符串String、Class、注解、枚举,以及以上类型的一维数组(注意:不支持基本数据类型的包装类型)
定义注解使用关键字: @interface
1. 定义类: class
2. 定义接口:interface
3. 定义枚举:enum
// #1 定义注解
@interface MyAnno1{
}
定义带有属性的注解
//#2 定义含有属性的注解
@interface MyAnno2{
public String username() default "jack";
}
属性格式:修饰符 返回值类型 属性名() [default 默认值]
1. 修饰符:默认值 public abstract ,且只能是public abstract。
2. 返回值类型:基本类型、字符串String、Class、注解、枚举,以及以上类型的一维数组
3. 属性名:自定义
4. default 默认值:可以省略
完整案例
//#3 完整含属性注解
@interface MyAnno3{
int age() default 1;
String password();
Class clazz();
MyAnno2 myAnno(); // 注解
Color color(); // 枚举
String[] arrs();
}
enum Color{
BLUE,RED;
}
1.4 自定义注解:使用
使用格式:@注解类名( 属性名= 值 , 属性名 = 值 , …..)
@MyAnno1
@MyAnno2(username="rose")
@MyAnno3(
age=18 ,
password="1234" ,
clazz=String.class ,
myAnno=@MyAnno2 ,
color = Color.RED ,
arrs = {"itcast","itheima"}
)
public class TestAnno2 {
注解使用的注意事项:
注解可以没有属性,如果有属性需要使用小括号括住。例如:@MyAnno1 或 @MyAnno1()
属性格式:属性名=属性值,多个属性使用逗号分隔。例如:@MyAnno2(username=”rose”)
如果属性名为value,且当前只有一个属性,value可以省略。
如果使用多个属性时,k的名称为value不能省略
如果属性类型为数组,设置内容格式为:{ 1,2,3 }。例如:arrs = {“itcast”,”itheima”}
如果属性类型为数组,值只有一个{} 可以省略的。例如:arrs = “itcast”
一个对象上,注解只能使用一次,不能重复使用。
(插播:
自定义了一个枚举数据类型的类:
关键字: enum
数据类型:属于引用数据类型;
作用:相当于定义了一个常量数组,数组中的每一个元素都是常量,常量的值的数据类型就是这个枚举类型;
常量定义格式:直接写常量名即可,多个常量名之间使用逗号分隔
* 常量名使用格式:类名.常量名
举例:
public enum Color {
//定义常量
RED,GREEN;
}
public @interface MyAn2 {
//定义属性
public abstract Color yanSe() default Color.GREEN;
}
1.5 自定义注解:定义—元注解
注解和反射一样,默认只在编译时期存在(在.java中存在,当编译成.class时会编译擦除)
元注解:用于修饰注解的注解。(用于修饰自定义注解的JDK提供的注解)
JDK提供4种元注解:
@Retention 用于确定被修饰的自定义注解生命周期(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{
}
举例:
MyAn3.java
/*
* 使用元注解,让MyAn3在代码运行的时候,也存活
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(value=RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAn3 {
public abstract String abc() default "abc的默认值";
}
MyAn3Test.java
import java.lang.reflect.Method;
import org.junit.Test;
@SuppressWarnings("all")
public class MyAn3Test {
@MyAn3
@Test
public void m1() throws Exception{
//1:解析m1这个方法头上的MyAn3注解的abc属性值
//1:判断m1的上面是否有MyAn3这个注解
Class c1 = MyAn3Test.class;
Method m = c1.getMethod("m1");
boolean b = m.isAnnotationPresent(MyAn3.class);
System.out.println(b);
//2:从m对象上面,获取一个MyAn3注解类型的对象
MyAn3 a3 = m.getAnnotation(MyAn3.class);
//3:调用属性名()即可获取属性值
String abc = a3.abc();
System.out.println(abc);
}
}
1.6 自定义注解:解析
如果给类、方法等添加注解,如果需要获得注解上设置的数据,那么我们就必须对注解进行解析,JDK提供java.lang.reflect.AnnotatedElement接口允许在运行时通过反射获得注解。
常用方法:
boolean isAnnotationPresent(Class annotationClass) 当前对象是否有注解
T getAnnotation(Class
Annotation[] getAnnotations() 获得当前对象及其从父类上继承的,所有的注解
Annotation[] getDeclaredAnnotations() 获得当前对象上所有的注解
测试
@MyAnno1
public class TestAnno2 {
public static void main(String[] args) {
boolean b = TestAnno2.class.isAnnotationPresent(MyAnno1.class);
System.out.println(b); //false
}
}
当运行上面程序后,我们希望输出结果是true,但实际是false。TestAnno2类上有@MyAnno1注解,但运行后不能获得,因为每一个自定义注解,需要使用JDK提供的元注解进行修饰才可以真正的使用。
1.7 模拟@Test功能练习
MyTest.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
}
Demo.java
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Demo {
@MyTest
public void m1(){
System.out.println("m1...");
}
public void m2(){
System.out.println("m2...");
}
public void m3(){
System.out.println("m3...");
}
@MyTest
public void m4(){
System.out.println("m4...");
}
//编写main方法,识别上面的m1,m2,m3,m4哪些方法上面有MyTest,可以直接,否则不能执行
public static void main(String[] args) throws Exception{
//1:反射获取m1,m2,m3,m4方法对象
Class c=Demo.class;
Method[] ms = c.getMethods();
//2:迭代所有的方法对象,判断,如果方法上面有MyTest,可以直接,否则不能执行
for (Method m : ms) {
boolean b = m.isAnnotationPresent(MyTest.class);
if(b){
m.invoke(c.newInstance());
}
}
}
}
2 JDK的动态代理
2.1 动态代理流程分析
2.1 java.lang.reflect.Proxy
用于创建动态代理对象的工具类,将来使用者直接面向这个工具类,即可获取一个动态代理对象,面向动态代理对象即可操作刘德华对象的方法。
创建动态代理对象的步骤:
面向JDK的Proxy工具类,直接调用静态方法即可获取一个动态代理对象。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
参数说明:
(1):loader,是目标类的类加载器。
(2):interfaces,是目标类实现的所有接口。
(3):h,调用处理程序,是专门用于告诉生成的动态代理对象如何处理目标类中的方法。
返回值说明:
Proxy的newProxyInstance方法返回的是一个动态代理对象,,这个动态代理对象由Proxy工具类负责创建出来,是目标类的兄弟对象,需要使用目标类的接口类型来接收。
InvocationHandler接口中的方法:
Object invoke(Object proxy, Method method, Object[] args)
在代理实例上处理方法调用并返回结果。
参数说明:
1:proxy 就是即将生成的动态代理对象,这里不能使用,否则语法报错。
2:method 代表的是使用者想调用的目标类中的方法对象。
3:args 代表的是method执行时所需要的实际参数。
返回值说明:
InvocationHandler的invoke方法。返回的就是增强后的处理结果。一般会直接返回method方法的invoke执行的结果。
2.3 练习举例
YiRen.java
public interface YiRen {
public void sing();
public void dance();
}
LDH.java
public class LDH implements YiRen{
public void sing(){
System.out.println("刘德华唱练习...");
}
public void dance(){
System.out.println("刘德华跳太空步...");
}
}
MyUtils.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyUtils {
//创建一个刘德华的对象
private static LDH l = new LDH();
//静态方法,直接造
public static Object getDongTaiDaiLi(){
//面向JDK工具类,直接造动态代理对象即可
YiRen y=(YiRen)Proxy.newProxyInstance(l.getClass().getClassLoader(),l.getClass().getInterfaces(),new InvocationHandler(){ //y是临时在内存中创建的,和l是兄弟,实现了相同接口
//invoke方法,程序员只负责编写,由生成的动态代理对象负责调用
public Object invoke(Object proxy, Method method, Object[] args){
//在这里就可以编写调用处理流程了........
System.out.println("刘德华开始执行 "+method.getName()+" 之前,都要执行的流程.........");
try {
//让刘德华的方法执行
Object in = method.invoke(l, args);
//控制方法执行后
System.out.println("刘德华的"+method.getName()+"方法执行后..........");
return in;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
});
//2:返回动态代理对象y
return y;
}
}
Test.java
/*
* 歌迷类
*/
public class Test {
public static void main(String[] args) {
//1>获取一个经纪人对象
YiRen y = (YiRen) MyUtils.getDongTaiDaiLi();
//2:面向经纪人对象,调用具体的方法
y.sing();
y.dance();
}
}
3 类加载器
类加载器:类加载器是负责加载类的对象。将class文件(硬盘)加载到内存生成Class对象。
所有的类加载器 都是 java.lang.ClassLoader 的子类
使用 类.class.getClassLoader() 获得加载自己的类加载器
类加载器加载机制:全盘负责委托机制
全盘负责:A类如果要使用B类(不存在),A类加载器C必须负责加载B类。
委托机制:A类加载器如果要加载资源B,必须询问父类加载是否加载。
如果加载,将直接使用。
如果没有机制,自己再加载。
采用 全盘负责委托机制 保证 一个class文件 只会被加载一次,形成一个Class对象。
注意:
如果一个class文件,被两个类加载器加载,将是两个对象。
提示 com.itheima.Hello 不能强制成 com.itheima.Hello
h.getClass() —>A h.getClass() —>B
自定义类加载,可以将一个class文件加载多次。
练习:
import sun.security.ec.SunEC;
/*
* 类加载器练习:
*
*
*/
public class Test {
public static void main(String[] args) {
//1:获取引导类加载器
ClassLoader l1 = String.class.getClassLoader();
System.out.println(l1);
//2:获取扩展类加载器
ClassLoader l2 = SunEC.class.getClassLoader(); //SunEC就是ext结尾的一个类
System.out.println(l2);
//3:获取应用类加载器
ClassLoader l3 = Test.class.getClassLoader();
System.out.println(l3);
System.out.println("---------------------");
ClassLoader l4 = l3.getParent();
System.out.println(l4);
System.out.println(l4.getParent());
}
}
输出结果:
null
sun.misc.Launcher$ExtClassLoader@1b6d3586
sun.misc.Launcher$AppClassLoader@6d06d69c
——————————-
sun.misc.Launcher$ExtClassLoader@1b6d3586
null
注意:
1:引导类加载器(根类加载器):是用c和c++写的所以在java里获取不到。java.lang包下的东西由他负责加载。
2:扩展类加载器:java包里带ext后缀的是由扩展类加载器加载的。
3:应用类加载器:只加载第三方jar包和程序员自己写的类。
4:父类加载器:读法:父-类加载器,强调是调用关系而不是继承关系。
5:在使用时,应用类加载器使用比较多,获取方法要记住。类.class.getClassLoader()
6:在敲上面代码的时候可能发生eclipse没有给SunEC的导包动作,这是因为eclipse默认情况下,sun公司的包,eclipse是没有权限使用的,不能使用sun开头的这些包的。此时需要的操作是:
(1)右键点击工程,选Properties。
(2)找Java Build Path
(3)找Libraries
(4)找JRE System Library,展开,找Access rules:,然后edit,然后add,
(5)在Resolution中选Accessible,Rule Pattern中添加一个“sun/**”。
4 综合练习:动态代理解决一个参数乱码问题
MyEncodingFilter.java
package
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 使用动态代理的方式,完成中文编码过滤的问题
*/
public class MyEncodingFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/*
* 思路:
* 如果是get请求,则动态的创建一个代理对象,并放行代理对象,
* 如果是post请求,则直接设置编码,并放行
*/
//1:转成子接口类型
final HttpServletRequest r = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
//2:解决响应乱码
res.setContentType("text/html;charset=utf-8");
//3:获取请求方式
String m = r.getMethod();
if("get".equalsIgnoreCase(m)){
//4:创建代理对象并放行
HttpServletRequest req=(HttpServletRequest)Proxy.newProxyInstance(r.getClass().getClassLoader(),r.getClass().getInterfaces(),new InvocationHandler() {
boolean flag = true;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在这里写处理流程
//根据method对象,获取方法名
String name = method.getName();
//让method对象执行
Object o=method.invoke(r, args);
if(name.equals("getParameter")){
//说明此时的method是getParameter方法
String s =(String)o;
if(s==null){
return null;
}
s=new String(s.getBytes("iso8859-1"),"utf-8");
return s;
}else if(name.equals("getParameterValues")){//注意:每次调用getParameterValues的时候会得到一个新数组,所以里面不能用增强for(具体见下图debug模式图)
//说明此时的method是getParameterValues方法
String[] s =(String[])o;
if(s!=null){
for (int i=0;i<s.length;i++) {
s[i] = new String(s[i].getBytes("iso8859-1"),"utf-8");
}
}
return s;
}else if(name.equals("getParameterMap")){//注意:每次调用getParameterMap的时候会得到的都是同一个map,所以里面可以用增强for,又因为是同一个map,所以只能转一次,需要定义一个布尔类型变量flag控制一下。(具体见下图debug模式图)
//说明此时的method是getParameterValues方法
Map<String,String[]> map =(Map<String,String[]>)o;
if(flag){
Set<String> set = map.keySet();
for (String key : set) {
String[] s = map.get(key);
for (int i=0;i<s.length;i++) {
s[i] = new String(s[i].getBytes("iso8859-1"),"utf-8");
}
}
flag=false;
}
return map;
}
//其他方法,直接返回原始值
return o;
}
});
//5:放行req对象
chain.doFilter(req, res);
}else{
//设置编码.,并放行
r.setCharacterEncoding("utf-8");
chain.doFilter(r, res);
}
}
public void destroy() {
}
public void init(FilterConfig fConfig) throws ServletException {
}
}
Hello.java
package
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 验证参数的servlet
*/
public class Hello extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String n = request.getParameter("name");
String[] values2 = request.getParameterValues("hobby");
String[] values = request.getParameterValues("hobby");
Map<String, String[]> map2 = request.getParameterMap();
Map<String, String[]> map = request.getParameterMap();
System.out.println("一个参数:"+n);
System.out.println("一个数组:"+Arrays.toString(values));
Set<String> set = map.keySet();
for (String key : set) {
System.out.println("map:"+key+"====>"+Arrays.toString(map.get(key)));
}
response.getWriter().println("你好啊");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
测试:
浏览器输入:
localhost:/day45/Hello?name=张三&hobby=抽烟&hobby=喝酒