思維導圖
前言
代理模式給某一個目標對象(target)提供代理對象(proxy),并由代理對象控制對target對象的引用夫否。
模式圖:
代理模式中的角色有:
- 抽象對象角色(AbstractObject):聲明了目標對象和代理對象的共同接口断序,這樣依賴在任何可以使用目標對象的地方都可以使用代理對象岖是。
- 目標對象角色(RealObject):定義了代理對象所代表的目標對象棉圈。
- 代理對象角色(ProxyObject):代理對象內(nèi)部含有目標對象的引用,從而可以在任何時候操作目標對象碰凶;代理對象提供一個與目標對象相同的接口,以便可以在任何時候替代目標對象鹿驼。代理對象通常在客戶端調(diào)用傳遞給目標對象之前或者之后欲低,執(zhí)行某個操作,而不是單純的將調(diào)用傳遞給目標對象畜晰。
按照代理類的創(chuàng)建時期砾莱,可分為靜態(tài)代理和動態(tài)代理:
- 靜態(tài)代理:由程序員創(chuàng)建代理類或特定工具自動生成源代碼再對其編譯。在程序運行前代理類的.class文件就已經(jīng)存在了舷蟀。
- 動態(tài)代理:在程序運行時運用反射機制動態(tài)創(chuàng)建而成恤磷。
靜態(tài)代理
實例:
1、抽象對象
public interface IDoctor {
void display();
}
2野宜、目標對象
public class Doctor implements IDoctor {
@Override
public void display() {
System.out.println("I am a doctor");
}
}
3扫步、代理對象
public class DoctorProxy {
private IDoctor iDoctor;
public DoctorProxy() {
this.iDoctor = new Doctor();
}
public void doProxy() {
// 權限控制
boolean isLogin = true;
if (isLogin) {
iDoctor.display();
}
// 記錄訪問日志
System.out.println("記錄訪問日志");
}
}
4、客戶端調(diào)用
public class StaticProxyTest {
public static void main(String[] args) {
DoctorProxy proxy = new DoctorProxy();
proxy.doProxy();
}
}
靜態(tài)代理在運行前就已經(jīng)確定了代理對象匈子,這種方式簡單河胎、效率高
但是靜態(tài)代理有以下缺點:
- 代理類和委托類實現(xiàn)了相同的接口,如果接口增加一個方法虎敦,除了所有實現(xiàn)類需要實現(xiàn)這個方法外游岳,所有代理類也需要實現(xiàn)此方法,因此增加了代碼維護的復雜度其徙。
- 代理對象只服務于一種類型的對象胚迫。如果還要為其他類提供代理的話,就需要我們再次添加代理類唾那。
動態(tài)代理
動態(tài)代理與靜態(tài)代理相比較访锻,最大的好處是接口中聲明的所有方法都被轉移到調(diào)用處理器一個集中的方法中處理。這樣闹获,在接口方法數(shù)量比較多的時候期犬,我們可以進行靈活處理,而不需要像靜態(tài)代理那樣每一個方法進行中轉避诽。而且動態(tài)代理的應用使我們的類職責更加單一龟虎,復用性更強。
JDK動態(tài)代理
利用攔截器(必須實現(xiàn)InvocationHandler)加上反射機制生成一個代理接口的匿名類沙庐,在調(diào)用具體方法前調(diào)用InvokeHandler來處理
實例:
1鲤妥、定義接口
public interface IStudent {
void display();
}
2佳吞、目標對象
public class Student implements IStudent {
@Override
public void display() {
System.out.println("I am a student");
}
}
3、實現(xiàn)代理對象處理器接口
public class StudentHandler implements InvocationHandler {
private IStudent student;
public Object newProxyInstance(IStudent student) {
this.student = student;
return Proxy.newProxyInstance(student.getClass().getClassLoader(), student.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass().getName());
// 定義預處理的工作旭斥,當然你也可以根據(jù) method 的不同進行不同的預處理工作
System.out.println("====before====");
Object result = method.invoke(student, args);
System.out.println("====after====");
return result;
}
}
4容达、測試
public class JdkProxyTest {
public static void main(String[] args) {
// 目標對象
StudentHandler handler = new StudentHandler();
// 動態(tài)生成代理對象
IStudent proxy = (IStudent) handler.newProxyInstance(new Student());
// 過代理對象調(diào)用方法
proxy.display();
}
}
CGLIB動態(tài)代理
上面的靜態(tài)代理和JDK動態(tài)代理需要目標對象是一個實現(xiàn)了接口的目標對象。但是有的時候垂券,目標對象可能只是一個單獨的對象花盐,并沒有實現(xiàn)任何的接口。這個時候菇爪,我們就可以使用Cglib代理方式算芯,它構建目標對象的子類對象,從而實現(xiàn)對目標對象的功能拓展凳宙。
利用ASM框架熙揍,對代理對象類生成的class文件加載進來,通過修改其字節(jié)碼生成子類來處理
實例:
1氏涩、引入POM:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2届囚、目標類
public class Teacher {
public void display() {
System.out.println("I am a teacher");
}
}
3、實現(xiàn)攔截器接口
public class CglibInterceptor implements MethodInterceptor {
private Teacher teacher;
// 獲取代理對象方法
public Object getProxyInstance(Teacher teacher) {
this.teacher = teacher;
Enhancer enhancer = new Enhancer(); // 1 實例化工具類
enhancer.setSuperclass(teacher.getClass()); // 設置父類對象
enhancer.setCallback(this); // 設置回調(diào)函數(shù)
return enhancer.create(); // 創(chuàng)建子類是尖,也就是代理對象
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before invoke");
Object result = method.invoke(teacher, objects);
System.out.println("after invoke");
return result;
}
}
4意系、測試
public class CglibProxyTest {
public static void main(String[] args) {
CglibInterceptor interceptor = new CglibInterceptor();
Teacher proxy = (Teacher) interceptor.getProxyInstance(new Teacher());
proxy.display();
}
}
cglib和jdk動態(tài)代理區(qū)別
1、JDK動態(tài)代理只能對實現(xiàn)了接口的類生成代理饺汹,而不能針對類
2蛔添、Cglib是針對類實現(xiàn)代理,對指定的類生成一個子類兜辞,覆蓋其中的方法迎瞧,并對該方法實現(xiàn)增強。因為采用的是繼承逸吵,所以該類或方法不能是final的
框架應用
AOP
AOP(Aspect Oriented Programming凶硅,面向切面編程),像日志扫皱、安全咏尝、緩存、事務 等與業(yè)務邏輯分離的功能啸罢,可能會散布于各個業(yè)務bean,這樣的稱為 橫切關注點(cross-cutting concern)胎食。AOP有助于橫切關注點與它們所影響的對象之間解耦扰才。
AOP的實現(xiàn)主要是AspectJ(基于靜態(tài)代理)和Spring AOP(基于動態(tài)代理)
Spring AOP是目前市面上最為流行的AOP應用,下面只針對這個進行闡述
Spring AOP
AOP的實現(xiàn)原理是基于動態(tài)代理厕怜。在Spring AOP編程中:
- 如果加入容器的目標對象有實現(xiàn)接口衩匣,就使用JDK代理
- 如果目標對象沒有實現(xiàn)接口蕾总,就使用Cglib代理
除此之外,Spring AOP增加了AspectJ切點表達式語言的支持琅捏,并且可以無縫地集成AspectJ
實例:
1生百、引入POM
<!-- aop依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、service
@Service
public class BossService {
public void display() {
System.out.println("I am a boss");
}
}
3柄延、controller
@RestController
@RequestMapping("/boss")
public class BossController {
@Autowired
private BossService bossService;
@PostMapping("/display")
public String display() {
bossService.display();
return "success";
}
}
4蚀浆、切面
@Aspect
@Component
public class BossAspect {
@Pointcut("execution(public * com.test.framework.springaop.BossService.*(..))")
public void pointCut() {}
@Before("pointCut()")
public void before(JoinPoint point) throws Throwable {
System.out.println("----- before ----");
}
@After("pointCut()")
public void after(JoinPoint point) throws Throwable {
System.out.println("----- after ----");
}
}
5、測試
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
注意:
Spring AOP必須在Spring環(huán)境下使用