面试 Java 框架八股文十问十答第二期
作者:程序员小白条,个人博客
相信看了本文后,对你的面试是有一定帮助的!
⭐点赞⭐收藏⭐不迷路!⭐
1.AOP的术语,以及两种动态代理实现方法,以及它们的区别是什么?
1.连接点:哪些方法可以被增强
2.切入点:真正被增强的方法
3.通知:实际增强的逻辑部分称为通知
4.切面:把通知应用到切入点过程
Spring4版本
(1).正常情况
1.环绕之前通知
2.前置通知Before
3.被增强的方法
4.环绕之后通知
5.After最终通知
6.AfterReturning 后置通知
(2).异常情况
1.环绕之前通知
2.前置通知Before
3.被增强的方法
4.After最终通知
5.AfterThrowing 异常通知
Spring5版本
(1).正常情况
1.环绕之前通知
2.前置通知Before
3.被增强的方法
4.环绕之后通知
5.AfterReturning后置通知
6.After最终通知
(2).异常情况
1.环绕之前通知
2.前置通知Before
3.被增强的方法
4.AfterThrowing异常通知
5.After最终通知
JDK动态代理:在有接口的情况下,创建接口的实现类的代理对象。
CGLIB动态代理:在没有接口的情况下,创建子类的代理对象。
JDK动态代理实现:
创建一个增加类,JDKProxy, 调用Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),要增加的接口数组(实现类),new 实现类的代理对象去implements InvocationHandler 重写invoke方法,method.invoke是真正执行代码的逻辑。
public class JDKProxy {
public static void main(String[] args) {
//创建接口实现类的代理对象
UserDaoImpl userDao = new UserDaoImpl();
Class[] interfaces = {UserDao.class};
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1, 2);
System.out.println("result = " + result);
}
}
class UserDaoProxy implements InvocationHandler {
//1.把创建的是谁的代理对象,把谁传递过来
//有参数构造器
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
//增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前:
System.out.println("方法之前执行...." + method.getName() + ":传递的参数..." + Arrays.toString(args));
//被增强的方法执行
Object res = method.invoke(obj, args);
//方法之后
System.out.println("方法之后执行..." + obj);
return res;
}
}
2.SpringBoot与Spring框架的区别?
Spring Boot是Spring框架的一部分,它是一个用于快速构建基于Spring的应用程序的开发工具。Spring框架本身是一个轻量级的开源Java企业应用程序开发框架,用于构建可移植、可伸缩和易于测试的企业级应用程序。
Spring Boot相对于Spring框架来说有以下几个区别:
- 简化配置:Spring Boot通过自动配置功能,可以根据应用程序的依赖和配置文件自动配置应用程序。因此,开发者不需要手动配置大量的Spring特性和依赖项。
- 内嵌服务器:Spring Boot可以内置Tomcat、Jetty等服务器,使得构建和运行应用程序变得更加简单。
- 快速启动:Spring Boot可以快速启动应用程序,减少了开发和部署过程中的等待时间。
- 简化依赖管理:Spring Boot通过使用Spring的starter模块,简化了对各种第三方库和框架的依赖管理。
- 自动化管理:Spring Boot可以通过Actuator模块,提供对应用程序的运行时度量、监视和管理功能。
3.Spring事务一般使用什么注解?有了解过事务注解失效的场景吗?
- Spring 事务一般使用 @Transactional注解
- 首先我们主要知道 @ Transactional 注解可以用于哪些地方?
- 当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。
- 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
- 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
事务有哪些属性?
propagation属性(事务传播行为)
propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:
Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )
Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )
Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
Propagation.NESTED :和 Propagation.REQUIRED 效果一样。
isolation 属性(事务隔离级别)
isolation :事务的隔离级别,默认值为 Isolation.DEFAULT。
Isolation.DEFAULT:使用底层数据库默认的隔离级别。
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
timeout 属性 (事务超时时间)
timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly 属性 (是否只读)
readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollbackFor 属性(指定要回滚的异常)
rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
noRollbackFor属性(指定不回滚的异常)
noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
4.事务失效的场景有哪些?
1、@Transactional 应用在非 public 修饰的方法上
失效原因:
因为在Spring AOP 代理时,如上图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用
AbstractFallbackTransactionAttributeSource的
computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。
2、@Transactional 注解属性 propagation 设置错误
这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
3、@Transactional 注解属性 rollbackFor 设置错误
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。
4、同一个类中方法调用,导致@Transactional失效
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
//@Transactional
@GetMapping("/test")
private Integer A() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
/**
* B 插入字段为 3的数据
*/
this.insertB();
/**
* A 插入字段为 2的数据
*/
int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert;
}
@Transactional()
public Integer insertB() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("3");
cityInfoDict.setParentCityId(3);
return cityInfoDictMapper.insert(cityInfoDict);
}
5、异常被你的 catch“吃了”导致@Transactional失效
这种情况是最常见的一种@Transactional注解失效场景,
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2);
/**
* A 插入字段为 2的数据
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 插入字段为 3的数据
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?
答案:不能!
会抛出异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的
UnexpectedRollbackException异常。
spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。
在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。
6、被final、static关键字修饰的类或方法
CGLIB是通过生成目标类子类的方式生成代理类的,被final、static修饰后,那么在它的代理类中,就无法重写该方法,而添加事务功能。
5.现在有一个外部的jar包、如何将该jar包交给IOC容器进行管理,并且在项目中使用;
1.使用包扫描 @Component +@ Configuration注解
2.使用@Import 进行引入。
3.@Bean 注解,去set注入或者 @Value 注解,获取配置文件的值。
6.Spring 怎么知道Bean已经装配完毕了?
InitializingBean接口只有一个方法:afterPropertiesSet()。在Spring框架中,当一个bean的所有属性都已经被设置完毕后,这个方法就会被调用。也就是说,这个bean一旦被初始化,Spring就会调用这个方法。我们可以在bean的所有属性被设置后,进行一些自定义的初始化工作。
DisposableBean接口也只有一个方法:destroy()。当Spring容器关闭并销毁bean时,这个方法就会被调用。我们可以在bean被销毁前,进行一些清理工作。
在Spring框架中,你可以使用ApplicationListener接口或者@EventListener注解来监听bean初始化完毕的事件,并在初始化完毕后执行相应的操作。
- 使用ApplicationListener接口:
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 在bean初始化完毕后执行的操作
System.out.println("所有bean初始化完毕");
}
}
在上面的示例中,MyApplicationListener实现了ApplicationListener接口,并重写了onApplicationEvent方法,在该方法中可以编写在bean初始化完毕后需要执行的操作。
- 使用@EventListener注解:
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class MyEventListener {
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
// 在bean初始化完毕后执行的操作
System.out.println("所有bean初始化完毕");
}
}
在上面的示例中,MyEventListener类使用了@Component注解,表示它是一个Spring组件,并使用了@EventListener注解来标记handleContextRefresh方法,该方法会在bean初始化完毕后执行。
无论是使用ApplicationListener接口还是@EventListener注解,当Spring容器初始化完毕后,会触发ContextRefreshedEvent事件,你可以在相应的监听器或方法中编写初始化完毕后需要执行的操作。
7.Spring 的 IOC 用到了 哪些设计模式?
Spring IOC使用了多种设计模式,其中最主要的是依赖注入(Dependency Injection)模式。以下是Spring IOC使用的一些常见设计模式:
- 依赖注入(Dependency Injection):在Spring IOC中,通过将对象之间的依赖关系交由IOC容器来处理,实现了松耦合和高度可测试性。
- 工厂模式(Factory Pattern):在Spring IOC中,BeanFactory就是一个工厂类,用于创建和管理Bean实例。
- 单例模式(Singleton Pattern):在Spring IOC中,默认情况下所有Bean都是单例的,并且可以通过配置控制是否启用单例模式。
- 模板方法模式(Template Method Pattern):在Spring AOP中,通过定义抽象切面类和具体实现类来实现切面逻辑复用。
- 观察者模式(Observer Pattern):在Spring事件机制中,观察者监听某个事件源发生的事件,并进行相应的处理。
- 策略模式(Strategy Pattern):在Spring MVC中,通过定义多个HandlerMapping策略来实现URL到Controller映射。
8.Spring的管理事务的方式有几种?
- 编程式事务:在代码中硬编码(不推荐使用) : 通过 TransactionTemplate的execute或者 TransactionManager的commit和rollback 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
- 声明式事务:在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)