精华内容
下载资源
问答
  • 使用注解来创建切面是 AspectJ 5 所引入的关键特性。我们前一篇已经定义了 Performance 接口,它是切面中切点的目标对象。...因此,将观众定义为一个切面,并将其应用到演出上就是较为明智的做法。之前需要

    使用注解来创建切面AspectJ 5 所引入的关键特性。

    我们前一篇已经定义了 Performance 接口,它是切面中切点的目标对象

    Q:如何定义切面?

    A: 给出一个描述:如果一场演出没有观众的话,那就不能称之为演出。对不对?从演出的角度来看,观众是非常重要的,但是对演出本身的功能来将,它并不是核心,这是一个单独的关注点。因此,将观众定义为一个切面,并将其应用到演出上就是较为明智的做法。

    之前需要在 build.gradle 导入 aspectjrt

    // https://mvnrepository.com/artifact/org.aspectj/aspectjrt
    compile group: 'org.aspectj', name: 'aspectjrt', version: '1.9.0.BETA-6'
    package concert;
    
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    
    /**
     * 该类使用 @AspectJ 注解进行了标注。该注解表明该类不仅仅是一个 POJO,还是一个切面。
     * 该类中的方法都使用注解来定义切面的具体行为。
     */
    @Aspect
    public class Audience {
    
        //表演之前:将手机调至静音状态
        @Before("execution(**concert.Performance.perform(..))")
        public void silenceCellPhones() {
            System.out.println("Silencing cell phones");
        }
    
        //表演之前:就座
        @Before("execution(**concert.Performance.perform(..))")
        public void takeSeats() {
            System.out.println("Taking seats");
        }
    
        //表演之后:精彩的话,观众应该会鼓掌喝彩
        @AfterReturning("execution(**concert.Performance.perform(..))")
        public void applause() {
            System.out.println("CLAP CLAP CLAP!!!");
        }
    
        //表演失败之后:没有达到观众预期的话,观众会要求退款
        @AfterThrowing("execution(**concert.Performance.perform(..))")
        public void demandRefund() {
            System.out.println("Demanding a refund");
        }
    
    }

    可以简化一下,使用 @Pointcut 注解。

    package concert;
    
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    
    /**
     * 简化方案:@Pointcut 注解能够在一个 @AspectJ 切面内定义可重用的切点。
     */
    @Aspect
    public class Audience {
    
        //通过在该方法上添加 @Pointcut 注解,我们实际上扩展了切点表达式语言,
        //这样就可以在任何的切点表达式中使用 performance()了。
        //该方法内容并不重要,实际上应该是空的。只是一个标识,供 @Pointcut 注解依附。
        @Pointcut("execution(**concert.Performance.perform(..))")
        public void performance(){}
    
        //表演之前:将手机调至静音状态
        @Before("performance()")
        public void silenceCellPhones() {
            System.out.println("Silencing cell phones");
        }
    
        //表演之前:就座
        @Before("performance()")
        public void takeSeats() {
            System.out.println("Taking seats");
        }
    
        //表演之后:精彩的话,观众应该会鼓掌喝彩
        @AfterReturning("performance()")
        public void applause() {
            System.out.println("CLAP CLAP CLAP!!!");
        }
    
        //表演失败之后:没有达到观众预期的话,观众会要求退款
        @AfterThrowing("performance()")
        public void demandRefund() {
            System.out.println("Demanding a refund");
        }
    
    
    }
    

    AspectJ 提供了五个注解来定义通知。
    这里写图片描述

    package concert;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    /**
     * 如果你使用 JavaConfig 的话,可以在配置类的类级别上通过使用 @EnableAspectJAutoProxy 注解启用自动代理功能。
     */
    @Configuration
    @EnableAspectJAutoProxy //启用 AspectJ 自动代理
    @ComponentScan
    public class ConcertConfig {
    
        @Bean
        public Audience audience(){  // 声明 Audience bean
            return new Audience();
        }
    }
    

    或者 XML 中配置:使用 Spring aop 命名空间中的 aop:aspectJ-autoproxy 元素。

    <?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:context="http://www.springframework.org/schema/context"
           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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <context:component-scan base-package="concert"/>
    
        <!-- 启用 AspectJ 自动代理 -->
        <aop:aspectj-autoproxy/>
    
        <!-- 声明 Audience bean -->
        <bean class="concert.Audience"/>
    
    </beans>

    不管使用 JavaConfig 还是 XML,AspectJ 自动代理都会为使用 @Aspect 注解的 bean 创建一个代理,这个代理会围绕着所有该切面的切点所匹配的 bean。在这种情况下,将会为 Concert bean 创建一个代理,Audience 类中的通知方法将会在 perform() 调用前后执行。

    需要记住的是:Spring 的 AspectJ 自动代理仅仅使用 @AspectJ 作为创建切面的指导,切面依然是基于代理的。本质上,它依然是 Spring 基于代理的切面。这就意味着,尽管使用的是 @AspectJ 注解,但我们仍然限于代理方法的调用。如果想利用 AspectJ 的所有能力,我们必须在运行时使用 AspectJ 并且不依赖于 Spring 来创建基于代理的切面

    Q:如何创建环绕通知?

    A: 环绕通知是最为强大的通知类型。实际上就像在一个通知方法中同时编写前置通知和后置通知。

    package concert;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    
    /**
     * 使用环绕通知重新实现 Audience 切面
     * 这个通知所达到的效果跟之前的前置后置通知是一样的。但是,现在它们位于一个方法中。
     */
    @Aspect
    public class Audience {
    
        // 定义命名的切点
        @Pointcut("execution(**concert.Performance.perform(..))")
        public void performance(){}
    
        /**
         * 环绕通知方法
         * @param joinPoint 这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。
         */
        @Around("performance()")
        public void watchPerformance(ProceedingJoinPoint joinPoint){
            try {
                System.out.println("Silencing cell phones");
                System.out.println("Taking seats");
                // 通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,它需要调用该方法。
                // 如果不调用这个方法的话,那么你的通知实际上会阻塞对被通知方法的调用。
                // 有可能这就是你想要的效果,但更多的情况是你希望在某个点上执行被通知的方法。
                joinPoint.proceed();
                System.out.println("CLAP CLAP CLAP!!!");
            } catch (Throwable throwable) {
                System.out.println("Demanding a refund");
            }
        }
    
    }
    

    对于 proceed() 方法,你也可以在通知中对它进行多次调用,这样做的一个场景就是实现重试逻辑,也就是在被通知方法失败后,进行重复尝试

    Q:如何处理通知的参数?(切面能访问和使用传递给被通知方法的参数吗?)

    A:如下列代码所示:BlankDisc.java & CompactDisc.java 参考 Spring 如何通过 XML 装配 bean?,并加以修改 play() 方法。

    package concert;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 假设你想记录每个磁道被播放的次数。由于记录磁道的播放次数与播放本身是不同的关注点,因此应该是切面要完成的任务。
     * 使用参数化的通知来记录磁道播放的次数
     */
    @Aspect
    public class TrackCounter {
    
        // 记录每个磁道播放次数
        private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();
    
        // 通知 play() 方法,在切点表达式中声明参数,这个参数传入到通知方法 countTrack() 中
        // args(trackNumber) 表明传递给 play() 方法的 int 类型参数也会传递到通知中(countTrack()方法)去。
        // 参数名称与切点方法签名中的参数相匹配。
        @Pointcut("execution(* soundsystem.CompactDisc.play(int)) && args(trackNumber)")
        public void trackPlayed(int trackNumber) {
        }
    
        // 在播放之前,为该磁道计数
        // 这个通知方法是通过 @Before 注解和命名切点 trackPlayed(trackNumber) 定义的。
        // 切点定义中的参数与切点方法中的参数名称是一样的,这样就完成了从命名切点到通知方法的参数转移。
        @Before("trackPlayed(trackNumber)")
        public void countTrack(int trackNumber) {
            int currentCount = getPlauCount(trackNumber);
            trackCounts.put(trackNumber, currentCount + 1);
        }
    
        public int getPlauCount(int trackNumber) {
            return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
        }
    
    }
    
    package soundsystem;
    
    import concert.TrackCounter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 在 Spring 配置中将 BlankDisc 和 TrackCounter 定义为 bean,并启用自动代理。
     * 配置 TrackCount 记录每个磁道播放的次数
     */
    @Configuration
    @EnableAspectJAutoProxy // 启用 AspectJ 自动代理
    public class TrackCounterConfig {
    
        @Bean
        public CompactDisc sgtPeppers(){
            BlankDisc cd = new BlankDisc();
            cd.setTitle("titleValue01");
            cd.setArtist("ArtistValue01");
            List<String> tracks = new ArrayList<String>();
            tracks.add("trackValue01");
            tracks.add("trackValue02");
            tracks.add("trackValue03");
            tracks.add("trackValue04");
            tracks.add("trackValue05");
            cd.setTracks(tracks);
            return cd;
        }
    
        @Bean
        public TrackCounter trackCounter(){
            return new TrackCounter();
        }
    
    }
    

    Q:如何通过注解引入新功能?

    A: Java 不是动态语言,一旦类编译完成了,我们就很难再为该类添加新功能了。但是我们可以使用切面,为对象拥有的方法添加了新功能,而没有为对象添加任何新的方法。实际上,利用被称为引入的 AOP 概念,切面可以为 Spring bean 添加新的方法

    这里写图片描述

    注意:当引入接口的方法被调用时,代理会把此调用委托给实现了新接口的某个其他对象。实际上,一个 bean 的实现被拆分到了多个类中

    代码如下:

    package concert;
    
    /**
     * 为所有的 Performance 实现引入下面的 Encoreable 接口
     *
     * 需要将这个接口应用到 Performance 实现中。
     * 两个问题:不能直接实现 Encoreable 接口,并不是所有的 Performance 都是具有 Encoreable 特性的;
     *           也有可能无法修改所有的 Performance 实现(使用第三方实现并且没有源码时)。
     * 解决方案:借助于 AOP 的引入功能,我可以避免以上两个问题。
     */
    public interface Encoreable {
        void performEncore();
    }
    
    package concert;
    
    /**
     * Encoreable 接口的实现类
     */
    public class DefaultEncoreable implements Encoreable {
        @Override
        public void performEncore() {
    
        }
    }
    
    package concert;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.DeclareParents;
    
    /**
     * 创建一个切面
     */
    @Aspect
    public class EncoreableIntroducer {
    
        // 通过 @DeclareParents 注解,将 Encoreable 接口引入到 Performance bean 中。
        // @DeclareParents 注解由三部分组成:
        // ①、value 属性指定了哪种类型的 bean 要引入该接口。
        //     此处 value 的值,代表所有实现 Performance 的类型。(标记符后面的 "+" 表示是 Performance 的所有子类型,而不是它本身)
        // ②、defaultImpl 属性指定了为引入功能提供实现的类。这里,指定的是 DefaultEncoreable 提供实现。
        // ③、@DeclareParents 注解所标注的静态属性指明了要引入的接口。这里,所引入的是 Encoreable 接口。
        @DeclareParents(value = "concert.Performance+",defaultImpl = DefaultEncoreable.class)
        public static Encoreable encoreable;
    
    }
    
        <!-- 将 EncoreableIntroducer 声明为一个 bean -->
        <bean class="concert.EncoreableIntroducer"/>

    Spring 的自动代理机制将会获取到它的声明,当 Spring 发现了一个 bean 使用了 @Aspect 注解时,Spring 就会创建一个代理,然后将调用委托给被代理的 bean被引入的实现,这取决于调用的方法属于被代理的 bean 还是属于被引入的接口

    面向注解的切面声明有一个明显的劣势你必须能够为通知类添加注解。为了做到这一点,必须要有源码。

    如果没有源码的话,或者不想将 AspectJ 注解放到你的代码之中,我们可以在 Spring XML 配置文件中声明切面。

    上一篇: 面向切面的 Spring —— 如何通过切点来选择连接点?
    下一篇:面向切面的 Spring —— 如何在 XML 中声明切面?

    展开全文
  • 2. 探针死锁是发生在多个探针循环依赖的关系,多切面直接不会出现这种关系,因为切面的作用就是加强被切业务的功能,所有加强逻辑都可以放在一个切面,如果出现多切面互切那一定是设计不合理,画蛇添足的做法。...
    1. CGLIB多个切面是按照优先级执行的,@Order()来控制切面切入顺序
    
    2. 探针死锁是发生在多个探针循环依赖的关系,多切面直接不会出现这种关系,因为切面的作用就是加强被切业务的功能,所有加强逻辑都可以放在一个切面,如果出现多切面互切那一定是设计不合理,画蛇添足的做法。
    

    字节码执行

    ​ 方法调用在JVM中转换成的是字节码执行,字节码指令执行的数据结构就是栈帧(stack frame)。也就是在虚拟机栈中的栈元素。虚拟机会为每个方法分配一个栈帧,因为虚拟机栈是LIFO(后进先出)的,所以当前线程正在活动的栈帧,也就是栈顶的栈帧,JVM规范中称之为“CurrentFrame”,这个当前栈帧对应的方法就是“CurrentMethod”。字节码的执行操作,指的就是对当前栈帧数据结构进行的操作

    img

    运行时数据区

    栈帧的数据结构主要分为四个部分:局部变量表、操作数栈、动态链接以及方法返回地址(包括正常调用和异常调用的完成结果)。下面就一一介绍下这四种数据结构。

    局部变量表(local variables)

    当方法被调用时,参数会传递到从0开始的连续的局部变量表的索引位置上。栈帧中局部变量表的长度存储在类或接口的二进制表示中。阅读Class文件会找到Code属性,所以能知道local variables的最大长度是在编译期间决定的。一个局部变量表的占用了32位的存储空间(一个存储单位称之为slot,槽),所以可以存储一个boolean、byte、char、short、float、int、refrence和returnAdress数据,long和double需要2个连续的局部变量表来保存,通过较小位置的索引来获取。如果被调用的是实例方法,那么第0个位置存储“this”关键字代表当前实例对象的引用。这个可以通过javap 工具查看实例方法和静态方法对比字节码指令的0位置。例子也可以参考JVM 字节码指令对于栈帧数据操作举例

    操作数栈(operand stack)

    操作数栈同局部变量表一样,也是编译期间就能决定了其存储空间(最大的单位长度),通过 Code属性存储在类或接口的字节流中。操作数栈也是个LIFO栈。
      操作数栈是在JVM字节码执行一些指令(第二部分会介绍一些指令集)时创建的,主要是把局部变量表中的变量压入操作数栈,在操作数栈中进行字节码指令的操作,再将变量出操作数栈,结果入操作数栈。同局部变量表,除了long和double,其他类型数据都只占用一个栈的单位深度。

    动态链接

    每个栈帧指向运行时常量池中该栈帧所属的方法的引用,也就是字节码的发放调用的引用。动态链接就是将符号引用所表示的方法,转换成方法的直接引用。加载阶段或第一次使用时转化为直接引用的(将变量的访问转化为访问这些变量的存储结构所在的运行时内存位置)就叫做静态解析。JVM的动态链接还支持运行期转化为直接引用。也可以叫做Late Binding,晚期绑定。动态链接是java灵活OO的基础结构。可以参考一个例子来加深理解从字节码指令看重写在JVM中的实现

    方法返回地址

    方法正常退出,JVM执行引擎会恢复上层方法局部变量表操作数栈并把返回值压入调用者的栈帧的操作数栈,PC计数器的值就会调整到方法调用指令后面的一条指令。这样使得当前的栈帧能够和调用者连接起来,并且让调用者的栈帧的操作数栈继续往下执行。
      方法的异常调用完成,主要是JVM抛出的异常,如果异常没有被捕获住,或者遇到athrow字节码指令显示抛出,那么就没有返回值给调用者。

    结论

    在这里插入图片描述

    1. CGLIB字节码插装是在解释器解释运行和即时编译的过程进行的,多个切面切一个服务的方法对应都会在虚拟机栈生成一个线程私有的栈帧结构,字节码执行只执行栈顶栈帧结构--CurrentFrame,线程安全。
    
    2. 代理对象是在运行时生成,代理类除了自己的加强逻辑之外还会调用被代理类的方法--被切方法。
    
    3. 根据实际业务试验,多线程执行多个切面去切同一个方法时,默认按照代理类类名的ASCII码进行有序执行,如果用了@Order注解,就会按照我们自定义的优先级进行执行。
    
    4. 整个切面执行到产生代理类再到加强代理方法运行的全过程都是有序进行不会出现死锁或争抢临界资源问题。
    
    展开全文
  • 最近在搞一个统一给接口参数加解密的工具吧,之前的做法是通过filter进行参数解密,然后再分发到对象的controller接口,业务接口处理后,通过AOP切面去加密参数返回给第三方,后面想了另外一种方式,直接通过AOP的...

    最近在搞一个统一给接口参数加解密的工具吧,之前的做法是通过filter进行参数解密,然后再分发到对象的controller接口,业务接口处理后,通过AOP切面去加密参数返回给第三方,后面想了另外一种方式,直接通过AOP的环绕增强给参数做加解密;切面这块就不展开讲了,主要处理的是标题的内容,下面进入正题:

    @getter
    @setter
    class BaseParam<T>{
    	//密文
    	private String ciphertext;
    	//接口的真实对象
    	private T realData;
    	
    }
    

    比如上述对象,前端或者其他人调用的时候传密文进来,然后我这边通过切面,先把参数解密,然后set进真实的对象里面;如下面的controller登录接口

    public String login(@RequestBody BaseParam<LoginVO> body){
    	//业务处理
    }
    

    这样,登录接口就把泛型确定了;然后通过切面如何获取登录接口的泛型的真实类型呢?
    很简单,我先卖个关子,先讲一下我的心路历程:

    切面
    用户
    filter
    controller

    如上图所示,我一开始的思路是这样的:(我不知道controller上一层是谁调过来的啊,哪位大能看到希望可以指正一下)我在切面里面处理filter给过来的参数,去判断filter给过来的参数的realData的真实泛型是什么类型,途中还使用了ResolvableType去获取类型,但是获取的结果不是Object就是“?”,或者是"T",根本无法知道真实类型
    后面我想了下,思路倒过来了,应该想去到目标的接口去获取方法上面的泛型的真实类型,再通过解密后set回realData里面;果然方向错了就容易进死胡同
    在切面里是如何获取真实泛型呢;
    看代码:

    public Object doAround(ProceedingJoinPoint joinPoint){
    
     MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
     Method method = methodSignature.getMethod();
     Type[] types = method.getGenericParameterTypes();
      //获取方法里面第一个参数里面的第一个泛型
     Type argument = ((ParameterizedType) 
     
     types[0]).getActualTypeArguments()[0];
     Object[] args = joinPoint.getArgs();
     BaseParam arg;
    if( args[o] instanceof BaseParam){
    	//这里一般是遍历,我为了方便写就直接拿第一个了
    	arg =  (BaseParam )args[0];
    }
     //解密业务 todo
     String realDataJsonStr = decrypt(arg .realData);
     //使用的是ObjectMapper
     Object value = json.readValue(realDataJsonStr , json.constructType(argument));
     arg.setRealData(value);
     args[0] = arg;
     //执行方法
     Object proceed = joinPoint.proceed(args);
     //参数加密 todo
     
     return obj; 
    }
    

    代码纯手敲,中间可能会有些语法或者不知名错误,可以参考思路;
    参数加解密一般所有接口都会有,所以用个切面或者拦截器做统一处理对于走业务接口的话,就会很方便了;

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

    2021-01-06 11:41:37
    AOP的做法是将通用、于业务无关的功能抽象封装为切面切面可配置在目标方法的执行前、后运行,真正做到即插即用 Spring AOP 用到的依赖dependency: <dependencies> <dependency> <groupId>...

    Spring AOP面向切面编程

    Spring AOP

    • Spring AOP - Aspect Oriented Programming 面向切面编程
    • AOP的做法是将通用、于业务无关的功能抽象封装为切面类
    • 切面可配置在目标方法的执行前、后运行,真正做到即插即用

    Spring AOP

    用到的依赖dependency:

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
    </dependencies>
    

    applicationContext.xml中schema依赖

    <?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 https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!-- bean definitions here -->
    </beans>
    

    Spring AOP概念

    Spring AOP与AspectJ的关系

    • Eclipse AspectJ,一种基于Java平台的面向切面编程的语言
    • Spring AOP使用AspectJWeaver实现类与方法的匹配
    • Spring AOP利用代理模式实现对象运行时功能扩展

    几个关键概念

    注解 说明
    Aspect 切面,具体的可插拔组件功能类,通常一个切面只实现一个通用功能
    Target Class/Method 目标类、目标方法,指真正要执行与业务相关的方法
    PointCut 切入点,使用execution表达式说明切面要作用在系统的哪些类上
    JoinPoint 连接点,切面运行过程中时包括了目标类/方法元数据的对象
    Advice 通知,说明具体的切面的执行时机,Spring包含了五种不同类型通知

    AOP配置过程

    • 依赖AspectJ
    • 实现切面类/方法
    • 配置Aspect Bean
    • 定义PointCut
    • 配置Advice

    JoinPoint核心方法

    注解 说明
    Object getTarget() 获取IoC容器内目标对象
    Signature getSignature() 获取目标方法
    Object[] getArgs() 获取目标方法参数

    PointCut切点表达式

    在这里插入图片描述

    通知

    五种通知类型

    注解 说明
    Before Advice 前置通知,目标方法运行前执行
    After Returning Advice 返回后通知,目标方法返回数据后执行
    After Throwing Advice 异常通知,目标方法抛出异常后执行
    After Advice 后置通知,目标方法运行
    Around Advice 最强大通知,自定义通知执行时机,可决定目标方法是否运行

    特殊的“通知”-引介增强

    • 引介增强(Introduction Interceptor)是对类的增强,而非方法
    • 引介增强允许在运行时为目标类增加新属性或方法
    • 引介增强允许在运行时改变类的行为,让类随运行环境动态变更

    Spring AOP实现原理

    • Spring基于代理模式实现功能动态扩展,包含俩种形式
    • 目标类拥有接口,通过JDK动态代理实现功能扩展
    • 目标类没有接口,通过CGLib组件实现功能扩展

    代理模式:

    • 代理模式通过代理对象对原对象的实现功能扩展

    在这里插入图片描述

    静态代理示例:

    UserService.java

    package com.imooc.spring.aop.service;
    
    /**
     * @author Rex
     * @create 2021-01-05 11:47
     */
    public interface UserService {
        public void createUser();
    }
    
    

    UserService.java

    package com.imooc.spring.aop.service;
    
    /**
     * @author Rex
     * @create 2021-01-05 11:47
     */
    public class UserServiceImpl implements UserService{
        @Override
        public void createUser() {
            System.out.println("执行创建用户业务逻辑");
        }
    }
    
    

    UserServiceProxy.java

    package com.imooc.spring.aop.service;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * @author Rex
     * @create 2021-01-05 11:49
     */
    //静态代理是指必须手动创建代理类的代理模式使用方式
    public class UserServiceProxy implements UserService{
        //持有委托类的对象
        private UserService userService;
        public UserServiceProxy(UserService userService){
            this.userService = userService;
        }
    
        @Override
        public void createUser() {
            System.out.println("======" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").format(new Date()) +"==========");
            this.userService.createUser();
        }
    }
    
    

    UserServiceProxy1.java

    package com.imooc.spring.aop.service;
    
    /**
     * @author Rex
     * @create 2021-01-05 11:54
     */
    public class UserServiceProxy1 implements UserService {
        private UserService userService;
        public UserServiceProxy1(UserService userService){
            this.userService = userService;
        }
    
        @Override
        public void createUser() {
            userService.createUser();
            System.out.println("=======后置扩展功能======");
        }
    }
    
    

    运行结果:

    ======2021-01-05 12:28:08 008==========
    执行创建用户业务逻辑
    =======后置扩展功能======
    

    动态代理示例

    UserSerivce.java

    package com.imooc.spring.aop.service;
    
    /**
     * @author Rex
     * @create 2021-01-05 11:47
     */
    public interface UserService {
        public void createUser();
    }
    
    

    UserServiceImpl.java

    package com.imooc.spring.aop.service;
    
    /**
     * @author Rex
     * @create 2021-01-05 11:47
     */
    public class UserServiceImpl implements UserService{
        @Override
        public void createUser() {
            System.out.println("执行创建用户业务逻辑");
        }
    }
    
    

    EmployeeService.java

    package com.imooc.spring.aop.service;
    
    /**
     * @author Rex
     * @create 2021-01-05 12:20
     */
    public interface EmployeeService {
        public void createEmployee();
    }
    
    

    EmployeeServiceImpl.java

    package com.imooc.spring.aop.service;
    
    /**
     * @author Rex
     * @create 2021-01-05 12:20
     */
    public class EmployeeServiceImpl implements EmployeeService{
        @Override
        public void createEmployee() {
            System.out.println("执行创建员工实现类");
        }
    }
    
    

    ProxyInvocationHandler.java

    package com.imooc.spring.aop.service;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * InvocationHandler是JDK提供的反射类,用于在JDK动态代理中对目标方法进行增强
     * InvocationHandler实现类与切面类的的环绕通知类似
     * @author Rex
     * @create 2021-01-05 12:06
     */
    public class ProxyInvocationHandler implements InvocationHandler {
        private Object target; //目标对象
        private ProxyInvocationHandler(Object target){
            this.target = target;
        }
    
        /**
         * 在invoke()方法对目标方法进行增强
         * @param proxy 代理类对象
         * @param method 目标方法对象
         * @param args 目标方法实参
         * @return 目标方法运行后返回值
         * @throws Throwable 目标方法抛出的异常
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("======" +new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").format(new Date()) + "======");
            Object ret = method.invoke(target, args);//调用目标方法, ProceedingJoinPoint.proceed()
            return ret;
        }
    
        public static void main(String[] args) {
            UserService userService = new UserServiceImpl();
            ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(userService);
            //动态创建代理类
            UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                    userService.getClass().getInterfaces(),
                    proxyInvocationHandler);
            userServiceProxy.createUser();
    
            //动态代理,必须实现接口才可以运行
            EmployeeService employeeService = new EmployeeServiceImpl();
            EmployeeService employeeServiceProxy = (EmployeeService) Proxy.newProxyInstance(employeeService.getClass().getClassLoader(),
                    employeeService.getClass().getInterfaces(),
                    new ProxyInvocationHandler(employeeService));
            employeeServiceProxy.createEmployee();
        }
    }
    
    

    运行结果:

    ======2021-01-05 12:23:20 020======
    执行创建用户业务逻辑
    ======2021-01-05 12:23:20 020======
    执行创建员工实现类
    

    CGLib实现代理类

    • CGLib是运行时字节码增强技术
    • Spring AOP扩展无接口类使用CGLib
    • AOP会运行时生成目标继承类字节码的方式进行行为扩展

    AOP代码实例:

    XML形式

    applicationContext.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 https://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
     <bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"></bean>
     <bean id="employeeDao" class="com.imooc.spring.aop.dao.EmployeeDao"></bean>
     <bean id="userService" class="com.imooc.spring.aop.service.UserService">
         <property name="userDao" ref="userDao"></property>
     </bean>
     <bean id="employeeService" class="com.imooc.spring.aop.service.EmployeeService">
         <property name="employeeDao" ref="employeeDao"></property>
     </bean>
    
     <bean id="methodChecker" class="com.imooc.spring.aop.aspect.MethodChecker"></bean>
     <aop:config>
         <aop:pointcut id="pointcut" expression="execution(* com.imooc..*.*(..))"/>
         <aop:aspect ref="methodChecker">
             <aop:around method="check" pointcut-ref="pointcut"></aop:around>
         </aop:aspect>
    
     </aop:config>
    </beans>
    

    UserDao.java

    package com.imooc.spring.aop.dao;
    
    /**
     * 用户表Dao
     */
    public class UserDao {
        public void insert(){
            System.out.println("新增用户数据");
        }
    }
    
    

    EmployeeDao.java

    package com.imooc.spring.aop.dao;
    
    /**
     * 员工表Dao
     * @author Rex
     * @create 2021-01-04 23:20
     */
    public class EmployeeDao {
        public void insert(){
            System.out.println("新增员工数据");
        }
    }
    
    

    Userervice.java

    package com.imooc.spring.aop.service;
    
    import com.imooc.spring.aop.dao.UserDao;
    
    /**
     * 用户服务
     * @author Rex
     * @create 2021-01-04 22:46
     */
    public class UserService {
        private UserDao userDao;
    
        public void createUser(){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("执行员工入职业务逻辑");
            userDao.insert();
        }
        public String generateRandomPassword(String type, Integer length){
            System.out.println("按" + type + "方式生成" + length + "位随机密码");
            return "Zxcquil";
        }
        public UserDao getUserDao() {
            return userDao;
        }
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    }
    
    

    EmployeeService.java

    package com.imooc.spring.aop.service;
    
    import com.imooc.spring.aop.dao.EmployeeDao;
    
    /**
     * 员工服务
     * @author Rex
     * @create 2021-01-04 23:20
     */
    public class EmployeeService {
        private EmployeeDao employeeDao;
        public void entry(){
            System.out.println("执行员工入职业务逻辑");
            employeeDao.insert();
        }
    
        public EmployeeDao getEmployeeDao() {
            return employeeDao;
        }
    
        public void setEmployeeDao(EmployeeDao employeeDao) {
            this.employeeDao = employeeDao;
        }
    }
    
    

    MethodCheck.java

    package com.imooc.spring.aop.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * @author Rex
     * @create 2021-01-04 23:07
     */
    public class MethodChecker {
        //ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
        public Object check(ProceedingJoinPoint pjp) throws Throwable {
    
            try {
                long startTime = new Date().getTime();
                Object ret = pjp.proceed();//执行目标方法
                long endTime = new Date().getTime();
                long duration = endTime - startTime;
                if (duration >= 1000){
                    String className = pjp.getTarget().getClass().getName();
                    String methodName = pjp.getSignature().getName();
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss");
                    String now = sdf.format(new Date());
                    System.out.println("==========="+now+":"+className+"."+methodName + "(" + duration+"ms)======");
                }
                return ret;
            } catch (Throwable throwable) {
                System.out.println("Exception message:"+throwable.getMessage());
                throw throwable;
            }
        }
    }
    
    

    SpringContext.java

    package com.imooc.spring.aop;
    
    
    import com.imooc.spring.aop.service.UserService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * @author Rex
     * @create 2021-01-04 23:20
     */
    public class SpringContext {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
            UserService userService = context.getBean("userService", UserService.class);
            userService.createUser();
        }
    }
    
    

    执行结果:

    执行员工入职业务逻辑
    新增用户数据
    ===========2021-01-05 14:15:17 017:com.imooc.spring.aop.service.UserService.createUser(3046ms)======
    

    注解形式

    applicationContext.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"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
        <context:component-scan base-package="com.imooc"></context:component-scan>
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    </beans>
    

    BookShop.java

    package com.imooc.spring.aop.entity;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @author Rex
     * @create 2021-01-05 10:53
     */
    @Component
    public class BookShop {
        public void sellingBooks(){
            System.out.println("卖出一本java基础书籍");
        }
    }
    
    

    MethodPro.java

    package com.imooc.spring.aop.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    /**
     * @author Rex
     * @create 2021-01-05 10:54
     */
    @Component
    @Aspect
    public class MethodPro {
    //    @Around("execution(public * com.imooc..*.*())")
        public void welcome(ProceedingJoinPoint pjd) throws Throwable {
            System.out.println("欢迎光临慕课小店");
            pjd.proceed();
            System.out.println("欢迎再次光临");
        }
    
        @Before("execution(public * com.imooc..*.*(..))")
        public void preSales(){
            System.out.println("=====售前服务=====");
        }
    
        @After("execution(public * com.imooc..*.*(..))")
        public void afterSale(){
            System.out.println("=====售后服务======");
        }
    }
    
    

    SpringApplication.java

    package com.imooc.spring.aop;
    
    import com.imooc.spring.aop.entity.BookShop;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * @author Rex
     * @create 2021-01-05 11:03
     */
    @ComponentScan
    public class SpringApplication {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
            BookShop bookShop = context.getBean("bookShop", BookShop.class);
            bookShop.sellingBooks();
        }
    }
    
    

    执行结果:

    =====售前服务=====
    卖出一本java基础书籍
    =====售后服务======
    
    
    展开全文
  • 北京炸酱面的做法

    2009-11-03 12:24:00
    材料:半肥瘦肉末500克、六必居黄酱1袋、清水150毫升、京葱1...京葱成碎末备用 2、炒锅内倒入比炒菜更多油烧热,油热后放入京葱末翻炒半分钟后倒入肉末一起翻炒至变色脱生,然后倒入调匀黄酱用最小火慢慢翻炒78
  • 除了IOC外, AOP是Spring的另一个核心. Spring利用AOP解决应用横关注点(cross-cutting concern)与业务逻辑...在OOP中, 如果要复用同一功能, 一般的做法是使用继承或委托. 继承容易导致脆弱的对象体系, 而委托实现起...
  • 成小丁(最好是有一点肥,比较香)丁越小越好,葱成末,备用。 4.油锅烧热,倒油。多少视你喜好而定。当然不要太少了,否则粘锅 5.待油烧热后,放入肉丁,煸一煸。 6.待肉丁8成熟时,把调好酱倒入锅内。...
  • AOP-面向切面编程

    2021-06-18 12:16:11
    AOP是Aspect-Oriented Programming 缩写,意思是面向切面变编程;代码在运行时候,是按照规定流程去运行,从这个角度看可以把我们写代码理解成一条固定流程;        ...
  • 切面里面实现一个全局管理的操作日志是件比较合理且容易的做法: 比如,谁,操作了那个接口.这个是毕竟容易的做法. 但是如果再细下去: 细化到每个人,具体改变了什么参数.改变之前是什么. 换言之,前后两次记录能够关联...
  • 面向切面编程 在程序中,如日志、安全、缓存、事务管理等不是业务本身该做,但是总是和很多业务逻辑一起出现,这些事件对业务逻辑来说是被动。在业务程序中被动地做这些事位置称为横切关注点,AOP所做事情...
  • 面条十种做法

    2010-10-01 21:48:00
    <br />1、红烧牛肉...   【做法】   ① 牛肉洗净块,氽烫去血水。   ② 起油锅,炒香葱、姜、葱段、八角、辣椒、牛肉,加入酱油1杯、水6杯、酒1汤匙、辣豆瓣酱1大匙,用中
  • 简言之、这种在运行时,编译时,类和方法加载时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。 我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点...
  • 结合小青煤矿W2-713工作实例,介绍了刨煤机工作眼断面形式对初采影响,总结了在实际生产过程中经验和做法,从更改眼断面设计入手,着重对施工方法、技术要求以及支护参数进行了分析,给出了...
  • 一、在Advice方法中获取目标方法的参数1、获取目标方法的信息访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的...
  • 访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。JoinPoint里包含了如下几个常用的方法: Object[] ...
  • 背景:我们可能希望对项目中接口性能进行监控,例如:接口调用时间,开始时间,结束时间,传入参数等等,...可以考虑使用spring切面和环绕通知来打印这部分日志,并使用log4j2将这部分日志单独拆分出来。ps:拆分...
  • 此时需要对数据库做水平切分,常见的做法是按照用户的账号进行hash,然后选择对应的数据库,以下是在springboot项目中利用AOP面向切面技术实现两个不同数据库之间的来回切换功能 一 配置数据源连接池 application....
  • 上网查了很多的资料,总结一下自己的做法历程吧。 最先想到的是用HttpSessionBindingListener的一个监听器,于是乎我就写了一个aop。。。呵呵呵。了一个判断session的方法,为什么这么做呢?业务需要。。。 上...
  • Author:赵志乾 ...场景:项目采用多数据源配置,在切面中完成数据源动态切换。当业务代码抛异常后,没有自动切回默认数据源。 做法: public Object around(ProceedingJoinPoint point, Ch...
  • AOP:切面编程常用API

    2018-05-07 10:02:44
    获取目标方法的信息 访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。JoinPoint里包含了如下几个常用...
  • 一、在Advice方法中获取目标方法的参数1、获取目标方法的信息访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的...
  • 具体做法,就是利用SpringAOP,拦截所有需要拦截方法(连接点)行成一个切入点(Pointcut),然后在其前面加入通知(advice就是一些操作),比如我要登录,我before操作就是查看是否有登录。如果有,就继续...
  • 首先控制反转和面向切面编程是spring核心机制,由于控制反转不好理解,因此有人提出依赖注入概念,其实依赖注入是控制反转一种方式,但现在大家默认控制反转等同于依赖注入 一.控制反转:控制反转是指spring...
  • 打个比方,你晚上想约个妹子去看电影,假设这个妹子是一个类(温柔、善良、小鸟依人),那么你需要实例化她,在JAVA中,通常的做法就是new一个类,让她变成一个实体的对象。然后,你跟她约了时间、地点,说要请客...
  • 备料:(两成人用)细切面(同阳春面细程度)500克,五花肉100克,黄豆芽(或豇豆,蒜苔)250克。 1.将肉切成条状或块状,用老抽,五香粉,姜片,葱段,料酒腌15分钟。 2.用蒸锅烧水,将细切面上锅,水沸后中火...
  • 访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。JoinPoint里包含了如下几个常用的方法: Object[] ...
  • 打个比方,你晚上想约个妹子去看电影,假设这个妹子是一个类(温柔、善良、小鸟依人),那么你需要实例化她,在JAVA中,通常的做法就是new一个类,让她变成一个实体的对象。然后,你跟她约了时间、地点,说要请客...

空空如也

空空如也

1 2 3 4 5 ... 16
收藏数 306
精华内容 122
关键字:

切面的做法