springboot
打包
mvn install:install-file -Dfile=<path-to-jar> -DgroupId=<group-id> -DartifactId=<artifact-id> -Dversion=<version> -Dpackaging=jar
<path-to-jar> 是你的 JAR 文件的路径。
<group-id> 是你的项目的组 ID。
<artifact-id> 是你的项目的构件 ID。
<version> 是你的项目的版本号。mvn install:install-file -Dfile=D:\ojdbc6-11.2.0.1.0.jar -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0.1.0 -Dpackaging=jar
AOP
根据目标对象是否实现接口自动选择动态代理机制。
-
JDK 动态代理:适用于目标对象实现了接口的情况。
JDK 动态代理是 Java 提供的一种动态创建代理对象的机制。它允许在运行时动态地创建一个代理类,该代理类实现了与目标对象相同的接口,并且可以拦截对目标对象方法的调用。通过这种方式,可以在不修改目标对象代码的情况下,对目标对象的方法调用进行增强(如添加日志、事务管理等)。
-
CGLIB 动态代理:适用于目标对象没有实现接口的情况。
通过aspect注解定义切面,可以结合自定义注解实现 不改变源代码情况下,在方法执行前后增加功能。
IOC
通过 IoC,Spring 容器负责管理对象的生命周期和依赖关系,而不是由开发者手动管理。
@Repository
:标记一个类为数据访问层组件,通常用于数据库操作。@Qualifier
:用于指定注入的 Bean 的名称,当存在多个同类型的 Bean 时,可以使用该注解来明确指定注入哪一个 Bean。@Primary
:用于指定默认注入的 Bean,当存在多个同类型的 Bean 时,Spring 容器会优先注入标记为@Primary
的 Bean。- @Cacheable` 是 Spring 缓存框架提供的一个注解,用于声明一个方法的结果是可缓存的。当方法被调用时,Spring 会检查缓存中是否已经存在相应的值。如果存在,则直接从缓存中返回结果,而不会执行方法体;如果不存在,则执行方法体并将结果存入缓存,以便下次调用时可以直接从缓存中获取。
- @Transactional: 被标记的方法如果出现**
RuntimeException
和Error
**会回滚事物。- Spring 事务的隔离级别有哪些?
READ_UNCOMMITTED
:最低的隔离级别,允许读取未提交的数据,可能出现脏读。READ_COMMITTED
:允许读取已提交的数据,避免了脏读,但可能出现不可重复读。REPEATABLE_READ
(默认级别):保证在同一个事务中多次读取数据的结果是一致的,避免了不可重复读,但可能出现幻读。SERIALIZABLE
:最高的隔离级别,完全隔离并发事务,避免了脏读、不可重复读和幻读,但性能开销最大。
- 什么是脏读、不可重复读和幻读?
- 脏读:一个事务读取了另一个事务未提交的数据。
- 不可重复读:一个事务在两次读取同一数据时,数据被另一个事务修改,导致两次读取的结果不一致。
- 幻读:一个事务在两次读取同一范围的数据时,数据被另一个事务插入或删除,导致两次读取的结果不一致。
- Spring 事务的隔离级别有哪些?
线程池
线程池的基本概念
- 什么是线程池?
- 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建了线程后,从队列中取出任务并执行。
- 线程池的主要目的是减少线程创建和销毁的开销,提高线程的复用性。
- 线程池的主要优点是什么?
- 减少线程创建和销毁的开销:线程池会复用已创建的线程,减少线程创建和销毁的次数。
- 提高响应速度:任务提交后可以直接从线程池中获取线程执行,减少了线程创建的时间。
- 提高线程的可管理性:线程池可以对线程进行统一管理,如设置线程的最大数量、队列大小等。
- 避免资源耗尽:通过限制线程的最大数量,避免系统资源耗尽。
- 线程池的主要组成部分是什么?
- 线程池管理器:负责创建和管理线程池。
- 工作线程:线程池中实际执行任务的线程。
- 任务队列:用于存储待执行任务的队列。
- 任务接口:所有任务必须实现的接口,以便工作线程可以执行。
线程池的实现原理
- Java 中的线程池是如何实现的?
- Java 的线程池主要通过
java.util.concurrent
包中的Executor
框架实现。 Executor
是一个接口,定义了执行任务的方法。ExecutorService
是Executor
的子接口,提供了更丰富的功能,如线程池的管理。ThreadPoolExecutor
是ExecutorService
的实现类,提供了线程池的核心实现。
- Java 的线程池主要通过
ThreadPoolExecutor
的构造参数有哪些?corePoolSize
:核心线程数,线程池中始终保持的线程数量。maximumPoolSize
:最大线程数,线程池中允许的最大线程数量。keepAliveTime
:非核心线程的空闲存活时间。unit
:keepAliveTime
的时间单位。workQueue
:任务队列,用于存储待执行任务的队列。threadFactory
:线程工厂,用于创建线程。handler
:拒绝策略,当任务队列满且线程数达到最大值时,如何处理新任务。
- 线程池的拒绝策略有哪些?
AbortPolicy
:直接抛出RejectedExecutionException
。CallerRunsPolicy
:由调用线程执行任务。DiscardPolicy
:直接丢弃任务。DiscardOldestPolicy
:丢弃队列中最老的任务,然后尝试提交新任务。
线程池的使用方法
-
如何创建一个线程池?
-
使用
ThreadPoolExecutor
的构造方法创建线程池。 -
示例:
import java.util.concurrent.*;public class ThreadPoolExample {public static void main(String[] args) {ThreadPoolExecutor executor = new ThreadPoolExecutor(2, // 核心线程数4, // 最大线程数60L, // 空闲存活时间TimeUnit.SECONDS, // 时间单位new LinkedBlockingQueue<Runnable>(100) // 任务队列);for (int i = 0; i < 10; i++) {executor.execute(() -> {System.out.println("Task executed by: " + Thread.currentThread().getName());});}executor.shutdown();} }
-
-
如何关闭线程池?
- 使用
shutdown()
方法关闭线程池,等待所有任务完成。 - 使用
shutdownNow()
方法立即关闭线程池,尝试中断正在执行的任务。
- 使用
-
如何提交任务到线程池?
- 使用
execute(Runnable command)
方法提交任务。 - 使用
submit(Callable<T> task)
方法提交任务并返回Future
对象。
- 使用
线程池的性能优化
- 如何优化线程池的性能?
- 合理设置线程池大小:根据系统的硬件资源和任务类型,合理设置核心线程数和最大线程数。
- 选择合适的任务队列:根据任务的特点选择合适的任务队列,如
LinkedBlockingQueue
或ArrayBlockingQueue
。 - 设置合理的拒绝策略:根据业务需求选择合适的拒绝策略,避免任务丢失。
- 监控线程池状态:通过
ThreadPoolExecutor
提供的方法监控线程池的状态,如getActiveCount()
、getCompletedTaskCount()
等。
线程池的高级特性
-
什么是线程池的饱和策略?
- 当任务队列满且线程数达到最大值时,线程池会采取的策略。可以通过
RejectedExecutionHandler
接口自定义饱和策略。
- 当任务队列满且线程数达到最大值时,线程池会采取的策略。可以通过
-
如何自定义线程工厂?
-
实现
ThreadFactory
接口,自定义线程的创建逻辑。 -
示例:
import java.util.concurrent.*;public class CustomThreadFactory implements ThreadFactory {private final String threadNamePrefix;public CustomThreadFactory(String threadNamePrefix) {this.threadNamePrefix = threadNamePrefix;}@Overridepublic Thread newThread(Runnable r) {return new Thread(r, threadNamePrefix + "-Thread-" + Thread.currentThread().getId());}public static void main(String[] args) {ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),new CustomThreadFactory("MyCustomThread"));for (int i = 0; i < 10; i++) {executor.execute(() -> {System.out.println("Task executed by: " + Thread.currentThread().getName());});}executor.shutdown();} }
-
线程池的常见问题
- 线程池中的线程是如何复用的?
- 线程池中的线程在执行完一个任务后,会返回线程池,等待下一个任务。如果线程池中的线程数量超过核心线程数且空闲时间超过
keepAliveTime
,则会销毁多余的线程。
- 线程池中的线程在执行完一个任务后,会返回线程池,等待下一个任务。如果线程池中的线程数量超过核心线程数且空闲时间超过
- 线程池中的线程是如何销毁的?
- 当线程池中的线程数量超过核心线程数且空闲时间超过
keepAliveTime
时,多余的线程会被销毁。 - 当调用
shutdown()
方法时,线程池会等待所有任务完成后再销毁所有线程。 - 当调用
shutdownNow()
方法时,线程池会尝试中断正在执行的任务,并立即销毁所有线程。
- 当线程池中的线程数量超过核心线程数且空闲时间超过
- 线程池中的任务是如何调度的?
- 线程池中的任务通过任务队列进行调度。任务队列可以是阻塞队列(如
LinkedBlockingQueue
、ArrayBlockingQueue
)或非阻塞队列(如SynchronousQueue
)。 - 线程池会从任务队列中取出任务并分配给空闲的线程执行。
- 线程池中的任务通过任务队列进行调度。任务队列可以是阻塞队列(如
线程池的监控
- 如何监控线程池的状态?
- 使用
ThreadPoolExecutor
提供的方法监控线程池的状态,如:getActiveCount()
:获取当前活跃的线程数。getCompletedTaskCount()
:获取已完成的任务数。getTaskCount()
:获取任务总数。getPoolSize()
:获取线程池中的线程数。getLargestPoolSize()
:获取线程池中曾经出现的最大线程数。
- 使用
设计模式
装饰器加适配器
理解:
准备一个接口,两个实现类,实现类一实现基本功能,实现类二中将实现类一注入进去,调用实现类一的方法,在方法前后添加特殊逻辑。
应用场景:
流程里用不用适配器,就看一点:各环节接口对不上,但又得一起干活。 对不上(参数、格式、方法名不一样),又改不了其中一方,就用适配器当“翻译”;能对上,或者能直接改接口,就不用折腾。
示例:
public interface ZSQservice {String ZSQService(String arg0, String arg1);
}
@Service
public class ZSQserviceImpl implements ZSQservice {@Overridepublic String ZSQService(String arg0, String arg1) {System.out.println("ZSQservice impl, arg0=" + arg0 + ", arg1=" + arg1);return "return, arg0=" + arg0 + ", arg1=" + arg1;}
}
@Slf4j
@Service
public class ZSQLOGIMPL implements ZSQservice {private ZSQservice zsqservice;public ZSQLOGIMPL(@Qualifier("ZSQEncryImpl")ZSQservice zsqservice) {this.zsqservice = zsqservice;}@Overridepublic String ZSQService(String arg0, String arg1) {log.info("执行前记录参数 arg0=" + arg0 + ", arg1=" + arg1);String returnJson = zsqservice.ZSQService(arg0, arg1);log.info("执行后记录结果 "+returnJson);return returnJson;}
}
@Slf4j
@Service
public class ZSQEncryImpl implements ZSQservice {private ZSQservice zsqservice;public ZSQEncryImpl(@Qualifier("ZSQserviceImpl") ZSQservice zsqservice) {this.zsqservice = zsqservice;}@Overridepublic String ZSQService(String arg0, String arg1) {log.info("数据加密中");String json = zsqservice.ZSQService(arg0, arg1);log.info("数据加密完成");return json;}
}
@Testvoid zsqTest(){ZSQservice zsQservice = new ZSQLOGIMPL(new ZSQEncryImpl(new ZSQserviceImpl()));zsQservice.ZSQService("1","2");}
: 执行前记录参数 arg0=1, arg1=2
: 数据加密中ZSQservice impl, arg0=1, arg1=2
: 数据加密完成
: 执行后记录结果 return, arg0=1, arg1=2
代理模式
只要装饰器模式不添加特定功能就是代理,也可以看aop是spring自动进行代理的。
AOP加自定义注解
示例:
1. 添加依赖
在 pom.xml
中添加 Spring AOP 的依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 自定义注解
2.1 @LogAction
作用:记录方法的执行日志,包括方法名、入参和出参。
属性:
action
:描述日志内容,可选,默认值为"日志记录"
。
package com.example.annotation;import java.lang.annotation.*;@Target({ElementType.METHOD, ElementType.TYPE}) // 可以标注在方法或类上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
public @interface LogAction {String action() default "日志记录"; // 默认值
}
3. 自定义注解例子
3.1 @LogAction
示例
package com.example.service;import com.example.annotation.LogAction;
import org.springframework.stereotype.Service;