JAVA面试题分享五百三十三:Spring 框架实现 AOP 的 3 种方法

目录

1、Spring对AOP的支持

2、使用Spring实现AOP

2.1、方式一:通过Spring API实现AOP(早期)

2.2、方式二:通过自定义类实现AOP(推荐)

2.3、方式三:通过注解实现AOP(主流)

附:aop:aspectj-autoproxy(开启注解支持)说明


1、Spring对AOP的支持

Spring的底层已经使用动态代理帮助我们实现了对AOP的支持。

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标对象,这种关系可由IOC容器的依赖注入提供。

Spring创建动态代理的规则为:

(1)默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建动态代理了。

(2)当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB。

2、使用Spring实现AOP

需要先在父项目的pom.xml文件中导入AOP织入依赖包!

pom.xml文件:

<!-- AOP织入依赖 -->
<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
</dependency>

2.1、方式一:通过Spring API实现AOP(早期)

(1)在父项目中新建一个模块(Module),并在其中创建一个普通的Maven子项目,项目名称为spring-09-AOP

(2)填写子项目名称和Maven项目GAV,点击Finish按钮完成子项目创建,等待Maven依赖包导入完毕。

(3)在子项目的java目录下新建一个com.atangbiji.service包,并在该包下新建一个UserService接口及其接口实现类UserServiceImpl

UserService.java文件:

package com.atangbiji.service;
//抽象角色:增删改查业务接口
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}

UserServiceImpl.java文件:

package com.atangbiji.service;
//真实角色:去实现增删改查接口
public class UserServiceImpl implements UserService {

    public void add() {
        System.out.println("增加了一个用户!");
    }

    public void delete() {
        System.out.println("删除了一个用户!");
    }

    public void update() {
        System.out.println("更新了一个用户!");
    }

    public void select() {
        System.out.println("查询了一个用户!");
    }
}

(4)在子项目的java目录下新建一个com.atangbiji.log包,并在该包下新建两个增强类:一个前置增强(Log类);一个后置增强(AfterLog类)。

Log.java文件:

package com.atangbiji.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;
//日志类(即:切面)
//通知类型:前置通知
public class Log implements MethodBeforeAdvice {
    // 输入参数:
    //(1)method:要执行的目标对象的方法
    //(2)args:被调用方法的参数
    //(3)target:目标对象
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "类的" + method.getName() + "方法被执行了");
    }
}

AfterLog.java文件:

package com.atangbiji.log;

import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

//日志类(即:切面)
//通知类型:后置通知
public class AfterLog implements AfterReturningAdvice {
    // 输入参数:
    //(1)returnValue:返回值
    //(2)method:要执行的目标对象的方法
    //(3)args:被调用方法的参数
    //(4)target:目标对象
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + target.getClass().getName() + "类的" + method.getName() + "方法,返回值为:" + returnValue);
    }
}

(5)在子项目的resource目录下新建一个applicationContext.xml配置文件,用于注册bean和配置AOP

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--bean就是那些组成我们应用程序的Java对象,由Spring中的IOC容器创建、组装和管理-->
    <!--(1)id:bean的唯一标识符。(相当于之前new的对象名)-->
    <!--(2)class:bean所对应对象的全限定类名。(全限定名=包名+类名)-->

    <!--方式一:通过Spring API实现AOP-->
    <!--注册bean(切面及通知类型)-->
    <bean id="userServiceImpl" class="com.atangbiji.service.UserServiceImpl"/>
    <bean id="log" class="com.atangbiji.log.Log"/>
    <bean id="afterLog" class="com.atangbiji.log.AfterLog"/>

    <!--配置AOP-->
    <aop:config>
        <!--切入点(通过expression:表达式匹配要切入的位置)-->
        <aop:pointcut id="pointCut" expression="execution(* com.atangbiji.service.UserServiceImpl.*(..))"/>

        <!--通知(把log类和afterLog类从pointCut切入点织入)-->
        <!--(1)advice-ref:通知的引用;(2)pointcut-ref:切入点的引用-->
        <aop:advisor advice-ref="log" pointcut-ref="pointCut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/>
    </aop:config>
</beans>

注:注意在xml配置文件的头部导入AOP的约束。

xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
 http://www.springframework.org/schema/aop/spring-aop.xsd">

附:expression(表达式)的语法格式为:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)  throws-pattern?)

其中:(以问号结束的部分都是可以省略

  • modifiers-pattern表示:方法的访问类型,如public等;

  • ret-type-pattern表示:方法的返回值类型,如String表示返回类型是String*表示所有的返回类型;

  • declaring-type-pattern表示:方法的声明类,如com.atangbiji..*表示com.atangbiji包及其子包下面的所有类型;

  • name-pattern表示:方法的名称,如add*表示所有以add开头的方法名;

  • param-pattern表示:方法的参数类型name-pattern(param-pattern)一起表示方法及对应的参数类型,如add()表示不带参数的add方法,add(*)表示带一个任意类型的参数的add方法,add(*,String)则表示带两个参数,且第二个参数是String类型的add方法;

  • throws-pattern表示:异常类型

(6)在子项目的src/test/java目录下,新建一个MyTest测试类。

MyTest.java文件:

import com.atangbiji.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public void test(){
        //解析applicationContext.xml文件,IOC容器生成并管理相应的Bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过Spring的上下文对象,从IOC容器中获取Bean对象
        //注:由于动态代理代理的是接口,因此需要将UserServiceImpl类型强制转换为UserService类型
        UserService userServiceImpl = (UserService)context.getBean("userServiceImpl");
        //调用Bean对象的方法
        userServiceImpl.add();
        userServiceImpl.delete();
        userServiceImpl.update();
        userServiceImpl.select();
    }
}

运行测试程序test,切面织入成功。执行结果如下图所示:

注:由于动态代理代理的是接口(抽象角色),因此需要将UserServiceImpl类型强制转换为UserService类型。

2.2、方式二:通过自定义类实现AOP(推荐)

(1)保持spring-09-AOP子项目com.atangbiji.service包下的UserService接口及其接口实现类UserServiceImpl不变。

注:UserService.java文件与UserServiceImpl.java文件参见方式一。

(2)在spring-09-AOP子项目的java目录下新建一个com.atangbiji.diy包,并在该包下新建一个DiyAspect类,用于自定义切面。

DiyAspect.java文件:

package com.atangbiji.diy;
//自定义切面
public class DiyAspect {
    //通知1
    public void before(){
        System.out.println("---------方法执行前---------");
    }
    //通知2
    public void after(){
        System.out.println("---------方法执行后---------");
    }
}

(3)在子项目resource目录下的applicationContext.xml配置文件中注册bean,并配置AOP

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--bean就是那些组成我们应用程序的Java对象,由Spring中的IOC容器创建、组装和管理-->
    <!--(1)id:bean的唯一标识符。(相当于之前new的对象名)-->
    <!--(2)class:bean所对应对象的全限定类名。(全限定名=包名+类名)-->

    <!--方式二:通过自定义类实现AOP-->
    <!--注册bean(切面及通知类型)-->
    <bean id="userServiceImpl" class="com.atangbiji.service.UserServiceImpl"/>
    <bean id="diy" class="com.atangbiji.diy.DiyAspect"/>

    <!--配置AOP-->
    <aop:config>
        <!--自定义切面的引用-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="diyPointcut" expression="execution(* com.atangbiji.service.UserServiceImpl.*(..))"/>
            <!--通知类型(把diy类从diyPointcut切入点织入)-->
            <!--(1)method:通知的引用;(2)pointcut-ref:切入点的引用-->
            <aop:before method="before" pointcut-ref="diyPointcut"/>
            <aop:after method="after" pointcut-ref="diyPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

(4)保持子项目的src/test/java目录下的MyTest测试类不变,重新运行测试程序test,切面同样织入成功。执行结果如下图所示:

 

2.3、方式三:通过注解实现AOP(主流)

(1)保持spring-09-AOP子项目com.atangbiji.service包下的UserService接口及其接口实现类UserServiceImpl不变。

注:UserService.java文件与UserServiceImpl.java文件参见方式一。

(2)在spring-09-AOP子项目的java目录下新建一个com.atangbiji.annotation包,并在该包下新建一个AnnotationAspect类,用于自定义切面。

AnnotationAspect.java文件:

package com.atangbiji.annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//使用注解配置自定义切面
@Aspect
public class AnnotationAspect {
    //使用注解配置(前置)通知。语法格式:@Before("切入点")
    @Before("execution(* com.atangbiji.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("===========方法执行前=========");
    }

    //使用注解配置(后置)通知。语法格式:@After("切入点")
    @After("execution(* com.atangbiji.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("===========方法执行后=========");
    }

    //使用注解配置(环绕)通知。语法格式:@Around("切入点")
    @Around("execution(* com.atangbiji.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("------------环绕前-----------");
        //执行目标方法
        jp.proceed();
        System.out.println("------------环绕后-----------");
    }
}

注:在环绕增强(通知)中,我们可以传入一个连接点参数,代表我们要获取处理切入的点。

(3)在子项目resource目录下的applicationContext.xml配置文件中注册bean,并开启注解支持。

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--bean就是那些组成我们应用程序的Java对象,由Spring中的IOC容器创建、组装和管理-->
    <!--(1)id:bean的唯一标识符。(相当于之前new的对象名)-->
    <!--(2)class:bean所对应对象的全限定类名。(全限定名=包名+类名)-->

    <!--方式三:通过注解实现AOP-->
    <!--注册bean(切面及通知类型)-->
    <bean id="userServiceImpl" class="com.atangbiji.service.UserServiceImpl"/>
    <bean id="annotationAspect" class="com.atangbiji.annotation.AnnotationAspect"/>
    <!--开启注解支持-->
    <aop:aspectj-autoproxy/>
</beans>

注:我们也可以通过注解方式注册bean,为了方便我们理解通过注解实现AOP,这里我们仍然使用xml配置文件的方式注册bean

(4)保持子项目的src/test/java目录下的MyTest测试类不变,重新运行测试程序test,切面同样织入成功。执行结果如下图所示:

附:aop:aspectj-autoproxy(开启注解支持)说明

  • 通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了。

  • 有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

 

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

之乎者也·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值