編寫一個與目標類具有相同接口的代理類,代理類的每個方法調用目標類的相同方法,並在調用方法時加上系統功能的代碼。
如果採用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標類、還是代理類,這樣以後很容易切換,譬如,想要日誌功能時就配置代理類,否則配置目標類,這樣,增加系統功能很容易,以後運行一段時間後,又想去掉系統功能也很容易。
安全 事務 日誌StudentService ------|----------|------------|-------------CourseService ------|----------|------------|-------------MiscService ------|----------|------------|-------------
用具體的程序代碼描述交叉業務:
method1 method2 method3{ { {------------------------------------------------------切面.... .... ......------------------------------------------------------切面} } }
------------------------------------------------------切面func1 func2 func3{ { {.... .... ......} } }------------------------------------------------------切面
JVM可以在運行期動態生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類。
JVM生成的動態類必須實現一個或多個接口,所以,JVM生成的動態類只能用作具有相同接口的目標類的代理。
CGLIB庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理,所以,如果要爲一個沒有實現接口的類生成動態代理類,那麼可以使用CGLIB庫。
代理類的各個方法中通常除了要調用目標的相應方法和對外返回目標返回的結果外,還可以在代理方法中的如下四個位置加上系統功能代碼:
1.在調用目標方法之前2.在調用目標方法之後3.在調用目標方法前後4.在處理目標方法異常的catch塊中
創建實現了Collection接口的動態類和查看其名稱,分析Proxy.getProxyClass方法的各個參數。編碼列出動態類中的所有構造方法和參數簽名編碼列出動態類中的所有方法和參數簽名
package cn.itheima.Proxy;
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 clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//獲得動態代理類的類名
System.out.println(clazzProxy.getName());
//獲取動態代理類中的構造函數
System.out.println("--------begin constructors list-------");
Constructor[] constructors = clazzProxy.getConstructors();
for (Constructor constructor : constructors)
{
String name = constructor.getName();
//將名稱存入字符串容器StringBuilder中
StringBuilder sb = new StringBuilder(name);
sb.append("(");
Class[] clazzParams = constructor.getParameterTypes();
for(Class clazzParam : clazzParams)
{
sb.append(clazzParam.getName()).append(",");
}
//排除無構造函數的情況
if (clazzParams !=null && clazzParams.length != 0)
{
sb.deleteCharAt(sb.length()-1);
}
sb.append(")");
System.out.println(sb.toString());
}
//獲取動態代理類中的所有方法和接收參數的類型。
System.out.println("--------begin method list-------");
Method [] methods = clazzProxy.getMethods();
for (Method method : methods)
{
String name = method.getName();
StringBuilder sb =new StringBuilder(name);
sb.append("(");
Class[] clazzParams = method.getParameterTypes();
for (Class clazzParam : clazzParams)
{
sb.append(clazzParam.getName()).append(",");
}
if(clazzParams !=null && clazzParams.length != 0)
{
sb.deleteCharAt(sb.length()-1);
}
sb.append(")");
System.out.println(sb.toString());
}
}
}
輸出結果:
--------begin constructors list-------
$Proxy0(java.lang.reflect.InvocationHandler) //唯一的一個構造函數(有一個參數)
--------begin method list-------
hashCode() //以下是Collection中的方法
equals(java.lang.Object)
toString()
add(java.lang.Object)
contains(java.lang.Object)
isEmpty()
size()
toArray()
toArray([Ljava.lang.Object;)
addAll(java.util.Collection)
iterator()
remove(java.lang.Object)
clear()
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)//以下是java.lang.reflect.Proxy類中的方法
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
getClass()
notify()
notifyAll()
wait()
wait(long,int)
wait(long)
用反射獲得構造方法
編寫一個最簡單的InvocationHandler類
調用構造方法創建動態類的實例對象,並將編寫的InvocationHandler類的實例對象傳進去
打印創建的對象和調用對象的沒有返回值的方法和getClass方法,演示調用其他有返回值的方法報告了異常。
將創建動態類的實例對象的代理改成匿名內部類的形式編寫,鍛鍊大家習慣匿名內部類。
package cn.itheima.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
public class ProxyTest2
{
public static void main(String[] args)throws Exception
{
//使用動態代理類創建一份字節碼文件
Class<?> clazzProxy =
Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//創建動態代理類的對象
Collection<?> proxy =
(Collection<?>)clazzProxy.getConstructor(InvocationHandler.class).newInstance(
new InvocationHandler()
{
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable
{
return null;
}
});
System.out.println(proxy); //結果:null
proxy.clear(); //執行沒有返回值的方法,不會報告異常
//proxy.size(); //執行有返回值的方法,會報告異常 因爲newInstance()是一個空參數的方法
}
}
總結:
1.生成的類中有哪些方法,通過讓其實現哪些接口的方式進行告知;2.產生的類字節碼必須有一個關聯的類加載器對象;3.生成的類中的方法的代碼是怎樣的,也得由我們提供。把我們的代碼寫在一個約定好了接口對象的方法中,把對象傳給它,它調用我的方法,即相當於插入了我的代碼。提供執行代碼的對象就是那個InvocationHandler對象,它是在創建動態類的實例對象的構造方法時傳遞進去的。在上面的InvocationHandler對象的invoke方法中加一點代碼,就可以看到這些代碼被調用運行了。
package cn.itheima.Proxy;
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 ProxyTest3
{
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception
{
@SuppressWarnings("rawtypes")
//使用newProxyInstance()方法創建代理類的實例對象
Collection proxy = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(), new Class[] {Collection.class },
new InvocationHandler()
{
ArrayList<String> target =new ArrayList<String>(); //此段代碼放在放在成員變量位置上,可返回正確結果
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable
{
long startTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endTime =System.currentTimeMillis();
System.out.println(method.getName() +" running time is "
+ (endTime - startTime));
return retVal;
}
});
proxy.add("zhangsan");
proxy.add("lisi");
proxy.add("wangwu");
System.out.println(proxy.size()); //結果:3
}
}
注意:
如果作爲target的ArrayList對象放置在invoke方法內部定義,那麼每次調用代理的某個方法,都會調用invoke 方法,這樣作爲target的ArrayList對象每次都會被創建,這樣就導致最後調用proxy.size()的時候,結果爲0。將ArrayList定義在成員位置,就不會出現這種情況,最終結果也就變爲了3.
動態生成的類實現了Collection接口(可以實現若干接口),生成的類有Collection接口中的所有方法和一個如下接受InvocationHandler參數的構造方法。構造方法接受一個InvocationHandler對象,接受對象了要幹什麼用呢?該方法內部的代碼會是怎樣的呢?$Proxy0 implements Collection { InvocationHandler handler; public $Proxy0(InvocationHandler handler) { this.handler = handler; } }
實現Collection接口的動態類中的各個方法的代碼又是怎樣的呢?InvocationHandler接口中定義的invoke方法接受的三個參數又是什麼意思?圖解說明如下:
分析先前打印動態類的實例對象時,結果爲什麼會是null呢?因爲實際輸出的是proxy.toString(),代理對象將調用請求轉發給invocationHandler對象,執行如下代碼:String toString(){
return handler.invoke(this,this.getClass().getMethod("toString"),null);
}返回值是null,所以輸出null;調用有基本類型返回值的方法時爲什麼會出現NullPointerException異常?例如:size()方法。代理類調用size()方法,將目標方法傳入invoke()方法中,就是如下代碼:int size(){
return handler.invoke(this,this.getClass().getMethod("size"),null);
}返回值類型是null,轉換爲int型數據就會報空指針異常分析爲什麼動態類的實例對象的getClass()方法返回了正確結果呢?
調用代理對象的從Object類繼承的hashCode, equals, 或toString這幾個方法時,代理對象將調用請求轉發給InvocationHandler對象,對於其他方法,則不轉發調用請求。
直接在InvocationHandler實現類中創建目標類的實例對象,可以看運行效果和加入日誌代碼,但沒有實際意義。爲InvocationHandler實現類注入目標類的實例對象,不能採用匿名內部類的形式了。讓匿名的InvocationHandler實現類訪問外面方法中的目標類實例對象的final類型的引用變量。
package cn.itheima.Proxy;
import java.lang.reflect.Method;
public interface Advice
{
void beforeMethod();
void afterMethod(Object target,Method method);
}
package cn.itheima.Proxy;
import java.lang.reflect.Method;
public class MyAdvice implements Advice
{
private long startTime;
@Override
public void beforeMethod()
{
startTime = System.currentTimeMillis();
}
@Override
public void afterMethod(Object target, Method method)
{
long endTime =System.currentTimeMillis();
System.out.println("The target is "+target.getClass().getName()+"target調用方法:"+method.getName() +" running time is " + (endTime - startTime));
}
}
package cn.itheima.Proxy;
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 ProxyTest4
{
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception
{
ArrayList<String> target =new ArrayList<String>();
Collection<String> proxy = (Collection<String>) getProxy(target,new MyAdvice());
proxy.add("zhangsan");
proxy.add("lisi");
proxy.add("wangwu");
System.out.println(proxy.size());
}
private static Object getProxy(final Object target,final Advice advice)
{
Object proxy = Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[] {Collection.class },
new InvocationHandler()
{
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable
{
advice.beforeMethod();
Object retVal = method.invoke(target, args);
advice.afterMethod(target, method);
return retVal;
}
});
return proxy;
}
}
將系統功能代碼模塊化,即將切面代碼也改爲通過參數形式提供,怎樣把要執行的系統功能代碼以參數形式提供?
把要執行的代碼裝到一個對象的某個方法裏,然後把這個對象作爲參數傳遞,接收者只要調用這個對象的方法,即等於執行了外界提供的代碼!爲bind方法增加一個Advice參數。
BeanFactory的構造方法接收代表配置文件的輸入流對象,配置文件格式如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice
ProxyFacotryBean充當封裝生成動態代理的工廠,需要爲工廠類提供哪些配置參數信息?
目標
通知
編寫客戶端應用:
編寫實現Advice接口的類和在配置文件中進行配置
調用BeanFactory獲取對象
package cn.itheima.Proxy;
import java.lang.reflect.Method;
public interface Advice
{
void beforeMethod();
void afterMethod(Object target,Method method);
}
package cn.itheima.Proxy;
import java.lang.reflect.Method;
public class MyAdvice implements Advice
{
private long startTime;
@Override
public void beforeMethod()
{
startTime = System.currentTimeMillis();
}
@Override
public void afterMethod(Object target, Method method)
{
long endTime =System.currentTimeMillis();
System.out.println("The target is "+target.getClass().getName()+"target調用方法:"+method.getName() +" running time is " + (endTime - startTime));
}
}
package cn.itheima.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactoryBean
{
private Advice advice;
private Object target;
ProxyFactoryBean(){}
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.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
advice.beforeMethod();
Object retVal = method.invoke(target, args);
advice.afterMethod(target ,method);
return retVal;
}
});
return proxy;
}
}
package cn.itheima.Proxy;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory
{
private Properties prop = new Properties();
public BeanFactory(InputStream ips)
{
try
{
prop.load(ips);
}
catch (Exception e)
{
e.printStackTrace();
}
}
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();
}
if(bean instanceof ProxyFactoryBean)
{
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;
Object proxy = null;
try
{
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);
proxy = proxyFactoryBean.getProxy();
}
catch (Exception e)
{
e.printStackTrace();
}
return proxy;
}
return bean;
}
}
package cn.itheima.Proxy;
import java.io.InputStream;
public class AopFramworkTest
{
public static void main(String[] args)
{
// TODO Auto-generated method stub
InputStream ips = AopFramworkTest.class.getResourceAsStream("config.properties");
Object Proxy = new BeanFactory(ips).getBean("proxy");
System.out.println(Proxy.getClass().getName());
}
}
config.properties配置文件內容 :