Java动态代理与AOP概念

AOP

标签(空格分隔): Java


1. 分析代理类的作用与原理及AOP概念

1.生活中的代理:
  一个人从武汉的代理商手中买联想电脑和直接跑到北京联想总部买电脑 最终的主体业务目标基本上一样吧,都解决了核心问题。但是通过代理的方式购买比直接到总部购买方便,这是使用代理的好处,缺点也是很明显,从代理买电脑的价钱比直接从总部买的价钱要贵。
2.程序中的代理:
  要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能 。例如:异常处理,日志,计算方法的运行时间,事务管理等等,你准备如何做?
  编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
  如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类,还是代理类,这样以后很容易切换(用接口来引用)。譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易
这里写图片描述

2. AOP

1、系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:

         安全   事务    日志
Student Service ———-|———–|———-|———-
CourseService ———-|———–|———-|———-
MiscService  ————|———–|———-|———-

  安全,失误,日志等功能要贯穿到好多个模块中,所以他们就是交叉业务。
2、用具体的程序代码描述交叉业务:

method1           method2           method3
{                 {                 {
------------------------------------------------切面
 ------      --------         ------
------------------------------------------------切面
}                  }                 }

3、交叉业务的编程问题即为面向方面的编程(Aspect oiented program,简称AOP),AOP的目标就是要使交叉业务模块化
  可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。如下所示:

--------------------------------------------------切面
func1                  func2                  func3
{                      {                      {
  ---                        ---                    ---
}                      }                      }
-------------------------------------------------切面

4、使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

3. 动态代理技术:

  1. 要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情.

  2. JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类

  3. JVM生成的动态类必须实现一个或多个接口,所以JVM生成的动态类只能用作具有相同接口的目标类的代理

  4. 如果一个目标类自身没有实现接口,如何让JVM动态生成的代理类与目标类有相同的方法列表呢?
      生成的代理类的方法声明要不要和目标类的方法一样?要。但目标类自身并没有实现接口,那通过什么样的方式告诉JVM生成的代理类与目标类有相同的方法列表,JVM干不了这件事情,因为没接口。CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

  5. 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果 ,还可以在代理方法中的如下四个位置加上系统功能代码 :
      (1)在调用目标方法之前
      (2)在调用目标方法之后
      (3)在调用目标方法前后
      (4)在处理目标方法异常的catch块中

4. JVM动态生成的类

(一)创建动态类及查看其方法列表信息

  1、要求:
  (1)创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
  (2)编码列出动态类中的所有构造方法和参数类型
  (3)编码列出动态类中的所有方法和参数类型
  2、示例代码:

package com.qianming;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;

public class ProxyTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
        //System.out.println(clazzProxy1.getName());//com.sun.proxy.$Proxy0
        Constructor[] constructors = clazzProxy1.getConstructors();
        // 拼凑成方法(参数)的形式
        System.out.println("----constructors----");
        for(Constructor constructor : constructors){
            String name = constructor.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            sBuilder.append('(');
            Class[] clazzParams = constructor.getParameterTypes();
            for(Class clazzParam : clazzParams){
                sBuilder.append(clazzParam.getName() + ",");
            }
            if(clazzParams != null && clazzParams.length != 0){
                sBuilder.deleteCharAt(sBuilder.length()-1);
            }
            sBuilder.append(')');
            System.out.println(sBuilder);
        }
        System.out.println("----methods----");
        Method[] methods = clazzProxy1.getMethods();
        for(Method method : methods){
            String name = method.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            sBuilder.append('(');
            Class[] clazzParams = method.getParameterTypes();
            for(Class clazzParam : clazzParams){
                sBuilder.append(clazzParam.getName() + ",");
            }
            if(clazzParams != null && clazzParams.length != 0){
                sBuilder.deleteCharAt(sBuilder.length()-1);
            }
            sBuilder.append(')');
            System.out.println(sBuilder);
        }
    }

}
打印结果:
----constructors----
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
----methods----
add(java.lang.Object)
equals(java.lang.Object)
toString()
hashCode()
clear()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait(long)
wait()
wait(long,int)
getClass()
notify()
notifyAll()

(二)创建动态类的实例对象及调用其方法

  1、创建动态类的实例对象有三种方式:
  (1)首先通过Proxy类的getProxyClass(ClassLoader loader, Class

package com.qianming;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyTest {

    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
        Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
        //System.out.println(clazzProxy1.getName());//com.sun.proxy.$Proxy0
        Constructor[] constructors = clazzProxy1.getConstructors();
        // 拼凑成方法(参数)的形式
        System.out.println("----constructors----");
        for(Constructor constructor : constructors){
            String name = constructor.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            sBuilder.append('(');
            Class[] clazzParams = constructor.getParameterTypes();
            for(Class clazzParam : clazzParams){
                sBuilder.append(clazzParam.getName() + ",");
            }
            if(clazzParams != null && clazzParams.length != 0){
                sBuilder.deleteCharAt(sBuilder.length()-1);
            }
            sBuilder.append(')');
            System.out.println(sBuilder);
        }
        System.out.println("----methods----");
        Method[] methods = clazzProxy1.getMethods();
        for(Method method : methods){
            String name = method.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            sBuilder.append('(');
            Class[] clazzParams = method.getParameterTypes();
            for(Class clazzParam : clazzParams){
                sBuilder.append(clazzParam.getName() + ",");
            }
            if(clazzParams != null && clazzParams.length != 0){
                sBuilder.deleteCharAt(sBuilder.length()-1);
            }
            sBuilder.append(')');
            System.out.println(sBuilder);
        }
        System.out.println("----create new Instance----");
        //生成实例
        Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
        class MyInvocationHander1 implements InvocationHandler{

            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                // TODO Auto-generated method stub
                return null;
            }

        }
        //实现了Collection接口,所以可以用Collection引用
        Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHander1());
        System.out.println(proxy1);//返回null
        proxy1.clear();
        //proxy1.size();//报空指针异常

        //使用匿名类的方式实现
        Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler(){

            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                // TODO Auto-generated method stub
                return null;
            }

        });
        //直接调用Proxy的newProxyInstance方法
        Collection proxy3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), 
                new Class[]{Collection.class}, 
                new InvocationHandler() {
                    ArrayList target = new ArrayList();//这样结果就是3,加入了同一个target
                    @Override
                    //每使用一个add方法,就调用一次invoke方法
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        // TODO Auto-generated method stub
                        //ArrayList target = new ArrayList();//放在内部打印为0
                        long beginTime = System.currentTimeMillis();
                        Object retVal = method.invoke(target, args);
                        long endTime = System.currentTimeMillis();
                        System.out.println(method.getName() + " run time is " + (endTime - beginTime));
                        return retVal;
                    }
                });
        proxy3.add("123");
        proxy3.add("lhm");
        proxy3.add("hsp");
        System.out.println(proxy3.size());

    }

}

(三)总结思考

  问题:让JVM创建动态类及其实例对象,需要给它提供哪些信息?
  解答:主要包括三个方面的信息:
  (1)生成的类中有哪些方法,通过让其实现哪些 接口 的方式进行告知;
  (2)产生的类字节码必须有个一个关联的类加载器对象
  (3)生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。

5. 动态生成的类的内部代码分析

  在上面“创建动态类的实例对象”的代码中,动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。
  1、问题:构造方法接受一个InvocationHandler对象,接收这个对象要干什么用呢?该方法内部的代码是怎样的呢?
  (1)构造方法接收一个参数,为了记住这个参数,以后运用它。
  (2)内部代码:

 $Proxy0 implements Collection{
     InvocationHandler handler;
     public $Proxy0(InvocationHandler handler){
         this.handler = handler;
     }
 }

  2、问题:实现Collection接口的动态类中的各个方法的代码又是怎样的呢? InvocationHandler接口中定义的invoke方法接收的三个参数又是什么意思?

    //生成的Collection接口中的方法的运行原理
    int size(){
        return handler.invoke(this, this.getClass().getMethod("size"), null);
    }
    void clear(){
        handler.invoke(this, this.getClass().getMethod("clear"), null);
    }
    boolean add(Object obj){
        handler.invoke(this, this.getClass().getMethod("add"), obj);
    }
}

  (2)InvocationHandler接口中定义的invoke方法接收的三个参数意义,如下图说明:

  
  说明:客户端调用了代理对象objProxy,调用了代理对象的add()方法,为该方法传递了字符串参数”abc”。
  3、为什么动态类的实例对象的getClass()方法返回了正确结果呢?
  调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求

6. 动态生成的类成为目标类的代理

  1、动态代理的工作原理图
  此处加入图片的描述
  2、eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象,怎样将目标类作为参数传进去?
  (1)直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。
  (2)为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
  (3)让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
  3、在上面将目标类作为参数传入之后,将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供?
  (1)把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码!
  (2)为bind方法增加一个Advice参数。
  4、将目标类和系统功能作为参数传递给getProxy()方法,实现示例代码如下:
  (1)创建ProxyTest类

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
public class ProxyTest {
    public static void main(String[] args) throws Exception {
        final ArrayList target = new ArrayList();//将目标抽取出来,方法里面的内部类要访问局部变量必须添加final关键字        
        Collection proxy3 = (Collection)getProxy(target,new MyAdvice());//抽取出来的方法
        proxy3.add("zxx");
        proxy3.add("flx");
        proxy3.add("lhm");        
        System.out.println(proxy3.size());
    }
    private static Object getProxy(final Object target,final Advice advice) { /*做成通用的方法,返回Object*/
        Object proxy3 = Proxy.newProxyInstance(
                /*Collection.class.getClassLoader(),        //第一个参数*/
                target.getClass().getClassLoader(),            //    代理类的类加载器与目标类的类加载器相同,与目标类有关。

                /*new Class[]{Collection.class},        //第二个参数*/
                target.getClass().getInterfaces(),    //与target实现相同的接口,代理类要实现的接口也是目标类实现的接口,与目标类有关

                new InvocationHandler(){        //第三个参数,
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        /*
                        long beginTime = System.currentTimeMillis();    //将系统功能抽取为一个对象
                        Object retVal = method.invoke(target, args);
                        long endTime = System.currentTimeMillis();    //将系统功能抽取为一个对象                        
                        System.out.println(method.getName()+"run time"+(endTime-beginTime));
                        return retVal;
                        */
                        advice.beforeMethod(method);
                        Object retVal = method.invoke(target, args);
                        advice.afterMethod(method);    
                        return retVal;
                    }
                }
                );
        return proxy3;
    }
}

(2)创建Advice接口

import java.lang.reflect.Method;
public interface Advice {
    //一般来说,这个建议的接口应该有四个方法,这四个方法可以分别插入:
    /* 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,
     * 还可以在代理方法中的如下四个位置加上系统功能代码:
     *    1.在调用目标方法之前
     *    2.在调用目标方法之后
     *    3.在调用目标方法前后
     *    4.在处理目标方法异常的catch块中 
     * */
    void beforeMethod(Method method);
    void afterMethod(Method method);    
}

  (3)创建Advice接口的子类MyAdvice

import java.lang.reflect.Method;
public class MyAdvice implements Advice {
    long beginTime = 0;
    public void beforeMethod(Method method) {
        // TODO Auto-generated method stub
        System.out.println("到黑马程序员训练营来学习了!");
        beginTime = System.currentTimeMillis();    //将系统功能抽取为一个对象
    }

    public void afterMethod(Method method) {
        // TODO Auto-generated method stub
        System.out.println("从黑马程序员训练营毕业工作了!");
        long endTime = System.currentTimeMillis();    //将系统功能抽取为一个对象    
        System.out.println(method.getName()+" method run of time "+(endTime-beginTime));
        System.out.print(System.lineSeparator() );
    }
}

7. 实现类似spring的可配置的AOP框架

(一)工厂类BeanFactory

   1、工厂类BeanFactory:负责创建目标类或代理类的实例对象,并通过配置文件实现切换。
   2、getBean方法:根据参数字符串返回一个相应的实例对象。如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该类示例对象的getProxy方法返回的对象。
   3、BeanFactory的构造方法:接收代表配置文件的输入流对象的配置文件。
   4、ProxyFactoryBean为BeanFactory提供配置参数信息:
     (1)目标(target)(2)通告(advice)
   5、BeanFactory和ProxyFactoryBean:
   (1)BeanFactory是一个纯粹的bean工程,就是创建bean即相应的对象的工厂。
   (2)ProxyfactoryBean是BeanFactory中的一个特殊的Bean,是创建代理的工厂。

(二)实现类似spring的可配置的AOP框架的思路:

   1、创建BeanFactory类:
   (1)构造方法:接受一个配置文件,通过Properties对象加载InputStream流对象。
   (2)创建getBean(String name)方法:根据类名name,拿到对应的类名className。
   (3)根据类名获取其字节码对象,并创建实例对象bean。
   (4)判断bean是否是特殊的Bean即ProxyFactoryBean。
    ① 如果是,就要创建代理类,并设置目标(target)和通告(advice),分别得到各自的实例对象,并返回代理类实例对象。
    ② 如果不是在返回Bean对象自己。
   2、创建ProxyFactoryBean类,定义target和advice;定义getProxy()方法,用于创建代理类对象。
   3、创建配置文件config.properties,对配置文件进行配置,配置内容如下

 xxx=java.util.ArrayList 
 #xxx=cn.itheima.day3.aopframework.ProxyFactoryBean 
 xxx.advice=cn.itheima.day3.MyAdvice 
 xxx.target=java.util.ArrayList

    注: #表示注释当前行。
 
   4、作一个测试类:AopFrameworkTest进行测试。

(三)完整代码示例

   1、创建BeanFactory类:

 package cn.itheima.day3.aopframework;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import cn.itheima.day3.Advice;
public class BeanFactory {
    Properties props = new Properties();
    public BeanFactory(InputStream ips){
        try {
            props.load(ips);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public Object getBean(String name){
        String className = props.getProperty(name);//根据类名name,拿到对应的类名。
        Object bean = null;
        try {
            Class clazz = Class.forName(className);
            bean = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if(bean instanceof ProxyFactoryBean){
            Object proxy = null;
            ProxyFactoryBean ProxyFactoryBean = (ProxyFactoryBean)bean;
            try {
                Advice advice = (Advice)Class.forName(props.getProperty(name+".advice")).newInstance();
                Object target = Class.forName(props.getProperty(name+".target")).newInstance();
                ProxyFactoryBean.setAdvice(advice);
                ProxyFactoryBean.setTarget(target);
                proxy = ProxyFactoryBean.getProxy();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return proxy;
        }
        return bean;
    }
}
 ```
  2、创建ProxyFactoryBean类: 
```Java
package cn.itheima.day3.aopframework;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import cn.itheima.day3.Advice;
public class ProxyFactoryBean {
    private Advice advice;
    private Object target;
    public Advice getAdvice() {
        return advice;
    }
    public void setAdvice(Advice advice) {
        this.advice = advice;
    }
    public Object getTarget() {
        return target;
    }
    public void setTarget(Object target) {
        this.target = target;
    }
    public Object getProxy() {
        Object proxy3 = Proxy.newProxyInstance(                
                target.getClass().getClassLoader(),    //    代理类的类加载器与目标类的类加载器相同,与目标类有关。
                target.getClass().getInterfaces(),        //与target实现相同的接口,代理类要实现的接口也是目标类实现的接口,与目标类有关

                new InvocationHandler(){    //第三个参数
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        advice.beforeMethod(method);
                        Object retVal = method.invoke(target, args);
                        advice.afterMethod(method);
                        return retVal;
                    }
                }
                );
        return proxy3;
    }
}




<div class="se-preview-section-delimiter"></div>

  3、创建配置文件config.properties。

xxx=java.util.ArrayList




<div class="se-preview-section-delimiter"></div>

#xxx=cn.itheima.day3.aopframework.ProxyFactoryBean
xxx.advice=cn.itheima.day3.MyAdvice
xxx.target=java.util.ArrayList




<div class="se-preview-section-delimiter"></div>

  4、创建AopFrameworkTest测试类,进行测试:

package cn.itheima.day3.aopframework;

import java.io.InputStream;
public class AopFrameworkTest {
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
        Object bean = new BeanFactory(ips).getBean("xxx");
        System.out.println(bean.getClass().getName());
    }
}




<div class="se-preview-section-delimiter"></div>

5、设计到的接口Advice及其子类MyAdvice

  (1)Advice接口:

1 package cn.itheima.day3;
2 import java.lang.reflect.Method;
3 public interface Advice {    
4     void beforeMethod(Method method);
5     void afterMethod(Method method);
6 }




<div class="se-preview-section-delimiter"></div>

  (2)MyAdvice类:

package cn.itheima.day3;
import java.lang.reflect.Method;
public class MyAdvice implements Advice {
    long beginTime = 0;
    public void beforeMethod(Method method) {
        // TODO Auto-generated method stub
        System.out.println("到黑马程序员训练营来学习了!");
        beginTime = System.currentTimeMillis();    //将系统功能抽取为一个对象
    }

    public void afterMethod(Method method) {
        // TODO Auto-generated method stub
        System.out.println("从黑马程序员训练营毕业工作了!");
        long endTime = System.currentTimeMillis();    //将系统功能抽取为一个对象    
        System.out.println(method.getName()+" method run of time "+(endTime-beginTime));
        System.out.print(System.lineSeparator() );
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值