AOP實現事務和記錄日志


AOP Aspect Oriented Programming)

將非功能性需求從功能性需求中剝離出來,解耦並且解決代碼復用的問題,比如說權限控制,事務控制,記錄操作日志,全局捕獲異常等

@Aspect   切面

@PointCut  描述在哪些類哪些方法織入代碼

@Advice   在方法的什么執行時機(之前或者之后)去執行

Advice分為5

@Before,前置通知

@After(finally) 后置通知,方法執行完后

@AfterReturning,返回通知,方法成功執行之后

@AfterThrowing,異常通知,發生異常之后

@Around,環繞通知

 

AOP實現的方式有靜態代理和動態代理

動態代理實現分為兩種:基於接口和基於繼承

基於接口的是JDK代理,基於繼承的是Gglib代理

Spring中的代理可以使用配置項指定采用哪一種

 

添加AOP依賴后,編寫切面

@Aspect
@Component
public class AdviceAspectConfig {

  
    /**
     *  每種通配符表示的含義: | *表示任意字符  | ..表示本包和子包 或者是任意參數  | 
     *  切入點:修飾符是public ,返回值任意類型,  service包和他的子包,以Service結尾的類,任意的方法
     */
    @Pointcut("execution(public * com.irish.service..*Service.*(..))")
    public void matchServiceMethod(){}

   
    /**
    * 可以在方法執行的前后添加非功能性的代碼
    */
    @Around("matchServiceMethod()")
    public java.lang.Object after(ProceedingJoinPoint joinPoint){
        System.out.println("###before");
        java.lang.Object result = null;
        try{
            System.out.println("方法參數:");
            java.lang.Object[] args = joinPoint.getArgs();
            System.out.println("被代理的對象"+joinPoint.getTarget());
            result = joinPoint.proceed(joinPoint.getArgs());
            System.out.println("###after returning");
        }catch (Throwable e){
            System.out.println("###after throwing");
            e.printStackTrace();
            
        }finally {
            System.out.println("###finally");
        }
        return result;
    }

}

 

(1)編程式事務,手動開啟事務,提交事務,回滾事務

@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserDao userDao;
    
    @Autowired
    private TransactionUtils transactionUtils;

    public void add() {
        TransactionStatus transactionStatus = null;
        try {
            // 開啟事務
            transactionStatus = transactionUtils.begin();
            userDao.add(20, "password001","test001");
            //System.out.println("開始報錯啦!@!!");
            //int i = 1 / 0;
            System.out.println("################");
            userDao.add(21, "password002","test002");
            // 提交事務
            if (transactionStatus != null)
                transactionUtils.commit(transactionStatus);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            // 回滾事務
            if (transactionStatus != null)
                transactionUtils.rollback(transactionStatus);
        }
    }

}

項目結構:

 

 

(2)記錄操作日志

 對dao包的所有以save開頭和以delete開頭的方法,進行攔截處理,生成操作日志,記錄在mongodb中

 1 引入依賴jar包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.irish.aop</groupId>
    <artifactId>datalog</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>datalog</name>
    <description>datalog</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.28</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

2 編寫切面

@Aspect
@Component
public class DatalogAspect {

    private static final Logger logger = LoggerFactory.getLogger(DatalogAspect.class);

    @Autowired
    ActionDao actionDao;

    @Pointcut("execution(public * com.irish.aop.dao.*.save*(..))")
    public void save(){

    }

    @Pointcut("execution(public * com.irish.aop.dao.*.delete*(..))")
    public void delete(){

    }

    /**
     * 1\判斷是什么類型的操作,增加\刪除\還是更新
     *  增加/更新 save(Product),通過id區分是增加還是更新
     *  刪除delete(id)
     * 2\獲取changeitem
     *   (1)新增操作,before直接獲取,after記錄下新增之后的id
     *   (2)更新操作,before獲取操作之前的記錄,after獲取操作之后的記錄,然后diff
     *   (3)刪除操作,before根據id取記錄
     * 3\保存操作記錄
     *    actionType
     *    objectId
     *    objectClass
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("save() || delete()")
    public Object addOperateLog(ProceedingJoinPoint pjp) throws Throwable {
        Object returnObj = null;

        //TODO BEFORE OPERATION init action
        String method = pjp.getSignature().getName();
        ActionType actionType = null;
        Action action = new Action();
        Long id = null;
        Object oldObj = null;
        try{

            if("save".equals(method)){
                //insert or update
                Object obj = pjp.getArgs()[0];
                try{
                    id = Long.valueOf(PropertyUtils.getProperty(obj,"id").toString());
                }catch (Exception e){
                    //ignore
                }
                if(id == null){
                    actionType = ActionType.INSERT;
                    List<ChangeItem> changeItems = DiffUtil.getInsertChangeItems(obj);
                    action.getChanges().addAll(changeItems);
                    action.setObjectClass(obj.getClass().getName());
                }else{
                    actionType = ActionType.UPDATE;
                    action.setObjectId(id);
                    //pjp.getTarget()被代理的對象,即ProductDao
                    oldObj = DiffUtil.getObjectById(pjp.getTarget(),id);
                    action.setObjectClass(oldObj.getClass().getName());
                }

            }else if("delete".equals(method)){
                id = Long.valueOf(pjp.getArgs()[0].toString());
                actionType = ActionType.DELETE;
                oldObj = DiffUtil.getObjectById(pjp.getTarget(),id);
                ChangeItem changeItem = DiffUtil.getDeleteChangeItem(oldObj);
                action.getChanges().add(changeItem);
                action.setObjectId(Long.valueOf(pjp.getArgs()[0].toString()));
                action.setObjectClass(oldObj.getClass().getName());
            }

            returnObj = pjp.proceed(pjp.getArgs());

            //TODO AFTER OPERATION save action
            action.setActionType(actionType);
            if(ActionType.INSERT == actionType){
                //new id
                Object newId = PropertyUtils.getProperty(returnObj,"id");
                action.setObjectId(Long.valueOf(newId.toString()));

            }else if(ActionType.UPDATE == actionType){
                //pjp.getTarget()獲取的是被代理的對象 【目標對象(target object):被代理對象】
                //在保存方法之后,所以這時候獲取的是新值
                Object newObj = DiffUtil.getObjectById(pjp.getTarget(),id);
                List<ChangeItem> changeItems = DiffUtil.getChangeItems(oldObj,newObj);
                action.getChanges().addAll(changeItems);
            }

            action.setOperator("admin"); //dynamic get from threadlocal/session
            action.setOperateTime(new Date());

            actionDao.save(action);

        }catch (Exception e){
            logger.error(e.getMessage(),e);
        }

        return returnObj;
    }
}

項目結構

 

(3)在springboot項目中使用注解事務

spring集成的事務只是對事務的管理,真正實現事務的是持久層框架,如jdbcTemplate ,hibernate等

測試時候注意mysql的版本要是5.5以上,數據庫引擎是InnoDB

在一張表中設置一個字段是唯一約束,插入相同數據時,這時候要么兩張表都插入失敗,保證了事務的原子性

項目結構

 

1 @Transactional只能被應用到public方法上,對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能

@Transactional annotations only work on public methods. If you have a private or protected method with this annotation there’s no (easy) way for Spring AOP to see the annotation. It doesn’t go crazy trying to find them so make sure all of your annotated methods are public.

 

2  默認情況下,一個有事務的方法 遇到RuntimeException 時會回滾 . 但是遇到檢查異常 是不會回滾的,要想所有異常都回滾,要加上 @Transactional( rollbackFor={Exception.class,其它異常}) 

 

(4)自定義注解實現事務

思路:注解版本的事務其實是AOP+編程式事務,

     1定義注解 @interface ExtTransaction 

     2在AOP切入點中配置要掃描的類及方法

     3在Around通知中,判斷方法是否有自定義注解,有就開啟事務,方法執行完提交事務,發生異常回滾事務

  

@Aspect
@Component
public class AopExtTransaction {
    // 一個事務實例子 針對一個事務
    @Autowired
    private TransactionUtils transactionUtils;
    
    // 環繞通知 在方法之前和之后處理事情
    @Around("execution( * com.irish.service.*.*.*(..))")
    public Object  around(ProceedingJoinPoint pjp) throws Throwable {
        Object result = null;
        try {
            // 1.獲取該方法上是否加上注解
             ExtTransaction extTransaction = getMethodExtTransaction(pjp);
             TransactionStatus transactionStatus = begin(extTransaction);
            // 2.調用目標代理對象方法
             result  =  pjp.proceed();
            // 3.判斷該方法上是否就上注解
            commit(transactionStatus);
            
        }catch(Exception e) {
            //4發生異常回滾
            transactionUtils.rollback();
        }
        return result;
    }

    private TransactionStatus begin(ExtTransaction extTransaction) {
        if (extTransaction == null) {
            return null;
        }
        // 2.如果存在事務注解,開啟事務
        return transactionUtils.begin();
    }

    private void commit(TransactionStatus transactionStatus) {
        if (transactionStatus != null) {
            // 5.如果存在注解,提交事務
            transactionUtils.commit(transactionStatus);
        }

    }

    // 獲取方法上是否存在事務注解
    private ExtTransaction getMethodExtTransaction(ProceedingJoinPoint pjp)
            throws NoSuchMethodException, SecurityException {
        String methodName = pjp.getSignature().getName();
        // 獲取目標對象
        Class<?> classTarget = pjp.getTarget().getClass();
        // 獲取目標對象類型
        Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
        // 獲取目標對象方法
        Method objMethod = classTarget.getMethod(methodName, par);
        ExtTransaction extTransaction = objMethod.getDeclaredAnnotation(ExtTransaction.class);
        return extTransaction;
    }

}

 

項目結構:

 

 github地址:https://github.com/jake1263/AOP


免責聲明!

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



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