Spring IOC和DI
IOC(控制反转)和DI(依赖注入)是软件设计模式中的两个概念。
IOC(控制反转)是一种设计模式,它将控制权从应用程序代码中移动到框架或容器中。这意味着,应用程序不再直接创建和管理组件,而是由框架或容器来创建和管理组件。这种模式使应用程序代码更加灵活,并且可以更轻松地进行测试、扩展和升级。
DI(依赖注入)是一种实现IOC模式的方法。依赖注入是指,框架或容器将依赖项注入到组件中,而不是该组件去创建它们。这些依赖项可以是其他组件、服务、数据库连接、配置信息等。依赖注入有助于减少组件之间的耦合,并提高应用程序的可维护性和可扩展性。
总之,IOC和DI是一种设计模式和一种实现方式,可以帮助软件开发人员更好地管理组件和依赖关系,从而使应用程序更加健壮、灵活和可维护。
Spring AOP
1.Spring AOP可以做什么?
- 实现面向切面编程
AOP可以在应用中的多个对象之间实现横切关注点的复用,避免代码的分散并降低了类之间的耦合度。 - 让底层的应用逻辑与非业务逻辑分离
AOP可以从业务逻辑中抽离出可重用的非业务逻辑,例如事务管理、日志记录等。 - 将对象的功能性分成多个不同的模块
AOP对于将对象的功能性分成多个不同的模块,每个模块专注于一个特定的面向切面关注点,这有利于提高应用的可维护性和可扩展性。 - 实现一些功能性需求
AOP可以实现一些非功能性需求,例如性能监控、缓存处理、安全检查等。
2.Spring AOP的使用场景
- 记录日志: 对方法调用进行记录日志,例如进入方法、离开方法、出现异常等情况。
- 事务管理: 在方法调用期间管理数据库事务,例如开始事务、提交事务、回滚事务等。
- 缓存管理: 对方法调用实现缓存,避免频繁访问数据库。
- 安全检查: 在方法调用期间进行安全检查,例如验证用户权限等。
- 性能监控: 对方法调用进行性能监控,例如记录方法执行时间、统计调用次数。
- 异常处理: 在方法调用期间处理异常,例如捕获和处理异常。
- 参数校验: 对方法调用进行参数校验,例如验证参数的正确性、类型等。
总之,Spring AOP的使用场景非常广泛,可以应用于各种类型的应用程序中,实现各种不同的非业务逻辑需求。
3.Spring AOP原理
Spring AOP基于动态代理和字节码生成实现面向切面编程。当我们在Spring应用程序中使用AOP时,容器会在运行期间动态生成一个代理类。代理类是目标对象的子类,该代理类中被增强的方法会被重写,这些方法会在执行前后织入指定的切面逻辑。
具体的实现原理,可以分为以下几个步骤:
-
定义切面
在切面中定义切点和增强逻辑,切点是指在哪些连接点上要进行增强,增强逻辑是指要在连接点上执行的非业务逻辑。 -
创建代理对象
Spring容器会动态创建一个代理对象,这个代理对象会与目标对象放在同一个BeanFactory中,并且拥有与目标对象相同的接口。 -
生成字节码
对于每个连接点,Spring AOP会先通过字节码生成技术创建一个代理对象,这个代理对象包含目标对象的所有可执行方法和增强逻辑。 -
执行增强逻辑
当目标对象的方法被调用时,Spring AOP会自动执行与该方法相对应的增强逻辑,并在执行前后进行织入。 -
执行业务逻辑
执行完增强逻辑后,代理对象会委托目标对象执行与该方法对应的业务逻辑。
总结:Spring AOP的原理主要是基于动态代理和字节码生成,通过代理对象在目标对象方法执行前后织入切面逻辑实现面向切面编程。
4.spring 动态代理
Spring动态代理是使用JDK动态代理或CGLIB代理技术通过代理类实现横切逻辑的方法。Spring动态代理主要针对接口代理和类代理两种情况,对于接口代理,使用JDK动态代理技术,对于类代理,使用CGLIB代理技术。即如果目标对象实现了接口,则会使用JDK动态代理,如果目标对象没有实现接口,则会使用CGLIB代理。Spring动态代理的原理是:当容器在运行期间动态创建代理对象时,会通过Java反射机制来获取目标对象的所有接口信息,然后通过动态代理技术创建一个代理对象,并将该代理对象与目标对象的接口绑定在一起。当代理对象调用方法时,会自动调用与该方法相对应的增强逻辑,并在执行前后进行织入。
需要注意的是,JDK动态代理只能代理实现了接口的类,而CGLIB代理可以代理任何类,包括没有实现接口的类,但是CGLIB代理是通过对目标对象进行继承来实现的,因此如果我们使用CGLIB代理,需要确保目标对象没有被final修饰,否则会报错。
总之,Spring动态代理是Spring实现AOP的核心技术,通过代理技术实现横切逻辑的方法,可以大大提高代码的维护性和可重用性。
4.1.JDK动态代理
JDK动态代理是一种基于Java反射机制实现的动态代理技术,通过代理对象实现了对目标方法的拦截和增强,是Java中自带的动态代理技术,也是Spring中默认使用的代理方式。
JDK动态代理主要原理如下:
- 创建接口
定义一个接口及其实现类。 - 实现InvocationHandler接口
代理实现InvocationHandler接口,并重写invoke方法,invoke方法中会去调用目标方法。 - 创建代理对象
创建Proxy代理对象,通过调用静态方法Proxy.newProxyInstance()来创建。 - 调用目标方法
代理对象调用目标方法时,会被转发到InvocationHandler的invoke方法,可以在该方法中对目标方法进行增强或拦截。
JDK动态代理的基本过程就是:生成动态代理之后,调用目标方法时会通过代理对象调用到InvocationHandler的invoke方法,在invoke方法中可以对目标方法进行增强或拦截。由于JDK动态代理生成的代理类必须实现至少一个接口,因此它只能代理有接口的类。
总结:JDK动态代理基于Java反射机制实现,通过代理对象拦截并增强目标方法实现横切逻辑。它只能代理实现了接口的类,且目标对象必须实现接口。
4.2 CGLIB动态代理
CGLIB动态代理是一种基于ASM(Java字节码操作框架)实现的动态代理技术,它通过生成目标类的子类进行代理,实现了对目标方法的拦截和增强,可以代理任何类。
CGLIB动态代理主要原理如下:
- 创建子类
CGLIB动态代理通过创建目标类的子类来作为代理,实现对目标方法的拦截。 - 实现MethodInterceptor接口
代理实现MethodInterceptor接口,并重写intercept方法,调用该方法时会先执行拦截器的逻辑,然后在调用目标方法。 - 创建代理对象
使用Enhancer类中的create()方法创建代理对象。 - 调用目标方法
代理对象调用目标方法时,会先执行代理类中的拦截器的逻辑,然后再调用目标方法,实现对目标方法的增强或拦截。
CGLIB动态代理的基本过程就是:通过生成目标类的子类实现代理,调用目标方法时会先执行代理对象中的拦截器逻辑,然后再执行目标方法。由于CGLIB动态代理是通过生成目标类的子类来实现代理,它可以代理任何类,包括没有实现接口的类。
需要注意的是,CGLIB动态代理是通过继承实现的,在代理类中,目标方法不能为final方法,否则无法进行代理。
总结: CGLIB动态代理基于ASM实现,通过生成目标类的子类实现代理,可以代理任何类,包括没有实现接口的类。它的原理是在子类中重写父类的方法并添加增强逻辑,然后通过代理对象调用目标方法时,会先执行代理类中的拦截器逻辑,再执行目标方法。
5. ASM
5.1. ASM简单使用
ASM是一个字节码处理框架,可以通过访问Java字节码来生成、转换和生成优化代码。以下是使用ASM的一般步骤:
- 添加ASM依赖
使用Maven或其他构建工具,在项目中添加ASM依赖,例如:
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.2</version>
</dependency>
- 定义ClassWriter
ASM中的ClassWriter类可以用来生成新的Java字节码,定义如下:
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
其中,COMPUTE_MAXS用于自动计算栈大小和局部变量表大小,COMPUTE_FRAMES用于自动生成Java字节码栈映射表和局部变量表。
- 创建类信息
使用cw定义的ClassWriter对象,创建新的类信息,定义如下:
String className = "com.example.MyClass"; // 类名
String classPath = className.replace(".", "/");
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, classPath, null, "java/lang/Object", null);
这段代码创建了一个名为com.example.MyClass的类,并继承自java/lang/Object。
- 定义方法信息:使用MethodVisitor类定义方法,例如:
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "testMethod", "()V", null, null);
其中,ACC_PUBLIC用于定义方法的可访问性;testMethod表示方法名;()V表示方法返回类型为void,无参数;
- 定义方法具体内容
使用visitVarInsn、visitMethodInsn等方法定义方法具体内容,例如:
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitLdcInsn("Hello World");
mv.visitFieldInsn(Opcodes.PUTFIELD, classPath, "message", "Ljava/lang/String;");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2,1);
mv.visitEnd();
其中,ALOAD用于加载this指针;visitLdcInsn用于将"Hello World"字符串加载到栈中;visitFieldInsn用于设置message字段的值;visitMaxs用于定义方法的栈空间和局部变量表大小。
- 生成字节码
使用ClassWriter生成新的Java字节码,例如:
//定义新类的字节码
byte[] code = cw.toByteArray();
- 加载并运行字节码
可以使用Java字节码的ClassLoader加载新的字节码,执行其中的方法,例如:
ClassLoader loader = new ByteArrayClassLoader();
Class<?> cls = loader.defineClass(className, code);
Object instance = cls.newInstance();
Method method = cls.getMethod("testMethod", null);
method.invoke(instance, null);
以上是使用ASM生成和运行新的Java字节码的一般步骤,可以根据实际需求进行相关修改和调整。
5.2. ASM学习步骤
ASM是一个非常强大,但也比较复杂的技术。下面是一些学习ASM的建议:
- 先学习Java字节码
ASM的本质就是对Java字节码进行操作,因此了解Java字节码的结构和操作非常重要。建议先学习Java字节码的基本结构、常量池、栈帧、局部变量表等。 - 阅读ASM文档
ASM提供了很详细的文档,其中包括了ASM的API、使用示例、注意事项等,都是学习ASM非常有帮助的资源。 官方文档:https://asm.ow2.io - 阅读源码
ASM的源码非常清晰和易于理解,可以使用IDE等工具阅读源码,找到其中的关键类和方法,深入理解ASM的实现原理。 - 实践操作
ASM是一个非常实用的技术,需要结合实际场景进行操作。可以使用ASM实现一些实用的插件或者优化工具,或者借助现有的ASM插件或工具进行学习。 - 学习其他字节码处理工具
ASM并不是唯一的字节码处理工具,还有其他工具如Javassist、BCEL等,可以对比研究不同工具的用法和优劣。 - 参与社区学习
ASM社区非常活跃,有很多开发者分享了自己的经验和心得,甚至有一些社区提供了在线的学习资源和交流平台,可以积极参与其中,扩展自己的ASM技能和认识。
6. Spring AOP增强方式
Spring AOP提供了多种增强方式:
- Before advice(前置增强)
在目标方法执行前,执行增强逻辑。如果增强代码抛出异常,目标方法将不会执行。 - After returning advice(后置增强)
在目标方法返回结果后,执行增强逻辑。如果目标方法抛出异常,后置增强将不会执行。 - After throwing advice(异常增强)
在目标方法抛出异常时,执行增强逻辑。 - After advice(返回增强)
在目标方法执行后,无论是否抛出异常,均执行增强逻辑。 - Around advice(环绕增强)
最灵活的增强方式,可以完全控制目标方法的执行。在目标方法执行前和执行后,都可以编写增强逻辑。如果目标方法抛出异常,环绕增强可以捕获并处理异常。
这些增强方式可以通过Spring AOP提供的注解或XML配置实现。例如,在Spring中,使用@Before、@AfterReturning、@AfterThrowing、@After和@Around注解,或使用XML配置文件来定义切面和增强方法。这些增强方式都能够实现对目标方法的增强,并在需要的时间点执行增强逻辑。
7.Spring AOP核心类
Spring AOP(面向切面编程)是Spring框架的重要功能之一,它的核心类包括以下几个:
- AspectJExpressionPointcut
切入点表达式,用于匹配目标对象的方法。 - DefaultPointcutAdvisor
默认切面通知类,封装了切面和切入点。 - ProxyFactoryBean
代理工厂,用于创建代理对象并绑定切入点和通知。 - AdvisedSupport
代理对象的支持类,用于管理切面、切入点和通知。 - ProxyFactory
类似于ProxyFactoryBean,也是代理工厂,用于创建代理对象并绑定切入点和通知。 - AopProxyFactory
AOP代理工厂,用于创建代理对象并织入切面。
这些类是Spring AOP中的核心组件,用于定义和管理切面、切入点和通知,并在运行时创建代理对象,从而实现对目标对象的动态增强和控制。
Spring事务
Spring事务是Spring框架提供的一种事务管理机制,可以在Spring应用程序中执行事务操作。Spring事务管理的主要目的是保证在一组相关的操作中,要么全部成功,要么全部失败和撤销,从而确保数据的一致性和完整性。Spring事务管理提供了声明式和编程式两种方式。
1、Spring的事务管理机制是什么
Spring的事务管理机制是一种声明式事务管理机制,其主要作用是使得开发人员可以在业务方法上添加声明式事务的注解,Spring会根据这些注解来自动管理事务的定义、开始、提交和回滚等操作。
Spring的事务管理机制主要有以下几个组件:
- 事务管理器(TransactionManager)
负责管理事务的定义、开始、提交和回滚等操作,通常是由数据库或JTA提供的一个实现类。 - 事务定义(TransactionDefinition)
定义了事务的隔离级别、传播行为、超时时间等属性。 - 事务状态(TransactionStatus)
表示某个事务的状态,包括事务是否已经开始、是否已经提交或回滚等。 - 声明式事务注解(@Transactional)
用于将一个业务方法声明为需要使用事务的方法,Spring容器会自动管理注解标注的方法,处理事务的开始、提交和回滚等操作。
在使用Spring的事务管理机制时,开发人员只需要在业务方法上添加@Transactional注解,声明该方法需要事务支持,Spring就会根据注解中指定的事务定义来自动处理事务,保证数据的一致性和完整性。
2、声明式事务管理
在声明式事务管理中,通过配置文件或注解实现事务管理。常见的方式是使用Spring的AOP技术,在目标方法执行前、执行后、抛出异常后等时刻插入增强代码,从而实现事务管理。通常需要进行以下步骤:
- 配置事务管理器:配置数据源和事务管理器,以确保正确地操作底层数据库。
- 配置事务通知:使用AOP,在事务开始和提交或回滚时执行增强代码,以确保事务处理正确进行。
- 配置事务切入点:指定需要启用事务管理的方法或类。
以下是一个例子:
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务通知和事务切入点(这里使用注解方式)-->
<aop:config>
<aop:pointcut id="transactionPointcut" expression="execution(* com.example.service.*.*(..))"/>
<aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"/>
</aop:config>
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
3、编程式事务管理
在编程式事务管理中,使用编程方式实现事务管理,这通常是在需要对事务进行更细粒度的控制时使用的方式。可以在Java代码中使用TransactionTemplate或TransactionManager等Spring类来执行事务。
以下是一个例子:
@Autowired
private DataSource dataSource;
@Autowired
private TransactionTemplate transactionTemplate;
public void doTransaction(){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
Connection conn = DataSourceUtils.getConnection(dataSource);
try{
// 执行一些数据库操作
// ...
} catch(Exception e){
// 如果发生异常,则回滚事务
status.setRollbackOnly();
} finally{
DataSourceUtils.releaseConnection(conn, dataSource);
}
}
});
}
在上面的代码中,使用TransactionTemplate执行具体的事务逻辑,如果发生异常,则使用TransactionStatus.setRollbackOnly()方法将事务回滚。虽然编程式事务管理相对于声明式事务管理要繁琐一些,但它更加灵活,可以更细粒度地控制事务的行为。
4、事务注解
Spring注解事务是一种基于注解的声明式事务管理方式,通过为特定的方法添加@Transactional注解来启用事务管理,注解中可配置事务的传播属性、隔离级别、超时时间等事务属性。使用注解事务管理时,需要进行以下配置:
- 开启注解事务支持
在Spring配置文件中添加以下配置,开启注解事务的支持。
<tx:annotation-driven/>
- 添加事务注解
在需要使用事务的方法上添加@Transactional注解。
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public void foo(){
// 一些事务操作
}
- 配置事务管理器
配置数据源和事务管理器,以确保正确地操作底层数据库。
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
@Transactional注解中的主要属性:
-
propagation:事务的传播属性,表示事务在不同方法之间传播的行为,默认值为REQUIRED。常用取值如下:
- REQUIRED: 如果当前存在事务,则加入该事务中,否则新建一个事务;
- REQUIRES_NEW:每次都新建一个事务,并将当前事务挂起;
- SUPPORTS:支持当前事务,如果当前没有事务,则以非事务的方式执行;
- NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在事务,则将当前事务挂起;
- MANDATORY:强制事务存在,如果当前没有事务,则抛出异常。
-
isolation:事务的隔离级别,默认值为DEFAULT。常用取值如下:
- DEFAULT:使用数据库默认的隔离级别;
- READ_UNCOMMITTED:可以读取未提交的数据;
- READ_COMMITTED:只能读取已提交的数据;
- REPEATABLE_READ:确保事务期间多次读取的数据是相同的;
- SERIALIZABLE:最严格的隔离级别,确保事务一个接一个地执行。
-
timeout:事务的超时时间,默认值为-1,表示没有时间限制。
-
readOnly:事务是否是只读的,默认为false。如果只是查询操作,建议将该属性设置为true,以便于优化性能。
对于注解事务管理,常见的使用方式是在Service层的方法中添加@Transactional注解,同时建议将该层的方法封装得更加细致,确保每个方法只处理一些具体的业务逻辑,避免出现一个方法中做了太多的操作造成事务范围过大的问题。
5、Spring事务不生效原因
- 事务注解未被正确识别或被忽略
- 未配置事务管理器或配置不正确
- 没有开启注解配置
- 事务操作在非公共方法中进行
- 可能出现了Session对线程丢失的情况,导致事务失效
- 抛出了RuntimeException(RuntimeException默认会回滚事务,除非手动加上@Transactional注解的noRollbackFor属性)
- 出现多次嵌套事务,但未设置支持嵌套事务
- 数据库的表引擎不支持事务或者表锁定所在的sql语句没有被正确识别。
在Spring中,事务的有效性是基于AOP实现的。如果一个非事务方法调用了一个事务方法,事务是不会生效的。这是因为这个非事务方法没有经过Spring的事务代理,因此Spring无法对该方法应用事务。
为了保证事务的有效性,可以采用以下两种方法:
- 将事务方法作为一个公开的单独的Bean,让其他Bean通过引用该Bean来使用其事务功能。在其他Bean中调用该Bean的方法时,Spring会对该方法应用事务。
- 在被调用的事务方法上添加Propagation.REQUIRES_NEW的传播行为。该传播行为表示在调用该方法时,Spring将创建一个新的事务,而不是使用当前事务。这样,在被调用的方法中,即使调用者的方法没有事务,仍然可以创建独立的事务,保证事务的有效性。例如:
@Transactional
public class TransactionalBeanImpl implements TransactionalBean {
// ...
@Override
@Transactional(propagation=Propagation.REQUIRES\_NEW)
public void doTransactionalMethod() {
// This method is transactional
}
}
在上述代码中,doTransactionalMethod()方法是一个事务方法,并且在它的@Transactional注解下使用了Propagation.REQUIRES_NEW传播行为。当这个方法被其他方法调用时,Spring将为它创建一个新的事务,确保该方法在独立的事务中执行。
Spring MVC
Spring mvc使用
Spring MVC的使用可以分为以下几个步骤:
- 引入Spring MVC依赖
在项目的pom.xml文件中加入Spring MVC的依赖,建议使用Spring Boot项目,可以自动导入和管理依赖。 - 配置Web.xml
在Web.xml中配置DispatcherServlet,它将作为前端控制器,管理请求和响应的流程。 - 编写Controller
编写一个Controller类,它会处理客户端的请求,并根据客户端请求参数进行相应处理,并返回处理结果。 - 编写视图
编写视图,用于展示处理结果,可以使用JSP、HTML等方式进行实现; - 配置视图解析器
在Web配置类中配置视图解析器,解析视图并返回响应。
一个简单的Spring MVC示例代码:
@Controller
@RequestMapping("/greeting")
public class GreetingController {
@GetMapping("/hello")
public String hello(Model model) {
model.addAttribute("name", "world");
return "hello";
}
}
上述代码中,我们定义了一个GreetingController类,其中hello()方法用于处理GET请求,返回hello视图,并在model中添加一个“name”属性。
此外,我们可以在项目中设置多个Controller和视图,来实现各种复杂的Web应用程序。
总之,使用Spring MVC可以帮助开发者轻松构建Web应用程序,并提供灵活性、可测试性和可配置性,值得开发者学习和使用。
Spring mvc原理
Spring MVC 是一个基于模型-视图-控制器(MVC)模式的 Web 框架。它将应用程序分为三个主要部分:模型(Model)、控制器(Controller)和视图(View)。以下是 Spring MVC 的工作原理:
- 请求到达 DispatcherServlet
所有的请求都将被 DispatcherServlet 拦截,它是 Spring MVC 框架的核心控制器。 - HandlerMapping 根据请求中的 URL 选择合适的 Controller
DispatcherServlet 将请求路由到适当的 Controller 上。处理程序映射(HandlerMapping)是一个策略接口,用于确定请求的处理程序对象。 - Controller 处理请求并根据需要调用适当的服务方法
Controller 接收请求并进行相应的处理,通常会将请求委托给一个服务层。 - Service 层处理业务逻辑并调用适当的 DAO 方法
服务层包含业务逻辑,可以调用多个 DAO 方法来从数据库中读取或写入数据。 - DAO 层通过 JDBC 或 ORM 等技术访问数据库
DAO 层实现了数据访问层,它负责从数据库中读取或写入数据。 - Service 层返回响应的视图名称或数据
一旦服务层完成任务,它将返回响应的视图名称或数据。这些数据将用于生成响应,例如 HTML 页面或 JSON 格式的数据。 - ViewResolver 解析视图名称并渲染视图
Spring MVC 的视图解析器(ViewResolver)用于解析视图名称并返回相应的视图对象。视图对象将根据需要渲染视图。 - 渲染响应并返回客户端
一旦视图对象渲染视图,响应将发送回客户端。
总之,Spring MVC 通过将请求路由到适当的控制器,并使用服务层和数据访问层的支持来处理应用程序的请求和响应。它提供了一个松耦合的体系结构,易于扩展和维护。
Spring常见问题
Spring容器的启动流程
- 加载配置文件:Spring容器的启动过程首先会加载配置文件,其中包括XML格式的配置文件、JavaConfig配置类、注解式配置等。
- 创建BeanFactory:根据配置文件中的描述,Spring容器会创建BeanFactory,并根据Bean的定义文件建立Bean的定义信息,包括Bean的类型、属性、依赖关系等。
- 实例化Bean:在BeanFactory中实例化Bean实例,根据Bean的依赖关系和Bean的定义文件加载Bean实例,并将Bean实例放置到容器中。
- 注入依赖:Spring容器将获取Bean的依赖关系,通过setter方法、构造方法或接口方式来将Bean实例注入到其他Bean中。
- 初始化Bean:初始化Bean实例,如果Bean实现了特定的接口,那么Spring容器将调用这些接口的方法完成Bean实例的初始化。
- 执行自定义初始化方法:如果Bean实例中有自定义的初始化方法,Spring容器也会执行这些方法。
- 容器可用:初始化完成后,Spring容器可以提供Bean实例的获取方法,外界可以调用这些方法来获取容器中的Bean实例。
- Bean的销毁:当Spring容器关闭时,会执行所有Bean的销毁方法,同时将Bean实例从容器中移除。
Spring Bean的生命周期
在Spring框架中,一个完整的Bean的生命周期包括以下几个阶段:
- 实例化:容器创建Bean实例。这可以通过构造函数实现。
- 设置Bean属性:容器将Bean的属性值注入到Bean实例中。这可以通过setter方法实现。
- BeanPostProcessor的前置处理:容器在Bean的初始化前调用实现BeanPostProcessor接口的类的postProcessBeforeInitialization()方法,允许用户在Bean的初始化前进行自定义处理。
- 初始化:容器调用Bean的初始化方法。这可以通过实现接口InitializingBean或使用注解@PostConstruct实现。
- BeanPostProcessor的后置处理:容器在Bean的初始化后调用实现BeanPostProcessor接口的类的postProcessAfterInitialization()方法,允许用户在Bean的初始化后进行自定义处理。
- 使用:应用程序可以使用Bean实例。
- 销毁:容器调用Bean的销毁方法。这可以通过实现接口DisposableBean或使用注解@PreDestroy实现。
需要注意的是,不是所有的Bean都会经过所有这些阶段。有些Bean可能只需要实例化和属性赋值,而不需要初始化方法或销毁方法。
Spring的自动装配
Spring的自动装配是指在Spring容器中自动地构建和连接Bean之间的依赖关系。当一个Bean需要依赖其他Bean时,Spring会自动搜索容器中所有合适的Bean,并将它们自动注入到当前Bean中。
Spring的自动装配有三种方式:
- ByName
根据Bean的属性名称自动注入。如果一个属性的名称与另一个Bean的名称相同,那么就可以将这个属性自动注入到当前的Bean中。 - ByType
根据Bean的类型自动注入。如果一个属性的类型与另一个Bean的类型相同,那么就可以将这个属性自动注入到当前的Bean中。 - Constructor
根据构造函数自动注入。当一个Bean需要实例化时,Spring会自动搜索容器中所有合适的Bean,并根据构造函数的参数类型进行自动注入。
Spring的自动装配可以大大降低代码的耦合度,使得代码更加简洁、易于维护。但是,自动装配也有可能会引发一些问题,例如循环依赖、不明确的依赖关系等等,因此在使用自动装配时需要谨慎处理。
Spring基础模块
Spring框架中常用的基础模块包括:
- Core Container模块:包括Bean模块、Core模块、Context模块和Expression模块。这些模块提供了Spring框架的核心功能,如IoC容器、依赖注入、AOP、事件传输等。
- Data Access/Integration模块:包括JDBC模块、ORM模块(如Hibernate、MyBatis)、Transaction模块、OXM模块、JMS模块等。这些模块提供了与数据的交互、事务管理、消息传递等方面的支持。
- Web模块:包括Web模块、MVC模块、WebSocket模块。这些模块提供了与Web应用程序相关的功能,如Web请求处理、视图解析、拦截器、WebSocket通信等。
下面分别对这些模块的主要内容进行简要描述:
1.Core Container模块
- Bean模块:提供了IoC容器和Bean工厂,可以通过配置文件或注解将对象的创建、依赖注入和生命周期管理等过程交给Spring框架处理。
- Core模块:提供了Spring框架的基础架构支持,包括Resource、TypeConversion、ClassLoader、Event、AopNamespaceHandler等。
- Context模块:扩展了Bean模块中的IoC容器和Bean工厂,提供了企业级应用程序所需的上下文支持,包括Environment、MessageSource、ResourceLoader、ApplicationEventPublisher等。
- Expression模块:提供了一个强大的表达式语言,可以作为Spring框架中其他模块的通用表达式语言使用。
2.Data Access/Integration模块
- JDBC模块:提供了对JDBC的抽象,使得开发人员可以使用更加简单和灵活的方式访问和操作关系型数据库。
- ORM模块:提供了与ORM框架(如Hibernate、MyBatis)的集成支持,以方便访问和操作数据库。
- Transaction模块:提供了与事务管理相关的API和支持,以便在Spring框架中管理数据库事务。
- OXM模块:提供了对象/ XML映射的支持,以便在Spring框架中转换Java对象和XML数据。
- JMS模块:提供了与Java消息服务的集成支持,以便在Spring框架中使用消息传递模式。
3.Web模块
- Web模块:提供了访问Web资源、处理Web请求的支持,并提供基于Servlet API的IoC容器。
- MVC模块:提供了Spring MVC框架的支持,可以处理Web请求、生成Web视图以及提供其它Web页面的控制和操作。
- WebSocket模块:提供了WebSocket通信的支持,以便在Spring MVC应用程序中实现实时通信。
Spring异常处理
- 使用@ControllerAdvice注解来定义一个统一的异常处理类,可以在该类中定义多个用@ExceptionHandler注解标记的方法来处理不同的异常类型。
- 在方法上使用@ExceptionHandler注解来处理异常。
- 在Spring配置文件中配置异常处理器和异常解析器。
- 在方法上使用@ResponseStatus注解来定义异常时的响应状态码。
- 在方法上使用@ResponseStatus和@ExceptionHandler注解来同时定义异常时的响应状态码和异常处理。
Spring Security
Spring Security是Spring框架的一个安全框架,它提供了一种全面的安全服务,包括认证和授权。
Spring Security的主要作用是保护Web应用程序中的安全性和隐私性,它通过提供各种机制来实现在Web应用程序中进行身份验证和授权,以确保只有经过身份验证的用户才能访问Web资源。在安全性方面,Spring Security提供了以下功能:
- 认证:Spring Security可以通过多种方式来认证用户,包括基于表单、HTTP基本认证、OpenID、OAuth等。
- 授权:Spring Security提供了基于角色授权和基于资源授权两种授权方式。
- 会话管理:Spring Security提供了会话管理功能,可以跟踪用户会话、最大会话数限制等。
- 密码加密:Spring Security提供了一种密码加密机制,防止密码被窃取或破解。
- 防止CSRF和XSS攻击:Spring Security提供了防止跨站请求伪造和跨站脚本攻击的功能。
- 单点登录:Spring Security可以实现单点登录机制,使得用户只需要登录一次就可以访问多个应用程序。
总的来说,Spring Security可以帮助Web应用程序提供良好的安全性保障,降低安全风险,保护用户的数据和隐私。
Spring模板
Spring提供了许多模板,其中一些模板如下:
- JdbcTemplate:用于简化与JDBC的交互,处理底层细节。
- NamedParameterJdbcTemplate:用于具有命名参数的JDBC交互。
- RestTemplate:用于处理基于HTTP的远程调用。
- JMS Template:用于简化与JMS中间件的交互。
- Mail Template:用于发送电子邮件。
- MockMvc:用于测试Spring MVC应用程序的多个方面,包括控制器端点、返回值和异常处理等。
- Redis Template:用于与Redis数据库进行交互。
- Web Socket :用于实现WebSocket服务器和客户端。
这些模板都提供了一种简化与某些技术(如数据库、Web服务等)交互的方式,可以使开发人员更快速、高效地开发应用程序。
Spring核心特点
- 轻量级:Spring框架是非侵入式的,不强制框架特定的编程模型,从而使得应用程序代码更加清晰和易于维护。
- 控制反转(IoC)和依赖注入(DI):Spring 提供了一个容器,使对象之间的依赖关系变得非常清晰,从而方便进行配置和管理。
- 面向切面编程(AOP):Spring框架提供了AOP框架,可以使您轻松地实现跨越对象的业务功能,如事务管理、安全性、日志记录等。
- 组件化:Spring框架支持使用组件进行模块化开发,使得应用程序可以按照业务逻辑组织为不同的模块,是应用更加容易维护和扩展。
- 支持多种技术:Spring框架支持多种技术,如Web框架、ORM框架、集成框架、消息传递框架等,使得企业应用程序的开发更加容易和高效。
- 部署方便:Spring框架的轻量级和非侵入式特性使得其非常适合在不同的应用服务器上部署,并且可以很容易地进行集成测试和单元测试。
- 企业级支持:Spring框架最初的目标是为了简化企业级Java应用程序的开发,因此在开发企业级应用程序时,Spring框架提供了很多有用的功能和工具,如事务管理、安全性、缓存等。
BeanFactory和ApplicationContext区别
BeanFactory和ApplicationContext是Spring框架中两个不同的容器,它们有以下几点区别:
- 功能不同:BeanFactory是Spring的基础容器,提供基本的IOC和DI容器的功能,主要用于实现依赖注入和Bean的实例化等工作,而ApplicationContext在BeanFactory的基础上构建,提供了更全面的功能,例如国际化处理、AOP等。
- 加载方式不同:BeanFactory是延迟加载,只有在第一次访问Bean时才会实例化,也就是懒加载;而ApplicationContext是在启动时就实例化全部的Bean。
- 作用域不同:BeanFactory主要基于Bean的单例和原型具有不同的作用域,而ApplicationContext还支持其他的作用域,例如session作用域、请求作用域等。
- 消耗资源不同:由于ApplicationContext实例化所有的Bean,所以启动时需要的时间和资源也会更多。
- 开销不同:由于ApplicationContext支持更多的功能,并且预先实例化所有Bean,所以内存消耗会更高,程序启动的开销会更大。
总之,BeanFactory是Spring的基础容器,提供基本的IOC和DI的功能。ApplicationContext是在BeanFactory的基础上构建而成,提供了更多的功能,并且内部预先实例化所有的Bean,启动时开销比较高。因此,选择哪一个容器要根据实际需求来选择。如果需要更多的功能支持,可以考虑使用ApplicationContext;如果需要更轻量级的容器,可以考虑使用BeanFactory。
Spring中bean的作用域
在Spring中,bean的作用域用于控制bean实例的创建和销毁方式,以及在何时向Spring容器暴露实例。
Spring提供了以下五种作用域:
- 单例(singleton):在整个应用中只创建一个bean实例,所有请求都将返回同一个实例。
- 原型(prototype):每次请求创建新的bean实例,并不进行缓存或共享。
- 会话(session):在Web应用中,每一次会话创建一个新的bean实例。
- 请求(request):在Web应用中,每次请求创建一个新的bean实例。
- 全局会话(global session):在Web应用中,全局会话作用域类似于会话作用域,但只有在基于portlet的Web应用中才有意义。
除了以上五种作用域外,Spring还提供了一些扩展作用域,如Spring Security提供的安全作用域(“security”)、Spring WebSocket提供的WebSocket会话作用域(“websocket”)等。每一种作用域都有自己的生命周期,Spring容器通过AOP的技术来控制bean的生命周期和作用域。
Spring-Bean的线程安全问题
Spring框架中的Bean是否线程安全,取决于具体的bean实现和作用域。一般情况下,单例作用域的bean是线程不安全的,而原型作用域的bean则是线程安全的。
因为单例作用域的bean是在整个应用中只创建一个实例,所有请求都将返回同一个实例,如果多个线程同时访问该实例的共享数据,就会出现数据不同步,从而导致线程安全问题。针对单例作用域的bean线程安全问题,解决方案可以通过以下几种方式:
- 避免共享数据,让bean变得线程安全。
- 采用同步措施,保证线程安全,如synchronized关键字,ReentrantLock等。
- 将单例作用域的bean改为原型作用域的bean,每次请求都创建新的实例,从而避免多线程共享。
- 使用ThreadLocal将共享数据变成每个线程独立的数据,从而提高线程安全性。
需要注意的是,虽然原型作用域的bean在每次请求时都创建新的实例,但是如果这些实例仍然包含共享变量或状态,也可能会存在线程安全问题。因此,在编写线程安全的bean时,需谨慎考虑并尽可能避免共享状态。
Spring注入bean的方式
基于XML
在Spring中,基于XML配置文件注入bean的方式常常有以下几种:
1.构造函数注入:使用标签将参数值注入到bean的构造函数中, 例如如下配置:
<bean id="userService" class="com.example.UserService">
<constructor-arg value="admin" type="String"></constructor-arg>
<constructor-arg value="123456" type="int"></constructor-arg>
</bean>
2.属性注入:使用标签将值注入到bean的属性中,例如如下配置:
<bean id="userService" class="com.example.UserService">
<property name="userName" value="admin"></property>
<property name="password" value="123456"></property>
</bean>
3.注入对象实例:使用标签引用其他bean对象,例如如下配置:
<bean id="userDao" class="com.example.UserDao"></bean>
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"></property>
</bean>
4.注入集合类型:使用,
<bean id="userList" class="java.util.ArrayList">
<constructor-arg>
<list>
<value>admin</value>
<value>test</value>
<value>guest</value>
</list>
</constructor-arg>
</bean>
<bean id="userInfo" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="projectId" value="100"></entry>
<entry key="projectName" value="Spring Project"></entry>
</map>
</constructor-arg>
</bean>
<bean id="userSet" class="java.util.HashSet">
<constructor-arg>
<set>
<value>admin</value>
<value>test</value>
<value>guest</value>
</set>
</constructor-arg>
</bean>
<bean id="dbConfig" class="java.util.Properties">
<constructor-arg>
<props>
<prop key="url">jdbc:mysql://localhost:3306/test</prop>
<prop key="driver">com.mysql.jdbc.Driver</prop>
</props>
</constructor-arg>
</bean>
基于注解
在Spring中,基于注解的bean注入方式有以下几种:
- @Autowired注解
可以对属性、构造函数、方法等进行注入,Spring会自动查找和匹配合适的bean进行注入。例如:
@Service
public class UserService {
@Autowired
private UserDao userDao;
// ...
}
- @Resource注解
可以对属性、setter方法进行注入,也可以指定具体的bean名称进行注入。例如:
@Service
public class UserService {
@Resource(name = "userDao")
private UserDao userDao;
// ...
}
- @Qualifier注解
与@Autowired或@Resource同时使用,指定需要注入的bean的名称。例如:
@Service
public class UserService {
@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao;
// ...
}
- @Value注解
注入常量值或通过占位符引用配置文件中的值。例如:
@Service
public class UserService {
@Value("admin")
private String userName;
@Value("${jdbc.url}")
private String jdbcUrl;
// ...
}
- @Component注解
用于声明一个类是Spring管理的一个组件,可以与@Autowired注解一起使用,实现自动发现和注入。例如:
@Component
public class UserDaoImpl implements UserDao {
// ...
}
@Service
public class UserService {
@Autowired
private UserDaoImpl userDao;
// ...
}
除了以上几种,还有一些注解如@Repository、@Controller、@Configuration等,它们也都可以作为Spring组件进行注入和管理。
基于JavaConfig
在Spring框架中可以通过JavaConfig或XML配置文件来定义bean,这里以JavaConfig为例说明如何注入bean。
假设我们有两个类:Person
和Cat
,通过Config
类配置注入这两个bean:
public class Person {
private String name;
private int age;
// constructors, getters and setters...
}
public class Cat {
private String name;
// constructors, getters and setters...
}
@Configuration
public class Config {
@Bean
public Person person() {
Person person = new Person();
person.setName("Tom");
person.setAge(25);
return person;
}
@Bean Cat cat() {
Cat cat = new Cat();
cat.setName("Mimi");
return cat;
}
}
上面的Config
类中使用了@Configuration
注解,表示这个类是配置类,它可以定义BeanDefinition,并通过BeanFactory生成Bean实例。
在上面的例子中,person()
方法和cat()
方法分别使用了@Bean
注释,用于定义BeanDefinition,返回一个Person实例和一个Cat实例。
我们也可以在一个bean中使用另一个bean,这里假设我们想让Person
类包含一个Cat
类的实例:
public class Person {
private String name;
private int age;
private Cat cat;
// constructors, getters and setters...
public void setCat(Cat cat) {
this.cat = cat;
}
}
@Configuration
public class Config {
// ...
@Bean
public Person person() {
Person person = new Person();
person.setName("Tom");
person.setAge(25);
person.setCat(cat());
return person;
}
}
在person()
方法中,我们通过cat()
方法获取一个Cat
实例,并将其注入到Person实例中。