黑马程序员_基础加强(AOP)

---------------------- ASP.Net+Android+IO开发S.Net培训、期待与您交流! ----------------------

一、代理的概念与作用

1)程序中的代理

a)要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事物管理等。



目标类:                              代理类:


class X{                               Xproxy{


  void sayHello(){                       void sayHello(){


syso:Hello;                            startTime


}                                           X. sayHello();


}                                      endTime;}}

b)编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。

代理原理图:


3)如果采用工厂模式和配置文件的方式进行管理 ,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

二、AOP

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

                              安全       事务         日志

StudentService  ------|----------|------------|-------------
CourseService   ------|----------|------------|-------------
MiscService        ------|----------|------------|-------------


2)用具体的程序代码描述交叉业务:

method1         method2          method3

{                      {                       { 
------------------------------------------------------切面
....            ....              ......
------------------------------------------------------切面
}                       }                       }


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


------------------------------------------------------切面
func1         func2            func3

{             {                { 
....            ....              ......
}             }                }

------------------------------------------------------切面


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

安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务

重要原则:不要把供货商暴露给你的客户。

三、动态代理技术

1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!

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

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

4、CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

5、代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:

1)在调用目标方法之前

2)在调用目标方法之后

3)在调用目标方法前后

4)在处理目标方法异常的catch块中

四、分析JVM动态生成的类

1)创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass()方法的各个参数。

2)编码列出动态类中的所有构造方法和参数(详见代码示例)。

3)编码列出动态类中的所有方法和参数签名。

4)创建动态类的实例对象

a)用反射方法获得构造方法。

b)编写一个最简单的InvocationHandle类。

c)调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去。

d)打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他没有返回值的方法报告了异常。

总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?

三个方面:

1、生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;

2、产生的类字节码必须有个一个关联的类加载器对象;

3、生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提

供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就

可以看到这些代码被调用运行了。

用newProxyInstance()方法直接一步就创建出代理对象。

五,猜想分析动态生成的类的内部代码

1)动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。

2)构造方法接受一个InvocationHandler对象,接受了对象有什么用呢?该方法内部代码怎样?

3)实现的Collection接口中的各个方法的代码又是怎样呢?InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思呢?图解如下:


4)分析先前打印动态类的实例对象时,结果为什么会是null呢?调用有基本类型返回值的方法时为什么会出现NullPointException异常呢?

5)分析为什么动态类的实例对象的getClass()方法返回了正确结果呢?


因为调用代理对象从Object类上继承了的hashCode、equals或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。


六、让动态生成的类成为目标类的代理

1、分析动态代理的工作原理图:




2、怎样将目标传进去:

1)直接在InvocationHandler实现类中创建目标类的实例对象,可看运行效果和加入日志代码,但是毫无意义。

2)为InvocationHandler实现类注入目标的实例对象,不能采用匿名内部类的形式了。

3)让匿名内部类的InvocationHandler实现类访问外面的方法中的目标类实例对象的final类型的引用变量。

代码示例:

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 {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception{
		Class classProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		System.out.println(classProxy1.getName()); 
		
		//这个类上有什么构造方法呢?
		Constructor[] constructors = classProxy1.getConstructors();
		for(Constructor constructor : constructors){
			String name = constructor.getName();
			//单线程StringBuider效率高,不考虑安全;Stringbuffer考虑安全,效率低。多线程。
			StringBuilder  sBuilder = new StringBuilder(name);
			sBuilder.append('(');
			Class[] clazzParams = constructor.getParameterTypes();
			for(Class clazzParam : clazzParams){
				sBuilder.append(clazzParam.getName()).append(',');	
			}
			if(clazzParams.length == 0)
				sBuilder.deleteCharAt(sBuilder.length() - 1);
			sBuilder.append(')');
			System.out.println(sBuilder.toString());
			//System.out.println(constructor); 
		}
		// TODO Auto-generated method stub
		//加载Collection的加载类的字节码
		//Class classProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		//System.out.println(classProxy1.getName()); 
		
		//这个类上有什么方法呢?
		Method[] methods = classProxy1.getMethods();
		for(Method method : methods){
			String name = method.getName();
			//单线程StringBuider效率高,不考虑安全;Stringbuffer考虑安全,效率低。多线程。
			StringBuilder  sBuilder = new StringBuilder(name);
			sBuilder.append('(');
			Class[] clazzParams = method.getParameterTypes();
			for(Class clazzParam : clazzParams){
				sBuilder.append(clazzParam.getName()).append(',');	
			}
			if(clazzParams.length == 0)
				sBuilder.deleteCharAt(sBuilder.length() - 1);
			sBuilder.append(')');
			System.out.println(sBuilder.toString());
			//System.out.println(constructor); 
		}
		//创建动态类的实例对象。
		//classProxy1.newInstance();
		Constructor constructor = classProxy1.getConstructor(InvocationHandler.class);
		//自定义一个类实现接口
		class MyInvocationHandler1 implements InvocationHandler{
			public Object invoke(Object proxy,Method method,Object[] args)
				throws Throwable{
					return null;
				}
			}
		//这里传递的对象是内部类类的实例对象。这也是为什么自定义类。
		//因为InvocationHandler是一个接口,所以要创建一个类实现它,作为参数传递。
		//生成动态类的实例对象。
		Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());
		System.out.println(proxy1);
		proxy1.clear();
		//proxy1.size();由于size方法有返回值,所以会报错。
		
		//通过匿名内部类传递参数。并获得动态类的实例对象。
		Collection Proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
			public Object invoke(Object proxy,Method method,Object[] args)
			throws Throwable{
				return null;
			}
		});
		Collection proxy3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
				new Class[]{Collection.class},
				new InvocationHandler(){
			//将目标取出后,成为成员变量,这时每次调用都是指向同一个目标,就不会出现size是0的情况了。
			ArrayList target = new ArrayList();	
			public Object invoke(Object proxy,Method method,Object[] args)
			throws Throwable{
				long beginTime = System.currentTimeMillis();
				//指定一个目标
				//ArrayList target = new ArrayList();	
				Object retVal = method.invoke(target, args);
				long endTime = System.currentTimeMillis();
				System.out.println(method.getName()+":"+"time:"+ (endTime - beginTime));
				return retVal;
			}
		});
		//调用add方法,它会调用Handler的invoke方法,每次都是一个新目标,所以size是0;
		proxy3.add("zxx");
		System.out.println(proxy3.size()); 
	}

}


七、实现类似spring的可配置的AOP框架

①工厂类BeanFactory:

1、工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。

2、getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该类示例

对象的getProxy方法返回的对象。

3、BeanFactory的构造方法接收代表配置文件的输入流对象的配置文件格式如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.test3.aopframework.ProxyFactoryBean
xxx.advice=cn.itcast.test3.MyAdvice
xxx.target=java.util. ArrayList
注意:其中的#代表注释当前行。

4、ProxyFactoryBean充当封装成动态的工厂,需为工厂提供的配置参数信息包括:

目标(target)

通告(advice)

5、BeanFactory和ProxyFactoryBean:

1)BeanFactory是一个纯粹的bean工程,就是创建bean即相应的对象的工厂。

2)ProxyfactoryBean是BeanFactory中的一个特殊的Bean,是创建代理的工厂。

②、实现类似spring的可配置的AOP框架的思路:

1、创建BeanFactory类:

1)构造方法:接受一个配置文件,通过Properties对象加载InputStream流对象获得。

2)创建getBean(String name)方法,接收Bean的名字,从上面加载后的对象获得。

3)通过其字节码对象创建实例对象bean。

4)判断bean是否是特殊的Bean即ProxyFactoryBean,如果是,就要创建代理类,并设置目标和通告,分别得到各自的实例对象,并返回代理类实例对象。如果不是在返回普

通类的实例对象。

2、创建ProxyFactoryBean(接口),此处用类做测试,其中有一个getProxy方法,用于获得代理类对象。

3、对配置文件进行配置,如上面配置一样。

4、作一个测试类:AopFrameworkTest进行测试。

//创建BeanFactory类   
package cn.itcast.test3.aopframework;  
import java.io.*;  
import java.util.Properties;  
import cn.itcast.test3.Advice;  
public class BeanFactory {  
    Properties prop = new Properties();  
    //创建对象时需要传入一个配置文件中的数据,所以需要在构造方法中接受一个参数   
    public BeanFactory(InputStream ips) {  
        try {  
            //将配置文件加载进来   
            prop.load(ips);  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
    //创建getBean方法,通过配置文件中的名字获取bean对象   
    public Object getBean(String name){  
        //从配置文件中读取类名   
        String className = prop.getProperty(name);  
        Object bean = null;  
        try {  
            //由类的字节码获取对象   
            Class clazz = Class.forName(className);  
            bean = clazz.newInstance();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }   
        //判断bean是特殊的bean即ProxyFactoryBean还是普通的bean   
        if(bean instanceof ProxyFactoryBean){  
            Object proxy = null;  
            try {  
                //是ProxyFactoryBean的话,强转,并获取目标和通告   
                ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;  
                //获取advice和target   
                Advice advice = (Advice)Class.forName(prop.getProperty(name + ".advice")).newInstance();  
                Object target = Class.forName(prop.getProperty(name + ".target")).newInstance();  
                //设置目标和通告   
                proxyFactoryBean.setAdvice(advice);  
                proxyFactoryBean.setTarget(target);  
                //通过类ProxyFactoryBean(开发中是作为接口存在)中获得proxy对象   
                proxy = proxyFactoryBean.getProxy();  
            } catch (Exception e) {  
                // TODO Auto-generated catch block   
                e.printStackTrace();  
            }   
            //是ProxyFactoryBean的话,返回proxy对象   
            return proxy;  
        }  
        //否则返回普通bean对象   
        return bean;  
    }  
}  
  
//创建ProxyFactoryBean类   
package cn.itcast.test3.aopframework;  
import java.lang.reflect.*;  
import cn.itcast.test3.Advice;  
public class ProxyFactoryBean {  
    private Object target;  
    private Advice advice;  
    public Object getTarget() {  
        return target;  
    }  
    public void setTarget(Object target) {  
        this.target = target;  
    }  
    public Advice getAdvice() {  
        return advice;  
    }  
    public void setAdvice(Advice advice) {  
        this.advice = advice;  
    }  
    public Object getProxy() {  
        Object proxy = Proxy.newProxyInstance(  
                target.getClass().getClassLoader(),  
                //这里的接口要和target实现相同的接口   
                target.getClass().getInterfaces(),  
                new InvocationHandler() {  
                    public Object invoke(Object proxy, Method method, Object[] args)  
                            throws Throwable {  
                        //通过契约,使用其方法--before和after方法   
                        advice.beforeMethod(method);  
                        Object value = method.invoke(target, args);  
                        advice.afterMethod(method);  
                        return value;  
                    }  
                }  
                );  
        return proxy;  
    }  
}  
//创建测试类AopFrameworkTest   
package cn.itcast.test3.aopframework;  
import java.io.InputStream;  
public class AopFramewrorkTest {  
    public static void main(String[] args)throws Exception {  
        //读取配置文件的数据   
        InputStream ips =   
                AopFramewrorkTest.class.getResourceAsStream("config.property");  
        //获取bean对象   
        Object bean = new BeanFactory(ips).getBean("xxx");  
        System.out.println(bean.getClass().getName());  
    }  
}  


---------------------- ASP.Net+Android+IO开发S.Net培训、期待与您交流! ----------------------

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值