spring aop簡單理解


aop原理是spring幫我們封裝了動態代理,然后我們只管寫具體的業務,我們將公共業務也寫到具體的一個類中並實現spring為我們提供的對應要連接切入哪個位置的接口,然后再xml中配置它們的關系即可。

優點:和動態代理一樣,具體實現只管具體實現使的代碼更加純粹,公共業務只需實現自己對應的接口,然后編碼即可,有了動態代理的好處,又沒有手寫動態代理那么復雜,這就是aop(被分裝后的動態代理)。

一:實現spring接口的方式

1.UserService接口:

package mr.li.service;

public interface UserService {

    void add();
    
    void remove();
    
}

2.UserServiceImpl實現類:

package mr.li.service.impl;

import mr.li.service.UserService;

public class UserServiceImpl implements UserService{

    @Override
    public void add() {
        System.out.println("添加一條數據");
    }

    @Override
    public void remove() {
        System.out.println("刪除一條數據");
    }

}

3.前置通知:Log日志打印

package mr.li.log;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;
/**
 * 前置切面:日志打印
 * @author yanLong.Li
 * @date 2019年3月16日 下午10:33:22
 */
public class Log implements MethodBeforeAdvice{

    @Override
    public void before(Method method, Object[] arg1, Object target) throws Throwable {
        System.out.println("aop前置:"+target.getClass().getName()+"類的"+ method + "方法執行了~~");
    }

}

4后置通知:AfterLog 日志打印

package mr.li.log;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;
/**
 * 后置切面:日志打印
 * @author yanLong.Li
 * @date 2019年3月16日 下午10:33:44
 */
public class AfterLog implements AfterReturningAdvice{

    @Override
    public void afterReturning(Object result, Method method, Object[] arg2, Object target) throws Throwable {
        System.out.println("aop后置:"+target.getClass().getName()+"類的"+ method + "方法執行了,返回結果為"+result);
    }
}

5.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="userService" class="mr.li.service.impl.UserServiceImpl"/> 
       <bean id = "log" class="mr.li.log.Log"/>
       <bean id = "afterLog" class="mr.li.log.AfterLog"/>
       <aop:config>
              <!-- 配置被切入的哪個類使用 execution表達式第一個:“*”表示返回值 第二個*表示所有的 ..標識所有的參數 -->
              <aop:pointcut expression="execution(* mr.li.service.impl.UserServiceImpl.*(..))" id="aoop"/>
              <!-- 設置要切入的類:-->
              <aop:advisor advice-ref="log" pointcut-ref="aoop"/>
              <aop:advisor advice-ref="afterLog" pointcut-ref="aoop"/>
       </aop:config> 
</beans>

6.測試類

package mr.li.client;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import mr.li.service.UserService;

public class Client {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.remove();
        userService.add();
    }
}

打印結果:

aop前置:mr.li.service.impl.UserServiceImpl類的public abstract void mr.li.service.UserService.remove()方法執行了~~
刪除一條數據
aop后置:mr.li.service.impl.UserServiceImpl類的public abstract void mr.li.service.UserService.remove()方法執行了,返回結果為null
aop前置:mr.li.service.impl.UserServiceImpl類的public abstract void mr.li.service.UserService.add()方法執行了~~
添加一條數據
aop后置:mr.li.service.impl.UserServiceImpl類的public abstract void mr.li.service.UserService.add()方法執行了,返回結果為null

二:自定義aop,切面不在需要實現接口,自己寫即可,只是調整下配置

1.UserService:和上面一樣

2.UserServiceImpl:和上面一樣

3.Log切面

package mr.li.log;

/**
 * 前置切面:日志打印
 * @author yanLong.Li
 * @date 2019年3月16日 下午10:33:22
 */
public class Log {

    public void before() {
        System.out.println("前置通知");
    }
    
    public void after() {
        System.out.println("后置通知");
    }

}

4.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="userService" class="mr.li.service.impl.UserServiceImpl"/> 
       <bean id = "log" class="mr.li.log.Log"/>
       <aop:config>
               <aop:aspect ref="log">
                   <!-- 配置被切入的哪個類使用 execution表達式 *表示所有的 ..標識所有的參數 -->
                      <aop:pointcut expression="execution(* mr.li.service.impl.UserServiceImpl.*(..))" id="pointcut"/>
                   <!-- 配置前置通知 -->
                   <aop:before method="before" pointcut-ref="pointcut"/>
                   <!-- 配置后置通知 -->
                   <aop:after method="after" pointcut-ref="pointcut"/>
               </aop:aspect>
       </aop:config> 
</beans>

5.測試

package mr.li.client;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import mr.li.service.UserService;

public class Client {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.remove();
        userService.add();
    }
}

打印結果:

前置通知
刪除一條數據
后置通知
前置通知
添加一條數據
后置通知

三:注解的方式:注意下環繞通知,參數說明,以及使用(proceedingJoinpoint在下面有額外解釋)

1.UserService:和最上面一樣

2.UserServiceImpl:和最上面一樣

3.切面:Log

package mr.li.log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * 切面:日志打印
 * @author yanLong.Li
 * @date 2019年3月16日 下午10:33:22
 */
@Aspect
public class Log {

    /**
     * 前置通知:execution表示切入點
     */
    @Before("execution(* mr.li.service.impl.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("注解:前置通知");
    }
    
    /**
     * 后置通知
     */
    @After("execution(* mr.li.service.impl.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("注解:后置通知");
    }

    /**
     * 環繞通知
     * @param jp 此參數是spring給我們的,它里面可以干很多事,包括繼續執行被切面的方法,拿到方法簽名,他會在被切面的方法之前運作
     * @throws Throwable 
     */
    @Around("execution(* mr.li.service.impl.UserServiceImpl.*(..))")
    public Object aroud(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("環繞前");
        System.out.println("方法簽名:"+jp.getSignature());
        //執行此方法
        Object result = jp.proceed();
        System.out.println("環繞后");
        return result;
    }
}

4.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="userService" class="mr.li.service.impl.UserServiceImpl"/> 
       <bean id = "log" class="mr.li.log.Log"/>
       <!-- 此配置會自動去找aop配置 -->
       <aop:aspectj-autoproxy/> 
</beans>

5.測試:

package mr.li.client;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import mr.li.service.UserService;

public class Client {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.remove();
        userService.add();
    }
}

打印結果:

注解:前置通知
刪除一條數據
環繞后
注解:后置通知
環繞前
方法簽名:void mr.li.service.UserService.add()
注解:前置通知
添加一條數據
環繞后
注解:后置通知

 額外對ProceedingJoinPoint參數的描述

package com.qty.arena.util;

import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import com.qty.arena.helper.MessagePushHelper;
import com.qty.database.dto.arena.match.SyncAll;

/**
 * 在被@NotDuplicate注解所標注的方法上,過濾請求者重復請求的消息
 * @author yanLong.Li
 * @date 2019年2月11日 上午11:11:01
 */
@Aspect
@Component
public class NotDuplicateAop {

//    private static final Set<String> KEY =  new ConcurrentSkipListSet<>();

    private static final Set<Long> KEY =  new ConcurrentSkipListSet<>();
    
    @Pointcut("@annotation(com.qty.arena.util.NotDuplicate)")
    public void duplicate() {}

    /**
     * 對方法攔截后進行參數驗證
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("duplicate()")
    public Object duplicate(ProceedingJoinPoint pjp) throws Throwable{
        Object[] objs = pjp.getArgs();
        Long dbId = null;
        if(objs[0] instanceof Long) {
            dbId = (Long)objs[0];                 
        }
        if(dbId == null) {
            throw new NumberFormatException("在解析請求的dbId時發生異常,數字不能解析,值:"+objs[0]);
        }
        boolean success = KEY.add(dbId);
        if(!success){
            MessagePushHelper.getInstance().pushSingleMessage(dbId, SyncAll.valueOf("頻繁操作,請稍后再試"));
            return null;
        }
        try {
            //方法執行前
            return pjp.proceed();
        } finally {
            //方法執行后
            KEY.remove(dbId);
        }
    }

 


免責聲明!

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



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