Java之代理(jdk靜態代理,jdk動態代理,cglib動態代理,aop,aspectj)
一.概念
代理是什麼呢?舉個例子,一個公司是賣攝像頭的,但公司不直接跟使用者打交道,而是通過代理商跟使用者打交道。如果:公司介面中有一個賣產品的方法,那麼公司需要實現這個方法,而代理商也必須實現這個方法。如果公司賣多少錢,代理商也賣多少錢,那麼代理商就賺不了錢。所以代理商在呼叫公司的賣方法後,加上自己的利潤然後再把產品賣給客戶。而客戶部直接跟公司打交道,或者客戶根本不知道公司的存在,然而客戶最終卻買到了產品。
專業點說:代理模式是物件的結構型模式,程式碼模式給某一個物件提供代理,並由代理物件控制原物件(目標物件,被代理物件)的引用。簡單點說,就是通過一個工廠生成一個類的代理物件,當客戶端使用的時候不直接使用目標物件,而是直接使用代理物件。
二.jdk的靜態代理
Jdk的靜態代理要求,目標物件和代理物件都要實現相同的介面。然後提供給客戶端使用。這個代理對客戶端是可見的,其結果圖如下:
下面給出一個例子:
首先建立1個介面:UserService.java定義如下方法:
- package com.xie.service;
- publicinterface UserService {
- publicvoid addUser(String userId,String userName);
-
publicvoid delUser(String userId);
- publicvoid modfiyUser(String userId,String userName);
- public String findUser(String userId);
- }
然後實現這個介面的目標物件:UserServiceImpl.java
package com.xie.serviceimpl;
import com.xie.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public void addUser(String userId, String userName) {
System.out.println("UserServiceImpl addUser userId->>"+userId);
}
@Override
public void delUser(String userId) {
System.out.println("UserServiceImpl delUser userId->>"+userId);
}
@Override
public void modfiyUser(String userId, String userName) {
System.out.println("UserServiceImpl modfiyUser userId->>"+userId);
}
@Override
public String findUser(String userId) {
System.out.println("UserServiceImpl findUser userId->>"+userId);
return "張山";
}
}
為目標物件建立代理物件:UserServiceImplProxy.java代理物件持有目標物件的引用。
package com.xie.serviceproxy;
import com.xie.service.UserService;
public class UserServiceImplProxy implements UserService {
private UserService userService;
public UserServiceImplProxy(UserService userService){
this.userService = userService;
}
@Override
public void addUser(String userId, String userName) {
try {
System.out.println("開始執行:addUser");
userService.addUser(userId, userName);
System.out.println("addUser執行成功。");
} catch (Exception e) {
System.out.println("addUser執行失敗。");
}
}
@Override
public void delUser(String userId) {
}
@Override
public void modfiyUser(String userId, String userName) {
}
@Override
public String findUser(String userId) {
return null;
}
}
最後呼叫代理物件完成功能:Client.java
package com.xie.client;
import com.xie.service.UserService;
import com.xie.serviceimpl.UserServiceImpl;
import com.xie.serviceproxy.UserServiceImplProxy;
public class Client {
public static void main(String[] args) {
UserService userService = new UserServiceImplProxy(new UserServiceImpl());
userService.addUser("001", "centre");
}
}
一.jdk動態代理
靜態代理要為每個目標類建立一個代理類,當需要代理的物件太多,那麼代理類也變得很多。同時代理類違背了可重複代理只寫一次的原則。jdk給我們提供了動態代理。其原理圖如下:
Jdk的動態要求目標物件必須實現介面,因為它建立代理物件的時候是根據介面建立的。如果不實現介面,jdk無法給目標物件建立代理物件。被代理物件可以可以實現多個介面,建立代理時指定建立某個介面的代理物件就可以呼叫該介面定義的方法了。
首先定義2個介面:Service介面和UserService介面(上面的介面)
Service.java
package com.xie.service;
public interface Service {
public void sayHello(String name);
public int addOperter(int num,int num2);
}
然後定義實現這2個介面的目標物件:UserServiceImpl.java
package com.xie.serviceimpl;
import com.xie.service.Service;
import com.xie.service.UserService;
public class UserServiceImpl implements UserService ,Service{
@Override
public void addUser(String userId, String userName) {
System.out.println("UserServiceImpl addUser userId->>"+userId);
}
@Override
public void delUser(String userId) {
System.out.println("UserServiceImpl delUser userId->>"+userId);
}
@Override
public void modfiyUser(String userId, String userName) {
System.out.println("UserServiceImpl modfiyUser userId->>"+userId);
}
@Override
public String findUser(String userId) {
System.out.println("UserServiceImpl findUser userId->>"+userId);
return "張山";
}
@Override
public void sayHello(String name) {
System.out.println("你好:"+name);
}
@Override
public int addOperter(int num, int num2) {
return num+num2;
}
}
提供一個生成代理物件的的類:LogHandler.java
package com.xie.serviceproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LogHandler implements InvocationHandler {
private Object targertObject;
public Object newInstance(Object targertObject){
this.targertObject = targertObject;
Class targertClass = targertObject.getClass();
return Proxy.newProxyInstance(targertClass.getClassLoader(), targertClass.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("呼叫方法"+method.getName());
Object ret = null;
try {
ret = method.invoke(targertObject, args);
System.out.print("成功呼叫方法:"+method.getName()+";引數為:");
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
} catch (Exception e) {
e.printStackTrace();
System.out.print("呼叫方法:"+method.getName()+"失敗;引數為:");
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
}
}
return ret;
}
}
編寫一個客戶端類來使用工廠類生成目標類的代理:Client.java
package com.xie.client;
import com.xie.service.Service;
import com.xie.service.UserService;
import com.xie.serviceimpl.UserServiceImpl;
import com.xie.serviceproxy.LogHandler;
public class Client {
public static void main(String[] args) {
Service Service = (Service)new LogHandler().newInstance(new UserServiceImpl());
UserService userService = (UserService)new LogHandler().newInstance(new UserServiceImpl());
userService.addUser("001", "centre");
String name = userService.findUser("002");
System.out.println(name);
int num = Service.addOperter(12, 23);
System.out.println(num);
Service.sayHello("centre");
}
}
一.cglib 動態代理
jdk給目標類提供動態要求目標類必須實現介面,當一個目標類不實現介面時,jdk是無法為其提供動態代理的。cglib 卻能給這樣的類提供動態代理。Spring在給某個類提供動態代理時會自動在jdk動態代理和cglib動態代理中動態的選擇。
使用cglib為目標類提供動態代理:需要匯入cglib.jar和asm.jar
如果出現asm中的類無法找到的異常,在java工程中是真的缺少asm.jar,而在web工程中很可能是asm.jar和spring提供的org.springframework.asm-3.0.4.RELEASE.jar包衝突。
首先編寫一個目標類:UserServiceImpl.java(上面的類),
然後為其建立一個代理工廠,用於生成目標類的代理物件:CglibProxy.java
注意:如果一個類繼承了某個類,在子類中沒有一個方法,用cglib生成該子類的動態代理類中將沒有一個方法。
package com.xie.serviceproxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("呼叫的方法是:" + method.getName());
Object ret = null;
try {
ret = proxy.invokeSuper(obj, args);
System.out.print("成功呼叫方法:"+method.getName()+";引數為:");
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
}
} catch (Exception e) {
e.printStackTrace();
System.out.print("呼叫方法:"+method.getName()+"失敗;引數為:");
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
}
}
return ret;
}
}
編寫一個客戶端使用代理工廠生成代理物件:CglibClient.java
package com.xie.client;
import net.sf.cglib.proxy.Enhancer;
import com.xie.serviceimpl.UserServiceImpl;
import com.xie.serviceproxy.CglibProxy;
public class CglibClient {
public static void main(String[] args) {
cglibUse1();
}
public static void cglibUse1(){
Enhancer enhancer = new Enhancer();
// 設定被代理的類(目標類)
enhancer.setSuperclass(UserServiceImpl.class);
//使用回撥
enhancer.setCallback(new CglibProxy());
// 創造 代理 (動態擴充套件了UserServiceImpl類)
UserServiceImpl my = (UserServiceImpl) enhancer.create();
//my.addUser("001", "centre");
int ret = my.addOperter(15, 22);
System.out.println("返回的結果是:"+ret);
}
}
五. jdk動態和cglib動態代理比較
Jdk動態代理要求被代理的類要實現介面,而cglib不需要,cglib能根據記憶體中為其建立子類(代理物件)。那麼它們的效率是怎麼樣的呢?可以做如下測試:
我們用jdk和cglib動態代理分別為同一個代理建立10000個代理物件。
程式碼1:
public static void testFlexiable(){
UserService test = new UserServiceImpl();
long nums = 1000;
Date start = new Date();
for (int i = 0; i < nums; i++) {
UserService userService = (UserService)new LogHandler().newInstance(test);
}
Date end = new Date();
System.out.println("建立"+nums+"個代理代理物件用時:"+(end.getTime()-start.getTime())+"毫秒。");
}
測試結果:
建立1000個代理代理物件用時:32毫秒。
建立1000個代理代理物件用時:31毫秒。
建立1000個代理代理物件用時:31毫秒。
建立1000個代理代理物件用時:31毫秒。
建立10000個代理代理物件用時:94毫秒。
建立10000個代理代理物件用時:78毫秒。
建立10000個代理代理物件用時:78毫秒。
建立10000個代理代理物件用時:78毫秒。
程式碼2:
public static void testFlexiable(){
Enhancer enhancer = new Enhancer();
// 設定被代理的類(目標類)
enhancer.setSuperclass(UserServiceImpl.class);
//使用回撥
enhancer.setCallback(new CglibProxy());
long nums = 1000;
Date start = new Date();
for (int i = 0; i < nums; i++) {
UserServiceImpl my = (UserServiceImpl) enhancer.create();
}
Date end = new Date();
System.out.println("建立"+nums+"個代理代理物件用時:"+(end.getTime()-start.getTime())+"毫秒。");
}
測試結果:
建立1000個代理代理物件用時:47毫秒。
建立1000個代理代理物件用時:62毫秒。
建立1000個代理代理物件用時:62毫秒。
建立1000個代理代理物件用時:47毫秒。
建立1000個代理代理物件用時:47毫秒。
建立1000個代理代理物件用時:47毫秒。
建立10000個代理物件會拋異常,cglib執行速度明顯比jdk動態代理慢,由於是通過類建立的子類,比jdk通過介面建立代理更耗記憶體。因此在s2sh框架中,spring通過為類提供代理採用jdk比cglib應該要好一些吧。
六. 面向切面程式設計
面向切面程式設計是繼面向物件後,又一種重要的思維方式。面向物件比較重要的是通過繼承實現程式碼重用。而面向切面程式設計,則注重縱向程式設計,他能將2個不同的功能分開,實現最大程度的解耦,比如我們現在有業務邏輯層和日誌層,如果不分開,那麼在每個業務邏輯方法中除了要實現業務外還要加上日誌程式碼,如果某一天我不需要日誌了,而有很多這樣的類的,很多方法都加上日誌程式碼,那改動的工作量是可想而知的。有沒有一種方法讓我們只寫一次日誌程式碼,而把他應用在需要寫日誌的方法前面,當不需要的時候直接刪除呢?面向切面程式設計給我們提供了辦法。
Struts2的攔截器就是採用這種思想編寫的。下面模擬實現一個攔截器,設計圖如下:
首先定義一個攔截器介面:Interceptor.java
package com.xie.interceptor;
public interface Interceptor {
public void intercept(ActionInvocation invocation);
}
然後實現攔截器介面,實現3個攔截器:
FirstInterceptor.java,SecondInterceptor.java,ThirdInterceptor.java
package com.xie.interceptor;
public class FirstInterceptor implements Interceptor {
@Override
public void intercept(ActionInvocation invocation) {
System.out.println(1);
invocation.invoke();
System.out.println(-1);
}
}
package com.xie.interceptor;
public class SecInterceptor implements Interceptor {
@Override
public void intercept(ActionInvocation invocation) {
System.out.println(2);
invocation.invoke();
System.out.println(-2);
}
}
package com.xie.interceptor;
public class ThirInterceptor implements Interceptor{
@Override
public void intercept(ActionInvocation invocation) {
System.out.println(3);
invocation.invoke();
System.out.println(-3);
}
}
接下來定義一個InvokeInterceptory.java
package com.xie.interceptor;
import java.util.ArrayList;
import java.util.List;
public class ActionInvocation {
List<Interceptor> interceptors=new ArrayList<Interceptor>();
int index=-1;
Action action=new Action();
public ActionInvocation(){
this.interceptors.add(new FirstInterceptor());
this.interceptors.add(new SecInterceptor());
this.interceptors.add(new ThirInterceptor());
}
public void invoke(){
if (index+1>=this.interceptors.size()) {
action.execute();
}else {
index++;
this.interceptors.get(index).intercept(this);
}
}
}
定義一個action:Action.java
package com.xie.interceptor;
public class Action {
public void execute(){
System.out.println("execute action.");
}
}
最後定義Main類來呼叫action的execute方法:
package com.xie.interceptor;
public class Main {
public static void main(String[] args) {
new ActionInvocation().invoke();
}
}
執行結果如下:
1
2
3
execute action.
-3
-2
-1
在javaEE中,像Filter(過濾器),Intercepetor(攔截器),spring的事務管理都採用了面向切面的程式設計思想。
1.幾個應用
寫一個過濾器:這個過濾器的功能是實現web頁面訪問許可權:
package com.ibeifeng.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpSession session = request.getSession();
String username = (String) session.getAttribute("username");
HttpServletResponse response = (HttpServletResponse) res;
String uri = request.getRequestURI();
//如果使用者請求了index.html,這時就必須做登入判斷,判斷使用者是否登入。
if("/Pfms/index.html".equals(uri)) {
if(username == null || "".equals(username)) {
response.sendRedirect("login.html");
} else {
chain.doFilter(request, response);
}
}else {
chain.doFilter(request, response);
}
}
public void init(FilterConfig filterConfig) throws ServletException {
}
}
再寫一個struts2的攔截器:
package interceptor;
相關推薦
java之Spring(AOP)前奏-動態代理設計模式(上)
對象 .cn 分享圖片 賦值 alt his 編程習慣 輸出 style 我們常常會遇到這樣的事,項目經理讓你為一個功能類再加一個功能A,然後你加班為這個類加上了功能A; 過了兩天又來了新需求,再在A功能後面加上一個新功能B,你加班寫好了這個功能B,加在了A後面;又過
Java之Future(cancel,iSDone)
前言 在學習Future介面的過程中,注意到它具有一個cancel()方法,用於取消非同步的任務,它傳入一個boolean型別的引數,傳入true會中斷執行緒停止任務,而傳入false則會讓執行緒正常執行至完成,並返回false。 由此讓我產生了疑問,false引數並不會
Java之Array(數組)說明
blog ava nis .com add body tps new lin 代碼說明: 1 package array; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 impor
(轉)java之Spring(IOC)註解裝配Bean詳解
pos work 多個 public pre tor not 註解裝配 creat 在這裏我們要詳細說明一下利用Annotation-註解來裝配Bean。 因為如果你學會了註解,你就再也不願意去手動配置xml文件了,下面就看看Annotation的魅力所在吧。 先
行java之道(一)學習的方法
行java之道(一)學習的心得 自序 我是一名普通的JAVA開發從業者,接下來一段時間我會更新一些自己的心得體會,之所以想要這麼做,一是因為自己早有將自己的心得體會記錄下來的願景;二是因為自己在近來招聘中所遇見的應聘者誇誇其談框架,卻對基礎答非所問
JOKER_的JAVA之路(Java概念的匯入及開發環境的搭建)
一.java 第一個概念 1. JDK(Java Development Kit) 是 Java 語言的軟體開發工具包(SDK)。 2.JRE(Java Runtime Environment)縮寫,指Java執行環境 二.java開發環境的搭建 (對比之
java之ArrayList初始容量原始碼解析【jdk 1.8】
ArrayList解析 繼承的類和實現的介面 public class ArrayList<E>extends AbstractList<E>implements List<
Java反射例項(實戰方能理解jdk的各種方法)
什麼是反射?反射有什麼用處? 1. 什麼是反射? “反射(Reflection)能夠讓運行於JVM中的程式檢測和修改執行時的行為。”這個概念常常會和內省(Introspection)混淆,以下是這兩個術語在Wikipedia中的解釋: 內省用於在執行
java之介面(interface)和抽象(abstract)關鍵字詳解
1.抽象類: abstract class kabstract { public abstract void say(); public abstract void run(); }(1)抽象類必須用abstract【抽象的,】關鍵字修飾 (2)抽象方法是
Java之重寫(Override)
重寫(Override) 1) 方法重寫(Override):又叫做覆寫,子類與父類的方法返回型別一樣、方法名稱一樣,引數一樣,這樣我們說子類與父類的方法構成了重寫關係。 &nbs
java之異常(基本概念)
什麼是異常 異常是指程式可以編譯,由於程式內部或外部的原因造成的問題。並不等同於錯誤。錯誤是指語法上的錯誤等導致程式碼不能編譯。 java中所有的異常類都是從Throwable繼承來的。Error類是jvm內部出現資源耗盡等問題時報的異常,這類異常往往無
Java之BigDecimal(存任意精度的浮點型)
BigDecimal :可以存任意精度的浮點型 構造方法: BigDecimal(double b) : 會丟失精度 BigDecimal(String s)
java之旅(二)
多型:父型別的引用可以指向子型別的物件 Parent p =new Child();當使用多型方式呼叫時,首先檢查父類中是否有該呼叫的方法。如果沒有則編譯錯誤;如果有,再去呼叫子類中相同名稱的方法
【 C 】經典抽象資料型別(ADT)之堆疊(用靜態陣列實現堆疊)
堆疊簡介 堆疊(stack)最鮮明的特點就是後進先出(Last-In First-Out,LIFO)的資料進出方式。 基本的堆疊操作通常被稱為 push 和 pop。push就是將一個新值壓入到堆疊的頂部, pop就是把堆疊頂部的值移出堆疊並返回這個值。堆疊只提供對它的頂
從c++到java 之三(inline)
(4) 所有方法都是在類的主體定義的。所以用C++的眼光看,似乎所有函式都已嵌入,但實情並非如何(嵌入的問題在後面講述)。(30) Java不存在“嵌入”(inline)方法。Java編譯器也許會自行決定嵌入一個方法,但我們對此沒有更多的控制權力。在Java中,可為一個方法使
grpc-java之helloworld(一)
develop doc tps .com 學習記錄 nbsp developer style color 1、參考資料 (1)grpc-java官網QuickStart: https://grpc.io/docs/quickstart/java.html (2)
資料庫之SQL(Transact-SQL-聚合函式、時間函式、標量值函式、表值函式)
我們在SQL查詢中除了數學函式、字串函式外還用很多實用的函式,今天就對聚合函式,時間函式,使用者自定義函式進行梳理。 一、聚合函式 聚合函式又被稱為列函式,即對列資料進行聚合。 常用的聚合函式: ①、AVG()函式 問題:如何給查詢的結果新增列名? 新
linux驅動由淺入深系列:usb子系統之三(usb系統中的裝置、配置、介面、端點概念及程式碼解釋)
一個usb裝置對應一個或多個配置 一個配置包含一個或多個usb介面 一個usb介面可能存在一種或多種設定 一個設定會使用零個或多個usb端點裝置 裝置就是一個usb物理裝置,一個usb裝置可以只包含一個簡單功能的device,也可以包含一個由hub連線的多個裝置,叫作複合裝
Java---設計模組(設計模組的簡介及最簡單的倆個單例程式碼加測試)
設計模式學習概述: ★ 為什麼要學習設計模式 1、設計模式都是一些相對優秀的解決方案,很多問題都是典型的、有代表性的問題,學習設計模式,我們就不用自己從頭來解決這些問題,相當於在巨人的肩膀上,複用這些方案即可。 2、設計模式已經成為專業人士的常用詞彙,不懂
深度學習torch之三(神經網路的前向傳播和反向傳播以及損失函式的基本操作)
1.神經網路的前向傳播和反向傳播 require'image'; input=torch.rand(1,32,32) itorch.image(input) 隨即生產一張照片,1通道,32x32畫素的。為了直觀像是,匯入image包,然後用itorch.image()方法