AOP简介

AOP(Aspect Orient Programming)是一种设计思维,是软件设计畛域中的面向切面编程,它是面向对象编程(OOP)的一种补充和欠缺。理论我的项目中咱们通常将面向对象了解
为一个动态过程(例如一个零碎有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面了解为一个动静过程(在对象运行时动静织入一些扩大性能或管制对象执行)。
AOP 与 OOP 字面意思相近,但其实两者齐全是面向不同畛域的设计思维。理论我的项目中咱们通常将面向对象了解为一个动态过程(例如一个零碎有多少个模块,一个模块有哪些
对象,对象有哪些属性),面向切面的运行期代理形式,了解为一个动静过程,能够在对象运行时动静织入一些扩大性能或管制对象执行。

实现原理

AOP 能够在系统启动时为指标类型创立子类或兄弟类型对象,这样的对象咱们通常会称之为动静代理对象.如图所示:

其中,为指标类型(XxxServiceImpl)创立其代理对象形式有两种:
第一种形式:借助 JDK 官网 API 为指标对象类型创立其兄弟类型对象,然而指标对象类型须要实现相应接口.
第二种形式:借助 CGLIB 库为指标对象类型创立其子类类型对象,然而指标对象类型不能应用 final 润饰.

相干术语剖析

切面(aspect):横切面对象,个别为一个具体类对象
切入点(pointcut):定义了切入扩大业务逻辑的地位(哪些办法运行时切入扩大业务),个别会通过表达式进行相干定义,一个切面中能够定义多个切入点。
告诉(Advice):外部封装扩大业务逻辑的具体方法对象,一个切面中能够有多个告诉(在切面的某个地位上执行的动作(扩大性能))
连接点(joinpoint):程序执行过程中,封装了某个正在执行的指标办法信息的对象,能够通过此对象获取具体的指标办法信息,甚至去调用指标办法。连接点与切入点定义如图:

案例

增加依赖

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

业务切面对象设计

通过设计切面对象,为指标业务办法做性能加强,关键步骤如下:

第一步:创立注解类型,利用于切入点表达式的定义,要害代码如下:

@Target 用于形容定义的注解可能润饰的对象,@Retention 用于形容定义的注解何时无效。

package com.cy.pj.sys.commen.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface RequiredLog{    String operation();//在指标业务上应用@RequiredLog时须要为operation赋值//    String operation() default "";//在指标业务上应用@RequiredLog时不须要为operation赋值}

第二步:创立切面对象,用于做日志业务加强,要害代码如下:

package com.cy.pj.sys.service.aspect;import com.cy.pj.sys.commen.annotation.RequiredLog;import com.cy.pj.sys.dao.SysLogDao;import com.cy.pj.sys.pojo.SysLog;import com.cy.pj.sys.service.SysLogService;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.lang.reflect.Method;import java.util.Date;import java.util.List;/*在spring aop利用中,基于@Aspect注解形容的类型为一个切面类型,此类中要封装切入点及告诉办法的定义1.切入点:要切入扩大业务逻辑的一些指标办法汇合2.告诉:封装了扩大业务逻辑的一个办法* 创立切面对象,用于做日志业务加强* */@Aspect@Componentpublic class SysLogAspect {    private static final Logger log=            LoggerFactory.getLogger(SysLogAspect.class);    /*定义切入点,基于@pointcut注解定义,这里的@annotation为一种切入点表达式 * 示意由RequiredLog注解形容的办法为一个切入点办法,咱们在这样的办法上增加业务扩大 * */ @Pointcut("@annotation(com.cy.pj.sys.commen.annotation.RequiredLog)")    public void doLog(){}//此办法只负责承载切入点的定义 /* * @Around注解形容的办法,能够在切入点执行之前和之后进行业务拓展 * @param jp连接点对象,在此对象封装了要执行的指标办法信息 * ProceedingJoinPoint此类型的注解只能定义在@Around中 * 能够通过连接点对象调用指标办法 * @return指标办法的执行后果 * @throws Throwable * */ @Around("doLog()")        //@Around("@annotation(com.cy.pj.sys.commen.annotation.RequiredLog)") public Object doAround(ProceedingJoinPoint jp) throws Throwable {            log.info("method start {}", System.currentTimeMillis());            long t1=System.currentTimeMillis();            try {                Object result=jp.proceed();//调用指标办法 System.out.println(jp.getTarget().getClass().getName());                log.info("method after {}", System.currentTimeMillis());                long t2=System.currentTimeMillis();                doLogInfo(jp,(t2-t1),null);                return result;            }catch (Throwable e){                log.error("exception {}", e.getMessage());                long t3=System.currentTimeMillis();                doLogInfo(jp,(t3-t1),e);                throw e;            }        }//记录用户行为日志 @Autowired private SysLogService sysLogService;        private void doLogInfo(ProceedingJoinPoint jp,long time,Throwable e) throws NoSuchMethodException, JsonProcessingException {            //获取行为日志 String username="tony";//登录用户 String ip="192.168.100.11";//登录用户的IP借助三方工具类 //获取用户操作 //获取办法所在类的字节码对象(指标对象对应的字节码对象) Class<?>cls=jp.getTarget().getClass();            //获取注解形容的办法对象(字节码对象,办法名,参数列表) //System.out.println("cls="+cls); Signature signature=jp.getSignature();            //System.out.println("signature="+signature.getClass().getName());//MethodSignatureImpl MethodSignature methodSignature= (MethodSignature) signature;//            Method targetMethod=methodSignature.getMethod();//cglib Method targetMethod=//cglib,jdk cls.getDeclaredMethod(methodSignature.getName(),                            methodSignature.getParameterTypes());//            System.out.println("targetMethod="+targetMethod); //获取RequiredLog注解 RequiredLog requiredLog=targetMethod.getAnnotation(RequiredLog.class);            String operation= requiredLog.operation();            //String operation=null; //获取办法申明信息//            System.out.println(cls.getName());//com.cy.pj.sys.service.serviceImpl.SysLogServiceImpl//            System.out.println(targetMethod.getName());//findLogs String method=cls.getName()+"."+targetMethod.getName();            //获取办法执行时传入的理论参数 String params=new ObjectMapper().writeValueAsString(jp.getArgs());            //获取状态信息 Integer status=e==null?1:0;            String error=e==null?null:e.getMessage();            //封装用户行为日志 SysLog sysLog=new SysLog();            sysLog.setUsername(username);            sysLog.setIp(ip);            sysLog.setCreatedTime(new Date());            sysLog.setOperation(operation);            sysLog.setParams(params);            sysLog.setStatus(status);            sysLog.setError(error);            sysLog.setTime(time);            //打印日志 log.info("user log {}", new ObjectMapper().writeValueAsString(sysLog));            //将日志写入到数据库 sysLogService.saveLog(sysLog);        }}

第三步:通过注解 RequiredLog 注解形容日志查问或删除业务相干办法,此时这个办法为日志切入点办法,例如:

 @RequiredLog(operation = "布告查问") @Override public List<SysLog> findLogs(SysLog sysLog) {     return sysLogDao.selectLogs(sysLog); } @RequiredLog(operation = "删除日志")// @RequiredLog @Override public int deleteById(long... id) {     return sysLogDao.deleteLogs(id); }