Java——基于AspectJ的AOP开发

Wesley13
• 阅读 817

1.AspectJ简介

AspectJ是一个基于Java语言的AOP框架。
Spring2.0以后新增了对AdpectJ切点表达式的支持。
@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。
新版本Spring框架,建议使用AspectJ方式来开发AOP。
使用AspectJ需要导入Spring AOP和AspectJ相关jar包。

2.语法简介

(1)@AspectJ提供不同的通知类型

@Before 前置通知,相当于BeforeAdvice
@AfterReturning 后置通知,相当于AfterReturningAdvice
@Around 环绕通知,相当于MethodInterceptor
@AfterThrowing异常抛出通知,相当于ThrowAdvice
@After 最终final通知,不管是否异常,该通知都会执行
@DeclareParents 引介通知,相当于IntroductionInterceptor

(2)在通知中通过value属性定义切点

通过execution函数,可以定义切点的方法切入。
语法:
  execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
例如:
  匹配所有类public方法 execution(public * *(..)) 第一个*任意方法返回值,第二个*任意参数 ..表示任意参数
  匹配指定包下所有类方法(不包含子包) execution(* com.ikidana.dao.*(..)) 访问修饰符可以没有,第一个*返回值类型 第二个*方法名称 ..表示任意参数
  匹配指定包下所有类方法(包含子包) 第一个..*表示包、子孙包下所有类
  匹配指定类所有方法 execution(* com.ikidana.service.UserService.*(..))
  匹配实现特定接口所有类方法 execution(* com.imooc.dao.GenericDAO+.*(..))
  匹配所有save开头的方法 execution(* save*(..))

3.简单案例

导入依赖包:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.1.12</version>
</dependency>
<!--引入Spring的基本开发包-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>4.2.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.2.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-beans</artifactId>
  <version>4.2.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-expression</artifactId>
  <version>4.2.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>aopalliance</groupId>
  <artifactId>aopalliance</artifactId>
  <version>1.0</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>4.2.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.9</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.2.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>4.2.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>compile</scope>
</dependency>

创建XML配置文件,开启自动代理:

<aop:aspectj-autoproxy/>

1)前置通知

a.创建一个实例类,并创建许多方法,现在我需要增强这个类中的方法

public class ProductDAO {
    public void save(){
        System.out.println("ProductDAO save");
    }
    public void delete(){
        System.out.println("ProductDAO delete");
    }
    public void update(){
        System.out.println("ProductDAO update");
    }
    public void find(){
        System.out.println("ProductDAO find");
    }
}

b.添加一个切面

@Aspect  //代表一个切面
public class MyAspectAnno {
    @Before(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.*(..))")  //ProductDao所有方法
    public void before(){
        System.out.println("前置通知");
    }
}

c.在XML中声明注册

<!--目标类,属性注入-->
<bean id="productDao" class="com.imooc.aspectJ.demo1.ProductDAO"/>
<!--定义切面,不需要引用,所以不需要方法-->
<bean class="com.imooc.aspectJ.demo1.MyAspectAnno"/>

d.属性注入、目标注入、通知测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {
    @Resource(name="productDao")  //目标注入
    private ProductDAO productDAO;

    @Test
    public void demo1(){
        productDAO.save();
        productDAO.delete();
        productDAO.update();
        productDAO.find();
    }
} 

e.测试结果

  前置通知
  ProductDAO save
  前置通知
  ProductDAO delete
  前置通知
  ProductDAO update
  前置通知
  ProductDAO find

我们可以发现,ProductDAO类下面的所有方法,都添加了前置通知。

如果我们这样定义切面:
@Before(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.save(..))")
那么只会在save前面添加前置通知

可以在方法中传入JoinPoint对象,用来获得切点信息。

@Before(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.*(..))")  //ProductDao所有方法
public void before(JoinPoint joinPoint){
    System.out.println("前置通知"  + joinPoint) ;
}

切点信息类似如下:
execution(void com.imooc.aspectJ.demo1.ProductDAO.save())

2)后置通知

添加一个切面:

@AfterReturning(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.update(..))",returning = "ret")
public void afterReturing(Object ret){
    System.out.println("后置通知" + ret);
}

结果:
ProductDAO save
ProductDAO delete
ProductDAO update
后置通知  ProductDAO update 返回值
ProductDAO find

通过returning属性,可以定义方法返回值。

3)环绕通知

around方法的返回值就是目标代理方法执行的返回值。
可以通过ProceedingJoinPoint可以调用拦截目标方法执行。
a.添加一个环绕通知的切面

public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("环绕前通知");
    Object obj = joinPoint.proceed();  //执行目标方法,如果不调用这句话,那么目标方法将不会执行
    System.out.println("环绕后通知");
    return obj;
}
//结果:
ProductDAO save
环绕前通知
ProductDAO delete
环绕后通知
ProductDAO update
ProductDAO find

4)异常抛出通知

通过设置throwing属性,可以设置发生异常对象参数。

@AfterThrowing(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.find(..))")
public void afterThrowing(){
    System.out.println("异常抛出通知" );
}

当然我们还可以打印异常:

@AfterThrowing(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.find(..))",throwing = "e")
public void afterThrowing(Throwable e){
    System.out.println("异常抛出通知" + " " + e);
}

5)最终通知

无论是否出现异常,最终通知总是会被执行的。

@After(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.find(..))")
public void after(){
    System.out.println("最终通知");
}

4.切点命中

通过@Pointcut为切点命名。
在每个通知内定义切点,会造成工作量大,不易维护,对于重复的切点,可以使用@Pointcut进行定义。

切点方法:private void 无参方法,方法名为切点名。
当通知多个切点时,可以使用||进行连接。

大概意思就是,如果同一个切点value被很多地方引用,改起来就不太方便。

@Pointcut(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.save(..))")
private void myPointcut1(){}

@AfterReturning(value = "myPointcut1()")
public void afterReturing(){
    System.out.println("后置通知");
}

相当于创建了别名,修改了别名,就相当于修改了所有的引用。

5.基于AspectJ的XML方式的AOP开发

1)前置通知

//创建接口类和实例类
public interface CustomerDao {
    public void save();
    public void update();
    public void delete();
    public void find();
}

public class CustomerDaoImpl implements CustomerDao {
    public void save() {
        System.out.println("c-save");
    }

    public void update() {
        System.out.println("c-update");
    }

    public void delete() {
        System.out.println("c-delete");
    }

    public void find() {
        System.out.println("c-find");
    }
}

//创建通知
public class MyAspectXml {
    //前置通知
    public void before(){
        System.out.println("XML方式的前置通知");
    }
}

//配置增强
<!--XML配置的方式完成AOP开发-->
<!--配置目标类-->
<bean id="customerDao" class="com.imooc.aspectJ.demo2.CustomerDaoImpl"/>
<!--配置切面类-->
<bean id="myAspectXml" class="com.imooc.aspectJ.demo2.MyAspectXml"/>
<!--AOP相关配置-->
<aop:config>
    <!--定义切入点:那些类的那些方法需要应用增强-->
    <aop:pointcut id="pointcut1" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.save(..))"/>
    <!--配置AOP切面:使用那些增强-->
    <aop:aspect ref="myAspectXml">
        <!--配置前置增强 pointcut-ref哪个切点增强 method使用那个增强方法 aop:before增强类型-->
        <aop:before method="before" pointcut-ref="pointcut1"/>
    </aop:aspect>
</aop:config>

//测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = "classpath:applicationContext2.xml")
public class SpringDemo2 {
    @Resource(name = "customerDao")
    private CustomerDao customerDao;

    @Test
    public void demo1(){
        customerDao.save();
        customerDao.delete();
        customerDao.find();
        customerDao.update();
    }
}

//结果
XML方式的前置通知
c-save
c-delete
c-find
c-update

2)其他通知

public class MyAspectXml {
    //前置通知
    public void before(){  //一样可以打印JoinPoint连接点
        System.out.println("XML方式的前置通知");
    }

    //后置通知
    public void afterReturing(){
        System.out.println("XML方式的后置通知");
    }

    //环绕通知
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前");
        Object obj = joinPoint.proceed(); //执行目标方法
        System.out.println("环绕后");
        return obj;
    }
}

<aop:config>
    <!--定义切入点:那些类的那些方法需要应用增强-->
    <aop:pointcut id="pointcut1" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.save(..))"/>
    <aop:pointcut id="pointcut2" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.find(..))"/>
    <aop:pointcut id="pointcut3" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.delete(..))"/>

    <!--配置AOP切面:使用那些增强-->
    <aop:aspect ref="myAspectXml">
        <!--配置前置增强 pointcut-ref哪个切点增强 method使用那个增强方法 aop:before增强类型-->
        <aop:before method="before" pointcut-ref="pointcut1"/>
        <!--配置后置通知-->
        <aop:after method="afterReturing" pointcut-ref="pointcut2"/>
        <!--环绕通知-->
        <aop:around method="around" pointcut-ref="pointcut3"/>
    </aop:aspect>
</aop:config>

在现在企业中,spring在进行AOP开发的时候,都不会使用传统方式,都会基于AspectJ的AOP开发。
传统AOP开发需要手动开发代理层,这样工作量会稍大。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
AOP系列
AOP(AspectOrientProgramming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以SpringAOP为代表。本文
Easter79 Easter79
3年前
Spring的AOP逐层深入——采用注解完成AOP(七)
上篇博文AOP基本原理6我们介绍了AOP的基本原理,以及5种通知的类型,AOP的两种配置方式:XML配置和Aspectj注解方式。    这篇我们使用注解方式来实现一个AOP,我们先看一下项目的目录。     !(https://static.oschina.net/uploads/img/201801/13190133_UNQ
Stella981 Stella981
3年前
Spring Aspect Oriented Programming
  本文是一篇SpringAOP的基础知识分析文章,其中不牵扯源码分析,只包含AOP中重要概念的讲解,分析,以及SpringAOP的用法。    Spring从2.0版本引入了更加简单却强大的基于xml和AspectJ注解的面向切面的编程方式。在深入了解如何用Spring进行面向切面的编程前,我们先了解AOP中的几个重要的基本概念,这几个概念
Easter79 Easter79
3年前
Spring中的AOP(三)——基于Annotation的配置方式(一)
    AspectJ允许使用注解用于定义切面、切入点和增强处理,而Spring框架则可以识别并根据这些注解来生成AOP代理。Spring只是使用了和AspectJ5一样的注解,但并没有使用AspectJ的编译器或者织入器,底层依然使用SpringAOP来实现,依然是在运行时动态生成AOP代理,因此不需要增加额外的编译,也不需要AspectJ的织入器支持。
Stella981 Stella981
3年前
Spring 2.0 的AOP介绍及其通知类型
Spring2.0的AOP在Spring2.0中最激动人心的增强之一是关于SpringAOP,它变得更加便于使用而且更加强大,主要是通过复杂而成熟的AspectJ语言的支持功能来实现,而同时保留纯的基于代理的Java运行时。Spring2.0的AOP提供给我们一种新的思考程序结构的方法,能够解决很多纯OOP无法解决的问题——让我们能够在
Stella981 Stella981
3年前
AspectJ AOP学习基础
一、切入点表达式  1、execution:匹配方法的执行    格式:execution(修饰符返回值类型包.类.方法(参数)throw异常)      1.1修饰符,表示方法的修饰符,一般省略。      1.2返回类型String表示返回String;void表示没有返回值;\表示返回任意类型,包括无返回值。      
Easter79 Easter79
3年前
Springboot加入拦截器(待完善)
1、在sdk中加入拦截器主类packagecom.self.activity.sdk.aop;importjava.io.IOException;importjava.sql.SQLException;importjava.util.List;importorg.aspectj.lang.ProceedingJoinPoin