动态代理与AOP----6
动态代理实例化的过程升级--目标对象+系统功能的参数化
实现类似Spring的可配置AOP框架
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------
上次课基本上推理出动态生成的代理类的结构以及InvocationHandler的结构。这次针对InvocationHandler的结构进行改进。
1. 动态代理类实例化过程升级--目标对象+系统功能的参数化
1). 现有InvocationHandler实现类存在的问题
(1). 现有InvocationHandler的实现类的一般结构
[1]. InvocationHandler实现类的代码如下
Object proxyInstance=con.newInstance(new InvocationHandler() {
private ArrayList target =new ArrayList();
public Object invoke(Object proxy, Methodmethod, Object[] args) throws Throwable {
//交叉业务
Object retVal = method.invoke(target, args);
//交叉业务
return retVal;
}
});
(2). 存在的问题如下:
[1]. 目标类对象已经固化到代码中,没有办法由用户传入进行设置
[2]. 交叉业务也同样被固化到代码中,无法由用户自由配置。【交叉业务也叫系统功能】
目标:将目标类对象移动到匿名局部类newInvocationHandler(){}的外部进行配置
将系统功能 (交叉业务) 通过参数传入InvocationHandler的局部匿名类
2). 目标对象和系统功能的参数化
(1). 目标对象和系统功能独立应注意的问题
[1]. 目标类对象从InvocationHandler的匿名局部类中独立出来
【注意】局部类只能访问外部final变量
[2]. 系统功能从InvocationHandler的匿名局部类中独立出来
{1}. 系统功能是代码块,要独立出去并且以参数的形式传入InvocationHandler的invoke方法,就必须要包装成独立的对象的方法才可以。
{2}. 系统功能以独立对象的方法被封装到InvocationHandler匿名局部类的外面之后,又需要被这个匿名局部类引用。那么封装着系统功能的对象就必须也是final的。
(2). 封装系统功能的对象所属的类的设计思路
{1}. 系统功能的位置
[1]. 前面[2]. 后面[3]. 前面+后面[4]. catch块中
这四个位置的代码一定都要被封装成四个方法并且一定要实现。因此这是一种强制性的工作并且系统功能也并不是确定的。所以向上抽取成有含有四个方法的接口更合理
{1}. 接口的命名
Spring将系统功能看做是用户对代码的建议,所以接口命名成Advice
【注意】这里面仅仅定义两个方法作为示例方法前 + 方法后
{2}. 接口中方法的命名
[1]. 目标类对象代码前面--- beforeMethod
[2]. 目标类对象代码后面--- afterMethod
{3}. 接口中方法的参数列表
Spring规定封装业务代码的接口的方法应该传入目标类对象、目标类对象调用的方法和目标类对象调用的方法对应的参数
[3]. 对交叉业务 (系统功能) 封装的接口代码如下
public interface Advice {
//目标代码之前的交叉业务
void beforeMethod(Object target, Methodmethod, Object[] args);
//目标代码之后的交叉业务
void afeterMethod(Object target, Methodmethod, Object[] args);
}
(3). 构造既可以生成代理类对象又可以插入系统功能的通用方法
[1]. 构建封装了交叉业务的接口Advice的实现类MyAdvice
实现Advice的接口中要求的具体交叉业务 (系统功能)
这里假设交叉业务的功能是计算目标方法运行的时间
public class MyAdvice implements Advice{
private long beginTime;
private long endTime;
@Override
public void beforeMethod(Object target, Methodmethod, Object[] args) {
//交叉业务
System.out.println("到黑马学习啦!");
beginTime =System.currentTimeMillis();
}
@Override
public void afeterMethod(Object target, Methodmethod, Object[] args) {
//交叉业务
System.out.println("从黑马马上毕业了!");
endTime =System.currentTimeMillis();
System.out.println(method.getName() +" using "+ (endTime -beginTime)/1000+ "s");
}
}
[2]. 编写可以插入系统功能和目标类对象的代理类实例生成器
public static Object getProxy(final Object target, final Advice advice){
ClassLoaderloader =target.getClass().getClassLoader();
Class<?>[]interfaces =target.getClass().getInterfaces();
InvocationHandlerh =new InvocationHandler() {
@Override
public Object invoke(Object proxy, Methodmethod, Object[] args)
throws Throwable {
advice.beforeMethod(target,method, args);
ObjectretVal =method.invoke(target, args);
advice.afterMethod(target,method, args);
return retVal;
}
};
ObjectproxyObj =Proxy.newProxyInstance(loader, interfaces, h);
return proxyObj;
}
[3]. 测试
Collection target =new ArrayList();
Advice advice =new MyAdvice();
Collection proxyInstance =(Collection)getProxy(target,advice);
proxyInstance.add("123");
System.out.println("*************************");
proxyInstance.add("456");
System.out.println("*************************");
System.out.println(proxyInstance.size());
【打印结果】
【总结】
{1}. 将目标和系统功能均抽取到外面之后,如果再向生成代理类实例的话,直接传入目标类对象和封装有交叉业务的对象。
{2}. getProxy()方法可以固定下来做框架使用。
【经验】使用Spring仅仅需要做的事情:在配置文件中配置目标对象 + 在Advice的方法实现中编写业务代码。
2. 实现类似Spring的可配置的AOP框架
需求:通过配置文件配置出想要实例化的对象的所属类,通过反射技术在代码中获取配置文件的信息并实例化要配置的类。
【附加需求】实例化的对象既可以是有字节码的文件存在的预先通过编译的类对象,也可以是没有字节码文件的动态生成的代理类对象。
1). 配置文件的设计
[1]. 动态生成类和普通类生成的区别
{1}. 普通类直接给出类名就可以在程序中实例化这个类的对象
{2}. 动态代理类的生成需要InvocationHandler的实现子类 ( 封装了系统功能的类 ) 和代理的目标类这两个额外的信息来生成动态代理类对象。
[2]. 现在采用简单工厂模式生成普通类的实例。为了统一起见将生成动态代理类对象的任务交给指定名称的自定义的普通的Java类来实现。
{1}. 这个指定的普通类名叫spring.ProxyFactoryBean
一旦在配置的类名是这个类的名字,那么工厂类就会实例化ProxyFactoryBean类的实例。这个类被实例化之后,就要调用内部编好的方法(直接可以调用第一部分的getProxy方法)来实例化动态代理类对象。
{2}. 如果是其他的类名,就直接实例化就可以了。
[3]. 配置文件内容的构成
{1}. 普通类的类名:设置为beanName
{2}. 如果普通类的类名是spring.ProxyFactoryBean,标志着这个实例对象是用来生成动态代理类的普通类。此时要额外配置代理类需要的代理类目标类类名和封装业务代码的Advice子类的类名
{2}1. 代理类代理的目标类类名:beanName.target
{2}2. 代理类需要的交叉业务所在的类名:beanName.advice
[4]. 配置文件举例如下:
2). Bean工厂的设计 ----BeanFactory
通过字符串封装的类名来获取类对象 ----getBean(String className)
无论是普通类对象还是动态代理类对象都通过这个类BeanFactory的getBean(String)来获取相应的对象。
【流程】. 这样无论是代理类对象的实例化还是普通类对象的实例化都首先经过Class对象进行实例化。一旦出现指定的普通类ProxyFactoryBean类出现在配置文件中并创建Class对象实例化之后,就要调用这个ProxyFactoryBean类实例的方法生成动态代理类对象。
3). 工程测试
(1). 工程配置
(2). ProxyFactoryBean类代码示例
package spring;
importjava.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactoryBean {
private Advice advice;
private Object target;
//生成动态代理类对象
public Object getProxyBean(){
ObjectproxyInstance =Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Methodmethod,
Object[]args) throws Throwable {
advice.beforeMethod(target, method, args);
ObjectretVal =method.invoke(target,args);
advice.afterMethod(target, method, args);
return retVal;
}
});
return proxyInstance;
}
//Setter & Getter
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;
}
}
(3). BeanFactory示例代码
package spring;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
Propertiesprops =new Properties();
public BeanFactory(InputStream ips){
try {
props.load(ips);
}catch (IOException e) {
e.printStackTrace();
}
}
public Object getBean(String name) throws ClassNotFoundException,InstantiationException, IllegalAccessException{
StringbeanName =props.getProperty(name);
ClassbeanClass =Class.forName(beanName);
//JavaBean必须有一个无参的构造函数
Objectbean =beanClass.newInstance();
//看看是否是动态代理类的普通标志对象
if(bean instanceof ProxyFactoryBean){
//从配置文件中获取交叉业务的配置
StringadviceName =props.getProperty(name+".advice");
//从配置文件中获取目标类
StringtargetName =props.getProperty(name+".target");
Adviceadvice =(Advice)Class.forName(adviceName).newInstance();
Objecttarget =Class.forName(targetName).newInstance();
ProxyFactoryBeanproxyFactoryBean =(ProxyFactoryBean)bean;
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
//实例化动态代理类对象
ObjectproxyBean =proxyFactoryBean.getProxyBean();
return proxyBean;
}
return bean;
}
}
(4). 测试类代码
package spring;
import java.io.InputStream;
import java.util.Collection;
//客户端程序
public class AOPFrameWorkTest {
public static void main(String[] args) {
InputStreamips =null;
try {
ips=AOPFrameWorkTest.class.getResourceAsStream("config.properties");
Collectionbean =(Collection)new BeanFactory(ips).getBean("beanName");
StringclassName =bean.getClass().getName();
if(className.equals("$Proxy0"))
System.out.println("动态代理对应的普通类:"+ className);
else
System.out.println("普通类:"+className);
//客户端调用功能
bean.add("Benjamin");
System.out.println("******");
bean.add("Zhang");
System.out.println("******");
bean.add("will finish the blog stagein some days....");
System.out.println("******");
System.out.println(bean.size());
System.out.println("******");
System.out.println(bean);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(5). 测试I ----普通类
[1]. config.properties配置文件内容
beanName=java.util.ArrayList
#beanName=spring.ProxyFactoryBean
#The following items used forcreating dynamic proxy objects
beanName.target=java.util.ArrayList
beanName.advice=spring.MyAdvice
[2]. 测试结果
(6). 测试II ----动态代理类
[1]. config.properties配置文件内容
beanName=spring.ProxyFactoryBean
#beanName=java.util.ArrayList
#The following items used forcreating dynamic proxy objects
beanName.target=java.util.ArrayList
beanName.advice=spring.MyAdvice
[2]. 测试结果
【解释】原本就是Collection实例的add和获取集合元素个数size()
但是这回在这些方法之前增加了交叉业务:打印系统信息并且计算目标方法的运行时间所以打印结果如上所示。
(7). Spring的两大核心
[1]. Bean工厂
[2]. AOP框架
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------