首页 > 编程语言 >基于AspectJ的AOP注解式开发

基于AspectJ的AOP注解式开发

时间:2023-06-11 17:35:14浏览次数:37  
标签:service .. 通知 org 切点 AspectJ AOP 注解 public

1. Spring对AOP的实现包括以下3种方式: 106

1.1 三种方式  106

● 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。

● 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。

● 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。

实际开发中,都是Spring+AspectJ来实现AOP。所以我们重点学习第一种和第二种方式。

1.2 什么是AspectJ?  106

(Eclipse组织的一个支持AOP的框架。AspectJ框架是独立于Spring框架之外的一个框架,Spring框架用了AspectJ) 

AspectJ项目起源于帕洛阿尔托(Palo Alto)研究中心(缩写为PARC)。该中心由Xerox集团资助,Gregor Kiczales领导,从1997年开始致力于AspectJ的开发,1998年第一次发布给外部用户,2001年发布1.0 release。为了推动AspectJ技术和社团的发展,PARC在2003年3月正式将AspectJ项目移交给了Eclipse组织,因为AspectJ的发展和受关注程度大大超出了PARC的预期,他们已经无力继续维持它的发展。

2. 准备工作  107

使用Spring+AspectJ的AOP需要引入的依赖如下:

<!--spring context依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>6.0.0-M2</version>
</dependency>
<!--spring aop依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>6.0.0-M2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>6.0.0-M2</version>
</dependency>

Spring配置文件中添加context命名空间和aop命名空间

<?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">

</beans>

3. 基于AspectJ的AOP注解式开发  108

实现步骤

3.1 第一步:定义目标类以及目标方法

package com.powernode.spring6.service;

import org.springframework.stereotype.Service;

/**
 * 基于AspectJ的AOP注解式开发  108
 * 业务流程
 **/
@Service("userService")
public class UserService { // 目标类

    public void login(){ // 目标方法
        System.out.println("系统正在进行身份认证....");
    }

//    public void logout(){
//        System.out.println("退出系统...");
//    }

}

3.2 第二步:定义切面类

package com.powernode.spring6.service;

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

/**
 * 基于AspectJ的AOP注解式开发  108
 * 切面
 **/
@Component("logAspect") //这是纳入spring容器管理
@Aspect // 切面类是需要使用@Aspect注解进行标注的。
public class LogAspect { // 切面

}

3.3 第三步:目标类和切面类都纳入spring bean管理

在目标类OrderService上添加@Component注解。

在切面类MyAspect类上添加@Component注解。

3.4 第四步:在spring配置文件中添加组建扫描

<?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">

<!--    组件扫描  108-->
    <context:component-scan base-package="com.powernode.spring6.service"/>
</beans>

3.5 第五步:在切面类中添加通知

package com.powernode.spring6.service;

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

/**
 * 基于AspectJ的AOP注解式开发  108
 * 切面
 **/
@Component("logAspect") //这是纳入spring容器管理
@Aspect // 切面类是需要使用@Aspect注解进行标注的。
public class LogAspect { // 切面
    // 切面 = 通知 + 切点
    // 通知就是增强,就是具体的要编写的增强代码
    // 这里通知Advice以方法的形式出现。(因为方法中可以写代码)
    // @Before注解标注的方法就是一个前置通知。
       public void 增强(){ //通知
        System.out.println("我是一个通知,我是一段增强代码....");
    }
}

3.6 第六步:在通知上添加切点表达式

package com.powernode.spring6.service;

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

/**
 * 基于AspectJ的AOP注解式开发  108
 * 切面
 **/
@Component("logAspect") //这是纳入spring容器管理
@Aspect // 切面类是需要使用@Aspect注解进行标注的。
public class LogAspect { // 切面
    // 切面 = 通知 + 切点
    // 通知就是增强,就是具体的要编写的增强代码
    // 这里通知Advice以方法的形式出现。(因为方法中可以写代码)
    // @Before注解标注的方法就是一个前置通知。
    @Before("execution(* com.powernode.spring6.service.UserService.*(..))") //切点
    public void 增强(){ //通知
        System.out.println("我是一个通知,我是一段增强代码....");
    }
}

注解@Before表示前置通知。

3.7 第七步:在spring配置文件中启用自动代理

<?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">

<!--    组件扫描  108-->
    <context:component-scan base-package="com.powernode.spring6.service"/>
    <!--开启aspectj的自动代理  108 -->
    <!--spring容器在扫描类的时候,查看该类上是否有@Aspect注解,如果有,则给这个类生成代理对象。-->
    <!--
        proxy-target-class="true"  表示强制使用CGLIB动态代理
        proxy-target-class="false" 这是默认值,表示接口使用JDK动态代理,反之使用CGLIB动态代理。
     -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

</beans>

 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。

proxy-target-class="true" 表示采用cglib动态代理。

proxy-target-class="false" 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。

测试程序:

//基于AspectJ的AOP注解式开发  108
    @Test
    public void testBefore(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
    }

基于AspectJ的AOP注解式开发_AOP

4. 切点表达式专题  109

要OrderService类也被增强

package com.powernode.spring6.service;

import org.springframework.stereotype.Service;

/**
 * 基于AspectJ的AOP注解式开发  109
 * 配合研究切点表达式
 **/
@Service("orderService")
public class OrderService { // 目标类

    // 目标方法
    public void generate(){
        System.out.println("系统正在生成订单.....");
        /*if (1 == 1) {
           throw new RuntimeException("运行时异常");
        }*/
    }

}

修改切点表达式如下

@Before("execution(* com.powernode.spring6.service..*(..))") //切点
    public void 增强(){ //通知
        System.out.println("我是一个通知,我是一段增强代码....");
    }

测试

//基于AspectJ的AOP注解式开发  108
    @Test
    public void testBefore(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
        userService.logout();

        //要OrderService类也被增强  109
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
    }

基于AspectJ的AOP注解式开发_基于注解的AOP实现_02

5. 通知类型  110

通知类型包括:

● 前置通知:@Before 目标方法执行之前的通知

● 后置通知:@AfterReturning 目标方法执行之后的通知

● 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。

● 异常通知:@AfterThrowing 发生异常之后执行的通知

● 最终通知:@After 放在finally语句块中的通知

接下来,编写程序来测试这几个通知的执行顺序:

package com.powernode.spring6.service;

import org.springframework.stereotype.Service;

/**
 * 基于AspectJ的AOP注解式开发  109
 * 配合研究切点表达式
 **/
@Service("orderService")
public class OrderService { // 目标类

    // 目标方法
    public void generate(){
        System.out.println("系统正在生成订单.....");
        /*if (1 == 1) {
           throw new RuntimeException("运行时异常");
        }*/
    }

}
package com.powernode.spring6.service;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 基于AspectJ的AOP注解式开发  108
 * 切面
 **/
@Component("logAspect") //这是纳入spring容器管理
@Aspect // 切面类是需要使用@Aspect注解进行标注的。
public class LogAspect { // 切面

    // 切面 = 通知 + 切点
    // 通知就是增强,就是具体的要编写的增强代码
    // 这里通知Advice以方法的形式出现。(因为方法中可以写代码)
    // @Before注解标注的方法就是一个前置通知。
    /*@Before("execution(* com.powernode.spring6.service..*(..))") //切点
    public void 增强(){ //通知
        System.out.println("我是一个通知,我是一段增强代码....");
    }*/
    
    
    // 前置通知    110
    @Before("execution(* com.powernode.spring6.service..*(..))") //切点
    public void beforeAdvice(JoinPoint joinPoint){
        System.out.println("前置通知");
     }

    // 后置通知   110
    @AfterReturning("execution(* com.powernode.spring6.service..*(..))") //切点
    public void afterReturningAdvice(JoinPoint joinPoint){
        System.out.println("后置通知");
    }

    // 环绕通知(环绕是最大的通知,在前置通知之前,在后置通知之后。)  110
    @Around("execution(* com.powernode.spring6.service..*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        // 前面的代码
        System.out.println("前环绕");
        // 执行目标
        joinPoint.proceed(); // 执行目标
        // 后面的代码
        System.out.println("后环绕");
    }

    // 异常通知
    @AfterThrowing("execution(* com.powernode.spring6.service..*(..))")
    public void afterThrowingAdvice(){
        System.out.println("异常通知");
    }

    // 最终通知 (finally语句块中的通知)
    @After("execution(* com.powernode.spring6.service..*(..))")
    public void afterAdvice(){
        System.out.println("最终通知");
    }
}
//测试  108
public class SpringAOPTest {

    //基于AspectJ的AOP注解式开发  108
    @Test
    public void testBefore(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        /*UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
        userService.logout();*/

        //要OrderService类也被增强  109
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
    }
}

基于AspectJ的AOP注解式开发_xml_03

6. 切面的先后顺序  111

我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。

再定义一个切面类,如下:

package com.powernode.spring6.service;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//基于AspectJ的AOP注解式开发
//安全切面   111
@Aspect
@Component
@Order(0) //切面通知的顺序是,数字越小优先级越高  111
public class SecurityAspect {
    //通知+切点
    @Before("execution(* com.powernode.spring6.service..*(..))") //切点
    public void beforeAdvice(){
        System.out.println("前置通知:安全...");
    }
}

基于AspectJ的AOP注解式开发_spring_04

7. 通用切点  112

以上代码缺点是:

第一:切点表达式重复写了多次,没有得到复用。

第二:如果要修改切点表达式,需要修改多处,难维护。

可以这样做:将切点表达式单独的定义出来,在需要的位置引入即可。如下:

//定义通用的切点表达式   112
    @Pointcut("execution(* com.powernode.spring6.service..*(..))")
    public void 通用切点(){
        // 这个方法只是一个标记,方法名随意,方法体中也不需要写任何代码。
    }

案例如下

package com.powernode.spring6.service;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 基于AspectJ的AOP注解式开发  108
 * 切面
 **/
@Component("logAspect") //这是纳入spring容器管理
@Aspect // 切面类是需要使用@Aspect注解进行标注的。
@Order(2)
public class LogAspect { // 切面

    // 切面 = 通知 + 切点
    // 通知就是增强,就是具体的要编写的增强代码
    // 这里通知Advice以方法的形式出现。(因为方法中可以写代码)
    // @Before注解标注的方法就是一个前置通知。
    /*@Before("execution(* com.powernode.spring6.service..*(..))") //切点
    public void 增强(){ //通知
        System.out.println("我是一个通知,我是一段增强代码....");
    }*/

     //定义通用的切点表达式   112
    @Pointcut("execution(* com.powernode.spring6.service..*(..))")
    public void 通用切点(){
        // 这个方法只是一个标记,方法名随意,方法体中也不需要写任何代码。
    }

    // 前置通知    110
    @Before("通用切点()")
    //@Before("execution(* com.powernode.spring6.service..*(..))") //切点
    public void beforeAdvice(JoinPoint joinPoint){
        System.out.println("前置通知");
        // 这个JoinPoint joinPoint,在Spring容器调用这个方法的时候自动传过来.
        // 我们可以直接用。用这个JoinPoint joinPoint干啥?
        // Signature signature = joinPoint.getSignature(); 获取目标方法的签名。
        // 通过方法的签名可以获取到一个方法的具体信息。
        // 获取目标方法的方法名。
        //System.out.println("目标方法的方法名:" + joinPoint.getSignature().getName());
    }

    // 后置通知   110
    @AfterReturning("通用切点()")
    //@AfterReturning("execution(* com.powernode.spring6.service..*(..))") //切点
    public void afterReturningAdvice(JoinPoint joinPoint){
        System.out.println("后置通知");
    }

    // 环绕通知(环绕是最大的通知,在前置通知之前,在后置通知之后。)  110
    @Around("通用切点()")
    //@Around("execution(* com.powernode.spring6.service..*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        // 前面的代码
        System.out.println("前环绕");
        // 执行目标
        joinPoint.proceed(); // 执行目标
        // 后面的代码
        System.out.println("后环绕");
    }

    // 异常通知  110
    @AfterThrowing("通用切点()")
    //@AfterThrowing("execution(* com.powernode.spring6.service..*(..))")
    public void afterThrowingAdvice(){
        System.out.println("异常通知");
    }

    // 最终通知 (finally语句块中的通知)   110
    @After("通用切点()")
    //@After("execution(* com.powernode.spring6.service..*(..))")
    public void afterAdvice(){
        System.out.println("最终通知");
    }
}
package com.powernode.spring6.service;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//基于AspectJ的AOP注解式开发
//安全切面   111
@Aspect
@Component
@Order(0) //切面通知的顺序是,数字越小优先级越高  111
public class SecurityAspect {
    //通知+切点
    //@Before("execution(* com.powernode.spring6.service..*(..))") //切点
    //使用LogAspect切面类中定义的  通用切点() 表达式方法   112
    //需要引入报名 com.powernode.spring6.service.LogAspect
    @Before("com.powernode.spring6.service.LogAspect.通用切点()")
    public void beforeAdvice(){
        System.out.println("前置通知:安全...");
    }
}
//基于AspectJ的AOP注解式开发  108
    @Test
    public void testBefore(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        /*UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.login();
        userService.logout();*/

        //要OrderService类也被增强  109
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
    }

基于AspectJ的AOP注解式开发_AOP_05

8. 小知识点113

这个JoinPoint joinPoint,在Spring容器调用这个方法的时候自动传过来.   113

我们可以直接用。用这个JoinPoint joinPoint干啥?

Signature signature = joinPoint.getSignature(); //获取目标方法的签名。

通过方法的签名可以获取到一个方法的具体信息。

获取目标方法的方法名。例如

System.out.println("目标方法的方法名:" + joinPoint.getSignature().getName());

其实我们的每个通知类型中都可以加入参数JoinPoint joinPoint 它不只局限于环绕通知

9. 全注解开发  114

就是编写一个类,在这个类上面使用大量注解来代替spring的配置文件,spring配置文件消失了,如下

package com.powernode.spring6.service;

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

//全注解开发   114
@Configuration //代表这个类是spring配置文件
@ComponentScan({"com.powernode.spring6.service"})// 组件扫描
@EnableAspectJAutoProxy(proxyTargetClass = true)//开启aspectj的自动代理
public class Spring6Config {
}

测试

//全注解开发  114
    @Test
    public void testNoXml(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();

    }

基于AspectJ的AOP注解式开发_AOP_06

标签:service,..,通知,org,切点,AspectJ,AOP,注解,public
From: https://blog.51cto.com/u_15784725/6458354

相关文章

  • 分析spring事务@Transactional注解在同一个类中的方法之间调用不生效的原因及解决方案
    问题:在Spring管理的项目中,方法A使用了Transactional注解,试图实现事务性。但当同一个class中的方法B调用方法A时,会发现方法A中的异常不再导致回滚,也即事务失效了。当这个方法被同一个类调用的时候,spring无法将这个方法加到事务管理中。我们来看一下生效时候和不生效时候调用堆栈日志......
  • [c/c++/OC]高质量的面试题及答案及注解
    一、选择题C语言:1.声明语句为inta[3][4];下列表达式中与数组元素a[2][1]等价的是(A)。A、*(a[2]+1)B、a[9]C、*(a[1]+2)D、*(*(a+2))+1a[2]<==>*(a+2)是等价的C两个数反过来了,D、1放进去2.请问经过表达式a=5?0:1的运算,变量a的最终值是(C......
  • 注解是干什么的
    注解(Annotation)是一种在Java代码中使用的元数据形式,它提供了对程序的额外信息和说明。注解可以被添加到类、方法、字段以及其他程序元素上,以提供关于这些元素的更多信息。注解在Java开发中具有广泛的应用,它可以用于以下目的:提供元数据信息:注解可以用于提供程序元素的额外......
  • mybatis的注解开发
    1. 基本介绍  130mybatis中也提供了注解式开发⽅式,采⽤注解可以减少Sql映射⽂件的配置。当然,使⽤注解式开发的话,sql语句是写在java程序中的,这种⽅式也会给sql语句的维护带来成本。官⽅是这么说的:使⽤注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂⼀点的语句,Java 注解......
  • JDK 动态代理 和 CGLIB 动态代理 的区别【SpringAOP】
    一、原理区别(版本一)Java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。1、如果目标对象实现了接口,默认......
  • 注解:认识注解
          ......
  • Kotlin中的@JvmOverloads注解的解释
    在自定义一些控件的时候,我经常会直接写入需要的参数,比如:classMaskedCardView(context:Context,attributeSet:AttributeSet?=null,defStyle:Int=com.google.android.material.R.attr.materialCardViewStyle):MaterialCardView(context,attributeSet,defS......
  • validation校验注解
    空检查@Null验证对象是否为null@NotNull验证对象是否不为null,无法查检长度为0的字符串@NotBlank检查约束字符串是不是Null还有被trim的长度是否大于0,只对字符串,且会去掉前后空格.@NotEmpty检查约束元素是否为NULL或者是EMPTY.布尔检查说明......
  • java注解详解及示例
    本文简单介绍java的注解原理与示例。(文章目录)一、基本语法1、声明注解与元注解我们先来看看前面的org.junit.Test注解是如何声明的//声明Test注解@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public@interfaceTest{staticclassNoneextend......
  • SpringAOP
    一、proxy增强1、基于JDKjava自带的代理功能,只能针对接口,目标类与代理类为平级关系publicclassJDKProxy{ interfaceFoo{ voidfoo(); } staticclassTargetimplementsFoo{ publicvoidfoo(){ System.out.println("targetfoo"); } } publicstaticvo......