spring 必知必会
beanfactory 和 factorybean的区别
-
beanfactory 是spring的一个接口,主要是获取bean的方法。其实现类有保存bean的map容器,以及从容器中获取bean的方法。
-
简单来说BeanFactory是对内用的,不面向开发者。FactoryBean是对外用的,面向开发者。BeanFactory是Spring实现IOC的顶层接口,FactoryBean是给开发者自定义bean的规范化接口,让开发者自己new的对象又想交给Spring管理,但是注意这个getObject方法new出来的bean不是真的由spring管理,而是套了一个马甲,Spring直接管理马甲来间接管理new出来的对象。说实话不用FactoryBean也可以让Spring管理。
在实际工作中,FactoryBean通常用于创建复杂对象或者对对象的创建过程进行定制化。以下是一个实际工作中的例子:
假设你正在开发一个企业级应用,其中有一个数据库连接池的管理模块。
首先,定义一个数据库连接池的工厂类实现FactoryBean接口:
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class DataSourceFactoryBean implements FactoryBean<DataSource> {
@Override
public DataSource getObject() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://your-db-url");
dataSource.setUser("username");
dataSource.setPassword("password");
return dataSource;
}
@Override
public Class<?> getObjectType() {
return DataSource.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
在 Spring 配置文件中,可以这样配置:
<bean id="dataSource" class="your.package.DataSourceFactoryBean"/>
这样,当 Spring 容器启动时,会调用DataSourceFactoryBean的getObject方法来创建数据库连接池对象,并将其注入到需要DataSource的地方。
spring IOC的构建流程
1、spring的IOC是控制反转,把创建对象的过程交给spring容器。
2、核心构建过程也就是application的refresh方法,首先加载xml配置解析成beanDefination,并把对象存放在beanfactory的map中。
3、通过BeanFactoryPostProcessor进行修改;接着实例化Bean,并处理Aware接口;
4、在初始化前后,分别调用BeanPostProcessor进行处理;初始化Bean,包括调用afterPropertiesSet和自定义的init-method;
最后,Bean准备好供使用。当容器关闭时,对于singleton作用域的Bean,Spring会调用销毁方法,包括destroy和自定义的destroy-method。
JDK 动态代理和 Cglib 代理的区别
1、JDK 动态代理本质上是实现了被代理对象的接口,而Cglib 本质上是继承了被代理对象,覆盖其中的方法。
2、JDK 动态代理只能对实现了接口的类生成代理,Cglib则没有这个限制。但是 Cglib 因为使用继承实现,所以 Cglib 无法代理被final 修饰的方法或类。
3、在调用代理方法上,JDK 是通过反射机制调用,Cglib是通过FastClass机制直接调用。FastClass 简单的理解,就是使用 index 作为入参,可以直接定位到要调用的方法直接进行调用。
4、在性能上,JDK1.7 之前,由于使用了 FastClass 机制,Cglib在执行效率上比 JDK 快,但是随着 JDK 动态代理的不断优化,从JDK 1.7 开始,JDK 动态代理已经明显比 Cglib 更快了
BeanFactoryPostProcessor在实际工作中如何使用?
1、 根据不同环境,设置不同的数据源。
假设在一个多环境的应用中,需要根据不同的环境动态设置数据源的连接信息。可以通过实现BeanFactoryPostProcessor来读取环境变量或特定配置文件中的数据源配置信息,并修改数据源 bean 的配置。
例如:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
@Component
public class DataSourceConfigPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 假设从环境变量中读取数据库连接信息
String url = System.getenv("DATABASE_URL");
String username = System.getenv("DATABASE_USERNAME");
String password = System.getenv("DATABASE_PASSWORD");
DataSource dataSource = DataSourceBuilder.create()
.url(url)
.username(username)
.password(password)
.build();
// 将自定义的数据源替换掉原来可能存在的数据源 bean
beanFactory.registerSingleton("dataSource", dataSource);
}
}
2、实现自定义注解
对字段进行翻译,例如数据库返回的部门编号,要把部门编号翻译成中文。
使用BeanFactoryPostProcessor和@Aspect来实现自定义注解以翻译某些字段的
一、使用BeanFactoryPostProcessor
自定义注解@TranslateField,用于标记需要翻译的字段。
- 定义自定义注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TranslateField {
}
- 定义一个带有可翻译字段的类:
public class User {
@TranslateField
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 创建
BeanFactoryPostProcessor实现类来处理注解:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
@Component
public class TranslationPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
Object bean = beanFactory.getBean(beanName);
Class<?> beanClass = bean.getClass();
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(TranslateField.class)) {
try {
field.setAccessible(true);
String originalValue = (String) field.get(bean);
// 在这里进行翻译逻辑,假设简单地添加"Translated: "前缀
String translatedValue = "Translated: " + originalValue;
field.set(bean, translatedValue);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
二、使用@Aspect
-
定义自定义注解(同上)。
-
定义带有可翻译字段的类(同上)。
-
创建切面类:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.FieldSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
@Aspect
@Component
public class TranslationAspect {
@Before("@annotation(com.example.demo.annotation.TranslateField)")
public void translateFields(JoinPoint joinPoint) {
Object targetObject = joinPoint.getTarget();
Field[] fields = targetObject.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(TranslateField.class)) {
try {
field.setAccessible(true);
String originalValue = (String) field.get(targetObject);
// 进行翻译逻辑,假设简单地添加"Translated: "前缀
String translatedValue = "Translated: " + originalValue;
field.set(targetObject, translatedValue);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
JDK 动态代理和 Cglib 代理的区别
1、JDK 动态代理本质上是实现了被代理对象的接口,而Cglib 本质上是继承了被代理对象,覆盖其中的方法。
2、JDK 动态代理只能对实现了接口的类生成代理,Cglib则没有这个限制。但是 Cglib 因为使用继承实现,所以 Cglib 无法代理被final 修饰的方法或类。
3、在调用代理方法上,JDK 是通过反射机制调用,Cglib是通过FastClass机制直接调用。FastClass 简单的理解,就是使用 index 作为入参,可以直接定位到要调用的方法直接进行调用。
4、在性能上,JDK1.7 之前,由于使用了 FastClass 机制,Cglib在执行效率上比 JDK 快,但是随着 JDK 动态代理的不断优化,从JDK 1.7 开始,JDK 动态代理已经明显比 Cglib 更快了。
spring如何解决循环依赖
Spring 是通过提前暴露 bean 的引用来解决的,具体如下。
Spring 首先使用构造函数创建一个 “不完整” 的bean 实例(之所以说不完整,是因为此时该 bean 实例还未初始化),并且提前曝光该 bean 实例的ObjectFactory(提前曝光就是将 ObjectFactory 放到 singletonFactories 缓存).
通过ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的ObjectFactory 来拿到 bean 实例,从而避免出现循环引用导致的死循环。
举个例子:A依赖了 B,B 也依赖了 A,那么依赖注入过程如下。
- 检查 A 是否在缓存中,发现不存在,进行实例化
- 通过构造函数创建 bean A,并通过ObjectFactory 提前曝光 bean A
- A 走到属性填充阶段,发现依赖了 B,所以开始实例化 B。
- 首先检查 B 是否在缓存中,发现不存在,进行实例化
- 通过构造函数创建 bean B,并通过ObjectFactory 曝光创建的 bean B
- B 走到属性填充阶段,发现依赖了 A,所以开始实例化 A。
- 检查 A 是否在缓存中,发现存在,拿到 A 对应的 ObjectFactory 来获得bean A,并返回。
- B 继续接下来的流程,直至创建完毕,然后返回 A 的创建流程,A 同样继续接下来的流程,直至创建完毕。
这边通过缓存中的 ObjectFactory 拿到的 bean 实例虽然拿到的是 “不完整” 的 bean 实例,但是由于是单例,所以后续初始化完成后,该 bean 实例的引用地址并不会变,所以最终我们看到的还是完整bean 实例。
Spring 三级缓存
Spring 的三级缓存其实就是解决循环依赖时所用到的三个缓存。
singletonObjects:正常情况下的 bean 被创建完毕后会被放到该缓存,key:beanName,value:bean实例。
singletonFactories:上面说的提前曝光的 ObjectFactory 就会被放到该缓存中,key:beanName,value:ObjectFactory。
earlySingletonObjects:该缓存用于存放 ObjectFactory 返回的bean,也就是说对于一个 bean,ObjectFactory 只会被用一次,之后就通过 earlySingletonObjects 来获取,key:beanName,早期bean 实例。
Spring 能解决构造函数循环依赖吗(6分)
NO,对于使用构造函数注入产生的循环依赖,Spring 会直接抛异常。
为什么无法解决构造函数循环依赖?
上面解决逻辑的第一句话:“首先使用构造函数创建一个 “不完整” 的 bean 实例”,从这句话可以看出,构造函数循环依赖是无法解决的,因为当构造函数出现循环依赖,我
们连 “不完整” 的 bean 实例都构建不出来。
Spring 常用注解解析
-
@Component 和 @Bean 的区别:
- 作用对象:@Component注解作用于类,而@Bean注解作用于方法。
- 自动装配:@Component通过路径扫描自动侦测及装配到Spring容器中,而@Bean则需要手动定义bean的创建过程。
- 自定义性:@Bean注解比@Component注解具有更强的自定义性,特别是在引用第三方库类时只能通过@Bean实现。
-
@Autowired 和 @Resource 的区别:
- 装配方式:@Autowired默认按类型装配,而@Resource默认按名称装配。
- null 值处理:@Autowired可以设置required属性为false以允许null值,@Resource没有直接设置null值的属性。
- 配合使用:@Autowired与@Qualifier配合使用可以达到@Resource相同的效果。
- 装配顺序:@Resource在指定name和type时,按照特定顺序查找bean。
-
Spring Bean 的声明注解:
- @Component:通用的Spring组件注解,可用于任意类。
- @Repository:用于持久层,主要与数据库操作相关。
- @Service:对应服务层,设计复杂逻辑并调用Dao层。
- @Controller:用于MVC的控制层,接收请求并返回数据给前端。
- @Configuration:声明配置类,可在其中定义@Bean方法。
-
@Configuration 的使用与约束:
- 配置类定义:通过@Configuration声明配置类,并在其中定义@Bean方法。
- 注册配置类:可使用AnnotationConfigApplicationContext或组件扫描来注册配置类。
- 约束:配置类必须是类、非final、非本地且静态嵌套配置类需声明为static。
-
@ControllerAdvice 处理全局异常:
- 功能:用于定义全局异常处理器、数据绑定和初始化。
- 选择器:通过annotations、basePackageClasses或basePackages定义应用范围。
- 异常处理:结合@ExceptionHandler用于捕获自定义异常并处理,可自定义返回状态码。
-
@Component, @Repository, @Service 的区别:
- 通用与特定:@Component是通用注解,而@Repository、@Service、@Controller是针对不同使用场景的特定注解。
- 功能差异:特定注解除了提供组件功能外,还附加了与层相关的特性。
- 选择建议:在业务层不确定使用哪个注解时,@Service是更好的选择。

浙公网安备 33010602011771号