Java匹马行天下之J2EE框架开发——Spring—>用IDEA开发Spring程序(01)
一、心动不如行动
一、创建项目
*注:在IDEA中我创建的Maven项目,不了解Maven的朋友可以看我之前的博客“我们一起走进Maven——知己知彼”,了解Maven后可以看我之前的博客“Maven的安装与配置”,自行安装,行动起来吧。
二、加载依赖
在pom.xml文件中添加Spring依赖和日志相关依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <dependencies> <!--测试相关--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version> 4.11 </version> <scope>test</scope> </dependency> <!--Spring核心基础依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version> 5.0 . 2 .RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version> 5.0 . 2 .RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version> 5.0 . 2 .RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version> 5.0 . 2 .RELEASE</version> </dependency> <!--日志相关--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version> 1.2 </version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version> 1.2 . 17 </version> </dependency> </dependencies> |
三、项目结构
在main目录下面创建java和resources目录
四、基础代码实现(练手)
- 创建TestDao接口和实现类
- 获得TestDao实现类的实例
- 在之前开发中,我们是直接new一个对象即可。即:`private TestDao dao = new TestDaoImpl();`
- 在学习Spring之后,将由Spring来创建对象的实例 --> 即:`IoC 控制反转(Inverse of Control)`
之后需要实例对象时,从Spring工厂(容器)中获得即可,需要将实现类的全限定名称配置到xml文件中。
创建dao包,在dao包下创建TestDao接口和TestDao接口的实现类,结构如下图:
TestDao接口代码示例:
1 2 3 4 5 | package dao; public interface TestDao { public void sayHello(); } |
TestDaoImpl实现类代码示例:
1 2 3 4 5 6 7 8 | package dao; public class TestDaoImpl implements TestDao{ @Override public void sayHello() { System.out.println( "Hello,Spring!" ); } } |
在resources资源目录点击右键,依次选择New-->XML Configuration File-->Spring Config,创建applicationContext.xml的配置文件
1 2 3 4 5 6 | <?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" > <!--把testDao对象的创建权交给Spring--> <!-- <bean> 配置需要创建的对象<br> id :用于之后从Spring容器中获得实例时使用的<br> class :需要创建实例的全限定类名--><bean id= "testDao" class = "dao.TestDaoImpl" ></bean> </beans> |
创建test包,在test包下创建测试类SpringTest
1 2 3 4 5 6 | package test; import dao.TestDao; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { @Test <br> public void demo1(){<br> // 之前开发,自己手写new出对象<br> TestDao dao= new TestDaoImpl();<br> dao.sayHello();<br>}<br><br>@Test<br>public void demo2() {<br> // 现在从spring容器中获得对象实例<br> // 1 、获得容器<br> //初始化Spring容器ApplicationContext,加载配置文件<br> ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");<br> // 2、获得内容 ,注意此时不需要自己new出对象了,都是从spring容器中获得<br> //通过容器获取testDao实例<br> TestDao testDao = (TestDao) application.getBean("testDao");<br> testDao.sayHello();<br>}} |
- IoC(Inverse of Control)反转控制的概念,就是将原本在程序中手动创建TestDaoImpl对象的控制权,交由Spring框架管理。
- 简单说,就是创建TestDaoImpl对象的控制权被反转到了Spring框架。
点击测试方法左侧的运行按钮,选择Run,测试代码
运行后控制台显示结果
项目运行成功!!!!!!!
五、Spring入门案例:DI(掌握)
- DI :Dependency Injection :依赖注入
- is a :是一个,继承。
- has a:有一个,成员变量,依赖。
1 2 3 4 5 6 | class B { private A a; // B类依赖A类,B类使用A类。 } 依赖:一个对象需要使用另一个对象。 注入:通过setter方法进行另一个对象实例设置。 |
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class BookServiceImpl { // 之前开发:接口 = 实现类(service和dao耦合了,写死了,知道具体的实现类是谁,那么我的具体实现类变化,那么这行代码也得跟着变) // private BookDao bookDao = new BookDaoImpl(); // spring之后(解耦:service实现类使用了dao的接口,这样就不知道具体的实现类是谁了) private BookDao bookDao; setter方法 } 模拟spring执行过程 创建service实例:BookService bookService = new BookServiceImpl(); => IoC <bean> 创建dao实例:BookDao bookDao = new BookDaoImple(); => IoC 将dao设置给service:bookService.setBookDao(bookDao); => DI <property> |
具体代码实现:
实现步骤:
- 创建BookDao接口和实现类
- 创建BookService接口和实现类
- 将dao和service配置到 applicationContext.xml文件中
- 使用api测试
项目结构:
加载依赖:
在pom.xml文件中添加Spring依赖和日志相关依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <dependencies> <!--测试相关--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version> 4.11 </version> <scope>test</scope> </dependency> <!--Spring核心基础依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version> 5.0 . 2 .RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version> 5.0 . 2 .RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version> 5.0 . 2 .RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version> 5.0 . 2 .RELEASE</version> </dependency> <!--日志相关--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version> 1.2 </version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version> 1.2 . 17 </version> </dependency> </dependencies> |
BookDao接口和实现类
1 2 3 4 5 | package dao; public interface BookDao { void save(); } |
1 2 3 4 5 6 7 8 | package dao; public class BookDaoImpl implements BookDao { @Override public void save() { System.out.println( "实现添加功能" ); } } |
BookService接口和实现类
1 2 3 4 5 | package Service; public interface BookService { void addBook(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package Service; import dao.BookDao; import dao.BookDaoImpl; public class BookServiceImpl implements BookService { //方式一:之前,接口=实现类 //private BookDao bookDao= new BookDaoImpl(); //方式二:现在,接口+setter方法 private BookDao bookDao; public void setBookDao(BookDao bookDao){ this .bookDao=bookDao; } @Override public void addBook() { this .bookDao.save(); } } |
将dao和service配置到 applicationContext.xml文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?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> 配置需要创建的对象 id :用于之后从Spring容器中获得实例时使用的 class :需要创建实例的全限定类名--> <!-- 模拟spring执行过程 创建service实例:BookService bookService = new BookServiceImpl(); => IoC <bean> 创建dao实例:BookDao bookDao = new BookDaoImple(); => IoC 将dao实例设置给service实例:bookService.setBookDao(bookDao); => DI <property> <property> 用于进行属性注入 name : Bean的属性名称,通过setter方法获得 setBookDao => BookDao => bookDao ref :另一个Bean的id值的引用 --> <!-- 创建service实例 --> <bean id= "bookServiceId" class = "Service.BookServiceImpl" > <!-- 将dao实例设置给service实例 --> <property name= "bookDao" ref= "bookDaoId" ></property> <!-- 用于进行属性注入 --> </bean> <!-- 创建dao实例 --> <bean id= "bookDaoId" class = "dao.BookDaoImpl" ></bean> </beans> |
使用api测试
创建test包,在test包下创建测试类SpringTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package test; import Service.BookService; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { @Test public void Test1(){ // 之前开发,自己手写new出对象 // BookService bookService = new BookServiceImpl(); // bookService.addBook(); } @Test public void Test2(){ // 现在从spring容器中获得对象实例 // 1 、获得容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" ); // 2、获得内容 ,注意此时不需要自己new出对象了,都是从spring容器中获得 BookService bookService = (BookService) applicationContext.getBean( "bookServiceId" ); bookService.addBook(); } } |
- DI:Dependency Injection 依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件。
- getBean("bookServiceId"); 从spring容器中获得指定名称对象的实例时,会先判断本实例对象是否需要使用其他实例化对象,由于设置了< property name="bookDao" ref="bookDaoId">< /property>,说明需要使用其他实例化对象,所以就根据其他Bean的id值的引用,去spring容器中获得指定名称对象的实例,相当于将dao实例设置给service实例。
运行后控制台显示结果
这就成功了,开心一下,通过这两个案例,多Spring有了初步的理解,加油!
六、Spring的核心API(了解)
api整体了解即可,之后不使用,在学习过程需要。
- BeanFactory :这是一个
工厂
,用于生成任意Bean。采取延迟加载,第一次调用getBean(); 时才会初始化Bean。(即实例化对象)
- ApplicationContext :是BeanFactory的子接口,功能更强大。(国际化处理、事件传递、Bean自动装配、各种不同应用层的Context实现)。
采取非延时加载,当配置文件被加载时,就进行对象的实例化。
- ClassPathXmlApplicationContext 用于加载classpath(类路径/src)下的xml
- 加载xml运行时位置 --> /WEB-INF/classes/xxx.xml
- FileSystemXmlApplicationContext 用于加载指定盘符下的xml
- 加载xml运行时位置 --> /WEB-INF/xxx.xml
- 通过java web学习过的 ServletContext.getRealPath(); 获得具体盘符
- ClassPathXmlApplicationContext 用于加载classpath(类路径/src)下的xml
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | package test; import Service.BookService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; public class SpringTest { @Test public void Test1(){ // 之前开发,自己手写new出对象 // BookService bookService = new BookServiceImpl(); // bookService.addBook(); } @Test public void Test2(){ // 现在从spring容器中获得对象实例 // 1 、获得容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" ); // 采取非延时加载,当配置文件被加载时,就进行对象的实例化。 // 2、获得内容 ,注意此时不需要自己new出对象了,都是从spring容器中获得 BookService bookService = (BookService) applicationContext.getBean( "bookServiceId" ); bookService.addBook(); } @Test public void demo3() { // 现在从spring容器中获得对象实例,使用的是BeanFactory,里面需要一个Resource,该Resource又是一个接口,需要找它的实现类ClassPathResource // 1 、获得容器 BeanFactory beanFactory = new XmlBeanFactory( new ClassPathResource( "applicationContext.xml" )); // 2、获得内容 ,注意此时不需要自己new出对象了,都是从spring容器中获得 BookService bookService = (BookService) beanFactory.getBean( "bookServiceId" ); // 采取延迟加载,第一次调用getBean(); 时才会初始化Bean(即实例化对象)。 bookService.addBook(); } } |
七、装配Bean:基于XML
3种bean实例化方式:
- 使用`默认构造方法`实例化
- 使用`静态工厂方法`实例化
- 使用`实例工厂方法`实例化
使用默认构造方法实例化
格式:
1 2 | <bean id= "从Spring容器中获得实例时使用的" class = "需要创建实例的全限定类名" ></bean> 例如:<bean id= "userServiceId" class = "Service.UserServiceImpl" ></bean> |
示例中用到的 UserService.java 和 UserServiceImpl.java 代码同上面实例中的代码,这里不再赘述!
在spring容器中进行配置:
1 | applicationContext.xml |
1 2 3 4 5 6 | <?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 的方式 :使用默认构造方法实例化,即要实例化的Bean必须要提供默认构造方法 --> <bean id= "userServiceId" class = "Service.UserServiceImpl" ></bean></beans> |
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class TestIoC { @Test public void demo01() { // 之前开发,自己手写new出对象 UserService userService = new UserServiceImpl(); // 直接手动创建实例 userService.addUser(); } @Test public void demo02() { // 现在从spring容器中获得对象实例 // 1 、获得容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" ); // 2、获得内容 ,注意此时不需要自己new出对象了,都是从spring容器中获得 UserService userService = (UserService) applicationContext.getBean( "userServiceId" ); userService.addUser(); } } |
使用`静态工厂方法`实例化
- 静态工厂:常用与spring整合其他框架(工具)时。
- 静态工厂:用于生成实例对象,所有的方法必须是static。
示例中用到的 UserService.java 和 UserServiceImpl.java 代码同上面实例中的代码,这里不再赘述!
格式:
1 | <bean id= "" class = "工厂全限定类名" factory-method= "静态方法名称" > |
在spring容器中进行配置:
1 | applicationContext.xml |
1 2 3 4 5 6 7 | <?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 的方式 :使用静态工厂方法实例化 将自定义的静态工厂创建的实例交给spring管理 class 自定义静态工厂全限定类名 factory-method 静态方法名 --> <bean id= "userServiceId" class = "Service.MyBeanFactory" factory-method= "createService" ></bean> </beans> |
1 | 静态工厂类代码: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class MyBeanFactory { /** * 创建实例的静态工厂,所有的方法必须是静态的(static)。 * * @return */ public static UserService createService() { return new UserServiceImpl(); } // 还有创建其他实例的静态工厂 // ...... } |
1 |
测试代码:
TestStaticFactory.java
1 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * 第二种实例化Bean 的方式 :使用静态工厂方法实例化 * */ public class TestStaticFactory { @Test public void demo01() { // 以前:使用自定义的静态实例工厂 UserService userService = MyBeanFactory.createService(); userService.addUser(); } @Test public void demo02() { // 现在:使用spring 工厂:将自定义的静态工厂创建的实例交给spring管理 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = applicationContext.getBean("userServiceId", UserService.class); // 这种方式底层会自动转换 <br>// UserService userService = (UserService) applicationContext.getBean("userServiceId");<br> userService.addUser();<br> }<br> } |
注意:当使用JDK版本为1.8时,运行上面的测试代码会出现一个问题: java.lang.IllegalArgumentException,
问题解决链接:使用Junit测试一个 spring静态工厂实例化bean 的例子,所有代码都没有问题,但是出现java.lang.IllegalArgumentException异常
小结:在以后的开发中,工厂类不需要我们去手写,因为别人已经写好了,我们通过编写配置文件
,把别人写好的工厂类拿来,写上要用的方法名,然后把它生产后的实例给Spring存起来,以后我们要用什么实例,跟Spring说一下,去拿就可以了。
使用`实例工厂方法`实例化
实例工厂:必须先有工厂的实例对象,然后通过实例对象去创建对象。特点:提供所有的方法都是“非静态”的。
示例中用到的 UserService.java 和 UserServiceImpl.java 代码同上面实例中的代码,这里不再赘述!
在spring容器中进行配置:
1 | applicationContext.xml |
1 2 3 4 5 6 | <?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 的方式 :使用实例工厂方法实例化 --> <!--创建工厂实例 --> <bean id= "myBeanFactoryId" class = "com.itheima.c_inject.c_factory.MyBeanFactory" ></bean> <!--通过工厂实例,获得对象 factory-bean 工厂实例名称 factory-method 普通方法名称 --> <bean id= "userServiceId" factory-bean= "myBeanFactoryId" factory-method= "createService" ></bean> </beans> |
静态工厂类代码:
1 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class MyBeanFactory { /** * 创建实例的工厂,所有方法非静态 * * @return */ public UserService createService() { return new UserServiceImpl(); } // 还有创建其他实例的工厂 // ...... } |
1 |
测试代码:
TestFactory.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * 第三种实例化Bean 的方式 :使用实例工厂方法实例化 * */ public class TestFactory { @Test public void demo01() { // 以前:使用自定义的实例工厂 // 1、创建工厂实例 MyBeanFactory myBeanFactory = new MyBeanFactory(); // 2、通过工厂实例,获得对象 UserService userService = myBeanFactory.createService(); userService.addUser(); } @Test public void demo02() { // 现在:使用spring 工厂:将自定义的实例工厂创建的实例交给spring管理 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = applicationContext.getBean("userServiceId", UserService.class); // 这种方式底层会自动转换<br> // UserService userService = (UserService) applicationContext.getBean("userServiceId");<br> userService.addUser();<br> }<br> } |
-
一、Bean的种类
-
普通bean :之前操作的都是普通bean。例如:
< bean id="xxx" class="A" >
,这句代码的意思是:Spring直接创建A的实例,并返回。 -
FactoryBean :是一个特殊的bean,
具有工厂生成对象能力
,但是只能生成特定的对象。
想要生产对象的bean 必须实现FactoryBean 接口,此接口提供方法getObject(); 用于获得特定bean。- 示例:
< bean id="xxx" class="FB">
,这句代码的意思是:Spring会先创建FB实例,然后调用getObject(); 方法,并返回方法的返回值。
即相当于如下两行代码:
FB fb = new FB();
return fb.getObject();
- 示例:
-
BeanFactory 和 FactoryBean 对比?
- BeanFactory :是一个生产bean的工厂,用于生成任意的bean。
- FactoryBean :是一个特殊的bean,用于生成另一个特定的bean。
- 例如:ProxyFactoryBean ,此工厂bean用于生产代理对象的实例。
< bean id="xxx" class="….ProxyFactoryBean">
,这句代码的意思是:获得代理对象的实例。即AOP使用。
- 例如:ProxyFactoryBean ,此工厂bean用于生产代理对象的实例。
-
spring容器中bean元素id和name属性的区别?
-
在spring容器中添加以下配置:
示例:< bean id="userServiceId" class="com.itheima.a_ioc.UserServiceImpl">
-
bean节点中id和name的区别:
-
区别一:
- id : 指定唯一实例引用
- name : 可以指定多个实例引用,例如name="名称1, 名称2"
-
区别二:
-
id :id的命名要满足XML对ID属性命名规范
例如:必须以字母开始,可以使用字母、数字、连字符、下划线、句话、冒号 -
name : 如果Bean的名称中含有特殊字符,就需要使用name属性
例如 :< bean name="# boy" class="cn.itheima.ioc.Boy"/>
-
因为name属性可以相同,所以后出现Bean会覆盖之前出现的同名的Bean。
-
总结:项目开发的时候,强烈要求用id,因为id可以表示唯一引用。
-
-
-
二、Bean的作用域
Bean 的作用域:用于确定spring所创建bean 的实例个数。
- 取值:
- singleton 单例,默认值。
- prototype 多例,每执行一次getBean() 将获得一个实例。例如:在struts整合spring时,需要配置action为多例。
- 配置示例:默认情况下会在容器启动时初始化bean,但我们可以指定Bean节点的
lazy-init="true"
来延迟初始化bean,这时候,只有第一次获取bean会才初始化bean。- 例如:`< bean id="xxx" class="xxx" scope="xxx">`
例如:< bean id="xxx" class="service.UserServiceImpl" lazy-init="true">
- 如果想对所有bean都应用延迟初始化,可以在根节点beans设置
default-lazy-init="true"
,
例如:< beans default-lazy-init="true“>
- Portlet是基于java的web组件,由portlet容器管理,并由容器处理请求,生产动态内容。
- Portals使用portlets作为可插拔用户接口组件,提供信息系统的表示层。
- 作为利用servlets进行web应用编程的下一步,portlets实现了web应用的模块化和用户中心化。
在spring容器中进行配置:
1 | applicationContext.xml |
1 2 3 4 5 6 | <?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 id= "userServiceId" class = "com.itheima.d_scope.UserServiceImpl" scope= "prototype" ></bean> </beans> |
测试代码:
TestScope.java
1 2 3 4 5 | public class TestScope { @Test public void demo01() { // 现在:使用spring 工厂 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService1 = applicationContext.getBean("userServiceId", UserService.class);<br> // 这种方式底层会自动转换 <br>UserService userService2 = applicationContext.getBean("userServiceId", UserService.class); <br>// 这种方式底层会自动转换<br> // 默认Bean的作用域是单例,所以打印的对象的地址是一样的<br> // System.out.println(userService1); // service.UserServiceImpl@2ac273d3<br> // System.out.println(userService2); // service.UserServiceImpl@2ac273d3<br> // 现在在配置文件中添加scope属性,值为prototype,此时Bean的作用域变为多例了,再次打印,输出地址不一样了 <br>System.out.println(userService1);<br> //service.UserServiceImpl@66480dd7 <br>System.out.println(userService2); <br>//service.UserServiceImpl@52a86356<br> }<br> } |
-
三、Bean的生命周期
Bean 的生命周期详情
- instantiate bean 对象实例化。
- populate properties 封装属性。
- 如果Bean实现 BeanNameAware,则表示执行
setBeanName
。 - 如果Bean实现 BeanFactoryAware 或者 ApplicationContextAware,则表示设置实例工厂(
setBeanFactory
)或者上下文对象(setApplicationContext
)。 - 如果存在类实现 BeanPostProcessor(后处理Bean),则表示执行
postProcessBeforeInitialization
。 - 如果Bean实现 InitializingBean,则表示执行
afterPropertiesSet
。 - 调用
,则表示指定初始化方法
init
。 - 如果存在类实现 BeanPostProcessor(处理Bean),则表示执行
postProcessAfterInitialization
。 - 执行业务处理
- 如果Bean实现 DisposableBean,则表示执行
destroy
。 - 调用
,则表示指定销毁方法
customerDestroy
。
-
四、Bean 的初始化和销毁
目标方法执行前和执行后,将进行Bean的初始化或销毁。
示例:<bean id="xxx" class="xxx" init-method="初始化的方法名称" destroy-method="销毁的方法名称"></bean>
示例代码如下:
编写目标类代码:
UserService.java
1 2 3 | public interface UserService { void addUser(); } |
UserServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println( "e_lifecycle add user" ); } public void myInit() { System.out.println( "我的初始化方法" ); } public void myDestory() { System.out.println( "我的销毁方法" ); } } |
在spring容器中进行配置:
1 | applicationContext.xml |
1 2 3 4 5 6 | <?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" > <!-- init-method 用于配置初始化方法,用于准备数据等使用场景 destroy-method 用于配置销毁方法,用于清理资源等使用场景 --> <bean id= "userServiceId" class = "service.UserServiceImpl" init-method= "myInit" destroy-method= "myDestory" ></bean> </beans> |
编写测试代码:
1 | |
1 2 3 4 5 | public class TestLifecycle { @Test public void demo01() throws Exception { // 现在:使用spring 工厂(spring 容器) ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) applicationContext.getBean("userServiceId"); <br> userService.addUser(); // 要想使我的销毁方法也执行,必须要求:<br> // 1.容器必须先close,我的销毁方法才会执行; <br> // 2.必须是单例的(spring所创建该bean的实例个数只有一个)即bean中的scope配置成默认即可。<br> // 因为此close方法在接口 ApplicationContext 中没有定义,而在实现类中提供了该方法,我们可以使用反射,因为反射最后执行的就是实现类中的方法。 <br> applicationContext.getClass().getMethod("close").invoke(applicationContext); } } |
1 |
-
五、BeanPostProcessor 后处理Bean
- 是由spring提供的一种机制,只要实现类实现此接口BeanPostProcessor,并将该实现类提供给spring容器,spring容器将自动执行两个方法:在初始化方法前执行before()方法,在初始化方法后执行after()方法。配置格式:
这句代码的意思就是:把实现类提供给了spring容器。
- Factory hook(勾子) that allows for custom modification of new bean instances, e.g. checking for marker interfaces or wrapping them with proxies.
- spring提供工厂勾子,用于修改实例对象,可以生成代理对象。(是AOP底层)
谷歌翻译:Factory hook(勾子),允许自定义修改新的bean实例,例如:检查标记接口或用代理包装它们。
我们来模拟这句话的意思:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | before() => postProcessAfterInitialization(Object bean, String beanName) after() => postProcessBeforeInitialization(Object bean, String beanName) A a = new A(); a = B.before(a); // 将a的实例对象传递给后处理bean,可以什么都没做,也可以做一些事情,比如:生成jdk代理对象并返回给a,这样a就从实例对象变成代理对象了,此时的a就具有了AOP功能;再比如,如果把null返回给a,再用a去调用方法,就会出现空指针异常。 a.init(); a = B.after(a); // 以下是AOP演示: // 我们现在在后处理Bean 代码执行完之后,把jdk代理对象返回给a。让a在调用addUser()之前先做一些事情 // 之前要做的事情 a.addUser(); // 在目标方法的前后可以做一些事情,例如:开启事务、提交事务、性能监控(前后时间)等等 // 之后要做的事情 a.destroy(); |
目标类示例代码如下:
UserService.java
1 2 3 | public interface UserService { void addUser(); } |
UserServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println( "e_lifecycle add user" ); } public void myInit() { System.out.println( "我的初始化方法" ); } public void myDestory() { System.out.println( "我的销毁方法" ); } } |
实现类示例代码如下:
MyBeanPostProcessor.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println( "执行了前方法:" + beanName); return bean; } @Override public Object postProcessAfterInitialization( final Object bean, String beanName) throws BeansException { System.out.println( "执行了后方法:" + beanName); // 传入的参数bean是我们的目标对象,此时我们的目标对象只有一个接口,那么我们的代理对象也只有一个接口 // 生成jdk代理对象 return Proxy.newProxyInstance( MyBeanPostProcessor. class .getClassLoader(), // 代理对象 bean.getClass().getInterfaces(), // 目标对象 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println( "---开启事务---" ); Object obj = method.invoke(bean, args); // 执行目标方法,本例中的目标方法是addUser System.out.println( "---关闭事务---" ); return obj; } }); // 代理的处理程序 } } |
在spring容器中进行配置:
1 | applicationContext.xml |
1 2 3 4 5 6 | <?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" > <!-- init-method 用于配置初始化方法,用于准备数据等使用场景 destroy-method 用于配置销毁方法,用于清理资源等使用场景 --> <bean id= "userServiceId" class = "service.UserServiceImpl" init-method= "myInit" destroy-method= "myDestory" ></bean> <!-- 将后处理的实现类注册给spring --> <bean class = "com.itheima.e_lifecycle.MyBeanPostProcessor" ></bean> </beans> |
测试示例代码如下:
TestLifecycle.java
1 2 3 4 5 6 | public class TestLifecycle { @Test public void demo01() throws Exception { // 现在:使用spring 工厂(spring 容器) ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" ); <br> UserService userService = (UserService) applicationContext.getBean( "userServiceId" ); <br> userService.addUser();<br> // 要想使我的销毁方法也执行,必须要求:<br> // 1.容器必须先close,我的销毁方法才会执行;<br> // 2.必须是单例的(spring所创建该bean的实例个数只有一个)即bean中的scope配置成默认即可。<br> // 因为此close方法在接口 ApplicationContext 中没有定义,而在实现类中提供了该方法,我们可以使用反射,因为反射最后执行的就是实现类中的方法。 <br> applicationContext.getClass().getMethod("close").invoke(applicationContext);<br> }<br> } |
- 运行结果截图:
- 问题1:后处理bean作用某一个目标类,还是所有目标类?
答:所有。 - 问题2:如何只作用一个?
答:通过获取"参数2"beanName进行控制。例如:"xxx".equals(method.getName());
-
六、依赖注入Bean 的属性(xml)
- 注入依赖对象可以采用:
手工装配
或自动装配
。在实际应用中
建议使用手工装配
,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果。- 手动装配:一般进行配置信息都采用手动装配。
- 基于xml装配基于注解装配 => 之后讲解
- 构造方法注入
- 属性setter方法注入
- 接口注入(spring不支持)
- 基于xml装配基于注解装配 => 之后讲解
- 自动装配:在struts 和spring 整合的时候使用自动装配。
- byType:按类型装配
- byName:按名称装配
- constructor:按构造装配
- autodetect:不确定装配(即自动装配)
- 手动装配:一般进行配置信息都采用手动装配。
构造方法
Bean对象类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class User { private Integer uid; private String username; private Integer age; public User(Integer uid, String username) { // 构造方法一 super (); this .uid = uid; this .username = username; } public User(String username, Integer age) { // 构造方法二 super (); this .username = username; this .age = age; } // 省略getter 和 setter 方法 // ...... |
在spring容器中进行配置:
1 | applicationContext.xml |
1 2 3 4 5 6 | <?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" > <!-- 6.5 . 1 、构造方法注入 <constructor-arg> 用于配置构造方法的一个参数argument name :参数的名称 value :设置普通数据 ref :设置引用数据,一般是另一个bean 的id值 index :参数的索引号,从 0 开始 。如果只有索引,匹配到了多个构造方法时,默认使用第一个。 type :确定参数类型 例如 1 :name属性开发中不常用,因为使用该属性需要关联要实例化对象的源码,否则name的值你就不知道。而一般开发中我们我们不会得到源码。 <constructor-arg name= "username" value= "李晓艺" ></constructor-arg> <constructor-arg name= "age" value= "26" ></constructor-arg> 例如 2 :类型type 和 索引index (这两者结合使用) <constructor-arg index= "0" type= "java.lang.String" value= "1" ></constructor-arg> <constructor-arg index= "1" type= "java.lang.Integer" value= "2" ></constructor-arg> 注意:在开发中为了指定执行的是哪个构造方法,一般使用index属性和type属性结合的方式。 --> <bean id= "userId" class = "entity.User" > <constructor-arg index= "0" type= "java.lang.String" value= "1" ></constructor-arg> <constructor-arg index= "1" type= "java.lang.Integer" value= "2" ></constructor-arg> </bean> </beans> |
1 |
setter方法
1 |
在spring容器中进行配置:
1 |
1 | applicationContext.xml |
1 | |
1 2 3 4 5 6 | <?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" > <!-- 6.5 . 2 、setter方法注入 * 注入的是普通数据 时 <property name= "" value= "值" ></property> 等效 <property name= "" > <value>值</value> </property> * 注入的是引用数据时 <property name= "" ref= "另一个bean的id" ></property> 等效 <property name= "" > <ref bean= "另一个bean的id" ></ref> </property> --> <bean id= "personId" class = "entity.Person" > <property name= "pname" value= "晓艺" ></property> <property name= "age" > <value> 26 </value> </property> <property name= "homeAddr" ref= "homeAddrId" ></property> <property name= "companyAddr" > <ref bean= "companyAddrId" ></ref> </property> </bean> <bean id= "homeAddrId" class = "entity.Address" > <property name= "addr" value= "山西运城" ></property> <property name= "tel" value= "911" ></property> </bean> <bean id= "companyAddrId" class = "entity.Address" > <property name= "addr" value= "百子湾" ></property> <property name= "tel" value= "120" ></property> </bean> </beans> |
1 |
-
七、P命名空间 [了解]
-
是对"setter方法注入"进行简化,替换
,
而是在
-
p命名空间使用前提:必须添加命名空间。如下图所示:
-
在spring容器中进行配置:
1 |
1 | applicationContext.xml |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <!-- 6.5 . 3 、P命名空间[了解] 是对 "setter方法注入" 进行简化,替换`<property name= "属性名称" >`, 而是在`<bean p:属性名称= "普通值" 和 p:属性名称-ref= "引用值" >` p命名空间使用前提:必须添加命名空间。 注意:开发中一般不这么用,一般用于装逼用。 --> <bean id= "personId" class = "com.itheima.f_xml.c_p.Person" p:pname= "明军" p:age= "26" p:homeAddr-ref= "homeAddrId" p:companyAddr-ref= "companyAddrId" > </bean> <bean id= "homeAddrId" class = "entity.Address" p:addr= "河南信阳" p:tel= "119" > </bean> <bean id= "companyAddrId" class = "entity.Address" p:addr= "青年路" p:tel= "110" > </bean> |
1 | |
-
八、SpEL [了解]
1 |
- 对
进行统一编程,所有的内容都使用value。
格式:
#{123}、#{'bruce'}、#{2e5} :数字、字符串、科学计数法(常量)
#{beanId} :引用另一个Bean
#{beanId.propName} :引用Bean 的属性(操作数据)
#{beanId.toString()} :引用Bean 的方法(执行方法)
#{T(类).字段|方法} :引用静态方法或字段,例如:T(java.lang.Math).PI
#{3 lt 4 == 4 ge 3} :运算符支持
#{user.name matches ‘[a-z]{6,}’} :正则表达式支持
#{likes[3]} :集合支持
1 |
示例代码如下:
在spring容器中进行配置:
1 |
1 | applicationContext.xml |
1 |
1 2 3 4 5 6 | <?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" > <!-- 6.5 . 4 、SpEL <property name= "cname" value= "#{'Bruce'}" ></property> <property name= "cname" value= "#{customerId.cname.toUpperCase()}" ></property> 通过另一个bean,获得属性,再调用的方法。 <property name= "cname" value= "#{customerId.cname?.toUpperCase()}" ></property> ?. 如果对象不为 null ,将调用方法,为 null ,也去调用方法,不报错。 --> <bean id= "customerId" class = "com.itheima.f_xml.d_SpEL.Customer" > <property name= "cname" value= "#{customerId.cname?.toUpperCase()}" ></property> <property name= "pi" value= "#{T(java.lang.Math).PI}" ></property> </bean> </beans> |
-
九、集合注入
在spring容器中进行配置:
1 |
1 | applicationContext.xml |
1 2 3 4 5 6 | <?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" > <!-- 6.5 . 5 、集合注入 集合的注入都是给<property>添加子标签 数组:<array> List:<list> Set:<set> Map:<map> 注意:map存放的是k/v键值对,使用<entry>描述 Properties 使用 <props> 和 <prop> 描述,示例:<props><prop key= "" ></prop></props> 普通数据 放在:<value> 引用数据 放在:<ref> --> <bean id= "collectionDataId" class = "entity.CollectionData" > <property name= "arrayData" > <array> <value>cmj</value> <value>lxy</value> 3 <value>明军</value> <value>晓艺</value> </array> </property> <property name= "listData" > <list> <value>琴棋书画</value> <value>撸哑铃</value> <value>花鸟鱼虫</value> <value>撸娃娃</value> </list> </property> <property name= "setData" > <set> <value>看电影</value> <value>运动</value> <value>创作</value> <value>旅行</value> </set> </property> <property name= "mapData" > <map> <entry key= "bruce" value= "布鲁斯" ></entry> <entry> <key><value>lucy</value></key> <value>露西</value> </entry> </map> </property> <property name= "propsData" > <props> <prop key= "处女座" >内心善良</prop> <prop key= "天蝎座" >宅心仁厚</prop> <prop key= "缘定今生" >此生不悔</prop> </props> </property> </bean> </beans> |
-
十、装配Bean:基于annotation(注解)
- 注解:就是一个类,格式:@注解名称
- 开发中:使用注解 取代 xml配置文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 1 . @Component 取代 <bean class = "" ></bean> @Component ( "id的值" ) 2 . web开发中,提供 3 个 @Component 注解衍生注解(功能一样)取代 <bean class = "" ></bean> @Repository :dao层 @Service :service层 @Controller :web层 注意:SpringMVC 中推荐使用注解哦! 3 . 依赖注入,给私有字段设置,也可以给setter方法设置 普通值: @Value ( "" ) 引用值: 方式 1 :按照【类型】注入 @Autowired 方式 2 :按照【类型+名称】注入 1 @Autowired @Qualifier ( "名称" ) 方式 3 :按照【名称】注入 2 @Resource ( "名称" ) 4 . 生命周期 @PostConstruct :初始化 @PreDestroy :销毁 5 . 作用域 @Scope ( "prototype" ) 多例 |
- 注解使用前提,必须添加命名空间,让spring扫描含有注解类。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version= "1.0" encoding= "UTF-8" ?> <beans xmlns= "http://www.springframework.org/schema/beans" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:context= "http://www.springframework.org/schema/context" xsi:schemaLocation="http: //www.springframework.org/schema/beans http: //www.springframework.org/schema/beans/spring-beans.xsd http: //www.springframework.org/schema/context http: //www.springframework.org/schema/context/spring-context.xsd"> <!-- 组件扫描:扫描含有注解的类 --> <context:component-scan base- package = "com.itheima.g_annotation.a_ioc" ></context:component-scan> </beans> |
- 示例截图:
演示:
单例、多例、初始化、销毁
UserService.java
1 2 3 4 5 | package service; public interface UserService { void addUser(); } |
UserServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Service ( "userServiceId" ) // 单例 @Scope ( "prototype" ) // 多例 public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println( "c_other add user" ); } @PostConstruct // 初始化 public void myInit() { System.out.println( "我的初始化方法" ); } @PreDestroy // 销毁 public void myDestory() { System.out.println( "我的销毁方法" ); } } |
在spring容器中进行配置:
1 |
1 | applicationContext.xml |
1 2 3 4 5 6 7 8 9 10 11 | <?xml version= "1.0" encoding= "UTF-8" ?> <beans xmlns= "http://www.springframework.org/schema/beans" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:context= "http://www.springframework.org/schema/context" xsi:schemaLocation="http: //www.springframework.org/schema/beans http: //www.springframework.org/schema/beans/spring-beans.xsd http: //www.springframework.org/schema/context http: //www.springframework.org/schema/context/spring-context.xsd"> <!-- 组件扫描:扫描含有注解的类 --> <context:component-scan base- package = "service" ></context:component-scan> </beans> |
测试代码:
TestOther.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package service; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestOther { @Test public void demo01() { // 现在:使用spring 工厂 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml" ); UserService userService1 = applicationContext.getBean( "userServiceId" , UserService. class ); // 这种方式底层会自动转换 UserService userService2 = applicationContext.getBean( "userServiceId" , UserService. class ); // 这种方式底层会自动转换 // 默认Bean的作用域是单例,所以打印的对象的地址是一样的 // System.out.println(userService1); // com.itheima.c_inject.d_scope.UserServiceImpl@2ac273d3 // System.out.println(userService2); // com.itheima.c_inject.d_scope.UserServiceImpl@2ac273d3 // 现在在配置文件中添加scope属性,值为prototype,此时Bean的作用域变为多例了,再次打印,输出地址不一样了 System.out.println(userService1); // com.itheima.c_inject.d_scope.UserServiceImpl@66480dd7 System.out.println(userService2); // com.itheima.c_inject.d_scope.UserServiceImpl@52a86356 applicationContext.close(); } } |
参考于笔记和博客园https://www.cnblogs.com/chenmingjun/(黑泽君)
********************************************************************************************
我的博客园地址:https://www.cnblogs.com/zyx110/

作者:泰斗贤若如
微信公众号:去有风的地方飞翔
Github:https://github.com/zyx110
有事微信:zyxt1637039050
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

我不能保证我所说的都是对的,但我能保证每一篇都是用心去写的,我始终认同: “分享的越多,你的价值增值越大”,我们一同在分享中进步,在分享中成长,越努力越幸运。再分享一句话“十年前你是谁,一年前你是谁,甚至昨天你是谁,都不重要。重要的是,今天你是谁,以及明天你将成为谁。”
人生赢在转折处,改变从现在开始!
支持我的朋友们记得点波推荐哦,您的肯定就是我前进的动力。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?