zoukankan      html  css  js  c++  java
  • aop详解与实战

    1. 什么是AOP

    aop:面向切面编程。采用横向机制。
    oop:面向对象编程。采用纵向机制。

    AOP,面向切面编程。就是通过某个切入点(比如方法开始、结束)向某个切面(被切的对象)切入环绕通知(需要切入的逻辑代码)。
    比如一个类中的所有方法执行前都需要打印日志,那么可以通过AOP的方式来统一实现,而不需要在每个方法中都加入打印日志的代码逻辑。

    2. AOP的常用使用场景

    • 日志记录
    • 权限控制
    • 事物管理
    • 缓存处理
      ...

    3. AOP的实现方式

    • Spring AOP
          a) JDK 动态代理
          b) Cglib 动态代理
    • AspectJ

    4. AspectJ是什么

    AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
    Spring2.0以后新增了对AspectJ的全面支持。
    常用注解:

    @Before             前置通知                                          使用时需要指定一个value属性值,用于指定一个切入点表达式。【可以是现写的表达式,也阔以是已有的】。
    @AfterReturning     后置通知                                          使用时需要指定pointcut/value属性值,用于指定切入点表达式。还能使用returning属性指定一个形参,该形参可用于访问目标方法的返回值。
    @Around             环绕通知                                          使用时需要指定一个value属性值,用于指定一个切入点表达式。
    @AfterThrowing      异常通知                                          使用时需要指定pointcut/value属性值,用于指定切入点表达式。还能使用throwing属性指定一个形参,该形参可用于访问目标方法抛出的异常。
    @After              最终final通知,不管是否异常,该通知都会执行            使用时需要指定一个value属性值,用于指定一个切入点表达式。
    @DeclareParents     引介通知
    @Aspect             用在类上,表示当前类为一个切面类
    
    在通知中可以使用了 JoinPoint 接口及其子接口 ProceedingJoinPoint 作为参数来获得目标对象的————类名,方法名,方法参数等。
    ★环绕通知必须接受一个类型为 ProceedingJoinPoint 的参数, 返回值也必须是 Object 类型,且需要抛出异常。返回值即为目标方法的返回值★
    ★异常通知可以传入Throwable类型的参数 来接收异常信息★
    

    切入点表达式:
    格式:execution( * com.example.aop.*.*(..))
        execution:就是表达式的主体。
        第一个*:返回类型,可以用代表所有类型
        com.example.aop:表示需要拦截的路径包名
        第二个*:类名,可以用
    代表所有类
        第三个*:方法名,可以用*表示所有方法
        (..):..表示任意参数

    还支持通配符的使用:

    1) *:匹配所有字符
    2) ..:一般用于匹配多个包,多个参数
    3) +:表示类及其子类
    4)运算符有:&&,||,!  AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。
    

    除了上述表达式写法,aop还支持如下的几种写法:
    参考
    @Around("within(类型表达式)")

    within(cn.javass..*)                        cn.javass包及子包下的任何方法执行
    
    within(cn.javass..IPointcutService+)        cn.javass包或所有子包下IPointcutService类型及子类型的任何方法
    
    within(@cn.javass..Secure *)                持有cn.javass..Secure注解的任何类型的任何方法,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
    

    @Around("@within(注解类型))

    @within cn.javass.spring.chapter6.Secure)   任何目标对象对应的类型持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
    

    @Around("target(类型全限定名)")

    target(cn.javass.spring.chapter6.service.IPointcutService)      当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法
    target(cn.javass.spring.chapter6.service.IIntroductionService)  当前目标对象(非AOP对象) 实现了IIntroductionService 接口的任何方法不可能是引入接口
    

    @Around("@target(注解类型)")

    @target (cn.javass.spring.chapter6.Secure)    任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
    

    @Around("args(参数类型列表)")

    args (java.io.Serializable,..)         任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数的方法执行,args指定的参数类型是在运行时动态匹配的
    

    @Around("@args(注解列表)")

    @args (cn.javass.spring.chapter6.Secure)   任何一个只接受一个参数的方法,且方法运行时传入的参数持有注解 cn.javass.spring.chapter6.Secure;动态切入点,类似于arg指示符;
    

    @Around("@annotation(注解类型)")

    @annotation(cn.javass.spring.chapter6.Secure )    当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配
    

    @Around("this(类型全限定名)")

    this(cn.javass.spring.chapter6.service.IPointcutService)         当前AOP对象实现了 IPointcutService接口的任何方法
    this(cn.javass.spring.chapter6.service.IIntroductionService)     当前AOP对象实现了 IIntroductionService接口的任何方法也可能是引入接口
    

    关于JoinPoint对象介绍:
    JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。

    补充aop相关概念
        连接点(Join point)
            连接点是在应用执行过程中能够插入切面的一个点。
        切点(Pointcut)
            一个切面并不需要通知应用的所有连接点,切点有助于缩小切面所通知的连接点范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。
            因此,切点其实就是定义了需要执行在哪些连接点上执行通知。
        切面(Aspect)
            切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和在何处完成其功能。
        织入(Weaving)
            织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。

    public interface JoinPoint {  
       String toString();          //连接点所在位置的相关信息  
       String toShortString();     //连接点所在位置的简短相关信息  
       String toLongString();      //连接点所在位置的全部相关信息  
       Object getThis();           //返回AOP代理对象  
       Object getTarget();         //返回目标对象  
       Object[] getArgs();         //返回被通知方法参数列表  
       Signature getSignature();   //返回当前连接点签名  
       SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置  
       String getKind();           //连接点类型  
       StaticPart getStaticPart(); //返回连接点静态部分  
      }  
    

    关于ProceedingJoinPoint对象介绍:
    ProceedingJoinPoint对象是JoinPoint的子接口。

    主要多了如下两个方法:
    Object proceed() throws Throwable              //执行目标方法 
    Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法 
    

    关于Signature对象介绍:
    Signature对象:是一个接口。用于获取或记录有关连接点的反射信息。他的子接口还提供了很多额外的实用方法。
    常用子接口如下:

    UnlockSignature       实现类     UnlockSignatureImpl
    LockSignature         实现类     LockSignatureImpl
    CatchClauseSignature  实现类     CatchClauseSignatureImpl
    MemberSignature       
          子接口     FieldSignature                     实现类   FieldSignatureImpl
          子接口     CodeSignature[它还有很多子接口]      实现类   CodeSignatureImpl
    

    常用方法如下:

    String toString();         
    String toShortString();        //返回此签名的字符串缩写形式
    String toLongString();         //返回此签名的字符串扩展形式
    String getName();              //返回此签名的方法【即返回方法名】
    int    getModifiers();         //以整数形式返回此签名方法的修饰符。 
    Class  getDeclaringType();     //返回一个java.lang.Class对象,该对象表示声明此成员的类或接口。
    String getDeclaringTypeName(); //返回声明类型的全限定名称
    

    关于签名咋个理解看下面的代码

    5. SpringBoot整合AspectJ实现日志记录

    ★也可以AspectJ+自定义注解来做,这里没有展示这种★

    5.1 导入依赖

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

    5.2 定义切面类

    ① 在类上使用 @Component 注解 把切面类加入到IOC容器中
    ② 在类上使用 @Aspect 注解 使之成为切面类

    @Aspect
    @Component
    public class LogAspect {
    	private Logger logger = LoggerFactory.getLogger(LogAspect.class);
    	
    	/**
    	 * 定义切入点,切入点为com.jsy.community下的函数
    	 */
    	@Pointcut("execution( * com.lihao.community..*.*(..)) && !execution(* com.lihao.community.intercepter..*.*(..)) && !execution(* com.lihao.community.exception..*.*(..))")
    	public void webLog() {
    	}
    	
    	/**
    	 * 前置通知:在连接点之前执行的通知
    	 *
    	 * @param joinPoint
    	 * @throws Throwable
    	 */
    	@Before("webLog()")
    	public void doBefore(JoinPoint joinPoint) throws Throwable {
    		// 接收到请求,记录请求内容
    		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    		if (attributes != null) {
    			HttpServletRequest request = attributes.getRequest();
    			// 记录下请求内容    
    			SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ");
    			Calendar ca = Calendar.getInstance();
    			String time = df.format(ca.getTime());
    			logger.info("");
    			logger.info("访问时间 : " + time);
    			logger.info("访问路径 : " + request.getRequestURL().toString());
    			logger.info("请求方式 : " + request.getMethod());
    			logger.info("访问方法 : " + joinPoint.getSignature().getName());    //签名?
    			logger.info("访问IP : " + request.getRemoteAddr());
    			logger.info("方法参数 : " + Arrays.toString(joinPoint.getArgs()));
    		}
    	}
    	
    	/**
    	 * @return void
    	 * @Author lihao
    	 * @Description 后置通知
    	 * @Date 2021/1/19 17:09
    	 * @Param [ret]
    	 **/
    	@AfterReturning(returning = "ret", pointcut = "webLog()")
    	public void doAfterReturning(Object ret) throws Throwable {
    		// 处理完请求,返回内容
    		logger.info("返回结果 : " + ret);
    	}
    	
    	/**
    	 * @return void
    	 * @Author lihao
    	 * @Description 异常通知
    	 * @Date 2021/1/19 17:43
    	 * @Param [joinPoint, ex]
    	 **/
    	@AfterThrowing(pointcut = "webLog()", throwing = "ex")
    	public void afterThrowing(JoinPoint joinPoint, Exception ex) {
    		String methodName = joinPoint.getSignature().getName();
    		logger.info("异常信息 : " + methodName + "() 出现了异常——————" + ex.getMessage());
    	}
    }
    
    

    6 SpringBoot整合AspectJ实现缓存[利用aop做延时双删]

    实现原理:
    热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存,毕竟强大到冒泡的QPS和极强的稳定性不是所有类似工具都有的,而且相比于memcached还提供了丰富的数据类型可以使用,另外,内存中的数据也提供了AOF和RDB等持久化机制可以选择,要冷、热的还是忽冷忽热的都可选。

    结合具体应用需要注意一下:很多人用spring的AOP来构建redis缓存的自动生产和清除,过程可能如下:

    Select 数据库前查询redis,有的话使用redis数据,放弃select 数据库,没有的话,select 数据库,然后将数据插入redis

    update或者delete数据库钱,查询redis是否存在该数据,存在的话先删除redis中数据,然后再update或者delete数据库中的数据

    上面这种操作,如果并发量很小的情况下基本没问题,但是高并发的情况请注意下面场景:

    为了update先删掉了redis中的该数据,这时候另一个线程执行查询,发现redis中没有,瞬间执行了查询SQL,并且插入到redis中一条数据,回到刚才那个update语句,这个悲催的线程压根不知道刚才那个该死的select线程犯了一个弥天大错!于是这个redis中的错误数据就永远的存在了下去,直到下一个update或者delete。

  • 相关阅读:
    使用纯 CSS 实现响应式的图片显示效果
    10个帮助你快速调试和排错的小技巧
    《JavaScript 实战》:JavaScript 实现拖拽缩放效果
    周末发福利了!26个免费的HTML5模版
    程序人生的四个象限和两条主线
    50份简历设计,助你找到梦寐以求的工作
    6个重构方法可帮你提升 80% 的代码质量
    开发者必须收藏的6款源码搜索引擎
    常用的20个强大的 Sublime Text 插件
    你知道吗?.NET Framework 4.5 五个很棒的特性
  • 原文地址:https://www.cnblogs.com/itlihao/p/14298681.html
Copyright © 2011-2022 走看看