Spring Aop详解以及管理日志实战

目录

一、什么是面向切面编程?

二 、SpringAop实现

实现方式一: 使用xml完成定义切面的功能

实现方式二: 使用注解来定义切面


一、什么是面向切面编程?

        面向切面编程是Java语言提供的一种编程思想,通过切面来管理共性功能,如日志、事务和安全,SpringAop是面向切面编程的一种代表,通过对多模块下共同功能的统一管理,来控制业务逻辑与公有逻辑的解耦,而散布于应用多处共有的功能称为横切关注点,把这些横切关注点与业务逻辑相分离是面向切面编程需要解决的问题。

二 、SpringAop实现

实现方式一: 使用xml完成定义切面的功能

第一步,搭建项目环境,工程目录图如下:

添加依赖,spring-context包,aspectj包:

 <dependency>
	   <groupId>org.springframework</groupId>
	   <artifactId>spring-context</artifactId>
	   <version>4.3.9.RELEASE</version>
    </dependency>
    
    
      <dependency>
	   <groupId>org.aspectj</groupId>
	   <artifactId>aspectjrt</artifactId>
	   <version>1.8.8</version>
    </dependency>

    <dependency>
	   <groupId>org.aspectj</groupId>
	   <artifactId>aspectjweaver</artifactId>
	   <version>1.8.3</version>
    </dependency>

第二步: 新建一个接口

package com.hand.proxy.aop;

public interface EatInter {
    void eat();
}

并去实现它:

package com.hand.proxy.aop;

public class People implements EatInter {

	public void eat() {
		System.out.println("吃饭!");
	}

}

第三步,创建切面类

package com.hand.proxy.aop;


public class DoSomethingHelp {
	
	/**
	   * 切面类,指定公有的方执行的一些动作
	 */
	
	public void eatPoint() {
       System.out.println("切点");
	}
	
	public void beforeEat() {
	    System.out.println("吃饭之前,我们应该去洗手!");	
	}
	
	
	public void afterEat() {
		System.out.println("吃饭后,去睡觉!");
	}

}

第四步,添加applicationContext.xml文件,配置切面类和bean,通过xml文件的 方式来配置切面和通知类型:

其中创建的切点时,指定连接点,此处把接口中的eat()方法来作为连接点。

<aop:config>  标签用来配置aop

<aop:aspectj> 标签用来指定切面,其中ref属性就是用来引用切面类。

<aop:before> 和<aop:after> 标签表示通知的类型

<?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"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"

    xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        http://www.springframework.org/schema/util  http://www.springframework.org/schema/util/spring-util-4.0.xsd
        http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"> 
   
   <bean id="people" class="com.hand.proxy.aop.People" ></bean>
   <bean id='aspectJ' class="com.hand.proxy.aop.DoSomethingHelp"></bean>
   
   <!-- 需要添加此配置,将需要代理的类织入到切面中 -->
   <aop:aspectj-autoproxy proxy-target-class="true"/> 
   <aop:config>
     <aop:aspect ref="aspectJ">
        <aop:after method="afterEat" pointcut="execution(* com.hand.proxy.aop.EatInter.eat(..))"/>
        <aop:before method="beforeEat" pointcut="execution(* com.hand.proxy.aop.EatInter.eat(..))"></aop:before>
     </aop:aspect> 
   </aop:config>
   
</beans>

注:如果此处不添加如下标签 :

<aop:aspectj-autoproxy proxy-target-class="true"/>

会报错:

org.springframework.beans.factory.BeanNotOfRequiredTypeException:
 Bean named 'people' is expected to be of type 'com.hand.proxy.aop.People' but was actually of type 'com.sun.proxy.$Proxy6'

原因是:需要通过此配置将切点织入到目标的切面类中,默认的proxy-target-class为false。

测试案例:

package com.hand.proxy.aop;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class AopTest {
	
	
	@Test
	public void testAop(){
	ApplicationContext ac
		 =new ClassPathXmlApplicationContext("applicationContext.xml");
	People people=ac.getBean("people",People.class);
	people.eat();
	}

}

打印结果如下:

吃饭之前,我们应该去洗手!
吃饭!
吃饭后,去睡觉!

实现方式二: 使用注解来定义切面

  • @Aspect:  定义切面。
  • @PointCut: 定义切点。配置execution使用,扫描到所有的controller,支持模糊匹配,一级目录相当于一个 *,  方法任意参数就用(..)
  • @Around:  定义切点的通知方式为环绕通知。

   我们可以通过ProceedingJoinPoing获取到方法对象,拿到执行的方法对象和方法参数。

Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

获取到方法参数:

joinPoint.getArgs()

如果想要获取到完整的参数列表,包含参数类型和参数值。可以根据方法对象method和所有的参数args生成一个完整的参数列表,完整代码如下: 

package com.example.shop.aop;

import com.example.shop.dto.ParameterDto;
import io.swagger.annotations.ApiOperation;
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.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Aspect
@Component
@Order(1)
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);


    //定义切点,指向controller
    @Pointcut("execution(public * com.example.shop.controller.*.*(..))")
    public void webLog() {

    }

    //定义环绕通知
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求信息
        Object result = joinPoint.proceed();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        long endTime = System.currentTimeMillis();
        long spentTime = (endTime - startTime);
        logger.info("花费时间:{}ms", spentTime);
        logger.info("ip地址:{}", request.getRemoteHost());
        logger.info("请求参数:{}", getParameter(method, joinPoint.getArgs()));
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            logger.info("操作:{}", apiOperation.value());
        }
        return result;
    }

//    private Object getParameter(Method method, Object[] args) {
//        List<Object> argList = new ArrayList<>();
//        Parameter[] parameters = method.getParameters();
//        for (int i = 0; i < parameters.length; i++) {
//            //将RequestBody注解修饰的参数作为请求参数
//            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
//            if (requestBody != null) {
//                argList.add(args[i]);
//            }
//            //将RequestParam注解修饰的参数作为请求参数
//            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
//            if (requestParam != null) {
//                Map<String, Object> map = new HashMap<>();
//                String key = parameters[i].getName();
//                if (!StringUtils.isEmpty(requestParam.value())) {
//                    key = requestParam.value();
//                }
//                map.put(key, args[i]);
//                argList.add(map);
//            }
//        }
//        if (argList.size() == 0) {
//            return null;
//        } else if (argList.size() == 1) {
//            return argList.get(0);
//        } else {
//            return argList;
//        }
//    }

    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        Object[] parametertype = method.getParameterTypes();
        for (int i = 0; i < parameters.length; i++) {
            Map<String, String> parametersValuesMap = new HashMap<>();
            parametersValuesMap.put(new ParameterDto(parametertype[i], parameters[i].getName()).toString(), args[i].toString());
            argList.add(parametersValuesMap);
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }

}

主要包含5种通知类型:

前置通知:在方法执行前通知@Before(value = “”)

后置通知:在方法正常执行完成进行通知,可以访问到方法的返回值的。@AfterReturning(value = “”)
环绕通知:可以将要执行的方法(point.proceed())进行包裹执行,可以在前后添加需要执行的操作,@Around(value = “”)
异常通知:在方法出现异常时进行通知,可以访问到异常对象,且可以指定在出现特定异常时在执行通知。@AfterThrowing(value = “”)
方法执行后通知: 在目标方法执行后无论是否发生异常,执行通知,不能访问目标方法的执行的结果。
@After(value = “”)

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring AOP(面向切面编程)是Spring框架中的一个模块,用于提供横切关注点(Cross-Cutting Concerns)的支持。横切关注点是与应用程序的核心业务逻辑无关的功能,例如日志记录、性能统计、事务管理等。 在Spring AOP中,通过定义切面(Aspect)来捕获横切关注点,并将其应用到目标对象的方法中。切面由切点(Pointcut)和通知(Advice)组成。切点定义了在何处应用通知,通知则定义了在切点处执行的操作。 Spring AOP支持以下几种类型的通知: 1. 前置通知(Before Advice):在目标方法执行之前执行的通知。 2. 后置通知(After Advice):在目标方法执行之后执行的通知,不管方法是否抛出异常。 3. 返回通知(After Returning Advice):在目标方法成功执行并返回结果后执行的通知。 4. 异常通知(After Throwing Advice):在目标方法抛出异常后执行的通知。 5. 环绕通知(Around Advice):围绕目标方法执行的通知,可以在方法调用前后执行自定义操作。 除了通知,Spring AOP还支持引入(Introduction)和切点表达式(Pointcut Expression)等功能。引入允许为目标对象添加新的接口和实现,而切点表达式则允许开发人员定义切点的匹配规则。 要在Spring应用程序中使用AOP,需要进行以下步骤: 1. 引入Spring AOP的依赖。 2. 配置AOP代理。 3. 定义切面和通知。 4. 配置切点和通知之间的关系。 总之,Spring AOP提供了一种便捷的方式来处理横切关注点,使得开发人员可以将关注点与核心业务逻辑分离,提高代码的可维护性和可重用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乌托邦钢铁侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值