一、代理(Proxy): (java.lang.reflect.Proxy)
(一)代理概念:
代理如字面意思,受目标类委托代理执行目标类的功能。
在Java代理运行时调用代理,代理会通过内部的接口InvocationHandler去调用/访问目标类的方法或数据,返回结果给代理,代理可对其进行处理后返回给调用者】
PS:接口InvocationHandler :
使用代理的好处:
1、提高程序扩展性: 在代理框架下可以通过反射在程序启动时明确调用目标类还是加强装饰的代理类;
2、提高程序安全性,这是针对网络方面而言的,例如:代理服务器VPN等,外部网络要访问学校、企业FTP内网数据时,通过访问VPN代理服务器来实现,而VPN就将访问请求发送给内网系统,由内网系统返回所需;既完成数据访问,也避免内网系统的数据安全(访问请求可以在代理处被审核等)。
代理实现的理解:
代理的实现(格式模式)其实可以看做子父类(或装饰类),代理类继承目标类,复写目标类的方法(不是完全复写,而是以装饰的形式添加所需的共性功能)。
代码示例:
class Student extends Person{
public String name(){
.......(装饰所需方法功能代码)........; //切面方法;
super.name(); [调用目标类的方法]
.......(装饰所需方法功能代码)........; //切面方法;
} }
由示例可理解代理的格式,用普通类也能实现代理的方式,那为什么要使用代理呢?
因为目标类的方法可能有许多,因此一个个方法的复写成指定格式相当浪费时间,因此通过Java提供的自动创建动态类,可以帮助我们简化实现代理的代码。
【以上示例的比方比较类似CGLIB库的动态生成子类作为代理类的情况,而Java自身提供的是需要目标类实现一个或多个接口,通过接口来创建对应的代理类】
面向方面(切面)编程AOP:
代理中是给目标类中的所有方法装饰共性功能,而装饰在目标类方法之前或之后,而不是插在方法代码中。Java中将这样的编程方式称为面向方面(切面)的编程(Aspectoriented program,简称AOP),也就是处理交叉业务的编程问题。而代理就是实现AOP功能的核心和关键技术。
【PS: OOP即面向对象编程 】
(二)创建动态代理的方式: (两种方式)
1、通过Proxy字节码获取代理类构造方法并实例化动态代理类对象:
A、先获取动态代理类的字节码:
Class clazzProxy =Proxy.getProxyClass( //获取动态代理类字节码;
Collection.class.getClassLoader(),Collection.class);
【PS:通过此字节码可反射获得动态代理类的构造方法或一般方法等属性行为。】
B、通过字节码反射获得动态代理的构造方法:
Constructor cst = clazzProxy1.getConstructor(InvocationHandler.class);
C、并创建实例化对象:
【InvocationHandler(接口,需要实现该接口或创建匿名内部类)】
Collection cl1 = (Collection)cst.newInstance(new InvocationHandler(){
ArrayList target= new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[]args)
throws Throwable {
//———添加切面方法———
Object retVal = method.invoke(target, args);
//———添加切面方法———
return retVal;
}
});
2、通过Proxy类的newProxyInstance方法直接创建代理类实例对象;
Collection cl2 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{ Collection.class },newInvocationHandler(){ //同上,复写invoke方法;}
3、两种方式的区别:
第一种通过获取代理类构造方法(无参、有参)来创建实例对象;
第二种要求目标类拥有无参构造方法,通过Proxy类newProxyInstance()方法直接创建实例对象。 【如果目标类有无参构造方法,建议使用第二种方式,简化代码】
(三)其他: (小细节)
1、打印一个类,返回值为null情况,存在两种可能:
A、该类为null(未初始化或初赋值为null);
B、该类的toString方法返回值为null(通过调用该类的toString()方法可验证,如果报异常则是第1种可能,否则反之)。
2、InvocationHandler中除了hashCode、equals与toString三个方法是由InvocationHandler实例对象执行的,其他继承方法由目标类自己实现。
例: 由代理创建的实例对象的.getClass()方法返回的class字节码是代理类的字节码。
二、代理框架:
概念:
1、动态语言:传递字符串,由接收端接收并对该字符串中的数据进行解析操作。
2、工厂模式定义:实例化对象,用工厂方法代替new操作;实例化对象时尽可能用工厂方法来实现,这样可以提高程序系统的可扩展性和尽量少的修改量。
PS: Advice通告/建议。
(一)整体思路:
前面演示的是直接创建代理对象,但是如果要修改则需要修改源代码,因此实际开发时是通过反射形式来在程序启动时指定代理目标类。
建立代理框架,调用或修改时只需修改框架的配置文件,就可以选择使用目标类还是代理类。
以下以创建Bean工厂来创建动态代理类等。
将创建动态代理类的类设计成JavaBean类必须要有不带参数的构造方法;
【因为(工厂模式等)创建JavaBean的实例对象使用的是无参构造方法】
(二)创建AOP代理框架:
前言: (注意与解析)
1、编写创建目标代理类的类文件,由于有接收值(Advice接口类与目标类),建议将其设计成JavaBean类,这样既能方便反射创建目标代理类,也能使用JavaBean类方法。
2、BeanFactory: 称之为Bean工厂的原因是该类是根据配置信息创建目标类或调用创建代理类(操作JavaBean),同时在大量使用代理类时该类也就要频繁生产JavaBean类就类似工厂。PS:大量创建不同代理类,对BeanFactory的代码设计的简繁有考究,那就涉及抽象工厂设计模式等(工厂模式分为:
3、由于要简化框架代码,所以下面都是直接反射使用类的无参构造方法的方式,因此要求该JavaBean类与目标类(创建代理类需要)都有无参构造方法(如果没有自定义有参构造方法,默认自动生成无参构造方法)。
————————————创建AOP框架————————————
(三)代码编写流程: (帮助理解、整理思路)
(代码下载详见: http://dl.vmall.com/c0y38zarjt中的aopframework包源文件)
1、框架平台(主函数)AopFrameWork与配置文件config.properties;
【注:
package com.Edward.day03.aopframework;
import java.io.InputStream;
//import java.util.Collection;
public class AopFrameWork {
public static void main(String[] args) {
/*一般创建实例化对象的方式:
Runnable run = new Runnable(){
@Override
public void run() {
for(int i =0;i<3;i++){
System.out.println("xixiixixi");
}
}
};
new Thread(run).start();*/
//类加载器加载配置文件;
InputStream ips = AopFrameWork.class.getResourceAsStream("config.properties");
//根据配置信息传入流,并获取指定的类;
Object bean = new BeanFactory(ips).getBean("TargetClass");
System.out.println("bean.class: "+bean.getClass().getName());
//对反射框架获得实例对象进行强转,使其可直接调用目标接口的方法;
Runnable thread = (Runnable)bean;
new Thread(thread).start();
//检测强转后的类是否指向代理类;
System.out.println(bean.getClass().getName());
/*对反射框架获得实例对象进行强转,使其可直接调用目标接口的方法;
(示例:目标类指向Collection,强转为Collection后,使其可直接调用Collection接口类方法)
Collection collection = (Collection)bean;
collection.add("hehe");
collection.add("xixi");
collection.add("hehe");
collection.add("gege");
for(Object obj : collection){
System.out.println(obj);
}*/
}
}
配置文件(
config.properties
信息
代码:
#TargetClass= java.lang.Runnable
TargetClass= com.Edward.day03.aopframework.ProxyFactoryBean
TargetClass.advice= com.Edward.day03.aopframework.TimeAdvice
TargetClass.target=java.lang.Thread
2、创建目标类对象的类/方法:BeanFactory;
注:A、初始化BeanFactory对象(传递流),并调用getBean方法(根据配置信息创建目标类或调用创建代理类);
B、调用的类名("xxx")指向目标类(例java.util.ArrayList)则直接创建目标类对象;反之指向代理类(路径.ProxyFacoryBean)则传递读取的其他信息,并创建目标的代理类。
package com.Edward.day03.aopframework;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
Properties props = new Properties();
//初始化构造方法,要求传入InputStream流,读取配置文件信息;
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) {
System.out.println("出错啦............");
e.printStackTrace();
}
//判断TargetClass指定的是否为代理类ProxyFactoryBean;
if(bean instanceof ProxyFactoryBean){
Object proxy = null;
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
//创建代理类Proxy;
try {
//多态形式创建接口子类对象,提高程序扩展性;
Advice advice = (Advice)Class.forName(props.getProperty(name+".advice")).newInstance();
//获取目标类实例化对象;
Object target = Class.forName(props.getProperty(name+".target")).newInstance();
//将接口子类对象与目标类对象传递给代理类工厂ProxyFactoryBean;
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
//调用获取目标代理类实例对象;
proxy = proxyFactoryBean.getProxy();
} catch (Exception e) {
e.printStackTrace();
}
//返回目标代理类对象;
return proxy;
}
//返回由BeanFactory创建的目标类对象;
return bean;
}
}
3、创建代理类的类: ProxyFactoryBean;
【注: 根据xxx.advice(代理方法类)与xxx.target(目标类)等信息创建目标的代理类。】
package com.Edward.day03.aopframework;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactoryBean{
//创建代理类所需的成员变量: 目标类target与切面方法advice;
private Advice advice;
private Object target;
//私有化成员变量,将此类设计成JavaBean类;
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 proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
advice.beginMethod(); //代理前置方法;
Object reVal = null;
try {
reVal = method.invoke(target, args);
} catch (Exception e) {
advice.catchMethod(); //代理catch块方法(提高扩展性);
e.printStackTrace();
}
advice.endMethod(); //代理后置方法;
return reVal;
}
});
return proxy;
}
}
4、创建代理的方法类:接口Advice与实现接口的方法类TimeAdvice(提供程序扩展性);
接口Advice代码:
package com.Edward.day03.aopframework;
public interface Advice {
void beginMethod(); //用于代理类调用目标类前的接口方法;
void catchMethod(); //catch块中的抽象方法,提高扩展性;
void endMethod(); //用于代理类调用目标类后的接口方法;
}
接口子类TimeAdvice代码:
package com.Edward.day03.aopframework;
public class TimeAdvice implements Advice {
long beginTime = 0;
@Override //注解,指下面方法复写父类接口中方法;
public void beginMethod() { //复写接口方法,与endMethod()结合使用,统计调用目标类方法消耗时间;
beginTime = System.currentTimeMillis(); //获取系统当前时间;
}
@Override
public void catchMethod() {
//略.................
}
@Override
public void endMethod() { //同上;
long endTime = System.currentTimeMillis();
System.out.println("此方法耗时: "+(endTime-beginTime)); //打印目标类方法执行消耗时间;
}
}
总结:
框架体系:代理生成类BeanFactory与ProxyFactoryBean、代理方法接口Advice;
【框架体系,可有框架提供/自动生成的程序代码类,可重复使用】
工具或自定义: 代理方法类TimeAdvice、主函数AopFrameworkTest与配置文件config;
【工具或自定义:可调用框架等提供的工具或需要根据需要自定义编写的程序代码,因编写不同程序所需可能不同】
注意: (调用目标类的注意事项)
A、此代理框架是要求动态类必须实现一个或多个接口,所以调用目标类是实现接口的类;
B、实例化动态类(目标类)对象使用的是目标类的无参构造方法,因此目标类必须有无参构造方法。
其他(MyEclipse小技巧):
MyEclipse中选择方法,右键———> References ———> Project:查看指定方法被哪些所引用。