精华内容
下载资源
问答
  • 1. 面向切面编程 以下内容来自百度百科: 定义:面向切面编程(AOP,Aspect Oriented Programming)是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 作用:利用AOP可以对业务逻辑的各个...

    目录

    1. 面向切面编程

    2. AOP核心概念

    3. AOP的实现

    ​4. Spring 对AOP支持

    4.1 支持@Aspect

    4.2 声明一个切面

    4.3 声明一个切入点

    4.4 声明增强

    5. 用AOP实现日志拦截

    5.1 一般的实现

    5.2 仅拦截需要的方法

    5.3 requestId传递

    5.4 关于增强执行的顺序

    6. 思考

    参考


    1. 面向切面编程

    定义:面向切面编程(AOP,Aspect Oriented Programming)是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

    作用:利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

    主要功能:日志记录、性能统计、安全控制、事务处理、异常处理等。

    总结:面向切面编程是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只是修改这个行为即可。

    AOP通过提供另一种思考程序结构的方式来补充了面向对象编程(OOP)。OOP中模块化的基本单元是类(class),而AOP中模块化的基本单元是切面(aspect)。可以这么理解,OOP是解决了纵向的代码复用问题,AOP是解决了横向的代码复用问题。

    Spring的关键组件之一是AOP框架。虽然Spring IOC容器不依赖于AOP,意味着如果你不想使用AOP,则可以不使用AOP,但AOP补充了Spring IOC以提供一个非常强大的中间件解决方案。

    2. AOP核心概念

    • 切面(aspect):在AOP中,切面一般使用@Aspect注解来标识。
    • 连接点(Join Point):在Spring AOP,一个连接点总是代表一次方法的执行。
    • 增强(Advice):在连接点执行的动作。
    • 切入点(Pointcout):说明如何匹配到连接点。
    • 引介(Introduction):为现有类型声明额外的方法和属性。
    • 目标对象(Target Object):由一个或者多个切面建议的对象,也被称为“建议对象”,由于Spring AOP是通过动态代理来实现的,这个对象永远是一个代理对象。
    • AOP代理(AOP proxy):一个被AOP框架创建的对象,用于实现切面约定(增强方法的执行等)。在Spring Framework中,一个AOP代理是一个JDK动态代理或者CGLIB代理。
    • 织入(Weaving):连接切面和目标对象或类型创建代理对象的过程。它能在编译时(例如使用AspectJ编译器)、加载时或者运行时完成。Spring AOP与其他的纯Java AOP框架一样是在运行时进行织入的。

    Spring AOP包括以下类型的增强:

    • 前置增强(Before advice):在连接点之前运行,但不能阻止到连接点的流程继续执行(除非抛出异常)
    • 返回增强(After returning advice):在连接点正常完成后运行的增强(例如,方法返回没有抛出异常)
    • 异常增强(After thorwing advice):如果方法抛出异常退出需要执行的增强
    • 后置增强(After (finally) Advice):无论连接点是正常或者异常退出,都会执行该增强
    • 环绕增强(Around advice):围绕连接点的增强,例如方法的调用。环绕增强能在方法的调用之前和调用之后自定义行为。它还可以选择方法是继续执行或者去缩短方法的执行通过返回自己的值或者抛出异常。

    3. AOP的实现

    AOP的两种实现方式:静态织入(以AspectJ为代表)和动态代理(Spring AOP实现)

    AspectJ是一个采用Java实现的AOP框架,它能够对代码进行编译(在编译期进行),让代码具有AspectJ的AOP功能,当然它也可支持动态代理的方式;

    Spring AOP实现:通过动态代理技术来实现,Spring2.0集成了AspectJ,主要用于PointCut的解析和匹配,底层的技术还是使用的Spring1.x中的动态代理来实现。

     Spring AOP采用了两种混合的实现方式:JDK动态代理和CGLib动态代理。

    • JDK动态代理:Spring AOP的首选方法。每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口。
    • CGLIB:如果目标对象没有实现接口,则可以使用CGLIB代理。 

    比较Spring AOP和AspectJ_JKX_geek的博客-程序员资料- 程序员资料4. Spring 对AOP支持

    Spring可以使用两种方式来实现AOP:基于注解式配置和基于XML配置

    下面介绍基于注解配置的形式

    4.1 支持@Aspect

    如果是Spring Framework,需要使用aspectjweaver.jar包,然后创建我们自己的AppConfig,如下,并加上@EnableAspectJAutoProxy注解开启AOP代理自动配置(Spring Boot默认是开启的,则不需要增加配置),如下:

    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
    
    }

    4.2 声明一个切面

    @Aspect //告诉Spring 这是一个切面
    @Component  //交给Spring容器管理
    public class MyAspect {
    
    }

    可以使用@Aspect来定义一个类作为切面,但是这样,该类并不会自动被Spring加载,还是需要加上@Component注解

    4.3 声明一个切入点

    一个切入点的生命包含两个部分:一个包含名称和任何参数的签名和一个切入点的表达式,这个表达式确定了我们对哪些方法的执行感兴趣。

    我们以拦截Controller层中的MyController中的test方法为例子,代码如下:

    @RestController
    @RequestMapping("/my")
    public class MyController {
    
        @GetMapping("/test")
        public void test() {
            System.out.println("test 方法");
        }
    }

    下面定义一个名为controller的切入点,该切入点与上述的test方法相匹配,切入点需要用@Pointcut注解来标注,如下:

      //表达式
      @Pointcut("execution (public * com.yc.springboot.controller.MyController.test())")
      public void controller(){}; //签名

    切入点表达式的格式如下:

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
    
    execution(方法修饰符(可选) 返回类型 类路径 方法名 参数 异常模式(可选))

    AspectJ描述符如下:

    AspectJ描述符描述
    arg()限制连接点匹配参数为指定类型的执行方法
    @args()限制连接点匹配参数由指定注解标注的执行方法
    execution()用于匹配是连接点的执行方法
    this()限制连接点匹配的AOP代理的bean引用为指定类型的类
    target限制连接点匹配目标对象为指定类型的类
    @target()限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
    within()限制连接点匹配指定的类型
    @within()限制连接点匹配指定注解所标注的类型
    @annotationn限定匹配带有指定注解的连接点

    常用的主要是:execution()

    AspectJ类型匹配的通配符

    通配符含义
    *匹配任何数量字符
    ..匹配任何数量字符的重复
    +匹配指定类型的子类型;仅能用于后缀放在类型模式后面

    常用的匹配规则

    表达式内容
    execution(public * *(..))
    匹配所有public方法
    execution(* set*(..))
    匹配所有方法名开头为set的方法
    execution(* com.xyz.service.AccountService.*(..))
    匹配AccountService下的所有方法
    execution(* com.xyz.service.*.*(..))
    匹配service包下的所有方法
    execution(* com.xyz.service..*.*(..))
    匹配service包或其子包下的所有方法
    @annotation(org.springframework.transaction.annotation.Transactional)
    匹配所有打了@Transactional注解的方法
    bean(*Service)
    匹配命名后缀为Service的类的方法

    4.4 声明增强

    增强与切点表达式相关联,并且在与切点匹配的方法之前、之后或者前后执行。

    在3当中已经对各类增强做了绍,这里就不详细展开了,下面直接罗列了各种增强的声明,用于拦截MyController中的各个方法

    
    @Aspect //告诉Spring 这是一个切面
    @Component  //告诉Spring容器需要管理该对象
    public class MyAspect {
    
        //通过规则确定哪些方法是需要增强的
        @Pointcut("execution (public * com.yc.springboot.controller.MyController.*(..))")
        public void controller() {
        }
    
        //前置增强
        @Before("controller()")
        public void before(JoinPoint joinPoint) {
            System.out.println("before advice");
        }
    
        //返回增强
        @AfterReturning(
                pointcut = "controller()",
                returning = "retVal"
        )
        public void afterReturning(Object retVal) {
            System.out.println("after returning advice, 返回结果 retVal:" + retVal);
        }
    
        //异常增强
        @AfterThrowing(
                pointcut = "controller()",
                throwing = "ex"
        )
        public void afterThrowing(Exception ex) {
            System.out.println("after throwing advice, 异常 ex:" + ex.getMessage());
        }
    
        //后置增强
        @After("controller()")
        public void after(JoinPoint joinPoint) {
            System.out.println("after advice");
        }
    
        //环绕增强
        @Around("controller()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("before advice");
            //相当于是before advice
            Object reVal = null;
            try {
                reVal = joinPoint.proceed();
            } catch (Exception e) {
                //相当于afterthrowing advice
                System.out.println("afterthrowing advice");
            }
            //相当于是after advice
            System.out.println("after advice");
            return reVal;
        }
    }

    需要注意的是:

    1. 在返回增强中,我们需要给@AfterReturing设置returning的值,且需要与方法的参数名一致,用于表示业务方法的返回值
    2. 在异常增强中,需要给@AfterThrowing设置throwing的值,且需要与方法的参数名一致,用于表示业务方法产生的异常
    3. 在环绕增强中,参数为ProceedingJoinPoint类型,它是JoinPoint的子接口,我们需要在这个方法中手动调用其proceed方法来触发业务方法
    4. 在所有的增强方法中都可以申明第一个参数为JoinPoint(注意的是,环绕增强是使用ProceedingJoinPoint来进行申明,它实现了JoinPoint接口)
    5. JoinPoint接口提供了几个有用的方法
      1. getArgs():返回这个方法的参数
      2. getThis():返回这个代理对象
      3. getTarget():返回目标对象(被代理的对象)
      4. getSignature():返回被增强方法的描述
      5. toString():打印被增强方法的有用描述

    下面为Mycontroller测试类:

    @RestController
    @RequestMapping("/my")
    public class MyController {
    
        @GetMapping("/testBefore")
        public void testBefore() {
            System.out.println("testBefore 业务方法");
        }
    
        @GetMapping("/testAfterReturning")
        public String testAfterReturning() {
            System.out.println("testAfterReturning 业务方法");
            return "我是一个返回值";
        }
    
        @GetMapping("/testAfterThrowing")
        public void testAfterThrowing() {
            System.out.println("testAfterThrowing 业务方法");
            int a = 0;
            int b = 1 / a;
        }
    
        @GetMapping("/testAfter")
        public void testAfter() {
            System.out.println("testAfter 业务方法");
        }
    
        @GetMapping("/around")
        public void around() {
            System.out.println("around 业务方法");
        }
    }

    5. 用AOP实现日志拦截

    5.1 一般的实现

    打印日志是AOP的一个常见应用场景,我们可以对Controller层向外提供的接口做统一的日志拦截,用日志记录请求参数、返回参数、请求时长以及异常信息,方便我们线上排查问题,下面是核心类LogAspect的实现

    /**
     * 日志的切面
     */
    @Aspect
    @Component
    public class LogAspect {
    
        @Resource
        private IdWorker idWorker;
    
        @Pointcut("execution (public * com.yc.core.controller.*.*(..))")
        public void log(){}
    
        /**
         * 使用环绕增强实现日志打印
         * @param joinPoint
         * @return
         * @throws Throwable
         */
        @Around("log()")
        public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
            //获得执行方法的类和名称
            String className = joinPoint.getTarget().getClass().getName();
            //获得方法名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            String methodName = signature.getMethod().getName();
            //获得参数
            Object[] args = joinPoint.getArgs();
            long requestId = idWorker.nextId();
            //打印参数
            LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",params:" + JSONObject.toJSONString(args));
            long startTime = System.currentTimeMillis();
            //执行业务方法
            Object result = null;
            try {
                result = joinPoint.proceed();
            } catch (Exception e) {
                LogHelper.writeErrLog(className, methodName, "requestId:" + requestId + ",异常啦:" + LogAspect.getStackTrace(e));
            }
            long endTime = System.currentTimeMillis();
            //打印结果
            LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",耗时:" + (endTime - startTime) +  "ms,result:" + JSONObject.toJSONString(result));
            //返回
            return result;
        }
    
        /**
         * 获取异常的堆栈信息
         * @param throwable
         * @return
         */
        public static String getStackTrace(Throwable throwable)
        {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            try
            {
                throwable.printStackTrace(pw);
                return sw.toString();
            } finally
            {
                pw.close();
            }
        }
    }
    1. 在proceed()方法之前,相当于前置增强,收集类名、方法名、参数,记录开始时间,生成requestId
    2. 在proceed()方法之后,相当于后置增强,并能获取到返回值,计算耗时
    3. 在前置增强时,生成requestId,用于串联多条日志
    4. 使用try、catch包裹proceed()方法,在catch中记录异常日志
    5. 提供了getStackTrace方法获取异常的堆栈信息,便于排查报错详细情况

    5.2 仅拦截需要的方法

    但是上面的日志是针对所有controller层中的方法进行了日志拦截,如果我们有些方法不想进行日志输出,比如文件上传的接口、大量数据返回的接口,这个时候定义切入点的时候可以使用@annotation描述符来匹配加了特定注解的方法,步骤如下:

    1. 先定义一个日志注解Log

    /**
     * 自定义日志注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Log {
    }

    2.用@annotation定义切入点

        @Pointcut("@annotation(com.yc.core.annotation.Log)")
        public void logAnnotation(){}

    3.在想做日志输出的方法上使用注解Log

        @Log
        @PostMapping(value = "testannotation")
        public AOPTestVO testannotation(@RequestBody AOPTestDTO aopTestDTO) {
            AOPTestVO aopTestVO = new AOPTestVO();
            aopTestVO.setCode(1);
            aopTestVO.setMsg("哈哈哈");
            return aopTestVO;
        }

    这样,我们就可以自定义哪些方法需要日志输出了

    5.3 requestId传递

    后来有同事提到,如果这是针对Controller层的拦截,但是Service层也有自定义的日志输出,怎么在Service层获取到上述的requestId呢?

    其实就是我们拦截之后,是否可以针对方法的参数进行修改呢?其实注意是看

    result = joinPoint.proceed();

    我们发现ProceedingJoinPoint还有另外一个带有参数的proceed方法,定义如下:

    public Object proceed(Object[] args) throws Throwable;

    我们可以利用这个方法,在环绕增强中去增加requestId,这样后面的增强方法或业务方法中就能获取到这个requestId了。

    首先,我们先定义一个基类AOPBaseDTO,只有一个属性requestId

    @Data
    @ApiModel("aop参数基类")
    public class AOPBaseDTO {
        @ApiModelProperty(value = "请求id", hidden = true)
        private long requestId;
    }

    然后我们让Controller层接口的参数AOPTestDTO继承上述AOPBaseDTO,如下:

    @Data
    @ApiModel("aop测试类")
    public class AOPTestDTO extends AOPBaseDTO{
    
        @ApiModelProperty(value = "姓名")
        private String name;
    
        @ApiModelProperty(value = "年龄")
        private int age;
    }

    最后在环绕的增强中添加上requestId,如下:

        /**
         * 使用环绕增强实现日志打印
         * @param joinPoint
         * @return
         * @throws Throwable
         */
        @Around("logAnnotation()")
        public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
            //获得执行方法的类和名称
            String className = joinPoint.getTarget().getClass().getName();
            //获得方法名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            String methodName = signature.getMethod().getName();
            //获得参数
            Object[] args = joinPoint.getArgs();
            long requestId = idWorker.nextId();
            for(int i = 0; i < args.length; i++) {
                if (args[i] instanceof AOPBaseDTO) {
                    //增加requestId
                    ((AOPBaseDTO) args[i]).setRequestId(requestId);
                }
            }
            //打印参数
            LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",params:" + JSONObject.toJSONString(args));
            long startTime = System.currentTimeMillis();
            //执行业务方法
            Object result = null;
            try {
                result = joinPoint.proceed(args);
            } catch (Exception e) {
                LogHelper.writeErrLog(className, methodName, "requestId:" + requestId + ",异常啦:" + LogAspect.getStackTrace(e));
            }
            long endTime = System.currentTimeMillis();
            //打印结果
            LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",耗时:" + (endTime - startTime) +  "ms,result:" + JSONObject.toJSONString(result));
            //返回
            return result;
        }

    我们运行起代码,访问一下,下面是运行结果:

    可以看到,我们的业务方法中已经能获取到requestId,如果Service层需要,可以通过传递AOPTestDTO,从中获取。

    5.4 关于增强执行的顺序

    1. 针对不同类型的增强,顺序固定的,比如before在after前面

    2. 针对同一切面的相同类型的增强,根据定义先后顺序依次执行

    3. 针对不同切面的相同增强,可以通过使我们的切面实现Ordered接口,重写getOrder方法,返回值最小,优先级越高。注意的是before和after的相反的,before的优先级越高越早执行,after的优先级越高,越晚执行

    6. 思考

    1. 代理对象是什么时候创建的?

    2. 当存在多个不同类型增强时,执行顺序是怎么保证的?

    3. 真正的业务方法是什么时候调用的,怎么做到只调用一次?

    参考

    1.  AOP(面向切面编程)_百度百科

    2. Core Technologies

    展开全文
  • 1 spring容器中bean特性 Spring容器的javabean对象默认是单例的。 通过在xml文件中,配置可以使用某些对象为多列。 Spring容器中的javabean对象默认是立即加载(立即实例化:spring加载完成,立即创建对象) ...

    1 spring容器中bean特性

    Spring容器的javabean对象默认是单例的。

    通过在xml文件中,配置可以使用某些对象为多列。

    Spring容器中的javabean对象默认是立即加载(立即实例化:spring加载完成,立即创建对象)

    scope:属性

            singleton:默认值为单例,默认也是立即加载,在加载完成spring容器的时候,bean对象已经创建完成   

            prototype:多例的,默认懒加载,spring容器加载完成的时候,不会创建bean的对象,只有从容器获得bean对象的时候,才进行bean对象的实例化

            request: 将创建的javabean对象,封装到request范围

            session:将创建的javabean对象,封装到session范围

    Spring容器bean的对象生命周期:

    Bean对象的创建一直到销毁为bean的生命周期。

             生命周期的开始:

             如果为单例,由加载完spring容器开始

             如果为多例,由从容器获得bean对象开始

             实例化

             初始化

             服务

             销毁(单例:关闭容器的时候,多例由jvm自动回收)

    2 spring的AOP面向切面编程

    2.1 模拟银行转账业务

    需求:实现银行的转账功能,在转账的时候需要完成

    1 身份认证(登陆)

    2 权限的验证

    3 转账实现

    4 历史交易记录,

    分析:1,2,4三个功能对于银行的业务,属于公共的功能(共性的功能)

    在功能实现的时候,需要将1,2,4抽取出来,单独实现,

    做到了将共性的功能和核心的业务功能进行了分离

    通过动态代理实现:共性的功能和核心业务功能的合并,产生核心业务对象的

     在代码实现的时候,进行了功能实现的分离:

     代码开发的进行分离,程序在运行的时候进行合并。

    2.2 springAOP的思想

    在系统开发中,将系统的共性的公共的功能独立实现,在程序运行的过程中,将共性功能和核心的业务功能,进行整合。

    好处:

    1 完成共性功能和核心业务功能的解耦合

    2 提供共性功能的复用性。

    2.3springAOP的概念 

    Aspect切面:封装共性功能的(增强功能的)类

    Advice通过:切面类中封装的增强功能的方法。

    PointCut:切入点,是一个集合的概念,该集合的表达使用一个正则表达式表达

          所有核心业务对象的所有方法的前后(事务处理AOP典型的应用)

    JoinPoint:连接点,程序中需要加入advice的地方,而且正在执行的ponitCut

    织入(Weaving):将aspect和核心业务对象,进行整合的过程。

    3 springAOP的实现

    3.1通过特定接口实现

    Aop通知的类型:

          Before:前置通知

          After:后置通知

          Around:环绕通知

          Throwing:异常通知

    需求:实现在业务对象中的方法执行的时候,记录日志功能

    3.1.1前置通知

    package org.guangsoft.utils;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.Date;
    import org.springframework.aop.MethodBeforeAdvice;
    /****
     * 前置增强:
     * MethodBeforeAdvice 接口表示重写的方法为前置advice
     * ***/
    public class BeforeLog implements MethodBeforeAdvice
    {
        @Override
        public void before(Method method,
        Object[] args, Object obj)
        throws Throwable
        {
            System.out.println(method);
            System.out.println(Arrays.toString(args));
            System.out.println(obj);
            System.out.println("BeforeLog-------------" + new Date());
        }
    }

    AOP配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 到入xml文件的约束 -->
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
        <!-- 实例化BeforeLog对象 -->
        <bean id="bf" class="org.guangsoft.utils.BeforeLog"></bean>
        <!-- 实例化service对象 -->
        <bean id="us" class="org.guangsoft.service.impl.UsersServiceImpl" />
        <!-- 进行aop的配置,产生代理对象 -->
        <aop:config>
            <!-- 声明切入点 -->
            <aop:pointcut expression="execution(* org.guansoft.service.impl.*.*(..))"
                id="pc" />
            <!-- 织入 将通知和切入点进行合并(切面+核心业务对象) -->
            <aop:advisor advice-ref="bf" pointcut-ref="pc" />
        </aop:config>
    </beans>

    3.1.2后置通知

    对业务对象的方法进行后增强。

    package org.guangsoft.utils;
    import java.lang.reflect.Method;
    import java.util.Date;
    import org.springframework.aop.AfterReturningAdvice;
    /***
     * 后置通知
     * ***/
    public class AfterLog implements AfterReturningAdvice
    {
        @Override
        public void afterReturning(Object obj1,// obj1 接收目标方法的返回值
                Method method,
                Object[] args,
                Object obj2) throws Throwable
        {
            // System.out.println(obj1+"----------------------"+obj2);
            System.out.println("AfterLog-------------------" + new Date());
        }
    } 

    AOP配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 到入xml文件的约束 -->
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
        <!-- 实例化BeforeLog对象 -->
        <bean id="bf" class="org.guangsoft.utils.BeforeLog"></bean>
        <bean id="af" class="org.guangsoft.utils.AfterLog"></bean>
        <!-- 实例化service对象 -->
        <bean id="us" class="org.guangsoft.service.impl.UsersServiceImpl" />
        <!-- 进行aop的配置,产生代理对象 -->
        <aop:config>
            <!-- 声明切入点 -->
            <aop:pointcut expression="execution(* org.guangsoft.service.impl.*.*(..))"
                id="pc" />
            <!-- 织入 将通知和切入点进行合并(切面+核心业务对象) -->
            <aop:advisor advice-ref="bf" pointcut-ref="pc" />
            <aop:advisor advice-ref="af" pointcut-ref="pc" />
        </aop:config>
    </beans>

    3.1.3环绕通知

    package org.guangsoft.utils;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.Date;
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    /***
     * 环绕通知
     * ***/
    public class AoundLog implements MethodInterceptor
    {
        /**
         * MethodInvocation中封装了目标对象,调用的方法,方法需要的参数
         * ***/
        @Override
        public Object invoke(MethodInvocation mi) throws Throwable
        {
            Method method = mi.getMethod();
            Object[] args = mi.getArguments();
            Object obj = mi.getThis();
            System.out.println(method);
            System.out.println(Arrays.toString(args));
            System.out.println(obj);
            System.out.println("around------before--------" + new Date());
            Object rv = method.invoke(obj, args);// 调用目标对象的方法,放行
            System.out.println("around------after--------" + new Date());
            return rv;
        }
    }

    AOP配置:同上

    3.1.4 异常通知

    package org.guangsoft.utils;
    import java.util.Date;
    import org.springframework.aop.ThrowsAdvice;
    /****
     * 异常通知
     * **/
    public class ExceptionLog implements ThrowsAdvice
    {
        /***
         * 该类中的方法参考AfterReturningAdvice写
         * 该参数是用来接收异常信息的
         * ***/
        public void afterThrowing(Throwable ex) throws Throwable
        {
            // System.out.println(obj1+"----------------------"+obj2);
            System.out.println("ExceptionLog-----------" + ex.getMessage()
                    + "--------" + new Date());
        }
    }

    Pointcut:核心业务对象

    Advice:通知

     

    展开全文
  • 前面两篇文章记录了 Spring IOC 的相关知识,本文记录 Spring 中的另一特性 AOP 相关知识。...AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。

    前面两篇文章记录了 Spring IOC 的相关知识,本文记录 Spring 中的另一特性 AOP 相关知识。

    部分参考资料:
    《Spring实战(第4版)》
    《轻量级 JavaEE 企业应用实战(第四版)》
    Spring 官方文档
    W3CSchool Spring教程
    易百教程 Spring教程

    一、AOP——另一种编程思想

    1.1 什么是 AOP

    AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
    从《Spring实战(第4版)》图书中扒了一张图:

    从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。

    1.2 为什么需要 AOP

    想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。

    1.3 AOP 实现分类

    AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,看到这其实应该明白了,AOP 其实就是前面一篇文章讲的代理模式的典型应用。
    按照 AOP 框架修改源代码的时机,可以将其分为两类:

    • 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
    • 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。

    下面给出常用 AOP 实现比较

    如不清楚动态代理的,可参考我前面的一篇文章,有讲解静态代理、JDK动态代理和 CGlib 动态代理。
    静态代理和动态代理 https://www.cnblogs.com/joy99/p/10865391.html

    二、AOP 术语

    AOP 领域中的特性术语:

    • 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
    • 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
    • 切点(PointCut): 可以插入增强处理的连接点。
    • 切面(Aspect): 切面是通知和切点的结合。
    • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
    • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。

    概念看起来总是有点懵,并且上述术语,不同的参考书籍上翻译还不一样,所以需要慢慢在应用中理解。

    三、初步认识 Spring AOP

    3.1 Spring AOP 的特点

    AOP 框架有很多种,1.3节中介绍了 AOP 框架的实现方式有可能不同, Spring 中的 AOP 是通过动态代理实现的。不同的 AOP 框架支持的连接点也有所区别,例如,AspectJ 和 JBoss,除了支持方法切点,它们还支持字段和构造器的连接点。而 Spring AOP 不能拦截对对象字段的修改,也不支持构造器连接点,我们无法在 Bean 创建时应用通知。

    3.2 Spring AOP 的简单例子

    下面先上代码,对着代码说比较好说,看下面这个例子:
    这个例子是基于gradle创建的,首先 build.gradle 文件添加依赖:

    dependencies {
        compile 'org.springframework:spring-context:5.0.6.RELEASE'
    }
    

    首先创建一个接口 IBuy.java

    package com.sharpcj.aopdemo.test1;
    
    public interface IBuy {
        String buy();
    }
    

    Boy 和 Gril 两个类分别实现了这个接口:
    Boy.java

    package com.sharpcj.aopdemo.test1;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Boy implements IBuy {
        @Override
        public String buy() {
            System.out.println("男孩买了一个游戏机");
            return "游戏机";
        }
    }
    

    Girl.java

    package com.sharpcj.aopdemo.test1;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Girl implements IBuy {
        @Override
        public String buy() {
            System.out.println("女孩买了一件漂亮的衣服");
            return "衣服";
        }
    }
    

    配置文件, AppConfig.java

    package com.sharpcj.aopdemo;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
    public class AppConfig {
    }
    

    测试类, AppTest.java

    package com.sharpcj.aopdemo;
    
    import com.sharpcj.aopdemo.test1.Boy;
    import com.sharpcj.aopdemo.test1.Girl;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class AppTest {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
            Boy boy = context.getBean("boy",Boy.class);
            Girl girl = (Girl) context.getBean("girl");
            boy.buy();
            girl.buy();
        }
    }
    

    运行结果:

    这里运用SpringIOC里的自动部署。现在需求改变了,我们需要在男孩和女孩的 buy 方法之前,需要打印出“男孩女孩都买了自己喜欢的东西”。用 Spring AOP 来实现这个需求只需下面几个步骤:
    1、 既然用到 Spring AOP, 首先在 build.gralde 文件中引入相关依赖:

    dependencies {
        compile 'org.springframework:spring-context:5.0.6.RELEASE'
        compile 'org.springframework:spring-aspects:5.0.6.RELEASE'
    }
    

    2、 定义一个切面类,BuyAspectJ.java

    package com.sharpcj.aopdemo.test1;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class BuyAspectJ {
        @Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
        public void haha(){
            System.out.println("男孩女孩都买自己喜欢的东西");
        }
    }
    

    这个类,我们使用了注解 @Component 表明它将作为一个Spring Bean 被装配,使用注解 @Aspect 表示它是一个切面。
    类中只有一个方法 haha 我们使用 @Before 这个注解,表示他将在方法执行之前执行。关于这个注解后文再作解释。
    参数("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))") 声明了切点,表明在该切面的切点是com.sharpcj.aopdemo.test1.Ibuy这个接口中的buy方法。至于为什么这么写,下文再解释。
    3、 在配置文件中启用AOP切面功能

    package com.sharpcj.aopdemo;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class AppConfig {
    }
    

    我们在配置文件类增加了@EnableAspectJAutoProxy注解,启用了 AOP 功能,参数proxyTargetClass的值设为了 true 。默认值是 false,两者的区别下文再解释。
    OK,下面只需测试代码,运行结果如下:

    我们看到,结果与我们需求一致,我们并没有修改 Boy 和 Girl 类的 Buy 方法,也没有修改测试类的代码,几乎是完全无侵入式地实现了需求。这就是 AOP 的“神奇”之处。

    四、通过注解配置 Spring AOP

    4.1 通过注解声明切点指示器

    Spring AOP 所支持的 AspectJ 切点指示器

    在spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常。

    当我们查看上面展示的这些spring支持的指示器时,注意只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器,在此基础上,我们使用其他指示器来限制所匹配的切点。

    下图的切点表达式表示当Instrument的play方法执行时会触发通知。

    我们使用execution指示器选择Instrument的play方法,方法表达式以 * 号开始,标识我们不关心方法的返回值类型。然后我们指定了全限定类名和方法名。对于方法参数列表,我们使用 .. 标识切点选择任意的play方法,无论该方法的入参是什么。
    多个匹配之间我们可以使用链接符 &&||来表示 “且”、“或”、“非”的关系。但是在使用 XML 文件配置时,这些符号有特殊的含义,所以我们使用 “and”、“or”、“not”来表示。

    举例:
    限定该切点仅匹配的包是 com.sharpcj.aopdemo.test1,可以使用
    execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*)
    在切点中选择 bean,可以使用
    execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && bean(girl)
    修改 BuyAspectJ.java

    package com.sharpcj.aopdemo.test1;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class BuyAspectJ {
        @Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*) && bean(girl)")
        public void hehe(){
            System.out.println("男孩女孩都买自己喜欢的东西");
        }
    }
    

    此时,切面只会对 Girl.java 这个类生效,执行结果:

    细心的你,可能发现了,切面中的方法名,已经被我悄悄地从haha改成了hehe,丝毫没有影响结果,说明方法名没有影响。和 Spring IOC 中用 java 配置文件装配 Bean 时,用@Bean 注解修饰的方法名一样,没有影响。

    4.2 通过注解声明 5 种通知类型

    Spring AOP 中有 5 中通知类型,分别如下:

    下面修改切面类:

    package com.sharpcj.aopdemo.test1;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class BuyAspectJ {
        @Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
        public void hehe() {
            System.out.println("before ...");
        }
    
        @After("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
        public void haha() {
            System.out.println("After ...");
        }
    
        @AfterReturning("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
        public void xixi() {
            System.out.println("AfterReturning ...");
        }
    
        @Around("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
        public void xxx(ProceedingJoinPoint pj) {
            try {
                System.out.println("Around aaa ...");
                pj.proceed();
                System.out.println("Around bbb ...");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
    
    }
    

    为了方便看效果,我们测试类中,只要 Boy 类:

    package com.sharpcj.aopdemo;
    
    import com.sharpcj.aopdemo.test1.Boy;
    import com.sharpcj.aopdemo.test1.Girl;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class AppTest {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
            Boy boy = context.getBean("boy",Boy.class);
            Girl girl = (Girl) context.getBean("girl");
            boy.buy();
            // girl.buy();
        }
    }
    

    执行结果如下:

    结果显而易见。指的注意的是 @Around 修饰的环绕通知类型,是将整个目标方法封装起来了,在使用时,我们传入了 ProceedingJoinPoint 类型的参数,这个对象是必须要有的,并且需要调用 ProceedingJoinPoint 的 proceed() 方法。 如果没有调用 该方法,执行结果为 :

    Around aaa ...
    Around bbb ...
    After ...
    AfterReturning ...
    

    可见,如果不调用该对象的 proceed() 方法,表示原目标方法被阻塞调用,当然也有可能你的实际需求就是这样。

    4.3 通过注解声明切点表达式

    如你看到的,上面我们写的多个通知使用了相同的切点表达式,对于像这样频繁出现的相同的表达式,我们可以使用 @Pointcut注解声明切点表达式,然后使用表达式,修改代码如下:
    BuyAspectJ.java

    package com.sharpcj.aopdemo.test1;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class BuyAspectJ {
    
        @Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
        public void point(){}
    
        @Before("point()")
        public void hehe() {
            System.out.println("before ...");
        }
    
        @After("point()")
        public void haha() {
            System.out.println("After ...");
        }
    
        @AfterReturning("point()")
        public void xixi() {
            System.out.println("AfterReturning ...");
        }
    
        @Around("point()")
        public void xxx(ProceedingJoinPoint pj) {
            try {
                System.out.println("Around aaa ...");
                pj.proceed();
                System.out.println("Around bbb ...");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
    }
    

    程序运行结果没有变化。
    这里,我们使用

    @Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
    public void point(){}
    

    声明了一个切点表达式,该方法 point 的内容并不重要,方法名也不重要,实际上它只是作为一个标识,供通知使用。

    4.4 通过注解处理通知中的参数

    上面的例子,我们要进行增强处理的目标方法没有参数,下面我们来说说有参数的情况,并且在增强处理中使用该参数。
    下面我们给接口增加一个参数,表示购买所花的金钱。通过AOP 增强处理,如果女孩买衣服超过了 68 元,就可以赠送一双袜子。
    更改代码如下:
    IBuy.java

    package com.sharpcj.aopdemo.test1;
    
    public interface IBuy {
        String buy(double price);
    }
    

    Girl.java

    package com.sharpcj.aopdemo.test1;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Girl implements IBuy {
        @Override
        public String buy(double price) {
            System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));
            return "衣服";
        }
    }
    

    Boy.java

    package com.sharpcj.aopdemo.test1;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Boy implements IBuy {
        @Override
        public String buy(double price) {
            System.out.println(String.format("男孩花了%s元买了一个游戏机", price));
            return "游戏机";
        }
    }
    

    再看 BuyAspectJ 类,我们将之前的通知都注释掉。用一个环绕通知来实现这个功能:

    package com.sharpcj.aopdemo.test1;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class BuyAspectJ {
    
        /*
        @Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
        public void point(){}
    
        @Before("point()")
        public void hehe() {
            System.out.println("before ...");
        }
    
        @After("point()")
        public void haha() {
            System.out.println("After ...");
        }
    
        @AfterReturning("point()")
        public void xixi() {
            System.out.println("AfterReturning ...");
        }
    
        @Around("point()")
        public void xxx(ProceedingJoinPoint pj) {
            try {
                System.out.println("Around aaa ...");
                pj.proceed();
                System.out.println("Around bbb ...");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
        */
    
    
        @Pointcut("execution(String com.sharpcj.aopdemo.test1.IBuy.buy(double)) && args(price) && bean(girl)")
        public void gif(double price) {
        }
    
        @Around("gif(price)")
        public String hehe(ProceedingJoinPoint pj, double price){
            try {
                pj.proceed();
                if (price > 68) {
                    System.out.println("女孩买衣服超过了68元,赠送一双袜子");
                    return "衣服和袜子";
                }
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return "衣服";
        }
    }
    

    前文提到,当不关心方法返回值的时候,我们在编写切点指示器的时候使用了 * , 当不关心方法参数的时候,我们使用了 ..。现在如果我们需要传入参数,并且有返回值的时候,则需要使用对应的类型。在编写通知的时候,我们也需要声明对应的返回值类型和参数类型。

    测试类:AppTest.java

    package com.sharpcj.aopdemo;
    
    import com.sharpcj.aopdemo.test1.Boy;
    import com.sharpcj.aopdemo.test1.Girl;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class AppTest {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
            Boy boy = context.getBean("boy",Boy.class);
            Girl girl = (Girl) context.getBean("girl");
            String boyBought = boy.buy(35);
            String girlBought = girl.buy(99.8);
    
            System.out.println("男孩买到了:" + boyBought);
            System.out.println("女孩买到了:" + girlBought);
        }
    }
    

    测试结果:

    可以看到,我们成功通过 AOP 实现了需求,并将结果打印了出来。

    4.5 通过注解配置织入的方式

    前面还有一个遗留问题,在配置文件中,我们用注解 @EnableAspectJAutoProxy() 启用Spring AOP 的时候,我们给参数 proxyTargetClass 赋值为 true,如果我们不写参数,默认为 false。这个时候运行程序,程序抛出异常

    这是一个强制类型转换异常。为什么会抛出这个异常呢?或许已经能够想到,这跟Spring AOP 动态代理的机制有关,这个 proxyTargetClass 参数决定了代理的机制。当这个参数为 false 时,
    通过jdk的基于接口的方式进行织入,这时候代理生成的是一个接口对象,将这个接口对象强制转换为实现该接口的一个类,自然就抛出了上述类型转换异常。
    反之,proxyTargetClass 为 true,则会使用 cglib 的动态代理方式。这种方式的缺点是拓展类的方法被final修饰时,无法进行织入。
    测试一下,我们将 proxyTargetClass 参数设为 true,同时将 Girl.java 的 Buy 方法用 final 修饰:
    AppConfig.java

    package com.sharpcj.aopdemo;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class AppConfig {
    }
    

    Girl.java

    package com.sharpcj.aopdemo.test1;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Girl implements IBuy {
        @Override
        public final String buy(double price) {
            System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));
            return "衣服";
        }
    }
    

    此时运行结果:

    可以看到,我们的切面并没有织入生效。

    五、通过 XML 配置文件声明切面

    前面的示例中,我们已经展示了如何通过注解配置去声明切面,下面我们看看如何在 XML 文件中声明切面。下面先列出 XML 中声明 AOP 的常用元素:

    我们依然可以使用 <aop:aspectj-autoproxy> 元素,他能够自动代理AspectJ注解的通知类。

    5.1 XML 配置文件中切点指示器

    在XML配置文件中,切点指示器表达式与通过注解配置的写法基本一致,区别前面有提到,即XML文件中需要使用 “and”、“or”、“not”来表示 “且”、“或”、“非”的关系。

    5.2 XML 文件配置 AOP 实例

    下面我们不使用任何注解改造上面的例子:
    BuyAspectJ.java

    package com.sharpcj.aopdemo.test2;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    
    public class BuyAspectJ {
    
        public void hehe() {
            System.out.println("before ...");
        }
    
        public void haha() {
            System.out.println("After ...");
        }
    
        public void xixi() {
            System.out.println("AfterReturning ...");
        }
    
        public void xxx(ProceedingJoinPoint pj) {
            try {
                System.out.println("Around aaa ...");
                pj.proceed();
                System.out.println("Around bbb ...");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
    }
    

    在 Resource 目录下新建一个配置文件 aopdemo.xml :

    <?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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
        <bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
        <bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>
    
        <aop:config proxy-target-class="true">
            <aop:aspect id="qiemian" ref="buyAspectJ">
                <aop:before pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="hehe"/>
                <aop:after pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="haha"/>
                <aop:after-returning pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="xixi"/>
                <aop:around pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="xxx"/>
            </aop:aspect>
        </aop:config>
    </beans>
    

    这里分别定义了一个切面,里面包含四种类型的通知。
    测试文件中,使用

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aopdemo.xml");
    

    来获取 ApplicationContext,其它代码不变。

    5.3 XML 文件配置声明切点

    对于频繁重复使用的切点表达式,我们也可以声明成切点。
    配置文件如下:aopdemo.xml

    <?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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
        <bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
        <bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>
    
        <aop:config proxy-target-class="true">
            <aop:pointcut id="apoint" expression="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))"/>
            <aop:aspect id="qiemian" ref="buyAspectJ">
                <aop:before pointcut-ref="apoint" method="hehe"/>
                <aop:after pointcut-ref="apoint" method="haha"/>
                <aop:after-returning pointcut-ref="apoint" method="xixi"/>
                <aop:around pointcut-ref="apoint" method="xxx"/>
            </aop:aspect>
        </aop:config>
    </beans>
    

    5.4 XML文件配置为通知传递参数

    BuyAspectJ.java

    package com.sharpcj.aopdemo.test2;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    
    public class BuyAspectJ {
    public String hehe(ProceedingJoinPoint pj, double price){
            try {
                pj.proceed();
                if (price > 68) {
                    System.out.println("女孩买衣服超过了68元,赠送一双袜子");
                    return "衣服和袜子";
                }
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return "衣服";
        }
    }
    

    aopdemo.xml

    <?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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean>
        <bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean>
        <bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean>
    
        <aop:config proxy-target-class="true">
            <aop:pointcut id="apoint" expression="execution(String com.sharpcj.aopdemo.test2.IBuy.buy(double)) and args(price) and bean(girl)"/>
            <aop:aspect id="qiemian" ref="buyAspectJ">
                <aop:around pointcut-ref="apoint" method="hehe"/>
            </aop:aspect>
        </aop:config>
    </beans>
    

    5.5 Xml 文件配置织入的方式

    同注解配置类似,
    CGlib 代理方式:

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

    JDK 代理方式:

    <aop:config proxy-target-class="false"> </aop:config>
    

    六、总结

    本文简单记录了 AOP 的编程思想,然后介绍了 Spring 中 AOP 的相关概念,以及通过注解方式和XML配置文件两种方式使用 Spring AOP进行编程。 相比于 AspectJ 的面向切面编程,Spring AOP 也有一些局限性,但是已经可以解决开发中的绝大多数问题了,如果确实遇到了 Spring AOP 解决不了的场景,我们依然可以在 Spring 中使用 AspectJ 来解决。

    作者:SharpCJ

    出处:https://www.cnblogs.com/joy99/p/10941543.html

    本站使用「署名 4.0 国际」创作共享协议,转载请在文章明显位置注明作者及出处。

    展开全文
  • Spring 面向切面编程

    2021-03-18 15:23:57
    Spring 面向切面编程 什么是 AOP AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 AOP 是 OOP 的补充,是软件开发中的一个...

    Spring 面向切面编程


    什么是 AOP

    AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
    AOPOOP 的补充,是软件开发中的一个重要的技术,同时也是Spring框架中的核心内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的 耦合度降低 ,提高程序的可重用性,同时提高了开发的效率。

    AOP 的作用及其优势

    • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。
    • 优势:减少重复代码,提高开发效率,并且便于维护。

    AOP 的底层实现

    AOP的底层是通过Spring提供的动态代理技术实现的。运行期间Spring会通过动态代理技术动态生成代理对象,代理对象方法执行时会在合适的时机进行增强功能的介入,再调用目标对象的方法,实现功能的增强。

    AOP 动态代理技术

    常用的动态代理技术
    JDK 代理:基于接口的动态代理技术
    CGLB 代理:基于父类的动态代理技术

    动态代理的扩展

    动态代理都是通过继承父类实现接口,并进行方法重写完成的。

    继承的子类或实现接口的实现类都由工具创建,而不是我们自己实现的。

    Spring AOP

    **Spring AOP**实现底层就是对动态代理进行了封装,封装后我们只需要对我们需要关注的部分进行代码编写,并通过配置的方式完成制定目标的方法功能的增强。

    术语解释(术语并非属于Spring
    Target目标对象
    Proxy代理对象
    JoinPoint连接点
    指目标对象中的方法
    PointCut切入点
    指对哪些连接点进行拦截的定义
    Advice通知/建议
    值拦截指定方法后执行的操作
    Aspect切面
    由切点和通知组成
    weaving织入
    将切点与建议结合的过程

    在这里插入图片描述

    切点表达式

    用来匹配需要代理的方法,以便于拦截代理

    表达式语法:execution( [修饰符] 返回值类型 包名.类名.方法名( 参数类型列表 ) )

    • 访问修饰符可以省略
    • 返回值类型、包名、类名、方法名可以使用星号 * 代表任意
    • 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
    • 参数类型列表:多个用逗号隔开,可以使用两个点 .. 表示任意个数和类型的参数列表
    示例说明
    execution( public * *(..) )执行任何公共方法
    execution(* set*(..))名称以set开头的任何方法的执行
    execution(* com.xuetang9.service.UserService.*(..))执行UserService接口定义的任何方法
    execution(* com.xuetang9.service.* .*(..))执行service包中定义的任何方法
    execution(* com.xuetang9.service.. *. *(..))执行service包或其子包中定义的任何方法

    通知的类型

    名称标签注解说明
    前置建议<aop:before>@Before运行在连接点之前,但不能阻止连接点运行
    后置运行建议<aop:after-returning>@AfterReturning运行在连接点之后,正常结束不抛出异常
    后置异常建议<aop:after-throwing>@AfterThrowing运行在连接点出现异常之后
    后置建议<aop:after>@After不管连接点是否出现异常都会运行
    前置建议<aop:around>@Around环绕在一个连接点运行,可处理上面的所有操作

    纯配置文件实现方式

    导入aspectjweaver的坐标

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

    书写增强类

    @Component
    @Aspect
    public class AdviceAop {
    
        public void methodBefore(){
            System.out.println("执行之前...");
        }
    
        public void methodAfterReturning(){
            System.out.println("方法执行返回结果之后...");
        }
    
        public void methodAfterThrowing(){
            System.out.println("方法执行出现异常之后...");
        }
    
        public void methodAfterFinally(){
            System.out.println("方法执行之后...");
        }
    
        public void methodAround(){
            System.out.println("环绕增强...");
        }
    }
    

    在XML中手动配置AOP的代理

    <!--配置功能增强通知类-->
    <bean id="adviceAop" class="demo.aop.AdviceAop"></bean>
    
    <!--配置AOP-->
    <aop:config>
        <!--配置一个具体的切面织入-->
        <aop:aspect ref="adviceAop">
            <!--配置切点-->
            <aop:pointcut id="methodPointcut" expression="execution(* com.xuetang9.spring.demo..*.*(..))"/>
            <!--配置切点通知-->
            <aop:before method="methodBefore" pointcut-ref="methodPointcut"/>
            <aop:after-returning method="methodAfterReturning" pointcut-ref="methodPointcut"/>
            <aop:after-throwing method="methodAfterThrowing" pointcut-ref="methodPointcut"/>
            <aop:after method="methodAfterFinally" pointcut-ref="methodPointcut"/>
            <aop:around method="methodAround" pointcut-ref="methodPointcut"/>
        </aop:aspect>
    </aop:config>
    

    基于注解+配置方式的实现

    导入坐标同上
    用注解定义增强类

    public class AdviceAop {
    
        /**
         * 定义通用的切点
         */
        @Pointcut("execution(* demo.dao..*.*(..))")
        public void joinPoint() {}
    
        @Before("joinPoint()")
        public void methodBefore() {
            System.out.println("执行之前...");
        }
    
        @AfterReturning("joinPoint()")
        public void methodAfterReturning() {
            System.out.println("方法执行返回结果之后...");
        }
    
        @AfterThrowing("joinPoint()")
        public void methodAfterThrowing() {
            System.out.println("方法执行出现异常之后...");
        }
    
        @After("joinPoint()")
        public void methodAfterFinally() {
            System.out.println("方法执行之后...");
        }
    
        @Around("joinPoint()")
        public void methodAround() {
            System.out.println("环绕增强...");
        }
    }
    

    在xml中配置自动代理

    <aop:aspectj-autoproxy proxy-target-class="true"/>
    
    • proxy-target-class :默认值为false
      • false:使用JDK动态代理
      • true:使用CGLIB动态代理

    纯注解方式实现

    导入坐标同上
    书写增强类同上
    书写注解配置类启用自动代理

    @Configuration
    @ComponentScan("demo")
    @EnableAspectJAutoProxy
    public class AppConfig {
    
    }
    
    展开全文
  • 15.Spring 面向切面编程

    2021-09-09 19:39:33
    Spring 面向切面编程 AOP 是 Spring 框架除了 IOC 之外的另一个核心概念。   AOP:Aspect Oriented Programming,意为面向切面编程。这是一个新的概念,但是我们知道 Java 是面向对象编程(OOP:Object Oriented ...
  • Spring面向切面编程(AOP)详解 面向切面编程(AOP)是Spring框架的另外一个重要的核心内容。 而在讲AOP之前,先来了解一下动态代理这个概念,因为AOP基于动态代理。 动态代理概念:在程序执行的过程中,创建代理...
  • Spring 面向切面编程AOP

    2021-07-27 00:05:59
    Spring 面向切面编程AOP AOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的...
  • 今天学到了面向切面编程的第三种方式 注解 导入依赖的步骤以及ApplicationContext.xml的步骤和上两种种方法一致(下想想这都是废话了,都是面向切面编程用aop的导入的依赖肯定一样啊,我真傻) 依赖如下 <...
  • spring的AOP(面向切面编程) 切面编程,就是在你项目原有的功能基础上,通过AOP去添加新的功能,这些功能是建立在原有功能的基础上的,并且不修改任何原来功能的代码。 例如:你先打车去银行 > 进入银行大门 &...
  • 今天学到了面向切面编程aop的第二种方法 自定义切面类 导入依赖的步骤以及ApplicationContext.xml的步骤和上一种方法一致 依赖如下 <dependency> <groupId>org.aspectj</groupId> <...
  • 1 建立切面类 不需要实现任何特定接口,按照需要自己定义通知。 package org.guangsoft.utils; import java.util.Date; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After...
  • Java Spring 面向切面编程 AOP:面向切面编程 (Aspect Oriented Programming) 概念:百度版: 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现...
  • Spring面向切面编程AOP

    2021-09-26 15:04:30
    AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式...
  • AOP(Aspect Oriented Programming):面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的...
  • 今天学到了AOP 面向切面编程 在这里学到了两个类 1.MethodBeforeAdice 2.AfterReturningAdvice implement MethodBeforeAdice的类里面有一个before方法会在执行bean之前执行 package com.zyy.log; import org.spring...
  • 1.传统编程模式的弊端 通过以下案例你将会发现,虽然在调用addUser()方法时实现了日志的输出,但这个程序有一个问题, 就是不得不在addUser()方法的主业务代码前面添加"log.info(“开始添加用户。。。”)语句, 以及...
  • 1.aop全称Aspect Oriented Programming 面向切面编程2.aop应用场景场景一: 记录日志场景二: 监控方法运行时间 (监控性能)场景三: 权限控制场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第...
  • AOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现在不修改源代码的情况下,给程序动态统一添加功能的一种技术,可以理解成动态代理。是Spring框架中的一个重要内容。利用 ...
  • Spring面向切面编程

    2021-02-16 17:43:22
    Spring AOP——Spring面向切面编程 前面两篇文章记录了 Spring IOC 的相关知识,本文记录 Spring 中的另一特性 AOP 相关知识。 部分参考资料: 《Spring实战(第4版)》 《轻量级 JavaEE 企业应用实战(第四版...
  • Aspectj表示切面执行时间,用的通知(Advice)。 这个通知可以使用注解表示。 5个注解表示切面的5个执行时间, 这些注解叫做通知注解。 @Before : 前置通知 @AfterRetunring: 后置通知 @Around: 环绕通知 @...
  • Aspectj表示切面执行时间,用的通知(Advice)。 这个通知可以使用注解表示。 5个注解表示切面的5个执行时间, 这些注解叫做通知注解。 @Before : 前置通知 @AfterRetunring: 后置通知 @Around: 环绕通知 @...
  • 我们为什么要使用AOP(面向切面编程)?当我们在现实中完成实际的项目时,我们总是需要在一个“动作”进行前,进行中,或进行后进行一些操作,比如当我们在运行程序时,我们想要进行日志保存,或者在每一个方法调用...
  • AOP(Aspect Oriented Programming)面向切面编程,是Spring框架中的内容,它是针对于一个事件的某个步骤或阶段,主要应用于:日志,事务处理,异常处理等方面,它的原理是:通过过滤,对指定的一些操作直接添加设定好...
  • 学习目的:学会使用注解进行面向切面编程(AOP),实现在面向切面编程(AOP)中,使用XML配置完成的操作。Part 1修改cn.vaefun.dao.UserServiceImpl.java,在类上添加Component注解,告诉spring这是一个bean,并命名为...
  • (1)本篇博客的代码,基于【Spring AOP面向切面编程2:初识AOP二:AOP初体验;(一个Spring AOP的案例,走了一遍流程)】中的代码; (2)如在【Spring AOP面向切面编程2:初识AOP二:AOP初体验;(一个Spring AOP...
  • 想象一段自顶而下的程序,在这一段程序中的某个方法想要在执行后打印出日志,这时候使用面向切面编程的思想就可以很好的解决这个问题,我们可以通过代理模式来实现我们的打印日志功能,因为代理模式就是面向切面...
  • Spring AOP 面向切面编程 AOP背景 AOP英语全称 Aspect Oriented Programming 即面向切面编程,它主要实现横切方向的编程。何为横切方向编程呢?如下图所示:左侧的就是我们平时编程习惯,将所有的功能串起来,而右侧...
  • 在service层织入新的功能 ...import org.springframework.stereotype.Repository; @Repository("StudentDao") public class StudentDaoImpl implements StudentDao { public int deleteStudentById(Int.
  • 被通知(被代理)的对象 注1:完成具体的业务逻辑 通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理) 注2:完成切面编程 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 66,181
精华内容 26,472
关键字:

spring面向切面编程

spring 订阅