黑马程序员_Java高新技术(3)动态代理和AOP技术

------- android培训java培训、期待与您交流! ----------

@@49分析代理的作用与原理及AOP概念

程序中的代理:
  为已存在多个具有相同接口的目标类的各个方法增加一些系统功能,
  例如,异常处理,日志,事物管理,计算方法的运行时间,等等。
-->编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法。
    并在调用方法时加上系统功能的代码。
    如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,
    在配置文件中配置是使用目标类还是代理类,这样以后很容易切换,
    例如,想要日志功能时就配置代理类,否则配置目标类。这样增加系统功能很容易,
    以后运行一段时间后,又想去掉系统功能也很容易。
交叉业务的编程问题即为 面向方面的编程。
AOP=Aspect oriented program
AOP的目标就是要使交叉业务模块化。

@@动态代理技术
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以JVM生成的动态类只能用作具有相同接口的目标类的代理。

//如果目标类没有实现什么接口,那么可以用下面的办法。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果后,还可以在代理方法中的如下四个位置加上系统功能的代码:
*1,在调用目标方法之前
*2,在调用目标方法之后
*3,在调用目标方法前后
*4,在处理目标方法异常的catch块中。

@@50创建代理类的class对象
    并查看其方法列表信息

分析JVM动态生成的类

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) {    
        Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
        
        System.out.println(clazzProxy1.getName());    //类名为  $Proxy0
        System.out.println("--------------- 构造方法列表------------");
        /* 想要打印该类的构造方法及参数列表!        
           $Proxy0(InvocationHandler h)
        */        
        Constructor[] constructors = clazzProxy1.getConstructors();
        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()).append(',');
            }
            if(clazzParams != null && clazzParams.length != 0)    /*如果参数列表不为空
                sBuilder.deleteCharAt(sBuilder.length()-1);      去掉最后一个多余的逗号*/
            sBuilder.append(')');
            
            System.out.println(sBuilder);
        }
        
        System.out.println("---------------普通方法列表------------");                        
            //得到代理类的函数的参数列表
        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()).append(',');
            }
            if(clazzParams != null && clazzParams.length != 0)
            sBuilder.deleteCharAt(sBuilder.length()-1);        //去掉最后一个多余的逗号
            sBuilder.append(')');
            
            System.out.println(sBuilder);
            }    
        }


    }
运行结果
$Proxy0
--------------- 构造方法列表------------
$Proxy0(java.lang.reflect.InvocationHandler)
---------------普通方法列表------------
add(java.lang.Object)
hashCode()
clear()
equals(java.lang.Object)
toString()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
...等等

51
@@创建动态类的实例对象
手段(一)    通过Proxy.getProxyClass获取代理类的class对象,
        再获取代理类的构造方法对象,再newInstance(new MyInvocationHandler1());
        获得代理类对象。
1,获得目标类的代理类的class对象。
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);

2,用反射获得目标类的代理类的构造方法对象。
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);

3,编写一个的实现InvocationHandler接口的类,因为这种获取代理类对象的手段需要向代理类的构造方法中传入参数。
class MyInvocationHandler1 implements InvocationHandler{
        //没有指定目标类对象
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {            
            return null;    //返回值为null
            }            
        }

4,用代理类的构造方法对象的newInstance方法,创建动态类的实例对象,并将实现InvocationHandler接口的实例对象传进去。
Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());

5,打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示其他有返回值的方法报告了异常。
    System.out.println(proxy1);    //proxy1.toString()方法返回null
    proxy1.clear();        //调用成功
    proxy1.size();        //size返回值为int型,而invoke方法返回值为null,所以报告了异常
    proxy1.add(4);        //出现异常

手段(二)    用Proxy.newInstance方法直接一步就创建出代理对象。
    
    Collection proxy2 = (Collection)Proxy.newProxyInstance(
            Collection.class.getClassLoader(),        //第一个参数为目标类加载器
            new Class[]{Collection.class},            //第二个参数为目标类实现的接口的class对象数组
            new InvocationHandler(){            //第三个参数为代理类的调用处理类对象
                ArrayList target = new ArrayList();    //指定目标类对象
                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() + "  running time = " +(endTime - beginTime));
                    return retVal;
                }                    
            });
    proxy2.add("123");    //所以此处代理类调用 带参数的普通方法 不报异常。
    proxy2.add("456");
    proxy2.add("789");
    System.out.println(proxy2.size());//目标类对象放invoke内部,结果为0;放invoke外面,结果为3.
    proxy2.getClass().getName();//结果 $Proxy0  为什么?
/*
从Object继承下来的方法中,只有hashCode,equals,toString这三个方法交给handler对象实现。
其他的方法,如getClass(),Proxy自己会实现的。
*/

想要JVM创建动态类,需要给他提供那些信息?(即Proxy.newInstance(参数一,参数二,参数三) 的参数信息)

1,(第一个参数为目标类加载器)
    产生的类字节码必须有一个关联的类加载器对象。

2,(第二个参数为目标类实现的接口的class对象数组)
    生成的类中有哪些方法,通过实现哪些接口进行告知。

3,(第三个参数为代理类的调用处理类对象)
    生成的代理类中的方法的代码是怎样的,把代码写在实现了InvocationHandler接口的
    对象的invoke方法中,可以用匿名对象表示这个代理类的调用处理类对象。
    即 new InvocationHandler(){实现了invoke方法的代码}
    

53@@分析代理类的调用处理类对象的运行原理
    
InvocationHandler接口中的invoke方法中的三个参数
Object invoke(Object proxy,
              Method method,
              Object[] args)
              throws Throwable

Client客户端调用proxy.add("abc")方法时,涉及三要素:

Proxy代理类对象,add代理对象的方法,"abc"方法的参数

Class Proxy${
    add(Object object){
    return handler.invoke(Object proxy,Method method,Object[] args);
    }
}

生成的Collection接口中的方法的运行原理
int size()
{
    return handler.invoke(this,this.getClass().getMethod("size"),null);
}
void clear()
{
    handler.invoke(this,this.getClass().getMethod("clear"),null);
}
54@总结分析动态代理类的设计原理与结构

动态代理的工作原理图?

client-->class $Proxy1            -->InvocationHandler        -->target
    {                invoke                test1(){}                    
    $Proxy1(InvocationHandler){}    {                test2(){}    
    test1()                log()//这部分做成框架,提供出去               ...
    {                method.invoke(target)    
    handler.invoke()        }
    }                ...
    test2()
    {
    handler.invoke()
    }
    ...
    }


怎么将目标类传进去?
1,直接在InvocationHandler实现了类中创建目标类的实例对象,可以看运行结果和加入日志代码,但没有实际意义。
2,为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
3,让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。

将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接受目标同时返回代理对象。
让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。

将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以午餐形式提供?
1,把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,
    接受者只要调用这个对象的方法,即等于执行了外界提供的代码!
2,为bind方法增加一个Advice参数。


55@@编写可生成代理和插入通告的通用方法

1,把目标抽成对象。
2,把想实现的系统功能抽取成对象。

(一)import java.lang.reflect.Method;

/*Spring框架中有四中方法:
方法前
方法后
方法前后
异常中*/
public interface Advice {    
    void afterMethod(Method method);
    void beforeMethod(Method method);
}

(二)import java.lang.reflect.Method;

public class MyAdvice implements Advice {    
    long beginTime = 0;
    @Override
    public void afterMethod(Method method) {
        long endTime = System.currentTimeMillis();
        System.out.println(method.getName() + "  running time = " +(endTime - beginTime));
        System.out.println("从itcast毕业上班了");
    }
    @Override
    public void beforeMethod(Method method) {
        System.out.println("到itcast来学习了");
        beginTime = System.currentTimeMillis();
    }
}

(三)用Proxy.newInstance方法直接一步就创建出代理对象
    public static void main(String[] args){    
        final ArrayList target = new ArrayList();
        Collection proxy3 = (Collection) getProxy(target,new MyAdvice());
        proxy3.add("123");
        proxy3.add("456");
        proxy3.add("789");
        System.out.println(proxy3.size());
                
        System.out.println(proxy3.getClass().getName());
        System.out.println(proxy3.getClass().toString());        
    }

    private static Object getProxy(final Object target,final Advice advice) {
    Object proxy3 =  Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new InvocationHandler(){
            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;
}

56@@实现AOP功能的封装与配置
Aspect Oriented Programming面向切面编程(面向方面编程)
需求:类Spring的AOP框架
1,BeanFactory类负责创建目标类或代理类的实例对象,并通过配置文件实现切换。
   其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件
   中对应的类名不是ProxyFactoryBean,则直接返回目标类的实例对象,
   否则,返回目标类的getProxy方法返回的对象,即代理类。
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);
        Object bean = null;
        
        try {
            Class clazz =Class.forName(className);
            bean = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        if(bean instanceof ProxyFactoryBean){    //如果要创建代理            
            Object proxy =null;
            
            try {
                ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
                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充当封装生成动态代理的工厂,
    需要为工厂类提供目标类对象和通知类对象;

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(),
                new InvocationHandler(){
                    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;    //返回代理类对象
    }
}
3,客户端:
    编写实现Advice接口的类和在配置文件中进行配置
    调用BeanFactory获取对象。
public class AopFrameworkTest {

    public static void main(String[] args) throws Exception{
        InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
        Object bean = new BeanFactory(ips).getBean("xxx");
        System.out.println(bean.getClass().getName());
    }

}

config.properties配置文件

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

------- android培训java培训、期待与您交流! ----------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值