前言

因为假期原因,有一段时间没给大家更新了!和大家说个事吧,放假的时候一位粉丝和我说了下自己的被虐经历,在假期前他去某互联网公司面试,结果直接被人家面试官Spring AOP三连问给问的一脸懵逼!其实我觉着吧,这玩意不是挺简单的吗?

大家在学习 AOP 之前,如果清楚代理模式的话,则学习起来非常轻松,接下来就由我为大家介绍 AOP 这个重要的知识点!

代理模式

代理模式在 Java 开发中是一种比较常见的设计模式。设计目的旨在为服务类与客户类之间插入其他功能,插入的功能对于调用者是透明的,起到伪装控制的作用。如租房的例子:房客、中介、房东。对应于代理模式中即:客户类、代理类 、委托类(被代理类)。

为某一个对象(委托类)提供一个代理(代理类),用来控制对这个对象的访问。委托类和代理类有一个共同的父类或父接口。代理类会对请求做预处理、过滤,将请求分配给指定对象。

生活中常见的代理情况: 租房中介、婚庆公司等

代理模式的两个设计原则:

  1. 代理类与委托类具有相似的行为(共同)
  2. 代理类增强委托类的行为

常用的代理模式:

  1. 静态代理
  2. 动态代理

静态代理

某个对象提供一个代理,代理角色固定,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。

代理的三要素

  • 有共同的行为(结婚) - 接口

  • 目标角色(新人) - 实现行为

  • 代理角色(婚庆公司) - 实现行为 增强目标对象行为

静态代理的特点

  1. 目标角色固定
  2. 在应用程序执行前就得到目标角色
  3. 代理对象会增强目标对象的行为
  4. 有可能存在多个代理,引起"类爆炸"(缺点)

静态代理的实现

定义行为(共同)定义接口

/**
* 定义⾏为
*/
public interface Marry {
public void toMarry();
}

目标对象(实现行为)

/**
* 静态代理 ——> ⽬标对象
*/
public class You implements Marry {
// 实现⾏为
@Override
public void toMarry() {
System.out.println("我要结婚了...");
}
}

代理对象(实现行为、增强目标对象的行为)

/**
* 静态代理 ——> 代理对象
*/
public class MarryCompanyProxy implements Marry {
// ⽬标对象
private Marry marry;
// 通过构造器将⽬标对象传⼊
public MarryCompanyProxy(Marry marry) {
this.marry = marry;
}
// 实现⾏为
@Override
public void toMarry() {
// 增强⾏为
before(); // 执⾏⽬标对象中的⽅法
marry.toMarry();
// 增强⾏为
after();
}
/**
* 增强⾏为
*/
private void after() {
System.out.println("新婚快乐,早⽣贵⼦!");
}
/**
* 增强⾏为
*/
private void before() {
System.out.println("场地正在布置中...");
}
}

通过代理对象实现目标对象的功能

// ⽬标对象
You you = new You();
// 构造代理⻆⾊同时传⼊真实⻆⾊
MarryCompanyProxy marryCompanyProxy = new MarryCompanyProxy(you);
// 通过代理对象调⽤⽬标对象中的⽅法
marryCompanyProxy.toMarry();

静态代理对于代理的角色是固定的,如 dao 层有20个 dao 类,如果要对方法的访问权限进行代理,此时需要创建20个静态代理角色,引起类爆炸,无法满足生产上的需要,于是就催生了动态代理的思想。

动态代理

相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由 Java 反射机制动态产生。它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。动态代理不仅简化了编程工作,二且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理类。代理的行为可以代理多个方法,即满足生产需要的同时又达到代码通用的目的。

动态代理的两种实现方式:

  1. JDK 动态代理
  2. CGLIB动态代理

动态代理的特点

  1. 目标对象不固定
  2. 在应用程序执行时动态创建目标对象
  3. 代理对象会增强目标对象的行为

JDK动态代理

注:JDK动态代理的目标对象必须有接口实现

newProxyInstance

Proxy 类:

Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下操作方法:

/*
返回⼀个指定接⼝的代理类的实例⽅法调⽤分派到指定的调⽤处理程序。 (返回代理对象)
loader:⼀个ClassLoader对象,定义了由哪个ClassLoader对象来对⽣成的代理对象进⾏加载
interfaces:⼀个Interface对象的数组,表示的是我将要给我需要代理的对象提供⼀组什么接⼝,如果
我提供了⼀组接⼝给它,那么这个代理对象就宣称实现了该接⼝(多态),这样我就能调⽤这组接⼝中的⽅法了
h:⼀个InvocationHandler接⼝,表示代理实例的调⽤处理程序实现的接⼝。每个代理实例都具有⼀个关联
的调⽤处理程序。对代理实例调⽤⽅法时,将对⽅法调⽤进⾏编码并将其指派到它的调⽤处理程序的 invoke ⽅法
(传⼊InvocationHandler接⼝的⼦类)
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

获取代理对象

public class JdkHandler implements InvocationHandler {
// ⽬标对象
private Object target; // ⽬标对象的类型不固定,创建时动态⽣成
// 通过构造器将⽬标对象赋值
public JdkHandler(Object target) {
this.target = target;
}
/**
* 1、调⽤⽬标对象的⽅法(返回Object)
* 2、增强⽬标对象的⾏为
* @param proxy 调⽤该⽅法的代理实例
* @param method ⽬标对象的⽅法
* @param args ⽬标对象的⽅法形参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
// 增强⾏为
System.out.println("==============⽅法前执⾏");
// 调⽤⽬标对象的⽅法(返回Object)
Object result = method.invoke(target,args);
// 增强⾏为
System.out.println("⽅法后执⾏==============");
return result;
}
/**
* 得到代理对象
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h)
* loader:类加载器
* interfaces:接⼝数组
* h:InvocationHandler接⼝ (传⼊InvocationHandler接⼝的实现类)
*
*
* @return
*/
public Object getProxy() {
return
Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterface
s(),this);
}
}

通过代理对象实现目标对象的功能

// ⽬标对象
You you = new You();
// 获取代理对象
JdkHandler jdkHandler = new JdkHandler(you);
Marry marry = (Marry) jdkHandler.getProxy();
// 通过代理对象调⽤⽬标对象中的⽅法
marry.toMarry();

问:Java 动态代理类中的 invoke 是怎么调用的?

答:在生成的动态代理类 $Proxy0.class 中,构造方法调用了父类Proxy.class 的构造方法,给成员变量 invocationHandler 赋值,$Proxy0.class的 static 模块中创建了被代理类的方法,调用相应方法时方法体中调用了父类中的成员变量 InvocationHandler 的 invoke ()方法。

注:JDK 的动态代理依靠接口实现,如果有些类并没有接口实现,则不能使用 JDK 代理。

CGLIB 动态代理

JDK 的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能使用 JDK 的动态代理,cglib 是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理。

添加依赖

在 pom.xml 文件中引入 cglib 的相关依赖

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>

定义类

实现 MethodInterceptor 接口

public class CglibInterceptor implements MethodInterceptor {
// ⽬标对象
private Object target;
// 通过构造器传⼊⽬标对象
public CglibInterceptor(Object target) {
this.target = target;
}
/**
* 获取代理对象
* @return
*/
public Object getProxy() {
// 通过Enhancer对象的create()⽅法可以⽣成⼀个类,⽤于⽣成代理对象
Enhancer enhancer = new Enhancer();
// 设置⽗类 (将⽬标类作为其⽗类)
enhancer.setSuperclass(target.getClass());
// 设置拦截器 回调对象为本身对象
enhancer.setCallback(this);
// ⽣成⼀个代理类对象,并返回
return enhancer.create();
}
/**
* 拦截器
* 1、⽬标对象的⽅法调⽤
* 2、增强⾏为
* @param object 由CGLib动态⽣成的代理类实例
* @param method 实体类所调⽤的被代理的⽅法引⽤
* @param objects 参数值列表
* @param methodProxy ⽣成的代理类对⽅法的代理引⽤
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object object, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
// 增强⾏为
System.out.println("==============⽅法前执⾏");
// 调⽤⽬标对象的⽅法(返回Object)
Object result = methodProxy.invoke(target,objects);
// 增强⾏为
System.out.println("⽅法后执⾏==============");
return result;
}
}

调用方法

// ⽬标对象
You you = new You();
CglibInterceptor cglibInterceptor = new CglibInterceptor(you);
Marry marry = (Marry) cglibInterceptor.getProxy();
marry.toMarry();
User user = new User();
CglibInterceptor cglibInterceptor = new CglibInterceptor(user);
User u = (User) cglibInterceptor.getProxy();
u.test();

JDK代理与CGLIB代理的区别

  • JDK 动态代理实现接口,Cglib 动态代理继承思想
  • JDK 动态代理(目标对象存在接口时)执行效率高于 Ciglib
  • 如果目标对象有接口实现,选择 JDK 代理,如果没有接口实现选择 Cglib 代理

Spring AOP

日志处理带来的问题

我们有一个 Pay (接口) 然后两个实现类 DollarPay 和 RmbPay,都需要重写 pay ()方法, 这时我们需要对 pay 方法进行性能监控,日志的添加等等怎么做?

最容易想到的方法

对每个字符方法均做日志代码的编写处理,如下面方式

缺点: 代码重复太多, 添加的日志代码耦合度太高(如果需要更改日志记录代码功能需求,类中方法需要全部改动,工程量浩大)

使用装饰器模式 /代理模式改进解决方案

装饰器模式:动态地给一个对象添加一些额外的职责。

代理模式:以上刚讲过。于是得出以下结构:

仔细考虑过后发现虽然对原有内部代码没有进行改动,对于每个类做日志处理,并引用目标类,但是如果待添加日志的业务类的数量很多,此时手动为每个业务类实现一个装饰器或创建对应的代理类,同时代码的耦合度也加大,需求一旦改变,改动的工程量也是可想而知的。

有没有更好的解决方案,只要写一次代码,对想要添加日志记录的地方能够实现代码的复用,达到松耦合的同时,又能够完美完成功能?

答案是肯定的,存在这样的技术,aop 已经对其提供了完美的实现!

什么是AOP?

Aspect Oriented Programing 面向切面编程,相比较 oop 面向对象编程来说,Aop 关注的不再是程序代码中某个类,某些方法,而 aop 考虑的更多的是一种面到面的切入,即层与层之间的一种切入,所以称之为切面。联想大家吃的汉堡(中间夹肉)。那么 aop 是怎么做到拦截整个面的功能呢?考虑前面学到的 servlet filter /* 的配置 ,实际上也是 aop 的实现。

AOP能做什么?

AOP 主要应用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能性的重复使用。

AOP的特点

  1. 降低模块与模块之间的耦合度,提高业务代码的聚合度。(高内聚低耦合)
  2. 提高了代码的复用性
  3. 提高了代码的复用性
  4. 可以在不影响原有的功能基础上添加新的功能

AOP的底层实现

动态代理(JDK + CGLIB)

AOP基本概念

被拦截到的每个点,spring 中指被拦截到的每一个方法,spring aop 一个连接点即代表一个方法的执行。

Pointcut(切入点)

对连接点进行拦截的定义(匹配规则定义 规定拦截哪些方法,对哪些方法进行处理),spring 有专门的表达式语言定义。

Advice(通知)

拦截到每一个连接点即(每一个方法)后所要做的操作

  1. 前置通知 (前置增强)— before() 执行方法前通知
  2. 返回通知(返回增强)— afterReturn 方法正常结束返回后的通知
  3. 异常抛出通知(异常抛出增强)— afetrThrow()
  4. 最终通知 — after 无论方法是否发生异常,均会执行该通知。
  5. 环绕通知 — around 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

Aspect(切面)

切入点与通知的结合,决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面则是横切关注点抽象。

Target(目标对象)

被代理的目标对象

Weave(织入)

将切面应用到目标对象并生成代理对象的这个过程即为织入

Introduction(引入)

在不修改原有应用程序代码的情况下,在程序运行期为类动态添加方法或者字段的过程称为引入

Spring AOP的实现

Spring AOP环境搭建

坐标依赖引入

<!--Spring AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>

添加 spring.xml 的配置

添加命名空间

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

注解实现

定义切面

/**
* 切⾯
* 切⼊点和通知的抽象 (与⾯向对象中的 类 相似)
* 定义 切⼊点和通知 (切⼊点定义了要拦截哪些类的哪些⽅法,通知则定义了拦截过⽅法后要做什么)
*/
@Component // 将对象交给IOC容器去实例化
@Aspect // 声明当前类是⼀个切⾯
public class LogCut {
/**
* 切⼊点:
* 匹配规则。规定什么⽅法被拦截、需要处理什么⽅法
* 定义切⼊点
* @Pointcut("匹配规则")
*
* Aop 切⼊点表达式简介
* 1. 执⾏任意公共⽅法:
* execution(public *(..))
* 2. 执⾏任意的set⽅法
* execution(* set*(..))
* 3. 执⾏com.xxxx.service包下任意类的任意⽅法
* execution(* com.xxxx.service.*.*(..))
* 4. 执⾏com.xxxx.service 包 以及⼦包下任意类的任意⽅法
* execution(* com.xxxx.service..*.*(..))
*
* 注:表达式中的第⼀个* 代表的是⽅法的修饰范围
* 可选值:private、protected、public (* 表示所有范围)
*/
@Pointcut("execution (* com.xxxx.service..*.*(..) )")
public void cut(){}
/**
* 声明前置通知 并将通知应⽤到定义的切⼊点上
* ⽬标类⽅法执⾏前 执⾏该通知
*
*/
@Before(value = "cut()")
public void before() {
System.out.println("前置通知.....");
}
/**
* 声明返回通知 并将通知应⽤到定义的切⼊点上
* ⽬标类⽅法(⽆异常)执⾏后 执⾏该通知
*
*/
@AfterReturning(value = "cut()")
public void afterReturn() {
System.out.println("返回通知.....");
}
/**
* 声明最终通知 并将通知应⽤到定义的切⼊点上
* ⽬标类⽅法(⽆异常或有异常)执⾏后 执⾏该通知
*
*/
@After(value = "cut()")
public void after() {
System.out.println("最终通知.....");
}
/**
* 声明异常通知 并将通知应⽤到定义的切⼊点上
* ⽬标类⽅法出现异常时 执⾏该通知
*/
@AfterThrowing(value="cut()",throwing = "e")
public void afterThrow(Exception e) {
System.out.println("异常通知....." + " 异常原因:" + e.getCause());
}
/**
* 声明环绕通知 并将通知应⽤到切⼊点上
* ⽅法执⾏前后 通过环绕通知定义相应处理
* 需要通过显式调⽤对应的⽅法,否则⽆法访问指定⽅法 (pjp.proceed();)
* @param pjp
* @return
*/
@Around(value = "cut()")
public Object around(ProceedingJoinPoint pjp) {
System.out.println("前置通知...");
Object object = null;
try {
object = pjp.proceed();
System.out.println(pjp.getTarget() + "======" + pjp.getSignature());
// System.out.println("返回通知...");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常通知...");
}
System.out.println("最终通知...");
return object;
}
}

配置文件(spring.xml)

<!--配置AOP代理-->
<aop:aspectj-autoproxy/>

XML实现

定义切面

**
* 切⾯
* 切⼊点和通知的抽象 (与⾯向对象中的 类 相似)
* 定义 切⼊点和通知 (切⼊点定义了要拦截哪些类的哪些⽅法,通知则定义了拦截过⽅法后要做什么)
*/
@Component // 将对象交给IOC容器去实例化
public class LogCut02 {
public void cut(){}
/**
* 声明前置通知 并将通知应⽤到定义的切⼊点上
* ⽬标类⽅法执⾏前 执⾏该通知
*/
public void before() {
System.out.println("前置通知.....");
}
/**
* 声明返回通知 并将通知应⽤到定义的切⼊点上
* ⽬标类⽅法(⽆异常)执⾏后 执⾏该通知
*
*/
public void afterReturn() {
System.out.println("返回通知.....");
}
/**
* 声明最终通知 并将通知应⽤到定义的切⼊点上
* ⽬标类⽅法(⽆异常或有异常)执⾏后 执⾏该通知
*
*/
public void after() {
System.out.println("最终通知.....");
}
/**
* 声明异常通知 并将通知应⽤到定义的切⼊点上
* ⽬标类⽅法出现异常时 执⾏该通知
*/
public void afterThrow(Exception e) {
System.out.println("异常通知....." + " 异常原因:" + e.getCause());
}
/**
* 声明环绕通知 并将通知应⽤到切⼊点上
* ⽅法执⾏前后 通过环绕通知定义相应处理
* 需要通过显式调⽤对应的⽅法,否则⽆法访问指定⽅法 (pjp.proceed();)
* @param pjp
* @return
*/
public Object around(ProceedingJoinPoint pjp) {
System.out.println("前置通知...");
Object object = null;
try {
object = pjp.proceed();
System.out.println(pjp.getTarget() + "======" + pjp.getSignature());
// System.out.println("返回通知...");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常通知...");
}
System.out.println("最终通知...");
return object;
}
}

配置文件(spring.xml)

<!--aop相关配置-->
<aop:config>
<!--aop切⾯-->
<aop:aspect ref="logCut02">
<!-- 定义aop 切⼊点 -->
<aop:pointcut id="cut" expression="execution(* com.xxxx.service..*.*(..))"/>
<!-- 配置前置通知 指定前置通知⽅法名 并引⽤切⼊点定义 -->
<aop:before method="before" pointcut-ref="cut"/>
<!-- 配置返回通知 指定返回通知⽅法名 并引⽤切⼊点定义 -->
<aop:after-returning method="afterReturn" pointcut-ref="cut"/>
<!-- 配置异常通知 指定异常通知⽅法名 并引⽤切⼊点定义 -->
<aop:after-throwing method="afterThrow" throwing="e" pointcut-ref="cut"/>
<!-- 配置最终通知 指定最终通知⽅法名 并引⽤切⼊点定义 -->
<aop:after method="after" pointcut-ref="cut"/>
<!-- 配置环绕通知 指定环绕通知⽅法名 并引⽤切⼊点定义 -->
<aop:around method="around" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>

Spring AOP总结

代理模式实现三要素

  1. 接口定义
  2. 目标对象与代理对象必须实现统一接口
  3. 代理对象持有目标对象的引用,增强目标对象行为

代理模式实现分类以及对应区别

  1. 静态代理:手动为目标对象制作代理对象,即在程序编译阶段完成代理对象的创建
  2. 动态代理:在程序运行期动态创建目标对象对应代理对象。
  3. jdk 动态代理:被代理目标对象必须实现某一或某一组接口实现方式通过回调创建代理对象。
  4. cglib 动态代理:被代理目标对象可以不必实现接口,继承的方式实现

动态代理相比较静态代理,提高开发效率,可以批量化创建代理,提高代码复用率。

Aop 理解

  1. 面向切面,相比 oop 关注的是代码中的层或面
  2. 解耦,提高系统扩展性
  3. 提高代码复用

Aop 关键词

  1. 连接点:每一个方法
  2. 切入点:匹配的方法集合
  3. 切面:连接点与切入点的集合决定了切面,横切关注点的抽象
  4. 通知:几种通知
  5. 目标对象:被代理对象
  6. 织入:程序运行期将切面应用到目标对象并生成代理对象的过程
  7. 引入:在不修改原始代码情况下,在程序运行期为程序动态引入方法或字段的过程

最后

感谢你看到这里,文章有什么不足还请指正,觉得文章对你有帮助的话记得给我点个赞!

面试官:连Spring AOP都说不明白,自己走还是我送你?的更多相关文章

  1. 面试官:“谈谈Spring中都用到了那些设计模式?”。

    我自己总结的Java学习的系统知识点以及面试问题,已经开源,目前已经 41k+ Star.会一直完善下去,欢迎建议和指导,同时也欢迎Star: https://github.com/Snailclim ...

  2. 面试官:spring中定义bean的方法有哪些?我一口气说出了12种,把面试官整懵了。

    前言 在庞大的java体系中,spring有着举足轻重的地位,它给每位开发者带来了极大的便利和惊喜.我们都知道spring是创建和管理bean的工厂,它提供了多种定义bean的方式,能够满足我们日常工 ...

  3. Spring AOP 和 AspectJ

    现如今有许多个可用的 AOP 库,使用这些库需要能够回答以下问题: 是否与现有的或新的应用程序兼容? 在哪里可以使用 AOP ? 如何迅速与应用程序集成? 性能开销是多少? 在本文中,我们将回答这些问 ...

  4. Spring AOP常见面试题

    一.AOP是什么? 与OOP对比,面向切面,传统的OOP开发中的代码逻辑是至上而下的过程中会长生一些横切性问题,这些横切性的问题和我们的主业务逻辑关系不会散落在代码的各个地方,造成难以维护,AOP的编 ...

  5. 面试常问Spring IOC,不得不会。

    广义的 IOC IoC(Inversion of Control) 控制反转,即“不用打电话过来,我们会打给你”. 两种实现: 依赖查找(DL)和依赖注入(DI). IOC 和 DI .DL 的关系( ...

  6. 面试官:说一说Zookeeper中Leader选举机制

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 今天又是一个阳光明媚的一天,我又 ...

  7. spring AOP advice 类型 和 通用的切点的配置方式

    spring aop advice的类型: 1.前置通知(before advice) 2.返回后通知(after returning advice) 3.抛出异常后通知(after throwing ...

  8. Spring 学习——Spring AOP——AOP配置篇Aspect、Pointcut

    Schena——based AOP 声明 Spring所有的切面和通知器都必须放在一个<aop:config>标签内,可以同时配置多个<aop:config>元素. 每一个&l ...

  9. spring Aop概念

    面向切面编程(AOP)通过提供另外一种思考程序结构的途经来弥补面向对象编程(OOP)的不足.在OOP中模块化的关键单元是类(classes),而在AOP中模块化的单元则是切面.切面能对关注点进行模块化 ...

随机推荐

  1. Redis基础——剖析基础数据结构及其用法

    这是一个系列的文章,打算把Redis的基础数据结构.高级数据结构.持久化的方式以及高可用的方式都讲一遍,公众号会比其他的平台提前更新,感兴趣的可以提前关注,「SH的全栈笔记」,下面开始正文. 如果你是 ...

  2. 这是2020年最强Python学习路线,从入门到精通!

    给大家整理的这套python学习路线图,按照此教程一步步的学习来,肯定会对python有更深刻的认识.或许可以喜欢上python这个易学,精简,开源的语言.全民学Python的话题铺天盖地,中国的Py ...

  3. 小白也能看懂的JVM内存区域

    前言 最近在准备面试题刷到了JVM这块,作为一个小白,巩固知识点最好的方式就是亲手写出来并分享:相信我的理解,同样是小白的你,一定有很大的帮助.不信,请你往下看! JVM内存区域简介 如果有人问Jav ...

  4. 十年老苹果(A1286)强升Catalina及Win10踩坑记(续)

    背景 自上次发布十年老苹果(A1286)强升Catalina及Win10踩坑记以来,因为后半部分-----系统安装上的细节描述过于简略,一些朋友在安装过程中总是又遇到坑,由此特意详述这一过程,让园友少 ...

  5. 通过两行代码即可调整苹果电脑 Launchpad 图标大小

    之前用 13 寸 Mac 的时候我还没觉得,后来换了 16 寸就发现有点不对劲了.因为 Mac 的高分辨率,当你进入 Launchpad 界面,应用图标的大小可能会让你怀疑:这特么是苹果的设计吗?有点 ...

  6. 如何使用Python爬取基金数据,并可视化显示

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 以下文章来源于Will的大食堂,作者打饭大叔 前言 美国疫情越来越严峻,大选也进入 ...

  7. Interface 接口详解

    简介 接口主要用来描述类具有哪些功能,并不给出每个功能的具体实现方式.一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了响应接口的对象. 在 Java 程序设计语言中,接口不是类,而是对 ...

  8. src/plugins/Export2Excel.js(目录没有可以重建)

    第一步Install cnpm install file-saver 或者 yarn add file-saver cnpm install xlsx 或者 yarn add xlsx cnpm in ...

  9. VM Linux (Centos)联网

    今天新建一个linux突然发现不能上网,然后百度好多都不行,最后还是解决掉了,这是我的配置方法 鼠标左击虚拟机(或者选中之后直接设置),找到设置选项,打开网络适配器,设为NAT模式 然后进入虚拟机,进 ...

  10. Elasticsearch原理解析与性能调优

    基本概念 定义 一个分布式的实时文档存储,每个字段 可以被索引与搜索 一个分布式实时分析搜索引擎 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据 用途 全文检索 结构化搜索 分 ...