pursue wind pursue wind
首页
Java
Python
数据库
框架
Linux
中间件
前端
计算机基础
DevOps
项目
面试
书
关于
归档
MacOS🤣 (opens new window)
GitHub (opens new window)
首页
Java
Python
数据库
框架
Linux
中间件
前端
计算机基础
DevOps
项目
面试
书
关于
归档
MacOS🤣 (opens new window)
GitHub (opens new window)
  • 技术面试题篇

  • 面试准备篇

  • 技术面试题自测篇

  • 练级攻略篇

  • 工作篇

  • 面经篇

  • 笑傲Java面试

    • 2-1 导学-Java编程技巧部分
    • 2-2 IDEA Java配置补充
    • 2-4 Java8 Stream 接口:流和并发计算实例
    • 2-5 和面试官聊聊实现管道和流计算的基石:函数式的Monad
    • 2-6 Buffer的原理和使用场景+面试题解读
    • 2-7 补充提问:同步和阻塞、异步和非阻塞等不等价?
    • 2-8 阿里面试题:中文乱码处理和大文件计算词频
    • 2-9 实战场景Coding训练:解读反射+代理+AOP 并结合业务逻辑实现
      • [](#反射reflection的概念)反射(reflection)的概念
        • [](#定义)定义
        • [](#面向切面编程aspect-oriented-programming-aop)面向切面编程(Aspect Oriented Programming, AOP)
      • [](#aop的实现:代理模式)AOP的实现:代理模式
        • [](#题目的答案)题目的答案
      • [](#总结)总结
    • 2-10 注解部分答案
    • 2-11 反射-元编程面试题目合集
    • 2-12 面试必备:Java8-11的新特性和理解的误区
    • 2-13 白板篇-Java编程总结(以及面试题)
    • 3-1 算法和数据结构导学
    • 3-2 教你面试时不会忘记的5种手写排序
    • 3-3 手写链表算法
    • 3-4 手写栈和队列面试专项
    • 3-5 课后习题+面试题:用栈和队列实现表达式解析
    • 3-6 迷宫伪代码和8皇后问题源代码
    • 3-7 3-7 树部分源代码
    • 3-8 8皇后问题
    • 3-10 动态规划的课前题目
    • 3-11 总结和课后习题:白板篇-数据结构和算法
    • 4-1 解读:并发编程知识体系
    • 4-2 看看你的基础:Java线程状态之间如何转换?
    • 4-3 CAS和原子操作
    • 4-4 同步器(上篇)——面试官问synchronized本质是什么?
    • 4-5 同步器(中)——AbstractQueuedSynchronizer
    • 4-6 面试官:说6个Java的同步器?
    • 4-7 面试官出难题:并发环境下单例怎么写性能最高
    • 4-8 面试官:LinkedBlockingDeque和SynchronousQueue工作原理一样吗?
    • 4-9 面试要点:volatile的简短补充
    • 4-10 给面试官讲讲无锁编程(Lock-Free Programming)
    • 4-11 高阶并发编程Coding训练:N种优化哲学家就餐问题的方法
    • 4-12 并发基础篇:总结和思考题
    • 4-13 并发部分的通关Boss: 生成、发放大量红包并控制资金流速
  • LeetCode

  • 面试
  • 笑傲Java面试
pursuewind
2021-12-13
目录

2-9 实战场景Coding训练:解读反射+代理+AOP 并结合业务逻辑实现

# 实战场景Coding训练:解读反射+代理+AOP 并结合业务逻辑实现

作为架构的核心工具,代理、AOP、ORM等等工具的实现,在面试当中会出现。遇到和反射相关的问题应该如何回答呢,我要准备到什么程度呢?其实,所有的准备还是回归到本质、理解原理,并且可以实现,作为学习应该达到的深度是比较合适的,也可以比较好的通过面试拿到offer。

关联题目:

  1. 什么是反射?有什么用处?
  2. 写一个关于XXX反射的程序?
  3. 解释下为什么要面向切面编程?4
  4. 为什么Proxy.newProxyInstance要传入ClassLoader?5
  5. 写一个程序实现AOP?(关联上一节课的订单切面框架)

# 反射(reflection)的概念

# 定义

反射(reflection),就是运行时查看、反观程序内部结构,甚至修改。

为了实现反射,编程语言在运行时(runtime),必须了解正在执行的程序都由哪些部分组成。这样的了解,我们通常称之为元数据(Meta Data)。Java中,模块、类、函数、注解、源代码……都是元数据。通过反射,我们可以在运行时拿到这些数据。

有时候我们反射的目的是为了反观自身。比如查找元数据,通过字符串找到某个类并且调用它的方法。比如说去查看一个类有哪些属性和方法。比如去查看一个类有哪些注解,注解有哪些数据?这些都是反射的能力。

另一个反射提供的能力,就是让程序用自己动态的修改自己。这部分知识牵扯到直接修改Java的字节码,我会在讲ClassLoader的时候,为大家演示Javasist工具的使用方法。 Javasisit支持我们在运行时再去定义类、接口、方法,将这些定义转化成字节码,再通过ClassLoader加载。

# 面向切面编程(Aspect Oriented Programming, AOP)

Java编程中经常会用到反射。其中比较常见的场景包括: ORM工具、注解和AOP。 接下来我们说说AOP。

AOP最核心的指导思想就是关注点分离原则(Seperation of Concern )——把程序分成多个部分,每个部分负责独立的功能。

听上去是一件废话。我知道大家每天都在做这件事情。程序当然要分成很多个部分,让每个部分负责独立的能力。但是写着写着又忘记了。

以上节课的题目为例,是不是写着写着业务代码,打日志的代码就可业务代码混写了?是不是?写着写着支付逻辑,支付之后的通知,就在支付逻辑中实现了。

这就是关注点分离原则没有贯彻到底。所以我们不仅仅要了解原则,我们还要知道某一个原则可以如何去架构。或者说,代码怎么写?

image-20210217214528628

以订单场景为例,在这个场景中,核心逻辑当然是和订单相关的几个方法。比如说下单。支付。但是这里可能会有很多的辅助能力。比如说用户通知。写日志。触发某种奖励,比如抽奖。那像这样的程序。应该如何去架构呢?

这里其实有很多种方法,面向切面编程就是其中的一种方法。将每一种辅助能力看作是核心能力的一个切面。在下单前,下单中,或者下单之后。触发这些辅助能力。而何时触发,并不是描述在下单逻辑当中的。而是描述在一个其他的不相关的地方。这样就减少了代码的耦合。

我们之所以会用面向切面编程,是因为有两个重要的反模式需要避免。所谓反模式,就是不好的设计方法。其中一个叫做耦合,在一个业务逻辑中,比如支付,耦合了太多的可以分离的关注点,会导致主线逻辑混乱。第二种还是耦合,比如将业务逻辑用一个函数一做到底,没有分成不同的领域——比如支付领域、通知领域、任务领域等等,去看待自己的业务。

如何理解切面(Aspect)?

理解了,关注点分离原则,再去理解面向切面,就容易了。面向切面是这样看待一个业务的:

image-20210217215830148

# AOP的实现:代理模式

你可以思考:如何将不同的方面结合在一起工作?

image-20210218004738063

我们必须在方面和主要逻辑之间找到它们的结合点,这种结合方式必须是可以描述的,不需要,在主逻辑中去定义的。

用户调用的依然是核心方法,例如Order.pay()。而这个核心方法已经不是真正的核心方法了,他是通过配置生成的一个代理方法。

image-20210217220328085

具体来说,用户调用代理对象的pay方法,但实际触发的不是Order.pay,而是一个中间人(代理人)InvocationHander的invoke方法。invoke方法执行的时候,会调用order的pay方法,也会帮助执行结合点的程序(Aspect程序)。

Java的代理能力,是通过Proxy类实现的。 我们可以通过Proxy.newInstance创建一个代理对象。

Proxy.newInstance(classloader,  interfaces, InvocationHandler);
  • classloader:用于创建InvocationHander和代理对象的ClassLoader
  • interfaces: 代理哪些接口
  • InvocationHandler: 代理方法触发后的实际执行者

下面是通过代理对象访问pay方法的示例:

@Test
public void test_proxy() throws InterruptedException {

    var order = new Order();
    var proxy = (IOrder)Proxy.newProxyInstance(
        Order.class.getClassLoader(),
        new Class[]{IOrder.class},
        new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("before invoke method:" + method);
                return method.invoke(order);
            }
        }
    );
    proxy.pay();

}

建议看下视频的解读。

这里有3个思考:

  • 思考:为什么需要InvocationHandler?
  • 思考:为什么需要传入Interface?
  • 思考:为什么需要ClassLoader?

如果感兴趣答案,看下视频。

# 题目的答案

在Aspect中实现一个static 方法用于实现代理:

public interface Aspect {
    void before();
    void after();


    static <T> T getProxy(Class<T> cls, String ... aspects) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        var inst = cls.getConstructor().newInstance();
        var aspectInsts= Arrays.stream(aspects).map(name -> Try.ofFailable(() -> {
            System.out.println("Find class");
            var clazz = Class.forName(name);
            System.out.println(clazz);
            var aspectInst = (Aspect)clazz.getConstructor().newInstance();
            return aspectInst;
        }))
                .filter(aspectTry -> aspectTry.isSuccess())
                .collect(Collectors.toList());

        return (T) Proxy.newProxyInstance(
                cls.getClassLoader(),
                cls.getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        for(var aspect : aspectInsts) {
                            aspect.get().before();
                        }
//                        var result = method.invoke(inst);
                        for(var aspect : aspectInsts) {
                            aspect.get().after();
                        }

                        return null;
                        //return result;
                    }
                }
        );
    }
}

Line by Line的Coding见视频讲解。

小彩蛋

在上面程序中,读取方面对象出来的时候用了Try Monad。这个Monad非常重要,但是Java没有提供,如果感兴趣,看我视频的解读和在慕课网gitlab上的源代码。

# 总结

对编程语言来说,反射是一个非常重要的能力,是一个节省代码量(避免样板代码,就是代码重复)的大杀器。 ORM框架、Web框架中会大量使用反射——有的是使用注解、有的是为了实现AOP,有的是为了实现进行时修改程序(也被称作元编程)。

关于元编程,我们会在后续的课程中介绍。

Last Updated: 2023/02/14, 18:02:00
2-8 阿里面试题:中文乱码处理和大文件计算词频
2-10 注解部分答案

← 2-8 阿里面试题:中文乱码处理和大文件计算词频 2-10 注解部分答案→

Theme by Vdoing | Copyright © 2019-2023 pursue-wind | 粤ICP备2022093130号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
  • 飙升榜
  • 新歌榜
  • 云音乐民谣榜
  • 美国Billboard榜
  • UK排行榜周榜
  • 网络DJ