3、Spring Bean
Bean 代指的就是那些被 IoC 容器所管理的对象,我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。
Bean的创建方式
1. XML 配置文件: 传统上,使用 XML 文件来定义和配置 beans。
2. 注解(Annotation-based configuration): 使用如 @Component, @Service, @Repository, @Controller 等注解来自动注册 bean。
3. Java 配置类(Java-based configuration): 使用 @Configuration 和 @Bean 注解来定义配置类和方法。
将一个类声明为 Bean 的注解有哪些?
- @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
- @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
- @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
- @Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
@Component 和 @Bean 的区别是什么?
- @Component 注解作用于类,而@Bean注解作用于方法。
- @Component 通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中,@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
- @Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
Bean 的作用域有哪些?
singleton 单例 : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
prototype 原型: 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
request 请求(仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
session 会话(仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
## 配置作用域
xml 方式:
<bean id="..." class="..." scope="singleton"></bean>
单例 Bean 的线程安全问题了解吗?
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。
常见的有两种解决办法:
- 在 Bean 中尽量避免定义可变的成员变量。
- 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
4、Spring AoP
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
AOP组成结构:
切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象。切面可以包含通知和切点。
连接点(Join Point): 程序执行的某个特定位置,如方法调用或异常抛出的地方。在 Spring AOP 中,一个连接点总是表示一个方法的执行。
通知(Advice):
切面在特定连接点上执行的动作。主要有以下类型:
- 前置通知(Before advice):在某连接点之前执行(但不影响连接点的执行)。
- 后置通知(After returning advice):在某连接点正常完成后执行。
- 异常通知(After throwing advice):在方法抛出异常退出时执行。
- 最终通知(After advice):无论连接点退出的方式如何都将执行的通知。
- 环绕通知(Around advice):围绕一个连接点的通知,可以在方法调用前后自定义行为,甚至可以完全替换方法。
切点(Pointcut): 匹配连接点的断言,在 AOP 语法中,通知与一个切点表达式关联,并在满足切点的连接点上运行。
目标对象(Target Object): 被一个或多个切面所通知的对象。
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { // 定义切点:匹配com.example.service包下所有类的所有方法 @Pointcut("execution(* com.example.service.*.*(..))") public void serviceLayer() { } // 前置通知:在目标方法执行前调用 @Before("serviceLayer()") public void logBefore(JoinPoint joinPoint) { System.out.println("前置通知:即将执行方法: " + joinPoint.getSignature().getName()); } // 后置通知:在目标方法正常执行后调用 @AfterReturning(pointcut = "serviceLayer()", returning = "result") public void logAfterReturning(JoinPoint joinPoint, Object result) { System.out.println("后置通知:方法执行完成: " + joinPoint.getSignature().getName() + ", 返回值:" + result); } // 异常通知:在目标方法抛出异常后调用 @AfterThrowing(pointcut = "serviceLayer()", throwing = "error") public void logAfterThrowing(JoinPoint joinPoint, Throwable error) { System.out.println("异常通知:方法执行异常: " + joinPoint.getSignature().getName() + ", 异常信息:" + error.getMessage()); } // 最终通知:无论目标方法如何结束都会执行 @After("serviceLayer()") public void logAfter(JoinPoint joinPoint) { System.out.println("最终通知:无论方法如何执行完毕都会调用"); } // 环绕通知:可以在目标方法前后自定义行为,也可以阻止方法的执行 @Around("serviceLayer()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕通知开始:方法名:" + joinPoint.getSignature().getName()); try { Object result = joinPoint.proceed(); System.out.println("环绕通知成功结束,结果是:" + result); return result; } catch (Throwable e) { System.out.println("环绕通知捕获到异常:" + e.getMessage()); throw e; // 可以决定是否重新抛出异常 } finally { System.out.println("环绕通知结束"); } } }
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,, Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理
术语 | 含义 |
---|---|
目标(Target) | 被通知的对象 |
代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
切入点(Pointcut) | 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点) |
通知(Advice) | 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情 |
切面(Aspect) | 切入点(Pointcut)+通知(Advice) |
Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
AspectJ 定义的通知类型有哪些?
- Before(前置通知):目标对象的方法调用之前触发
- After (后置通知):目标对象的方法调用之后触发
- AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
- AfterThrowing(异常通知) :目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
- Around: (环绕通知)编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
多个切面的执行顺序如何控制?
// 通常使用@Order 注解直接定义切面顺序,值越小优先级越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {
// 实现Ordered 接口重写 getOrder 方法
@Component
@Aspect
public class LoggingAspect implements Ordered {
// ....
@Override
public int getOrder() {
// 返回值越小优先级越高
return 1;
}
}