当前位置: 首页 > news >正文

Spring IOC详解

Spring 笔记

官网:https://spring.io/

核心功能:当你的项目启动的时候,自动的将当前项目的各种 Bean 都自动的注册到 Spring 容器中,然后在项目的其他地方,如果需要用到这些 Bean,直接去 Spring 容器中查找需要的对象即可。

Spring 家族的产品:

  • Spring Framework:Spring 框架的基础,我们一般所说的 Spring、SpringMVC 其实都是 Spring Framework。
  • Spring Boot:简化 Spring 配置,可以一键创建一个带有各种配置的 Spring 环境。
  • Spring Data:简化数据库配置/简化数据库访问。
  • Spring Cloud:微服务。
  • Spring Security:安全管理框架。
  • Spring Session:session 共享。

Spring Framework:

  • Ioc
  • Aop
  • JdbcTemplate
  • 事务

Spring 容器注册 Bean

正常情况

  1. 创建一个 Maven 工程。

  2. 引入 Spring 依赖:

    1. <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.20</version></dependency>
      </dependencies>
      
  3. 加入 Spring 的配置。

  4. 创建一个 Bean 并注册。

    public class User {public void sayHello() {System.out.println("hello zhangsan");}
    }
    
    <?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"><!--class 指定类的全路径id 指定类的名称Spring 拿到这两个关键信息,就可以自动的创建一个 User 对象出来了--><bean class="com.qfedu.demo.model.User" id="user"/>
    </beans>
    
  5. 启动 Spring 容器,并加载配置文件,当 Spring 容器启动之后,无论你是否跟 Spring 容器去要 User 对象,此时 User 对象都是已经创建好的状态,并保存在 Spring 容器中。

    public class MainDemo01 {public static void main(String[] args) {//这里只需要写配置文件的文件名即可,系统会自动去 classpath 下查找配置文件//这个就是加载 Spring 容器,只要 Spring 容器启动了,那么配置文件中的所有 Bean 就会完成初始化ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");//根据名字去查找一个 user 对象,这个方法返回一个 Object,需要进行类型转换User u1 = (User) ctx.getBean("user");u1.sayHello();//去查找 User 类型的对象//这种方式有一个缺陷:Spring 容器中如果存在多个 User 对象,那么这个方法执行就会报错User u2 = ctx.getBean(User.class);u2.sayHello();//告诉 Spring 容器,查找一个名为 user 的 bean,并且类型是 UserUser u3 = ctx.getBean("user", User.class);u3.sayHello();}
    }
    

异常情况

NoSuchBeanDefinitionException

没有找到需要的 Bean:

  1. 先检查注册的时候,Bean 的名称是否正确。
  2. 检查跟 Spring 容器要的时候,Bean 的名称是否正确。
  3. 检查一下启动 Spring 容器时,Spring 的配置文件名称是否正确。

NoUniqueBeanDefinitionException:

这表示要查找的目标 Bean 有多个,查找异常。这种时候就不要按照类型去查找,而应该按照名字去查找。

Bean 注册的细节

属性注入方式

  1. 构造器注入

    1. 默认情况下,如果我们向 Spring 容器注入一个 Bean 的时候,不指定构造方法,那么默认使用的构造方法就是无参构造方法,所以如果你的类里边没有无参构造方法,就会出错。

      1. public class Book {private Integer id;private String name;private String author;public Book(Integer id, String name, String author) {this.id = id;this.name = name;this.author = author;}
        }
        
      2. 由于这个 Java 类没有无参构造方法,所以在注入 Bean 的时候,如果按照下面的方式注入,就会出错:

      3. <bean class="com.qfedu.demo.p2.model.Book" id="book"/>
        
      4. 在 bean 标签中,如果没有指定构造方法,默认就使用无参构造方法。

    2. 开发者也可以自己指定要使用哪一个构造方法:

      1. <!--这里没有指定使用哪个构造方法,默认就使用无参构造方法-->
        <bean class="com.qfedu.demo.p2.model.Book" id="book"/>
        <!--也可以指定要用哪个构造方法-->
        <bean class="com.qfedu.demo.p2.model.Book" id="book2"><constructor-arg name="id" value="1"/><constructor-arg name="name" value="三国演义"/><constructor-arg name="author" value="罗贯中"/>
        </bean>
        
  2. set 方法注入

    <!--
    这里没有设置构造方法参数,所以默认使用的就是无参构造方法
    -->
    <bean class="com.qfedu.demo.p2.model.Book" id="book3"><!--这个配置将来会调用到 setId 这个方法--><property name="id" value="2"/><property name="name" value="红楼梦"/><property name="author" value="曹雪芹"/>
    </bean>
    
  3. p 名称空间注入

    1. p 名称空间注入,本质上其实就是 set 方法注入。

    2. <!--没有指定构造方法,依然是使用默认的构造方法-->
      <bean class="com.qfedu.demo.p2.model.Book" id="book4" p:id="3" p:name="水浒传" p:author="施耐庵"></bean>
      

复杂属性的注入

List、数组、对象、Map、Set、Properties 等。

首先定义 User 类:

public class User {private Integer id;private String name;private String address;private Cat cat;private List<String> favorites;private List<Cat> cats;private Book[] books;private Map<String, Object> info;private Properties school;
}

属性注入:

<?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.qfedu.demo.model.Cat" id="cat"><property name="name" value="小白"/><property name="color" value="白色"/></bean><bean class="com.qfedu.demo.model.User" id="user"><property name="id" value="1"/><property name="name" value="zhangsan"/><property name="address" value="广州"/><!--用 set 方法给 cat 赋值的时候,有两种方式:Cat cat = new Cat();setCat(cat);setCat(new Cat());ref 表示这里的值是引用了一个提前定义好的 cat 对象--><property name="cat" ref="cat"/><property name="favorites"><!--list 标签表示数据类型是一个 List如果 list 中存放的数据是字符串,那么这里就直接使用 value如果 list 中存放的数据库是对象,那么可以使用 ref 去引用外部的 对象,也可以使用 bean 标签现场定义 Bean--><list><value>足球</value><value>篮球</value></list></property><property name="cats"><list><!--引用外部定义的 cat--><ref bean="cat"/><bean class="com.qfedu.demo.model.Cat"><property name="name" value="小黑"/><property name="color" value="黑色"/></bean></list></property><property name="books"><array><bean class="com.qfedu.demo.model.Book"><property name="id" value="1"/><property name="name" value="三国演义"/><property name="author" value="罗贯中"/></bean><bean class="com.qfedu.demo.model.Book"><property name="id" value="2"/><property name="name" value="红楼梦"/><property name="author" value="曹雪芹"/></bean></array></property><property name="info"><!--map 的定义--><map><entry key="gender" value=""/><entry key="age" value="99"/></map></property><property name="school"><props><prop key="name">广州千锋</prop><prop key="address">广州市白云区</prop></props></property></bean>
</beans>

如果是通过构造方法注入:

<bean class="com.qfedu.demo.model.User" id="user2"><constructor-arg name="books"><!--这个标签中的内容和 set 方法注入时的一模一样--></constructor-arg>
</bean>

注意属性的名字

内省。

对于框架而言,并不是看对象定义的属性叫什么名字,而是根据对象的 get/set 方法来推断属性名称,无论是 MyBatis、Spring、SpringMVC,所有框架,只要用到反射,都是这样的。所以,定义 get/set 方法的时候,不要写错,另一方面,变量的命名要符合规范。

Java 代码配置 Spring

用一个 Java 配置类,去代替 XML 配置即可:

/*** 这个是 Java 配置类,它的作用类似于 applicationContext.xml** @Configuration 表示这是一个配置类*/
@Configuration
public class JavaConfig {/*** @Bean 就表示将当前方法的返回值注册到 Spring 容器中** 默认情况下,方法名称就是 bean 的名字** 如果需要自定义 bean 的名称,那么在注解中配置即可** @return*/@Bean("u")User user() {User user = new User();user.setAddress("广州");user.setName("zhangsan");return user;}}

启动 Spring 容器时,加载这个配置类即可:

public class Demo01 {public static void main(String[] args) {//启动 Spring 容器,加载一个 Java 配置类,构造方法中指定配置类即可AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);User user = ctx.getBean("u", User.class);System.out.println("user = " + user);}
}

参数自动注入

  • @Configuration:这个注解表示当前类是一个配置类,那么当前类中,所有添加了 @Bean 注解的方法都会被注册到 Spring 容器中,如果有其他方法调用到一个添加了 @Bean 注解的方法,那么不会立马执行对应的方法,而是先去 Spring 容器中查看是否有对应的对象,如果有,则直接从容器中获取即可,如果容器中没有的话,才回去执行对应的方法。

  • @Component 虽然也可以加在配置类上,但是,如果有其他方法调用到一个添加了 @Bean 注解的方法,那么不会先去 Spring 容器中查看是否有对应的对象,而是直接执行对应的方法。所以一般在配置类中不使用 @Component 注解。如果一定要使用 @Component 注解,可以通过依赖注入来代替方法调用,类似下面这样:

    /*** 向 Spring 容器注册一个 Author 对象** @return*/
    @Bean
    Author author() {Author author = new Author();author.setName("鲁迅");author.setAge(55);return author;
    }
    /*** 向 Spring 容器中注册一个 Book 对象** book 中有一个 author 对象,book 中的 author 和 spring 容器中的 author 是否是同一个对象?* @return*/
    @Bean
    Book book2(Author author) {Book book = new Book();book.setName("故事新编");book.setAuthor(author);book.setPrice(18.0);return book;
    }
    

    在这里,所有的方法都是 Spring 容器调用的,当 Spring 容器调用 book2 这个方法的时候,就会发现这个方法的执行需要一个 Author 类型的参数,那么此时 Spring 容器就会去查找是否有一个 Author,如果有,则直接作为参数传进来,如果 Spring 容器中没有这个对象,那么直接抛出异常。

条件注解

条件注解是多环境配置的核心,思路就是提前准备好环境,所谓的环境,实际上就是 Condition 接口的实现类,然后在注册 Bean 的时候,通过 @Conditional 注解去指定环境,当满足某种条件的时候,bean 才会注入到 Spring 容器中。

两个非常经典的使用场景:

  • 项目中的多环境配置。
  • SpringBoot 中的自动化配置。

多环境切换

提前准备好生产环境、开发环境等的配置信息,将来通过 Profile 可以一键切换。

通过 Java 代码实现

主要是使用 @Profile 注解,这个注解的本质就是 @Conditional 条件注解,用到的条件实际上就是 ProfileCondition。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {/*** The set of profiles for which the annotated component should be registered.*/String[] value();}

条件注解:

class ProfileCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {//获取 Profile 注解的所有属性,其实这个注解只有一个 value 属性,属性的值是一个数组MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());if (attrs != null) {//将属性中的 value 读取出来,这个 value 的值实际上是一个 String 数组,遍历 String 数组for (Object value : attrs.get("value")) {//判断当前环境中,有没有 value 中的值if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {return true;}}return false;}return true;}}

具体配置:

@Configuration
public class JavaConfig {/*** 开发环境的数据源** 通过 @Profile("dev") 注解可以指定当前的环境是开发环境* @return*/@Profile("dev")@Bean("ds")DataSource devDataSource() {DataSource ds = new DataSource();ds.setUsername("root");ds.setPassword("123");ds.setUrl("jdbc:mysql:///test01");return ds;}/*** 配置生产环境的数据源* @return*/@Profile("prod")@Bean("ds")DataSource prodDataSource() {DataSource ds = new DataSource();ds.setUsername("zhangsan");ds.setPassword("jdfkslajfl890324");ds.setUrl("jdbc:mysql://114.132.43.22/prod01");return ds;}
}

注册 Bean 的时候,通过 @Profile(“prod”) 注解来指定当前 Bean 在哪个环境下生效。

当启动 Spring 容器的时候,要为 Spring 容器指定当前的环境信息:

public class Demo01 {public static void main(String[] args) {//注意先不要写配置类,要先设置环境信息AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();//设置当前的环境信息ctx.getEnvironment().addActiveProfile("prod");ctx.register(JavaConfig.class);ctx.refresh();DataSource ds = ctx.getBean(DataSource.class);System.out.println("ds = " + ds);}
}

通过 XML 配置实现

首先在 xml 文件中,通过 beans 标签来指定环境:

<?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"><beans profile="dev"><!--将来写在这个标签中的 Bean,都是在 dev 环境下才会生效的 Bean--><bean class="com.qfedu.demo.p1.model.DataSource" id="dataSource"><property name="username" value="root"/><property name="password" value="123"/><property name="url" value="jdbc:mysql:///test01"/></bean></beans><beans profile="prod"><!--将来写在这个标签中的 Bean,都是在 prod 环境下才会生效的 Bean--><bean class="com.qfedu.demo.p1.model.DataSource" id="dataSource"><property name="username" value="root"/><property name="password" value="jkld3u$%^"/><property name="url" value="jdbc:mysql://11.22.11.22/test01"/></bean></beans>
</beans>

将来 Spring 容器启动的时候,会根据当前的环境信息去注册不同的 beans 标签中的 Bean。

启动容器的时候,设置一下当前环境即可:

public class Demo02 {public static void main(String[] args) {//先不要加载配置文件ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();ctx.getEnvironment().addActiveProfile("dev");//设置好当前环境之后,再去设置配置文件的位置ctx.setConfigLocation("applicationContext.xml");ctx.refresh();DataSource ds = ctx.getBean(DataSource.class);System.out.println("ds = " + ds);}
}

配置文件的注入

主要是指 properties 配置文件的注入。

Java 代码配置

主要就是两个配置:

  • @PropertySource:项目启动的时候,将配置文件中的内容注册到 Spring 容器中。
  • @Value:从 Spring 容器要一个字符串回来。
@Configuration
@PropertySource("classpath:db.properties")
public class DsConfig {//跟 Spring 容器要一个字符串回来@Value("${db.username}")String username;@Value("${db.password}")String password;@Value("${db.url}")String url;@BeanDataSource dataSource() {DataSource ds = new DataSource();ds.setPassword(password);ds.setUsername(username);ds.setUrl(url);return ds;}
}

要能够从 Spring 容器中要到字符串,或者对象,有一个前提,当前 Bean 必须处于 Spring 容器中,不可以自己手动 new 一个 Bean。

XML 配置

<?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 https://www.springframework.org/schema/context/spring-context.xsd"><!--这个配置的作用类似于 @PropertySource,就是将配置文件注册到 Spring 容器中--><context:property-placeholder location="classpath:db.properties"/><!--${db.url} 表示引用 Spring 容器中,key 为 db.url 的变量--><bean class="com.qfedu.demo.p1.model.DataSource" id="dataSource"><property name="url" value="${db.url}"/><property name="password" value="${db.password}"/><property name="username" value="${db.username}"/></bean>
</beans>

包扫描

前面的 Bean 的注册方式,都是一个一个 Bean 去注册的。

Java 配置包扫描

官方推荐写法

整体上来说,三个方面:

  1. 将 Bean 注册到 Spring 容器中:@Repository、@Service、@Controller 以及 @Component。

    /*** 将 UserDao 注册到 Spring 容器中,将来谁需要使用 UserDao,就直接跟 Spring 容器去查找即可*** @Repository:一般是加在 Dao 层* @Service:一般是加在 Service 层* @Controller:一般是加在控制层,也就是 servlet* @Component:身份不明的 Bean 注册到 Spring 容器中时,使用这个注解** 技术上来说,这四个注解并没有差异,即实际上除了 @Controller 之外,其他几个注解是可以混用的。** 实际开发中,不要混用**/
    @Repository
    public class UserDao {public User getUserByUsername(String username) {User user = new User();user.setUsername(username);user.setAddress("广州");return user;}
    }
    
  2. 从 Spring 容器中要一个 Bean 回来,官方推荐的方式,是通过构造器来注入。

    /*** UserService 想要使用 UserDao* 1. UserService 自己需要在 Spring 容器* 2. 跟 Spring 容器去要一个 UserDao*/
    @Service
    public class UserService {UserDao userDao;/*** 官方推荐使用构造器注入。因为 UserService 要注册到 Spring 容器,要生成一个对象,必然就要调用它的构造方法,现在它只有这一个构造法方法,那么 Spring 容器只能通过这个构造方法来获取一个 UserService 的实例,那么 Spring 容器会自动去查找容器中是否有一个 UserDao 的实例,如果有,就作为这个方法的参数传入进来* @param userDao*/public UserService(UserDao userDao) {this.userDao = userDao;}public User getUserById(String username) {return userDao.getUserByUsername(username);}}
    

    这里又涉及到一个问题:如果有多个构造器怎么办?@Autowired 注解可解决。

    @Service
    public class UserService {UserDao userDao;/*** 官方推荐使用构造器注入。因为 UserService 要注册到 Spring 容器,要生成一个对象,必然就要调用它的构造方法,现在它只有这一个构造法方法,那么 Spring 容器只能通过这个构造方法来获取一个 UserService 的实例,那么 Spring 容器会自动去查找容器中是否有一个 UserDao 的实例,如果有,就作为这个方法的参数传入进来* * 如果有多个构造方法,那么可以通过 @Autowired 注解来告诉 Spring 容器,应该调用哪一个构造方法去初始化当前对象* @param userDao*/@Autowiredpublic UserService(UserDao userDao) {this.userDao = userDao;}public User getUserById(String username) {return userDao.getUserByUsername(username);}}
    
  3. 配置包扫描。

    @ComponentScan(basePackages = "com.qfedu.demo")
    @Configuration
    public class JavaConfig {
    }
    

    通过 @ComponentScan 注解指定组件的路径即可。

日常使用较多的写法

和前面的步骤相比,主要是第二步不一样,其他都是一样的。

@Controller
public class UserServlet {/*** @Autowired 表示根据类型去 Spring 容器中查找到相应的 Bean,并赋值给 userService 变量*/@AutowiredUserService userService;public User getUserById(String username) {return userService.getUserById(username);}
}

直接在属性上添加 @Autowired 注解就可以完成属性的注入了。

这种写法是我们在实际开发中使用较多的一种方式。

构造方法注入:

  • 官方考虑的是,这个类将来使用的时候,不一定就是跟 Spring 容器去要的,可能就是用户自己 new 出来的,例如用户如果直接 new 一个 UserServlet,那么此时 userService 变量就为 null,并且用户没有办法给 userService 赋值,导致 userService 为 null。但是如果用户是 new 一个 UserService 实例的话,那么就必须传递一个 UserDao 的实例。核心思想就是:这个 Bean 既可以跟 Spring 去要,也可以直接 new。
  • 大部分情况下,一个类的属性都特别多,导致类的构造方法特别长,所以这种方式在实际项目中用的少。

注解注入:

  • 简单便捷。
  • 如果用户使用不规范,自己 new 了一个该对象,可能会导致对象中的很多属性没有赋值,为 null。

XML 配置包扫描

XML 配置包扫描跟 Java 配置包扫描,区别在于第三步。即包扫描用 XML 文件来配置:

<?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 https://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.qfedu.demo"/>
</beans>

工厂 Bean

这个主要是解决一些第三方的 Bean,一些无法通过构造方法正常初始化、或者无法通过 set 方法正常为属性的赋值的 Bean,可以通过工厂 Bean 的方式将之注册到 Spring 容器中。

静态工厂

工厂方法是一个静态方法。

public class OkHttpClientFactory {private static OkHttpClient okHttpClient;public static OkHttpClient getInstance() {if (okHttpClient == null) {okHttpClient = new OkHttpClient.Builder()//设置服务端的读取超时时间.readTimeout(5000, TimeUnit.SECONDS)//连接超时.connectTimeout(5000, TimeUnit.SECONDS).build();}return okHttpClient;}
}

在 XML 文件中,直接配置工厂 Bean 即可:

<?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,将来会自动调用 getInstance 方法,并将该方法的返回值注入到 Spring 容器中--><bean class="com.qfedu.demo.OkHttpClientFactory" factory-method="getInstance" id="client01"/>
</beans>

需要注意的是,这里将来注册到 Spring 容器中的 Bean,不是 class 对应的类的对象,而是 getInstance 方法返回的对象。

实例工厂

工厂方法变为实例方法,执行的时候,必须要现有工厂对象,然后才能调用对应的方法。

public class OkHttpClientFactory2 {private OkHttpClient okHttpClient;public OkHttpClient getInstance() {if (okHttpClient == null) {okHttpClient = new OkHttpClient.Builder()//设置服务端的读取超时时间.readTimeout(5000, TimeUnit.SECONDS)//连接超时.connectTimeout(5000, TimeUnit.SECONDS).build();}return okHttpClient;}
}

Bean 的注册:

<!--
对于实例工厂方法,必须得先有一个工厂的实例,将来要通过这个实例才能调用工厂方法
-->
<bean class="com.qfedu.demo.OkHttpClientFactory2" id="clientFactory2"/>
<!--此时配置工厂的时候,既需要指定工厂的 Bean,也需要指定工厂方法-->
<bean class="okhttp3.OkHttpClient" factory-bean="clientFactory2" factory-method="getInstance" id="client02"/>

FactoryBean

这是 Spring 官方推荐的工厂 Bean 的实现方式。

public class OkHttpClientFactoryBean implements FactoryBean<OkHttpClient> {/*** 返回真正的对象* @return* @throws Exception*/@Overridepublic OkHttpClient getObject() throws Exception {return new OkHttpClient.Builder()//设置服务端的读取超时时间.readTimeout(5000, TimeUnit.SECONDS)//连接超时.connectTimeout(5000, TimeUnit.SECONDS).build();}/*** 返回对象的类型* @return*/@Overridepublic Class<?> getObjectType() {return OkHttpClient.class;}/*** 这个对象是否是单例的* @return*/@Overridepublic boolean isSingleton() {return true;}
}

然后在 XML 文件中注册即可:

<bean class="com.qfedu.demo.OkHttpClientFactoryBean" id="client03"/>

Bean 的作用域

默认情况下,注册到 Spring 容器中的 Bean 是单例的:

<?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.qfedu.demo.User" id="user"/>
</beans>

如果不希望这个 Bean 是单例的,那么可以通过如下方式进行修改:

<?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.qfedu.demo.User" id="user" scope="prototype"/>
</beans>

scope 有五种取值:

  • singleton:表示这个 bean 是单例的,从 Spring 容器中多次获取,拿到的是同一个 Bean。
  • prototype:每一次去 Spring 容器中,都可以获取到一个全新的 Bean。
  • request:在同一个请求中,如果多次获取同一个 Bean,获取到的是同一个(web 环境下生效)
  • session:在同一个 session 中,如果多次获取同一个 Bean,获取到的是同一个(web 环境下生效)
  • application:在同一个 web 应用中,如果多次获取同一个 Bean,获取到的是同一个(web 环境下生效)(web 环境下生效)

也可以通过 Java 代码配置 scope,通过 @Scope 注解,可以指定一个 Bean 的作用域:

@Configuration
public class JavaConfig {@Bean@Scope("prototype")User user() {return new User();}
}

Bean 的初始化和销毁

主要是两个方法:

public class User {private String username;private String address;public User() {System.out.println("构造方法。。。");}/*** 假设这个方法用来初始化当前 Bean*/public void init() {System.out.println("初始化方法。。。");}/*** 当当前 Bean 销毁的时候,可以在当前方法中做一些资源回收操作*/public void destroy() {System.out.println("销毁方法。。。");}
}

如果在 Spring 容器完成注册之后,还想要做一些配置,那么可以在 init 方法中完成,当 Spring 容器销毁的时候,可以在 destroy 方法中完成一些资源回收操作。

<bean class="com.qfedu.demo.User" init-method="init" destroy-method="destroy" id="user"/>

IoC 小结

控制反转,指的是对象的控制权反转。本来,UserService 需要一个 UserDao,那么直接在 UserSerivce 中 new 一个 UserDao,那么此时 UserDao 的对象的控制权就在 UserService 中;有了 Spring 之后,系统启动的时候,UserDao 会将自己的各种信息告诉 Spring 容器,Spring 利用这些信息,就可以结合反射创建一个 UserDao 对象,此时 UserDao 对象处于 Spring 容器中,这个对象的控制权在 Spring 容器中,对象的控制权从 UserService 中转移到了 Spring 容器中,就是控制(权)反正。利用 IoC 可以实现对象之间的解耦。

相关文章:

Spring IOC详解

Spring 笔记 官网&#xff1a;https://spring.io/ 核心功能&#xff1a;当你的项目启动的时候&#xff0c;自动的将当前项目的各种 Bean 都自动的注册到 Spring 容器中&#xff0c;然后在项目的其他地方&#xff0c;如果需要用到这些 Bean&#xff0c;直接去 Spring 容器中查…...

Unity——DOTween插件使用方法简介

缓动动画既是一种编程技术&#xff0c;也是一种动画的设计思路。从设计角度来看&#xff0c;可以有以下描述 事先设计很多基本的动画样式&#xff0c;如移动、缩放、旋转、变色和弹跳等。但这些动画都以抽象方式表示&#xff0c;一般封装为程序函数动画的参数可以在使用时指定&…...

数据库——Redis 单线程模型详解

文章目录 Redis 基于 Reactor 模式来设计开发了自己的一套高效的事件处理模型 &#xff08;Netty 的线程模型也基于 Reactor 模式&#xff0c;Reactor 模式不愧是高性能 IO 的基石&#xff09;&#xff0c;这套事件处理模型对应的是 Redis 中的文件事件处理器&#xff08;file …...

leetcode 567. 字符串的排列(滑动窗口-java)

滑动窗口 字符串的排列滑动窗口代码演示进阶优化版 上期经典 字符串的排列 难度 -中等 leetcode567. 字符串的排列 给你两个字符串 s1 和 s2 &#xff0c;写一个函数来判断 s2 是否包含 s1 的排列。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 换句…...

Git —— 分支重命名操作

在开发中&#xff0c;对某个分支进行重命名的操作&#xff1a; 1、本地分支重命名 本地分支是指&#xff1a;你当前这个分支还没有推送到远程的情况&#xff0c;这种情况修改分支名称就要方便很多 git branch -m 原始名称 新名称 //示例&#xff1a; 修改 test 为 newTest g…...

JavaIO流

JavaIO流 一、概念二、File类三、File类的使用1、File文件/文件夹类的创建2、File类的获取操作3、File类判断操作 - boolean4、File类对文件/文件夹的增删改5、File类的获取子文件夹以及子文件的方法 四、Java中IO流多种维度的维度1、按照流向 - Java程序2、按照流的大小分类3、…...

FlinkSql 如何实现数据去重?

摘要 很多时候flink消费上游kafka的数据是有重复的&#xff0c;因此有时候我们想数据在落盘之前进行去重&#xff0c;这在实际开发中具有广泛的应用场景&#xff0c;此处不说详细代码&#xff0c;只粘贴相应的flinksql 代码 --********************************************…...

机器学习概念

目录 一、人工智能、机器学习、深度学习的关系 二、什么是深度学习&#xff1f; 2.1 深度学习常用算法 一、人工智能、机器学习、深度学习的关系 人工智能、机器学习和深度学习的关系如下所示。 二、什么是深度学习&#xff1f; 深度学习( DL, Deep Learning) 是机器学习 …...

【数据结构】排序(插入、选择、交换、归并) -- 详解

一、排序的概念及其运用 1、排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记…...

游戏中的图片打包流程,免费的png打包plist工具,一款把若干资源图片拼接为一张大图的免费工具

手机游戏开发中&#xff0c;为了提高图片渲染性能&#xff0c;经常需要将小图片合并成一张大图进行渲染。如果手工来做的话就非常耗时。TexturePacker就是一款非常不错方便的处理工具。TexturePacker虽然非常优秀&#xff0c;但不是免费的。 对于打包流程&#xff0c;做游戏的…...

Springboot实现ENC加密

Springboot实现ENC加密 1、导入依赖2、配置加密秘钥&#xff08;盐&#xff09;3、获取并配置密文4、重启项目测试5、自定义前缀、后缀6、自定义加密方式 1、导入依赖 关于版本&#xff0c;需要根据spring-boot版本&#xff0c;自行修改 <dependency><groupId>co…...

nginx 托管vue项目配置

server {listen 80;server_name your_domain.com;location / {root /path/to/your/vue/project;index index.html;try_files $uri $uri/ /index.html;} }奇怪的现象,在vue路由中/会跳转到/abc/def&#xff0c;但如果直接输入/abc/def会显示404&#xff0c;添加 try_files $uri…...

Vue3中如何进行封装?—组件之间的传值

用了很久一段时间Vue3Ts了&#xff0c;工作中对一些常用的组件也进行了一些封装&#xff0c;这里对封装的一些方法进行一些简单的总结。 1.props传递 首先在主组件进行定义传值 <template><div>这里是主组件<common :first"first"></common&…...

实训笔记8.25

实训笔记8.25 8.25笔记一、Flume数据采集技术1.1 Flume实现数据采集主要借助Flume的组成架构1.2 Flume采集数据的时候&#xff0c;核心是编写Flume的采集脚本xxx.conf1.2.1 脚本文件主要由五部分组成 二、Flume案例实操2.1 采集一个网络端口的数据到控制台2.1.1 分析案例的组件…...

vue自定义监听元素宽高指令

在 main.js 中添加 // 自定义监听元素高度变化指令 const resizerMap new WeakMap() const resizeObserver new ResizeObserver((entries) > {for (const entry of entries) {const handle resizerMap.get(entry.target)if (handle) {handle({width: entry.borderBoxSiz…...

网络爬虫到底是个啥?

网络爬虫到底是个啥&#xff1f; 当涉及到网络爬虫技术时&#xff0c;需要考虑多个方面&#xff0c;从网页获取到最终的数据处理和分析&#xff0c;每个阶段都有不同的算法和策略。以下是这些方面的详细解释&#xff1a; 网页获取&#xff08;Web Crawling&#xff09;&#x…...

前端行级元素和块级元素的基本区别

块级元素和行内元素的基本区别是&#xff0c; 行内元素可以与其他行内元素并排&#xff1b;块级元素独占一行&#xff0c;不能与其他任何元素并列&#xff1b; 下面看一下&#xff1b; <!DOCTYPE html> <html> <head> <meta charset"utf-8"&…...

CentOS 7用二进制安装MySQL5.7

[rootlocalhost ~]# [rootlocalhost ~]# ll 总用量 662116 -rw-------. 1 root root 1401 8月 29 19:29 anaconda-ks.cfg -rw-r--r--. 1 root root 678001736 8月 29 19:44 mysql-5.7.40-linux-glibc2.12-x86_64.tar.gz [rootlocalhost ~]# tar xf mysql-5.7.40-linux-…...

华为加速回归Mate 60发布, 7nm全自研工艺芯片

华为于今天12:08推出“HUAWEI Mate 60 Pro先锋计划”&#xff0c;让部分消费者提前体验。在华为商城看到&#xff0c;华为Mate 60 pro手机已上架&#xff0c;售价6999元&#xff0c;提供雅川青、白沙银、南糯紫、雅丹黑四种配色供选择。 据介绍&#xff0c;华为在卫星通信领域…...

Linux系列讲解 —— 【systemd】下载及编译记录

Ubuntu18.04的init程序合并到了systemd中&#xff0c;本篇文章记录一下systemd的下载和编译。 1. 下载systemd源码 (1) 查看systemd版本号&#xff0c;用来确定需要下载的分支 sunsun-pc:~$ systemd --version systemd 237 PAM AUDIT SELINUX IMA APPARMOR SMACK SYSVINIT UT…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式&#xff0c;然后找到相应的网卡&#xff08;可以查看自己本机的网络连接&#xff09; windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置&#xff0c;选择刚才配置的桥接模式 静态ip设置&#xff1a; 我用的ubuntu24桌…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...