Spring初级:面向切面编程【AOP】

46 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第33天,点击查看活动详

一、代理模式

所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网上连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。 代理实现方式有:手动方式、半自动方式、全自动方式3种。

  • 手动方式:指所有功能手动实现

  • 半自动方式:指由容器自动创建代理。

  • 全自动方式:指如果配置了aop,从容器获得的目标类对象就是代理

image.png

静态代理

实现步骤

创建接口

//租房接口
public interface Rent {
    public void rent();
}

真实角色

 //房东
 public class Host implements Rent{
     public void rent() {
         System.out.println("房东要出租房子");
     }
 }

代理角色

 package com.zhao.demo01;
 ​
 import javax.sound.midi.SoundbankResource;
 ​
 //代理
 public class Proxy implements Rent{
     private Host host;
 ​
     public Proxy() {
     }
 ​
     public Proxy(Host host) {
         this.host = host;
     }
 ​
 ​
     public void rent() {
         setHost();
         host.rent();
         hetong();
         fare();
     }
 ​
     //看房
     public void setHost(){
         System.out.println("中介带你看房");
     }
     //签合同
     public void hetong(){
         System.out.println("中介签合同");
     }
     //看房
     public void fare(){
         System.out.println("中介收中介费");
     }
 }
 ​

客户端访问代理角色

 package com.zhao.demo01;
 //客户
 public class Client {
     public static void main(String[] args) {
         //房东出租房子
          Host host=new Host();
          //代理,中介帮房东出租房子,代理会有一些附属操作!
         Proxy proxy=new Proxy(host);
         //客户直接找中介租房,不要面对房东
         proxy.rent();
     }
 }

好处:

  • 可以使真是角色的操作更加纯粹,不用关注一些公共的业务
  • 公共也就交给代理角色,实现业务的分工
  • 公共业务发生扩展的时候,方便集中管理。

缺点:

  • 一个真实角色就会产生一个代理角色,代码量翻倍开发效率变低。

动态代理

动态代理分为两大类:基于接口的动态代理,基于类的动态代理

1.基于接口-----JDK动态代理【**】

2.基于类:cglib

JDK动态代理

创捷目标接口和实现类

public interface UserService {
    void saveUser();
}

public class UserServiceImpl implements UserService{
    /**
     * 切入点
     */
    @Override
    public void saveUser() {
        System.out.println("UserServiceImpl.saveUser");
    }
}

创建切面类

public class MyAspect {
    //增强 通知
    public void before(){
        System.out.println("打开事务");
    }

    public void after(){
        System.out.println("关闭事务");
    }
}

创建代理工厂类

/**
 * 代理工厂类 生产目标类的代理
 */
public class ProxyFactory {
    /**
     * 获得目标类对象的代理 同时用切面类进行增强
     *
     * @return
     */
    public UserService proxyUserService() {
        //目标类对象
        UserService userService = new UserServiceImpl();
        //切面类对象
        MyAspect myAspect = new MyAspect();
        //生成目标类的代理对象 使用jdk代理机制
        Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{UserService.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //目标方法前增强
                        myAspect.before();
                        //目标方法调用
                        method.invoke(userService);
                        //目标方法后增强
                        myAspect.after();

                        return proxy;
                    }
                });
        return (UserService) proxy;
    }
}

CGLIB字节码增强

创建目标实现类

public class UserSeriver {
    public void saveUser(){
        System.out.println("UserService.saveUser");
    }
}

编写切面类,对连接点进行增强。

public class MyAspect {

    public void before(){
        System.out.println("目标方法前增强");
    }
}

创建工厂,生产目标类的代理类对象对目标类进行增强。

public class CglkibProxyFactory {
    public UserSeriver cglibUserSeriver(){
        //创建目标类对象
        UserSeriver userSeriver=new UserSeriver();
        //创建切面类对象
        MyAspect myAspect=new MyAspect();
        //用cglib生成代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserSeriver.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //方法前增强
                myAspect.before();
                //目标方法
                method.invoke(userSeriver);
                return o;
            }
        });
        Object proxy = enhancer.create();
        return (UserSeriver) proxy;
    }
}

动态代理的好处:

  • 可以使真是角色的操作更加纯粹,不用关注一些公共的业务
  • 公共也就交给代理角色,实现业务的分工
  • 公共业务发生扩展的时候,方便集中管理。
  • 一个动态代理类代理的是一个接口,一般是对应的一类业务
  • 一个动态代理类可以代理多个类,只要实现了同一个接口即可!

二、面向切面AOP

什么是AOP?

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

image.png

引入依赖

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
</dependency>

相关术语

  • Target --目标类。表示将被添加横切逻辑的目标对象。

  • JoinPoint -- 连接点。表示程序中可以插入横切功能的点。Spring中仅支持方法调用前后作为连接点。

  • PointCut -- 切入点、切点。表示需要插入横切功能的一系列连接点。

  • Advice -- 通知、增强。代表了横切逻辑功能。

  • Aspect -- 切面、方面。表示一整套Advice、PointCut的组合,既包括了横切功能也包括了切点定义。

  • Weaving --织入。织入是指将增强(Advice)添加到切入点(PointCut)上的过程。

  • Proxy -- 代理 。注入成功后产生代理对象。

总结:根据PointCut(切点),在Target(目标类)的部分JoinPoint(连接点)处Weaving(织入) Advice(增强)。

image.png

Advice(通知)类型

  • 前置通知(before),目标方法之前执行的增强

  • 后置通知(after),目标方法之后执行的增强,包括以下子类型:

      (1) 方法正常返回的Advice(after returning);
    
      (2) 方法异常的Advice(after throwing);
    
      (3) 方法最终的Advice(after);
    
  • 环绕通知(around),环绕着目标方法的增强,可以在方法调用之前和执行之后进行增强,并且自行决定是否执行目标方法、还可以替换返回值等。

配置切面

在spring配置文件中配置切面信息

  • 在配置文件中添加aop命名空间:

      xmlns:aop="http://www.springframework.org/schema/aop"
    
      xsi:schemaLocation= " http://www.springframework.org/schema/aop
    
      [http://www.springframework.org/schema/aop/spring-aop.xsd"](http://www.springframework.org/schema/aop/spring-aop.xsd%22)
    
  • 配置AOP:所有AOP相关的配置都要放在aop:config元素内部,包括:

     <aop:pointcut id="" expression="">:定义切入点
    
     <aop:advisor>:只包含一个增强和一个切点。通常和事务增强一起使用。
    
     <aop:aspect ref="">:定义切面,可以包含多个增强。
    
  • 配置前置增强、通知:<aop:before method="" pointcut-ref="" pointcut=""/>

    pointcut-ref属性和pointcut属性不能同时出现

  • 配置后置通知、增强:<aop:after-returning method="" pointcut-ref="" returning=""/>

  •   配置异常通知、增强:<aop:after-throwing method="" pointcut-ref="" throwing=""/>

  •   配置最终通知、增强:<aop:after method="" pointcut-ref=""/>

  • 配置环绕通知、增强:<aop:around method="" pointcut-ref=""/>

切入点表达式

  • aop:pointcut元素的expression属性使用AspectJ的语法描述作为切点的目标方法:

      语法:execution([modifiers] returnType methodName(paramTypes) [throws] )
    
      modifiers:目标方法的权限修饰符,可以省略,通常省略。
    
      returnType:目标方法返回值类型。可使用全路径的类名、*代表任意类型返回值
    
      methodName:目标方法名,需带有包名和类名完全限定。也可使用通配符*表示所有方法
    
      paramTypes :目标方法参数个数及类型,(..)代表任意个数和任意类型,(*)代表一个任意类型参数。
    
  • 切入点表达式案例:

     exection(public * com.a.*(..)) //com包a类的所有public方法,返回值任意,参数任意
    
     exection(* com.abc.service.*.*(..))//com.abc.service包的所有类的所有方法,返回值任意,参数任意
    
     exection(* com.abc.service..*.*(..))//com.abc.service包及所有子包中的所有类的所有方法
    
     exection(* com.abc.service.*.save*(..))//com.abc.service包中所有类的save开头的所有方法
    
     exection(* com.abc.service.*Impl.*(..))
    
     exection(* com.*.*(*))
    
     exection(* com.*.*(int,String))
    
     exection(String com.*.*(..))