• Java——基于AspectJ的AOP开发


    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开发需要手动开发代理层,这样工作量会稍大。

  • 相关阅读:
    数据结构和算法学习笔记七:图的搜索
    数据结构和算法学习笔记六:图的相关实现
    Unity常用的3D数学知识
    Unity计时器--简版
    基于前缀树的红点系统
    数据结构和算法学习笔记五:图的基本概念
    C内存管理
    如何解决KEIL中文乱码问题
    C语言结构体变量作为函数形参
    C的结构体
  • 原文地址:https://www.cnblogs.com/yangmingxianshen/p/12520871.html
Copyright © 2020-2023  润新知