Spring AOP

  在前面的文章中已經和大家分享過關於spring IOC的知識,已經通過他的實現機制。我們都知道spring的兩大核心:AOP(面向切面)和IOC(控制反轉),本篇我們就一起學習一下AOP的知識的。

  這裏分享一個問題?當我們軟件開發完成後,需要給每一個方法添加操作日誌,我們怎麼操作呢?我想最簡單的方法就是在每一個方法的開始前將我們的日誌邏輯加入,當然這是最直接的一種方法,但是他的缺點也是很明細,如果我們的方法有很多,添加這個日誌邏輯就需要很多的工作量,顯然這是一種不可取的方式。如何更好的解決這個問題呢?spring很好的幫我們處理了這個難點,通過切面編程,我們可以在我們需要的切面添加相應的業務邏輯已達到我們需要的效果。

  接下來開始我們的內容,AOP的實現藉助於JAVA的動態代理知識,我先通過動態代理的方式爲大家介紹一下AOP的實現原理,以便大家更好的理解。

  每一個動態代理類都必須要實現InvocationHandler這個接口,並且每個代理類的實例都關聯到了一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發爲由InvocationHandler這個接口的 invoke 方法來進行調用。我們來看看InvocationHandler這個接口的唯一一個方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

  我們看到這個方法一共接受三個參數,那麼這三個參數分別代表什麼呢?

proxy:  指代我們所代理的那個真實對象
method:  指代的是我們所要調用真實對象的某個方法的Method對象
args:  指代的是調用真實對象某個方法時接受的參數

  這裏我以一個添加日誌需求爲因,實現一個這個過程:

public class LogInterception implements InvocationHandler {
    
    private Object target;
    
    public void setTarget(Object target) {
        this.target = target;
    }

    private void beforeMethod(){
        System.out.println("切面添加日誌開始");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        beforeMethod();
        Object result = method.invoke(target, args);
        afterMethod();
        return result;
    }
    
    private void afterMethod(){
        System.out.println("切面添加日誌結束");
    }

}

  我們寫好了代理,下面我們看一下如何將其加入到方法中使用:

  

//添加日誌管理切面
        LogInterception log = new LogInterception();
        log.setTarget(userService);
        IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), new Class[]{IUserService.class}, log);
//        IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), log);
        boolean flag = userServiceProxy.add();

  這裏簡單爲大家解釋一下,首先我們聲明動態代理的類,通過我們對外提供的setTarget方法將我們的代理類注入,然後通Proxy.newProxyInstance得到我們代理方法的代理對象,然後通過調用我們的代理對象實現我們的動態代理。

public class UserAction extends ActionSupport implements ServletRequestAware, ServletResponseAware{
    
    private IUserService userService;
    
    private HttpServletRequest request;
    private HttpSession session;
    private HttpServletResponse response;
    
    /**
     * @Description 添加用戶測試
     * @throws IOException
     *
     * @author 高尚
     * @version 1.0
     * @date 創建時間:2017年12月13日 上午10:56:30
     */
    public void addJdkInterception(){
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("status", 1);
        result.put("msg", "操作成功");
        
        System.out.println("action操作開始");
        
        //添加日誌管理切面
        LogInterception log = new LogInterception();
        log.setTarget(userService);
        IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), new Class[]{IUserService.class}, log);
//        IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), log);
        boolean flag = userServiceProxy.add();
        result.put("data", flag);
        
        System.out.println("action操作結束");
        
        //輸出到客戶端
        PrintWriter writer = null;
        try {
            if(null != response){
                response.setContentType("text/html;charset=UTF-8");// 解決中文亂碼
                response.setCharacterEncoding("UTF-8");
                writer = response.getWriter();
                writer.write(JSONObject.toJSONString(result));
            }else{
                System.out.println(JSONObject.toJSONString(result));
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            if(null != writer){
                writer.flush();
                writer.close();
            }
        }
    }

    public void setUserService(IUserService userService) {
        this.userService = userService;
    }

    @Override
    public void setServletResponse(HttpServletResponse response) {
        // TODO Auto-generated method stub
        this.response = response;
    }

    @Override
    public void setServletRequest(HttpServletRequest request) {
        // TODO Auto-generated method stub
        this.request = request;
        this.session = this.request.getSession();
    }
    
}

  啓動我們的項目試一把,通過控制檯的日誌信息,我想你一定能理解java動態代理的機制。

  通過上面的內容,相信對於你理解spring aop的知識一定很有幫助。下面我們來看一下srping aop的知識:

  首先我們搭建一個簡單的spring開發環境:

  1、dao接口和實現類

public interface IUserDao {

    boolean add();

    void delete();

}

public class UserDaoImpl implements IUserDao {

    @Override
    public boolean add() {
        System.out.println("dao添加操作成功");
        return false;
    }

    @Override
    public void delete() {
        System.out.println("dao刪除操作成功");
    }

}

  2、service接口和實現類

public interface IUserService {

    boolean add();
    
    void delete();

}

public class UserServiceImpl implements IUserService {
    
    private IUserDao userDao;

    @Override
    public boolean add() {
        System.out.println("service添加操作開始");
        userDao.add();
        System.out.println("service添加操作結束");
        return false;
    }
    
    @Override
    public void delete() {
        System.out.println("service刪除操作開始");
        userDao.delete();
        System.out.println("service刪除操作結束");
    }
    
    public void setUserDao(IUserDao userDao) {
        this.userDao = userDao;
    }

    
}

  3、action實現類

public class UserAction extends ActionSupport implements ServletRequestAware, ServletResponseAware{
    
    private IUserService userService;
    
    private HttpServletRequest request;
    private HttpSession session;
    private HttpServletResponse response;
    
    /**
     * @Description 添加用戶測試
     * @throws IOException
     *
     * @author 高尚
     * @version 1.0
     * @date 創建時間:2017年12月13日 上午10:56:30
     */
    public void add(){
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("status", 1);
        result.put("msg", "操作成功");
        
        System.out.println("action操作開始");
        
        boolean flag = userService.add();
        result.put("data", flag);
        
        System.out.println("action操作結束");
        
        //輸出到客戶端
        PrintWriter writer = null;
        try {
            if(null != response){
                response.setContentType("text/html;charset=UTF-8");// 解決中文亂碼
                response.setCharacterEncoding("UTF-8");
                writer = response.getWriter();
                writer.write(JSONObject.toJSONString(result));
            }else{
                System.out.println(JSONObject.toJSONString(result));
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            if(null != writer){
                writer.flush();
                writer.close();
            }
        }
    }
    
    /**
     * @Description 刪除用戶測試
     * @throws IOException
     *
     * @author 高尚
     * @version 1.0
     * @date 創建時間:2017年12月13日 上午10:56:30
     */
    public void delete(){
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("status", 1);
        result.put("msg", "操作成功");
        
        System.out.println("action操作開始");
        
        userService.delete();
        result.put("data", true);
        
        System.out.println("action操作結束");
        
        //輸出到客戶端
        PrintWriter writer = null;
        try {
            if(null != response){
                response.setContentType("text/html;charset=UTF-8");// 解決中文亂碼
                response.setCharacterEncoding("UTF-8");
                writer = response.getWriter();
                writer.write(JSONObject.toJSONString(result));
            }else{
                System.out.println(JSONObject.toJSONString(result));
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            if(null != writer){
                writer.flush();
                writer.close();
            }
        }
    }
    
    public void setUserService(IUserService userService) {
        this.userService = userService;
    }

    @Override
    public void setServletResponse(HttpServletResponse response) {
        // TODO Auto-generated method stub
        this.response = response;
    }

    @Override
    public void setServletRequest(HttpServletRequest request) {
        // TODO Auto-generated method stub
        this.request = request;
        this.session = this.request.getSession();
    }
    
}

  4、切面邏輯

public class TimeIntercepation {
    
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    
    public void before(){
        System.out.println("執行開始時間:" + sdf.format(new Date()));
    }
    
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("around 執行前");
        Object result = pjp.proceed();//獲得方法執行後的返回參數
        System.out.println("around 執行後");
        return result;
    }
    
    public void after(){
        System.out.println("執行開始時間:" + sdf.format(new Date()));
    }
    
    public void afterReturning(){
        System.out.println("方法正常執行完畢");
    }
    
    public void throwing(){
        System.out.println("方法執行出現異常");
    }
    
}

  5、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:context="http://www.springframework.org/schema/context" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    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.3.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

    <bean name="userDao" class="com.hpugs.spring.one.dao.UserDaoImpl" lazy-init="true"></bean>
    <bean name="userService" class="com.hpugs.spring.one.service.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
    <bean name="userAction" class="com.hpugs.spring.one.action.UserAction" scope="prototype">
        <property name="userService" ref="userService"></property>
    </bean>
    
    <bean name="timeIntercepation" class="com.hpugs.spring.one.interception.TimeIntercepation" lazy-init="true"></bean>
    
    <aop:config>
        <aop:pointcut expression="execution(* com.hpugs.spring.one.service.*.*(..))" id="serviceIntercepation"/>
        <aop:aspect id="timeAspect" ref="timeIntercepation">
            <aop:before method="before" pointcut-ref="serviceIntercepation"/>
            <aop:around method="around" pointcut-ref="serviceIntercepation"/>
            <aop:after-returning method="afterReturning" pointcut-ref="serviceIntercepation"/>
            <aop:after method="after" pointcut-ref="serviceIntercepation"/>
            <aop:after-throwing method="throwing" pointcut-ref="serviceIntercepation"/>
        </aop:aspect>
    </aop:config>

</beans>

  這裏簡單解釋一下,aop:config:設置spring切面,添加切面操作;aop:pointcut:聲明切面;aop:aspect:添加切面操作

  6、status配置

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
    <!-- 取消 struts2 動態方法調用 -->
    <constant name="struts.enable.DynamicMethodInvocation" value="false" />
    <!-- 是否啓用開發模式 -->
    <constant name="struts.devMode" value="false" />
    <!-- 所有匹配 *.action 的請求都由 struts2 處理 -->
    <constant name="struts.action.extension" value="action" />
    <!-- struts 配置文件改動後,是否重新加載 -->
    <constant name="struts.configuration.xml.reload" value="true" />
    <!-- 設置瀏覽器是否緩存靜態內容 -->
    <constant name="struts.serve.static.browserCache" value="false" />
    <!-- 自動重新加載映射加載 -->
    <constant name="struts.convention.classes.reload" value="true"/>
    <!-- action 對象是由Spring負責創建  -->
    <constant name="struts.objectFactory" value="spring" />

    <package name="user" namespace="/" extends="struts-default">
        <action name="user_add" class="userAction" method="add"></action>
        <action name="user_delete" class="userAction" method="delete"></action>
    </package>
     
</struts>

  7、web.xml配置

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>
    
    <web:welcome-file-list>
        <web:welcome-file>index.jsp</web:welcome-file>
    </web:welcome-file-list>

  8、junit單元測試

    @Test
    public void addTest(){
        //注入Spring Bean
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //獲取UserAction對象
        UserAction userAction = (UserAction) applicationContext.getBean("userAction");
        //都有添加操作
        userAction.add();
    }    

  9、單元測試結果:

action操作開始
執行開始時間:2018-01-18 15:34:14.412
around 執行前
service添加操作開始
dao添加操作成功
service添加操作結束
around 執行後
方法正常執行完畢
執行開始時間:2018-01-18 15:34:14.412
action操作結束
{"msg":"操作成功","data":false,"status":1}

  到這裏我們通過xml的方式就實現了方法操作時間切面方法的注入。

  最後在爲大家介紹一下關於spring annotation添加切面的實現:

  首先使我們的spring配置文件,添加註解事務

<!--     <bean name="timeIntercepation" class="com.hpugs.spring.one.interception.TimeIntercepation" lazy-init="true"></bean> -->
    
    <context:annotation-config></context:annotation-config>
    <context:component-scan base-package="com.hpugs.spring.one.interception"></context:component-scan>
    
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

  這裏解釋一下,我們可以通過bean的方式將我們的代理方法注入,但是不推薦大家使用,這裏推薦大家使用包掃描的方式進行代理方法注入。

  通過註解的方式進行切面聲明:

@Aspect
@Component
public class TimeIntercepation {
    
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
    @Pointcut("execution(* com.hpugs.spring.one.service.*.*(..))")
    public void myMethod(){}

    @Before("execution(* com.hpugs.spring.one.service.*.*(..))")
    public void before(){
        System.out.println("執行開始時間:" + sdf.format(new Date()));
    }
    
    @Around("myMethod()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("around 執行前");
        Object result = pjp.proceed();//獲得方法執行後的返回參數
        System.out.println("around 執行後");
        return result;
    }
    
    @After("execution(* com.hpugs.spring.one.service.*.*(..))")
    public void after(){
        System.out.println("執行開始時間:" + sdf.format(new Date()));
    }
    
    @AfterReturning("myMethod()")
    public void afterReturning(){
        System.out.println("方法正常執行完畢");
    }
    
    @AfterThrowing("myMethod()")
    public void throwing(){
        System.out.println("方法執行出現異常");
    }
    
}

  這裏解釋一下myMethod方法,如果我們的方法共用切面時,我們可以通過這種方式將面聲明進行復用。

  關於Spring AOP的內容就和大家探討到這裏,以上源代碼下載地址:https://github.com/hpugs/Spring-aop

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章