Spring IOC
什么是 IoC ?
IoC (Inversion of Control 控制反转)是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由给 Spring 框架来管理。
为什么叫控制反转?
- 控制:指的是对象的创建(实例化,管理)的权利
- 反转:控制权交给外部环境(Spring 框架、Ioc 容器)
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器来完成对象的注入。这样可以很大程度上简化应用的开发,并做到解耦合,把应用从复杂的依赖关系中抽取出来,开发人员只需要关注对象的使用,而不需要关心对象是如何创建的。
在实际项目中,一个 Service 类可能依赖很多其他的类。假如我们需要实例化这个 Service ,每次都需要搞清楚这个 Service 所有底层类的构造函数。如果使用 IoC的话,只需要配置好Bean ,在需要的地方注入就行了,大大降低了项目的可维护性和开发难度。
什么是 Spring Bean?
可以简单地理解为 Bean 指代的就是那些 被 IoC 容器所管理的对象。一般在 Spring Boot 中使用以下几种注解声明:
- Component : 通用注解,可以标注任意类为 Spring 组件。如果一个类不清楚分在那一层,可使用 @Component 注解标注
- @Repository:对应持久层 即 Dao 层,主要用于数据库相关操作
- @Service:对应服务层,主要设计一些复杂的逻辑处理
- @Controller:对应 控制层 , 主要用于接收用户的请求并调用 Service。
IoC 和 DI 有什么区别?IoC 可以理解为以中国控制反转的设计思想,而 DI 可以理解为这种设计思想的具体实现方式
IoC 解决了什么问题?
- 对象之间的耦合度降低
- 资源管理变得容易
例如,一个针对 User 的操作,利用 Service 和 Dao 层进行开发
在没有使用 IoC 思想的情况下,Service 层想要使用 Dao 层的话,需要通过 new 关键字在 UserServiceImpl 中手动 new 一个 UserDao 的具体实现类 UserDaoImpl。
但是,如果开发过程中出现新的需求,针对 UserDao 接口 开发出另一个 具体的实现类。由于 Service 层依赖了 UserDao 的具体实现,所以我们需要修改 UserServiceImpl 中 new 的对象。这就导致,如果我们的项目中很多地方都依赖于 UserDao 的实现类的话,就需要修改每一处,这就导致维护起来非常麻烦。
使用 IoC 的思想,我们将对象的控制权交给 Spring 容器管理后,我们在使用的时候直接向 IoC 容器 “要” 就行了。
Spring AOP
什么是 AOP?
AOP(Aspect Oriented Programming)即面向切面编程。
AOP 的目的是将横切关注点(例如日志管理、事务管理、权限控制、接口控制)从核心业务逻辑中分离出来,通过动态代理,字节码操作等技术,实现代码的复用和解耦,提高代码的可维护性和可扩展性。
OOP(面向对象编程)
AOP 和 OOP 其实并不冲突,两者互补
OOP的目的是将业务逻辑按照对象的属性和行为进行封装,通过类、对象、继承、多态等概念,实现代码的模块化和层次化,提高代码的可读性和可维护性
AOP 为什么叫面向切面编程?
因为 AOP 的核心思想是将横切关注点从核心业务逻辑中分离出来,形成一个一个的切面。
- 横切关注点:多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流等)
- 切面(Aspect):对横切关注点进行封装的类,一个切面是一个类。切面可以定义多个通知,用来实现具体的功能
- 连接点(JoinPoint):连接点是方法调用或方法执行时的某个特定时刻(如方法调用、抛出异常等)
- 通知(Advice):通知就是切面在某个连接点要执行的操作。通知有五种类型分别是:前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和 环绕通知(Around)。
- 切点(Pointcut):一个切点是一个表达式,它用来匹配哪些连接点需要被切面所增强。切点可以通过注解、正则表达式、逻辑运算等方式来定义。比如
execution(* com.xyz.service..*(..))
匹配com.xyz.service
包及其子包下的类或接口。 - 织入(Weaving):织入是将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。常见的织入时机有两种,分别是编译期织入(AspectJ)和运行期织入(AspectJ)
使用示例
AOP解决了什么问题?
OOP不能很好的处理一些分散在多个类或对象中的公共行为,这些行为通常被称为 横切关注点 。如果我们在每个类或者对象中都重复实现这些行为就会导致代码的冗余、复杂和难以维护。
AOP 可以将横切关注点 从 核心业务逻辑 中分离出来,实现关注点的分离。
比如说日志记录,记录每次访问请求的参数和信息:
@Aspect
@Slf4j
@Component
@Order(0)
public class AopLog {private static final String START_TIME = "request-start";/***切入点* */@Pointcut("execution( * com.whgcdx.demo1.controller..*Controller.*(..))")public void log(){}/*** 环绕操作*/@Around("log()")public Object aroundLog(ProceedingJoinPoint point) throws Throwable {Object result = point.proceed();log.info("【返回值】:{}", JSON.toJSONString(result));return result;}/**** 前置操作*/@Before("log()")public void beforeLog(JoinPoint point){//利用 RequestContextHolder 获取 HttpServletRequest 对象ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();UserAgent userAgent = UserAgentUtil.parse(httpServletRequest.getHeader(Header.USER_AGENT.toString()));//重组请求信息StringBuffer sb = new StringBuffer();sb.append("---------------------------------收到请求-------------------------------------------");sb.append("\r\n【浏览器类型】: " + userAgent.getBrowser() + " 【版本号】: " + userAgent.getVersion());sb.append("\r\n【user-agent】: " + httpServletRequest.getHeader(Header.USER_AGENT.toString()));sb.append("\r\n【访问URL】: " + httpServletRequest.getRequestURI());sb.append("\r\n【访问Method】: " + httpServletRequest.getMethod());sb.append("\r\n【访问IP】: " + httpServletRequest.getRemoteAddr());sb.append("\r\n【访问类名】:"+ point.getSignature().getDeclaringTypeName() + ", 【访问方法名】:" + point.getSignature().getName());Map<String ,String[]> paramerMap = httpServletRequest.getParameterMap();sb.append("\r\n【请求参数】: " + paramerMap);log.info(sb.toString());httpServletRequest.setAttribute(START_TIME,System.currentTimeMillis());}/*****/@After("log()")public void afterLog(){ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();Long start= (Long) request.getAttribute(START_TIME);Long end=System.currentTimeMillis();log.info("【请求耗时】:{}ms",end-start);String header=request.getHeader(Header.USER_AGENT.toString());UserAgent userAgent=UserAgentUtil.parse(header);log.info("【操作系统】:{},【原始User-Agent】:{}",userAgent.getOs().toString(),header);}}