序号 | 内容 |
---|---|
1 | 基础面试题 |
2 | JVM面试题 |
3 | 多线程面试题 |
4 | MySql面试题 |
5 | 集合容器面试题 |
6 | 设计模式面试题 |
7 | 分布式面试题 |
8 | Spring面试题 |
9 | SpringBoot面试题 |
10 | SpringCloud面试题 |
11 | Redis面试题 |
12 | RabbitMQ面试题 |
13 | ES面试题 |
14 | Nginx、Cancal |
15 | Mybatis面试题 |
16 | 消息队列面试题 |
17 | 网络面试题 |
18 | Linux、Kubenetes面试题 |
19 | Netty面试题 |
对设计模式的理解
设计模式的作用
用别人已经实践过的经验,避免我们采坑,提高代码的复用。
设计模式的原则
单一原则:这意味着一个类应该只做一件事,并且只负责一个功能点或业务逻辑。
开闭原则:软件实体对扩展开放,对修改关闭。当应用需求改变的时候,在不修改源码的前提下可以扩展模块的功能,使其满足新的需求。
里氏代换原则:子类应当能够替换其父类并保留原有功能,确保继承的正确使用。
依赖倒转原则:要面向接口编程,不要面向实现编程
接口隔离原则:将接口细化,客户端不应依赖不必要的接口,降低耦合度
迪米特法则:个对象应尽可能少地知道其他对象的细节,降低依赖,增强模块独立性。
合成复用原则:优先使用组合而非继承来实现功能的复用,提高代码灵活性和可维护性。
3、设计模式的分类
设计模式中有一个类型是创建型模式,专门用来做对象的创建,例如单例、原型。
结构型模式,把类或对象结合在一起形成一个更大的结构。例如代理、装饰者、适配器
行为型模式,类和对象如何交互,及划分责任和算法。例如责任链、策略、观察者
单例模式
单例的作用
某个类在软件生命周期只有唯一的一个对象。通过线程同步来控制资源的并发访问,控制实例产生的数量,达到节约资源,
实现方式
饿汉式
在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。缺点是不支持延迟加载实例和占用资源。
//声明一个final的成员变量,私有构造方法,提供一个静态公有方法。
public class Singleton {
private Singleton(){}
private static final Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载(第一次调用的时候再创建,创建完后就不需要在创建了)。多线程情况下,会导致获取到的对象不是初始的那个对象。
public class Singleton {
//成员变量不加载对象,私有构造方法,提供一个静态的获取对象的方法。
private Singleton(){}
private static Singleton instance;
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重检测
双重检测方式既支持延迟加载、又支持高并发的单例实现方式。不加第一个if (uniqueInstance == null) {} 判断,或导致多个请求阻塞到synchronized,影响效率
使用volatile防止指令重新排序。创建实例的步骤是:
1、分配内存空间。2、创建实例对象。3、把这个内存地址的值赋给变量引用。
如果没有防止指令重新排序,先执行到第三步,这个时候另外一个线程再获取到的对象就是空的。或造成对象为空的异常。
public class Singleton {
//加volatile能禁止指令重排。
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
}
return uniqueInstance;
}
静态内部类
当第一次加载 Singleton 类时并不会初始化 instance,只有在第一次调用 Singleton 的 getInstance 方法时才会导致 instance 被初始化。
第一次调用 getInstance 方法时会导致虚拟机加载 Instance 类,这种方式不仅能保证线程安全,也能够保证单例对象唯一,同时也延迟了单例的实例化。
public class Singleton {
private Singleton(){}
private static class Instance {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return Instance.instance;
}
}
枚举
保证了实例创建的线程安全性和实例的唯一性。
public enum Singleton {
INSTANCE;
}
DCL (双重检测)单例模式设计为什么需要 volatile 修饰实例对象
双重检测模式下的单例存在不完整对象的问题。这是因为指令重排导致的。我们使用 new Singleton(); 创建对象时,这个操作不是原子性的。new 的这个段代码最终会被编译成三个指令:为对象分配内存空间、初始化对象、把实例对象复制给instance引用。
根据重新排序规则,在不影响单线程执行结果的情况下,两个不存在依赖关系的指令允许重排。这样就导致其他线程可能拿到一个不完整的对象。也就是instance已经分配了引用实例,但是这个实例初始化的指令还没执行。在instance变量上加volatile,可以根据volatile的内存屏障机制来避免指令重排。
反射可以破解单例,这个怎么办
在反射获取单例对象之前,已经存在了单例对象的前提下。通过反射获取到对应的实例对象,和单例模式下获取到的实例对象不是同一个。反射破解单例是通过反射执行单例类私有的构造方法。可以加判断,判断单例对象不为空的情况下,且方法调用到了私有构造方法。在私有的构造方法中throw 一个异常,这样就无法通过反射去获取一个单例对象了。
以双重检测为例:
private Singleton() {
if (uniqueInstance != null) {
throw new RuntimeException("");
}
}
在反射获取单例对象之前,单例对象还没有被创建出来。可以通过加标识的方法,判断是否已经获取过单例对象。但是反射可以修改flag的值。
可以将flag 改为枚举类型,底层源码中通过反射获取枚举类会抛异常。
private volatile static Singleton uniqueInstance;
private static boolean flag = false;
private Singleton() {
if (flag == false) {
//说明是第一次创建。flag = true 之后就不再是第一次创建了,后面的需要获取
flag = true;
uniqueInstance = this;
} else {
throw new RuntimeException("");
}
}
防止序列和反序列化破解单例
需要在单例类中重写readResolve方法。
//直接加到单例类中
private volatile static Singleton uniqueInstance;
private Object readResolve() {
return uniqueInstance;
}
单例在哪些场景中用到
1、springBean
2、很多工厂类,例如BeanFactory ,SqlSessionFactory,用到了单例
3、保存配置文件的对象。例如springboot 的 xxxxAutoConfiguration
4、数据库连接池。
工厂模式
1、工厂模式的作用就是生产对象的。针对对象的复杂程度,开闭原则。工厂模式分为 简单工厂,工厂方法,抽象工厂。
简单工厂:用来生产同一等级结构中的任意产品。只有一个工厂,不同的产品需要不同额外参数的时候 不支持,扩展性差。
interface Character{
void run();
}
class A implement Character{
@Override
public void run(){sout("AAAAAAAAAA")};
}
class B implement Character{
@Override
public void run(){sout("BBBBBBBBBB")};
}
class CharacterFactory{
public static Character optCharacter(String type) {
if ("A".equals(type){
return new A();
} else if ("B".equals(type){
return new B();
} else {
sout("-----");
}
}
}
工厂方法:用来生产同一等级结构中的固定产品。工厂方法支持扩展性,但是比简单工厂复杂。
public static interface Character{
void run();
}
public static class A implement Character{
@Override
public void run(){sout("AAAAAAAAAA")};
}
public static class B implement Character{
@Override
public void run(){sout("BBBBBBBBBB")};
}
//----------------------------------------
public static interface CharacterFactory(){
public Character createCharacter();
}
public static class AFactory implement CharacterFactory{
@Override
public Character createCharacter(){
return new A();
}
}
public static class BFactory implement CharacterFactory{
@Override
public Character createCharacter(){
return new B();
}
}
抽象工厂:用来生产不同产品簇中的所有产品。对于增加新的产品无能为力,支持增加产品簇。
public static interface Character{
void run();
}
public static class A implement Character{
@Override
public void run(){sout("AAAAAAAAAA")};
}
public static class B implement Character{
@Override
public void run(){sout("BBBBBBBBBB")};
}
public static interface CharacterFactory(){
public Character createCharacter();
}
public static class AFactory implement CharacterFactory{
@Override
public Character createCharacter(){
return new A();
}
}
public static class BFactory implement CharacterFactory{
@Override
public Character createCharacter(){
return new B();
}
}
//----------------------------------------
public static interface CharacterOther{
void run();
}
public static class C implement CharacterOther{
@Override
public void run(){sout("CCCCCCCCCCC")};
}
public static class D implement CharacterOther{
@Override
public void run(){sout("DDDDDDDDDDD")};
}
public static interface CharacterOtherFactory(){
public CharacterOther createCharacterOther();
}
public static class CFactory implement CharacterOtherFactory{
@Override
public CharacterOther createCharacter(){
return new C();
}
}
public static class DFactory implement CharacterOtherFactory{
@Override
public CharacterOther createCharacter(){
return new D();
}
}
//另一个接口,另外两个接口的方法。
public static interface AbstractComponentFactory{
Character createCharacter();
CharacterOther createCharacterOther();
}
//例如A和C为一簇,B和D为一簇。调用的需要分为A、C一个实现,B、D一个实现。
public static class FirstFactory implement AbstractComponentFactory(){
@Override
public Character createCharacter(){
return new AFactory.createCharacter();
}
@Override
public CharacterOther createCharacterOther(){
return new CFactory.createCharacterOther();
}
}
public static class SecondFactory implement AbstractComponentFactory(){
@Override
public Character createCharacter(){
return new BFactory.createCharacter();
}
@Override
public CharacterOther createCharacterOther(){
return new DFactory.createCharacterOther();
}
}
建造者模式
建造者模式的作用是解决创建对象比较复杂的情况。一个对象不能直接被new 出来,
策略模式
什么是策略模式
是一种行为型模式。它将行为和对象分开,将行为定义为一个行为接口和具体行为的实现。
策略模式的优缺点
1、避免了多重 if else
2、算法可以自由切换,扩展性良好
3、策略类活很多
4、客户端必须知道所有的策略类,并自行决定使用哪一个策略类
作用和实现
1、策略模式的作用
策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口 和 具体行为的实现。它可以避免使用多重条件转移语句(多if判断)。
2、实现方式。
//定义一个接口
public interface Character {
void run(String params);
}
//接口实现类,需要交给Spring管理
@Component
public class TestAtype implements Character{
@Override
public void run(String params) {
sout("AAAAAAAAA");
}
}
@Component
public class TestBtype implements Character{
@Override
public void run(String params) {
sout("BBBBBBBBB");
}
}
//创建一个策略管理类,也需要交给Spring管理
@Component
public class StrategyFactory {
//Spring会自动将Strategy接口的实现类注入到这个Map中,key为bean id,value值则为对应的策略实现类。
@Autowired
private Map<String, Character> characterMap;
//type就是接口实现类的类名(testAtype,或者testBtype),params任意参数,可以为空。
public void test(String type,String params){
Character character = Optional.ofNullable(characterMap.get(type)).orElseThrow(() ->
new RuntimeException("类型为空"));
return character.run(params);
}
}
原型模式
原型模式的作用
快速的创建对象。例如 spring 中的Scpoe prototype。
深克隆、浅克隆
浅克隆:只克隆本对象,对对象内部的数组、已用对象都不克隆,克隆对象和被克隆对象都指向原生对象的内部元素地址,如果原生对象的某个基本属性值被改变了,克隆对象的这个基本属性值也会改变。如果克隆对象的基本属性值改变,被克隆对象的基本属性值不会变。(通过实现Cloneable接口)
深克隆:深克隆就是要克隆对象的所有引用的对象都复制一遍。使用序列化和反序列化实现深克隆。
深克隆相比于浅拷贝速度慢并且开销大,但是拷贝前后两个对象互不影响。(通过实现Serilable接口)
代理模式
静态代理
为了对已有的方法的功能进行增强。或者说在不修改原来代码的情况下更灵活的扩展其功能。
实现方法:一个接口,一个接口实现类,接口实现类中有自己的业务功能。另一个是代理类,代理类也实现这个接口,在接口的实现中,通过接口调用已经写好的业务逻辑,在这个已经写好的方法的上面或者下面增加代码,增强已经写好的代码的功能。
缺点:
-
静态代理类和委托类实现了相同的接口,代码重复。
-
静态代理是在编译时(开发阶段)就将代理类完成的。每一个目标类都需要一个代理类,会产生大量代理。
// 定义一个接口
public interface UserService {
void doSomething();
}
// 实现接口的类
public class UserServiceImpl implements UserService {
@Override
public void doSomething() {
System.out.println("UserServiceImpl doing something...");
}
}
// 静态代理类 这个类中的doSomething是为了给UserServiceImpl类中的doSomething功能做增强。
// 也可以控制访问权限、优化系统性能、增强功能、提供稳定网络连接以及保护用户隐私和安全
public class UserServiceStaticProxy implements UserService {
private UserService userService;
public UserServiceStaticProxy(UserService userService) {
this.userService = userService;
}
@Override
public void doSomething() {
System.out.println("Before doSomething method");
userService.doSomething();
System.out.println("After doSomething method");
}
}
动态代理
JDK动态代理
JDK动态代理在代理被执行的时候会生成一个全新的字节码代理类。这个代理类继承了Proxy和实现了被代理业务的接口。通过代码中传参的类加载器,将这个代理类加载到JVM内存中,使这个代理类变成可用类。
代理类在没有被任何对象引用的时候,且生命周期完了之后会被jvm的垃圾回收器回收。
//接口
public interface Subject {
/**
* 接口方法
*/
void doSomething();
/**
* sayHello
*
* @param name name
* @return string
*/
String sayHello(String name);
}
//接口实现类
public class RealSubject implements Subject{
@Override
public void doSomething() {
System.out.println("RealSubject do something");
}
@Override
public String sayHello(String name) {
System.out.println("RealSubject sayHello");
return "hello-" + name;
}
}
//代理工厂
public class JdkDynamicProxyFactory {
/**
* 创建target类的代理对象
* 注意:当调用代理对象中的方法时,其实就是调用的InvocationHandler里面的invoke方法,然后在invoke方法里调用目标对象对应的方法
* @param <T> 泛型
* @return 代理对象
*/
public static <T> T getProxy(Object target) {
// 创建代理实例,分别传入:【加载target类的类加载器、target类实现的接口、InvocationHandler】
Object proxyInstance = 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 result = method.invoke(target, args);
System.out.println("执行目标方法后");
// 返回目标方法的执行结果
return result;
}
});
// 返回代理对象
return (T) proxyInstance;
}
}
//测试类
public static void main(String[] args) {
// 保存生成的代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 目标对象
Subject subject = new RealSubject();
// 使用JDK动态代理为【target对象】创建代理对象
Subject proxy = MyHandler.getProxy(subject);
// 调用代理对象的方法
proxy.doSomething();
System.out.println("----------------------------------------");
proxy.sayHello("test");
}
//生成的代理对象,继承了Proxy类,实现了业务接口Subject
public final class $Proxy0 extends Proxy implements Subject {
// 在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m0;
// 以静态代码块的形式为属性赋值
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.thy.program.test.Subject").getMethod("doSomething");
m4 = Class.forName("com.thy.program.test.Subject").getMethod("sayHello", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
// 这里的supper是指的Proxy类,调用【Proxy类】的【h属性】的invoke方法执行
// 重点:【注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
// 这里也就体现了创建代理对象时为什么需要传入【InvocationHandler】,以及为什么调用代理对象的方法时都是执行的invoke方法
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void doSomething() throws {
try {
// 调用了【Proxy.h属性】的invoke方法
// 注意这里的 super.h 其实就是我们创建代理对象是传入的【InvocationHandler】,
// proxy对象中定义了protected InvocationHandler h;
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String sayHello(String var1) throws {
try {
return (String)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
JdkDynamicProxyFactory的Proxy.newProxyInstance是如何创建代理对象的
1、检查接口,判断传入的参数是否有效。
2、创建代理文件的字节码文件。通过invoke生成,每次调用不同的接口生成不同的文件。
3、将生成的代理文件加载到jvm内存中。
4、类加载完成后,Proxy.newProxyInstance会调用代理的构造方法创建一个新的代理对象。
5、返回创建的这个实例对象
为什么传类加载器
动态代理类则是在【运行过程中动态生成的类】,需要被加载到JVM内存中才能被执行。所以需要传类加载器将生成的代理文件加载到内存
为什么传targt的类接口实现
生成的代理类已经继承了Proxy类,Java中时单继承的,动态代理类还需要实现目标接口,来重写接口方法。所以需要传接口。通过实现这个接口,来重写目标方法。
为什么传InvocationHandler
生成的代理类需要重写目标接口的方法,在重写的方法中需要执行super.h.invoke,super.h.就是只Proxy类的InvocationHandler属性,根据这个对象属性的invoke方法(反射)来重写目标接口的实现。
cglib动态代理
//业务类
public class MyClass {
public void myMethod() {
System.out.println("This is my method.");
}
}
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
//拦截器
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method call.");
Object result = proxy.invokeSuper(obj, args); // 调用父类(即原始类)的方法
System.out.println("After method call.");
return result;
}
}
//调用
public class CGLIBProxyExample {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置父类,即我们要代理的类
enhancer.setSuperclass(MyClass.class);
// 设置回调函数,即代理对象的方法调用时应该执行的操作
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
MyClass proxyInstance = (MyClass) enhancer.create();
// 调用代理对象的方法,这将触发MyMethodInterceptor中的intercept方法
proxyInstance.myMethod();
}
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.thy.program.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class MyClass$$EnhancerByCGLIB$$bdc0f2b9 extends MyClass implements Factory {
...
final void CGLIB$myMethod$0() {
super.myMethod();
}
public final void myMethod() {
//MethodInterceptor 对象是在调用代理的时候Enhancer设置的Callback参数。
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
//最终执行的是自定义的拦截器的MyMethodInterceptor的intercept方法
var10000.intercept(this, CGLIB$myMethod$0$Method, CGLIB$emptyArgs, CGLIB$myMethod$0$Proxy);
} else {
super.myMethod();
}
}
final boolean CGLIB$equals$1(Object var1) {
return super.equals(var1);
}
...
}
jdk动态代理和cglib动态代理的区别
1、jdk动态代理,代理的是接口。cglib动态代理,代理的是类,需要引入cglib第三方类库。
2、dk动态代理是通过代理类Proxy 的InvocationHandler的反射实现动态代理。cglib动态代理是通过目标类生成一个子类的方式来实现动态代理。
3、cglib动态代理是通过生成一个子类,这个代理的子类继承父类,所以父类的方法不能添加final。
装饰器模式
在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能)。装饰器模式的核心是功能扩展,使用装饰器模式可以透明且动态地扩展类的功能。
装饰器模式和代理模式的区别
代理模式是一种保护对象免受意外变化的影响,即控制对另一个对象的访问。
装饰器模式是一种动态地给一个对象添加新的功能,它是一种对象结构型模式,它增加了类的复杂性,但不会增加类的数量。装饰器模式提供了一种动态的、运行时添加新的行为的方式。通过这种方式,可以在运行时改变对象的行为,而不需要修改其源代码。