JavaJava 杂谈Java学习笔记

动态代理&类加载器&注解

2017-06-10  本文已影响60人  明天你好向前奔跑

1. 动态代理【掌握】

1.1 简介

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

优点:

(1).职责清晰 
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,
通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的作用。

(3).高扩展性
结构
一个是真正的你要访问的对象(目标类),另一个是代理对象,
真正对象与代理对象实现同一个接口,由代理控制目标对象的一切方法。

1.2 创建动态代理的步骤

例:

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. 简介

2.2. 常见的注解

2.3. 自定义注解

2.4. 元注解

2.5. 注解解析

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.类加载器【了解】

类加载器.png
上一篇 下一篇

猜你喜欢

热点阅读