[2025常问知识点]关于spring的ioc和aop?ioc和DI的区别,ioc是怎么实现的?aop是怎么实现的?

一、IOC 和 DI 的区别

1. 概念区分

概念全称定义关系
IOCInversion of Control (控制反转)将对象创建和依赖管理的控制权从程序转移到容器是设计思想
DIDependency Injection (依赖注入)容器通过构造函数、setter或接口等方式注入依赖对象是IOC的实现方式

ioc的好处

1:使用者不用关心引用bean的实现细节,譬如对于A a = new A(c,d,e,f);来说,如果要使用A,那还要把c,d,e,f多个类全都感知一遍,这显然是非常麻烦且不合理的

2:不用创建多个相同的bean导致浪费。

3:Bean的修改使用方无需感知。同样是上面的例子,假如说BeanA需要修改,如果没有IOC的话,所有引用到A的其他bean都需要感知这个逻辑,并且做对应的修改。但是如果使用了IOC,其他bean就完全不用感知到

对于Spring的IOC来说,它是IOC思想的一种实现方式。在容器启动的时候,它会根据每个bean的要求,将bean注入到SpringContainer中。如果有其他bean需要使用,就直接从容器中获取即可,

2. 代码示例对比

// 传统方式(控制权在类内部)
class UserService {
    private UserRepository repo = new UserRepositoryImpl(); // 主动创建依赖
}

// IOC+DI方式(控制权在容器)
class UserService {
    private UserRepository repo;
    
    // 构造器注入(DI的实现)
    public UserService(UserRepository repo) {
        this.repo = repo; // 由容器注入
    }
}

二、IOC 实现原理

1:从配置元数据中获取要DI的业务POJO(这里的配置元数据包括xml,注解,configuration类等)
2:将业务POJO形成BeanDefinition注入到Spring Container中
3:使用方通过ApplicationContext从Spring Container直接获取即可。如下图所示:

1. 核心流程

2. 简易版的ioc实现方式

// 简化的IOC容器实现
public class SimpleContainer {
    private Map<String, Object> beans = new HashMap<>();
    
    public void register(String name, Object bean) {
        beans.put(name, bean);
    }
    
    public Object getBean(String name) {
        return beans.get(name);
    }
    
    // 依赖注入示例
    public void injectDependencies() {
        for (Object bean : beans.values()) {
            for (Field field : bean.getClass().getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    Object dependency = beans.get(field.getType().getName());
                    field.setAccessible(true);
                    field.set(bean, dependency);
                }
            }
        }
    }
}

3:DI注入(spring支持字段、setter,构造器注入)

Autowired:Autowired在获取bean的时候,先是byType的方式,再是byName的方式。意思就是先在Spring容器中找以Bean为类型的Bean实例,如果找不到或者找到多个bean,则会通过fieldName来找。缺点:单一职责问题,可能产生NPE,隐藏依赖,不利于测试

Resource:Resource在获取bean的时候,和Autowired恰好相反,先是byName方式,然后再是byType方式。当然,我们也可以通过注解中的参数显示指定通过哪种方式。

作用域不同
Autowired可以作用在构造器,字段,setter方法上
Resource 只可以使用在field,setter方法上

支持方不同
Autowired是Spring提供的自动注入注解,只有Spring容器会支持,如果做容器迁移,是需要修改代码的
Resource是JDK官方提供的自动注入注解(JSR-250)。它等于说是一个标准或者约定,所有的IOC容器都会支持这个注解。假如系统容器从Spring迁移到其他IOC容器中,是不需要修改代码的。

默认要求不同

@Autowired注解默认要求要注入的Bean必须存在,如果找不到匹配的Bean会抛出异常。

@Resource注解默认允许注入的Bean可以缺失,如果找不到匹配的Bean会使用默认值null。

4:beanfactory和factorbean的区别:

这两个东西都是接口(interface),然后都是在org.springframework.beans.factory包下面的。

BeanFactory比较常用,名字也比较容易理解,就是Bean工厂,他是整个Spring IoC容器的一部分,负责管理Bean的创建和生命周期。是Spring IoC容器的一个接口,用来获取Bean以及管理Bean的依赖注入和生命周期。

FactoryBean是一个接口,用于定义一个工厂Bean,它可以产生某种类型的对象通常用于创建很复杂的对象,比如需要通过某种特定的创建过程才能得到的对象。例如,创建与JNDI资源的连接或与代理对象的创建。就如我们的Dubbo中的ReferenceBean。

三、AOP 实现原理

和IOC一样,AOP也指的是一种思想。AOP思想是OOP(Object-Oriented Programming)的补充。OOP是面向类和对象的,但是AOP则是面向不同切面的。一个切面可以横跨多个类和对象去操作,极大的丰富了开发者的使用方式,提高了开发效率。

譬如,一个订单的创建,可能需要以下步骤:
1权限校验
2事务管理
3创建订单
4日志打印
如果使用AOP思想,我们就可以把这四步当成四个“切面”,让业务人员专注开发第三个切面,其他三个切面则是基础的通用逻辑,统一交给AOP封装和管理。

1. AOP核心概念

术语说明示例
Aspect切面(横切关注点的模块化)日志切面
Advice切面中的具体行为@Before, @After
Pointcut定义何处切入@Pointcut("execution(* com.service..(..))")
Join Point程序执行点(如方法调用)UserService.save()

2. 两种动态代理实现

从Bean的初始化流程中来讲,Spring的AOP会在bean实例的实例化已完成,进行初始化后置处理时创建代理对象,

Spring AOP默认使用标准的JDK动态代理进行AOP代理。这使得任何接口可以被代理。但是JDK动态代理有一个缺点,就是它不能代理没有接口的类。

所以Spring AOP就使用CGLIB代理没有接口的类。

 

(1) JDK动态代理

特点

  • 基于接口

  • 通过ProxyInvocationHandler实现

  • 性能较好

代码demo

public class JdkProxyDemo {
    interface Service {
        void serve();
    }
    
    static class RealService implements Service {
        public void serve() {
            System.out.println("Real service");
        }
    }
    
    public static void main(String[] args) {
        Service proxy = (Service) Proxy.newProxyInstance(
            Service.class.getClassLoader(),
            new Class[]{Service.class},
            (proxy1, method, args1) -> {
                System.out.println("Before call");
                Object result = method.invoke(new RealService(), args1);
                System.out.println("After call");
                return result;
            });
        
        proxy.serve();
    }
}
(2) CGLIB动态代理

特点

  • 基于类继承

  • 通过MethodInterceptor实现

  • 无需接口

  • 生成子类覆盖方法

示例代码

public class CglibProxyDemo {
    static class RealService {
        public void serve() {
            System.out.println("Real service");
        }
    }
    
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealService.class);
        enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> {
            System.out.println("Before call");
            Object result = proxy.invokeSuper(obj, args1);
            System.out.println("After call");
            return result;
        });
        
        RealService proxy = (RealService) enhancer.create();
        proxy.serve();
    }
}

3. Spring AOP实现选择

// 在ProxyCreatorSupport类中
protected Object createProxy(Class<?> beanClass, String beanName, 
                           Object[] specificInterceptors, TargetSource targetSource) {
    // 如果有接口使用JDK代理,否则使用CGLIB
    if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
        return createJdkDynamicAopProxy();
    }
    return createCglibAopProxy();
}

四、Bean的作用域

1. 标准作用域

作用域说明配置方式适用场景
singleton默认,单例@Scope("singleton")无状态服务
prototype每次获取新实例@Scope("prototype")有状态对象
request每个HTTP请求一个实例@Scope("request")Web请求相关
session每个会话一个实例@Scope("session")用户会话数据
applicationServletContext生命周期@Scope("application")全局共享

2. 作用域配置示例

// Java配置方式
@Configuration
public class AppConfig {
    @Bean
    @Scope("prototype")
    public Service prototypeService() {
        return new ServiceImpl();
    }
}

// XML配置方式
<bean id="userService" class="com.example.UserService" scope="singleton"/>

3. 作用域实现原理

// 在AbstractBeanFactory中
public Object getBean(String name) throws BeansException {
    // 根据scope决定创建新实例还是返回缓存
    if (isSingleton(name)) {
        return getSingletonInstance(name);
    } else if (isPrototype(name)) {
        return createNewInstance(name);
    }
    // 其他作用域处理...
}

4:Bean的生命周期是什么样的?

 整个生命周期可以大致分为3个大的阶段,分别是:创建、使用、销毁。还可以进一步分为5个小的阶段:实例化、初始化、注册Destruction回调、Bean的正常使用以及Bean的销毁

更细一点就是:

1实例化Bean:
○Spring容器首先创建Bean实例。
○在AbstractAutowireCapableBeanFactory类中的createBeanInstance方法中实现
2设置属性值:
○Spring容器注入必要的属性到Bean中。
○在AbstractAutowireCapableBeanFactory的populateBean方法中处理
3检查Aware:
○如果Bean实现了BeanNameAware、BeanClassLoaderAware等这些Aware接口,Spring容器会调用它们。
○在AbstractAutowireCapableBeanFactory的initializeBean方法中调用
4调用BeanPostProcessor的前置处理方法:
○在Bean初始化之前,允许自定义的BeanPostProcessor对Bean实例进行处理,如修改Bean的状态。BeanPostProcessor的postProcessBeforeInitialization方法会在此时被调用。
○由AbstractAutowireCapableBeanFactory的applyBeanPostProcessorsBeforeInitialization方法执行。
5调用InitializingBean的afterPropertiesSet方法:
○提供一个机会,在所有Bean属性设置完成后进行初始化操作。如果Bean实现了InitializingBean接口,afterPropertiesSet方法会被调用。
○在AbstractAutowireCapableBeanFactory的invokeInitMethods方法中调用。
6调用自定义init-method方法:
○提供一种配置方式,在XML配置中指定Bean的初始化方法。如果Bean在配置文件中定义了初始化方法,那么该方法会被调用。
○在AbstractAutowireCapableBeanFactory的invokeInitMethods方法中调用。
7调用BeanPostProcessor的后置处理方法:
○在Bean初始化之后,再次允许BeanPostProcessor对Bean进行处理。BeanPostProcessor的postProcessAfterInitialization方法会在此时被调用。
○由AbstractAutowireCapableBeanFactory的applyBeanPostProcessorsAfterInitialization方法执行
8注册Destruction回调:
○如果Bean实现了DisposableBean接口或在Bean定义中指定了自定义的销毁方法,Spring容器会为这些Bean注册一个销毁回调,确保在容器关闭时能够正确地清理资源。
○在AbstractAutowireCapableBeanFactory类中的registerDisposableBeanIfNecessary方法中实现
9Bean准备就绪:
○此时,Bean已完全初始化,可以开始处理应用程序的请求了。
10调用DisposableBean的destroy方法:
○当容器关闭时,如果Bean实现了DisposableBean接口,destroy方法会被调用。
○在DisposableBeanAdapter的destroy方法中实现
11调用自定义的destory-method
○如果Bean在配置文件中定义了销毁方法,那么该方法会被调用。
○在DisposableBeanAdapter的destroy方法中实现

关于常用函数的顺序

构造函数>@PostConstruct > afterPropertiesSet > init-method

整个Bean的创建的过程都依赖于AbstractAutowireCapableBeanFactory这个类,而销毁主要依赖DisposableBeanAdapter这个类

五、完整AOP实战示例

1. 定义切面

@Aspect
@Component
public class LoggingAspect {
    
    @Pointcut("execution(* com.example.service.*.*(..))")
    private void serviceLayer() {}
    
    @Before("serviceLayer()")
    public void logBefore(JoinPoint jp) {
        System.out.println("Entering: " + jp.getSignature());
    }
    
    @Around("serviceLayer()")
    public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        System.out.println("Execution time: " + (System.currentTimeMillis() - start));
        return result;
    }
}

2. 启用AOP

@Configuration
@EnableAspectJAutoProxy // 启用AOP
public class AppConfig {
    // Bean定义...
}

六、Spring容器核心架构

七:Spring 事务(声明式事务和编程式事务)

声明式事务管理方法允许开发者配置的帮助下来管理事务,而不需要依赖底层API进行硬编码。开发者可以只使用注解或基于配置的 XML 来管理事务。@Transactional即可给test方法增加事务控制。使用了 AOP 实现的,本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)包含三个参数:

propagation

Spring的事务传播机制用于控制在多个事务方法相互调用时事务的行为。

Spring的事务规定了7种事务的传播级别,默认的传播机制是REQUIRED

●REQUIRED,如果不存在事务则开启一个事务,如果存在事务则加入之前的事务,总是只有一个事务在执行
●REQUIRES_NEW,每次执行新开一个事务,如果当前存在事务,则把当前事务挂起
●SUPPORTS,有事务则加入事务,没有事务则普通执行
●NOT_SUPPORTED,有事务则暂停该事务,没有则普通执行
●MANDATORY,强制有事务,没有事务则报异常
●NEVER,有事务则报异常
●NESTED,如果之前有事务,则创建嵌套事务,嵌套事务回滚不影响父事务,反之父事务影响嵌套事务

rollback

rollbackFor是Spring事务中的一个属性,用于指定哪些异常会触发事务回滚。

在一个事务方法中,如果发生了rollbackFor属性指定的异常或其子类异常,则事务会回滚。如果不指定rollbackFor,则默认情况下只有RuntimeException和Error会触发事务回滚。

异常类.class

1、私有方法调用
2、静态方法调用
3、final方法调用
4、类内部自调用
5、内部类方法调用

声明式事务失效的场景:

1、@Transactional应用在非 public 修饰的方法上,

2、@Transactional注解属性 propagation 设置错误

3、@Transactional注解属性 rollbackFor 设置错误

4、同一个类中方法调用,导致@Transactional失效

5、异常被catch捕获导致@Transactional失效

6、数据库引擎不支持事务
7、Bean没有被Spring管理,而是自己new了一个Bean

8、final、static方法

9、如果在事务中,开启了新的线程,那么就会导致多个线程之间不在同一个事务中,会导致事务隔离。但是,如果在一个新的线程开启后,又开始了一个事务,那么在这个线程执行过程中,当前线程的所有操作都是在同一个事务中的。

10、多线程情况下。使用的是 ThreadLocal 机制来存储事务上下文,而 ThreadLocal 变量是线程隔离的,即每个线程都有自己的事务上下文副本。因此,在多线程环境下,Spring 的声明式事务会“失效”,即新线程中的操作不会被包含在原有的事务中。

八、最佳实践建议

  1. IOC使用

    • 优先使用构造器注入(强制依赖)

    • 可选依赖使用setter注入

    • 避免字段注入(不利于测试)

  2. AOP使用

    • 切面粒度要适中(通常按功能模块划分)

    • 避免在切面中处理过多业务逻辑

    • 注意代理失效问题(同类调用不会触发AOP)

  3. 作用域选择

    • 默认使用singleton(性能最佳)

    • 有状态bean使用prototype

    • Web相关作用域要确保线程安全

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值