按照自己的需求,找到自己不会的地方去解决
Spring框架包含众多模块,如Core、Testing、Data Access、Web Servlet等,其中Core是整个Spring框架的核心模块。Core模块提供了IoC容器、AOP功能、数据绑定、类型转换等一系列的基础功能,而这些功能以及其他模块的功能都是建立在IoC和AOP之上的,所以IoC和AOP是Spring框架的核心。
IoC(Inversion of Control)是控制反转的意思,这是一种面向对象编程的设计思想。在不采用这种思想的情况下,我们需要自己维护对象与对象之间的依赖关系,很容易造成对象之间的耦合度过高,在一个大型的项目中这十分的不利于代码的维护。IoC则可以解决这种问题,它可以帮我们维护对象与对象之间的依赖关系,降低对象之间的耦合度。
说到IoC就不得不说DI(Dependency Injection),DI是依赖注入的意思,它是IoC实现的实现方式,就是说IoC是通过DI来实现的。由于IoC这个词汇比较抽象而DI却更直观,所以很多时候我们就用DI来代替它,在很多时候我们简单地将IoC和DI划等号,这是一种习惯。而实现依赖注入的关键是IoC容器,它的本质就是一个工厂。
AOP(Aspect Oriented Programing)是面向切面编程思想,这种思想是对OOP的补充,它可以在OOP的基础上进一步提高编程的效率。简单来说,它可以统一解决一批组件的共性需求(如权限检查、记录日志、事务管理等)。在AOP思想下,我们可以将解决共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方、什么时机调用。当满足调用条件时,AOP会将该业务代码织入到我们指定的位置,从而统一解决了问题,又不需要修改这一批组件的代码。
1.两种工厂的创建方式:
2.两种工厂的关系(区别)
底层图解:
依赖注入有三种常见的注入方式:构造函数注入、Setter方法注入和接口注入。下面分别展示这三种注入方式的代码体现。
构造函数注入:
public class MyApplication { private MessageService messageService; // 通过构造函数注入依赖 public MyApplication(MessageService messageService) { this.messageService = messageService; } public void processMessage(String message) { // 使用依赖对象 messageService.sendMessage(message); } }
Setter方法注入:
public class MyApplication { private MessageService messageService; // 通过Setter方法注入依赖 public void setMessageService(MessageService messageService) { this.messageService = messageService; } public void processMessage(String message) { // 使用依赖对象 messageService.sendMessage(message); } }
接口注入:
public interface MessageServiceInjector { MyApplication getApplication(); } public class EmailServiceInjector implements MessageServiceInjector { public MyApplication getApplication() { // 创建依赖对象 MessageService messageService = new EmailService(); // 创建应用程序并注入依赖 MyApplication application = new MyApplication(messageService); return application; } }
在接口注入中,我们定义了一个MessageServiceInjector
接口,它包含一个getApplication
方法,用于获取MyApplication
对象。然后,我们创建了一个EmailServiceInjector
类,实现了MessageServiceInjector
接口,并在getApplication
方法中创建了依赖对象messageService
,并将其注入到MyApplication
对象中。
通过以上三种方式,我们可以将依赖对象注入到目标类中,实现依赖的解耦和灵活替换。具体选择哪种注入方式,取决于具体的需求和设计。
动态代理于AOP基本逻辑关系:
OOP编程的垂直性造成代码冗余,不方便管理;
解决代码冗余这类问题的思维是AOP面向切面编程思想;
实现这一思想,就要用动态代理技术;代理技术直接使用很麻烦,所以不会直接用,而是使用spring Aop技术框架来解决该类问题,aop框架底层就用到动态代理技术。
通过AOP例子理解AOP
下面是一个简单的Java AOP代码示例:
定义一个切面类,实现切面逻辑:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void beforeAdvice() { System.out.println("Before executing the method"); } }
创建一个服务类,该类中的方法将被切面所拦截:
import org.springframework.stereotype.Service; @Service public class UserService { public void addUser(String username) { System.out.println("Adding user: " + username); } }
创建一个配置类,启用AOP并扫描切面和服务类:
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @EnableAspectJAutoProxy @ComponentScan(basePackages = "com.example") public class AppConfig { }
创建一个主类,加载配置类并获取服务类的实例:
import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MainApp { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); UserService userService = context.getBean(UserService.class); userService.addUser("John"); context.close(); } }
在上述示例中,切面类 LoggingAspect
使用 @Aspect
注解标记为切面,并使用 @Before
注解定义了一个前置通知,该通知会在执行 com.example.service
包下的任何方法之前执行。服务类 UserService
中的 addUser
方法会被切面所拦截,在方法执行之前会打印一条日志。配置类 AppConfig
使用 @EnableAspectJAutoProxy
注解启用了AOP,并通过 @ComponentScan
注解扫描了切面和服务类。主类 MainApp
加载配置类,并获取服务类的实例,然后调用 addUser
方法。
运行主类 MainApp
,将会输出以下内容:
Before executing the method Adding user: John
这表明切面逻辑在方法执行之前被调用,成功拦截了服务类的方法。
补充:成员方法才存在线程安全问题
spring中的bean如果是无状态的(一般情况是),则是线程安全的;
spring中的bean如果是有状态的,则需要标注@Scope("prototype"),意思是允许多个实例
相反的@Scope("singleton"),意思是IOC容器中只允许创建一个实例
在Spring框架中,一般需要在以下情况下使用事务:
数据库操作:当需要进行数据库的增删改操作时,可以使用事务来确保操作的一致性和完整性。例如,插入用户信息和同时插入用户订单信息,这两个操作应该放在同一个事务中,要么都成功,要么都失败。
多个服务方法调用:当一个服务方法中调用了多个其他服务方法,并且这些方法需要保证一致性,可以使用事务来统一管理。例如,用户下单操作需要调用库存服务和支付服务,这些服务操作应该在同一个事务中。
并发操作:当多个并发请求需要修改同一份数据时,需要使用事务来保证数据的一致性。例如,多个用户同时对同一商品进行抢购,需要使用事务来控制商品库存的减少和订单的生成。
异常处理:当方法执行过程中发生异常,需要回滚之前的操作,可以使用事务来实现回滚。例如,用户购买商品时,如果支付过程中发生异常,需要回滚订单和库存的操作。
总之,一般需要在涉及到数据库操作、多个服务方法调用、并发操作和异常处理的场景下使用事务。事务可以确保操作的一致性和完整性,提供可靠的数据访问和更新机制。
步骤:
开启事务:在执行一组操作之前,通过调用数据库的事务管理器来开启一个事务。
执行操作:在事务中执行一组相关的操作,如插入、更新、删除等。
提交事务:如果所有操作都成功执行,通过调用事务管理器的提交方法来提交事务,将操作的结果永久保存到数据库中。
回滚事务:如果有任何操作失败或发生异常,通过调用事务管理器的回滚方法来回滚事务,撤销已经执行的操作,保持数据库的一致性。
.spring支持编程式事务和声明式事务是实现事务管理的两种主要方式。(重点)**
编程式事务:编程式事务是通过编写代码来管理事务的方式。在编程式事务中,开发人员需要手动编写事务的开启、提交和回滚等逻辑。通常,编程式事务使用的是底层的事务管理API,如JDBC的事务管理API。以下是一个使用编程式事务的示例:
Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); // 开启事务 // 执行数据库操作 // ... conn.commit(); // 提交事务 } catch (SQLException e) { if (conn != null) { try { conn.rollback(); // 回滚事务 } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } finally { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
声明式事务:声明式事务是通过配置的方式来管理事务的方式。在声明式事务中,开发人员只需要在配置文件或注解中声明事务的属性,框架会自动根据配置来管理事务。常见的使用声明式事务的框架有Spring的事务管理机制。以下是一个使用声明式事务的示例:
@Transactional public void doTransaction() { // 执行数据库操作 // ... }
在上述示例中,使用了@Transactional
注解来声明事务,并将事务逻辑封装在doTransaction
方法中。框架会根据注解的配置来管理事务的开启、提交和回滚等操作。
总的来说,编程式事务需要手动编写事务管理的代码,灵活性较高,但工作量较大;而声明式事务通过配置的方式来管理事务,简化了开发人员的工作,但灵活性相对较低。选择使用哪种方式取决于具体的需求和项目的特点。
异常捕获处理,自己处理了异常,没有抛出,解决:手动抛出
抛出检查异常,配置rollbackFor属性为Exception
非public方法导致的事务失效,改为public
详细解释:
第一种:异常捕获处理,自己处理了异常,没有抛出,解决:手动抛出(左图改成右图)
第二种:抛出检查异常,配置rollbackFor属性为Exception
第三种:public方法导致的事务失效,改为public
可以解决spring中注入引起循环依赖问题
构造方法中的循环依赖问题需要配合@Lazy懒加载注解解决