Spring题集 - Spring AOP相关面试题总结

文章目录

      • 01. Spring AOP 的理解?
      • 02. Spring AOP 思想的代码实现
      • 03. Spring AOP 的相关术语有哪些?
      • 04. Spring AOP 基于注解的切面实现?
      • 05. Spring AOP 的通知有哪些类型?
      • 06. AOP 有哪些实现方式?
      • 07. Spring AOP 和 AspectJ AOP 有什么区别?
      • 08. JDK动态代理和CGLIB动态代理的区别?
      • 09. 如何理解 Spring 中的代理?
      • 10. Spring在运行时通知对象?
      • 11. Spring 只支持方法级别的连接点?
      • 12. Spring AOP 中 Weaving 是什么?

01. Spring AOP 的理解?

Spring AOP(Aspect-Oriented Programming,面向切面编程)提供了一种在程序运行期间动态地将代码切入到方法调用前、调用后或抛出异常时等不同执行点上的能力,可以在不修改原有代码的情况下,对系统进行横切关注点的处理,如日志记录、性能统计、安全控制等。

Spring题集 - Spring AOP相关面试题总结_第1张图片

02. Spring AOP 思想的代码实现

在BeanPostProcessor的after方法中使用动态代理对Bean进行增强,实际存储到单例池singleObjects中的不是当前目标对象本身,而是当前目标对象的代理对象Proxy,这样在调用目标对象方法时,实际调用的是代理对象Proxy的同名方法,起到了目标方法前后都进行增强的功能。

将增强的方法提取出去到一个增强类中,且只对com.hh.service.impl包下的任何类的任何方法进行增强。

① 增强的方法:

@Component
public class MyAdvice1 {
    public void beforeAdvice(){
        System.out.println("前置的增强....");
    }

    public void afterAdvice(){
        System.out.println("最终的增强....");
    }
}

② 在BeanPostProcessor的after方法中对bean进行增强:

@Component
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        //目的:对UserServiceImpl中的show1和show2方法进行增强,增强方法存在与MyAdvice1中
        if(bean.getClass().getPackage().getName().equals("com.hh.service.impl")){
            //生成当前Bean的Proxy对象
            Object beanProxy = Proxy.newProxyInstance(
                    bean.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    (Object proxy, Method method, Object[] args) -> {
                        // 从Spring容器中获取增强类对象
                        MyAdvice1 myAdvice1 = applicationContext.getBean(MyAdvice1.class);
                        // 执行增强对象的before方法
                        myAdvice1.beforeAdvice();
                        // 执行目标对象的目标方法
                        Object result = method.invoke(bean, args);
                        // 执行增强对象的after方法
                        myAdvice1.afterAdvice();
                        return result;
                    }
            );
            return beanProxy;
        }
        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

③ UserServiceImpl中的show1和show2方法:

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("show1....");
    }

    @Override
    public void show2() {
        System.out.println("show2....");
    }
}

④ 配置类:

@Configuration
@ComponentScan("com.hh")   
public class SpringConfig {
}

④ 测试:

public class ApplicationContextTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userServiceImpl = (UserService)context.getBean("userServiceImpl");
        userServiceImpl.show1();
    }
}

环绕前的增强…
show1…
环绕后的增强…

03. Spring AOP 的相关术语有哪些?

Spring题集 - Spring AOP相关面试题总结_第2张图片

在Spring AOP中,有几个重要的名词需要理解:

① Aspect:切面包含了一组通知和切点。

② Advice:通知是切面的具体实现,它定义了在何时、何地、如何执行切面的逻辑。

③ Pointcut:切点是一个表达式,它定义了哪些方法需要被切入。

④ Joinpoint:连接点是程序执行过程中能够被切入的点。

⑤ Weaving:织入是将切面应用到目标对象中的过程,它可以在编译时、类加载时或运行时进行。

04. Spring AOP 基于注解的切面实现?

在 Spring AOP 中,基于注解的切面实现主要包括以下几个步骤:

  • 定义切面类:在切面类上使用 @Aspect 注解来标识该类为切面类,同时在该类中定义切点和通知等信息。
  • 定义切点:在切面类中使用 @Pointcut 注解来定义切点,切点是一个表达式,用于描述哪些连接点应该被拦截。
  • 定义通知:在切面类中定义通知方法,通知方法使用 @Before、@After、@AfterReturning、@AfterThrowing 或 @Around 等注解来标识通知类型,同时可以使用 JoinPoint 参数来获取连接点的信息。
  • 在Spring配置类中开启AOP支持。

① 定义切面类:

@Component
@Aspect
public class MyAspect {
    // 定义切点
    @Pointcut("execution(* com.hh.service.*.*(..))")
    public void myPointcut() {}

    // 定义前置通知
    @Before("myPointcut()")
    public void myBeforeAdvice(JoinPoint joinPoint) {
        System.out.println("Before method " + joinPoint.getSignature().getName());
    }

    // 定义后置通知
    @AfterReturning(pointcut = "myPointcut()", returning = "result")
    public void myAfterReturningAdvice(JoinPoint joinPoint, Object result) {
        System.out.println("After returning method " + joinPoint.getSignature().getName() + ", return value is " + result);
    }
}

② 在Spring配置类中开启AOP支持:

@Configuration
@ComponentScan("com.hh")   
@EnableAspectJAutoProxy  
public class SpringConfig {
}

③ 需要应用切面的Bean:

@Service
public class MyServiceImpl implements MyService {
    @Override
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

④ 测试:

public class ApplicationContextTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userServiceImpl = (UserService)context.getBean("userServiceImpl");
        userServiceImpl.show1();
    }
}

执行结果:

Before method show1
show1…
After returning method show1, return value is null

通过以上步骤,就可以配置一个切面,并将其应用到需要增强的Bean上。在执行目标方法时,切面中定义的通知会根据切点的匹配规则在目标方法的前后执行,从而实现对目标方法的增强。

05. Spring AOP 的通知有哪些类型?

在Spring AOP中,通知(Advice)是在切点(Pointcut)上执行的一段代码,用于实现横切关注点的具体逻辑。通知可以分为以下几种类型:

Spring题集 - Spring AOP相关面试题总结_第3张图片

① 在切面类中定义5种通知:

@Component
@Aspect
public class MyAspect {
    /**
     * 前置通知
     * @param joinPoint 连接点
     */
    @Before("execution(* com.hh.service.*.*(..))")
    public void myBeforeAdvice(JoinPoint joinPoint) {
        System.out.println("Before method " + joinPoint.getSignature().getName());
    }

    /**
     * 后置通知
     * @param joinPoint 连接点
     */
     @After("execution(* com.hh.service.*.*(..))")
    public void myAfterAdvice(JoinPoint joinPoint) {
        System.out.println("After method " + joinPoint.getSignature().getName());
    }

    /**
     * 返回通知
     * @param joinPoint 连接点
     */
    @AfterReturning(pointcut = "execution(* com.hh.service.*.*(..))", returning = "result")
    public void myAfterReturningAdvice(JoinPoint joinPoint, Object result) {
        System.out.println("After returning method " + joinPoint.getSignature().getName() + ", return value is " + result);
    }

    /** 
     * 异常通知 
     * @param joinPoint 连接点
     * @param ex 异常
     */
    @AfterThrowing(pointcut = "execution(* com.hh.service.*.*(..))", throwing = "ex")
    public void myAfterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
        System.out.println("After throwing method " + joinPoint.getSignature().getName() + ", exception is " + ex.getMessage());
    }

    /**
     * 环绕通知
     * @param proceedingJoinPoint 连接点
     * @return Object
     * @throws Throwable 异常
     */
    @Around("execution(* com.hh.service.*.*(..))")
    public Object myAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 在目标方法执行前执行的逻辑
        System.out.println("Around Before method " + proceedingJoinPoint.getSignature().getName());
        // 调用目标方法
        Object result = proceedingJoinPoint.proceed();
        // 在目标方法执行后执行的逻辑
        System.out.println("Around After method " + proceedingJoinPoint.getSignature().getName());
        return result;
    }
}

② 在配置类上开启 Spring AOP:

@Configuration
@ComponentScan("com.hh")   
@EnableAspectJAutoProxy 
public class SpringConfig {
}

③ 需要增强的目标对象和目标方法:

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("show1....");
    }
}

④ 测试:

public class ApplicationContextTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userServiceImpl = (UserService)context.getBean("userServiceImpl");
        userServiceImpl.show1();
    }
}

执行结果:

Around Before method show1
Before method show1
show1…
After returning method show1, return value is null
After method show1
Around After method show1

06. AOP 有哪些实现方式?

AOP(Aspect-Oriented Programming,面向切面编程)有多种实现方式,其中比较常见的包括以下几种:

① 基于代理的 AOP:基于代理的 AOP 是 Spring AOP 的实现方式,它通过创建代理对象来实现对目标对象方法的拦截和增强。基于代理的 AOP 又分为 JDK 动态代理和 CGLIB 代理两种方式,其中 JDK 动态代理只能代理实现了接口的类,而 CGLIB 代理可以代理任意类。

② 基于字节码增强的 AOP:基于字节码增强的 AOP 是 AspectJ 的实现方式,它通过在编译期或类加载期间修改字节码来实现对目标对象方法的拦截和增强。基于字节码增强的 AOP 可以拦截更多的操作,例如类的初始化、静态方法调用等。

③ 基于注解的 AOP:基于注解的 AOP 是一种简化 AOP 配置的方式,它通过在切面类和目标类的方法上添加注解来实现对目标对象方法的拦截和增强。基于注解的 AOP 可以减少配置文件的编写,提高开发效率。

需要注意的是,不同的 AOP 实现方式有各自的优缺点,选择合适的实现方式需要根据具体的业务需求和技术场景进行评估。

07. Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 和 AspectJ AOP 都是 AOP 的实现方式,它们有以下几点区别:

① 实现方式:Spring AOP 是基于代理的 AOP,它通过创建代理对象来实现对目标对象方法的拦截和增强;而 AspectJ AOP 是基于字节码增强的 AOP,它通过在编译期或类加载期间修改字节码来实现对目标对象方法的拦截和增强。

② 功能支持:AspectJ AOP 支持更多的 AOP 功能,例如拦截类的初始化、静态方法调用等操作,而 Spring AOP 只能拦截方法调用。

③ 配置方式:Spring AOP 的配置方式比较简单,可以通过 XML 配置文件或注解来实现;而 AspectJ AOP 的配置方式比较复杂,需要使用 AspectJ 的专用语言来编写切面。

④ 性能:由于 Spring AOP 是基于代理的 AOP,它的性能相对较低;而 AspectJ AOP 是基于字节码增强的 AOP,它的性能相对较高。

需要注意的是,Spring AOP 和 AspectJ AOP 都是 AOP 的实现方式,它们可以互相补充,例如可以使用 Spring AOP 来拦截方法调用,使用 AspectJ AOP 来拦截类的初始化等操作。选择合适的 AOP 实现方式需要根据具体的业务需求和技术场景进行评估。

08. JDK动态代理和CGLIB动态代理的区别?

JDK 动态代理和 CGLIB 动态代理都是基于代理模式实现的 AOP 技术,它们的主要区别如下:

① 实现方式:JDK 动态代理是基于接口的代理,它只能代理实现了接口的类;而 CGLIB 动态代理是基于继承的代理,它可以代理任意类,包括没有实现接口的类。

② 性能:JDK 动态代理的性能相对较低,因为它是基于反射实现的;而 CGLIB 动态代理的性能相对较高,因为它是基于字节码增强实现的。

③ 代理对象:JDK 动态代理创建的代理对象是目标对象实现的接口,而 CGLIB 动态代理创建的代理对象是目标对象的子类。

④ 依赖关系:JDK 动态代理依赖于 JDK 的接口和反射机制;而 CGLIB 动态代理依赖于 CGLIB 库。

需要注意的是,JDK 动态代理和 CGLIB 动态代理都有各自的优缺点,选择合适的代理方式需要根据具体的业务需求和技术场景进行评估。如果目标对象实现了接口,可以考虑使用 JDK 动态代理;如果目标对象没有实现接口,可以考虑使用 CGLIB 动态代理。

09. 如何理解 Spring 中的代理?

在 Spring 中,代理是一种常用的设计模式,它可以在不改变原有代码的情况下,为对象提供额外的功能。Spring 中的代理主要用于实现 AOP(Aspect-Oriented Programming,面向切面编程)功能,通过代理对象来拦截方法调用,并在方法调用前后执行一些额外的操作,例如日志记录、权限控制、事务管理等。

Spring 中的代理主要分为两种类型:JDK 动态代理和 CGLIB 动态代理。JDK 动态代理是基于接口的代理,它只能代理实现了接口的类,通过反射机制来实现代理;而 CGLIB 动态代理是基于继承的代理,它可以代理任意类,通过修改字节码来实现代理。

在 Spring 中,代理对象是通过 AOP 框架创建的,它包含了目标对象和切面,用于在目标对象方法执行前后执行通知。代理对象可以通过 Spring 容器来获取,也可以通过编程方式创建。在使用代理对象时,需要注意代理对象和目标对象的类型,以及代理对象的生命周期等问题。

需要注意的是,代理是一种常用的设计模式,不仅在 Spring 中有应用,在其他框架和系统中也有广泛的应用。理解代理的概念和原理,可以帮助我们更好地理解和使用代理模式。

10. Spring在运行时通知对象?

在 Spring 中,运行时通知对象是指在程序运行期间,通过 AOP(Aspect-Oriented Programming,面向切面编程)技术来拦截方法调用,并在方法调用前后执行一些额外的操作,例如日志记录、权限控制、事务管理等。Spring 中的 AOP 框架提供了一种方便的方式来实现运行时通知对象,它可以通过代理对象来拦截方法调用,并在方法调用前后执行通知。

在 Spring 中,运行时通知对象的实现主要分为以下几个步骤:

定义切面:切面是一个横跨多个核心关注点的模块化功能单元,它包含了一组切点和一组通知,用于描述在哪些切点上执行哪些通知。

配置切面:在 Spring 配置文件中,通过 aop:config 元素来配置切面,包括切点和通知等信息。

创建代理对象:在 Spring 容器中,通过 aop:proxy 元素来创建代理对象,代理对象包含了目标对象和切面,用于在目标对象方法执行前后执行通知。

使用代理对象:在程序中,通过 Spring 容器来获取代理对象,并使用代理对象来调用目标对象的方法。代理对象会拦截方法调用,并在方法调用前后执行通知。

需要注意的是,运行时通知对象是通过 AOP 技术实现的,它可以在不改变原有代码的情况下,为对象提供额外的功能。在使用 AOP 技术时,需要注意切面的定义和配置,以及代理对象的创建和使用等问题

11. Spring 只支持方法级别的连接点?

在 Spring AOP 中,连接点(Join Point)是指程序执行过程中能够插入切面的点,例如方法调用、异常抛出等。Spring AOP 只支持方法级别的连接点,也就是说,只能拦截方法调用,而不能拦截类的初始化、静态方法调用等操作。

这是因为 Spring AOP 是基于代理的 AOP 实现方式,它只能拦截方法调用,而不能拦截类的初始化、静态方法调用等操作。在 JDK 动态代理中,代理对象只能实现接口,而接口中只有方法定义,因此只能拦截方法调用。在 CGLIB 动态代理中,代理对象是目标对象的子类,因此也只能拦截方法调用。

需要注意的是,虽然 Spring AOP 只支持方法级别的连接点,但是它可以通过切点表达式(Pointcut Expression)来选择特定的方法进行拦截,从而实现对程序行为的统一管理和控制。如果需要拦截类的初始化、静态方法调用等操作,可以考虑使用 AspectJ 等其他 AOP 框架。

12. Spring AOP 中 Weaving 是什么?

在 Spring AOP 中,Weaving(织入)是指将切面代码插入到目标对象的字节码中,从而实现对目标对象方法调用的拦截和增强。Weaving 是 AOP 的核心概念之一,它是实现 AOP 的关键技术之一。

Spring AOP 中的织入方式主要有两种:编译时织入和运行时织入。

编译时织入:编译时织入是指在编译期间将切面代码织入到目标对象的字节码中,从而生成新的字节码文件。编译时织入需要使用特定的编译器和插件,例如 AspectJ 编译器和 Maven 插件等。

运行时织入:运行时织入是指在程序运行期间将切面代码织入到目标对象的字节码中,从而实现对目标对象方法调用的拦截和增强。运行时织入需要使用 AOP 框架提供的代理对象来实现,例如 Spring AOP 中的 JDK 动态代理和 CGLIB 动态代理。

需要注意的是,Weaving 是 AOP 的核心概念之一,它是实现 AOP 的关键技术之一。在使用 AOP 技术时,需要了解织入的原理和方式,以及织入对程序性能和可维护性等方面的影响。

① 前置通知(Before):在目标方法执行之前执行通知逻辑。

public class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        // 在目标方法执行前执行的逻辑
        System.out.println("Before method " + method.getName());
    }
}

② 后置通知(After):在目标方法执行之后执行通知逻辑。

public class MyAfterAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        // 在目标方法执行后执行的逻辑
        System.out.println("After method " + method.getName());
    }
}

③ 返回通知(AfterReturning):在目标方法执行之后执行通知逻辑,只有在目标方法成功返回时才会执行。

public class MyAfterReturningAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        // 在目标方法成功返回后执行的逻辑
        System.out.println("After returning method " + method.getName() + ", return value is " + returnValue);
    }
}

④ 异常通知(AfterThrowing):在目标方法抛出异常时执行通知逻辑。

public class MyAfterThrowingAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
        // 在目标方法抛出异常后执行的逻辑
        System.out.println("After throwing method " + method.getName() + ", exception is " + ex.getMessage());
    }
}

⑤ 环绕通知(Around):在目标方法执行之前和之后都可以执行通知逻辑,可以控制目标方法的执行过程,例如可以在目标方法执行前进行一些准备工作,执行后进行一些清理工作。

public class MyAroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 在目标方法执行前执行的逻辑
        System.out.println("Before method " + invocation.getMethod().getName());

        // 调用目标方法
        Object result = invocation.proceed();

        // 在目标方法执行后执行的逻辑
        System.out.println("After method " + invocation.getMethod().getName());

        return result;
    }
}

这些通知类型可以根据具体的需求进行选择和组合,从而实现对目标对象的增强。例如,可以使用前置通知记录日志,使用后置通知进行资源释放,使用返回通知进行结果处理,使用异常通知进行异常处理,使用环绕通知控制目标方法的执行过程。

你可能感兴趣的:(【框架-Spring】,spring,java,后端)