Spring (AOP、靜態代理、動態代理)
1. AOP
1.1 AOP簡介
-
AOP Aspect Oriented Programing 面向切面程式設計
-
AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性程式碼(效能監視、事務管理、安全檢查、快取)
-
Spring中的Aop是純Java來實現的,使用動態代理的方式增強程式碼
-
Spring使用動態代理的機制是判斷委託類是否實現了介面,如果實現了介面則使用jdk的動態代理,如果沒有實現介面則使用cglib的動態代理
-
AOP不是由Spring提出來的,是由AOP聯盟定義的
1.2 AOP的專業術語
Joinpoint(連線點)
Pointcut(切入點) :切點 ,要被增強的方法
Advice(通知/增強) :增強的程式碼
Target(目標物件) :委託物件
Weaving(織入) :把增強應用切點的過程
Proxy(代理): 一個類被AOP織入增強後,就產生一個結果代理類
Aspect(切面): 是切點和通知的結合
1.3 Spring中的AOP實現
1.3.1 傳統的Spring的AOP
一個切點和一個通知的組合
1.3.2 基於Aspectj的AOP
-
AspectJ是一個基於Java語言的面向切面的AOP框架
-
Spring2.0以後新增了對AspectJ
-
@AspectJ 是AspectJ1.5新增功能,通過JDK5註解技術,允許直接在Bean類中定義切面
-
新版本Spring框架,建議使用AspectJ方式來開發AOP
1.3.3 Aspectj的切點表示式
-
語法:execution(表示式)
-
execution(<訪問修飾符>?<返回型別><方法名>(<引數>)<異常>)
-
public * *(..) ---檢索所有的public方法
-
execution(“* cn.uplooking.spring4.demo1.dao.*(..)”) ---只檢索當前包
-
execution(“* cn.uplooking.spring4.demo1.dao..*(..)”) ---檢索包及當前包的子包.
1.3.4 Aspect的增強型別
-
@Before 前置通知,相當於BeforeAdvice
@Before("execution(* com.uplooking.aop.UserDao.add*(..))")
public void beforeAdvice() {
System.out.println("前置通知....");
}
-
@AfterReturning 後置通知,相當於AfterReturningAdvice
@AfterReturning(value = "execution(* com.uplooking.aop.UserDao.add*(..))", returning = "ret")
public void afterReturningAdvice(String ret) {
System.out.println("後置通知.." + ret);
}
-
@Around 環繞通知,相當於MethodInterceptor
@Around("execution(* com.uplooking.aop.UserDao.add*(..))")
public void arounrAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("環繞通知前..");
pjp.proceed();
System.out.println("環繞通知後..");
}
-
@AfterThrowing丟擲通知,相當於ThrowAdvice
@AfterThrowing("execution(* com.uplooking.aop.UserDao.add*(..))")
public void throwAdvice() {
System.out.println("異常通知....");
}
-
@After 最終final通知,不管是否異常,該通知都會執行
@After(value = "execution(* com.uplooking.aop.UserDao.add*(..))")
public void afterAdvice() {
System.out.println("最終通知....");
}
1.4 AOP的程式設計(AspectJ)
1.4.1 匯入pom依賴
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
引入約束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--元件掃描器-->
<context:component-scan base-package="com.ma.aop"/>
<!--aop自動建立代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
1.4.2(前置通知)
1.4.2.1 要被增強的類
package com.ma.aop;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
public void addUser(){
System.out.println("新增使用者...");
}
public void deleteUser(){
System.out.println("刪除使用者...");
}
public void updateUser(){
System.out.println("修改使用者...");
}
public void selectUser(){
System.out.println("查詢使用者...");
}
}
1.4.2.2 定義切面
package com.ma.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* 切面 = 切點(切點表示式)+ 通知 (方法)
*/
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.ma.aop.UserDao.addUser())")
public void beforeAdvice(){
System.out.println("前置通知...");
}
}
1.4.2.3測試
import com.ma.aop.UserDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestUserDao {
@Autowired
private UserDao userDao;
@Test
public void testAdd(){
userDao.addUser();
}
}
1.4.2.5結果
1.4.3(後置通知)
1.4.3.1 要被增強的方法
package com.ma.aop;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
public void addUser(){
System.out.println("新增使用者...");
}
public String deleteUser(){
System.out.println("刪除使用者...");
return "已刪除!";
}
public void updateUser(){
System.out.println("修改使用者...");
}
public void selectUser(){
System.out.println("查詢使用者...");
}
}
1.4.3.2 定義切面
@AfterReturning(value = "execution(* com.ma.aop.UserDao.delete*())",returning = "ret")
public void afterReturningAdvice(String ret){
System.out.println("後置通知..."+"ret");
}
注意:可以得到返回值。
1.4.3.3 測試
@Test
public void testDelete(){
userDao.deleteUser();
}
1.4.3.4 結果
1.4.4(環繞通知)
1.4.4.1 要被增強的方法
package com.ma.aop;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
public void addUser(){
System.out.println("新增使用者...");
}
public String deleteUser(){
System.out.println("刪除使用者...");
return "已刪除!";
}
public void updateUser(){
System.out.println("修改使用者...");
}
public void selectUser(){
System.out.println("查詢使用者...");
}
}
1.4.4.2 定義切面
@Around("execution(* com.ma.aop.UserDao.update*())")
public void afterReturningAdvice(){
System.out.println("環繞通知...");
}
注意:此時環繞通知可以阻止目標方法的執行,需要對目標方法進行放行。
@Around("execution(* com.ma.aop.UserDao.update*())")
public void afterReturningAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("環繞通知前...");
pjp.proceed();
System.out.println("環繞通知後...");
}
1.4.4.3 測試
@Test
public void testUpdate(){
userDao.updateUser();
}
1.4.4.4 效果
1.4.5(異常通知)
1.4.5.1 要被增強的方法
package com.ma.aop;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
public void addUser(){
System.out.println("新增使用者...");
}
public String deleteUser(){
System.out.println("刪除使用者...");
return "已刪除!";
}
public void updateUser(){
System.out.println("修改使用者...");
}
public void selectUser(){
int i = 10/0;
System.out.println("查詢使用者...");
}
}
1.4.5.2 定義切面
@AfterThrowing("execution(* com.ma.aop.UserDao.selectUser())")
public void throwAdvice(){
System.out.println("異常通知...");
}
1.4.5.3 測試
@Test
public void testSelect(){
userDao.selectUser();
}
1.4.5.4 效果
2.代理模式
不使用物件的真實操作,使用我們自己建立的代理物件來操作
2.1 靜態代理
委託類和代理類共同實現同一個介面,在代理類中有委託類的物件。
1、公共介面
package com.ma.aop.agent;
public interface WindWomen {
String say();
}
2、委託類
package com.ma.aop.agent;
/**
* 委託類
*/
public class Pan implements WindWomen {
public String say() {
return "晚上十點小河邊見......";
}
}
3、代理類
package com.ma.aop.agent;
public class Wang implements WindWomen {
private WindWomen windWomen;//委託物件
public Wang(WindWomen windWomen){
/**
* 在代理的構造器中持有一個委託的物件
*/
this.windWomen = windWomen;
}
public String say() {
System.out.println("她找我讓我給你帶個話:");
System.out.println(windWomen.say());
System.out.println("記得啊!");
return null;
}
}
4、測試類
package com.ma.aop.agent;
public class AgentTest {
public static void main(String[] args) {
Pan pan = new Pan();
Wang wang = new Wang(pan);
wang.say();
}
}
5、效果
2.1動態代理
2.2.1 基於原生的JDK的動態代理
package com.ma.aop.agent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class AgentTest {
public static void main(String[] args) {
final Pan pan = new Pan();
WindWomen windWomen = (WindWomen) Proxy.newProxyInstance(AgentTest.class.getClassLoader(), Pan.class.getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("她找我讓我給你帶個話:");
Object object = method.invoke(pan,args);
System.out.println("記得啊!");
return object;
}
});
windWomen.say();
}
}
實現
效果
2.2.2 基於CGLIB的動態代理
因為原生的jdk的動態代理存在缺陷,代理類和委託類必須實現同一個介面
所以有個開源的動態代理框架出現了(CGLIB)
CGLIB不要求委託類必須實現介面:因為CGLIB底層是基於繼承實現的