1. 动态代理【掌握】
1.1 简介
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
优点:
(1).职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,
通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的作用。
(3).高扩展性
结构
一个是真正的你要访问的对象(目标类),另一个是代理对象,
真正对象与代理对象实现同一个接口,由代理控制目标对象的一切方法。
1.2 创建动态代理的步骤
-
目标对象必须要实现一个接口,代理对象实现这个接口从而能够实现这些方法。
-
创建动态代理对象,传入三个参数。
1. 参数1:加载目标类的类加载器。web工程发布后由类加载器web-inf的classes中,从而能够找到该目标类 。 2. 参数2:目标对象实现的接口。 3. 参数3:代理类对目标对象方法的控制器。new InvocationHandler(proxy,method,args);
-
new InvocationHandler(Object proxy,Method method,Parameter args)对目标对象方法的控制。在这里可以进行方法增强等操作。
1. 参数1: proxy,当前代理对象的引用。一般不使用它 2. 参数2: method,调用方法时该方法的字节码对象。 3. 参数3: args,调用方法时传递进来的实际参数
例:
1. 要实现的共同接口:
public interface TargetInterface {
public abstract void method1();
public abstract String method2();
public abstract int method3(int num);
}
2. 被代理的目标类
//代理对象和目标对象必须实现同一个接口,即动态代理对象必须成为共同接口的实现类对象,这样就具备了共同的方法
//在jdk中,动态代理和目标对象必须共同实现一个接口,才能实现动态代理
public class Target implements TargetInterface {
public void method1() {
System.out.println("method1");
}
public String method2() {
System.out.println("method2");
return "method2";
}
public int method3(int num) {
System.out.println("method3");
return num;
}
}
3. 代理类并测试
public class ProxyTest {
public static void main(String[] args) {
final Target target = new Target();
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(//
target.getClass().getClassLoader(), // 要代理的目标对象的类加载器
target.getClass().getInterfaces(), // 和目标对象实现共同的接口
new InvocationHandler() { // 调用目标对象的某个方法
// 参数1:proxy : 动态代理对象
// 参数2:method : 要调用的目标对象的方法的字节码对象
// 参数3:args : 调用目标对象的方法时传递的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 动态代理调用目标函数的方法
Object invoke = method.invoke(target, args);
return invoke;// 返回调用目标函数方法后的返回值
}
});
// 使用动态代理对象调用方法
proxy.method1();// 使用目标函数对象,需要将动态代理的类型从Object强转为接口对象的实现子类(多态)
String method2 = proxy.method2();
int method3 = proxy.method3(100);
System.out.println(method2);
System.out.println(method3);
}
}
1.3 使用动态代理解决全站编码问题
1. 分析
1. 首先创建一个表单页面和一个servlet小程序用于测试。
2. 要对全站编码进行控制,需要经过Filter过滤器进行控制,过滤路径url-pattern设为/*即对所有请求进行拦截。
3.需求:我们解决的是servlet中获取参数的getParameter()方法时及servlet写回数据到客户端时的乱码问题。
4. 使用动态代理解决,首先找到需要被代理的对象是HttpServletRequest的实现类对象.
1. 创建一个动态代理。
2. 获取目标对象的类加载器,与目标对象实现同一个接口HttpServletRequest。
3. 在new InvocationHandler()方法控制器中找到getParameter()方法进行增强,在这里面解决乱码逻辑。
4. 对于其他无需改变的方法,不进行改变,按原来的逻辑返回。
5. 放行(这里的参数request要改为动态代理对象proxy了,这样servlet调用getParameter方法才是增强后的)。
2. 代码实现
这里省略jsp页面和servlet程序的代码,就是一个简单的jsp表单提交和回写数据
Filter的动态代理代码
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
// 需求: 使用动态代理解决全站编码问题
// 1. 强制转换
final HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
response.setContentType("text/html;charset=UTF-8");// 设置服务器写回客户端的编码格式
// 2. 业务逻辑 : 使用动态代理解决全站编码问题
// 2.1 创建动态代理对象,返回的是和目标对象实现了同一接口的动态代理对象
HttpServletRequest proxy = (HttpServletRequest) Proxy.newProxyInstance( //
request.getClass().getClassLoader(), // 参数1:加载目标对象的类加载器
request.getClass().getInterfaces(), // 参数2:与目标对象实现相同的接口
new InvocationHandler() {// 参数3:目标对象的方法控制器,即如何代理?
// 参数1:proxy:就是当前的动态代理对象
// 参数2:method:代理对象调用目标对象的某个方法的字节码对象
// 参数3:args:调用某个方法时传递的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 判断找出getParameter()方法进行方法曾倩
if ("getParameter".equals(method.getName())) {
// 解决编码集的代码
// 获取表单提交的方式
String requestMethod = request.getMethod();
if ("GET".equalsIgnoreCase(requestMethod)) { // 如果是get方式提交数据
// 获取提交的数据
String parameter = (String) method.invoke(request, args);
// 对获取的数据重新编解码
parameter = new String(parameter.getBytes("ISO-8859-1"), "UTF-8");
return parameter;
} else if ("POST".equalsIgnoreCase(requestMethod)) {
request.setCharacterEncoding("UTF-8");
}
}
// 对于不需增强的方法,按原来的逻辑执行后返回,不做任何修改
return method.invoke(request, args);
}
});
// 3. 放行(将与request实现了同一接口的动态代理作为参数传递放行)
chain.doFilter(proxy, response);
}
2. 注解【了解】
2.1. 简介
- 定义 : Annotation. 也叫元数据.一种代码级别的说明. 它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次. 它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释
- 比喻 : 注释是给成员看的.注解是给java编译器看的
- 作用
- 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查,例如Override
- 代码分析:通过代码里标识的元数据对代码进行分析,可以用来替代xml.例如3.0版本的Web工程就不需要使用web.xml对Servlet进行配置
- 编写文档:通过代码里标识的元数据生成文档, 例如JDK文档
2.2. 常见的注解
- @SuppressWarnings : 忽略警告
- @Override : 编译器在检查这段代码时,检查方法名和父类是否一致
- @Deprecated : 方法是否过期
2.3. 自定义注解
- 注解和类、接口是一样级别的,只不过它的作用更多的是对代码进行说明
- <font color='red'>实现步骤
-
创建一个接口,并在interface之前添加一个@
// 自定义注解 public @interface MyAnno { } // 使用注解 public class Test1 { @MyAnno public static void main(String[] args) { String aString; } }
-
添加属性
// 自定义注解 public @interface MyAnno { // 声明一个String类型的属性 String name(); // 声明一个int类型的属性 int age(); } // 使用注解 public class Test1 { @MyAnno(name = "zhangsan", age = 18) public static void main(String[] args) { String aString; } }
-
- 常用的注解属性类型
- 基本数据类型
- String类型
- 以上类型的数组
- 如果属性值只有一个,并且恰好为value,那么使用的时候,可以不写属性名
-
示例
// 自定义注解 public @interface MyAnno { // 声明一个String类型的属性 String value(); } // 使用注解 public class Test1 { @MyAnno("zhangsan") public static void main(String[] args) { String aString; } }
-
2.4. 元注解
-
定义 : 说明注解的注解
-
@Retention()
-
作用 : 指定注解的保留阶段(生命周期)
-
常量值
- RetentionPolicy.SOURCE : 只存在于源码中
- RetentionPolicy.CLASS : 默认值.保留到字节码阶段.
- RetentionPolicy.RUNTIME : 保留到运行阶段
源码阶段--->>字节码阶段--->>运行阶段
-
-
@Target()
- 作用 : 指定注解的使用位置
- 常量值
- ElementType.CONSTRUCTOR : 用于描述构造器
- ElementType.FIELD : 用于描述字段
- ElementType.LOCAL_VARIABLE : 用于描述局部变量
- ElementType.METHOD : 用于描述方法
- ElementType.PACKAGE : 用于描述包
- ElementType.PARAMETER : 用于描述参数
- ElementType.TYPE : 用于描述类、接口(包括注解类型) 或enum声明
2.5. 注解解析
-
API
- 判断一个类是否使用了一个注解 : 类名.class.isAnnotationPresent(注解类名.class)
- 解析注解 : 类名.class.getAnnotation(注解类名.class)
-
示例代码
// 判断某一个类是否使用了一个注解 // 格式: 类名.class.isAnnotationPresent(注解类名.class) // 如下所示,判断Person类是否使用了MyAnno注解 boolean isPresent = Person.class.isAnnotationPresent(MyAnno.class); if (isPresent) { // 解析注解 // 格式 : 类名.class.getAnnotation(注解类名.class) // 如下所示: 解析Person使用的MyAnno注解 MyAnno anno = Person.class.getAnnotation(MyAnno.class); // 获取注解具体字段的值 String value = anno.address(); System.out.println(value); }
2.6. 案例的实现
自定义注解必须定义@Retention和@Target的元注解,否则注解到不了运行阶段
// 1.自定义注解类
@Target(value = { ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MTest {
}
// 2.使用自定义注解的类
public class DAOTest {
public void aa() {
System.out.println("aa...");
}
// 在方法bb上使用自定义注解.模拟@Test注解
@MTest
public void bb() {
System.out.println("bb...");
}
}
// 3.模拟Junit的运行
public class Test {
// 测试类, 模拟@Test注解的运行
// @Test注解其实是插件+反射执行实现的
// 我们只能模拟反射执行的过程
public static void main(String[] args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
// 获取类的字节码
Class clazz = DAOTest.class;
// 获取所有本类自己声明的方法
Method[] methods = clazz.getDeclaredMethods();
// 遍历方法
for (Method method : methods) {
// 判断方法是否使用了MTest注解
if (method.isAnnotationPresent(MTest.class)) {
// 执行方法
method.invoke(clazz.newInstance());
}
}
}
}
3.类加载器【了解】
- 定义 :
- Java类的加载是由虚拟机来完成的
- 虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制
- JVM中用来完成上述功能的具体实现就是类加载器.
- 类加载器读取.class字节码文件将其转换成java.lang.Class类的一个实例.每个实例用来表示一个java类.通过该实例的newInstance()方法可以创建出一个该类的对象.
- 分类
-
Bootstrap ClassLoader : 将jre\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分
-
Extension ClassLoader : 加载jre\lib\ext目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器.
-
App ClassLoader : 加载我们编写的java类,以及第三方的jar
-
Custom ClassLoader : 自定义的类加载器,以满足特殊的需求,如tomcat就根据J2EE规范自行实现ClassLoader
-
测试代码
public static void main(String[] args) { // 当前当前类的类加载器 : AppClassLoader System.out.println(MyLoader.class.getClassLoader().getClass().getName()); // 获取当前类的类加载器/的父类 : ExtClassLoader System.out.println(MyLoader.class.getClassLoader().getParent().getClass().getName()); // 获取当前类的类加载器/的父类/的父类 : Bootstrap ClassLoader System.out.println(MyLoader.class.getClassLoader().getParent().getParent()); // List在rt.jar中,由Bootstrap ClassLoader进行加载 System.out.println(List.class.getClassLoader().getClass().getName()); }
-
- 双亲委托机制
- 定义 : 是一种组织类加载器之间关系的一种规范
- 作用 : 规定类如何被加载
- 工作原理 :
- 如果一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成
- 这样层层递进,最终所有的加载请求都被传到最顶层的启动类加载器中,只有当父类加载器无法完成这个加载请求(它的搜索范围内没有找到所需的类)时,才会交给子类加载器去尝试加载.
- 优点 :
- Java类随着它的类加载器一起具备了带有优先级的层次关系.这是十分必要的
- 比如java.langObject,它存放在\jre\lib\rt.jar中,它是所有java类的父类
- 因此无论哪个类加载都要加载这个类,最终所有的加载请求都汇总到顶层的启动类加载器中,这样Object类会由启动类加载器来加载,所以加载的都是同一个类
- 如果不使用双亲委派机制,由各个类加载器自行去加载的话,系统中就会出现不止一个Object类,应用程序就会全乱了
网友评论