1. 程式人生 > 實用技巧 >Why系列:0.1 + 0.2 != 0.3

Why系列:0.1 + 0.2 != 0.3

AOP

  • AOP面向切面程式設計,生成目標方法所屬類的代理類。代理類和目標類的關係:代理類繼承目標類,並重載了目標類的方法。代理類過載方法體里加入了切面業務邏輯和目標類方法的呼叫。

  • 使用者如何使用:從容器中獲取目標類,實際上是獲取代理類的例項, 代理類例項呼叫過載了父類的方法,就實現了AOP。

  • AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術.AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

  • 在spring AOP中業務邏輯僅僅只關注業務本身,將日誌記錄,效能統計,安全控制,事務處理,異常處理等程式碼從業務邏輯程式碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候不影響業務邏輯的程式碼。

相關注解介紹:

@Aspect:作用是把當前類標識為一個切面供容器讀取
@Pointcut:Pointcut是植入Advice的觸發條件。每個Pointcut的定義包括2部分,一是表示式,二是方法簽名。方法簽名必須是 public及void型。可以將Pointcut中的方法看作是一個被Advice引用的助記符,因為表示式不直觀,因此我們可以通過方法簽名的方式為 此表示式命名。因此Pointcut中的方法只需要方法簽名,而不需要在方法體內編寫實際程式碼。
@Around:環繞增強,相當於MethodInterceptor
@AfterReturning:後置增強,相當於AfterReturningAdvice,方法正常退出時執行
@Before:標識一個前置增強方法,相當於BeforeAdvice的功能,相似功能的還有
@AfterThrowing:異常丟擲增強,相當於ThrowsAdvice
@After: final增強,不管是丟擲異常或者正常退出都會執行

依賴

  • spring-aop:AOP核心功能,例如代理工廠等等
  • aspectjweaver:簡單理解,支援切入點表示式等等
  • aspectjrt:簡單理解,支援aop相關注解等等
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.8.RELEASE</version>
</dependency>
<!--aspectj支援-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.5</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

示例

  • 註解表示式

    • execution表示式第一個表示匹配任意的方法返回值,..(兩個點)表示零個或多個,第一個..表示module包及其子包,第二個表示所有類, 第三個*表示所有方法,第二個..表示方法的任意引數個數
  • JoinPoint :方法中的引數JoinPoint為連線點物件,它可以獲取當前切入的方法的引數、代理類等資訊,因此可以記錄一些資訊,驗證一些資訊等;

  • main方法上要先開啟@EnableAspectJAutoProxy

  • 對於非@Component注入的,可以用@Bean將代理物件注入

/aop代理物件
Object aThis = joinPoint.getThis();
//被代理物件
Object target = joinPoint.getTarget();

package com.aspectj.test.advice;
 
import java.util.Arrays;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
@Aspect
public class AdviceTest {
    @Around("execution(* com.abc.service.*.many*(..))")
    public Object process(ProceedingJoinPoint point) throws Throwable {
        System.out.println("@Around:執行目標方法之前...");
        //訪問目標方法的引數:
        Object[] args = point.getArgs();
        if (args != null && args.length > 0 && args[0].getClass() == String.class) {
            args[0] = "改變後的引數1";
        }
        //用改變後的引數執行目標方法
        Object returnValue = point.proceed(args);
        System.out.println("@Around:執行目標方法之後...");
        System.out.println("@Around:被織入的目標物件為:" + point.getTarget());
        return "原返回值:" + returnValue + ",這是返回結果的字尾";
    }
    
    @Before("execution(* com.abc.service.*.many*(..))")
    public void permissionCheck(JoinPoint point) {
        System.out.println("@Before:模擬許可權檢查...");
        System.out.println("@Before:目標方法為:" + 
                point.getSignature().getDeclaringTypeName() + 
                "." + point.getSignature().getName());
        System.out.println("@Before:引數為:" + Arrays.toString(point.getArgs()));
        System.out.println("@Before:被織入的目標物件為:" + point.getTarget());
    }
    
    @AfterReturning(pointcut="execution(* com.abc.service.*.many*(..))", 
        returning="returnValue")
    public void log(JoinPoint point, Object returnValue) {
        System.out.println("@AfterReturning:模擬日誌記錄功能...");
        System.out.println("@AfterReturning:目標方法為:" + 
                point.getSignature().getDeclaringTypeName() + 
                "." + point.getSignature().getName());
        System.out.println("@AfterReturning:引數為:" + 
                Arrays.toString(point.getArgs()));
        System.out.println("@AfterReturning:返回值為:" + returnValue);
        System.out.println("@AfterReturning:被織入的目標物件為:" + point.getTarget());
        
    }
    
    @After("execution(* com.abc.service.*.many*(..))")
    public void releaseResource(JoinPoint point) {
        System.out.println("@After:模擬釋放資源...");
        System.out.println("@After:目標方法為:" + 
                point.getSignature().getDeclaringTypeName() + 
                "." + point.getSignature().getName());
        System.out.println("@After:引數為:" + Arrays.toString(point.getArgs()));
        System.out.println("@After:被織入的目標物件為:" + point.getTarget());
    }
}

使用annotation程式碼:

package com.trip.demo;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
 
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface SMSAndMailSender {
    /*簡訊模板String格式化串*/
    String value() default "";
 
    String smsContent() default "";
 
    String mailContent() default "";
    /*是否啟用傳送功能*/
    boolean isActive() default true;
    /*主題*/
    String subject() default "";
}
@Aspect
@Component("smsAndMailSenderMonitor")
public class SMSAndMailSenderMonitor {
 
    private Logger logger = LoggerFactory.getLogger(SMSAndMailSenderMonitor.class);
 
 
    /**
     * 在所有標記了@SMSAndMailSender的方法中切入
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value="@annotation(com.trip.demo.SMSAndMailSender)", returning="result")//有註解標記的方法,執行該後置返回
    public void afterReturning(JoinPoint joinPoint , Object result//註解標註的方法返回值) {
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = ms.getMethod();
        boolean active = method.getAnnotation(SMSAndMailSender.class).isActive();
        if (!active) {
            return;
        }
        String smsContent = method.getAnnotation(SMSAndMailSender.class).smsContent();
        String mailContent = method.getAnnotation(SMSAndMailSender.class).mailContent();
        String subject = method.getAnnotation(SMSAndMailSender.class).subject();
       
    }
 
    
 
    /**
     * 在丟擲異常時使用
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value="@annotation(com.trip.order.monitor.SMSAndMailSender)",throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex//註解標註的方法丟擲的異常) {
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = ms.getMethod();
        String subject = method.getAnnotation(SMSAndMailSender.class).subject();
        
    }
 
}

//實體類中使用該註解標註方法

@Service("testService ")
public class TestService {
 
 
    @Override
    @SMSAndMailSender(smsContent = "MODEL_SUBMIT_SMS", mailContent =     
    "MODEL_SUPPLIER_EMAIL", subject = "MODEL_SUBJECT_EMAIL")
    public String test(String param) {
        return "success";
    }
}

@EnableAspectJAutoProxy

  • proxyTargetClass配置
    在Spring中,動態代理有2種實現方式:
    基於CGLIB來實現
    基於Java原生的Proxy實現,這種方式原類必須要定義介面。
    這個引數就是表示動態代理實現方式,如果值設定true,表示需要代理類都基於CGLIB來實現;預設情況下值是設定成false表示如果原類如果定義了介面則通過Proxy實現否則基於CGLIB來實現。