# spring(SpringlOC、SpringDI、常用注解、整合junit、SpringAOP) # 1.课程介绍 - Spring相关的介绍 - IOC思想 - DI依赖注入 - 案例演示 # 2.spring家族 官网:\[https://spring.io\](https://spring.io/) 1 Spring能做什么:用以开发web、微服务以及分布式系统等,光这三块就已经占了JavaEE开发的九成多。 2 Spring并不是单一的一个技术,而是一个大家族,可以从官网的\`Projects\`中查看其包含的所有技术。 !\[image-20250922165719466\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250922165719.png) 今天我们需要关注的是Spring Framework !\[image-20250922205217042\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250922205217.png) \`\`\` (1)核心层 - Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块 (2)AOP层 - AOP:面向切面编程,它依赖核心层容器,目的是==在不改变原有代码的前提下对其进行功能增强== - Aspects:AOP是思想,Aspects是对AOP思想的具体实现 (3)数据层 - Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术 - Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis - Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容 (4)Web层 - 这一层的内容将在SpringMVC框架具体学习 (5)Test层 - Spring主要整合了Junit来完成单元测试和集成测试 \`\`\` # 3.spring核心 1 Spring 是一个开源的 Java 应用框架,主要用于构建企业级应用程序 2 spring框架作用:简化开发、框架整合 3 两大核心技术:\*\*IOC(控制反转)/DI(依赖注入)、AOP(面相切面编程)\*\* # 4.spring入门 第一步:用idea创建一个maven项目 第二步:导入项目依赖 \`\`\`xml 4.0.0 org.wn spring_demo1 1.0-SNAPSHOT 17 17 org.springframework spring-context 5.3.7 junit junit 4.12 test \`\`\` 第三步:创建配置文件 1.在resources目录下面创建一个applicationContext.xml 内容如下: \`\`\`xml \`\`\` 第四步:准备 \`\`\`java public class MyBean { public void hello(){ System.out.println("hello spring..."); } } \`\`\` 第五步:将写好得类配置到applicationContext.xml中 \`\`\`xml \`\`\` 1. 大家要记得,Spring是一个容器,我们需要把我们的类交给Spring去管理。 因为,我们的测试是创建一个普通的类,然后再通过Spring帮我们把这个类的对象创建出来就算是成功了; 2. 在配置文件中将这个Java类交给Spring管理。在applicationContext.xml中配置 3. 元素和属性讲解: \`\`\` bean元素:表示对象配置或注册标签; id属性:这个bean对象在Spring容器中的唯一标识,也可以使用name,常用id(唯一特性),获取这个对象的时候就可以通过这个表示来获取; class属性:对应对象所属类的完全限定名。注意这里可以是JDK自带的类,也可以是自己新建的类; \`\`\` 注意:Spring容器中不允许有两个名字【不管是id指定还是name指定】一个的对象 第六步:测试 \`\`\`java @Test public void test(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Object mybean = context.getBean("mybean"); System.out.println(mybean); } \`\`\` # 5.IOC案例 ## 1.编写dao层和service层 面向接口编程原则 解耦合:通过接口定义契约,实现类具体实现,降低模块间的耦合度 可替换性:可以轻松替换不同的实现类,而不需要修改调用方代码 !\[image-20250922224758386\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250922224758.png) UserDao: \`\`\`java public interface UserDao { } \`\`\` UserDaoImpl: \`\`\`java public class UserDaoImpl implements UserDao { } \`\`\` UserService: \`\`\`java public interface UserService { } \`\`\` UserServiceImpl: \`\`\`java public class UserServiceImpl implements UserService { } \`\`\` ## 2.编写配置文件 \`\`\`xml \`\`\` ## 3.测试 \`\`\`java package com.woniu; import com.woniu.dao.UserDao; import com.woniu.service.UserService; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppTest { @Test public void test(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao)context.getBean("userDao"); //根据bean的id从容器中获取bean UserService userService=(UserService) context.getBean("userService"); //或者通过字节码获得,前置,必须保证该类型对应的bean对象只能有一个 // UserService userService=context.getBean(UserService.class); System.out.println(userDao); System.out.println(userService); } } \`\`\` # 6.DI案例 ## 6.1 service和dao层都添加save方法 UserDao: \`\`\`java public interface UserDao { public void save(); } \`\`\` UserDaoImpl: \`\`\`java public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("save..."); } } \`\`\` UserService: \`\`\`java public interface UserService { public void save(); } \`\`\` UserServiceImpl: \`\`\`java public class UserServiceImpl implements UserService { @Override public void save() { userDao.save(); } } \`\`\` ## 6.2建立依赖关系-类 (service依赖dao) \`\`\`java public class UserServiceImpl implements UserService { //依赖dao private UserDao userDao; //提供对应的set方法 public void setUserDao(UserDao userDao) { this.userDao = userDao; } \`\`\` ## 6.3建立依赖关系-xml (service依赖dao) \`\`\`xml \`\`\` ## 6.4测试 \`\`\`java @Test public void test(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService=(UserService) context.getBean("userService"); userService.save(); } \`\`\` 解释匹配关系:去掉set首字母小写 !\[image-20250922230305691\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250922230305.png) # 7.Bean ## 7.1bean的基础配置 \`\`\` id:通过id值可以获取对应的bean,一个容器中id值唯一 class:bean的类型 \`\`\` ## 7.2 bean作用范围scope配置 \`\`\` scope的值如下: 1 singleton 单例(默认) 2 prototype 多例 bean为单例的意思是在Spring的IOC容器中只会有该类的一个对象 bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高 \* 为什么bean默认为单例? \* bean为单例的意思是在Spring的IOC容器中只会有该类的一个对象 \* bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高 \* bean在容器中是单例的,会不会产生线程安全问题? \* 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的, \* 因为所有请求线程共用一个bean对象,所以会存在线程安全问题。 \* 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的, \* 因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。 \`\`\` 目前的service为线程安全的 !\[image-20250922232517018\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250922232517.png) 改为线程不安全的 !\[image-20250922232603331\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250922232603.png) \`\`\` 可能出现的问题: 多个线程同时执行 count++ 操作 count++ 不是原子操作,包含读取、增加、写入三个步骤 可能导致计数不准确 数据不一致: 线程A读取count=5 线程B也读取count=5 线程A执行count++,count变为6 线程B执行count++,count仍变为6(而不是7) \`\`\` ## 7.3 测试单例和多例 保持scope为单例: \`\`\`xml \`\`\` 测试: \`\`\`java @Test public void test(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService=(UserService) context.getBean("userService"); UserService userService2=(UserService) context.getBean("userService"); System.out.println(userService==userService2); //true } \`\`\` 更改scope为多例: \`\`\`xml \`\`\` 测试: \`\`\`java @Test public void test(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService=(UserService) context.getBean("userService"); UserService userService2=(UserService) context.getBean("userService"); System.out.println(userService==userService2); //false } \`\`\` ## 7.3 bean的生命周期控制 (了解) bean对象从创建到销毁的整体过程。 而所谓的bean的生命周期控制指的是在bean创建后到销毁前做一些事情。 UserServiceImpl: \`\`\`java public class UserServiceImpl implements UserService { //依赖dao private UserDao userDao; //提供对应的set方法 public void setUserDao(UserDao userDao) { this.userDao = userDao; } //构造方法 public UserServiceImpl(){ System.out.println("构造方法"); } //初始化 public void init(){ System.out.println("init"); } //销毁 public void destroy(){ System.out.println("destroy"); } @Override public void save() { userDao.save(); } } \`\`\` applicationContext.xml \`\`\`xml \`\`\` AppTest: \`\`\`java ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService=(UserService) context.getBean("userService"); userService.save(); //关闭容器 context.close(); \`\`\` !\[image-20250922234734676\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250922234734.png) 真实环境中容器不会自动关闭,需要通过适当的方式手动或被动关闭。 # 8.DI注入方式和类型 ## 8.1注入方式 ### 1 属性setter注入 --DI案例已经学过 类: !\[image-20250923001352695\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250923001352.png) xml: !\[image-20250923001420296\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250923001420.png) ### 2 构造器注入(官方推荐) 类: \`\`\`java public class UserServiceImpl implements UserService { private UserDao userDao; public UserServiceImpl( UserDao userDao){ this.userDao=userDao; } \`\`\` xml: \`\`\`xml \`\`\` 对应关系: !\[image-20250923001904470\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250923001904.png) 顺序: userDao先成为bean,然后通过UserServiceImpl中的构造方法的参数传入,注入到UserServiceImpl中的属性中 ## 官方推荐构造注入而非属性注入的原因 ### 1.不可变性保证 构造注入可以为要注入的属性添加final,而属性注入不行(避免了对象状态在生命周期中被意外改变的风险) \`\`\`java public class UserServiceImpl implements UserService { private final UserDao userDao; public UserServiceImpl( UserDao userDao){ this.userDao=userDao; } \`\`\` ### 2.完整性约束 确保对象在构造完成后处于完全初始化状态(避免了对象创建后某些依赖仍未注入的情况) 但其实工作中,用哪一种都行,以上的风险几乎不大可能发生(属性注入用起来还简单点) ## 8.2 注入数据的类型 简单类型:(了解) 类: \`\`\`java public class UserServiceImpl implements UserService { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } @Override public void save() { System.out.println(this.name+":"+this.age); } \`\`\` xml: \`\`\`java AppTest: @Test public void test(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService=(UserService) context.getBean("userService"); userService.save(); } \`\`\` 引用类型:我们之前的DI案例注入的userDao就是引用类型 # 9 DI之自动装配 IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配 自动装配方式:按类型(最常用)、按名称、按构造方法 ## 9.1案例-按类型: 类: \`\`\`java public class UserServiceImpl implements UserService { private UserDao userDao; //提供对应的set方法 public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void save() { userDao.save(); } \`\`\` xml: \`\`\`xml \`\`\` AppTest: \`\`\`java ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService=(UserService) context.getBean("userService"); userService.save(); \`\`\` ==注意事项:== \* 需要注入属性的类中对应属性的setter方法不能省略 \* 被注入的对象必须要被Spring的IOC容器管理 \* 按照类型在Spring的IOC容器中如果找到多个对象,会报\`NoUniqueBeanDefinitionException\`,如果在找到多个对象的场景中依然想要注入成功,则只能按照名称注入: 模拟报错: 1 新增userDao的实现类 UserDaoImpl2 !\[image-20250923003150727\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250923003150.png) 2 xml配置: \`\`\`xml \`\`\` 3 运行AppTest:报错: \`\`\`cmd 警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in class path resource \[applicationContext.xml\]: Unsatisfied dependency expressed through bean property 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.woniu.dao.UserDao' available: expected single matching bean but found 2: userDao2,userDao \`\`\` ## 9.2案例-按名字: 类: \`\`\`java public class UserServiceImpl implements UserService { private UserDao userDao; //提供对应的set方法 public void setUserDao2(UserDao userDao) { this.userDao = userDao; } @Override public void save() { userDao.save(); } \`\`\` xml: \`\`\`xml \`\`\` AppTest: \`\`\`java @Test public void test(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService=(UserService) context.getBean("userService"); userService.save(); } \`\`\` 对应关系:去掉set首字母小写 !\[image-20250923003741585\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250923003741.png) ## 9.3 总结 1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作 2. 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用 3. 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用 4. 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效 ## 9.4 集合注入(了解) 类: \`\`\`java public class UserServiceImpl implements UserService { private int\[\] array; private List list; private Map map; public void setArray(int\[\] array) { this.array = array; } public void setList(List list) { this.list = list; } public void setMap(Map map) { this.map = map; } @Override public void save() { System.out.println("遍历数组:" + Arrays.toString(array)); System.out.println("遍历List" + list); System.out.println("遍历Map" + map); } \`\`\` xml: \`\`\`xml 100 200 300 aa bb cc dd \`\`\` AppTest: \`\`\`java @Test public void test(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService=(UserService) context.getBean("userService"); userService.save(); } \`\`\` # 10.注解的支持 修改 \`applicationContext.xml\`,添加 \`context\` 命名空间和组件扫描 \`\`\`xml \`\`\` ## 10.1放入容器的注解 \| 注解 \| 作用 \| 说明 \| \| :------------ \| :------------- \| :----------------------------------------------------------- \| \| \`@Component\` \| 通用组件 \| 标注一个类会被 Spring 容器自动扫描并注册为 Bean \| \| \`@Service\` \| 业务层组件 \| 语义化标注 Service 层,功能同 \`@Component\` \| \| \`@Repository\` \| 数据访问层组件 \| 同时提供数据库异常转换(需配置 \`PersistenceExceptionTranslationPostProcessor\` 或配合 JPA/MyBatis 时有效) \| \| \`@Controller\` \| Web 层组件 \| 虽用于 Spring MVC,但仅 \`spring-context\` 也能识别,只是没有 Web 功能 \| ## 10.2从容器里面取对象的注解 \| 注解 \| 作用 \| 说明 \| \| :----------- \| :------------------------------ \| :----------------------------------------------------------- \| \| \`@Autowired\` \| 按类型自动装配 \| 可用于构造器、setter 方法、字段、甚至任意方法参数 \| \| \`@Qualifier\` \| 按名称装配(配合 \`@Autowired\`) \| 解决多个同类型 Bean 的歧义 \| \| \`@Value\` \| 注入外部属性值 \| 支持 SpEL 表达式,如 \`@Value("${jdbc.url}")\`,需配合 \`PropertySourcesPlaceholderConfigurer\` \| \| \`@Resource\` \| 按名称装配 \| JSR-250 注解,Spring 也支持,优先按名称查找 \| \| \`@Inject\` \| 按类型装配 \| JSR-330 注解,需额外引入 \`javax.inject\` 包,但 Spring 也能处理 \| ## 10.3Java 配置类(代替 XML) \| 注解 \| 作用 \| 说明 \| \| :---------------- \| :----------------------- \| :----------------------------------------- \| \| \`@Configuration\` \| 声明一个类为配置类 \| 内部可包含 \`@Bean\` 方法 \| \| \`@Bean\` \| 在配置类中声明一个 Bean \| 方法返回值对象会被注册到容器 \| \| \`@ComponentScan\` \| 指定包扫描路径 \| 等同于 XML 中的 \`\` \| \| \`@PropertySource\` \| 加载 properties 配置文件 \| 配合 \`@Value\` 或 \`Environment\` 使用 \| \| \`@Import\` \| 导入其他配置类 \| 用于模块化配置 \| \| \`@ImportResource\` \| 导入 XML 配置文件 \| 混合配置时使用 \|
01_spring(SpringlOC、SpringDI、常用注解、整合junit、SpringAOP)
https://xiaochenblog.icu/archives/eb4c6473-53e3-4a42-9ae2-141eee12692d
评论