Spring 6【前置通知注解实现、同一切面多个相同类型通知执行顺序、代理设计模式、动态代理示例】(十六)-全面详解(学习总结---从入门到深化)

目录

前置通知注解实现

同一切面多个相同类型通知执行顺序

代理设计模式

动态代理示例


2.前置通知注解实现

Spring 框架中AOP注解因为替代的是XML文件中AspectJ的配置,所以注解名称和标签名称相同,方便我们记忆。

2.1 编写切入点方法

新建com.tong.annotation.DemoServiceImpl

@Service// 放入IoC容器
public class DemoServiceImpl {
   // 方法上不需要添加任何注解
   public void demo(){
        System.out.println("demo");
    }
}

 2.2 编写前置通知方法

新建com.tong.annotation.AnnotationAdvice

@Before 注解参数写切点表达式。

@Aspect 切点类必须有的注解

@Aspect // 通知类必须有此注解
@Component
public class AnnotationAdvice {
     @Before("execution(* com.tong.annotation.DemoServiceImpl.demo())")
     public void before(){
          System.out.println("before");
     }
 }

2.3 编写测试类,调用切入点demo()方法

@SpringJUnitConfig(classes = AnnotationConfig.class)
public class AnnotationTest {
    @Autowired
    DemoServiceImpl demoService;
    @Test
    void test(){
        demoService.demo();
    }
}

3.其他通知注解实现

  • @AfterReturning 后置通知注解
  • @After 后置通知注解
  • @Around 环绕通知
  • @AfterThrowing 异常通知注解 
@AfterReturning("execution(* com.tong.annotation.DemoServiceImpl.demo())")
public void afterReturning(){
     System.out.println("after-returning");
  }
@After("execution(* com.tong.annotation.DemoServiceImpl.demo())")
public void after(){
     System.out.println("after");
}
@Around("execution(* com.tong.annotation.DemoServiceImpl.demo())")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
     System.out.println("around-前置");
     Object result = pjp.proceed();
     System.out.println("around-后置");
     return result;
}
@AfterThrowing("execution(* com.tong.annotation.DemoServiceImpl.demo())")
public void afterThrowing(){
      System.out.println("异常通知");
 }

 4.@Pointcut注解

一般情况下,一个通知类中不管什么类型通知,都是针对一个切点的。每次在写这些切点的时候表达式 都是比较长的。 这时可以编写一个空方法,空方法上面添加@Pointcut定义切点。其他通知方法直接引用这个空方法就 可以了。

@Aspect
@Component
public class DemoAdvice {
@Pointcut("execution(* com.tong.annotation.DemoServiceImpl.demo())")
public void mypointcut(){}
@Before("mypointcut()")
void before(){
     System.out.println("before-pointcut");
   }
}

 而且不用担心不同类中有相同的@Pointcut表达式。

可以把DemoAdvice再复制一份,变为Demo2Advice。运行程序,依然可以正常执行,并不会报错

@Aspect
@Component
public class Demo2Advice {
@Pointcut("execution(* com.tong.annotation.DemoServiceImpl.demo())")
public void mypointcut() {
   // 方法体内无论是否有内容,都不会被执行。
   }
   @Before("mypointcut()")
   void before() {
       System.out.println("before-pointcut2");
   }
}

 5.同一切面多个相同类型通知执行顺序

因为AspectJ是编译增强,所以在使用AspectJ注解方式实现时,同一个切面中,相同类型通知的执行顺 序和方法、类的编译顺序有关系。

主要分为三种情况:

1、多个相同类型通知方法在同一个类中,这时按照方法名称确定执行顺序

2、多个相同类型通知方法不在同一个类中,这时按照类名确定执行顺序。

3、可以在通知类上添加@Order(数字)控制类中方法优先级。数字越小优先级越高。这个类中相同类型 方法优先级搞过其他类相同方法优先级

强调:所说的相同类型通知肯定是同一个切面中,不是同一个切面的通知,没有套路的必要 

5.1 同类中相同类型通知 

同类中相同名称通知,按照名称确定执行顺序,下面代码先输出before1,后输出before2

@Before("execution(* com.tong.annotation.DemoServiceImpl.demo())")
public void before1(){
     System.out.println("before1");
}
@Before("execution(* com.tong.annotation.DemoServiceImpl.demo())")
public void before2(){
     System.out.println("before2");
}

如果把before1方法名称变为before3,这时先输出before2,后输出before1

@Before("execution(* com.tong.annotation.DemoServiceImpl.demo())")
public void before3(){
      System.out.println("before1");
}
@Before("execution(* com.tong.annotation.DemoServiceImpl.demo())")
public void before2(){
      System.out.println("before2");
}

5.2 不同类中相同类型通知的执行顺序

不同类中相同类型通知的执行顺序和通知方法所在类的名称有关。不同类中通知和方法名称无关。

类名优先级高的通知 > 类名优先级低的 - 同类中优先级高的 > 类名优先级低的 - 同类中优先级高的。

也就是说:对于同类型通知类名优先级高的通知会全部执行完,才会执行类名优先级低的通知。

同类型通知包含:

1、前置:前置通知、环绕-前置

2、后置:后置返回值通知、后置通知、环绕-后置

3、异常:异常通知 

新建AnnotationAdvice2类,并配置一个前置通知,发现输出顺序:环绕-前置->before2->before1- >before3

@Aspect
@Component
public class AnnotationAdvice2 {
@Before("execution(* com.tong.annotation.DemoServiceImpl.demo())")
     public void before1(){
         System.out.println("before3");
    }
}

修改AnnotationAdvice类名称为AnnotationAdvice3,执行发现执行顺序:

before3->环绕-前置->before2->before1

5.3 通知@Order注解,控制通知类中通知的执行顺序

@Order注解可以控制通知类的优先级。参数value是int类型值,参数值越小优先级越高,一般多使用正数。

当使用@Order注解后,通知的执行顺序和类名无关。

注意:虽然@Order是类、方法、属性级注解。但是在Spring Framework 6中无法给同类相同类型通知控制执行顺序。

在AnnotationAdvice3中添加@Order,并设置参数为1

@Aspect // 通知类必须有此注解
@Component
@Order(1)
public class AnnotationAdvice3 {
   
   

 在AnnotationAdvice2中添加@Order,并设置参数为2

@Aspect
@Component
@Order(2)
public class AnnotationAdvice2 {

运行程序,发现执行时执行顺序又变为:环绕-前置->before2->before1->before3

6.切面中所有通知的执行顺序

在同一个切面中通知的执行顺序和通知方法是否在同一个通知类中有关。

同一个通知类中,固定按照下面执行顺序,通过修改方法名称等无法控制执行顺序:

        1、环绕-前置

         2、前置

         3、切入点方法

         4、后置带返回值

         5、后置通知

不在同一个类中,同类型通知按照类的优先级执行。所以想要控制切面中不同类型执行顺序,最好 把这些通知定义到不同的类中。

7. 通知参数

使用注解时,通知方法也可以设置方法参数,使用方法参数可以获取到切入点的方法参数、返回值、抛出的异常。

在DemoServiceImpl中添加一个带有参数和返回值的方法。

public String demo2(String name,int age){
    System.out.println("demo2:name:"+name+",age:"+age);
    return name+","+age;
}

新建一个通知类MyAdviceParameter。

注意:

以前在XML中绑定参数表达式使用and关键字,而在Java代码中使用&&符号 使用@Pointcut绑定起来更简单。空方法必须带有能与之对应的参数

@Aspect
@Component
public class MyAdviceParameter {
@Pointcut("execution(* com.tong.annotation.DemoServiceImpl.demo2(String,int)) && args(name,age)")
public void pc(String name,int age){
        System.out.println("pc:name:"+name+",age:"+age);// 输出语句不会被输出
  }
// @Before(value = "execution(* com.tong.annotation.DemoServiceImpl.demo2(String,int)) && args(name,age)")
@Before("pc(name,age)")
void before(String name,int age){
       System.out.println("before-name:"+name+",age:"+age);
}
@AfterReturning(value="pc(name,age)",returning = "result")
void afterReturning(String name,int age,String result){
       System.out.println("afterReturning:name:"+name+",age:"+age+",result:"+result);
}
@After("pc(name,age)")
void after(String name,int age){
      System.out.println("after:name:"+name+",age:"+age);
}
@AfterThrowing(value = "pc(name,age)",throwing = "e")
void afterThrowing(String name,int age,Exception e){
      System.out.println("afterThrowing:name:"+name+",age:"+age+",e:"+e);
 }
}

代理设计模式

1.介绍

Spring 框架中AOP底层使用动态代理设计模式。通过学习动态代理设计模式可以很好的理解Spring框架 AOP底层

代理模式(Proxy)是GoF23种设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。

代理设计模式包括:静态代理和动态代理。

静态代理:代理对象由程序员自己编写,里面提供硬编码方式来访问调用者

动态代理:

        JDK动态代理:一种基于接口的代理实现

        Cglib动态代理:一种基于继承的代理实现 

代理设计模式优点:

保护代理对象。客户不能直接访问真实对象。只能通过代理对象进行访问。

可扩展性。复合开闭原则,可以实现在不修改真实代理对象情况下,进行扩展。 

对于一些访问困难的真实对象,可以使用代理对象,更轻松的访问。

代理设计模式缺点: 

系统复杂性增加。类的数目增加,代码相对更复杂一点。

性能降低。每次访问真实对象,只能通过代理对象。

2.静态代理示例

以房屋买卖为例子(简易流程),客户想要买房,找中介,由中介带看房东的房子。

客户看好了,想要买,房东也同意买了,中介收取客户的中介费。

创建房东(真实对象) 

新建com.tong.proxy.staticproxy.Fangdong

public class Fangdong {
public void bigHouse(){
    System.out.println("房东的大房子");
  }
}

 创建中介类(代理对象)

新建com.tong.proxy.staticproxy.Zhongjie

public class Zhongjie {
public void zhongjie(){
    System.out.println("约谈客户,进行带看");// 扩展功能
    Fangdong fangdong = new Fangdong();
    fangdong.bigHouse();
    System.out.println("交易成功,收取中介费"); // 扩展功能
  }
}

 创建客户类(调用者)

com.tong.proxy.staticproxy.Kehu

public class Kehu {
public static void main(String[] args) {
      Zhongjie zhongjie = new Zhongjie();
      zhongjie.zhongjie();
    }
}

 3.动态代理示例

3.1JDK动态代理

JDK动态代理是基于接口来实现的,底层是基于Java 反射技术实现的。

创建接口

创建接口com.tong.proxy.jdkproxy.House

因为JDK动态代理要求调用者必须实现接口。所以先建立接口

public interface House {
    void bigHouse();
}

创建真实对象类

创建类com.tong.proxy.jdkproxy.Fangdong

public class Fangdong implements House{
   @Override
   public void bigHouse() {
       System.out.println("大房子");
   }
}

创建代理类

创建com.tong.proxy.jdkproxy.Zhongjie

public class Zhongjie implements InvocationHandler {
  private Object target;
    public Zhongjie(Object target) {
         this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     System.out.println("带看");
     Object result = method.invoke(target, args);
     System.out.println("收取中介费");
     return result;
   }
}

 创建客户类

创建com.tong.proxy.jdkproxy.Kehu

public class Kehu {
     public static void main(String[] args) {
          InvocationHandler handler = new Zhongjie(new Fangdong());
          /*
            newProxyInstance(ClassLoader loader,Class<?>[]
            interfaces,InvocationHandler h)
            loader: 类加载器。让生成的代理对象立即生效
            interfaces:接口数组对象。基于哪个接口生成代理对象。
            h:负责调用真实对象的处理类,相当于代理类。
            House house 里面存储的实际上是Fangdong访问的代理对象。这个代理对象实现了House接口
             */
          House house = (House)
          Proxy.newProxyInstance(Fangdong.class.getClassLoader(),
          new Class[]{House.class},handler);
          house.bigHouse();
     }
}

 3.2Cglib动态代理

Cglig是基于继承的,是第三方提供的技术,需要导入jar包,并且。

<dependencies>
      <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>3.3.0</version>
      </dependency>
</dependencies>

创建房东类

public class Fangdong {
 public void bigHouse(){
       System.out.println("大房子");
   }
}

创建中介类

public class Zhongjie implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
      System.out.println("带看");
      // 注意方法,是invokeSuper,不是invoke
      Object result = methodProxy.invokeSuper(o, objects);
      System.out.println("收中介费");
      return result;
   }
}

创建客户类

public class Kehu {
public static void main(String[] args) {
       Enhancer enhancer = new Enhancer();
       enhancer.setSuperclass(Fangdong.class);// 代理对象为Fangdong的子类
       enhancer.setCallback(new Zhongjie());// 负责处理调用的
       Fangdong fangdong = (Fangdong) enhancer.create();// 创建代理对象
       fangdong.bigHouse();
    }
}

测试结果,发现出现异常,这是因为Java 17版本中的Java Platform Module System(java 9就开始有 了)引起的,特别是强封装的实现。

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make
protected final java.lang.Class
java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.
ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base
does not "opens java.lang" to unnamed module @1376c05c

把代码原封不动的放在Java 11的环境中就可以使用。

如果必须要在Java17中使用添加JVM参数,表示允许使用 未命名模块。

--add-opens java.base/java.lang=ALL-UNNAMED

  

猜你喜欢

转载自blog.csdn.net/m0_58719994/article/details/132001398