一、spring简介
Spring框架是 Java 平台的一个开源的全栈(full-stack)应用程序框架和控制反转容器实现,一般被直接称为 Spring。它由Rod Johnson创建,去搜索了它的资料很难想象Rod Johnson之前是学音乐。该框架的一些核心功能理论上可用于任何 Java 应用,它还为基于Java企业版平台构建的 Web 应用提供了大量的拓展支持。Spring 没有直接实现任何的编程模型,但它已经在 Java 社区中广为流行,基本上完全代替了 企业级JavaBeans(EJB)模型。
简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
目的:解决企业应用开发的复杂性
功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
范围:任何Java应用
spring 的核心模块
- 控制反转(IoC):Spring通过IoC容器管理应用程序中的对象依赖关系。它将对象的创建、组装和管理工作交给了容器,开发人员只需要关注业务逻辑的实现。
- 面向切面编程(AOP):Spring支持AOP,可以将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高代码的模块化和可重用性。
- 数据访问支持:Spring提供了对各种数据访问技术的支持,包括JDBC、ORM(如Hibernate、MyBatis)、JPA等。它简化了数据访问层的开发,提供了事务管理和异常处理等功能。
- Web开发支持:Spring提供了一套全面的Web开发框架,包括Spring MVC和Spring WebFlux。它们可以帮助开发人员构建灵活、高效的Web应用程序。
- 安全性支持:Spring提供了一套强大的安全性框架,可以用于认证、授权和安全性管理。它支持各种认证方式,如基于表单、基于HTTP基本认证、基于OAuth等。
此外,Spring还提供了许多其他功能和扩展,如缓存支持、消息队列、调度任务等。它的模块化设计使得开发人员可以根据需要选择使用的功能,从而提高开发效率。
二、IoC 的概念
2.1 IoC 详解
- Spring框架中的IoC(Inversion of Control,控制反转)是指将对象的创建、依赖关系的管理和对象的生命周期等控制权从应用程序代码中转移到框架中。在IoC的概念下,对象的创建和管理不再由开发人员直接控制,而是由Spring容器来负责。
- IoC的核心思想是通过依赖注入(Dependency Injection,DI)来实现对象之间的解耦。依赖注入是指在对象创建的过程中,将其所依赖的其他对象的引用注入到对象中,从而实现对象之间的关系建立和协作。通过依赖注入,对象不再负责自己依赖对象的创建和查找,而是由容器来完成。
- Spring框架中的IoC容器负责管理对象的生命周期和依赖关系。它通过配置文件(如XML配置文件)或注解的方式描述对象之间的依赖关系,并在应用程序启动时将这些对象实例化并组装起来。开发人员只需要定义对象的行为,而无需关心对象的创建和管理过程。
2.2 IoC的好处
- 解耦:通过依赖注入,对象之间的依赖关系被明确地声明在配置文件或注解中,使得对象之间的耦合度降低,提高了代码的可维护性和可测试性。
- 简化对象的创建和管理:由容器负责对象的创建和生命周期管理,开发人员只需关注对象的行为逻辑,而无需手动管理对象的创建和销毁。
- 提供灵活的配置方式:通过配置文件或注解,可以方便地修改对象之间的依赖关系,实现灵活的配置和组装。
- 支持AOP(Aspect-Oriented Programming):Spring框架提供了面向切面编程的支持,可以通过IoC容器将横切逻辑(如日志、事务管理等)与业务逻辑分离,提高了代码的模块化和可维护性。
2.3 谈谈你对IoC的理解(面试题)
- Spring框架中的IoC(控制反转)通过依赖注入实现对象之间的解耦,将对象的创建和管理交由容器负责。使用IoC容器可以简化对象的创建和管理过程,提高代码的可维护性和可测试性。通过灵活的配置方式,可以方便地修改对象之间的依赖关系。同时,IoC容器的存在也为AOP提供了支持,使得横切逻辑与业务逻辑分离,提高了代码的模块化和可维护性。
三、IOC的三种注入方式
3.1、构造方法注入
构造函数注入(Constructor Injection):通过构造函数来注入依赖对象。在类的构造函数中声明依赖对象的参数,并在容器中配置时提供相应的依赖对象。当创建对象实例时,容器会自动将依赖对象传递给构造函数,完成依赖注入。
代码示例:
1. 编写一个接口定义修改用户信息的方法
package com.wh.ioc.service; /** * @author wh * @create 2023-08-16 15:43 */ public interface UserService { /** * 方法功能:修改用户信息 */ public void edit(); }
2. 编写一个service类来实现这个接口中的方法
package com.wh.ioc.service.impl; import com.wh.ioc.service.UserService; /** * @author wh * @create 2023-08-16 15:45 * */ public class UserServiceImpl1 implements UserService { @Override public void edit() { System.out.println("更改了用户的信息"); } }
3. 编写控制器(action)进行调用并编写无参和有参构造方法
package com.wh.ioc.web; import com.wh.ioc.service.UserService; /** * @author wh * @create 2023-08-16 15:47 * */ public class UserAction { private UserService userService; public UserAction() { } public UserAction(UserService userService) { this.userService = userService; } public String edits(){ userService.edit(); return "list"; } }
4.编写spring.xml配置文件,用于定义和配置Spring应用程序的组件、依赖关系、行为和其他相关配置。它是Spring框架的核心配置文件之一。
定义Bean:在spring.xml文件中,可以使用XML配置元素来定义应用程序中的Bean对象。通过配置Bean的类路径、构造函数,Spring容器可以根据配置文件中的定义来创建和管理Bean对象。
配置依赖注入:spring.xml文件可以指定Bean之间的依赖关系,通过配置构造函数注入,将依赖的Bean (userService) 注入到目标Bean (userAction) 中,实现依赖注入。这样可以实现对象之间的解耦,提高代码的灵活性和可测试性。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.wh.ioc.web.UserAction" id="userAction"> <constructor-arg name="userService" ref="userService"></constructor-arg> </bean> <bean class="com.wh.ioc.service.impl.UserServiceImpl1" id="userService"></bean> </beans>
3.2、setter方法注入
Setter方法注入(Setter Injection):通过setter方法来注入依赖对象。在类中定义相应的setter方法,并在容器中配置时使用相应的属性或标签指定依赖对象。当创建对象实例后,容器会调用相应的setter方法,将依赖对象注入到对象中。
代码示例:
package com.wh.ioc.web; import com.wh.ioc.service.UserService; /** * @author wh * @create 2023-08-16 18:40 * */ public class UserAction { private UserService userService; public UserService getUserService() { return userService; } public void setUserService(UserService userService) { this.userService = userService; } public String edits(){ userService.edit(); return "list"; } }
xml文件修改bean的注入方式((property:属性的意思)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.wh.ioc.web.UserAction" id="userAction"> <property name="userService" ref="userService"></property> <bean class="com.wh.ioc.service.impl.UserServiceImpl1" id="userService"></bean> </beans>
3.3、byName(按名称自动装配)自动装配
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。
测试:
1、修改bean配置,增加一个属性 autowire=“byName”
<beans xmlns="http://www.springframework.org/schema/beans" default-autowire="byName" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
2、再次测试,结果依旧成功输出!
3、我们将 userservies的bean id修改为 servie
4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setgoodsAction就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结:
当一个bean节点带有 autowire byName的属性时
或是beans节点带有 default-autowire="byName"。
将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
去spring容器中寻找是否有此字符串名称id的对象。
如果有,就取出注入;如果没有,就报空指针异常。
3.4:byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException
测试:
1、将user的bean配置修改一下 : autowire=“byType”
2、测试,正常输出
3、在注册一个goodsAction的bean对象!
<bean class="com.wh.ioc.GoodsAction" id="goodsAction" autowire="byType"> </bean> <bean class="com.wh.ioc.UserAction" id="userAction" autowire="byType"> <property name="userService" ref="serviceImpl2"></property> </bean> <bean class="com.wh.ioc.service.impl.UserServiceImpl2" id="serviceImpl2"></bean> <bean class="com.wh.ioc.service.impl.UserServiceImpl1" id="serviceImpl1"></bean>
4、测试,报错:NoUniqueBeanDefinitionException
5、在这个配置中,我们定义了两个UserService的实现类 UserServiceImpl1和 UserServiceImpl2,并且将UserAction的autowire属性设置为byType,表示使用byType进行自动装配。
现在,如果我们运行这个程序,将会抛出一个异常,提示存在多个匹配的 bean,无法确定要注入哪个 bean。这是因为 UserAction类依赖于UserService接口,而容器中存在两个与 UserService接口匹配的 bean,即 UserServiceImpl1和 UserServiceImpl2,导致无法确定要注入哪个 bean。删除掉一个就好了
四、3.4 spring上下文与tomcat整合
在用户每一次发送任意请求,在对应请求处理代码中可以获取到spring容器中配置的任意的JavaBean,从而获取对应的javabean中定义的方法。
实现思路:
- 在tomcat启动的时候自动去加载spring的上下文(ApplicationContext)
- 利用监听器去把spring的上下文放到tomcat的上下文中
- 为了解决框架的配置文件冲突的问题,我们需要动态指定spring上下文的配置文件名称;
代码示例
配置servlet监听器
package com.wh.ioc.listener; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; /** * @author wh * @create 2023-08-16 19:25 */ @WebListener public class SpringLoadListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { //加载spring核心配置文件(建模,这个spring的上下文对象),上下文对象中可以获取任何javabean对象 ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml"); //获取tomcat上下文 ServletContext servletContext = sce.getServletContext(); //把srping的上下文放入tomcat中 servletContext.setAttribute("springContext",context); } }
编写servelt测试
package com.wh.ioc.web; import com.wh.ioc.service.UserService; import org.springframework.context.ApplicationContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author wh * @create 2023-08-16 19:29 */ @WebServlet("/userList") public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //当处理用户请求的时候,获取spring的上下文对象 ApplicationContext springContext = (ApplicationContext ) req.getServletContext().getAttribute("springContext"); //拿到userService的Bean UserService userAction = (UserService) springContext.getBean("userService"); System.out.println(userAction); userAction.edit(); } }
总结:
Spring框架提供了三种主要的依赖注入方式:构造函数注入、Setter方法注入和字段注入。构造函数注入通过构造函数来注入依赖对象,Setter方法注入通过setter方法来注入依赖对象,字段注入通过直接注入字段来实现依赖注入。这些注入方式可以单独使用,也可以结合使用,具体选择可以根据实际需求和个人偏好来决定。