Spring中的通知(Advice)和顧問(Advisor)


在Spring中,目前我學習了幾種增強的方式,和大家分享一下

之前的話:

1.AOP  (Aspect  Oriented Programming  面向切面編程)

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

面向對象編程是從【靜態角度】考慮程序的結構,而面向切面編程是從【動態角度】考慮程序運行過程。
AOP底層,就是采用【動態代理】模式實現的。采用了兩種代理:JDK動態代理和CGLIB動態代理。

基本術語(一些名詞):
(1)切面(Aspect)
切面泛指[*交叉業務邏輯*]。事務處理和日志處理可以理解為切面。常用的切面有通知(Advice)與顧問(Advisor)。實際就是對主業務邏輯的一種增強。

(2)織入(Weaving)
織入是指將切面代碼插入到目標對象的過程。代理的invoke方法完成的工作,可以稱為織入。

(3) 連接點(JoinPoint)
連接點是指可以被切面織入的方法。通常業務接口的方法均為連接點

(4)切入點(PointCut)
切入點指切面具體織入的方法
注意:被標記為final的方法是不能作為連接點與切入點的。因為最終的是不能被修改的,不能被增強的。

(5)目標對象(Target)
目標對象指將要被增強的對象。即包含主業務邏輯的類的對象。

(6)通知(Advice)
通知是切面的一種實現,可以完成簡單的織入功能。通知定義了增強代碼切入到目標代碼的時間點,是目標方法執行之前執行,還是執行之后執行等。切入點定義切入的位置,通知定義切入的時間。

(7)顧問(Advisor)
顧問是切面的另一種實現,能夠將通知以更為復雜的方式織入到目標對象中,是將通知包裝為更復雜切面的裝配器。

AOP是一種思想,而非實現
AOP是基於OOP,而又遠遠高於OOP,主要是將主要核心業務和交叉業務分離,交叉業務就是切面。例如,記錄日志和開啟事務。

一:前置增強和后置增強

源碼介紹:

1.User.java

package cn.zhang.entity;

public class User {
    private Integer id; // 用戶ID
    private String username; // 用戶名
    private String password; // 密碼
    private String email; // 電子郵件
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    
}
View Code

2.IDao.java

package cn.zhang.dao;
//定義接口
import cn.zhang.entity.User;

public interface IDao {
    //定義方法
    public void save(User user);
}
View Code

3.UserDao.java

package cn.zhang.dao.impl;
//實現接口
import cn.zhang.dao.IDao;
import cn.zhang.entity.User;

public class UserDao implements IDao  {

    @Override
    //實現方法
    public void save(User user) {
        System.out.println("save success!");        
    }

}
View Code

4.IUserBiz.java

package cn.zhang.biz;
//業務接口
import cn.zhang.entity.User;

public interface IUserBiz {
    //待處理的方法
     public void save(User user);
}
View Code

5.UserBiz.java

package cn.zhang.biz.impl;
//業務接口的實現類
import cn.zhang.biz.IUserBiz;
import cn.zhang.dao.IDao;
import cn.zhang.entity.User;

public class UserBiz implements IUserBiz {
    //引入IDao接口
    private IDao dao;
    @Override
    //實現方法
    public void save(User user) {
        dao.save(user);
    }
    //dao 屬性的setter訪問器,會被Spring調用,實現設值注入
    public IDao getDao() {
        return dao;
    }
    public void setDao(IDao dao) {
        this.dao = dao;
    }

}
View Code

6.LoggerAfter.java(后置增強)

package cn.zhang.aop;
//后置增強
import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class LoggerAfter implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object arg0, Method arg1, Object[] arg2,
            Object arg3) throws Throwable {
            System.out.println("后置增強代碼");        
    }

}
View Code

7.LoggerBefore.java(前置增強)

package cn.zhang.aop;
//前置增強
import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.springframework.aop.MethodBeforeAdvice;

public class LoggerBefore implements MethodBeforeAdvice {
    private static final Logger log = Logger.getLogger(LoggerBefore.class);
    @Override
    public void before(Method arg0, Object[] arg1, Object arg2)
            throws Throwable {
        log.info("前置內容AAA");    
        System.out.println("前置增強代碼");
    }

}
View Code

8.applicationContext.xml(Spring配置文件)

<?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"
    xmlns:p="http://www.springframework.org/schema/p"
    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-4.1.xsd
        ">
    <bean id="dao" class="cn.zhang.dao.impl.UserDao" />
    <bean id="biz" class="cn.zhang.biz.impl.UserBiz">
        <property name="dao" ref="dao"></property>
    </bean>
    <!-- 定義前置增強組件 -->
    <bean id="loggerBefore" class="cn.zhang.aop.LoggerBefore" />
    <!-- 定義后置增強組件 -->
    <bean id="loggerAfter" class="cn.zhang.aop.LoggerAfter" />
    <!-- 針對AOP的配置 -->
    <aop:config>
        <aop:pointcut id="pointcut"
            expression="execution(public void save(cn.zhang.entity.User))" />
        <!-- 將增強處理和切入點結合在一起,在切入點處插入增強處理,完成"織入" -->
        <aop:advisor pointcut-ref="pointcut" advice-ref="loggerBefore" />
        <aop:advisor pointcut-ref="pointcut" advice-ref="loggerAfter" />
    </aop:config>

</beans>  
View Code

當然,針對AOP的配置也可以使用代理對象 ProxyFactoryBean 代理工廠bean來實現,在測試類中:IUserBiz biz=(IUserBiz)ctx.getBean("serviceProxy");

<!-- 代理對象 ProxyFactoryBean 代理工廠bean -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="targetName" value="biz"></property>
        <property name="interceptorNames" value="loggerBefore,loggerAfter"></property>
    </bean>
View Code

9.MyTest.java

package cn.zhang.test;
//測試類
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.zhang.biz.IUserBiz;
import cn.zhang.entity.User;

public class MyTest {
public static void main(String[] args) {
    
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    IUserBiz biz=(IUserBiz)ctx.getBean("biz");
    User user=new User();
    biz.save(user);
    System.out.println("success!");
}
}
View Code

10.log4j.properties(日志的配置文件)

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=info, stdout
View Code

當然,別忘了引入我們需要的jar包啊!

常用的jar:

 

二:異常拋出增強和環繞增強

源碼介紹:

1.User.java

package cn.zhang.entity;

public class User {
    private Integer id; // 用戶ID
    private String username; // 用戶名
    private String password; // 密碼
    private String email; // 電子郵件    
    
    public User() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public User(Integer id, String username, String password, String email) {
        super();
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
    }

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    
}
View Code

2.UserService.java

package cn.zhang.service;

public class UserService {

    public void delete() {
        //int i = 5 / 0;//制造一個錯誤,用於測試異常拋出增強
        System.out.println("delete success!");
    }
}
View Code

3.ErrorLog.java(異常拋出增強)

package cn.zhang.aop;
//異常拋出增強
import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.springframework.aop.ThrowsAdvice;

public class ErrorLog implements ThrowsAdvice {
    private static final Logger log = Logger.getLogger(ErrorLog.class);
    public void afterThrowing(Method method, Object[] args, Object target,
            RuntimeException e){
        log.error(method.getName() + " 方法發生異常:" + e);
    }
}
View Code

4.AroundLog(環繞增強)

package cn.zhang.aop;
//環繞增強
import java.lang.reflect.Method;
import java.util.Arrays;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;

public class AroundLog implements MethodInterceptor {
    
    private static final Logger log = Logger.getLogger(AroundLog.class);
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
    
        Object target=invocation.getThis();//獲取被代理對象
        Method method = invocation.getMethod();//獲得被代理方法
        Object[] args = invocation.getArguments();//獲得方法參數
        
        System.out.println("調用"+target+"的"+method.getName()+"方法。方法參數:"+Arrays.toString(args));
        Object result;//調用目標方法,獲取目標方法返回值
        try {
            result = invocation.proceed();
            System.out.println("調用" + target + "的" + method.getName()
                    + "方法。方法返回值:" + result);
            return result;
        } catch (Exception e) {
            log.error(method.getName()+"方法發生異常:"+e);
            throw e;
        }
        
    }

}
View Code

5.applicationContext.xml(Spring配置文件)

<?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"
    xmlns:p="http://www.springframework.org/schema/p"
    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-4.1.xsd
        ">
    <bean id="service" class="cn.zhang.service.UserService" />
    <!-- 異常拋出增強 -->
    <!-- <bean id="error" class="cn.zhang.aop.ErrorLog"/> -->
    <!-- 環繞增強 -->
    <bean id="error" class="cn.zhang.aop.AroundLog"/>
    
    <aop:config>
        <aop:pointcut expression="execution(public void delete())"
            id="pointcut" />
        <aop:advisor advice-ref="error" pointcut-ref="pointcut" />
    </aop:config>
</beans> 
View Code

6.MyTest.java

package cn.zhang.test;
//測試類
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.zhang.service.UserService;

public class MyTest {
    public static void main(String[] args) {
        
        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        UserService service = (UserService) ctx.getBean("service");
        try {
            service.delete();
        } catch (Exception e) {
            System.out.println("錯誤了");
        }
        System.out.println("success!");
    }
}
View Code

三:注解增強方式實現前置增強和后置增強

源碼介紹:

1.UserService.java

package cn.service;
//業務處理類
public class UserService {
    //方法
    public void delete() {
        System.out.println("delete success!");
    }
}
View Code

2.AnnotationAdvice.java(注解增強)

package cn.aop;
//注解增強
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationAdvice {
    
    // 定義前置增強
    @Before("execution(* cn.service.UserService.*(..))")
    public void before(JoinPoint jp) {
        System.out.println("調用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法,參數:"+jp.getArgs()+",參數個數:"+jp.getArgs().length);
        System.out.println("before");
    }
    // 定義后置增強
    @AfterReturning(pointcut="execution(* cn.service.UserService.*(..))",returning="returnValue")
    public void afterReturning(JoinPoint jp,Object returnValue) { 
        System.out.println("調用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法,參數:"+jp.getArgs()+",返回值為:"+returnValue);
        System.out.println("after");
    }
}
View Code

注:

java.lang.Object[] getArgs():獲取連接點方法運行時的入參列表
Signature getSignature() :獲取連接點的方法簽名對象
java.lang.Object getTarget() :獲取連接點所在的目標對象
java.lang.Object getThis() :獲取代理對象本身

3.applicationContext.xml(Spring配置文件)

<?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"
    xmlns:p="http://www.springframework.org/schema/p"
    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-4.1.xsd
        ">
        
    <bean id="service" class="cn.service.UserService" />

    <bean id="error" class="cn.aop.AnnotationAdvice" />

    <!-- 針對AOP的配置 -->
    <aop:aspectj-autoproxy />
</beans> 
View Code

4.MyTest.java

package cn.test;
//注解增強測試
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.service.UserService;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService biz=(UserService)ctx.getBean("service");
        biz.delete();
        System.out.println("success!");
    }

}
View Code

四: 顧問(Advisor)實現前置增強

通知Advice是Spring提供的一種切面(Aspect)。但其功能過於簡單,只能
將切面織入到目標類的所有目標方法中,無法完成將切面織入到指定目標方法中。

顧問Advisor是Spring提供的另一種切面。其可以完成更為復雜的切面織入功能。PointcutAdvisor是顧問的一種,可以指定具體
的切入點。顧問將通知進行了包裝,會根據不同的通知類型,在不同的時間點,將切面織入到不同的切入點。
PointcutAdvisor接口有兩個較為常用的實現類:
*:NameMatchMethodPointcutAdvisor 名稱匹配方法切入點顧問
*:RegexpMethodPointcutAdvisor 正則表達式匹配方法切入點顧問
<property name="pattern" value=".*do.*"></property> 表示方法全名(包名,接口名,方法名)
運算符 名稱 意義
. 點號 表示任意單個字符
+ 加號 表示前一個字符出現一次或者多次
* 星號 表示前一個字符出現0次或者多次
=====默認Advisor自動代理生成器
DefaultAdvisorAutoProxyCreator
=====BeanName自動代理生成器
BeanNameAutoProxyCreator

實例:

 

源碼介紹:

1.ISomeService.java

package service;
//接口
public interface ISomeService {
    //待實現的方法
   public void doFirst();
   public void doSecond();
}
View Code

2.SomeServiceImpl.java

package service;
//接口實現類
public class SomeServiceImpl implements ISomeService {
    //實現接口定義的方法
    @Override
    public void doFirst() {
        System.out.println("方法A");
    }

    @Override
    public void doSecond() {
        System.out.println("方法B");    
    }

}
View Code

3.MyMethodBeforeAdvice.java

package aop;
//前置增強
import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class MyMethodBeforeAdvice implements MethodBeforeAdvice{
    
    @Override
    public void before(Method method, Object[] args, Object target)
            throws Throwable {
            System.out.println("目標方法執行之前執行");
    }

}
View Code

4.applicationContext.xml(Spring配置文件)

<?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"
    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">

    <!-- 目標對象 -->
    <bean id="someService" class="service.SomeServiceImpl"></bean>

    <!-- 切面:通知 -->
    <bean id="beforeAdvice" class="aop.MyMethodBeforeAdvice"></bean>
    
    <!-- *********************************************** -->
    
    <!-- 1.*:NameMatchMethodPointcutAdvisor 名稱匹配方法切入點顧問 -->
    <!-- 切面:顧問 顧問(Advisor)要包裝通知(Advice) -->    
    <bean id="beforeAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"> 
        <property name="advice" ref="beforeAdvice"></property> 
        <!--指定需要增強的方法:這里是doFirst()方法,而doSecond()方法則不會增強   -->
        <property name="mappedName" value="doFirst"></property>
        <!-- 也可以使用mappedNames指定多個方法
        <property name="mappedNames" value="doFirst,doSecond"></property>
        -->
    </bean>
        
    <!-- *********************************************** -->

    <!-- 2.*:RegexpMethodPointcutAdvisor 正則表達式匹配方法切入點顧問 -->
    <!-- 切面: 顧問 顧問(Advisor)要包裝通知(Advice) -->
    <!-- <bean id="beforeAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="beforeAdvice"></property>
        <property name="pattern" value=".*doF.*t"></property>
    </bean> -->
    
    <!-- *********************************************** -->
    
    <!-- 代理工廠Bean -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="someService"></property>
        <property name="interceptorNames" value="beforeAdvisor"></property>
    </bean>
</beans>
View Code

5.MyTest.java

package test;
//對顧問(Advisor)測試
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.ISomeService;

public class MyTest {
    @Test
    public void testOne(){
        ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
        ISomeService service = (ISomeService)ctx.getBean("serviceProxy");
        service.doFirst();
        service.doSecond();
    }
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM