近期一直在准备面试,所以为了巩固知识,也为了梳理,整理了一些java的基础面试题!同时也希望各位英雄和女侠能够补充!不胜荣幸!!!
1.spring是什么?它的优点是什么?
-
它提供了一系列的功能和工具,用于简化Java应用程序的开发,包括依赖注入(Dependency Injection)、面向切面编程(Aspect-Oriented Programming)、事务管理、数据访问、Web开发等
-
优点:
- Spring通过依赖注入来管理对象之间的依赖关系,将对象的创建和组装工作交由框架处理。这样可以降低组件之间的耦合性,使代码更加清晰、灵活和易于维护。
- Spring支持AOP编程,可以将横切关注点(如日志记录、安全验证等)与业务逻辑分开,提高代码的重用性和可维护性
- Spring对于主流的应用框架提供了集成支持
2.spring有哪些模块?
- Spring Context: 继承BeanFactory,提供上下文信息,扩展出JNDI,EJB,电子邮件,国际化等功能
- Spring Core: 框架的最基础部分,提供IOC容器,对bean 进行管理,它的主要的组件就是BeanFactory,是工厂模式的实现
- Spring AOP:集成了所有AOP功能,减弱代码的功能耦 合,清晰的被分离开
- Spring web:提供了基本的面向web的综合特性,提供对常见框架的支持,spring能管理这些框架,将spring对资源注入给框架,也能在这些框架的前后插入拦截器
- Spring MVC: 提供面向web应用的Model-View-Controller
- Spring DAO:提供了JDBC的抽象层,还提供了声明性事务管理方法
- Spring ORM:提供了JPA,JDO,Hibernate,Mybatis等ORM映射层
3.spring用到哪些设计模式?
- 工厂模式:BeanFactory就是简单的工厂模式的体现,用来创建对象的实例(工厂模式主要分为:简单工厂模式、工厂方法模式和抽象工厂模式)
- 单例模式:Bean默认采用单例方式,减少了对象的创建,从而减少了内存的消耗
- 策略模式:Resource的实现类,针对不同的资源文件, 实现了不同方式的资源获取策略
- 代理模式:Spring的AOP功能用到了JAVA的动态代理 以及CGlib动态代理
PS: 静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而SpringAOP则无需特定的编译器处理
- java动态代理:解决代码重复、混杂。基于接口实现,无法实现非接口方法
- Cglib动态代理:代理者是被代理者的子类。优点:解决代码重复、混杂,由于是基于继承实现,可实现非接口方法。缺点:需要导包,相对java代理性能较低
4.具体说一下工厂模式的几个情况?
- 简单工厂模式
简单工厂模式(Simple Factory Pattern):
在简单工厂模式中,有一个工厂类负责创建对象,而客户端通过工厂类来获取所需的对象,而无需关心对象的具体创建过程。
假设有一个披萨店,可以生产不同种类的披萨(比如CheesePizza和PepperoniPizza),我们使用简单工厂模式来实现披萨的生产过程。
首先,定义抽象披萨类和具体披萨类:
// 抽象披萨类
public abstract class Pizza {
public abstract void prepare();
public abstract void bake();
public abstract void cut();
public abstract void box();
}
// 具体披萨类
public class CheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println("准备Cheese披萨的材料");
}
@Override
public void bake() {
System.out.println("烘烤Cheese披萨");
}
@Override
public void cut() {
System.out.println("切割Cheese披萨");
}
@Override
public void box() {
System.out.println("打包Cheese披萨");
}
}
public class PepperoniPizza extends Pizza {
@Override
public void prepare() {
System.out.println("准备Pepperoni披萨的材料");
}
@Override
public void bake() {
System.out.println("烘烤Pepperoni披萨");
}
@Override
public void cut() {
System.out.println("切割Pepperoni披萨");
}
@Override
public void box() {
System.out.println("打包Pepperoni披萨");
}
}
然后,定义披萨工厂类来生产不同种类的披萨:
public class PizzaFactory {
public static Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
return pizza;
}
}
现在,客户端可以通过披萨工厂来获取所需的披萨,而无需关心披萨的具体创建过程:
public class PizzaStore {
public static void main(String[] args) {
Pizza cheesePizza = PizzaFactory.createPizza("cheese");
cheesePizza.prepare();
cheesePizza.bake();
cheesePizza.cut();
cheesePizza.box();
Pizza pepperoniPizza = PizzaFactory.createPizza("pepperoni");
pepperoniPizza.prepare();
pepperoniPizza.bake();
pepperoniPizza.cut();
pepperoniPizza.box();
}
}
输出结果:
1.准备Cheese披萨的材料
2.烘烤Cheese披萨
3.切割Cheese披萨
4.打包Cheese披萨
5.准备Pepperoni披萨的材料
6.烘烤Pepperoni披萨
7.切割Pepperoni披萨
8.打包Pepperoni披萨
通过简单工厂模式,我们将披萨的创建过程封装在PizzaFactory中,客户端只需要通过工厂类来获取披萨对象,而无需知道具体的披萨类
- 工厂方法模式
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它将对象的创建过程推迟到子类中,让子类决定具体要创建的对象。工厂方法模式通过引入抽象工厂和抽象产品来实现,客户端通过调用工厂方法来获取所需的产品对象,而不需要关心产品的具体创建细节。
假设我们有一个披萨店,可以生产不同种类的披萨(比如CheesePizza和PepperoniPizza),我们使用工厂方法模式来实现披萨的生产过程。
首先,定义抽象披萨类和具体披萨类:
// 抽象披萨类
public abstract class Pizza {
public abstract void prepare();
public abstract void bake();
public abstract void cut();
public abstract void box();
}
// 具体披萨类A
public class CheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println("准备Cheese披萨的材料");
}
@Override
public void bake() {
System.out.println("烘烤Cheese披萨");
}
@Override
public void cut() {
System.out.println("切割Cheese披萨");
}
@Override
public void box() {
System.out.println("打包Cheese披萨");
}
}
// 具体披萨类B
public class PepperoniPizza extends Pizza {
@Override
public void prepare() {
System.out.println("准备Pepperoni披萨的材料");
}
@Override
public void bake() {
System.out.println("烘烤Pepperoni披萨");
}
@Override
public void cut() {
System.out.println("切割Pepperoni披萨");
}
@Override
public void box() {
System.out.println("打包Pepperoni披萨");
}
}
然后,定义抽象工厂接口,用于创建披萨:
public interface PizzaFactory {
Pizza createPizza();
}
接着,实现具体的披萨工厂类:
// 具体披萨工厂A
public class CheesePizzaFactory implements PizzaFactory {
@Override
public Pizza createPizza() {
return new CheesePizza();
}
}
// 具体披萨工厂B
public class PepperoniPizzaFactory implements PizzaFactory {
@Override
public Pizza createPizza() {
return new PepperoniPizza();
}
}
现在,客户端可以通过披萨工厂来创建披萨,而无需关心具体披萨类的实现:
public class PizzaStore {
public static void main(String[] args) {
PizzaFactory factoryA = new CheesePizzaFactory();
Pizza pizzaA = factoryA.createPizza();
pizzaA.prepare();
pizzaA.bake();
pizzaA.cut();
pizzaA.box();
PizzaFactory factoryB = new PepperoniPizzaFactory();
Pizza pizzaB = factoryB.createPizza();
pizzaB.prepare();
pizzaB.bake();
pizzaB.cut();
pizzaB.box();
}
}
输出结果:
1.准备Cheese披萨的材料
2.烘烤Cheese披萨
3.切割Cheese披萨
4.打包Cheese披萨
5.准备Pepperoni披萨的材料
6.烘烤Pepperoni披萨
7.切割Pepperoni披萨
8.打包Pepperoni披萨
在上述代码中,我们使用了工厂方法模式来创建披萨。通过抽象工厂接口PizzaFactory和具体工厂类CheesePizzaFactory、PepperoniPizzaFactory,客户端可以使用工厂方法createPizza()来获取披萨对象。这样使得客户端代码与具体披萨类的实现解耦,同时也方便地扩展了新的披萨类和工厂类。工厂方法模式符合开闭原则,使得系统更加灵活和可扩展。
- 抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供一个接口来创建一组相关或相互依赖的对象,而无需指定它们的具体类。抽象工厂模式是工厂方法模式的升级版,它可以创建一族产品,而不仅仅是一个产品。
假设我们有一个图形界面库,它可以在不同的操作系统(比如Windows和Mac)上创建不同风格的按钮和文本框。我们使用抽象工厂模式来实现这个图形界面库。
首先,定义一组相关的抽象产品接口:
// 抽象按钮接口
public interface Button {
void click();
}
// 抽象文本框接口
public interface TextField {
void input();
}
然后,定义抽象工厂接口,用于创建按钮和文本框:
public interface GUIFactory {
Button createButton();
TextField createTextField();
}
接着,实现具体的按钮和文本框类:
// Windows按钮类
public class WindowsButton implements Button {
@Override
public void click() {
System.out.println("Windows按钮被点击");
}
}
// Windows文本框类
public class WindowsTextField implements TextField {
@Override
public void input() {
System.out.println("在Windows文本框中输入文本");
}
}
// Mac按钮类
public class MacButton implements Button {
@Override
public void click() {
System.out.println("Mac按钮被点击");
}
}
// Mac文本框类
public class MacTextField implements TextField {
@Override
public void input() {
System.out.println("在Mac文本框中输入文本");
}
}
现在,分别实现具体的工厂类,用于在不同操作系统上创建不同风格的按钮和文本框:
// Windows工厂类
public class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public TextField createTextField() {
return new WindowsTextField();
}
}
// Mac工厂类
public class MacFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public TextField createTextField() {
return new MacTextField();
}
}
现在,客户端可以通过抽象工厂来创建一组相关的按钮和文本框,而无需关心具体产品的实现:
public class Client {
public static void main(String[] args) {
// 在Windows系统上创建按钮和文本框
GUIFactory windowsFactory = new WindowsFactory();
Button windowsButton = windowsFactory.createButton();
TextField windowsTextField = windowsFactory.createTextField();
windowsButton.click();
windowsTextField.input();
// 在Mac系统上创建按钮和文本框
GUIFactory macFactory = new MacFactory();
Button macButton = macFactory.createButton();
TextField macTextField = macFactory.createTextField();
macButton.click();
macTextField.input();
}
}
输出结果:
1.Windows按钮被点击
2.在Windows文本框中输入文本
3.Mac按钮被点击
4.在Mac文本框中输入文本
在上述代码中,我们使用了抽象工厂模式来创建一组相关的产品(按钮和文本框)。通过抽象工厂接口GUIFactory和具体工厂类WindowsFactory、MacFactory,客户端可以使用工厂方法createButton()和createTextField()来获取按钮和文本框对象。这样使得客户端代码与具体产品的实现解耦,并且可以轻松地在不同操作系统上切换不同的界面风格。抽象工厂模式符合开闭原则,使得系统更加灵活和可扩展。
5.单例bean是单例模式吗?
是的,Spring中的单例Bean是一种应用了单例模式的对象。在Spring中,”单例Bean”表示一个类的实例在整个应用程序中只存在一个,并且由Spring容器负责创建和管理这个唯一的实例。
单例模式是一种常见的设计模式,它保证一个类只有一个实例,并提供一个全局访问点来获取这个实例。Spring中的单例Bean实现了这种设计模式的特性。在默认情况下,Spring会将所有的Bean配置为单例作用域,这意味着每个Bean在容器中只有一个实例,无论有多少次请求获取该Bean,都会返回同一个实例。
例如,如果在Spring配置文件或使用注解时,将一个类标记为单例作用域,那么每次通过Spring容器获取该类的Bean时,都会得到同一个实例,而不会创建新的实例。
<bean id="exampleBean" class="com.example.ExampleBean" scope="singleton"/>
@Component
@Scope("singleton")
public class ExampleBean {
// Bean的定义
}
因此,Spring中的单例Bean体现了单例模式的概念,确保了一个类的实例在整个应用程序中是唯一的,并且由容器负责创建和管理该实例。这样可以节省资源,并确保在整个应用程序中使用相同的实例,避免了重复创建相同对象的开销。
PS:注重点,如果同样的bean你配置的名字不一样,获取的到对象是不一样的。以下面代码例子演示一下
- 这个作为测试对象
package com.shisan.面试题合集.spring;
import org.springframework.stereotype.Component;
// 测试单例bean是否是单例模式
@Component
public class 单例模式 {
}
- 这个为配置单例测试类
package com.shisan.面试题合集.spring;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.shisan.面试题合集.spring")
public class Config {
@Bean
public 单例模式 testBean1() {
return new 单例模式();
}
@Bean
public 单例模式 testBean2() {
return new 单例模式();
}
}
- 这个是测试类
package com.shisan.面试题合集.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Config.class);
Object testBean1 = (单例模式)annotationConfigApplicationContext.getBean("testBean1");
Object testBean11 = (单例模式)annotationConfigApplicationContext.getBean("testBean1");
Object testBean2 = (单例模式)annotationConfigApplicationContext.getBean("testBean2");
System.out.println(testBean1);
System.out.println(testBean11);
System.out.println(testBean2);
}
}
- 返回结果
1.com.shisan.面试题合集.spring.单例模式@b3d7190
2.com.shisan.面试题合集.spring.单例模式@b3d7190
3.com.shisan.面试题合集.spring.单例模式@5fdba6f9
4.
5.Process finished with exit code 0
PS:可以看到,同名的返回对象是同一个,不同名的不是同一个对象
补充点:单例模式的几种情况,单例模式具体需要你了解静态变量这个知识点(既static关键字)。
单例模式有几种实现方式,以下是常见的几种实现方式,包括饿汉式、懒汉式、双重检查锁、静态内部类和枚举类实现:
- 饿汉式(线程安全,但可能导致资源浪费):
public class SingletonHungry {
private static final SingletonHungry INSTANCE = new SingletonHungry();
private SingletonHungry() {
}
public static SingletonHungry getInstance() {
return INSTANCE;
}
}
- 懒汉式(线程不安全):
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {
}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
- 懒汉式(线程安全,使用synchronized同步方法):
public class SingletonLazySync {
private static SingletonLazySync instance;
private SingletonLazySync() {
}
public static synchronized SingletonLazySync getInstance() {
if (instance == null) {
instance = new SingletonLazySync();
}
return instance;
}
}
- 双重检查锁(线程安全,减少锁粒度提高性能):
public class SingletonDoubleChecked {
private static volatile SingletonDoubleChecked instance;
private SingletonDoubleChecked() {
}
public static SingletonDoubleChecked getInstance() {
if (instance == null) {
synchronized (SingletonDoubleChecked.class) {
if (instance == null) {
instance = new SingletonDoubleChecked();
}
}
}
return instance;
}
}
- 静态内部类(线程安全且延迟加载):
public class SingletonStaticInner {
private SingletonStaticInner() {
}
private static class SingletonHolder {
private static final SingletonStaticInner INSTANCE = new SingletonStaticInner();
}
public static SingletonStaticInner getInstance() {
return SingletonHolder.INSTANCE;
}
}
- 枚举类(线程安全且避免反射和序列化破坏单例):
public enum SingletonEnum {
INSTANCE;
// 可以添加其他方法和属性
}
请注意,以上实现方式都可以确保在多线程环境下获取到唯一的实例。然而,实际使用时需要根据具体的场景和需求选择适合的实现方式。每种实现方式都有其优缺点,例如饿汉式可以确保线程安全,但可能在应用启动时就创建实例,造成资源浪费;而懒汉式虽然能实现延迟加载,但在多线程环境下需要考虑线程安全性。静态内部类可以解决饿汉式的问题,但是前提是你的访问修饰符要是私有的,因为私有的静态内部类只有在调用的时候才初始化。
6. 代理模式有哪些,简单的举个代码例子?
代理模式是一种结构型设计模式,它允许通过代理对象控制对另一个对象的访问。代理模式在访问对象时引入了一个代理对象,该代理对象可以作为另一个对象的接口,从而控制对真实对象的访问。代理模式有三种常见的形式:静态代理、动态代理和虚拟代理。下面分别介绍这三种代理模式,并举例说明。
- 静态代理:
静态代理是指在编译阶段就确定了代理类的代码,需要为每个具体业务类创建一个代理类。它通过在代理类中包含一个对真实对象的引用,从而实现对真实对象的代理访问。
// 抽象主题接口
public interface Subject {
void request();
}
// 具体主题类
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// 代理类
public class Proxy implements Subject {
private RealSubject realSubject;
public Proxy() {
realSubject = new RealSubject();
}
@Override
public void request() {
System.out.println("Proxy: Preprocessing request.");
realSubject.request();
System.out.println("Proxy: Postprocessing request.");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy();
proxy.request();
}
}
输出结果:
1.Proxy: Preprocessing request.
2.RealSubject: Handling request.
3.Proxy: Postprocessing request.
- 动态代理:
动态代理是指在运行时动态生成代理类的代码,不需要预先定义具体的代理类。Java提供了
java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现动态代理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 抽象主题接口
public interface Subject {
void request();
}
// 具体主题类
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// InvocationHandler实现类
public class DynamicProxyHandler implements InvocationHandler {
private Object realSubject;
public DynamicProxyHandler(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Dynamic Proxy: Preprocessing request.");
Object result = method.invoke(realSubject, args);
System.out.println("Dynamic Proxy: Postprocessing request.");
return result;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
InvocationHandler handler = new DynamicProxyHandler(realSubject);
Subject proxy = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
handler
);
proxy.request();
}
}
输出结果:
1.Dynamic Proxy: Preprocessing request.
2.RealSubject: Handling request.
3.Dynamic Proxy: Postprocessing request.
- 虚拟代理:
虚拟代理是代理模式的一种形式,它可以用于延迟加载对象或优化资源开销。在虚拟代理中,代理对象只有在真正需要时才会创建和访问真实对象。
下面通过一个简单的图片加载的例子来演示虚拟代理的实现:
首先,定义一个抽象图片接口:
// 抽象图片接口
public interface Image {
void display();
}
然后,实现具体的图片类和虚拟代理类:
// 具体图片类
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading " + filename + " from disk.");
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
// 虚拟代理类
public class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
最后,客户端代码使用虚拟代理来加载图片:
public class Client {
public static void main(String[] args) {
Image image = new ProxyImage("large_image.jpg");
// 第一次显示图片,实际加载图片
image.display();
// 第二次显示图片,不再重新加载图片
image.display();
}
}
输出结果:
1.Loading large_image.jpg from disk.
2.Displaying large_image.jpg
3.Displaying large_image.jpg
在上述代码中,我们使用了虚拟代理来加载图片。在第一次显示图片时,虚拟代理会创建一个真实图片对象并加载图片,然后显示图片。在第二次显示图片时,由于图片已经加载过,虚拟代理直接使用已加载的真实图片对象来显示图片,避免了重复加载图片的开销。
这种虚拟代理的实现方式可以帮助优化资源开销,特别是在处理大型图片或需要耗费较多资源的对象时,可以延迟加载或缓存对象,只有在需要时才实际创建和访问真实对象,从而提高系统性能和资源利用率。
- Cglib动态代理
CGLIB(Code Generation Library)动态代理是Java中另一种实现动态代理的方式,它与Java原生的动态代理相比,具有一些不同的特点。CGLIB动态代理不需要目标对象实现接口,而是通过生成目标类的子类来实现代理。CGLIB在运行时生成目标类的子类,并重写目标类中的方法来实现代理行为。
下面通过一个示例来演示CGLIB动态代理的使用:
首先,需要引入CGLIB库。在Maven项目中可以添加以下依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
然后,创建一个目标类,不需要实现接口:
public class RealSubject {
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
接着,创建CGLIB动态代理类:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB Proxy: Preprocessing request.");
Object result = method.invoke(target, args);
System.out.println("CGLIB Proxy: Postprocessing request.");
return result;
}
}
最后,客户端使用CGLIB动态代理来获取代理对象并调用目标方法:
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
CglibProxy cglibProxy = new CglibProxy(realSubject);
RealSubject proxy = (RealSubject) cglibProxy.getProxy();
proxy.request();
}
}
输出结果:
1.CGLIB Proxy: Preprocessing request.
2.RealSubject: Handling request.
3.CGLIB Proxy: Postprocessing request.
在上述代码中,我们使用CGLIB动态代理创建了目标类RealSubject的代理对象。在CglibProxy类中,我们通过Enhancer来生成目标类RealSubject的子类,并重写了intercept()方法来实现代理行为。客户端通过CglibProxy的getProxy()方法获取代理对象,然后通过该代理对象调用目标方法。
需要注意的是,CGLIB动态代理依赖于CGLIB库,相比Java原生动态代理,它可能会产生更多的字节码和更复杂的代理类,因此性能上可能会有所差异。在使用CGLIB动态代理时,建议仔细考虑应用场景和代理类的复杂度。
7.springIOC的理解?—————————-PS:面试被问 +2
- IOC就是控制反转,把创建对象的控制权转交给spring框架进行管理,并由spring根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松耦合,也利于功能的复用,DI依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖ioc容器来动态注入对象需要的外部依赖
- 最直观的表达就是,以前创建对象的主动权和时机都是自己把控的,ioc让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法
Spring的ioc有三种方式注入:构造器注入,setter方法注入,根据注解注入
8.springAOP的理解?————————-PS:面试被问 +3
- AOP一般被称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为切面(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性,可用于权限认证,日志,事务处理
- AOP的实现关键在于代理模式,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ,动态代理则以spring AOP为代表
- springAOP中的动态代理有两种情况:JDK代理和CGLIB代理,有接口的情况使用JDK动态代理,没有接口的情况,使用CGLIB动态代理
- 总结:将程序中的交叉业务逻辑(比如:事务,安全,日志等),封装成一个切面,然后注入到目标对象即具体业务中,AOP可以对某个对象或者某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前或之后做一些额外的事情。
PS:简单来说,AspectJ是实现AOP的一种技术。AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器),可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易
9.SpringAOP里面几个名词的概念?
-
切面(Aspect):共有功能的实现,如权限切面,日志切面等。通俗理解:指的就是“切面类”,切面类会管理切点和通知
-
通知(Advice):在实际开发中通常是切面类中的一个方法,具体属于哪类通知,通过方法上的注解区分。通俗理解:就是需要增加到业务方法中的公共代码,通常通知有很多种类型可以根据业务的需求来定义是在哪个节点来增加。
-
连接点(JoinPoint):程序在运行过程中能够插入切面的点。通俗理解:指的就是被增强的业务方法
-
切入点(Pointcut):用于定义通知应该切入到哪些点上,不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。通俗理解:切入点就是来定义哪些类里面的哪些方法会得到通知
-
目标对象(Target):那些即将切入切面的对象,也就是那些被通知的对象。这些对象专注业务本身的逻辑,所有的共有功能等待AOP容器的切入。通俗理解:指的就是被增强的对象
-
织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译时、类加载时、运行时。Spring是在运行时完成织入,运行时织入通过Java语言的反射机制与动态代理机制来动态实现
-
代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象本身业务逻辑加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。目标对象被织入共有功能后产生的对象
10.Spring通知(Advice)有哪些类型?——————PS:面试被问 +1
- 前置通知(Before Advice):在目标方法执行之前执行 的通知
- 后置通知(After Advice):在目标方法执行之后执行 的通知
- 环绕通知(Around Advice):在目标方法执行之前和之 后都可以执行额外代码的通知
- 返回后通知(AfterReturning Advice):是在目标方法 执行之后执行的通知
- 抛出异常后通知(AfterThrowing advice):在目标方法 抛出异常时执行的通知
11.Spring容器的启动流程?
-
加载配置文件: Spring 容器在启动时会加载配置文件,其中包含了应用程序的组件定义、依赖关系以及其他配置信息。常见的配置文件可以是 XML 配置文件、Java 配置类或者注解配置。
-
创建容器: Spring 根据配置文件中的信息创建一个应用程序上下文(ApplicationContext),这个上下文就是 Spring 容器。容器是 Spring 中负责对象管理的核心组件,它负责创建、配置和管理应用程序中的所有对象(Bean)。
-
实例化 Bean: 在容器启动过程中,Spring 根据配置信息实例化所有定义的 Bean 对象。根据配置方式的不同(XML、Java 或注解),Spring 可能使用反射或其他方式来创建对象。
-
处理依赖关系: Spring 容器会根据配置信息解析对象之间的依赖关系,确保依赖的对象在被依赖之前被创建。这个过程称为依赖注入(Dependency Injection,DI)。
-
初始化 Bean: 容器在实例化 Bean 后,会调用各个 Bean 的初始化方法(如果有定义的话)。可以通过 init-method 属性指定 Bean 的初始化方法。
-
应用后置处理器: 在容器初始化 Bean 的过程中,Spring 会调用注册的应用后置处理器(BeanPostProcessor)来对 Bean 进行增强。应用后置处理器可以在 Bean 初始化前后做一些额外的操作,比如日志记录、性能监控等。
-
容器就绪: 在完成 Bean 的初始化和依赖注入后,容器就绪,可以开始为应用程序提供服务。此时,应用程序中的 Bean 已经被创建、初始化,并且处于可用状态。
以上步骤描述的是典型的 Spring 容器启动流程。在实际应用中,还可能涉及更多的细节,比如 AOP 的配置、事件处理等。Spring 容器启动后,应用程序就可以通过容器获取所需的 Bean,并开始执行相应的业务逻辑。
12.Beanfactory和applicationcontext有什么区别?
- BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器(看底层源码可以知道,ApplicationContext是BeanFactory的子类)
- 当我们使用ApplicationContext去获取bean的时候,在加载XXX.xml的时候,会创建所有的配置单实例bean
- 当我们使用BeanFactory去获取Bean的时候,我们只是实例化了该容器,而该容器中的bean并没有被实例化。当我们getBean的时候,才会实时实例化该bean对象
- BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而 ApplicationContext则是自动注册
- 区别总结:ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactory,ApplicationContext 唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢
13.Spring Bean的生命周期?——————PS:面试被问 +1
- 简单来说spring bean只有四个阶段:实例化-属性赋值-初始化-销毁
- 但是具体来说,spring bean的生命周期包含如下:
PS:上图为bean的完全生命周期
- 实例化bean:
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean
-
设置对象属性(依赖注入):
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入 -
处理Aware接口:
Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:
如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字
如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例
如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身
如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)传入Spring上下文
-
BeanPostProcessor前置处理:
如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法,去执行一些在实例化这个Bean之前的操作。 -
InitializingBean:
如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。就是在实例化Bean的过程中的一些操作。可以用来定制初始化逻辑! -
init-method:
如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。注解配置:@PostConstruct -
BeanPostProcessor后置处理:
如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;PS:到这里bean就已经正式创建好了 -
DisposableBean:
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法 -
destroy-method:
最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。注解配置:@PreDestroy
14.Spring中bean的作用域?—————————PS:面试被问 +1
-
Singleton:当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对 bean的请求,只要id与该bean定义相匹配,则只会返回 bean的同一实例。Singleton是单例类型,就是在创建起容 器时就同时自动创建了一个bean的对象,不管你是否使 用,他都存在了,每次获取到的对象都是同一个对象。注 意,Singleton作用域是Spring中的缺省作用域
-
Prototype:当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域
PS:主要的两个就是上面的两个,其它三个可看图了解,其实还有几个,用的不多。所以也就不归纳了。可以百度了解
15.Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?
- Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论:
-
对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题
-
对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的
有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的
无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的 -
现在使用的MVC开发中,一般不会存在线程安全的问题,因为把有状态bean和无状态bean区分开来了。
但是如果非要去弄这么一个问题,我们可以采用将Bean的作用域改为prototype,这样就做到了线程之间不共享对象了,即没有线程安全的问题了。还可以使用ThreadLocal来解决问题,ThreadLocal为每个线程保存了一个副本变量,每个线程操作的都是这个副本变量。
16.Spring基于xml注入bean的几种方式?
- 构造函数注入(Constructor Injection):
通过元素在XML配置文件中为Bean的构造函数传递参数。可以指定参数的值、引用其他Bean,甚至混合使用。示例如下:
<bean id="myBean" class="com.example.MyBean">
<constructor-arg value="Hello World"/>
<constructor-arg ref="anotherBean"/>
</bean>
- 属性注入(Property Injection):
通过元素在XML配置文件中为Bean的属性注入值或引用其他Bean。示例如下:
<bean id="myBean" class="com.example.MyBean">
<property name="name" value="John Doe"/>
<property name="age" value="30"/>
<property name="address" ref="addressBean"/>
</bean>
- 集合注入(Collection Injection):
通过、、
<bean id="myBean" class="com.example.MyBean">
<property name="listProperty">
<list>
<value>Item 1</value>
<value>Item 2</value>
</list>
</property>
<property name="mapProperty">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
</bean>
- 自动装配(Autowiring):
通过autowire属性为Bean启用自动装配特性。Spring会自动根据类型或名称在容器中查找匹配的Bean并自动注入。示例如下:
<bean id="myBean" class="com.example.MyBean" autowire="byType"/>
- 继承(Inheritance):
可以使用parent属性来指定一个父Bean,让当前Bean继承父Bean的配置,避免重复定义相同的配置。示例如下:
<bean id="parentBean" class="com.example.ParentBean">
<!-- parentBean properties -->
</bean>
<bean id="childBean" class="com.example.ChildBean" parent="parentBean">
<!-- additional properties for childBean -->
</bean>
以上是一些常见的XML注入Bean的方式。可以根据具体的需求选择合适的方式来配置和注入Bean。
17.Spring如何解决循环依赖问题?
- 先了解一下spring中循环依赖的三种情况
- 构造器注入形成的循环依赖:使用互相谦让的方式解决,在构造器上加入@Lazy注解。(网上都说不饿能解决,我也不太确定我这个算不算一个解决办法)
- 通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题:使用二级缓存和三级缓存解决
- 通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题:目前无法解决
- 详解怎么解决setter方法循环依赖问题(单例模式)
- BeanA的setter依赖了BeanB的实例对象,BeanB的setter又依赖了BeanA的实例对象,此时循环依赖就产生了
- 解决:BeanA首先完成了初始化的第一步(实例化),并且将自己放到了三级缓存中(singletonFactory),此时进行初始化的第二步(属性填充),发现自己依赖对象BeanB,此时就尝试去get BeanB 但是发现BeanB还没有被create,所以走create流程
- BeanB在初始化第一步的时候发现自己依赖了对象BeanA,于是尝试get BeanA,先尝试从一级缓存singletonObject获取,但是这时候肯定没有,因为BeanA还没初始化完,然后尝试去二级缓存earlySingletonObjects中获取,很明显,也没有,最后尝试从三级缓存singletonFactories中获取。由于BeanA通过ObjectFactory将自己提前曝光了。所以BeanB能够通过ObjectFactory.getObject()拿到BeanA对象
- BeanB拿到BeanA对象后顺利的完成了初始化阶段。完全初始化之后将自己放入一级缓存singletonObject中,并且将BeanA放到二级缓存中,移除三级缓存中的BeanA
- 此时返回BeanA中,BeanA此时拿到了BeanB的对像,所以顺利完成初始化。最终BeanA也完成了初始化,将BeanA也添加到一级缓存singletonObject中
PS:当使用@AutoWired注入依赖的时候,spring底层会帮我们解决循环依赖的问题,但是我们需要保证循环依赖的Bean是有无参构造函数的。例子:BeanA和BeanB循环依赖,当BeanA初始化的时候,发现依赖了B,它会去池中寻找,但是肯定是没有的。但是spring会帮我们在二级缓存里面创建两个空的Bean对象,即BeanA和BeanB。然后BeanA就创建完成了。后续BeanB就可以成功创建了。
18.为什么Spring无法解决构造器注入的循环依赖和原型(prototype)对象的循环依赖?
- 我们在上面已经分析过了,Spring解决循环依赖就是通过先缓存一个原生对象,然后发现存在依赖时,先去创建依赖的对象,当依赖的对象发现循环依赖时,就去二级、三级缓冲池去找,而这个时候发现存在,就用原生对象完成属性填充,当被依赖的对象完成最终的实例化后,再完成自己的最终实例化(属性填充)。但是如果我们使用构造器注入的话,在初始化的时候就需要填充依赖,如在创建BeanA类时,构造器需要BeanB类,那将去创建BeanB,在创建BeanB类时又发现需要BeanA类,则又去创建BeanB,从而形成一个环,没办法创建(网上都说不能解决,所以就总结一个问题吧!我上面说的那个方法不知道算不算!)
- 对于”prototype”作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存”prototype”作用域的bean,因此无法提前暴露一个创建中的bean
19.spring自动装配Bean的方式?
- no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean
- byName:会自动在容器上下文中查找和自己set方法后面对应的bean id
- byType:通过参数的数据类型进行自动装配
- constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配
- default:表示默认采用上一级标签的自动装配的取值,如果存在多个配置文件的话,那么每一个配置文件的自动装配方式都是独立的。PS:这个不演示了,主要是这个不能粘贴图片实在是不方便,希望后续能增加这个功能吧
PS:上面的是基于xml文件实现自动装配
20.spring事务什么时候会失效?
- 根本原因:Spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了
- 常见失效情况:
- 发生自调用,类里面使用this调用本类的方法,此时这个this对象不是代理类,而是本身。解决办法:就是使用代理类去调用即可。
- 方法不是public的。@Transaction只能用于public的方法上,否则事务会失效。如果要用在非public方法,可以开启AspectJ代理模式
- 数据库不支持事务,比如开启的数据库引擎本身不支持事务,如:myisam
- 没有被spring管理
- 异常被捕获了(catch),事务就不会回滚
21.spring事务传播机制?
- 分析一下这个事情:所谓事务传播机制,也就是事务在多个方法的调用中是如何传递的。打个比方:方法A是一个事务方法,方法A在执行过程中调用了方法B,那么方法B有没有事务或者方法B对事务的要求不同都会对方法A的事务具体执行造成影响。反之也是,这种影响具体是什么就由来两个方法定义的事务传播方式来决定
- 事务的传播机制:
- REQUIRED:spring默认事务传播类型,如果当前没有事务,则自己新建一个事务,如果当前存在事务,那么就加入这个事务。
- REQUIRES_NEW:不管是否存在事务,都创建一个新的事务,如果存在当前的事务,则挂起事务,新的方法执行完毕后,继续执行老的事务
- NOT_SUPPORTED:Spring不为当前方法开启事务,相当于没有事务;即以非事务方式执行,如果当前存在事务,会挂起事务。
- SUPPORTS:如果存在事务,则加入当前事务,如果没有声明事务,那就不用事务
- NEVER:不适用事务,如果当前存在事务,那么抛出异常
- MANDATORY:如果存在事务,那么加入事务,如果没有事务,那么抛出异常
- NESTED:NESTED传播行为表示在当前事务的嵌套事务中执行,如果当前没有事务,则创建一个新的事务。如果外部事务提交,则嵌套事务也会提交;如果外部事务回滚,则嵌套事务也会回滚,但嵌套事务也可以独立回滚而不影响外部事务
PS:推荐查看
22.spring怎么开启事务 ————————-被问+1
- 基于注解的事务管理:
使用@Transactional注解来开启事务。在Service层或者需要开启事务的方法上加上@Transactional注解,Spring会自动为这些方法添加事务管理。例如:
@Service
public class YourServiceClass {
@Autowired
private YourDaoClass yourDao;
@Transactional
public void yourTransactionalMethod() {
// Your business logic here
}
}
- 基于XML配置的事务管理:
通过XML配置来开启事务管理。在Spring的配置文件中配置事务管理器和事务通知。例如:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="yourTransactionalMethod" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="30" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="yourServicePointcut" expression="execution(* com.example.YourServiceClass.yourTransactionalMethod(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="yourServicePointcut" />
</aop:config>
- 基于Java配置的事务管理:
如果你使用Java配置类来配置Spring,可以通过@EnableTransactionManagement注解开启事务管理,并在需要事务管理的方法上添加@Transactional注解,或者通过编程方式配置事务通知。
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
@Service
public class YourServiceClass {
@Autowired
private YourDaoClass yourDao;
@Transactional
public void yourTransactionalMethod() {
// Your business logic here
}
}
PS:不论使用哪种方式,开启事务前都需要确保已经配置了数据源(DataSource)和事务管理器(PlatformTransactionManager)。事务的配置属性可以根据需求进行调整,比如传播行为(Propagation)、隔离级别(Isolation)、超时时间(Timeout)等,这些属性可以在注解或者XML配置中设置。开启事务后,当方法成功执行,事务会被提交,如果方法发生异常,事务会回滚。
23.spring如何处理事务?
Spring处理事务主要是通过其声明式事务管理来实现的,这可以通过编程式事务管理来补充。声明式事务管理是优先选择的方式,因为它将事务管理代码从业务逻辑中分离出来,使得业务逻辑更加清晰,同时也减少了编码工作。Spring声明式事务管理的实现主要依赖于AOP(面向切面编程)。
以下是Spring处理事务的一些关键点:
1. @Transactional
注解
Spring提供了@Transactional
注解,使得声明式事务管理变得非常简单。你可以将此注解添加到类或方法上。当你在一个方法上标注@Transactional
,Spring会在该方法执行之前创建一个事务,并在方法执行后提交事务。如果方法执行期间发生异常,则回滚事务。
@Service
public class YourService {
@Transactional
public void performBusinessLogic() {
// your business logic here
}
}
2. 事务传播行为
Spring支持多种事务传播行为,例如REQUIRED
、REQUIRES_NEW
、SUPPORTS
等,可以通过@Transactional
注解的propagation
属性来指定。
@Transactional(propagation = Propagation.REQUIRED)
public void yourMethod() {
// ...
}
3. 事务隔离级别
Spring还允许你指定事务的隔离级别,它决定了一个事务可能受其他并发事务影响的程度。例如,READ_COMMITTED
、READ_UNCOMMITTED
、REPEATABLE_READ
和SERIALIZABLE
。
@Transactional(isolation = Isolation.READ_COMMITTED)
public void yourMethod() {
// ...
}
4. 编程式事务管理
虽然声明式事务管理更简单和常用,但有时你可能需要更细粒度的控制,这时可以使用编程式事务管理。这通常是通过TransactionTemplate
或直接使用PlatformTransactionManager
来完成的。
5. 底层事务抽象
Spring提供了一个一致的事务抽象层,无论是JDBC、Hibernate、JPA还是其他持久化技术,它都能以相同的方式来管理事务。
6. 事务管理器
Spring支持不同类型的事务管理器,它们对应于不同的持久化技术。例如,DataSourceTransactionManager
用于JDBC,HibernateTransactionManager
用于Hibernate。
配置一个事务管理器的简单示例:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
使用Spring框架进行事务管理时,你需要确保正确配置了事务管理器,并且必要时指定了适当的事务属性。这样,Spring就可以在运行时为你处理事务的创建、提交和回滚。
24.spring中后置处理器的作用?
在Spring框架中,后置处理器(PostProcessor)是一种特殊类型的Bean,它允许在Spring容器实例化和配置Bean之后,对Bean进行额外的自定义处理。后置处理器通过实现相应的接口,并注册到Spring容器中,可以拦截Bean的创建过程,在Bean实例化、属性注入和初始化阶段进行一些自定义操作。
后置处理器的作用在于在Bean的生命周期的特定阶段介入,以实现一些附加功能,比如:
-
Bean的修改和增强:后置处理器可以对Bean的属性进行修改或者在Bean实例化后进行增强,比如使用动态代理为Bean创建代理对象,实现AOP的切面功能。
-
资源清理:在Bean销毁之前,后置处理器可以用于释放资源、关闭连接或者执行一些清理操作。
-
自动装配依赖:后置处理器可以用于处理未被自动装配的依赖关系,自动注入某些属性或者解析占位符。
-
属性值验证和转换:后置处理器可以对Bean的属性进行验证和转换,确保属性值的合法性和正确性。
-
国际化支持:后置处理器可以用于处理多语言环境下的Bean,实现国际化支持。
常见的Spring后置处理器接口包括:
- BeanPostProcessor:对所有的Bean进行后置处理,在Bean实例化、属性注入和初始化前后进行拦截。
- DestructionAwareBeanPostProcessor:扩展了BeanPostProcessor接口,添加了在Bean销毁前进行拦截的功能。
- MergedBeanDefinitionPostProcessor:对合并后的BeanDefinition进行后置处理,通常用于处理父子BeanDefinition合并时的冲突。
PS:你可以通过实现这些接口,并将后置处理器注册到Spring容器中,来实现对Bean生命周期的干预和自定义处理。后置处理器是Spring框架中非常强大和重要的扩展点,通过合理使用后置处理器,可以实现许多复杂的功能和扩展。