欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring AOP 的实现方式(以日志管理为例)

程序员文章站 2022-04-25 20:06:29
...

Spring AOP 的实现方式(以日志管理为例)

本Markdown编辑器使用[StackEdit][6]修改而来,用它写博客,将会带来全新的体验哦:

  • Find Action Ctrl+Shift+A,在此对话框中输入想操作的英文立即出现想的操作

  • System.out.println输入sout即可

  • for循环N输入N.for即可

  • return n;输入n.return即可

  • 离线写博客

  • 导入导出Markdown文件

  • 丰富的快捷键


AOP的概念

AOP(Aspect Oriented Programming),是面向切面编程的技术。AOP基于IoC基础,是对OOP的有益补充,流行的AOP框架有Sping AOP、AspectJ

AOP技术它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性

相关概念:

1、切面(aspect)

散落在系统各处的通用的业务逻辑代码,如上图中的日志模块,权限模块,事务模块等,切面用来装载pointcut和advice

2、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

3、连接点(joinpoint)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

拦截的方法,连接点拦截后变成切入点

6、目标对象(Target Object)

代理的目标对象,指要织入的对象模块,如上图的模块一、二、三

7、织入(weave)

通过切入点切入,将切面应用到目标对象并导致代理对象创建的过程

8、AOP代理(AOP Proxy)

AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理

五种类型的通知

  • Before advice:在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
    < aop:before>

  • After advice:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
    < aop:after>

  • After returnadvice:在某连接点正常完成后执行的通知,不包括抛出异常的情况。
    < aop:after-returning>

  • Around advice:包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法执行的前后实现逻辑,也可以选择不执行方法
    < aop:around>

  • Afterthrowing advice:在方法抛出异常退出时执行的通知。
    < aop:after-throwing>

五种类型的通知

基于xml配置的实现

  • spring-mvc.xml
	<!-- 使用xml配置aop -->  
	<!-- 强制使用cglib代理,如果不设置,将默认使用jdk的代理,但是jdk的代理是基于接口的 -->  
	<aop:config proxy-target-class="true" />    
	<aop:config>  
	<!--定义切面-->  
    <aop:aspect id="logAspect" ref="logInterceptor">  
    <!-- 定义切入点 (配置在com.gray.user.controller下所有的类在调用之前都会被拦截)-->  
    <!-- 
    	第一个*:表示任意返回值
    	com.gray.user.controller表是包路径
    	第二个*:表示包下任意类
    	第三个*:表示类的任意方法
    	(..):表示任意参数
    -->
    <aop:pointcut expression="execution(* com.gray.user.controller.*.*(..))" id="logPointCut"/>  
    <!--方法执行之前被调用执行的-->  
    <aop:before method="before" pointcut-ref="logPointCut"/><!--一个切入点的引用-->  
    <aop:after method="after" pointcut-ref="logPointCut"/><!--一个切入点的引用-->  
    </aop:aspect>  
</aop:config>
  • 切入点通知方法
package com.gray.interceptor;  
      
    import org.springframework.stereotype.Component;  
    import org.slf4j.Logger;  
    import org.slf4j.LoggerFactory;  
      
    @Component  
    public class LogInterceptor {  
        private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);  
        public void before(){  
            logger.info("login start!");  
        }  
          
        public void after(){  
            logger.info("login end!");  
        }  
    }
  • 当我们访问配置切入点的controller时就会走到通知方法

基于注解的实现

  • spring-mvc.xml
 <aop:aspectj-autoproxy proxy-target-class="true">  
 </aop:aspectj-autoproxy> 
  • 切入点通知方法
@Aspect  
@Component  
public class LogInterceptor {  
    private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);  
    @Before(value = "execution(* com.gray.user.controller.*.*(..))")  
    public void before(){  
        logger.info("login start!");  
    }  
    @After(value = "execution(* com.gray.user.controller.*.*(..))")  
    public void after(){  
        logger.info("login end!");  
    }  
}
  • 原理同上,只是在xml文件引用入了aop,并没有指定aop要切入哪个方法,而在通知类上注入@Aspect (告诉spring这个配置类是aop通知类),通过注解方式实现切点的定义。

基于自定义注解的实现

  • spring-mvc.xml
 <aop:aspectj-autoproxy proxy-target-class="true">  
 </aop:aspectj-autoproxy> 
  • 切入点通知方法
	package com.gray.interceptor; 
    import java.lang.reflect.Method;  
    import org.aspectj.lang.JoinPoint;  
    import org.aspectj.lang.annotation.Aspect;  
    import org.aspectj.lang.annotation.Before;  
    import org.aspectj.lang.annotation.Pointcut;  
    import org.aspectj.lang.reflect.MethodSignature;  
    import org.slf4j.Logger;  
    import org.slf4j.LoggerFactory;  
    import org.springframework.stereotype.Component;  
    import com.gray.annotation.Log;  
      
    @Aspect  
    @Component  
    public class LogInterceptor {  
        private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);  
      
        @Pointcut("@annotation(com.gray.annotation.Log)")      
        public void controllerAspect() { 
        }
          
        @Before("controllerAspect()")  
        public void before(JoinPoint joinPoint){  
            logger.info("login before!");   
        } 
        
		@After("controllerAspect()")  
        public void After(JoinPoint joinPoint){  
            logger.info("login After!");   
        } 
    }
  • 定义好切点和通知方法,需要注入切入方式,并在切入方式后面加上切点方法

补充

  • joinPoint参数能获取到访问类及方法名称等一些关键信息
		//获得IP
        final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        System.out.println("访问客户端IP地址为:"+request.getRemoteAddr());

        //类名
        System.out.println("访问类名为:"+joinPoint.getTarget().getClass().getSimpleName());
        // 方法名
        System.out.println("访问方法名为:"+joinPoint.getSignature().getName());
        //参数
        StringBuilder param=new StringBuilder();
        for (int i = 0; i < joinPoint.getArgs().length; i++) {
            param.append("参数").append(i+1).append(":").append(joinPoint.getArgs()[i]);
        }
        System.out.println("访问方法参数为:"+param.toString());