aop心得及简单应用

一、什么是AOP

概念

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

通俗来说:
AOP就是在原有代码的基础上,增加额外的功能,而且不改变原有代码。

优点

为什么都说AOP减少耦合性,举个例子说明下:
当你的代码开发完毕后,如果需要增加日志功能怎么办呢?通常来说,我们可以在需要增加日志的接口或者说功能上添加记录日志的功能。这样需要改变原有代码,如果数量少可以接受。但是如果数量多,那样就会有很大的工作量。并且在之后如果有升级或者删除日志的需求后,还要对源代码进行修改,非常麻烦,而且容易出错。但是如果用了AOP,只需要一个切入点就搞定了。

简介

基本的概念直接省略

二、 切入点表达式

公式 aop = 切入点表达式 + 通知方法

@Pointcut(“bean(itemCatServiceImpl)”)
@Pointcut(“within(com.jt.service.)")
@Pointcut("execution(
com.jt.service..(…))”) //.* 当前包的一级子目录
@Pointcut(“execution(* com.jt.service….(…))”) //…* 当前包的所有的子目录

@Pointcut注解用于定义切入点
bean(“spring容器中bean的名字”)这个表达式为切入点表达式定义的一种语法,
它描述的是某个bean或多个bean中所有方法的集合为切入点,这个形式的切入点
表达式的缺陷是不能精确到具体方法的.
只有在定义了切入点,相应的接口或者方法才会有拓展的功能

注解方式定义切入点

1.首先自定义注解

@Target(ElementType.TYPE)     //在方法中使用注解
@Retention(RetentionPolicy.RUNTIME) //运行期有效
public @interface Test11 {

    String key();   //定义业务key
    int seconds() default -1;  //定义超时时间 -1表示不超时
}

2.在方法上添加注解

@Test1
public int updateObject(SysDept entity) {
	...
	}

三、通知

1.前置通知(before advice):在连接点前面执行,对连接点不会造成影响(前置通知有异常的话,会对后续操作有影响)
2.正常返回通知(after returning advice):在连接点正确执行之后执行,如果连接点抛异常,则不执行
3.异常返回通知(after throw Advice):在连接点抛异常的时候执行
4.返回通知(after):无论连接点是正确执行还是抛异常,都会执行
5.环绕通知(around):在连接点前后执行(必须在环绕通知中决定是继续处理还是中断执行,使用PreceedingJoinPonit下的方法决定是继续还是中断)

作者一般都用环绕通知around

四、应用配置

直接上代码

/**
 * @Aspect 注解描述的类型为切面对象类型,此切面中可以定义多个切入点和通知方法.
 */
@Order
@Slf4j
@Aspect
@Component
public class SysLogAspect {
    /**
     * @Pointcut注解用于定义切入点
     * bean("spring容器中bean的名字")这个表达式为切入点表达式定义的一种语法,
     * 它描述的是某个bean或多个bean中所有方法的集合为切入点,这个形式的切入点
     * 表达式的缺陷是不能精确到具体方法的.
     */
     //@Pointcut("bean(sysUserServiceImpl)")
     @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
     public void doLog(){}//此方法只负责承载切入点的定义

    /**
     * @Around注解描述的方法,可以在切入点执行之前和之后进行业务拓展,
     * 在当前业务中,此方法为日志通知方法
     * @param jp 连接点对象,此对象封装了要执行的切入点方法信息.
     * 可以通过连接点对象调用目标方法.
     * @return 目标方法的执行结果
     * @throws Throwable
     */
     @Around("doLog()")
     public Object doAround(ProceedingJoinPoint jp)throws Throwable{
         long t1=System.currentTimeMillis();
         log.info("Start:{}",t1);
         try {
             Object result = jp.proceed();//执行目标方法(切点方法中的某个方法)
             long t2=System.currentTimeMillis();
             log.info("After:{}",t2);
             saveUserLog(jp,t2-t1);
             return result;//目标业务方法的执行结果
         }catch(Throwable e){
             e.printStackTrace();
             log.error("Exception:{}",System.currentTimeMillis());
             throw e;
         }
     }
     @Autowired
     private SysLogService sysLogService;
     //记录用户行为日志
     private void saveUserLog(ProceedingJoinPoint jp,long time)
             throws Exception {
         //1.获取用户行为日志
         //1.1获取登录用户名(没做登录时,可以先给个固定值)
         String username="cgb";
         //1.2获取ip地址
         String ip= IPUtils.getIpAddr();
         //1.3获取操作名(operation)-@RequiredLog注解中value属性的值
         //1.3.1获取目标对象类型
         Class<?> targetCls=jp.getTarget().getClass();
         //1.3.2获取目标方法
         MethodSignature ms= (MethodSignature) jp.getSignature();//方法签名
         Method targetMethod=
                 targetCls.getMethod(ms.getName(),ms.getParameterTypes());
         //1.3.3 获取方法上RequiredLog注解
         RequiredLog annotation =
                 targetMethod.getAnnotation(RequiredLog.class);
         //1.3.4 获取注解中定义操作名
         String operation=annotation.value();
         //1.4获取方法声明(类全名+方法名)
         String classMethodName=targetCls.getName()+"."+targetMethod.getName();
         //1.5获取方法实际参数信息
         Object[]args=jp.getArgs();
         String params=new ObjectMapper().writeValueAsString(args);
         //2.封装用户行为日志
         SysLog sysLog=new SysLog();
         sysLog.setUsername(username);
         sysLog.setIp(ip);
         sysLog.setOperation(operation);
         sysLog.setMethod(classMethodName);
         sysLog.setParams(params);
         sysLog.setTime(time);
         //3.存储用户信息日志到数据库
         sysLogService.saveObject(sysLog);
       
     }
}

@around后面可直接加切入点表达式:

@Around("@annotation(cacheFind)")
 //注解方式,cacheFind为自定义注解名

五、整合AOP和Redis

aop代码

package com.jt.aop;

/*@Service
@Controller
@Repository*/
@Component  //组件 将类交给spring容器管理
@Aspect     //表示我是一个切面
public class RedisAOP {

    @Autowired
    //private Jedis jedis;              //使用单台redis
    //private ShardedJedis jedis;       //使用redis分片机制
    private JedisCluster jedis;  //引入redis集群

    /*
     * 实现AOP业务调用
     * 1.拦截指定的注解
     * 2.利用环绕通知实现
     * 实现步骤:
     *       1.获取KEY  必须先获取注解 从注解中获取key?
     *       2.校验redis中是否有值
     *     *
     * 3.知识点补充:
     *   指定参数名称进行传值,运行期绑定参数类型完成注解的拦截
     *   joinPoint必须位于参数的第一位.
     */
    @Around("@annotation(cacheFind)")
    public Object around(ProceedingJoinPoint joinPoint, CacheFind cacheFind) {
        Object result = null;
        //key=业务名称::参数
        String key = cacheFind.key();
        String args = Arrays.toString(joinPoint.getArgs());
        key = key + "::" + args;

        //2.校验是否有值
        if (jedis.exists(key)) {
            String json = jedis.get(key);
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            //获取返回值类型
            Class returnType = methodSignature.getReturnType();
            result = ObjectMapperUtil.toObj(json, returnType);
            System.out.println("AOP查询redis缓存");

        } else {
            //redis中没有数据,所以需要查询数据库,将数据保存到缓存中
            try {
                result = joinPoint.proceed();
                String json = ObjectMapperUtil.toJSON(result);
                //是否设定超时时间
                if (cacheFind.seconds() > 0) {
                    jedis.setex(key, cacheFind.seconds(), json);
                } else {
                    jedis.set(key, json);
                }
                System.out.println("AOP查询数据库");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }

        return result;
    }


    /**
     *   //1.获取key 注解  方法对象  类 方法名称  参数
     *         Class targetClass = joinPoint.getTarget().getClass();
     *         //2.获取方法对象
     *         String methodName = joinPoint.getSignature().getName();
     *         Object[] args = joinPoint.getArgs();
     *         Class[] classArgs = new Class[args.length];
     *         for(int i=0;i<args.length;i++){
     *             classArgs[i] = args[i].getClass();
     *         }
     *         try {
     *             //反射实例化对象
     *             Method method = targetClass.getMethod(methodName,classArgs);
     *             CacheFind cacheFind = method.getAnnotation(CacheFind.class);
     *             String key = cacheFind.key();
     *             System.out.println(key);
     *         } catch (NoSuchMethodException e) {
     *             e.printStackTrace();
     *         }
     */


    //公式 aop = 切入点表达式   +   通知方法
    //@Pointcut("bean(itemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.*)")
    //@Pointcut("execution(* com.jt.service.*.*(..))")   //.* 当前包的一级子目录
   /* @Pointcut("execution(* com.jt.service..*.*(..))")  //..* 当前包的所有的子目录
    public void pointCut(){

    }*/

    //如何获取目标对象的相关参数?
    //ProceedingJoinPoint is only supported for around advice
   /* @Before("pointCut()")
    public void before(JoinPoint joinPoint){    //连接点
        Object target = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("目标对象:"+target);
        System.out.println("方法参数:"+Arrays.toString(args));
        System.out.println("类名称:"+className);
        System.out.println("方法名称:"+methodName);
    }*/


}

redis配置文件

# 准备redis节点信息
redis.host=127.0.0.1
redis.port=6379

redis 配置类

@Configuration  //表示一个配置类  一般会与@Bean的注解联用
@PropertySource("classpath:/redis.properties") //导入配置文件

public class RedisConfig {

==redis集群==
    //springBoot整合Redis集群
//    @Value("${redis.nodes}")
//    private String nodes;   //node,node,node
//
//    @Bean
//    public JedisCluster jedisCluster(){
//        Set<HostAndPort> nodesSet = new HashSet<>();
//        String[] nodeArray = nodes.split(",");
//        for (String node : nodeArray){ //node=IP:PORT
//            String host = node.split(":")[0];
//            int port = Integer.parseInt(node.split(":")[1]);
//            HostAndPort hostAndPort = new HostAndPort(host, port);
//            nodesSet.add(hostAndPort);
//        }
//        return new JedisCluster(nodesSet);
//    }


   /* @Value("${redis.nodes}")
    private String nodes;   //node,node,node

    @Bean
    public ShardedJedis shardedJedis(){

        List<JedisShardInfo> shards = new ArrayList<>();
        String[] nodeArray = nodes.split(",");
        for (String node : nodeArray){  //host:port
            String host = node.split(":")[0];
            int port = Integer.parseInt(node.split(":")[1]);
            shards.add(new JedisShardInfo(host,port));
        }
        return new ShardedJedis(shards);
    }*/

==单独redis==

    @Value("${redis.host}")
    private String host;
    @Value("${redis.port}")
    private Integer port;

    @Bean   //将方法的返回值结果,交给spring容器进行管理.
    public Jedis jedis(){

        return new Jedis(host, port);
    }

}