spring核心之AOP面向切面编程

109 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。 AOP面向切面编程,增强方法功能。

举例说明:

比如你要给你几个方法都增加一个打印日志的功能,传统办法是每个方法内都增加打印日志的代码,这样的问题是增加了代码的复杂度,aop思想就是可以使用代理的方式,通过映射这些方法,就可以使用一次代码给每个方法增加 打印日志的功能。

切面实现方式都是基于代理的方式。

一。java jdk实现AOp编程实例

我的目的:要给MyCalculator所有接口方法都打印日志。 切面(共同之处):都打印日志。

1.接口MyCalculator.interface

package com.chuan.method;

public interface MyCalculator {
    int add(int a ,int b);
    int sub(int a ,int  b);
}

2.接口实现类MyCalculator.java

package com.chuan.Impl;

import com.chuan.method.MyCalculator;

/**
 * @Author 张川
 * @博客 zhangchuan.blog.csdn.net
 * @Date 2020-09-11-10:29
 */

/**
 *
 * 实现MyCalculator接口
 */
public class MyCalculatorImpl  implements MyCalculator {

    //实现add方法
    public int add(int a, int b) {
        return  a+b;
    }
    //实现sub方法。
    public int sub(int a, int b) {

        return a-b;
    }
}

3.代理类 MyCalculatorProxy.java

package com.chuan.Agent;

import com.chuan.Impl.MyCalculatorImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Author 张川
 * @博客 zhangchuan.blog.csdn.net
 * @Date 2020-09-11-10:31
 */
public class MyCalculatorProxy {

    //获得一个代理实例  参数是MyCalculator接口
    public static Object getInstance(final MyCalculatorImpl myCalculator){
        /**
         *
         * newProxyInstance参数1 是代理实例映射,第二个是接口映射,第三个是动态代理具体方法。
         */
        return Proxy.newProxyInstance (MyCalculatorProxy.class.getClassLoader (), myCalculator.getClass ().getInterfaces (), new InvocationHandler () {
            /**
             * @param proxy   代理对象
             * @param method  代理方法   这里就是myCalculator实现的接口方法add
             * @param args      代理方法的参数。  指的就是add方法参数 int a, int b
             * @return    返回值就是方法
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object invoke = null;
                System.out.println (method.getName ()+"开始执行方法");
                invoke = method.invoke (myCalculator, args);  //调用myCalculator里面的接口方法,指向Mycalculator所有实现的接口方法
                System.out.println (method.getName ()+"方法执行完了");
                return invoke;
            }
        });
    }

}

//我们可以看到就用了一次代码,就实现了对所有接口方法增加打印日志功能。

4.main方法调用

package com.chuan.Main;

import com.chuan.Agent.MyCalculatorProxy;
import com.chuan.Impl.MyCalculatorImpl;
import com.chuan.method.MyCalculator;

/**
 * @Author 张川
 * @博客 zhangchuan.blog.csdn.net
 * @Date 2020-09-11-10:47
 */
public class Main {
    public static void main(String[] args) 
        
        MyCalculatorImpl myCalculator = new MyCalculatorImpl ();
        //使用代理MyCalculatorProxy调用
        MyCalculator calculator = ((MyCalculator) MyCalculatorProxy.getInstance (myCalculator));
        int add = calculator.add (3, 4);
        System.out.println ("add = " + add);
        int sub = calculator.sub (5, 4);
        System.out.println ("sub = " + sub);

    }

}

  1. 代码结果 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6103JYnS-1607788957397)(i.niupic.com/images/2020…)]

二。spring里面的AOP编程

AOP思想:使用不侵入的方式,给一些方法增加你想要增加的功能。

切面:里面存放了很多切点

切点:就是你想要增强的的方法

实现方式:

  • spring xml配置方式
  • 注解的方式

AOP里面的五种通知。

  • 前置通知
  • 后置通知
  • 异常通知
  • 返回通知
  • 环绕通知

最常见的是要给一些方法中都增加通知,那么就有以上通知类型。 其中 环绕通知是 前置通知 和后置通知集成。

使用到的注解:

  • Aspect 标识这是切面
  • @Pointcut("execution(* com.chuan.method.CalculatorImpl.add(..)))") 标识切点
  • @AfterReturning @Before (前置通知) @After(后置通知) @AfterThrowing(异常通知) 几种通知使用的注解
spring aop使用到的包:

        <!--aop切面编程使用的两个包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.5</version>
        </dependency>


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

我们创建一个接口Calculator:
package com.chuan.method;

public interface Calculator {

    void add(int a ,int b);

    int  min(int a, int b);

}

实现接口CalculatorImpl:

package com.chuan.method;
import org.springframework.stereotype.Component;

/**
 * @Author 张川
 * @博客 zhangchuan.blog.csdn.net
 * @Date 2020-09-11-21:13
 */

@Component
//这是一个组件
public class CalculatorImpl implements Calculator  {

    public void add(int a, int b) {
        System.out.println (a+"+"+b+"="+(a+b));

    }

    public int  min(int a, int b) {
        System.out.println (a+"-"+b+"="+(a-b));
        return (a-b);
    }
}

创建一个切面类:
package com.chuan.method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Author 张川
 * @博客 zhangchuan.blog.csdn.net
 * @Date 2020-09-12-14:42
 */
/**
 *
 * Aspect这是一个切面
 */
@Component
@Aspect
public class LogAspect {

    /**
     * 这是自定义的切点pointcut, execution是自定义规则拦截,也就是符合这些规则的方法 将增加这些通知
     */
    @Pointcut("execution(* com.chuan.method.CalculatorImpl.add(..)))") //*是 任意
    public void pointcut(){
    }

    /**
     * 前置通知,满足切点pointcut就会通知,在方法执行前通知
     * @param joinPoint  指向满足切点规则的方法对象
     */
    @Before ("pointcut())")
    public void before(JoinPoint joinPoint){
        String name = joinPoint.getSignature ().getName ();
        System.out.println (name+"方法开始执行了。。。");
    }

    /**
     * 满足切点是pointcut的,就会有后置通知
     * 他在方法执行完之后执行
     * @param joinPoint
     */
    @After (value = "pointcut()")
  public void after(JoinPoint joinPoint){
      String name = joinPoint.getSignature ().getName ();
      System.out.println (name+"方法执行完了。。。");
  }


  @AfterReturning(value = "pointcut()",returning ="r" )
//返回通知,returning是返回的参数名称
  public void  failException(JoinPoint joinPoint,Object r){
      String name = joinPoint.getSignature ().getName ();
      System.out.println (name+"方法返回通知"+r);

  }

    /**
     * 异常通知,满足切点是pointcut的就会有异常通知
     */
    @AfterThrowing(value = "pointcut()",throwing = "e")
public void Throwing(JoinPoint joinPoint,Exception e){
        String name = joinPoint.getSignature ().getName ();
        System.out.println (name+"方法异常通知:"+e.getMessage ());
    }
}

切面里面通常有5种通知,我们自定义切点,通过切点,给需要的方法,增加这些通知。

JavaConfig配置文件

IOC就是Spring的一个容器,将一些类通过注解@Configuration注入IOC容器中,成为一个个Bean,需要使用的时候直接使用:AnnotationConfigApplicationContext加载JavaCOnfig配置文件,就可以获取Bean了,这样有利于降低耦合度。

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext (JavaConfig.class);
        Calculator  calculatorImpl = (Calculator ) ctx.getBean ("CalculatorImpl",Calculator.class);

JavaConfig.class:

package com.chuan.method;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @Author 张川
 * @博客 zhangchuan.blog.csdn.net
 * @Date 2020-09-12-13:55
 */

//Configuration 使得可以注入IOC
@Configuration
@EnableAspectJAutoProxy  //很关键 要开启切面自动代理
public class JavaConfig {

    //这是一个Bean  名称是Calculator
    @Bean("CalculatorImpl")
    Calculator calculatorImpl(){
        return  new CalculatorImpl ();
    }
}


Main文件测试:
package com.chuan.method;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @Author 张川
 * @博客 zhangchuan.blog.csdn.net
 * @Date 2020-09-11-21:15
 */
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext (JavaConfig.class);
        Calculator  calculatorImpl = (Calculator ) ctx.getBean ("CalculatorImpl",Calculator.class);
        calculatorImpl.add (3,4);
        calculatorImpl.min (3,4);
        
        

    }

}

总的来说思想方法:

创建一个切面,自定义切点,定义规则 需要在哪些方法 切入通知。使用到的注解

  • @Aspect,

  • @Pointcut

  • @Before

  • @After

  • @AfterThrowing ,

  • @AfterReturning ,

  • @Around