代理設計模式
java有20多種設計模式,代理模式肯定是非常出名的一種。
代理模式可以理解為不直接訪問對象,讓代理對象去達到某種目的。
一般是用于對方法的增強,在不動原代碼的情況下,對該方法運行前,后,異常加一些處理。
最出名的Sping的AOP(面向切面編程)底層就是動態代理幫實現的。
在平時開發中,使用設計模式可以讓我們的代碼更加具有可擴展性。
靜態代理
這個靜態代理非常簡單的。理解了上述的意思,我們就可以寫個差不多的出來。看代碼
package com.zyc.proxydesignpattern.staticproxy;
//這個是登錄方法接口
public interface UserService {
String login(String username, String password);
}
package com.zyc.proxydesignpattern.staticproxy;
//這個是登錄方法實現類,并實現了 login方法
public class UserServiceImpl implements UserService {
@Override
public String login(String username, String password) {
if("admin".equals(username) && "123".equals(password)){
System.out.println("登錄成功");
return "loginSuccess";
}else{
System.out.println("登錄失敗");
return "error";
}
}
}
package com.zyc.proxydesignpattern.staticproxy;
//這個是代理類 也需要實現UserService接口
public class UserServiceProxy implements UserService {
//定義 被代理的對象
private UserService userService;
public UserServiceProxy(UserService userService){
this.userService = userService;
}
//在調用的時候,給該方法的前后添加操作。
@Override
public String login(String username, String password) {
System.out.println("---前置---");
String result = userService.login(username,password);
System.out.println("---后置---");
return result;
}
}
@Test
public void staticTest(){
new UserServiceProxy(userServiceByStatic).login("admin","123");
}
//當調用該方法后運行的結果為
---前置---
登錄成功
---后置---
上邊的三段代碼就是一個簡單的靜態代理的demo
靜態代理的缺點比較明顯,這是一個UserService的代理,那再有AService,BService呢?
就需要重新創建代理類,比較麻煩。所以靜態代理在實際開發中使用并不多。
動態代理(JDK)
說完靜態代理,就說說JDK本身自帶的動態代理。
JDK的動態代理是靠反射完成的。直接看代碼。
package com.zyc.proxydesignpattern.dynamicproxy.jdk;
//接口
public interface UserService {
String login(String username,String password);
}
package com.zyc.proxydesignpattern.dynamicproxy.jdk;
//實現類,到此為止和靜態代理是一模一樣的。
public class UserServiceImpl implements UserService{
//JDK的動態代理必須要有接口,這是和cglib最大不同的地方
@Override
public String login(String username, String password) {
if("admin".equals(username) && "123".equals(password)){
System.out.println("登錄成功");
return "loginSuccess";
}else{
System.out.println("登錄失敗");
return "error";
}
}
}
JDK的動態代理最重要的就是這個 InvocationHandler
我們需要實現他的 invoke方法,實際上也就是讓目標方法運行
package com.zyc.proxydesignpattern.dynamicproxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandler implements InvocationHandler{
//目標方法
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
}
public Object getProxy(){
//獲取該類的代理
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(), this);
}
//通過反射讓方法運行。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---前置---");
Object result = method.invoke(target,args);
System.out.println("---后置---");
return result;
}
}
@Test
public void dynamicTest(){
private UserService userService = new UserServiceImpl();
//創建MyInvocationHandler 來獲取代理類,從而讓代理類運行login方法
UserService proxy = (UserService) new MyInvocationHandler(userService).getProxy();
proxy.login("admin","123");
}
//當調用該方法后運行的結果為
---前置---
登錄成功
---后置---
有了動態代理,可以說就比靜態代理方便了很多。
你需要代理什么,就傳什么值就可以。也可以泛型下就不用強轉了。
這只是一個小demo。就不弄那么麻煩了。
一會說為什么被代理類要有接口,
如果不想要接口 ,。其實還有一種動態代理,。
動態代理(cglib)
cglib原理是讓目標類生成一個子類,然后讓子類去進行方法的增強。
package com.zyc.proxydesignpattern.dynamicproxy.cglib;
//被代理類
public class UserService {
//在用cglib的時候, UserService可以沒有接口。
//但是 login 方法不能用final修飾,不能用private修飾 ,因為cglib的代理原理是找該類的子類去繼承該方法去實現,
//如果用private 或者 final 修飾 無法繼承該方法。
public String login(String username, String password) {
if("admin".equals(username) && "123".equals(password)){
System.out.println("登錄成功");
return "loginSuccess";
}else{
System.out.println("登錄失敗");
return "error";
}
}
}
package com.zyc.proxydesignpattern.dynamicproxy.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//看好包名 一定要用 cglib下的MethodInterceptor
//這個是cglib代理要實現的接口
public class MyCglibProxy implements MethodInterceptor {
//實現 MethodInterceptor 中的 intercept 接口
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("---前置---");
//實際方法運行的地方
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("---后置---");
return result;
}
}
package com.zyc.proxydesignpattern.dynamicproxy.cglib;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import java.lang.reflect.Method;
//代理工廠
public class ProxyFactory {
/**
* 防止在外邊創建
*/
private ProxyFactory(){}
//代理工廠方法。
//這幾天有時間的話 在寫個工廠設計模式
public static UserService getUserServiceProxy(MyCglibProxy myCglibProxy){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(com.zyc.proxydesignpattern.dynamicproxy.cglib.UserService.class);
enhancer.setCallback(new MyCglibProxy());
//這里就不詳細寫了,cglib功能挺多的
//setCallbacks可是設置多個代理,然后根據 setCallbackFilter 的 accept 方法 看哪個方法走哪個代理。
// enhancer.setCallbacks(new Callback[]{new MyCglibProxy()});
// enhancer.setCallbackFilter((Method method)-> {
//
// return 0;
// });
return (com.zyc.proxydesignpattern.dynamicproxy.cglib.UserService)enhancer.create();
}
}
@Test
public void cglibTest(){
/*這樣搞不方便,可以搞一個工廠
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(com.zyc.proxydesignpattern.dynamicproxy.cglib.UserService.class);
enhancer.setCallback(new MyCglibProxy());
com.zyc.proxydesignpattern.dynamicproxy.cglib.UserService userService = (com.zyc.proxydesignpattern.dynamicproxy.cglib.UserService)enhancer.create();
userService.login("admin","4554");*/
//如果要是不用代理工廠的話,
//按照上邊寫就行,但是這樣寫更簡潔一點。也更有拓展性
//通過代理工廠獲取 代理,然后用代理去運行login
ProxyFactory.getUserServiceProxy(new MyCglibProxy()).login("123","3443");
}
//當調用該方法后運行的結果為
---前置---
登錄失敗
---后置---
雖然大家都知道反射的效率是很低的。但是在1.8以后 JDK的動態代理還是要比cglib要效率高一點的。
然后我們看看JDK的動態代理和cglib的動態代理有什么不同吧。
借圖:
參考:
深入理解CGLIB動態代理機制
動態代理在實際開發中的使用
在這里我們基于 JDK的動態代理來搞
比如,我們開發中獲取一個數據需要有降級處理,先從redis中獲取,如果沒有獲取到在從mysql中獲取。
我們看代碼。
package com.zyc.proxydesignpattern.inaction.service.impl;
import com.zyc.proxydesignpattern.inaction.entity.Project;
import com.zyc.proxydesignpattern.inaction.service.ProjectService;
import org.springframework.stereotype.Service;
//很普通的service層
@Service
public class ProjectServiceImpl implements ProjectService{
public Project getProjectById(String id){
return new Project("123","假裝從數據庫中取出的項目");
}
}
package com.zyc.proxydesignpattern.inaction.util;
import com.zyc.proxydesignpattern.inaction.entity.Project;
import org.springframework.stereotype.Component;
//同樣很普通的redis操作工具
//我們假設id為123的項目在緩存中存在
@Component
public class RedisUtil {
public Project getProjectById(String id){
if(id!=null && "123".equals(id)){
return new Project(id,"假裝從緩存中取出的項目");
}else{
return null;
}
}
}
package com.zyc.proxydesignpattern.inaction.util.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//也很普通的代理工具 ,就多了幾個操作類
public class DataBaseProxyHandler<T> implements InvocationHandler {
//被代理對象
private Object delegate;
//需要去做事情的接口
private ProxyInterface myProxyInterface;
//需要去做事情的類
private Object param;
//構造器 在這里給賦值
public DataBaseProxyHandler(Object v) {
this.param = v;
}
//proxy方法,返回
public <T> T proxy(T delegate, ProxyInterface myProxyInterface) {
this.myProxyInterface = myProxyInterface;
this.delegate = delegate;
return (T) Proxy.newProxyInstance(this.delegate.getClass().getClassLoader(),
this.delegate.getClass().getInterfaces(), this);
}
//就多了個幾個類,其他一模一樣
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object obj = null;
if (args != null && args.length > 0){
obj = myProxyInterface.doBegin(param,args);
}else{
obj = myProxyInterface.doBegin(param,null);
}
if (obj != null)
return obj;
obj = method.invoke(this.delegate, args);
if (args != null && args.length > 0){
myProxyInterface.doEnd(obj, param,args);
}else{
myProxyInterface.doEnd(obj, param,null);
}
return obj;
}
}
package com.zyc.proxydesignpattern.inaction.util.proxy;
public interface ProxyInterface<T,V> {
/**
* T 參數代表需要操作對象的工具類
* V 參數實體對象
* @param
* @return
*/
Object doBegin(T t, Object[] param);
/**
* T 參數代表需要操作對象的工具類
* returnObj invok 后返回的參數
*
* @param returnObj
* @param t
* @return
*/
Object doEnd(V returnObj, T t,Object[] param);
}
package com.zyc.proxydesignpattern.inaction.controller;
import com.zyc.proxydesignpattern.inaction.entity.Project;
import com.zyc.proxydesignpattern.inaction.service.ProjectService;
import com.zyc.proxydesignpattern.inaction.util.RedisUtil;
import com.zyc.proxydesignpattern.inaction.util.proxy.DataBaseProxyHandler;
import com.zyc.proxydesignpattern.inaction.util.proxy.ProxyInterface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class ProjectController {
@Autowired
ProjectService projectService;
@Autowired
RedisUtil redisUtil;
//如果前邊動態代理搞明白了。這些代碼很容易理解
//就多了個ProxyInterface去進行前置和后置
public Project getProjectById(String id){
//在這里用了個匿名內部類,就不在外邊創建新的Proxy了。
//在這里可以更加直觀的看,
return new DataBaseProxyHandler<ProjectService>(redisUtil).proxy(projectService, new ProxyInterface() {
//在doBegin方法中是 getProjectById 前做的事情。
//從 DataBaseProxyHandler 的invoke 方法,可以看到 如果返回的是null才會去運行 getProjectById
//如果不為null 則直接返回了。
@Override
public Object doBegin(Object o, Object[] param) {
RedisUtil redisUtil = (RedisUtil)o;
return redisUtil.getProjectById(id);
}
//在doEnd方法中 是 getProjectById 后做的事情
//在 getProjectById 中 doEnd是不用做任何事的。
//但是 如果是 saveProject 呢? 我們可以在doBegin中不做任何事情,
//在doEnd中可以 判斷如果saveProject插入到 mysql/oracle 那么在doEnd可以插入到緩存中。
@Override
public Object doEnd(Object returnObj, Object o, Object[] param) {
return null;
}
}).getProjectById(id);
}
}
以上就是動態代理在實際開發中的使用。
github:https://github.com/zycisbg/ProxyDesignPattern