一.类加载器
1.什么是类加载器,作用是什么
类加载器就加载字节码文件(.class)
2.类加载器的种类
类加载器有三种,不同加载器加载不同
- BootStrap:引导类加载器:加载都是最基础的文件
- ExtClassLoader:扩展类加载器:加载都是基础的文件
- AppClassLoader:应用类加载器:三方jar包和自己编写java文件
- 加载顺序:BootStrap -> ExtClassLoader -> AppClassLoader
怎么获得类加载器(重点)
- 获得字节码对象的三种方式
- Class.forName(“…”);
- 对象.getClass()
- 类名.class
- 字节码对象.getClassLoad()获得ClassLoader类型(即类加载器)的对象
- 由于src下的文件都会被编译,故通过类加载器的对象可以获得classes(即编译后的src)下的任何资源[注意是由于在classes内,故都是对应java文件的字节码文件]
- 实例代码:
package demo;
import java.net.URL;
public class Demo {
public static void main(String[]args) {
//获得Demo字节码文件的类加载器
Class clazz = Demo.class;//获得Demo的字节码对象
ClassLoader classLoader = clazz.getClassLoader();//获得Demo的类加载器
//getResource的参数路径相对classes(src)
URL jdbc_properties_url = classLoader.getResource("demo/jdbc.properties");//获得classes(src)下的任何资源地址
String path = jdbc_properties_url.getPath();
System.out.println(path);
}
}
//打印结果/Users/wangzhe/Documents/workspace/WEB25/build/classes/demo/jdbc.properties
二.注解 @xxx
1.什么是注解,注解的作用
注解就是符合一定格式的语法 @xxxx
注解作用:
- 注释:在阅读程序时清楚(给程序员看的)
- 注解:给jvm看的(给机器看的)
注解在目前而言最主流的应用:代替配置文件
如在创建web项目时选项Dynamic web module version3.0以上没有web.xml文件,是因为被注解取代,如下:
package demo; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/demo") public class DemoServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().append("Served at: ").append(request.getContextPath()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }
此时@WebServlet就代替在web.xml中配置url-pattern等相关配置。
关于配置文件与注解开发的优缺点:
- 注解优点:开发效率高,成本低
- 注解缺点:耦合性大,并且不利于后期维护(如果是在web.xml中配置则直接修改配置信息即可)
- 故企业开发中,我们通常把不变的内容定义成注解,变化的内容在xml中配置
2.jdk5提供的注解
@Override:告知编译器此方法是覆盖父类的(可以在方法上用),帮助检查覆盖父类的方法是否正确
@Deprecated:标注过时
@SuppressWarnings:压制警告(可以在方法上用,也可以在类上用,还可以在属性上用)
发现的问题:
不同的注解只能在不同的位置使用(方法上、字段上、类上)
实例代码:
package annotation; import java.util.ArrayList; import java.util.List; public class AnnoDemo { public static void main(String[] args) { @SuppressWarnings(value = { "rawtypes", "unused" })//rawtypes压制泛型的警告、unused压制未使用的警告,all压制所有警告 List list = new ArrayList(); show(); } @Deprecated public static void show() {} public static void show(String xx) {} @Override public String toString() { return "AnnoDemo []"; } }
3.自定义注解(了解)
思路
- 怎样去编写一个自定义的注解
- 怎样去使用注解
- 怎样去解析注解—–使用反射知识
编写一个注解
定义注解关键字:@interface
注解的属性
语法:返回值 名称();
注意:如果属性的名字是value,并且注解的属性值有一个那么在使用注解时可以省略value
如定义名为value的字符串类型属性,使用时如下:
//定义如下 public @interface MyAnno { String value(); } //使用如下 public class MyAnnoTest { @MyAnno("xxx") public void show(){} }
- 如定义名为value的字符串数组类型属性,使用时如下:
//定义如下 public @interface MyAnno { String[] value(); } //使用如下 public class MyAnnoTest { @MyAnno({"xxx","bbb"}) public void show(){} }
- 如定义其他名称的对应类型属性,使用时如下:
//定义如下 public @interface MyAnno { //注解的属性 String name(); int age() default 28; } //使用如下 public class MyAnnoTest { @MyAnno(name="aaa",age=10) public void show(){} }
注解属性类型只是以下几种
基本类型
- String
- 枚举类型
- 注解类型
- Class类型
- 以上类型的数组类型
使用注解
在类、方法、字段上面使用@XXX
解析使用了注解的类
- 介入一个概念:元注解:代表修饰注解的注解,作用:限制定义的注解的特性
- @Target代表注解修饰的范围:类上使用,方法上使用,字段上使用
- ElementType.FIELD:字段上可用此注解
- ElementType.METHOD:方法上可以用此注解
- ElementType.TYPE:类/接口上可以使用此注解
- @Retention
- RetentionPolicy.SOURCE: 注解在源码级别可见(默认)
- RetentionPolicy.CLASS:注解在字节码文件级别可见
- RetentionPolicy.RUNTIME:注解在整个运行阶段都可见
注意:
- 要想解析使用了注解的类,那么该注解的Retention必须设置成Runtime
- 关于注解解析的实质:从注解中解析出属性值
- 字节码对象存在于获得注解相关的方法
isAnnotationParsent<Class <?extends Annotation> annotationClass>
:判断该字节码对象身上是否使用该注解了getAnnotation(Class <A> annotationClass)
:获得该字节码对象身上的注解对象
实例代码:
- 定义注解:可以修饰类和方法;注解在整个运行阶段都可见(才能保证通过字节码对象可以取出对应注解);定义注解的属性
package annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnno { //注解的属性 String name(); int age() default 28; }
- 定义使用了注解的类
package annotation; public class MyAnnoTest { @MyAnno(name="aaa",age=10) public void show(String str){ System.out.println("show running......"+str); } }
- 解析使用了注解的类并输出注解的属性内容
package annotation; import java.lang.reflect.Method; public class MyAnnoParser { public static void main(String[] args) throws NoSuchMethodException, SecurityException { //解析show方法上面的@MyAnno //直接目的是:获得show方法上MyAnno中的参数 //获得show方法的字节码对象 Class clazz = MyAnnoTest.class; Method method = clazz.getMethod("show",String.class); //获得show方法上的@MyAnno MyAnno annotation = method.getAnnotation(MyAnno.class); //获得@MyAnno上的属性值 System.out.println(annotation.name()); System.out.println(annotation.age()); } }
4.注解案例:模拟单元测试
编写注解MyTest用于修饰方法,且注解在整个阶段都可见。此注解的作用仅做修饰,用于被注解修饰的方法需要执行某些内容。
package case1; 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 { //不需要属性 }
编写使用该注解的类。
package case1; import org.junit.Test; public class TestDemo { //程序员开发中测试用的代码 @Test public void test1() { System.out.println("test1 running..."); } @MyTest public void test2() { System.out.println("test2 running..."); } @MyTest public void test3() { System.out.println("test3 running..."); } }
编写执行对应解析内容的类,先通过字节码对象找到对应的类,获取所有的方法,再通过方法筛选被@MyTest注解修饰的方法,最后执行对应内容(本demo执行的是对应方法体)
package case1; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class MyTestParster { public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { //获得TestDemo Class clazz = TestDemo.class; //获得所有的方法 Method[] methods = clazz.getMethods(); if(methods!=null) { //获得注解使用了@MyTest的方法 for(Method method : methods) { //判断该方法是否使用了@MyTest注解 boolean annotationPresent = method.isAnnotationPresent(MyTest.class); if(annotationPresent) { //该方法使用MyTest注解 method.invoke(clazz.newInstance(), null); } } } } }
输出结果
test3 running... test2 running...
三.动态代理
1.什么是代理(中介)
- 目标对象/被代理对象(房主:真正的租房的方法)
- 代理对象(黑中介:有租房子的方法(调用房主的租房的方法))
- 执行代理对象方法的对象(租房的人 )
- 流程:我们要租房——>中介(租房的方法)——>房主(租房的方法)
- 抽象:调用对象——>代理对象——>目标对象
2.动态代理
动态代理:不用手动编写一个代理对象,不需要一一编写与目标对象相同的方法,这个过程,在运行时 的内存中动态生成代理对象。(字节码对象级别的代理对象 )
动态代理的API
在jdk的API中存在一个Proxy中存在一个生成动态代理的的方法newProxyInstance
返回值类型 | 方法 |
---|---|
static Object | newProxyInstance(ClassLoader loader,Class[]interfaces,InvoationHandlerh) |
* 返回值:Object就是代理对象
* 参数1:loader代表与目标对象相同的类加载器(目标对象.getClass().getClassLoader())
* 参数2:interfaces代表与目标对象实现的所有的接口字节码对象数组
* 参数3:h具体的代理的操作,InvacationHandler接口
注意:JDK的Proxy方式实现的动态代理,目标对象必须有接口,没有接口不能实现jdk版动态代理
代理对象与接口的关系
实例代码(利用动态代理的内容为目标对象进行动态代理)
- 编写接口内容
package proxy; public interface TargetInterface { public void method1(); public String method2(); public int method3(int x); }
- 编写目标实现类
package proxy; public class Target implements TargetInterface{ @Override public void method1() { System.out.println("method1 running..."); } @Override public String method2() { System.out.println("method2 running..."); return "method2"; } @Override public int method3(int x) { return x; } }
- 编写第一版代理内容,创建代理对象,利用匿名内部类重写invoke方法实现对目标对象的目标方法进行相关处理,并利用代理对象调用对应方法进行结果查看。(请参见注释)
package proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.junit.Test; public class ProxyTest { @Test public void test1() { //获得动态的代理对象(在运行时,内容中动态的为Target创建一个虚拟的代理对象) //objProxy是代理对象 根据参数确定到底是谁的代理对象 /* * 参数一ClassLoader loader:与目标对象相同的类加载器 * 参数二Class<?>[] interfaces:接口的字节码对象数组 * 参数三InvocationHandler h:具体的代理的操作,InvocationHandler接口 */ TargetInterface objProxy = (TargetInterface)Proxy.newProxyInstance(Target.class.getClassLoader(), new Class[]{TargetInterface.class}, new InvocationHandler() { //invoke()代表的是执行代理对象的方法 @Override /* * 参数二Method method:代表目标对象的方法字节码对象 * 参数三Object[] args:代表目标对象相应方法的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("目标方法前的逻辑"); //执行目标对象的方法 Object invoke = method.invoke(new Target(), args); System.out.println("目标方法后的逻辑"); return invoke; } }); objProxy.method1(); String method2 = objProxy.method2(); System.out.println(method2); } }
执行结果:
目标方法前的逻辑 method1 running... 目标方法后的逻辑 目标方法前的逻辑 method2 running... 目标方法后的逻辑 method2
- 编写第二版内容,对相关方法内容进行详细阐述。(请参见注释)
package proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyTest2 { public static void main(String[] args) { final Target target = new Target(); //动态创建代理对象 TargetInterface proxy = (TargetInterface)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , new InvocationHandler() { @Override //invoke方法被执行几次?看代理对象调用方法几次 //代理对象调用接口相应的方法,都是调用invoke() /* * 参数一Object proxy:指的就是代理对象 * 参数二Method method:指的是目标方法的字节码对象 * 参数三Object[] args:指的是调用目标方法时的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //执行相关处理操作... //利用反射的知识点 Object invoke = method.invoke(target, args);//目标对象的相应方法 //执行相关处理操作... return invoke;//return返回的值给代理对象 } }); proxy.method1();//调用invoke,对应参数二method就是目标对象的method1,args是null,返回值是null String method2 = proxy.method2();//调用invoke,对应参数二method就是目标对象的method2,args是null,返回值是”method2“ int method3 = proxy.method3(100);//调用invoke,对应参数二method就是目标对象的method3,args是Object[]{100},返回值是100 System.out.println(method2); System.out.println(method3); } }
执行结果:
method1 running... method2 running... method2 100
案例DEMO:使用动态代理完成全局编码
- 实例代码
package filter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; 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.HttpServletRequestWrapper; public class EncodingFilter2 implements Filter{ @Override public void destroy() {} @Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)arg0; //使用动态代理完成全局编码 HttpServletRequest enhanceRequest = (HttpServletRequest)Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //对getParameter方法进行增强 String name = method.getName();//这是目标对象的方法名称 if("getParameter".equals(name)) { String invoke = (String)method.invoke(request,args); //转码 invoke = new String(invoke.getBytes("ISO8859-1"),"UTF-8"); return invoke; } return method.invoke(request, args); } }); arg2.doFilter(enhanceRequest, arg1); } @Override public void init(FilterConfig arg0) throws ServletException {} }
总结
- 具体应用时,装饰者模式一般用于内容增强
- 动态代理一般用于拦截