2021-04-13

Spring框架

Spring AOP(面向切面编程)是什么?

        面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式。Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。
        AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
        AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
        目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。
        Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。
        AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
        为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,这些专业术语主要包含 Joinpoint、Pointcut、Advice、Target、Weaving、Proxy 和 Aspect,它们的含义如下表所示。

名称

说明

Joinpoint(连接点)

指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。

Pointcut(切入点)

指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。

Advice(通知)

指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。

Target(目标)

指代理的目标对象。

Weaving(植入)

指把增强代码应用到目标上,生成代理对象的过程。

Proxy(代理)

指生成的代理对象。

Aspect(切面)

切入点和通知的结合。

Spring JDK动态代理

JDK 动态代理是通过 JDK 中的 java.lang.reflect.Proxy 类实现的。下面通过具体的案例演示 JDK 动态代理的使用。

1. 创建项目

2. 创建接口 StudentDao

在项目的 src 目录下创建一个名为 com.wangxing.jdkproxydemo1.dao的包,在该包下创建一个 StudentDao接口,编辑后如下所示。

package com.wangxing.jdkproxydemo1.dao;
public interface StudentDao {
    public void add(); // 添加
    public void update(); // 修改
    public void delete(); // 删除
    public void find(); // 查询
}

3. 创建实现类 StudentDaoImpl

com.wangxing.jdkproxydemo1.dao.impl包下创建 CustomerDao 接口的实现类 StudentDaoImpl,并实现该接口中的所有方法,如下所示。

package com.wangxing.jdkproxydemo1.dao.impl;
import com.wangxing.jdkproxydemo1.dao.StudentDao;
public class StudentDaoImpl implements StudentDao {
    @Override
    public void add() {
        System.out.println("添加学生...");
    }
    @Override
    public void update() {
        System.out.println("修改学生...");
    }
    @Override
    public void delete() {
        System.out.println("删除学生...");
    }
    @Override
    public void find() {
        System.out.println("查询学生...");
    }
}

4. 创建切面类 MyAspect

在 src 目录下,创建一个名为com.wangxing.jdkproxydemo1.jdkaspect的包,在该包下创建一个切面类 MyAspect,编辑后如下所示。

package com.wangxing.jdkproxydemo1.jdkaspect;
public class MyAspect {
    public void myBefore() {
        System.out.println("方法执行之前");
    }
    public void myAfter() {
        System.out.println("方法执行之后");
    }
}

上述代码中,在切面中定义了两个增强的方法,分别为 myBefore() 方法和 myAfter() 方法,用于对目标类(CustomerDaoImpl)进行增强。

5. 创建代理类 MyBeanFactory

com.wangxing.jdkproxydemo1.jdkaspect包下创建一个名为 MyBeanFactory 的类,在该类中使用 java.lang.reflect.Proxy 实现 JDK 动态代理,如下所示。

package com.wangxing.jdkproxydemo1.jdkaspect;

import com.wangxing.jdkproxydemo1.dao.StudentDao;
import com.wangxing.jdkproxydemo1.dao.impl.StudentDaoImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyBeanFactory {
    public static StudentDao getBean() {
        // 准备目标类
        final StudentDao studentDao = new StudentDaoImpl();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 使用代理类,进行增强
        return (StudentDao) Proxy.newProxyInstance(
                MyBeanFactory.class.getClassLoader(),
                new Class[] { StudentDao.class }, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
                        myAspect.myBefore(); // 前增强
                        Object obj = method.invoke(studentDao, args);
                        myAspect.myAfter(); // 后增强
                        return obj;
                    }
             });
    }
}

上述代码中,定义了一个静态的 getBean() 方法,这里模拟 Spring 框架的 IoC 思想,通过调用 getBean() 方法创建实例,第 14 行代码创建了 customerDao 实例。
第 16 行代码创建的切面类实例用于调用切面类中相应的方法;第 18~26 行就是使用代理类对创建的实例 customerDao 中的方法进行增强的代码,其中 Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法。
在目标类方法执行的前后,分别执行切面类中的 myBefore() 方法和 myAfter() 方法。

6. 创建测试

@Test
public void test1(){
    //得到代理对象
    StudentDao studentDao=MyBeanFactory.getBean();
    //得到普通对象
    //StudentDao studentDao=new StudentDaoImpl();
    studentDao.add();
}

上述代码中,在调用 getBean() 方法时,获取的是 StudentDao  类的代理对象,然后调用了该对象中的方法。

7. 运行项目并查看结果

Spring CGLlB动态代理

        通过《Spring JDK动态代理》教程的学习可以知道,JDK 动态代理使用起来非常简单,但是它也有一定的局限性,这是因为 JDK 动态代理必须要实现一个或多个接口,如果不希望实现接口,则可以使用 CGLIB 代理。
        CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。因此 CGLIB 要依赖于 ASM 的包,解压 Spring 的核心包 spring-core-5.1.5.RELEASE.jar,文件目录如图 1 所示。

        在图 1 中可以看出,解压的核心包中包含 cglib 和 asm,也就是说 spring-core-5.1.5.RELEASE.jar 版本的核心包已经集成了 CGLIB 所需要的包,所以在开发中不需要另外导入 ASM 的 JAR 包了。

下面通过案例演示实现 CGLIB 的代理过程。

1. 创建项目,导入Spring依赖

2.在com.wangxing.cglibdemo1.dao包下创建接口 UserDao,在类中定义增、删、改、查方法,并在每个方法编写输出语句,如下所示。

package com.wangxing.cglibdemo1.dao;
public interface UserDao {
    public void add(); // 添加
    public void update(); // 修改
    public void delete(); // 删除
    public void find(); // 查询
}

3.在com.wangxing.cglibdemo1.dao.impl包下创建接口 UserDao的实现类

package com.wangxing.cglibdemo1.dao.impl;
import com.wangxing.cglibdemo1.dao.UserDao;
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("添加User...");
    }
    @Override
    public void update() {
        System.out.println("修改User...");
    }
    @Override
    public void delete() {
        System.out.println("删除User...");
    }
    @Override
    public void find() {
        System.out.println("查询User...");
    }
}

4.在com.wangxing.cglibdemo1.dao.cglibdemo创建切面类MyAspect

package com.wangxing.cglibdemo1.dao.cglibdemo;
public class MyAspect {
    public void myBefore() {
        System.out.println("方法执行之前");
    }
    public void myAfter() {
        System.out.println("方法执行之后");
    }
}

5.创建代理类 MyBeanFactory

package com.wangxing.cglibdemo1.dao.cglibdemo;
import com.wangxing.cglibdemo1.dao.UserDao;
import com.wangxing.cglibdemo1.dao.impl.UserDaoImpl;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyBeanFactory {
    public static UserDao getBean() {
        // 准备目标类
        final UserDao userDao = new UserDaoImpl();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
        Enhancer enhancer = new Enhancer();
        // 确定需要增强的类
        enhancer.setSuperclass(userDao.getClass());
        // 添加回调函数
        enhancer.setCallback(new MethodInterceptor() {
            // intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
            @Override
            public Object intercept(Object proxy, Method method, Object[] args,
                                    MethodProxy methodProxy) throws Throwable {
                myAspect.myBefore(); // 前增强
                Object obj = method.invoke(userDao, args); // 目标方法执行
                myAspect.myAfter(); // 后增强
                return obj;
            }
        });
        // 创建代理类
        UserDao userDaoProxy = (UserDao) enhancer.create();
        return userDaoProxy;
    }
}

        上述代码中,应用了 CGLIB 的核心类 Enhancer。在第 19 行代码调用了 Enhancer 类的 setSuperclass() 方法,确定目标对象。
        第 21 行代码调用 setCallback() 方法添加回调函数;第 24 行代码的 intercept() 方法相当于 JDK 动态代理方式中的 invoke() 方法,该方法会在目标方法执行的前后,对切面类中的方法进行增强;第 33~34 行代码调用 Enhancer 类的 create() 方法创建代理类,最后将代理类返回。

6创建测试

@Test
public void test1() {
    UserDao userDao=MyBeanFactory.getBean();
    userDao.add();
}

上述代码中,调用 getBean() 方法时,依然获取的是 UserDao的代理对象,然后调用该对象的方法。

        从图 2 的输出结果中可以看出,在调用目标类的方法前后,也成功调用了增强的代码,由此说明,使用 CGLIB 代理的方式同样实现了手动代理。

Spring通知类型及使用ProxyFactoryBean创建AOP代理

        在《Spring JDK动态代理》和《Spring CGLlB动态代理》中,讲解了 AOP 手动代理的两种方式,下面通过讲解 Spring 的通知介绍 Spring 是如何创建 AOP 代理的。

Spring 通知类型

        通过前面的学习可以知道,通知(Advice)其实就是对目标切入点进行增强的内容,Spring AOP 为通知(Advice)提供了 org.aopalliance.aop.Advice 接口。
Spring 通知按照在目标类方法的连接点位置,可以分为以下五种类型,如表 1 所示。

表 1 Spring 通知的 5 种类型

名称

说明

org.springframework.aop.MethodBeforeAdvice(前置通知)

在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。

org.springframework.aop.AfterReturningAdvice(后置通知)

在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。

org.aopalliance.intercept.MethodInterceptor(环绕通知)

在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。

org.springframework.aop.ThrowsAdvice(异常通知)

在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。

org.springframework.aop.IntroductionInterceptor(引介通知)

在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。

Spring AOPProxyFactoryBean类实现代理

        Spring 创建一个 AOP 代理的基本方法是使用

org.springframework.aop.framework.ProxyFactoryBean,这个类对应的切入点和通知提供了完整的控制能力,并可以生成指定的内容。
ProxyFactoryBean 类中的常用可配置属性如表 2 所示。

表 2 ProxyFactoryBean 的常用属性

属性名称

描  述

target

代理的目标对象

proxyInterfaces

代理要实现的接口,如果有多个接口,则可以使用以下格式赋值:
<list>
    <value ></value>
    ...
</list>

proxyTargetClass

是否对类代理而不是接口,设置为 true 时,使用 CGLIB 代理

interceptorNames

需要植入目标的 Advice

singleton

返回的代理是否为单例,默认为 true(返回单实例)

optimize

当设置为 true 时,强制使用 CGLIB

在 Spring 通知中,环绕通知是一个非常典型的应用。

下面通过环绕通知的案例演示 Spring 创建 AOP 代理的过程。

1. 创建项目,导入依赖,完善项目结构

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>

2. 创建业务接口以及实现类

package com.wangxing.proxyfactorybeandemo.service;
public interface PersonService {
    public  void  insertPerson();
    public  void  updatePerson();
    public  void  deletePerson();
    public  void  selectPerson();
}
package com.wangxing.proxyfactorybeandemo.service.impl;
import com.wangxing.proxyfactorybeandemo.service.PersonService;
public class PersonServiceImpl implements PersonService {
    @Override
    public void insertPerson() {
        System.out.println("添加Person。。。。。。");
    }
    @Override
    public void updatePerson() {
        System.out.println("修改Person。。。。。。");
    }
    @Override
    public void deletePerson() {
        System.out.println("删除Person。。。。。。");
    }
    @Override
    public void selectPerson() {
        System.out.println("查询Person。。。。。。");
    }
}

3. 创建切面类 MyAspect

package com.wangxing.proxyfactorybeandemo.myaspect;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("方法执行之前");
        // 执行目标方法
        Object obj = invocation.proceed();
        System.out.println("方法执行之后");
        return obj;
    }
}

上述代码中,MyAspect 类实现了org.aopalliance.intercept.MethodInterceptor接口,并实现了接口的 invoke() 方法。MethodInterceptor 接口是 Spring AOP 的 JAR 包提供的,而 invoke() 方法用于确定目标方法 MethodInvocation,并告诉 Spring 要在目标方法前后执行哪些方法,这里为了演示效果在目标方法前后分别向控制台输出了相应语句。

3. 创建 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--创建目标对象-->
    <bean id="personService" class="com.wangxing.proxyfactorybeandemo.service.impl.PersonServiceImpl"></bean>
    <!-- 通知 advice -->
    <bean id="myAspect" class="com.wangxing.proxyfactorybeandemo.myaspect.MyAspect" />
    <!-- org.springframework.aop.framework.ProxyFactoryBean -->
    <bean  id="personServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--代理实现的接口 -->
        <property name="proxyInterfaces" value="com.wangxing.proxyfactorybeandemo.service.PersonService"></property>
        <!--代理的目标对象 -->
        <property name="target" ref="personService"></property>
        <!--用通知增强目标 -->
        <property name="interceptorNames" value="myAspect"></property>
        <!-- 如何生成代理,true:使用cglib; false :使用jdk动态代理 -->
        <property name="proxyTargetClass" value="true"></property>
    </bean>
</beans>

        上述代码中,首先配置目标类和通知,然后使用 ProxyFactoryBean 类生成代理对象;第 14 行代码配置了代理实现的接口;第 16 行代码配置了代理的目标对象;第 18 行代码配置了需要植入目标的通知;当第 20 行代码中的 value 属性值为 true 时,表示使用 CGLIB 代理,属性值为 false 时,表示使用 JDK 动态代理。

4.创建测试

@Test
public void test1() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    PersonService personService = (PersonService) applicationContext.getBean("personServiceProxy");
    personService.insertPerson();
}

5. 运行项目并查看结果

Spring使用AspectJ开发AOP:基于XML和基于Annotation

        AspectJ 是一个基于 Java 语言的 AOP 框架,它扩展了 Java 语言。Spring 2.0 以后,新增了对 AspectJ 方式的支持,新版本的 Spring 框架,建议使用 AspectJ 方式开发 AOP。
        使用 AspectJ 开发 AOP 通常有两种方式:

        1. 基于 XML 的声明式。

        2. 基于 Annotation 的声明式。

接下来将对这两种 AOP 的开发方式进行讲解。

基于XML的声明式

        基于 XML 的声明式是指通过 Spring 配置文件的方式定义切面、切入点及声明通知,而所有的切面和通知都必须定义在 <aop:config> 元素中。
下面通过案例演示 Spring 中如何使用基于 XML 的声明式实现 AOP 的开发。

1. 创建项目,导入依赖包,完善结构

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.7.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>

2. 创建业务接口以及实现类

package com.wangxing.proxyfactorybeandemo.service;
public interface PersonService {
    public  void  insertPerson();
    public  void  updatePerson();
    public  void  deletePerson();
    public  void  selectPerson();
}
package com.wangxing.proxyfactorybeandemo.service.impl;
import com.wangxing.proxyfactorybeandemo.service.PersonService;
public class PersonServiceImpl implements PersonService {
    @Override
    public void insertPerson() {
        System.out.println("添加Person。。。。。。");
    }
    @Override
    public void updatePerson() {
        System.out.println("修改Person。。。。。。");
    }
    @Override
    public void deletePerson() {
        System.out.println("删除Person。。。。。。");
    }
    @Override
    public void selectPerson() {
        System.out.println("查询Person。。。。。。");
    }
}

3. 创建切面类 MyAspect

package com.mengma.aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
//切面类
public class MyAspect {
    // 前置通知
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知,目标:");
        System.out.print(joinPoint.getTarget() + "方法名称:");
        System.out.println(joinPoint.getSignature().getName());
    }
    // 后置通知
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    // 环绕通知
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
            throws Throwable {
        System.out.println("环绕开始"); // 开始
        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
        System.out.println("环绕结束"); // 结束
        return obj;
    }
    // 异常通知
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知" + "出错了" + e.getMessage());
    }
    // 最终通知
    public void myAfter() {
        System.out.println("最终通知");
    }
}

        上述代码中,分别定义了几种不同的通知类型方法,在这些方法中,通过 JoinPoint 参数可以获得目标对象的类名、目标方法名和目标方法参数等。需要注意的是,环绕通知必须接收一个类型为 ProceedingJoinPoint 的参数,返回值必须是 Object 类型,且必须抛出异常。异常通知中可以传入 Throwable 类型的参数,用于输出异常信息。

3. 创建 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: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/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--目标类 -->
    <bean id="personService" class="com.wangxing.aopdemo1.service.impl.PersonServiceImpl" />
    <!--切面类 -->
    <bean id="myAspect" class="com.wangxing.aopdemo1.aspect.MyAspect"></bean>
    <!--AOP 编程 -->
    <aop:config>
        <!--配置切面-->
        <aop:aspect ref="myAspect">
            <!--创建切入点-->
            <aop:pointcut id="mypointcut1" expression="execution ( * com.wangxing.aopdemo1.service.impl.PersonServiceImpl.insertPerson(..))"/>
            <aop:before method="myBefore" pointcut-ref="mypointcut1"></aop:before>
        </aop:aspect>
        <!--配置切面-->
        <aop:aspect ref="myAspect">
            <!--创建切入点-->
            <aop:pointcut id="mypointcut2" expression="execution ( * com.wangxing.aopdemo1.service.impl.PersonServiceImpl.updatePerson(..))"/>
            <aop:after-returning method="myAfterReturning"pointcut-ref="mypointcut2" returning="joinPoint" />
        </aop:aspect>
        <!--配置切面-->
        <aop:aspect ref="myAspect">
            <!--创建切入点-->
            <aop:pointcut id="mypointcut3" expression="execution ( * com.wangxing.aopdemo1.service.impl.PersonServiceImpl.deletePerson(..))"/>
            <aop:around method="myAround" pointcut-ref="mypointcut3" />
        </aop:aspect>
        <!--配置切面-->
        <aop:aspect ref="myAspect">
            <!--创建切入点-->
            <aop:pointcut id="mypointcut4" expression="execution ( * com.wangxing.aopdemo1.service.impl.PersonServiceImpl.selectPerson(..))"/>
            <!--作用通知-->
            <!---->
            <!--抛出通知:用于处理程序发生异常,可以接收当前方法产生的异常 -->
            <!-- *注意:如果程序没有异常,则不会执行增强 -->
            <!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
            <aop:after-throwing method="myAfterThrowing"pointcut-ref="mypointcut4"  throwing="e" />
            <!--最终通知:无论程序发生任何事情,都将执行 -->
            <!--<aop:after method="myAfter" pointcut-ref="myPointCut" />-->
        </aop:aspect>
    </aop:config>
</beans>

4. 创建测试

@Test
public void test1() {
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    PersonService personService=(PersonService)context.getBean("personService");
    personService.insertPerson();
    personService.updatePerson();
    personService.deletePerson();
    //personService.selectPerson();
}

5. 运行项目并查看结果

为了更好地演示异常通知,接下来在 PersonServiceImpl 类的 selectPerson() 方法中添加一行会抛出异常的代码,如“int i=1/0;”,重新运行测试方法,可以看到异常通知执行了,此时控制台的输出结果如图 2 所示。


图 2  运行结果
从图 1 和图 2 的输出结果中可以看出,基于 XML 声明式的 AOP 开发已经成功实现。

基于 Annotation 的声明式

        在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关的配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。
        为此,AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理。
关于 Annotation 注解的介绍如表 1 所示。

表 1 Annotation 注解介绍

名称

说明

@Aspect

用于定义一个切面。

@Before

用于定义前置通知,相当于 BeforeAdvice。

@AfterReturning

用于定义后置通知,相当于 AfterReturningAdvice。

@Around

用于定义环绕通知,相当于MethodInterceptor。

@AfterThrowing

用于定义抛出通知,相当于ThrowAdvice。

@After

用于定义最终final通知,不管是否异常,该通知都会执行。

@DeclareParents

用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。

下面使用注解的方式重新实现《基于XML的声明式》部分的功能。

1. 创建切面类 MyAspect

在 src 目录下创建一个名为 com.mengma.aspectj.annotation 的包,在该包下创建一个切面类 MyAspect,如下所示。

package com.mengma.aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//切面类
@Aspect
@Component
public class MyAspect {
    // 用于取代:<aop:pointcut
    // expression="execution(*com.mengma.dao..*.*(..))" id="myPointCut"/>
    // 要求:方法必须是private,没有值,名称自定义,没有参数
    @Pointcut("execution(*com.mengma.dao..*.*(..))")
    private void myPointCut() {
    }
    // 前置通知
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知,目标:");
        System.out.print(joinPoint.getTarget() + "方法名称:");
        System.out.println(joinPoint.getSignature().getName());
    }
    // 后置通知
    @AfterReturning(value = "myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    // 环绕通知
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
            throws Throwable {
        System.out.println("环绕开始"); // 开始
        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
        System.out.println("环绕结束"); // 结束
        return obj;
    }
    // 异常通知
    @AfterThrowing(value = "myPointCut()", throwing = "e")
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知" + "出错了" + e.getMessage());
    }
    // 最终通知
    @After("myPointCut()")
    public void myAfter() {
        System.out.println("最终通知");
    }
}

        上述代码中,第 13 行 @Aspect 注解用于声明这是一个切面类,该类作为组件使用,所以要添加 @Component 注解才能生效。第 19 行中 @Poincut 注解用于配置切入点,取代 XML 文件中配置切入点的代码。
        在每个通知相应的方法上都添加了注解声明,并且将切入点方法名“myPointCut”作为参数传递给要执行的方法,如需其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。

2. 为目标类添加注解

在 com.mengma.dao.CustomerDaoImpl 目标类中添加注解 @Repository("customerDao")。

3. 创建Spring配置文件

在 com.mengma.aspectj.annotation 包下创建 applicationContext.xml 配置文件,如下所示。

<?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:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    <!--扫描含com.mengma包下的所有注解-->
    <context:component-scan base-package="com.mengma"/>
    <!-- 使切面开启自动代理 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

        上述代码中,首先导入了 AOP 命名空间及其配套的约束,使切面类中的 @AspectJ 注解能够正常工作;第 13 行代码添加了扫描包,使注解生效。需要注意的是,这里还包括目标类 com.mengma.dao.CustomerDaoImpl 的注解,所以 base-package 的值为 com.mengma;第 15 行代码的作用是切面开启自动代理。

4. 创建测试类

在 com.mengma.aspectj.annotation 包下创建一个名为 AnnotationTest 的测试类,如下所示。

package com.mengma.aspectj.annotation;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mengma.dao.CustomerDao;
public class AnnotationTest {
    @Test
    public void test() {
        String xmlPath = "com/mengma/aspectj/xml/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 从spring容器获取实例
        CustomerDao customerDao = (CustomerDao) applicationContext
                .getBean("customerDao");
        // 执行方法
        customerDao.add();
    }
}

5. 运行项目并查看结果

使用 JUnit 测试运行 test() 方法,运行成功后,控制台的输出结果如图 3 所示。


图 3  运行结果

删除 add() 方法中的“int i=1/0;”,重新运行 test() 方法,此时控制台的输出结果如图 4 所示。


图 4  运行结果
从图 3 和图 4 的输出结果中可以看出,已成功使用 Annotation 的方式实现了 AOP 开发。与其他方式相比,基于 Annotation 方式实现 AOP 的效果是最方便的方式,所以实际开发中推荐使用注解的方式。

猜你喜欢

转载自blog.csdn.net/weixin_53123681/article/details/115675614