首页 > 其他分享 >面试类 - Spring基础(二)

面试类 - Spring基础(二)

时间:2024-04-29 10:23:47浏览次数:14  
标签:对象 Spring 基础 代理 目标 面试 AOP public

AOP

19.说说什么是 AOP?

AOP,也就是 Aspect-oriented Programming,译为面向切面编程。

简单点说,就是把一些业务逻辑中的相同代码抽取到一个独立的模块中,让业务逻辑更加清爽。

三分恶面渣逆袭:横向抽取

                                                                            横向抽取

举个例子,假如我们现在需要在业务代码开始前进行参数校验,在结束后打印日志,该怎么办呢?

我们可以把日志记录数据校验这两个功能抽取出来,形成一个切面,然后在业务代码中引入这个切面,这样就可以实现业务逻辑和通用逻辑的分离。

三分恶面渣逆袭:AOP应用示例AOP应用示例

业务代码不再关心这些通用逻辑,只需要关心自己的业务实现,这样就实现了业务逻辑和通用逻辑的分离。

我们来回顾一下 Java 语言的执行过程:

三分恶面渣逆袭:Java 执行过程

                                                                         Java 执行过程

AOP 的核心是动态代理,可以使用 JDK 动态代理来实现,也可以使用 CGLIB 来实现。

AOP 有哪些核心概念?

  • 切面(Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
  • 连接点(Join Point):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中,连接点指的是被拦截到的方法,实际上连接点还可以是字段或者构造方法
  • 切点(Pointcut):对连接点进行拦截的定位
  • 通知(Advice):指拦截到连接点之后要执行的代码,也可以称作增强
  • 目标对象 (Target):代理的目标对象
  • 引介(introduction):一种特殊的增强,可以动态地为类添加一些属性和方法
  • 织入(Weabing):织入是将增强添加到目标类的具体连接点上的过程。可以分为 3 种类型的织入:

①、编译期织入:切面在目标类编译时被织入。

②、类加载期织入:切面在目标类加载到 JVM 时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。

③、运行期织入:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。Spring AOP 就是以这种方式织入切面。

Spring 采用运行期织入,而 AspectJ 采用编译期织入和类加载器织入。

AOP 有哪些环绕方式?

AOP 一般有 5 种环绕方式:

  • 前置通知 (@Before)
  • 返回通知 (@AfterReturning)
  • 异常通知 (@AfterThrowing)
  • 后置通知 (@After)
  • 环绕通知 (@Around)

三分恶面渣逆袭:环绕方式

 

                                                        环绕方式

多个切面的情况下,可以通过 @Order 指定先后顺序,数字越小,优先级越高。代码示例如下:

 
@Aspect
@Component
public class WebLogAspect {

    private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    @Pointcut("@annotation(cn.fighter3.spring.aop_demo.WebLog)")
    public void webLog() {}

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 打印请求相关参数
        logger.info("========================================== Start ==========================================");
        // 打印请求 url
        logger.info("URL            : {}", request.getRequestURL().toString());
        // 打印 Http method
        logger.info("HTTP Method    : {}", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        logger.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        // 打印请求的 IP
        logger.info("IP             : {}", request.getRemoteAddr());
        // 打印请求入参
        logger.info("Request Args   : {}",new ObjectMapper().writeValueAsString(joinPoint.getArgs()));
    }

    @After("webLog()")
    public void doAfter() throws Throwable {
        // 结束后打个分隔线,方便查看
        logger.info("=========================================== End ===========================================");
    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //开始时间
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        // 打印出参
        logger.info("Response Args  : {}", new ObjectMapper().writeValueAsString(result));
        // 执行耗时
        logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
        return result;
    }
}
 

总结一下:

AOP,也就是面向切面编程,是一种编程范式,旨在提高代码的模块化。比如说可以将日志记录、事务管理等分离出来,来提高代码的可重用性。

AOP 的核心概念包括切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)和织入(Weaving)等。

① 像日志打印、事务管理等都可以抽离为切面,可以声明在类的方法上。

② 在 Spring AOP 中,连接点总是表示方法的执行。

③ Spring AOP 支持五种类型的通知:前置通知、后置通知、环绕通知、异常通知、最终通知等。

④ 在 AOP 中,切点用于指定我们想要在哪些连接点上执行通知的规则。

⑤ 织入是指将切面应用到目标对象并创建新的代理对象的过程。Spring AOP 默认在运行时通过动态代理方式实现织入。

像 @Transactional 注解,就是一个典型的 AOP 应用,它就是通过 AOP 来实现事务管理的。我们只需要在方法上添加 @Transactional 注解,Spring 就会在方法执行前后添加事务管理的逻辑。

  1. Java 面试指南(付费)open in new window收录的腾讯 Java 后端实习一面原题:说说 AOP 的原理。
  2. Java 面试指南(付费)open in new window收录的小米 25 届日常实习一面原题:说说你对 AOP 和 IoC 的理解。
  3. Java 面试指南(付费)open in new window收录的快手面经同学 7 Java 后端技术一面面试原题:说一下 Spring AOP 的实现原理

20.你平时有用到 AOP 吗?

有,我在技术派实战项目open in new window中就有使用,我利用 AOP 打印了接口的入参和出参日志,以及执行时间。

沉默王二:技术派教程                                                                                                                    技术派教程

我是这样使用的。

第一步,自定义一个注解作为切点

 
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MdcDot {
    String bizCode() default "";
}
 

第二步,配置 AOP 切面:

  • @Aspect:标识切面
  • @Pointcut:设置切点,这里以自定义注解为切点
  • @Around:环绕切点,打印方法签名和执行时间

技术派项目:配置 AOP 切面                                                                                                                                                                  技术派项目:配置 AOP 切面

第三步,在使用的地方加上自定义注解

技术派项目:使用注解                                                                                                                                            技术派项目:使用注解

第四步,当接口被调用时,就可以看到对应的执行日志。

 
2023-06-16 11:06:13,008 [http-nio-8080-exec-3] INFO |00000000.1686884772947.468581113|101|c.g.p.forum.core.mdc.MdcAspect.handle(MdcAspect.java:47) - 方法执行耗时: com.github.paicoding.forum.web.front.article.rest.ArticleRestControllerrecommend = 47
 

21.说说 JDK 动态代理和 CGLIB 代理?

Spring 的 AOP 是通过动态代理open in new window来实现的,动态代理主要有两种方式:JDK 动态代理和 CGLIB 代理。

①、JDK 动态代理是基于接口的代理方式,它使用 Java 原生的 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来创建和管理代理对象。

  1. 基于 Interface:JDK 动态代理要求目标对象必须实现一个或多个接口。代理对象不是直接继承自目标对象,而是实现了与目标对象相同的接口。
  2. 使用 InvocationHandler:在调用代理对象的任何方法时,调用都会被转发到一个 InvocationHandler 实例的 invoke 方法。可以在这个 invoke 方法中定义拦截逻辑,比如方法调用前后执行的操作。
  3. 基于 Proxy:Proxy 利用 InvocationHandler 动态创建一个符合目标类实现的接口实例,生成目标类的代理对象。

②、CGLIB(Code Generation Library)是一个第三方代码生成库,它通过继承方式实现代理,不需要接口,被广泛应用于 Spring AOP 中,用于提供方法拦截操作。

图片来源于网络

  1. 基于继承,CGLIB 通过在运行时生成目标对象的子类来创建代理对象,并在子类中覆盖非 final 的方法。因此,它不要求目标对象必须实现接口。
  2. 基于 ASM,ASM 是一个 Java 字节码操作和分析框架,CGLIB 可以通过 ASM 读取目标类的字节码,然后修改字节码生成新的类。它在运行时动态生成一个被代理类的子类,并在子类中覆盖父类的方法,通过方法拦截技术插入增强代码。

选择 CGLIB 还是 JDK 动态代理?

  • 如果目标对象没有实现任何接口,则只能使用 CGLIB 代理。如果目标对象实现了接口,通常首选 JDK 动态代理。
  • 虽然 CGLIB 在代理类的生成过程中可能消耗更多资源,但在运行时具有较高的性能。对于性能敏感且代理对象创建频率不高的场景,可以考虑使用 CGLIB。
  • JDK 动态代理是 Java 原生支持的,不需要额外引入库。而 CGLIB 需要将 CGLIB 库作为依赖加入项目中。

你会用 JDK 动态代理和 CGLIB 吗?

假设我们有这样一个小场景,客服中转,解决用户问题:

三分恶面渣逆袭:用户向客服提问题

                                                                               用户向客服提问题

①、JDK 动态代理实现:

三分恶面渣逆袭:JDK动态代理类图

 

                                                                                   JDK动态代理类图

第一步,创建接口

 
public interface ISolver {
    void solve();
}
 

第二步,实现对应接口

 
public class Solver implements ISolver {
    @Override
    public void solve() {
        System.out.println("疯狂掉头发解决问题……");
    }
}
 

第三步,动态代理工厂:ProxyFactory,直接用反射方式生成一个目标对象的代理,这里用了一个匿名内部类方式重写 InvocationHandler 方法。

 
public class ProxyFactory {

    // 维护一个目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    // 为目标对象生成代理对象
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("请问有什么可以帮到您?");

                        // 调用目标对象方法
                        Object returnValue = method.invoke(target, args);

                        System.out.println("问题已经解决啦!");
                        return null;
                    }
                });
    }
}
 

第五步,客户端:Client,生成一个代理对象实例,通过代理对象调用目标对象方法

 
public class Client {
    public static void main(String[] args) {
        //目标对象:程序员
        ISolver developer = new Solver();
        //代理:客服小姐姐
        ISolver csProxy = (ISolver) new ProxyFactory(developer).getProxyInstance();
        //目标方法:解决问题
        csProxy.solve();
    }
}
 

②、CGLIB 动态代理实现:

三分恶面渣逆袭:CGLIB动态代理类图

                                                                   CGLIB动态代理类图

第一步:定义目标类(Solver),目标类 Solver 定义了一个 solve 方法,模拟了解决问题的行为。目标类不需要实现任何接口,这与 JDK 动态代理的要求不同。

 
public class Solver {

    public void solve() {
        System.out.println("疯狂掉头发解决问题……");
    }
}
 

第二步:动态代理工厂(ProxyFactory),ProxyFactory 类实现了 MethodInterceptor 接口,这是 CGLIB 提供的一个方法拦截接口,用于定义方法的拦截逻辑。

 
public class ProxyFactory implements MethodInterceptor {

    //维护一个目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //为目标对象生成代理对象
    public Object getProxyInstance() {
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建子类对象代理
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("请问有什么可以帮到您?");
        // 执行目标对象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("问题已经解决啦!");
        return null;
    }

}
 
  • ProxyFactory 接收一个 Object 类型的 target,即目标对象的实例。
  • 使用 CGLIB 的 Enhancer 类来生成目标类的子类(代理对象)。通过 setSuperclass 设置代理对象的父类为目标对象的类,setCallback 设置方法拦截器为当前对象(this),最后调用 create 方法生成并返回代理对象。
  • 重写 MethodInterceptor 接口的 intercept 方法以提供方法拦截逻辑。在目标方法执行前后添加自定义逻辑,然后通过 method.invoke 调用目标对象的方法。

第三步:客户端使用代理,首先创建目标对象(Solver 的实例),然后使用 ProxyFactory 创建该目标对象的代理。通过代理对象调用 solve 方法时,会先执行 intercept 方法中定义的逻辑,然后执行目标方法,最后再执行 intercept 方法中的后续逻辑。

 
public class Client {
    public static void main(String[] args) {
        //目标对象:程序员
        Solver developer = new Solver();
        //代理:客服小姐姐
        Solver csProxy = (Solver) new ProxyFactory(developer).getProxyInstance();
        //目标方法:解决问题
        csProxy.solve();
    }
}
 
  1. Java 面试指南(付费)open in new window收录的帆软同学 3 Java 后端一面的原题:cglib 的原理
  2. Java 面试指南(付费)open in new window收录的腾讯面经同学 22 暑期实习一面面试原题:Spring AOP 实现原理

22.说说 Spring AOP 和 AspectJ AOP 区别?

Spring AOP

Spring AOP 属于运行时增强,主要具有如下特点:

  1. 基于动态代理来实现,默认如果使用接口的,用 JDK 提供的动态代理实现,如果是方法则使用 CGLIB 实现

  2. Spring AOP 需要依赖 IoC 容器来管理,并且只能作用于 Spring 容器,使用纯 Java 代码实现

  3. 在性能上,由于 Spring AOP 是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 的那么好。

  4. Spring AOP 致力于解决企业级开发中最普遍的 AOP(方法织入)。

AspectJ

AspectJ 是一个易用的功能强大的 AOP 框架,属于编译时增强, 可以单独使用,也可以整合到其它框架中,是 AOP 编程的完全解决方案。AspectJ 需要用到单独的编译器 ajc。

AspectJ 属于静态织入,通过修改代码来实现,在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的,一般有如下几个织入的时机:

  1. 编译期织入(Compile-time weaving):如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。

  2. 编译后织入(Post-compile weaving):也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。

  3. 类加载后织入(Load-time weaving):指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法

整体对比如下:

Spring AOP和AspectJ对比

                                                                        Spring AOP和AspectJ对比

标签:对象,Spring,基础,代理,目标,面试,AOP,public
From: https://www.cnblogs.com/pxzbky/p/18165091

相关文章

  • 面试官:素有Java锁王称号的‘StampedLock’你知道吗?我:这什么鬼?
    一、写在开头我们在上一篇写ReentrantReadWriteLock读写锁的末尾留了一个小坑,那就是读写锁因为写锁的悲观性,会导致“写饥饿”,这样一来会大大的降低读写效率,而今天我们就来将此坑填之!填坑工具为:StampedLock,一个素有Java锁王称号的同步类,也是在java.util.concurrent.locks包中......
  • 软件测试面试题(四)
    1、在项目中如何保证软件质量?项目质量不仅仅是某个人或某个团队来保障的,而是整个团队一起努力的结果,因此,在公司级别需要有一个规范的项目流程。产品,保证迭代过程中的产品逻辑,对于可能的兼容,升级做出预判并给出方案架构设计,满足产品表达的同时,保证设计的延续性开发,产品细......
  • Linux操作系统】——基础知识题集1to8
    Linux操作系统】——基础知识题集12345678    /*** 2024年4月28日周日·14:09 *** Linux操作系统基础知识89765421   --1--。2024年4月28日周日·   --2--。2024年4月28日·周日·14:09 。     --3--。2024年4月28日·周日·1......
  • mysql理论数据库优化MySQL数据库面试题
    mysql数据库优化MySQL数据库面试题 MySQL数据库面试题MySQL数据库面试题1、什么是SQL?        结构化查询语言(StructuredQueryLanguage)简称SQL,是一种数据库查询语言。作用:用于存取数据、查询、更新和管理关系数据库系统。 2、什么是MySQL?        M......
  • Java的基础知识
    一.Java入门Java的特性和优势:简单性,面向对象,可移植性,高性能,分布式,动态性,多线程,安全性,健壮性;Java三大版本:JavaSE:标准版(桌面程序,控制台开发)JavaME:嵌入式开发(手机,小家电)JavaEE:企业级开发(web端,服务器开发)JDK,JRE,JVM三者之间的关系:JDK(JavaDevelopmentKit):Java开发......
  • 机器学习笔试面试真题目
       【机器学习】——习题8:KNN原创 码龄1年 关注一.单选题1.下列选择中,关于KNN算法不正确的是?DA.能找出与待测样本相近的k个样本B.距离度量是影响KNN算法的主要因素之⼀C.实现过程相对简单,但是可解释性不强D.KNN的样本搜索中线性扫描效率很⾼2.影响KNN......
  • 第一章 python基础
    1.变量1.1变量的概念可以改变的量就是变量,实际上指代码的是内存的一块空间在内存中,变量默认一次只能指向一个值,当一个值没有任何变量指向的时候,内存会自动把数据从内存释放1.2变量的命名字母、数字、下划线,首字符不能为数字严格区分大小写,且不能使用关键字变量命......
  • SAP 通过配置传输的方式恢复某个CLIENT下的基础配置数据
    SAP通过配置传输的方式恢复某个CLIENT下的基础配置数据  比如某个项目上用户不小心误操作,将SAP系统标准的物料类型全部删除了(事务代码OMS2)。这导致系统无法继续使用了,需要采取技术手段将删除的数据恢复过来。 解决方案也很简单,换一个Client,进入这个配置界面,选中全部......
  • 38天【代码随想录算法训练营34期】第九章 动态规划part01 (● 理论基础 ● 509. 斐波
    理论基础斐波那契数classSolution:deffib(self,n:int)->int:ifn==0:return0ifn==1:return1returnself.fib(n-1)+self.fib(n-2)爬楼梯classSolution:defclimbStairs(self,n:int)->i......
  • vrp基础
     路由器把广播域隔开了            ......