JAVA動態代理技術
JAVA代理技術是JAVA核心技術之一,也是JAVA core中非常重要的一部分,對於學習Spring等JAVA生態圈的學習起著非常重要的作用,比如說AOP,cglib。動態代理技術就是產生對象的代理對象的。舉例現實場景就是:一個明星在出名之前可能沒有很多商演找他,所以商家可以直接聯系明星本人進行商談,但隨著明星知名度越來越高,商演越來越多,那麽這個時候明星本人就無法處理過多的商談,那麽這個時候就需要一個角色經紀人(明星代理人),而明星只要做商演即可。說道這裏是不是代理的作用就出來了:攔截直接訪問真實業務對象(被代理對象)。
代理分類
根據加載被代理類的時機不同,將代理分為靜態代理和動態代理。
如果在編譯時就確定被代理的類,那麽就可以直接使用靜態代理。
2.動態代理
如果編譯時不能確定,那麽使用類的動態加載機制,在代碼運行期間加載被代理的類這就是動態代理,比如RPC框架和Spring AOP機制。
靜態代理實現
1.ITask接口
/** * @ClassName ITask * @Description TODO:描述該接口職責 * @Author ckmike * @Date 18-12-5 下午5:15 * @Version 1.0 * @Copyright ckmike **/ public interface ITask { void writelog(String msg); }
2.PersonTask類
public class PeopleTask implements ITask { private String username; private String age; public PeopleTask() { } @Override public void writelog(String msg) { System.out.println("Hello! My name is"+username+",age is "+age); } public PeopleTask(String username, String age) { this.username = username; this.age = age; } public void setUsername(String username) { this.username = username; } public void setAge(String age) { this.age = age; } public String getUsername() { return username; } public String getAge() { return age; } }
3.TaskProxy代理類
/**
* TaskProxy 簡要描述
* <p> TODO:描述該類職責 </p>
*
* @author ckmike
* @version 1.0
* @date 18-12-5 下午5:20
* @copyright ckmike
**/
public class TaskProxy implements ITask {
private ITask tasker;
public TaskProxy(ITask tasker) {
this.tasker = tasker;
}
@Override
public void writelog(String msg) {
tasker.writelog(msg);
}
public void writelog(String msg,String singname){
tasker.writelog(msg);
sing(singname);
}
private void sing(String singname) {
System.out.println("唱歌:"+singname);
}
}
4.測試
TaskProxy taskProxy = new TaskProxy(new PeopleTask("Mike","18"));
taskProxy.writelog("靜態代理測試...","莫妮卡");
這種靜態代理的方式,非常適合做增強老舊復雜代碼。比如說一個復雜的業務代碼已經寫完了,經過很久後有一天需要對業務方法進行擴展,那麽這個時候不管出於風險還是出於業務復雜性代碼可讀性考慮都不適合去動原本已經穩定的代碼,這個時候進行代碼增強進行代理就非常合適。其實這個模型就是Spring AOP的雛形,你看我調用writelog方法在執行了增強方法sing方法,是不是有點像@After?
實現動態代理
JAVA中是通過java.lang.reflect.Proxy中的newProxyInstance方法創建一個代理對象。
1.StarAction接口
/**
* @ClassName IStarAction
* @Description TODO:描述該接口職責
* @Author ckmike
* @Date 18-12-7 下午5:46
* @Version 1.0
* @Copyright ckmike
**/
public interface IStarAction {
// 歌唱
void sing(String name);
// 跳舞
void dance(String name);
// 拍電影
void movie(String name);
}
2.Star類
/**
* Star 簡要描述
* <p> TODO:描述該類職責 </p>
*
* @author ckmike
* @version 1.0
* @date 18-12-7 下午5:51
* @copyright ckmike
**/
public class Star implements IStarAction {
private String name;
public Star(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void sing(String name) {
System.out.println(this.name+"演唱歌曲:"+ name);
}
@Override
public void dance(String name) {
System.out.println(this.name+"表演舞蹈:"+ name);
}
@Override
public void movie(String name) {
System.out.println(this.name+"參演電影:"+ name);
}
}
3.StarProxy類
/**
* StarProxy 簡要描述
* <p> TODO:描述該類職責 </p>
*
* @author ckmike
* @version 1.0
* @date 18-12-7 下午5:58
* @copyright ckmike
**/
public class StarProxy implements InvocationHandler {
// 代理目標對象
private Star target;
public Object bind(Star target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=null;
if(method.getName().equals("sing")){
System.out.println("邀請"+target.getName()+"演唱歌曲,每首報價60w!");
return method.invoke(target,args);
}else if(method.getName().equals("dance")){
System.out.println("邀請"+target.getName()+"演唱跳舞,每場報價200w!");
return method.invoke(target,args);
}else if(method.getName().equals("movie")){
System.out.println("邀請"+target.getName()+"出演電影,每部報價8000w!");
return method.invoke(target,args);
}
return result;
}
}
4.測試用例
/**
* Test 簡要描述
* <p> TODO:描述該類職責 </p>
*
* @author ckmike
* @version 1.0
* @date 18-12-7 下午6:15
* @copyright ckmike
**/
public class Test {
public static void main(String[] args) {
// 這裏特別要註意,只能轉為接口,因為代理返回的就是接口的代理對象,無法轉為Star對象
IStarAction star = (IStarAction) new StarProxy().bind(new Star("劉德華"));
star.sing("冰雨");
star.dance("MoonWalk");
star.movie("旺角");
}
}
總結:
所有的對被代理對象接口的訪問都會執行invoke方法,那麽也就是通過代理對象攔截了對被代理對象接口的訪問,這個代理對象類似於攔截器。根據上面的靜態和動態代理,我們發現一個問題,JAVA 的API代理要求每個被代理類必須實現某個接口,這也是JAVA面向接口編程的一種表現,但是如果我們某個類沒有實現任何接口,那麽我們還能實現它的代理嗎?答案是肯定的,cglib就是一個非常成熟流行的代表。接下來我們就來講講cglib代理。
cglib代理
cglib是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理。因為采用的是繼承,所以不能對final修飾的類進行代理,也無法代理final修飾的成員方法。
/**
* CglibProxy 簡要描述
* <p> TODO:描述該類職責 </p>
*
* @author ckmike
* @version 1.0
* @date 18-12-5 下午5:40
* @copyright ckmike
**/
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result=null;
System.out.println("播放電影院廣告...");
result = methodProxy.invokeSuper(o,objects);
System.out.println("播放電影類似新電影廣告...");
return result;
}
}
public class Movie {
public void play(String name){
System.out.println("播放電影:"+name);
}
public final void show(String info){
System.out.println(info);
}
}
public class Test {
public static void main(String[] args) {
// cglib代理
Movie movie = (Movie) new CglibProxy().getInstance(new Movie());
movie.play("十面埋伏");
movie.show("hello,大家好!");
}
}
JAVA動態代理應用場景
上面我們以及了解了JAVA靜態代理,JAVA動態代理以及cglib的動態代理。那麽我們通常什麽時候會使用代理技術呢?什麽場景上會使用呢?
1.設計模式中對關閉開放原則,如果要在原來穩定業務代碼中進行擴展或者增強,對於進入原功能方法進行修改是不應許的,所以這個時候采用代理技術就可以非常方便的實現這一原則。
2.Spring中的AOP面向切面編程就是使用的動態代理技術實現的。
總結
1.靜態代理、動態代理都是需要通過接口來實現,面向接口編程的方式。
2.cglib可以代理未實現任何接口的類的代理,但是該類不能是final類,並且final方法無法被代理。
3.Spring中AOP技術就是通過動態代理實現的。
JAVA動態代理技術