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);
        }
    }
原文地址:https://www.cnblogs.com/li-yan-long/p/10544772.html