Spring AOP的使用

1 篇文章 0 订阅

不用AOP会有什么问题

常规:OOP(Object Oriented Programming,面向对象编程)中,是按业务流程进行程序的设计,这样,不同的业务之间是相互独立的。
需求:在业务的每个方法执行时,需要将日志打印输出到指定地方、要进行权限认证、有事务的要求。
方案:在每个方法的首尾都加上相关代码,这样导致每个方法中除了要实现正常的业务逻辑外,还得加上日志、权限、事务的代码,代码非常冗余。这不是一个好的实现。

缺点:

  1. 工作量特别大,如果项目中有多个类,多个方法,则要修改多次。
  2. 违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,而为了增加功能把每个方法都修改了,也不便于维护。
  3. 违背了设计原则:单一职责(SRP),每个方法除了要完成自己本身的功能,还要计算耗时、延时;每一个方法引起它变化的原因就有多种。
    “一个方法只做一件事情”,方法除了包含业务逻辑代码外还需要加例如日志、事务等相关操作的代码或代码引用。这样我们一个方法就不是做一件事情,而是做了业务逻辑、日志、事务三件事情。因为日志、事务等属于通用型的功能,所以可以定义成一个切面,这样可以在代码需要日志和事务的时候切入程序。来达到一个方法只做一件事情的目的。
  4. 违背了设计原则:依赖倒转(DIP),抽象不应该依赖细节,两者都应该依赖抽象。而在Test类中,Test与Math都是细节。
         因此就需要用到Spring中的AOP!

AOP是什么

       AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP的补充和完善。
       OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合,是一种纵向的关系。
       AOP则是对纵向关系进行了横切,也就是将纵向关系中的对象、方法,通过切面的形式切了一刀,这样在执行业务方法的时候,就会通过这个切面,完成日志、权限、事务等通用功能。

AOP的底层实现为代理模式。
在这里插入图片描述

AOP核心概念

  1. 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
  2. 切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
  3. 连接点(joinpoint):被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
  4. 切入点(pointcut):对连接点进行拦截的定义
  5. 通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置(before)、后置(after)、异常(afterThrowing)、最终(afterReturn)、环绕(around)通知五类
  6. 目标对象:代理的目标对象
  7. 织入(weave):将切面应用到目标对象并导致代理对象创建的过程
  8. 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

Spring AOP的最原始实现为引介增强,即DelegatingIntroductionInterceptor,这个将在后续文章里写。

AOP的实现

如果要使用aop功能,不论哪种方法,都需要在XML中添加以下内容来支持AOP

<!-- 支持aop,proxy-target-class="true"指默认用Cglib,否则用JDK -->
<aop:aspectj-autoproxy proxy-target-class="true"/>

用到的POM,Spring的AOP包,一般在引用Spring-webmvc后,就都包含了

<!-- AspectJ的相关包 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${aspectj.version}</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>${aspectj.version}</version>
</dependency>

1. 通过XML和Java代码

  1. 要被切的方法(其实也就是普通的方法,我以controller为例)
@RestController
@RequestMapping(value = "/my/second")
public class MySecondController {
    private static final Logger LOGGER = LogManager.getLogger(MySecondController.class);

    @RequestMapping(value = "/{pathValue}", method = RequestMethod.GET)
    public String getFirst(@PathVariable String pathValue, HttpServletRequest request, HttpServletResponse response) throws Exception {
        LOGGER.info("开始getFirst方法,path={}", pathValue);
        if (pathValue.startsWith("s")) {
            throw new Exception();
        }
        return "Hello world";
    }
}
  1. 新加一个切面类,这个类中的方法为在执行普通方法的时候,需要做的事,也就是日志、权限、事务等
public class LoggerAspectByXml {
    private static final Logger LOGGER = LogManager.getLogger(LoggerAspectByXml.class);

    /**
     * @Title: myBeforeMethod
     * @Description: 前置,业务方法执行前
     * @param: joinPoint
     */
    public void myBeforeMethod(JoinPoint joinPoint) throws Exception {
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        LOGGER.info("myBeforeMethod 访问的类名:{},方法名:{},参数为:{}", simpleName, methodName, args);
        LOGGER.info("前置完成,准备访问业务方法:{}", methodName);
    }

    /**
     * @Title: myAfterMethod
     * @Description: 后置,无论业务方法是否发生异常都会执行, 所以访问不到方法的返回值
     * @param: joinPoint
     */
    public void myAfterMethod(JoinPoint joinPoint) throws Exception {
        LOGGER.info("业务方法完成,获取不到返回值");
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        LOGGER.info("myAfterMethod 访问的类名:{},方法名:{},参数为:{}", simpleName, methodName, args);
    }

    /**
     * @Title: myAfterReturnMethod
     * @Description: 只有当业务方法正常返回时,才执行
     * @param: joinPoint
     * @param: result,这个参数的名字必须与XML中的一致
     */
    public void myAfterReturnMethod(JoinPoint joinPoint, Object result) throws Exception {
        LOGGER.info("业务方法完成,返回值是:{}", result);
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        LOGGER.info("myAfterReturnMethod 访问的类名:{},方法名:{},参数为:{}", simpleName, methodName, args);
    }
	/**
     * @Title: myAfterThrowingMethod
     * @Description: 只有当业务方法抛出异常时,才执行
     * @param: joinPoint
     * @param: ex,这个参数的名字必须与XML中的一致
     */
    public void myAfterThrowingMethod(JoinPoint joinPoint, Exception ex) throws Exception {
        LOGGER.info("异常访问完成,异常:{}", ex);
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        LOGGER.info("myAfterThrowingMethod 访问的类名:{},方法名:{},参数为:{}", simpleName, methodName, args);
    }

    /**
     * @Title: myAroundMethod
     * @Description: 切点为around方法,ProceedingJoinPoint只能用于aroundMethod
     * @param: point
     * @return: java.lang.Object
     */
    public Object myAroundMethod(ProceedingJoinPoint point) {
        String simpleName = point.getTarget().getClass().getSimpleName();
        String methodName = point.getSignature().getName();
        List<Object> args = Arrays.asList(point.getArgs());
        LOGGER.info("myAroundMethod 访问的类名:{},方法名:{},参数为:{}", simpleName, methodName, args);

        Object result;
        try {
            LOGGER.info("开始访问的业务方法名:{}", methodName);
            result = point.proceed();
            LOGGER.info("结束访问,返回值:{}", result);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            LOGGER.error("出错:", throwable);
            throw new RuntimeException(throwable);
        }

        LOGGER.info("全部完成:{}.{}", simpleName, methodName);
        return result;
    }
}
  1. XML中的配置
    在Spring的xml中添加如下内容:
	<!-- 支持aop -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

    <!-- 实现AOP的方式:编写类和XML中的配置 -->
    <bean id="logAspect" class="com.my.springdemo.filter.LoggerAspectByXml"/>
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.my.springdemo.controller.*Controller.*(..))"/>
        <aop:aspect order="1" ref="logAspect">
            <!-- 前置标签 -->
            <aop:before method="myBeforeMethod" pointcut-ref="pointcut"/>
            <!-- 后置标签 -->
            <aop:after method="myAfterMethod" pointcut-ref="pointcut"/>
            <!-- 最终标签,returning属性只有after-returning这个标签有,且值必须与方法中的形参名一致 -->
            <aop:after-returning method="myAfterReturnMethod" pointcut-ref="pointcut" returning="result"/>
            <!-- 异常标签,throwing属性只有after-throwing这个标签有,且值必须与方法中的形参名一致 -->
            <aop:after-throwing method="myAfterThrowingMethod" pointcut-ref="pointcut" throwing="ex"/>
            <!-- 环绕标签 -->
            <aop:around method="myAroundMethod" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

从功能上看,用aroundMethod这个就可以实现其他四个的功能。

2. 通过Java代码

@Aspect    //该标签把LoggerAspectByAnnotation类声明为一个切面
@Order(1)  //设置切面的优先级:如果有多个切面,可通过设置优先级控制切面的执行顺序(数值越小,优先级越高)
@Component //该标签把LoggerAspectByAnnotation类放到IOC容器中
public class LoggerAspectByAnnotation {
    private static final Logger LOGGER = LogManager.getLogger(LoggerAspectByAnnotation.class);

    @Pointcut("execution(* com.my.springdemo.controller.*Controller.*(..))")
    public void declearJoinPointExpression() {
    }

    /**
     * @Title: myBeforeMethod
     * @Description: 前置,业务方法执行前
     * @param: joinPoint
     */
//    @Before("declearJoinPointExpression()")
    public void myBeforeMethod(JoinPoint joinPoint) throws Exception {
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        LOGGER.info("myBeforeMethod 访问的类名:{},方法名:{},参数为:{}", simpleName, methodName, args);
        LOGGER.info("前置完成,准备访问业务方法:{}", methodName);
    }

    /**
     * @Title: myAfterMethod
     * @Description: 后置,无论业务方法是否发生异常都会执行, 所以访问不到方法的返回值
     * @param: joinPoint
     */
//    @After("declearJoinPointExpression()")
    public void myAfterMethod(JoinPoint joinPoint) throws Exception {
        LOGGER.info("业务方法完成,获取不到返回值");
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        LOGGER.info("myAfterMethod 访问的类名:{},方法名:{},参数为:{}", simpleName, methodName, args);
    }

    /**
     * @Title: myAfterReturnMethod
     * @Description: 只有当业务方法正常返回时,才执行
     * @param: joinPoint
     * @param: result
     */
//    @AfterReturning(value = "declearJoinPointExpression()", returning = "result")
    public void myAfterReturnMethod(JoinPoint joinPoint, Object result) throws Exception {
        LOGGER.info("业务方法完成,返回值是:{}", result);
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        LOGGER.info("myAfterReturnMethod 访问的类名:{},方法名:{},参数为:{}", simpleName, methodName, args);
    }

    /**
     * @Title: myAfterThrowingMethod
     * @Description: 只有当业务方法抛出异常时,才执行
     * @param: joinPoint
     * @param: ex
     */
//    @AfterThrowing(value = "declearJoinPointExpression()", throwing = "ex")
    public void myAfterThrowingMethod(JoinPoint joinPoint, Exception ex) throws Exception {
        LOGGER.info("异常访问完成,异常:{}", ex);
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        LOGGER.info("myAfterThrowingMethod 访问的类名:{},方法名:{},参数为:{}", simpleName, methodName, args);
    }

    /**
     * @Title: myAroundMethod
     * @Description: 切点为around方法,ProceedingJoinPoint只能用于aroundMethod
     * @param: point
     * @return: java.lang.Object
     */
    @Around("declearJoinPointExpression()")
    public Object myAroundMethod(ProceedingJoinPoint point) {
        String simpleName = point.getTarget().getClass().getSimpleName();
        String methodName = point.getSignature().getName();
        List<Object> args = Arrays.asList(point.getArgs());
        LOGGER.info("myAroundMethod 访问的类名:{},方法名:{},参数为:{}", simpleName, methodName, args);

		//获取请求的URI,可以根据这个URI来进一步确定要不要切,面试可能会问哦!
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String uri = request.getRequestURI();
        LOGGER.info("请求的URI为:{}", uri);

        Object result;
        try {
            LOGGER.info("开始访问的方法名:{}", methodName);
            result = point.proceed();
            LOGGER.info("结束访问,返回值:{}", result);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            LOGGER.error("出错:", throwable);
            throw new RuntimeException(throwable);
        }

        LOGGER.info("完成:{}.{}", simpleName, methodName);
        return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值