基础相关

java有哪些基础类型

8个 int long byte float boolean double char short

面向对象是什么

封装:将数据(属性)和操作数据的方法(行为)封装在一起,限制直接访问类的内部数据,只能通过公开的方法进行操作。

继承:子类可以继承父类的属性和方法,重用已有的代码并扩展新功能。

多态:相同的接口可以有不同的实现,可以用同一接口调用不同的对象,执行不同的操作。

重载和重写的区别是什么

重载:

  1. 发生在同一个类中。

  2. 方法名相同,但参数列表不同(参数的个数、类型或顺序不同)。

  3. 返回值类型可以相同也可以不同。

  4. 重载是在编译时根据参数的类型和个数来决定调用哪个方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class OverloadingExample {
    public void method(int num) {
    System.out.println("Integer parameter: " + num);
    }

    public void method(String str) {
    System.out.println("String parameter: " + str);
    }
    }

重写:

  1. 发生在子类和父类之间。
  2. 方法名、参数列表和返回值类型都必须与父类中被重写的方法相同。
  3. 子类重写的方法访问权限不能小于父类被重写方法的访问权限。
  4. 重写的方法不能抛出比父类被重写方法更多的异常。
  5. 重写是在运行时根据对象的实际类型来决定调用哪个方法。
1
2
3
4
5
6
7
8
9
10
11
12
class Parent {
public void method() {
System.out.println("Parent's method");
}
}

class Child extends Parent {
@Override
public void method() {
System.out.println("Child's method");
}
}

==与equals的区别

==比较的是对象的引用地址,而equals()比较的是对象的内容。

String、StringBuffer 和 StringBuilder 的区别

String是不可变的字符串,StringBuffer是安全的可变的字符串,StringBuilder是不安全的可变的字符串

数组排序,及自定义排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Arrays;

public class Java8Sorting {
public static void main(String[] args) {
// 自定义比较器按升序排序整数数组
int[] numbers = {5, 2, 8, 1, 9};
Arrays.sort(numbers, (a, b) -> a - b);
System.out.println("升序排序后的数组: ");
Arrays.stream(numbers).forEach(num -> System.out.print(num + " "));

// 自定义比较器按降序排序字符串数组
String[] words = {"apple", "banana", "cherry"};
Arrays.sort(words, (s1, s2) -> s2.compareTo(s1));
System.out.println("\n 降序排序后的字符串数组: ");
Arrays.stream(words).forEach(word -> System.out.print(word + " "));
}
}

List,Set,Map的区别

在Java中,List、Set 和 Map 是三种不同的集合类型,它们各自有独特的特性和用法。

List

特点:

有序性:List 中的元素是按插入顺序保存的,可以通过索引访问元素。

允许重复:List 可以包含重复的元素。

常用实现:ArrayList、LinkedList、Vector

常见操作:

添加元素:add(E e)

获取元素:get(int index)

删除元素:remove(int index)

获取大小:size()

Set

特点:

无序性:Set 中的元素没有固定顺序。

不允许重复:Set 不允许包含重复的元素。

常用实现:HashSet、LinkedHashSet、TreeSet

常见操作:

添加元素:add(E e)

删除元素:remove(Object o)

判断是否包含:contains(Object o)

获取大小:size()

Map

特点:

键值对存储:Map 用键值对(key-value)来存储元素。

键不允许重复:Map 中的键必须是唯一的,但值可以重复。

常用实现:HashMap、LinkedHashMap、TreeMap

常见操作:

添加键值对:put(K key, V value)

获取值:get(Object key)

删除键值对:remove(Object key)

判断是否包含键:containsKey(Object key)

获取所有键:keySet()

获取所有值:values()

List分为哪几种

在Java中,List是一个接口,它是Collection接口的子接口,表示一个有序的集合,允许重复元素。List有多种实现类,常用的包括ArrayList、LinkedList和Vector。

ArrayList:基于动态数组实现的List,它可以自动增长以容纳新元素,并且支持随机访问。插入和删除元素的效率比较低,如果需要频繁的插入和删除操作,建议使用LinkedList。

LinkedList:基于双向链表实现的List,插入和删除元素的效率比ArrayList高,特别是在列表的中间位置。不支持随机访问,需要通过遍历来访问元素,访问速度较慢。

Vector:类似于ArrayList,但它是线程安全的,所有方法都使用了synchronized关键字进行同步,因此性能相对较低。通常不推荐使用Vector,因为它的性能较差,而且在大多数情况下并不需要线程安全的List。

Map怎么循环遍历

在 Java 中,对于 Map 的遍历,常见的有以下几种方式:

使用 for 循环结合 entrySet :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.HashMap;
import java.util.Map;

public class MapTraversalExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("orange", 7);

for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + ": " + value);
}
}
}

使用 forEach 方法结合 lambda 表达式 :

1
map.forEach((key, value) -> System.out.println(key + ": " + value));

使用 keySet 遍历键,然后通过键获取值 :

1
2
3
4
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + ": " + value);
}

多线程相关

什么是线程和进程

进程是程序在计算机上的一次执行活动,线程是进程中的一个执行单元

线程的几种创建方式

继承Thread类:创建一个类,继承自Thread类,并重写其run()方法,然后创建该类的实例并调用start()方法启动线程。

1
2
3
4
5
6
7
class MyThread extends Thread {
public void run() {
// 线程执行的代码
}
}
MyThread thread = new MyThread();
thread.start();

实现Runnable接口:创建一个类,实现Runnable接口,并实现其run()方法,然后创建该类的实例并传递给Thread类的构造函数,最后调用start()方法启动线程。

1
2
3
4
5
6
7
class MyRunnable implements Runnable {
public void run() {
// 线程执行的代码
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();

实现Callable接口:与Runnable接口类似,但是Callable接口的call()方法可以返回线程执行结果,可以通过Future对象获取。

1
2
3
4
5
6
7
8
9
10
class MyCallable implements Callable<Integer> {
public Integer call() {
// 线程执行的代码
return result;
}
}
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(new MyCallable());
Integer result = future.get();
executor.shutdown();

使用匿名内部类:在创建Thread类的实例时,可以直接使用匿名内部类来重写run()方法。

1
2
3
4
5
6
Thread thread = new Thread(new Runnable() {
public void run() {
// 线程执行的代码
}
});
thread.start();

使用Lambda表达式:在Java 8及以上版本中,可以使用Lambda表达式简化线程的创建。

1
2
3
4
Thread thread = new Thread(() -> {
// 线程执行的代码
});
thread.start();

这些是常见的几种创建线程的方式,每种方式都有其适用的场景和特点,开发者可以根据需求选择合适的方式来创建线程。

线程的几种状态

在Java中,线程有几种不同的状态,主要包括以下几种:

新建:当线程对象被创建但尚未启动时,线程处于新建状态。

就绪:当线程被start()方法启动后,线程处于就绪状态。此时线程已经被分配到了CPU,但是并未开始执行,等待CPU调度执行。

运行:当线程获得CPU资源并开始执行run()方法时,线程处于运行状态。

阻塞:当线程被阻塞时,处于阻塞状态。线程可能会在等待一些条件的满足,比如等待I/O操作完成、等待获取锁或者等待唤醒等。

等待:当线程处于等待状态时,它等待其他线程显式地调用notify()或notifyAll()方法唤醒它,或者等待指定的等待时间到达。

超时等待:当线程调用了具有超时参数的方法(如Thread.sleep()、Object.wait()、Thread.join())时,线程处于超时等待状态。线程在等待指定的时间段后会自动恢复到就绪状态。

终止:线程执行完毕或者因为异常退出了run()方法,线程进入终止状态。

多线程有使用过吗,怎么实现多线程

实现多线程可以通过创建多个线程对象来实现,并在每个线程中执行不同的任务。以下是实现多线程的一般步骤:

创建线程类:创建一个类,继承自Thread类或者实现Runnable接口,并重写其run()方法。在run()方法中编写线程需要执行的任务。

1
2
3
4
5
6
7
8
9
10
11
class MyThread extends Thread {
public void run() {
// 线程执行的代码
}
}

class MyRunnable implements Runnable {
public void run() {
// 线程执行的代码
}
}

创建线程对象:创建多个线程对象,每个线程对象代表一个独立的线程。

1
2
3
4
5
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();

Thread thread3 = new Thread(new MyRunnable());
Thread thread4 = new Thread(new MyRunnable());

启动线程:调用线程对象的start()方法启动线程,使线程进入就绪状态并开始执行run()方法中的代码。

1
2
3
4
thread1.start();
thread2.start();
thread3.start();
thread4.start();

通过上述步骤,可以创建并启动多个线程,每个线程都会执行相应的任务。在实际应用中,可以根据需求创建适当数量的线程,以实现并发执行任务,提高程序的性能和响应速度。需要注意的是,在多线程编程中要注意线程安全性和资源竞争问题,避免出现数据不一致或者死锁等情况。

线程池有使用过吗 ,怎么创建和使用

线程池是一种重用线程的机制,它可以有效地管理和调度线程,减少线程的创建和销毁次数,提高系统的性能和资源利用率。Java提供了java.util.concurrent包来支持线程池的创建和使用。以下是线程池的创建与使用的基本步骤:

创建线程池

可以使用Executors工厂类来创建线程池,常见的线程池类型包括:

FixedThreadPool:固定大小的线程池,线程数量固定,任务队列无界。

CachedThreadPool:可缓存的线程池,线程数量不固定,根据需求动态增加或减少线程。

ScheduledThreadPool:定时执行任务的线程池,可以执行定时任务和周期性任务。

SingleThreadExecutor:单线程的线程池,保证任务按顺序执行。

提交任务给线程池执行

使用execute()方法或submit()方法将任务提交给线程池执行。

关闭线程池

任务执行完毕后,需要关闭线程池以释放资源。可以调用shutdown()方法来优雅地关闭线程池,它会等待所有任务执行完成后再关闭线程池。如果希望立即关闭线程池,可以使用shutdownNow()方法。

相关配置

可以根据需要对线程池进行一些配置,如核心线程数、最大线程数、任务队列大小、线程存活时间等。可以通过ThreadPoolExecutor类的构造函数来自定义线程池的配置。

创建线程池示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.util.concurrent.*;

/**
* @author Chopper
*/
public class ThreadPoolUtil {

/**
* 核心线程数,会一直存活,即使没有任务,线程池也会维护线程的最少数量
*/
private static final int SIZE_CORE_POOL = 5;
/**
* 线程池维护线程的最大数量
*/
private static final int SIZE_MAX_POOL = 10;
/**
* 线程池维护线程所允许的空闲时间
*/
private static final long ALIVE_TIME = 2000;
/**
* 线程缓冲队列
*/
private static final BlockingQueue<Runnable> BQUEUE = new ArrayBlockingQueue<Runnable>(100);
private static final ThreadPoolExecutor POOL = new ThreadPoolExecutor(SIZE_CORE_POOL, SIZE_MAX_POOL, ALIVE_TIME, TimeUnit.MILLISECONDS, BQUEUE,
new ThreadPoolExecutor.CallerRunsPolicy());
/**
* volatile禁止指令重排
*/
public static volatile ThreadPoolExecutor threadPool;

static {
POOL.prestartAllCoreThreads();
}

/**
* 执行方法
*
* @param runnable
*/
public static void execute(Runnable runnable) {
getThreadPool().execute(runnable);
}

/**
* 提交返回值
*
* @param callable
*/
public static <T> Future<T> submit(Callable<T> callable) {
return getThreadPool().submit(callable);
}

/**
* DCL获取线程池
*
* @return 线程池对象
*/
public static ThreadPoolExecutor getThreadPool() {
if (threadPool != null) {
return threadPool;
}
synchronized (ThreadPoolUtil.class) {
if (threadPool == null) {
threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
}
}
return threadPool;
}

public static ThreadPoolExecutor getPool() {
return POOL;
}

public static void main(String[] args) {
System.out.println(POOL.getPoolSize());
}

}

使用线程池示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
void demo() throws InterruptedException {
for (int i = 0; i < 100; i++) {
ThreadPoolUtil.getPool().execute(new SaveSystemLogThread());
Thread.sleep(100);
}

}
private static class SaveSystemLogThread implements Runnable {
@Override
public void run() {
System.out.println(new Date());
}
}

线程池的创建与使用可以大大简化多线程编程,提高了程序的性能和健壮性。同时,合理配置线程池的参数也是优化系统性能的重要手段之一。

数据库相关

数据库引擎有哪些

InnoDB是MySQL的默认存储引擎,它支持事务处理、行级锁定以及外键约束,适用于需要高并发读写和事务处理的场景。

MyISAM是MySQL的另一种存储引擎,它强调快速读取操作,适用于读多写少的场景,如Web开发。

HEAP引擎允许只驻留在内存里的临时表格,适用于临时数据存储和处理。

数据库查询慢优化步骤

创建索引:为经常用于查询的列创建索引,可以加快查询速度。但是要避免过多的索引,因为每个索引都会占用额外的存储空间,并且在插入、更新和删除操作时需要维护索引,会增加系统的负担。

优化查询语句:使用合适的查询语句,避免使用SELECT *,只查询需要的列;避免在WHERE子句中使用函数,会导致索引失效;合理使用JOIN操作,避免全表扫描等。

避免全表扫描:尽量避免在大表上执行全表扫描的操作,可以通过索引或者优化查询语句来减少全表扫描的次数。

优化数据库结构:合理设计数据库的表结构,避免过度范式化或者反范式化,根据实际情况进行合理的优化。可以通过分表、分区等方式来减少单表的数据量。

使用EXPLAIN分析查询:使用EXPLAIN语句分析查询语句的执行计划,可以查看MySQL是如何执行查询的,从而找到可能存在的性能瓶颈,并进行相应的优化。

数据库索引有哪些类型

常见的索引类型有:

  1. 主键索引(Primary Key):用于唯一标识表中的每一行,一张表只能有一个主键索引。
  2. 唯一索引(Unique Index):确保某一列的值不重复,但可以为 NULL(具体取决于数据库的设置)。
  3. 普通索引(Index):可以提高对特定列的查询速度。
  4. 组合索引(Composite Index):基于多个列创建的索引。

数据库视图了解吗

数据库视图是一种虚拟表,它基于一个或多个实际表的数据通过定义查询语句而创建。

视图具有以下几个重要特性和用途:

  1. 数据安全性增强:可以限制用户对敏感数据的访问,只展示特定的列或符合特定条件的数据。
    • 例如,创建一个视图只显示员工表中的姓名和职位,而不显示工资等敏感信息。
  2. 简化复杂查询:将复杂的多表关联和条件查询封装为视图,使后续的查询更简洁直观。
    • 比如,经常需要从多个关联表中获取特定条件的数据,可以创建视图来简化查询语句。
  3. 逻辑独立性:当基础表的结构发生变化时,只要视图的定义不变,基于视图的应用程序无需修改。

创建视图的语法通常如下(以 MySQL 为例):

1
2
3
4
CREATE VIEW view_name AS
SELECT column1, column2,...
FROM table_name
WHERE condition;

使用视图时,就像使用普通表一样进行查询操作。

例如,有一个学生表包含学号、姓名、年龄、成绩等列,创建一个视图只显示成绩优秀(大于 85 分)的学生信息:

1
2
CREATE VIEW excellent_students AS
SELECT * FROM students WHERE score > 85;

之后就可以直接对 excellent_students 视图进行查询,获取成绩优秀学生的相关信息。

需要注意的是,视图通常不支持数据的插入、更新和删除操作,除非满足特定条件。并且,大量使用视图可能会对性能产生一定影响,尤其是复杂视图或频繁更新基础表的情况下。

数据库存储过程了解吗

存储过程是数据库中一组为了完成特定功能的 SQL 语句集合。

存储过程具有以下优点:

  1. 提高性能:存储过程在数据库服务器端预编译和优化,执行效率通常比单独执行多条 SQL 语句更高。
  2. 增强安全性:可以限制用户对某些敏感操作的直接访问,通过存储过程来控制数据的处理逻辑。
  3. 代码复用:可被多个应用程序或用户重复调用,减少重复编写相同逻辑的代码。
  4. 减少网络流量:因为一次调用可以执行多个操作,减少了客户端与服务器之间的交互次数。

创建存储过程的语法(以 MySQL 为例)通常如下:

1
2
3
4
5
6
7
8
DELIMITER //

CREATE PROCEDURE procedure_name(parameter1 data_type, parameter2 data_type,...)
BEGIN
-- SQL 语句块
END//

DELIMITER ;

例如,创建一个简单的存储过程用于计算两个数的和:

1
2
3
4
5
6
7
8
DELIMITER //

CREATE PROCEDURE add_numbers(IN num1 INT, IN num2 INT, OUT result INT)
BEGIN
SET result = num1 + num2;
END//

DELIMITER ;

调用存储过程的方式如下:

1
2
CALL add_numbers(5, 10, @result);
SELECT @result;

在实际应用中,存储过程常用于复杂的数据处理、数据迁移、批量操作等场景。但存储过程也存在一些缺点,如调试和维护相对复杂,移植性可能较差等。在使用时需要根据具体需求权衡其利弊。

数据库自定义函数了解吗

数据库自定义函数是用户在数据库中根据自己的特定需求创建的可复用的代码块。

自定义函数具有以下优点:

  1. 代码复用:可以在多个查询和操作中重复使用相同的逻辑,减少重复编写代码的工作量。
  2. 增强可读性:将复杂的逻辑封装在函数中,使查询语句更清晰易读。
  3. 提高性能:对于频繁执行的复杂计算,使用函数可以提高执行效率。

自定义函数的类型通常包括:

  1. 标量函数:返回单个值,例如计算某个数值的平方根。
  2. 表值函数:可以返回一个表结果集。

以下是一个在 MySQL 中创建标量函数计算两个数之和的示例:

1
2
3
4
5
6
7
8
9
DELIMITER //

CREATE FUNCTION add_numbers(num1 INT, num2 INT)
RETURNS INT
BEGIN
RETURN num1 + num2;
END//

DELIMITER ;

在实际应用中,例如在一个财务数据库中,可以创建一个自定义函数来根据特定规则计算税收金额;在一个库存管理系统中,可以创建函数来检查库存水平是否低于阈值等。

但需要注意的是,过度使用自定义函数可能会导致数据库维护的复杂性增加,并且在某些情况下可能会影响性能,因此应根据实际需求谨慎使用。

在 MySQL 中调用自定义函数可以像使用内置函数一样在查询中直接使用。

假设我们创建了一个名为 add_numbers 的自定义函数用于计算两个数的和,函数定义如下:

1
2
3
4
5
6
7
8
9
DELIMITER //

CREATE FUNCTION add_numbers(num1 INT, num2 INT)
RETURNS INT
BEGIN
RETURN num1 + num2;
END//

DELIMITER ;

要调用这个函数,可以使用以下语句:

1
SELECT add_numbers(5, 10); 

这将返回 15,即 5 和 10 的和。

再比如,如果自定义函数用于处理表中的数据,例如有一个表 numbers 包含列 num1 和 num2 ,可以这样调用函数来计算每一行的两列之和:

1
SELECT add_numbers(num1, num2) AS sum FROM numbers;

通过这种方式,就能够在 MySQL 的查询中灵活运用自定义函数来满足特定的计算需求。

框架相关

SSM是什么,SSH是什么

SSM:Spring、SpringMVC 、MyBatis

SSH:Spring、Struts 、Hibernate

Spring的核心有哪些

DI依赖注入 :把Spring框架创建好的对象注入到使用的地方,我们项目中都是用@AutoWired按照类型注入的方式,直接获取到这个类的对象。

IOC控制反转:控制反转的思想是将程序中各个组件之间的依赖关系交给容器管理,而不是由组件自己进行管理,从而实现组件的松耦合,不用Spring框架的话如果想创建一个对象,就new一个。用了Spring以后就直接把类交给Spring来管理,让Spring给创建对象,Spring就是一个大工厂模式,底层创建对象的方式是通过配置文件+反射的方式

AOP面向切面 :Spring AOP会根据切面定义生成代理类,这些代理类会继承被代理的自标类,并且在目标类的方法执行前、执行后或者抛出异常时,会执行切面定义中指定的增强逻辑。

Spring的注解有哪些

@Controller,@RequestMapping,@PathVariable,@RequestParam,@ResponseBody

SpringMvc的流程

用户发起请求到服务器,前端控制器接收请求,根据请求 URL 借助 HandlerMapping 找到对应 Controller ,再将请求交予 HandlerAdapter ,HandlerAdapter 调用 Controller 方法。Controller 处理请求并返回 ModelAndView ,包含模型数据和视图信息。前端控制器依据 ModelAndView ,通过 ViewResolver 得到具体视图,ViewResolver 返回 View 对象,其渲染模型数据生成 HTML 响应,前端控制器将响应返回给客户端。

@Resource 和@Autowired的区别

  1. 所属包不同:
    • @Resource 是 JSR-250 规范定义的注解。
    • @Autowired 是 Spring 框架特有的注解。
  2. 依赖查找方式:
    • @Resource 首先按名称进行匹配,如果找不到匹配名称的 bean,则按类型进行匹配。
    • @Autowired 主要是按照类型进行自动装配,如果存在多个相同类型的 bean,则还需要结合 @Qualifier 注解指定具体的 bean 名称。
  3. 可设置属性:
    • @Resource 可以通过 name 属性指定要注入的 bean 的名称。
    • @Autowired 一般不直接设置名称属性。
1
2
3
4
5
6
7
// @Resource 示例
@Resource(name = "userServiceImpl")
private UserService userService;

// @Autowired 示例
@Autowired
private UserService userService;

假设我们有一个 UserService 接口和两个实现类 UserServiceImpl1 和 UserServiceImpl2 。

如果使用 @Resource ,并且明确指定了 name 为 userServiceImpl ,那么就会注入名为 userServiceImpl 的 bean 。

如果使用 @Autowired ,如果只有一个 UserService 类型的 bean ,则会自动注入该 bean ;如果有多个,就需要结合 @Qualifier 来指定具体要注入的 bean

总的来说,在实际开发中,可以根据具体的需求和项目的规范来选择使用 @Resource 还是 @Autowired

SpringBoot的启动流程

加载启动类

SpringBoot:应用程序的启动类必须使用@SpringBootApplication注解标记,该注解包含了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解的功能。SpringBoot通过扫描启动类所在的包和子包,自动装配配置类和Bean定义。

加载配置文件

SpringBoot应用程序默认从application.properties或application.yml文件中加载配置属性。

创建spring容器

SpringBoot使用SpringApplication类创建Spring容器,SpringApplication是SpringBoot的核心类,它提供了各种配置和管理Spring应用程序的方法。SpringApplication会创建一个嵌入式的Tomcat服务器。

加载自动配置

SpringBoot通过@EnableAutoConfiguration注解和spring-boot-autoconfigure项目提供了大量的自动配置,根据classpath中的jar包和Bean的装配情况,自动装配相应的Bean。

运行springboot应用程序

当Spring容器准备就绪后SpringBoot就会启动嵌入式的Web服务器并运行应用程序。如果使用的是外部Web服务器,SpringBoot就会将应用程序打包成一个可执行的iar文件,并启动外部Web服务器。

SpringBoot的自动装配

Spring Boot的自动装配主要依赖于以下机制:

@EnableAutoConfiguration 注解:

  • 这个注解通常与@SpringBootApplication一起使用,它的作用是启用Spring Boot的自动配置功能。实际上,@SpringBootApplication包含了@EnableAutoConfiguration。
  • 它会触发Spring Boot的自动配置过程,自动装配所有可能的Bean。

spring.factories 文件:

  • Spring Boot在每个自动配置模块中都会定义一个META-INF/spring.factories文件,文件中列出了自动配置类。
  • Spring Boot会扫描这些配置文件,并将列出的自动配置类加载到Spring上下文中。

条件注解:

  • 自动配置类通常使用条件注解(例如@ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty等)来控制配置的启用。
  • 这些注解的组合决定了自动配置是否生效。例如,只有在特定的类存在时才进行某些Bean的配置,或者只有在某些属性被设置时才启用某个配置。

SpringBean的生命周期

实例化:Spring容器根据配置文件或注解信息实例化Bean对象。可以通过构造函数、工厂方法或反射机制来创建Bean的实例。

属性赋值:Spring容器将Bean的属性值注入到Bean实例中。包括基本类型、引用类型、集合类型等属性的赋值。

初始化前回调:在Bean初始化之前,Spring容器会调用Bean的InitializingBean接口的afterPropertiesSet()方法或自定义的@PostConstruct注解标注的方法进行初始化操作。

初始化:Spring容器执行一些初始化操作,如调用自定义的初始化方法、执行BeanPostProcessor的前置处理器等。这是Bean完成初始化的阶段。

初始化后回调:在Bean初始化之后,Spring容器会调用Bean的BeanPostProcessor接口的postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法,允许用户在Bean初始化前后进行一些额外的操作。

使用:Bean实例被注入到其他Bean中,或者通过Spring容器的getBean()方法获取Bean实例,开始被应用程序使用。

销毁前回调:在Bean销毁之前,Spring容器会调用Bean的DisposableBean接口的destroy()方法或自定义的@PreDestroy注解标注的方法进行清理操作。

销毁:Spring容器执行一些清理操作,如资源释放、关闭连接等。这是Bean完成销毁的阶段。

在这个生命周期过程中,Spring容器负责管理Bean的创建、初始化、使用和销毁等过程,通过BeanPostProcessor接口和相关注解,用户可以对Bean的生命周期进行自定义处理,实现一些额外的逻辑。这种生命周期的管理方式使得Spring框架非常灵活,能够满足不同类型的应用需求。

SpringCloud常用组件有哪些

Spring Cloud是基于Spring Boot的微服务架构开发工具,它提供了一系列组件和解决方案,用于快速构建分布式系统。以下是一些常见的Spring Cloud组件以及简要的使用说明:

Gateway网关

Gateway 是 Spring Cloud 提供的 API 网关解决方案。它的主要功能包括:

  • 路由:将请求路由到具体的微服务。
  • 过滤:在请求进入后和响应返回前对请求和响应进行处理。
  • 负载均衡:与 Spring Cloud LoadBalancer 集成,分发流量到多个服务实例。
  • 安全:提供身份验证和授权功能。
1
2
3
4
5
6
7
8
9
10
spring:
cloud:
gateway:
routes:
- id: service1
uri: lb://SERVICE1
predicates:
- Path=/service1/
filters:
- StripPrefix=1
LoadBalancer负载均衡

LoadBalancer 是一个用于在多个服务实例间分配流量的负载均衡器。它替代了 Netflix Ribbon,提供了更加灵活和易于扩展的负载均衡策略。

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
loadbalancer:
ribbon:
eureka:
enabled: false
clients:
service1:
loadBalancer:
strategy: roundRobin
OpenFeign调用远程服务

OpenFeign 是一个声明式 HTTP 客户端,集成了 Ribbon 和 Hystrix,可以让你使用接口和注解的方式来调用远程服务。

1
2
3
4
5
6
@FeignClient(name = "service1")
public interface Service1Client {

@GetMapping("/endpoint")
String getEndpoint();
}
Sentinel熔断降级

Sentinel 是阿里巴巴开源的一个分布式系统的流量防护组件,主要用于服务的流量控制、熔断降级、系统自适应保护等。

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
datasource:
ds:
nacos:
server-addr: localhost:8848
dataId: sentinel
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
Nacos服务注册

Nacos 是阿里巴巴开源的一个用于动态服务发现、配置管理和服务管理的平台。它可以作为服务注册中心和配置中心。

1
2
3
4
5
6
7
8
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yaml
Seata分布式事务

Seata 是一个分布式事务解决方案,用于保证在分布式系统中的一致性和可靠性。它支持 AT、TCC、SAGA 和 XA 模式的分布式事务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
seata:
enabled: true
application-id: seata-demo
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
enable-degrade: false
config:
type: nacos
nacos:
server-addr: localhost:8848
namespace: seata_namespace
group: SEATA_GROUP
data-id: seataServer.properties
registry:
type: nacos
nacos:
application: seata-server
server-addr: localhost:8848

事务相关

事务有哪些

常用的有spring提供的事务注解 适用于单体数据库项目,还有多数据源事务 DS ,还有分布式事务阿里的 satate 默认是AT模式 记得还有一个XT模式,在使用时需要部署satate服务,在对应的微服务引用,导入maven,使用注解,
在使用的过程在实际上是把多个需要控制的事务放在数据库中记录,事务成功了,删除对应信息,事务处理失败了,回滚对应信息

导致 Spring 事务失效的情况有哪些

1.在一个 Web 应用中,一个服务类中的方法 updateData 被标注为事务方法,但在另一个方法 processData 中调用 updateData 时,由于是自身调用,事务可能不会生效,导致数据更新出现不一致的情况。

2.在一个事务方法中捕获了异常并自行处理而没有抛出,可能会使本应回滚的事务被提交,造成数据错误。

3.如果使用的数据库引擎不支持事务,那么 Spring 事务也无法生效。

4.方法不是 public 修饰的:Spring 事务管理通常只对 public 方法生效。

异常处理有哪些

自定义异常

微信登陆流程

  1. 获取 code:如果用户授权,微信服务器会返回一个临时的 code 给小程序。
  2. 小程序服务器请求:小程序将获取到的 code 发送到自己的服务器。
  3. 服务器向微信服务器换取 session_key 和 openid:小程序服务器使用 code 向微信服务器发起请求,获取 session_key 和用户的唯一标识 openid。
  4. 生成自定义登录态:小程序服务器根据获取到的信息,生成自己的登录态(如 token),用于后续的业务逻辑和接口调用。

微信支付流程

  1. 创建订单:用户在商家的应用或网站中选择商品并提交订单,商家系统生成订单信息,包括订单金额、商品详情、收货地址等。
  2. 统一下单:商家系统将订单信息发送到微信支付系统,请求生成预付订单。微信支付系统返回预付订单信息,其中包含一个重要的参数prepay_id。
  3. 计算签名:商家系统根据微信支付的要求,计算签名(具体计算方法涉及多个参数的排序、拼接、加密等操作)。
  4. 生成客户端支付信息:商家系统使用预付订单信息和签名生成带签名的客户端支付信息。
  5. 返回支付信息:商家将签名和prepay_id等支付信息返回给应用或网站。
  6. 发起微信支付:用户确认支付后,应用通过微信 SDK 发起支付请求,将支付信息传递给微信客户端。
  7. 验证参数:微信系统验证支付参数,包括签名等。
  8. 用户授权:验证成功后,用户在微信客户端输入密码或进行指纹识别等操作以完成支付授权。
  9. 完成购买:微信系统验证授权成功后,完成购买交易。
  10. 异步通知:微信系统会异步通知商家后台支付结果,同时商家后台也会通过查询接口再次确认支付结果。
  11. 客户端提示:微信客户端收到支付结果后,向用户展示支付结果(如扣款成功等提示)。

OA审批流用过哪些引擎,怎么使用

Activiti:

  1. 引入相关依赖:在项目的构建文件(如 Maven 的 pom.xml)中添加 Activiti 的依赖。
  2. 配置数据库:Activiti 通常需要一个数据库来存储流程相关的数据,配置数据库连接信息。
  3. 设计流程模型:使用 BPMN 2.0 规范,使用工具(如 Activiti Modeler)创建流程定义文件(如 .bpmn 文件),定义审批节点、参与者、流转条件等。
  4. 部署流程:将流程定义文件部署到 Activiti 引擎中。
  5. 启动流程实例:通过编程方式启动流程实例,并关联相关的数据。
  6. 处理审批任务:在流程流转到相应的审批节点时,参与者可以通过编程获取待办任务并进行处理,如同意、拒绝等操作。
  7. 监控和管理流程:可以通过 Activiti 提供的接口或管理控制台监控流程的执行状态、查看历史数据等。

Redis相关

redis的应用场景有哪些

一个是验证码的存储 ,一个是字典数据的存储,一个是token的存储,一个是用户信息的存储,还有一个是通过redis实现订单取消的场景

redis的哨兵机制了解吗,怎么使用

Redis 哨兵机制是用于监控 Redis 主从节点的运行状态,并在主节点出现故障时自动进行故障转移的一种机制。

使用 Redis 哨兵机制的一般步骤如下:

  1. 配置哨兵:创建哨兵的配置文件(例如 sentinel.conf),指定要监控的主节点信息,例如主节点的名称、IP 地址、端口,以及哨兵的一些参数,如监控的超时时间、投票数量等。
  2. 启动哨兵:启动多个哨兵进程,通常建议至少启动三个哨兵以提高可靠性。
  3. 监控和故障转移:哨兵会持续监控主节点和从节点的状态。如果主节点不可达,哨兵会根据一定的规则和投票机制,从从节点中选择一个晋升为主节点,并通知其他从节点和客户端新的主节点信息。

例如,假设一个电商系统使用 Redis 存储商品的热门度数据,通过哨兵机制监控 Redis 主从节点。当主节点由于服务器故障宕机时,哨兵会自动将一个从节点提升为主节点,确保系统能够继续正常读取热门度数据,不影响用户的购物体验。

在实际应用中,需要合理配置哨兵的参数,以适应不同的网络环境和系统需求。同时,要确保哨兵进程的稳定运行和监控的准确性。

redis的主从机制了解吗,怎么使用

Redis 的主从机制是一种常见的部署架构,用于实现数据的备份、读写分离以提高系统的性能和可用性。

主从机制的主要特点和作用包括:

  1. 数据备份:从节点会实时复制主节点的数据,提供数据冗余,防止数据丢失。
  2. 读写分离:主节点负责处理写操作,从节点负责处理读操作,从而分担主节点的读压力,提高系统的并发处理能力。

使用 Redis 主从机制的一般步骤如下:

  1. 配置主节点:在 Redis 配置文件(redis.conf)中指定主节点的相关参数,如不进行特别配置,默认就是主节点。
  2. 配置从节点:在从节点的配置文件中添加以下配置指定主节点:
    • slaveof :指定主节点的 IP 地址和端口。
  3. 启动主从节点:分别启动主节点和从节点的 Redis 服务。
  4. 验证主从关系:可以通过 Redis 命令行工具连接到从节点,执行 INFO replication 命令查看主从复制的相关信息,确认主从关系是否建立成功。

例如,在一个高并发的 Web 应用中,将频繁读取但较少修改的数据存储在 Redis 中。可以配置一个主节点用于处理数据的写入,多个从节点用于处理大量的读请求,从而提高系统的整体性能和响应速度。

需要注意的是,在主从架构中,如果主节点出现故障,需要手动将某个从节点提升为主节点以继续提供服务。为了实现更高的可用性,可以结合哨兵机制或 Redis 集群来自动处理故障转移。

mybatis相关

mybtais有几级缓存

MyBatis 通常有两级缓存:

  1. 一级缓存(本地缓存):
    • 它是 SqlSession 级别的缓存,同一个 SqlSession 中执行相同的查询语句,会先从一级缓存中获取数据。
    • 如果在同一个 SqlSession 中对数据进行了修改操作(如插入、更新、删除),则会清空该 SqlSession 的一级缓存。
  2. 二级缓存(全局缓存):
    • 它是基于 namespace(命名空间)级别的缓存。
    • 不同的 SqlSession 之间可以共享二级缓存中的数据。
    • 要启用二级缓存,需要在对应的 Mapper XML 文件中进行配置,并指定缓存的相关属性。

例如,在实际应用中,如果一个系统频繁查询某个表的数据,而这些数据不经常变化,启用 MyBatis 的二级缓存可以提高查询性能,减少对数据库的访问次数。但在使用缓存时,要注意缓存数据的一致性和过期策略,以避免读取到过期或错误的数据。

mybatis的#和$的区别

#是预编译 $是字符替换,正常的开发场景一般使用#,如需要动态的更换表名查询,对应的信息,可以使用$,一般在IDEA打印输出SQL日志的时候可以看到#会被输出为?

mybtais 怎么按数组查询

foreach in

mybtais 常用的关键字有哪些

where ,if ,set,foreach

中间件相关

es是怎么查询的 TODO

rabbitmq是怎么使用的 TODO

java笔试题

多线程怎么让他按顺序执行,如让多个线程执行,打印输出12345678910

在代码中,创建了多个线程,每个线程的执行依赖于前一个线程的完成,通过 join 方法来等待前一个线程结束,从而实现按顺序执行的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SequentialThreadExecution {

public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.print("1");
});

Thread thread2 = new Thread(() -> {
try {
thread1.join();
System.out.print("2");
} catch (InterruptedException e) {
e.printStackTrace();
}
});

Thread thread3 = new Thread(() -> {
try {
thread2.join();
System.out.print("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
});

// 以此类推创建更多线程

thread1.start();
thread2.start();
thread3.start();
// 启动其他线程
}
}

前端相关

JavaScript有哪些数据类型

在 JavaScript 中,主要的数据类型包括以下几种:

  1. 基本数据类型:
    • Number:用于表示数字,包括整数和浮点数。
    • String:表示文本字符串。
    • Boolean:只有两个值,即 true 和 false ,表示真和假。
    • Null:表示一个空值,即不存在的对象或变量。
    • Undefined:表示未定义的变量或未初始化的变量。
    • Symbol(ES6 新增):一种唯一且不可变的数据类型。
  2. 引用数据类型(对象类型):
    • Object:包括普通对象、数组、函数等。
    • Array:用于存储一组有序的数据。
    • Function:表示函数。
1
2
3
4
5
6
7
8
9
10
11
let num = 10; // Number 类型
let str = "Hello"; // String 类型
let isTrue = true; // Boolean 类型
let empty = null; // Null 类型
let notDefined; // Undefined 类型

let sym = Symbol(); // Symbol 类型

let person = { name: "John", age: 30 }; // Object 类型
let numbers = [1, 2, 3]; // Array 类型
function add(a, b) { return a + b; } // Function 类型

JavaScript中什么是闭包

在 JavaScript 中,闭包(Closure)是指一个函数能够访问其外部函数作用域中的变量。

闭包的主要特点和作用包括:

  1. 延长变量的生命周期:即使外部函数已经执行完毕,其内部函数仍然可以访问外部函数中的变量。
  2. 实现私有变量:可以创建只能在特定函数内部访问和修改的变量,模拟私有变量的效果。
  3. 封装和模块化:将相关的功能和数据封装在一个闭包中,避免全局变量的污染。

以下是一个简单的闭包示例:

1
2
3
4
5
6
7
8
9
10
11
12
function outerFunction() {
let outerVariable = "I am from outer function";

function innerFunction() {
console.log(outerVariable);
}

return innerFunction;
}

let closure = outerFunction();
closure();

在上述示例中,innerFunction 就是一个闭包,它能够访问 outerFunction 中的 outerVariable 变量。

再例如:

1
2
3
4
5
6
7
8
9
10
11
12
function counter() {
let count = 0;

return function() {
count++;
console.log(count);
};
}

let increment = counter();
increment();
increment();

在这个例子中,每次调用 increment 函数都会增加并打印 count 的值,这是因为闭包使得 count 的值在函数调用之间得以保留。

闭包在 JavaScript 中是一个强大的特性,但如果使用不当,可能会导致内存泄漏等问题,因此需要谨慎使用。

Vue的生命周期

在 Vue 中,组件具有一系列的生命周期钩子函数,这些钩子函数允许开发者在组件的不同阶段执行特定的操作。

主要的生命周期钩子函数包括:

  1. beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
  2. created:实例已经创建完成,此时组件的数据观测、属性和方法的运算、watch/event 事件回调的配置都已完成,但 DOM 尚未生成,$el 属性还不可用。
  3. beforeMount:在挂载开始之前被调用,相关的 render 函数首次被调用。
  4. mounted:挂载完成,此时组件的 DOM 已经被渲染到页面上,可以进行 DOM 操作。
  5. beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
  6. updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。
  7. beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  8. destroyed:实例销毁后调用,调用后,所有的事件监听器会被移除,子实例也会被销毁。

例如,在 mounted 钩子中,可以发送网络请求获取数据并进行初始化操作。在 beforeDestroy 中,可以清理一些定时器或取消订阅事件。

以下是一个简单的示例展示如何使用生命周期钩子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<div>{{ message }}</div>
</template>

<script>
export default {
data() {
return {
message: 'Hello Vue!'
};
},
beforeCreate() {
console.log('beforeCreate: 实例初始化之前');
},
created() {
console.log('created: 实例创建完成');
},
beforeMount() {
console.log('beforeMount: 挂载开始之前');
},
mounted() {
console.log('mounted: 挂载完成');
},
beforeUpdate() {
console.log('beforeUpdate: 数据更新之前');
},
updated() {
console.log('updated: 数据更新完成');
},
beforeDestroy() {
console.log('beforeDestroy: 实例销毁之前');
},
destroyed() {
console.log('destroyed: 实例销毁完成');
}
};
</script>