Java 经典面试题解析:AOP 与 IOC 设计思想

一、IOC(控制反转)设计思想

1. 核心设计思想

IOC 的本质是将对象的创建权由开发者转移给框架,解决了传统开发中对象依赖关系复杂、耦合度高的问题。在传统开发中,开发者需要手动通过new关键字创建对象并维护依赖关系(如 Service 依赖 Dao、Controller 依赖 Service);而在 IOC 思想下,对象的创建、初始化、依赖注入等操作由容器(如 Spring 容器)统一管理,开发者只需关注对象的业务逻辑。

2. 实现原理

IOC 的实现依赖工厂模式反射机制

  • 工厂模式:Spring 容器作为 “对象工厂”,根据配置文件(XML)或注解(如@Component@Service)中的元数据,通过反射动态创建对象。

  • 依赖注入(DI):容器在创建对象时,自动将其依赖的其他对象注入(如通过@Autowired注解),无需开发者手动关联。

3. 项目案例与应用场景

案例:电商订单系统的 Service 层依赖管理

在传统开发中,订单 Service 需要手动创建商品 Dao 和用户 Dao 的实例:

public class OrderService {
    // 手动创建依赖对象,耦合度高
    private ProductDao productDao = new ProductDao();
    private UserDao userDao = new UserDao();
    
    public void createOrder() {
        productDao.checkStock();
        userDao.verifyUser();
        // 业务逻辑...
    }
}

采用 IOC 思想后,依赖关系由 Spring 容器管理:

@Service
public class OrderService {
    // 依赖由容器自动注入,无需手动创建
    @Autowired
    private ProductDao productDao;
    @Autowired
    private UserDao userDao;
    
    public void createOrder() {
        productDao.checkStock();
        userDao.verifyUser();
        // 业务逻辑...
    }
}

经典应用场景

  • 对象生命周期管理:如数据库连接池对象的创建与销毁由容器统一控制,避免资源泄露。
import javax.sql.DataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class DataSourceExample {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource dataSource = context.getBean("dataSource", DataSource.class);
        // 使用dataSource获取数据库连接进行操作,容器会管理其生命周期
    }
}

applicationContext.xml中配置:

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
    <property name="user" value="root"/>
    <property name="password" value="123456"/>
</bean>
  • 依赖解耦:在多层架构(Controller→Service→Dao)中,通过依赖注入减少层间耦合,便于单元测试(可通过 Mock 框架替换真实依赖)。
// Dao接口
public interface UserDao {
    void saveUser();
}

// Dao实现类
public class UserDaoImpl implements UserDao {
    @Override
    public void saveUser() {
        System.out.println("保存用户到数据库");
    }
}

// Service接口
public interface UserService {
    void save();
}

// Service实现类
public class UserServiceImpl implements UserService {
    private UserDao userDao;
    // 通过构造函数注入
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void save() {
        userDao.saveUser();
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        UserDao userDao = new UserDaoImpl();
        UserService userService = new UserServiceImpl(userDao);
        userService.save();
    }
}
  • 配置集中管理:如数据源参数(URL、用户名、密码)通过配置文件注入,避免硬编码。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:database.properties")
public class DatabaseConfig {
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    
    public void printConfig() {
        System.out.println("URL: " + url);
        System.out.println("Username: " + username);
        System.out.println("Password: " + password);
    }
}

database.properties中配置:

jdbc.url=jdbc:mysql://localhost:3306/test

jdbc.username=root

jdbc.password=123456

测试:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        DatabaseConfig config = context.getBean(DatabaseConfig.class);
        config.printConfig();
    }
}

二、AOP(面向切面编程)设计思想

1. 核心设计思想

AOP 的核心是将分散在多个业务逻辑中的通用功能(如日志、事务、权限校验)抽取为 “切面”,使业务逻辑与通用功能分离,实现 “关注点分离”。传统开发中,通用功能需重复编写在各业务方法中(如每个 Service 方法都添加日志打印);而 AOP 通过动态代理技术,在不修改原有代码的前提下,将通用功能 “织入” 目标方法的执行流程中。

2. 实现原理

AOP 基于动态代理实现,Spring 中分为两种代理方式:

  • JDK 动态代理:针对实现接口的类,通过Proxy类动态生成代理对象,代理逻辑封装在InvocationHandlerinvoke方法中。

  • CGLIB 动态代理:针对没有实现接口的类,通过继承目标类生成代理子类,重写方法实现代理逻辑。

AOP 的关键概念包括:

  • 切面(Aspect):封装通用功能的类(如LogAspectTransactionAspect)。

  • 连接点(JoinPoint):程序执行过程中的特定位置(如方法调用前、异常抛出后)。

  • 切入点(Pointcut):通过表达式(如execution(* com.service.*.*(..)))指定需要织入切面的连接点。

  • 通知(Advice):切面的具体执行逻辑(如@Before@After@Around)。

3. 项目案例与应用场景

案例:电商系统的日志记录与事务管理

在传统开发中,每个业务方法都需要手动添加日志打印和事务控制代码:

public class OrderService {
    private OrderDao orderDao;
    
    public OrderService(OrderDao orderDao) {
        this.orderDao = orderDao;
    }
    
    public void createOrder(Order order) {
        // 手动添加日志打印
        System.out.println("开始创建订单:" + order.getOrderId());
        try {
            // 手动开启事务
            // 业务逻辑
            orderDao.save(order);
            // 手动提交事务
        } catch (Exception e) {
            // 手动回滚事务
            System.out.println("创建订单失败:" + e.getMessage());
        }
        System.out.println("订单创建完成");
    }
}

使用 AOP 后,将日志记录和事务管理抽取为切面:

定义切面类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAndTransactionAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object logAndTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("开始执行方法:" + joinPoint.getSignature().getName());
        try {
            // 执行目标方法
            Object result = joinPoint.proceed();
            System.out.println("方法执行成功");
            return result;
        } catch (Exception e) {
            System.out.println("方法执行失败:" + e.getMessage());
            throw e;
        } finally {
            System.out.println("方法执行结束");
        }
    }
}

业务类简化后代码

public class OrderService {
    private OrderDao orderDao;
    
    public OrderService(OrderDao orderDao) {
        this.orderDao = orderDao;
    }
    
    public void createOrder(Order order) {
        orderDao.save(order);
    }
}

经典应用场景

  • 日志记录:在电商系统中,日志记录是 AOP 的重要应用场景之一。通过 AOP 技术,可以在不修改核心业务代码的情况下,对用户的关键操作如登录、下单、支付等进行日志记录。这些日志能够详细记录操作发生的时间、参与对象等信息,在系统出现问题时,可依据日志快速定位和排查问题,同时也有助于系统运行状态的实时监控。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Date;

@Aspect // 声明这是一个切面类
@Component // 将切面类纳入Spring容器管理
public class LogAspect {
    // 定义切入点表达式,拦截com.example.service包下所有类的所有方法
    @Before("execution(* com.example.service..*.*(..))")
    public void logOperation(JoinPoint joinPoint) {
        // 获取被拦截的方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取当前时间作为日志记录时间
        Date currentTime = new Date();
        System.out.println("[" + currentTime + "] 执行方法:" + methodName + ",参数:" + java.util.Arrays.toString(joinPoint.getArgs()));
    }
}
  • 事务管理:银行转账系统中数据的一致性至关重要。利用 AOP 对涉及资金变动的方法添加事务切面,在执行转账操作时,可确保一系列相关操作要么全部成功提交,要么全部回滚,避免出现数据不一致的情况,保障资金安全和数据的完整性。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

@Aspect
@Component
public class TransactionAspect {
    private final TransactionTemplate transactionTemplate;
    
    public TransactionAspect(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }
    
    // 定义切入点表达式,拦截com.example.bank.service包下所有类的所有方法
    @Around("execution(* com.example.bank.service..*.*(..))")
    public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        return transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    // 执行目标方法
                    joinPoint.proceed();
                } catch (Exception e) {
                    // 发生异常时回滚事务
                    status.setRollbackOnly();
                    throw e;
                }
            }
        });
    }
}
  • 权限校验:后台管理系统中的敏感操作需要严格的权限控制。通过 AOP 在需要权限控制的接口(如删除用户接口)添加权限切面,在请求到达接口业务逻辑之前进行拦截,判断请求者是否具备相应权限,只有授权用户的请求才能被放行,从而保障系统数据的安全性和操作的合法性。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;

@Aspect
@Component
public class PermissionAspect {
    // 模拟授权用户列表
    private static final List<String> AUTHORIZED_USERS = Arrays.asList("admin", "manager");
    
    // 定义切入点表达式,拦截com.example.admin.controller包下所有类的所有方法
    @Around("execution(* com.example.admin.controller..*.*(..))")
    public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
        // 假设从方法参数中获取当前用户(实际应用中从请求上下文获取)
        String currentUser = (String) joinPoint.getArgs()[0];
        
        if (AUTHORIZED_USERS.contains(currentUser)) {
            // 用户有权限,执行目标方法
            return joinPoint.proceed();
        } else {
            throw new RuntimeException("无权限访问");
        }
    }
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐