一、代理

Aop底層使用代理實現

1、代理作用

  1. 可以隱藏代理目標類的具體實現
  2. 在不修改目標類代碼情況下對目標進行增強

2、分類

分為靜態代理與動態代理

3、靜態代理實現過程

  1. 定義:若代理類在目標運行前就已經實現,那麼這種代理方式就是靜態代理,這種情況的代理方法通常是我們在java代碼中定義的。通常情況下,代理類與目標類實現同一介面,或是派生自同一父類
  2. 實現步驟:
  • 創建主業務介面(Serivce)

//主業務介面 public interface SomeService { String doSome(); }

  • 創建目標類實現主業務介面(SerivceImpl)

//目標類 public class SomeServiceImpl implements SomeService {
@Override
public String doSome() {
return "abc";
} }

  • 創建代理類(增強目標類)實現主業務介面(ServicePorxy)

//靜態代理類,要求與目標類實現相同介面
public class ServiceProxy implements SomeService {
SomeService target;
public ServiceProxy() {
super();
}
public ServiceProxy(SomeService target) {
super();
this.target = target;
}

@Override
public String doSome() {
return target.doSome().toUpperCase();
}
}

  • 測試類(Test)

//測試類
public class SomeTest {

public static void main(String[] args) {
//定義目標對象
SomeService target = new SomeServiceImpl();
//定義目標對象的代理對象
SomeService proxy = new ServiceProxy(target);//增強目標類方法
String result = proxy.doSome();
System.out.println(result);
}
}

4、動態代理

  1. 定義:代理類在程序運行時創建的代理方式被稱為動態代理。也就是說,這種情況下,代理類並不是在ava代碼中定義的,而是運行時根據Java代碼「指示」動態生成的。
  2. 分類:
  • JDK動態代理:如果目標對象實現介面,採用jdk動態代理
  • CGLIB動態代理:如果目標對象沒有實現介面,必須採用jdk動態代理
  1. JDK動態代理實現

//測試類
public class SomeTest {

public static void main(String[] args) {
//定義目標對象
SomeService target = new SomeServiceImpl();
//定義目標對象的代理對象
SomeService proxy = (SomeService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),//目標類的類載入器
target.getClass().getInterfaces(),//目標類實現所有介面
new InvocationHandler() {//調用處理器
//proxy:代理對象
//method:目標方法
//args:目標方法參數
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String result = (String) method.invoke(target, args);
return result.toUpperCase();
}
});
String result1 = proxy.doSome();
System.out.println(result1);
}
}

  1. CGLIB動態代理實現()

先導包,把以前的包刪掉只導入:這個包

cglib-nodep-3.1.jar

  • Cglib動態代理工廠

//Cglib動態代理工廠
public class CglibProxyFactory implements MethodInterceptor {

private SomeServiceImpl target;

public CglibProxyFactory() {
super();
// TODO 自動生成的構造函數存根
}

public CglibProxyFactory(SomeServiceImpl target) {
super();
this.target = target;
}

//創建 Cglib動態代理對象的方法
public SomeServiceImpl proxycCreator(){
//創建增強器
Enhancer en = new Enhancer();
//指定父類,指定目標類
en.setSuperclass(SomeServiceImpl.class);
//指定回調對象
en.setCallback(this);
//從當前代理對象
return (SomeServiceImpl) en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String result = (String) method.invoke(target, args);
return result.toUpperCase();
}
}

  • 目標類

//目標類
public class SomeServiceImpl {

public SomeServiceImpl() {
System.out.println("初始化");
}

public String doSome() {
return "abc";

}

  • 測試類

//測試類
public class SomeTest {

public static void main(String[] args) {
//定義目標對象
SomeServiceImpl target = new SomeServiceImpl();
//定義目標對象的代理對象
SomeServiceImpl proxy = new CglibProxyFactory(target).proxycCreator();
String result1 = proxy.doSome();
System.out.println(result1);
}
}

二、Aop

  1. 面向切面編程:

就是將交叉業務邏輯封裝成切面,利用Aop功能將切面織入到主業務邏輯中

所謂交叉業務邏輯就是:通用的、與主業務邏輯無關的代碼,如:安全檢查,日誌、事務

2.作用:

若不使用Aop則會造成交叉業務邏輯與主業務邏輯混合在一起,這樣會是的主業務邏輯

變得混雜不清,並且不容易後期維護。

3.基本術語介紹

  1. 切面:泛指交叉業務邏輯,如事務處理,日誌處理都可以理解為切面,常用的切面有通知顧問。實際就是對主業務邏輯的一種增強
  2. 織入:指將代碼插入到目標對象的過程
  3. 連接點:指切面可以織入的位置
  4. 切入點:指切面具體織入的位置
  5. 通知:通知是切面的一種實現,可以完成簡單的織入功能(織入就是在這裡實現的),通知定義了增強代碼切入目標代碼的時間點,是目標方法執行之前執行,還是之後執行,通知類型不同,切入時間也不同,
  6. 顧問:顧問是另一種切面實現,能夠將通知以更為複雜的方式織入到目標對象中,是將通知裝配為更複雜切面的裝配器,不僅可以指定切入時間,還可以具體指定切入點

4.通知的用法步驟 (基於Schema-based方式)

  1. 定義目標類
  2. 定義通知類
  3. .註冊目標類
  4. 註冊通知切面
  5. 註冊代理工廠Bean類對象ProxyFactoryBean
  6. 客戶端訪問動態代理對象

5.前置通知:

執行的時機點是在目標方法執行之前執行,

1、創建通知類

//切面前置通知
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
/**
* method 目標方法
* args 目標方法
* target 目標對象
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知的before()方法執行!");
}
}

2、在實現類中添加方法

public class SomeServiceImpl implements SomeService {

public SomeServiceImpl() {
System.out.println("初始化");
}
@Override
public void doSome() {
System.out.println("doSome執行。。。");
}
@Override
public String doOther() {
System.out.println("doOther執行。。。");
return "love";
}
}

3、applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 註冊目標類 -->
<bean id="someServiceImpl" class="com.sxt.service.impl.SomeServiceImpl"></bean>
<!-- 註冊切面,前置通知 -->
<bean id="myMethodBeforeAdvice" class="com.sxt.aspects.MyMethodBeforeAdvice"></bean>
<!-- 註冊代理 -->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 指定目標對象 -->
<property name="target" ref="someServiceImpl"></property>
<!-- 指定目標類實現所有介面 -->
<property name="interfaces" value="com.sxt.service.SomeService"></property>
<!-- 制定切面 -->
<property name="interceptorNames" value="myMethodBeforeAdvice"></property>
</bean>
</beans>

4、測試類

public class SomeTest {
@Test
public void test2(){
//創建容器對象,ApplicationContext容器初始化時,所有容器的bean創建完成
//缺點:佔用內存
ApplicationContext cs =
new ClassPathXmlApplicationContext("applicationContext.xml");
//從容器中獲取bean
SomeService service = cs.getBean("proxyFactoryBean", SomeService.class);
service.doSome();
String result = service.doOther();
System.out.println(result);
}
}

5、結果

  1. 後置通知:

執行的時機點是在目標方法執行之後執行,

1、創建通知類

//切面,後置通知
public class MyAfterReturningAdvice implements AfterReturningAdvice {
/**
* returnValue 目標方法返回值
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("後置通知的afterReturning()方法執行!returnValue:"+returnValue);
}
}

2、實現類中方法與前置一樣

3、在applicationContext.xml添加後置通知bean標籤

<!-- 註冊切面,後置通知 -->
<bean id="myAfterReturningAdvice" class="com.sxt.aspects.MyAfterReturningAdvice"></bean>
並且
<!-- 指定切面 -->
<property name="interceptorNames" value="myAfterReturningAdvice"></property>

4、測試類:與前置一樣

5、結果

6、目標返回值可以編譯(變成大寫)

@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("後置通知的afterReturning()方法執行!returnValue:"+returnValue);
if(returnValue!=null){
System.out.println("後置通知的afterReturning()方法執行!returnValue:"+((String) returnValue).toUpperCase());
}
}

7、結果

  1. 環繞通知:

執行的時機點是在目標方法執行之前之後都有執行

  1. 創建通知類

//切面,環繞通知
public class MyMethodInterceptor implements MethodInterceptor {
/**
* invoction方法調用器
*/
@Override
public Object invoke(MethodInvocation invoction) throws Throwable {
System.out.println("環繞通知,目標方法執行之前!");
//調用執行目標方法
Object result = invoction.proceed();
if(result!=null){
result = ((String)result).toUpperCase();
}
System.out.println("環繞通知,目標方法執行之後!");
return result;
}
}

  1. 在applicationContext.xml添加環繞通知bean標籤

<!-- 註冊切面,環繞通知 -->
<bean id="myMethodInterceptor" class="com.sxt.aspects.MyMethodInterceptor"></bean>
<!-- 指定切面,放到代理中-->
<property name="interceptorNames" value="myMethodInterceptor"></property>

  1. 實現類,創造異常
  1. 測試類:與前置一樣
  2. 結果
  1. 異常通知:

  1. 創建通知類

//通知類
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception ex){
System.out.println("異常通知執行!");
}
}

  1. 在applicationContext.xml添加異常通知bean標籤

<!-- 註冊切面,異常通知 -->
<bean id="myThrowsAdvice" class="com.sxt.aspects.MyThrowsAdvice"></bean>
<!-- 指定切面,放到代理中-->
<property name="interceptorNames" value="myThrowsAdvice"></property>

  1. 測試類:與前置一樣
  2. 結果

三、基於AspectJ方式

  1. AspectJ對AOP的實現 對於AOP這種編程思想,

很多框架都進行了實現。Spring就是其中之一,可 以完成面向切面編程。然而,AspectJ也實現了AOP的功能,且其實現方式更為簡捷, 使用更為方便,而且還支持註解式開發。所以,Spring又將AspectJ的對於AOP的實 現也引入到了自己的框架中。

在Spring中使用AOP開發時,一般使AspectJ的實現方式。

  1. AspectJ的通知類型 AspectJ中常用的通知有五種類型:

前置通知

後置通知

環繞通知

異常通知

最終通知:無論程序執行是否正常,該通知都會執行。類似於 try..catch中finally代碼塊

  1. AspectJ切入點表達式

execution(

[Modifiers-pattern] 訪問許可權類型

ret-type-pattern 返回值類型

[Declaring-type-pattern] 全限定性類名

name-pattern 方法名(參數名)

[Throws-pattern] 拋出異常類型

)

execution(* *..service.*.*(..)) 第一個*代表返回值類型,第二個*..代表多級包 ,service代表子包,第三個.*代表類名/介面名,第四個.*代表方法名,最後(..)代表方法參數

指定所有包下的serivce子包下所有類(介面)中的所有方法為切入點

  1. 搭建AspectJ開發環境

導入價包

  1. AspectJ對Aop實現有兩種方式
  • 註解方式
  • XML(配置)方式
  1. 註解方式

  1. 前置通知:

執行的時機點是在目標方法執行之前執行,

  1. 創建通知類

//切面
@Aspect //表明當前類是一個切面
public class MyAspects {
//該註解表明該方法是前置通知
@Before("execution(* *..service.*.doSome(..))")
public void before(){
System.out.println("前置通知方式執行。。。");
}
}

  1. 實現類

public class SomeServiceImpl implements SomeService {

public SomeServiceImpl() {
System.out.println("初始化");
}
@Override
public void doSome() {
//System.out.println("doSome執行。。。"+1/0);
System.out.println("doSome執行。。。");
}
@Override
public String doOther() {
System.out.println("doOther執行。。。");
return "love";
}
}

  1. applicationContext.xml

<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 註冊目標類 -->
<bean id="someServiceImpl" class="com.sxt.service.impl.SomeServiceImpl"></bean>
<!-- 註冊切面,前置通知 -->
<bean id="myAspects" class="com.sxt.aspects.MyAspects"></bean>
<!-- 註冊自動代理 -->
<aop:aspectj-autoproxy/>
</beans>

  1. 測試類

@Test
public void test(){
//創建容器對象,ApplicationContext容器初始化時,所有容器的bean創建完成
//缺點:佔用內存
ApplicationContext cs =
new ClassPathXmlApplicationContext("applicationContext.xml");
//從容器中獲取bean
SomeService service = cs.getBean("someServiceImpl", SomeService.class);
service.doSome();
String result = service.doOther();
System.out.println(result);
}
}

  1. 結果

2、後置通知

執行的時機點是在目標方法執行之前執行,

  1. 創建通知類

//該註解表明該方法是後置通知
@AfterReturning("execution(* *..service.*.doSome(..))")
public void afterReturning(){
System.out.println("後置通知方式執行。。。");
}

  1. 實現類、applicationContext.xml、測試類 不變
  2. 結果

3、環繞通知

執行的時機點是在目標方法執行之前與之後都會執行

  1. 創建通知類

//該註解表明該方法是環繞通知
@Around("execution(* *..service.*.doOther(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("環繞通知方式之前。。。");
String result = (String)pjp.proceed();
if(result!=null){
result = result.toUpperCase();
}
System.out.println("環繞通知方式之hou。。。");
return result;
}
}

  1. 實現類、applicationContext.xml、測試類 不變
  2. 結果

4、異常通知

  1. 創建通知類

//該註解表明該方法是環繞通知
@AfterThrowing("execution(* *..service.*.doSome(..))")
public void throwing(){
System.out.println("異常方法執行。。。");
}

  1. 實現類

public void doSome() {
System.out.println("doSome執行。。。"+1/0);
//System.out.println("doSome執行。。。");
}

  1. applicationContext.xml、測試類 不變
  2. 結果
  1. 獲取異常信息需要 throwing

//該註解表明該方法是異常通知
@AfterThrowing(value="execution(* *..service.*.doSome(..))",throwing="ex")
public void throwing(Exception ex){
System.out.println("異常方法執行,異常信息為"+ex);
}
}

  1. 結果

5、最終通知:

無論程序執行是否正常,該通知都會執行。類似於 try..catch中finally代碼塊

  1. 創建通知類

//該註解表明該方法是最終通知
@After("execution(* *..service.*.doSome(..))")
public void after(){
System.out.println("最終方法執行");
}

  1. 實現類

public void doSome() {
System.out.println("doSome執行。。。"+1/0);
//System.out.println("doSome執行。。。");
}

  1. applicationContext.xml、測試類 不變
  2. 結果

7、配置方式

  1. 前置通知

  1. 創建通知類

public void before(){
System.out.println("前置通知方式執行。。。");
}

  1. applicationContext.xml

<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 註冊目標類 -->
<bean id="someServiceImpl" class="com.sxt.service.impl.SomeServiceImpl"></bean>
<!-- 註冊切面,通知 -->
<bean id="myAspects" class="com.sxt.aspects.MyAspects"></bean>
<!-- Aop配置 -->
<aop:config>
<!-- 定義切入點 -->
<aop:pointcut expression="execution(* *..service.*.doSome(..))" id="doSomePC"/>
<aop:pointcut expression="execution(* *..service.*.doOther(..))" id="doOtherPc"/>
<aop:aspect ref="myAspects">
<aop:before method="before" pointcut-ref="doSomePC"/>
</aop:aspect>
</aop:config>
</beans>

  1. 實現類測試類 不變
  2. 結果
  1. 前置通知(帶參)

  1. 創建通知類

public void before(JoinPoint jp){
System.out.println("前置通知方式執行。。。jp="+jp);
}

  1. 修改applicationContext.xml

<aop:aspect ref="myAspects">
<!-- <aop:before method="before" pointcut-ref="doSomePC"/> -->
<aop:before method="before(org.aspectj.lang.JoinPoint)" pointcut-ref="doSomePC"/>
</aop:aspect>

  1. 結果
  1. 後置通知(帶參)

  1. 創建通知類

public void afterReturning(Object result){
System.out.println("後置通知方式執行。。。返回值"+result);
}

  1. 修改applicationContext.xml

<aop:aspect ref="myAspects">
<aop:after-returning method="afterReturning(java.lang.Object)" pointcut-ref="doOtherPc" returning="result"/>
</aop:aspect>

  1. 結果
  1. 環繞通知

  1. 創建通知類

public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("環繞通知方式之前。。。");
String result = (String)pjp.proceed();
if(result!=null){
result = result.toUpperCase();
}
System.out.println("環繞通知方式之後。。。");
return result;

  1. 修改applicationContext.xml

<aop:aspect ref="myAspects">
<aop:around method="around" pointcut-ref="doOtherPc"/>
</aop:aspect>

  1. 結果

  1. 異常通知

  1. 創建通知類

public void throwing(Exception ex){
System.out.println("異常方法執行,異常信息為"+ex);
}

  1. 修改applicationContext.xml

<aop:aspect ref="myAspects">
<aop:after-throwing method="throwing(java.lang.Exception)" pointcut-ref="doSomePC" throwing="ex"/>
</aop:aspect>

  1. 結果
  1. 最終通知

(有異常)

  1. 創建通知類

public void after(){
System.out.println("最終方法執行");

  1. 實現類

public SomeServiceImpl() {
System.out.println("初始化");
}
@Override
public void doSome() {
System.out.println("doSome執行。。。"+1/0);
//System.out.println("doSome執行。。。");
}
@Override
public String doOther() {
System.out.println("doOther執行。。。");
return "love";
}

  1. 修改applicationContext.xml

<aop:aspect ref="myAspects">
<aop:after method="after" pointcut-ref="doSomePC"/>
</aop:aspect>

  1. 結果

(無異常)

  1. 實現類

public SomeServiceImpl() {
System.out.println("初始化");
}
@Override
public void doSome() {
//System.out.println("doSome執行。。。"+1/0);
System.out.println("doSome執行。。。");
}
@Override
public String doOther() {
System.out.println("doOther執行。。。");

2.結果


推薦閱讀:
相关文章