1. 程式人生 > >Spring (AOP、靜態代理、動態代理)

Spring (AOP、靜態代理、動態代理)

1. AOP

1.1 AOP簡介

  1. AOP Aspect Oriented Programing 面向切面程式設計

  2. AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性程式碼(效能監視、事務管理、安全檢查、快取)

  3. Spring中的Aop是純Java來實現的,使用動態代理的方式增強程式碼

  4. Spring使用動態代理的機制是判斷委託類是否實現了介面,如果實現了介面則使用jdk的動態代理,如果沒有實現介面則使用cglib的動態代理

  5. 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

  1. AspectJ是一個基於Java語言的面向切面的AOP框架

  2. Spring2.0以後新增了對AspectJ

    切點表示式支援

  3. @AspectJ 是AspectJ1.5新增功能,通過JDK5註解技術,允許直接在Bean類中定義切面

  4. 新版本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底層是基於繼承實現的