当前位置: 首页 > 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…...

u-view 的u-calendar 组件设置默认日期后,多次点击后,就不滚动到默认日期的位置

场景&#xff1a;uniapp开发微信小程序 vue2 uview版本&#xff1a;2.0.36 &#xff1b; u-calendar 组件设置默认日期后 我打开弹窗&#xff0c;再关闭弹窗&#xff0c; 重复两次 就不显示默认日期了 在源码中找到这个位置进行打印值&#xff0c;根据出bug前后的值进行…...

vue naive ui 按钮绑定按键

使用vue (naive ui) 绑定Enter 按键 知识点: 按键绑定Button全局挂载使得message,notification, dialog, loadingBar 等NaiveUI 生效UMD方式使用vue 与 naive ui将vue默认的 分隔符大括号 替换 为 [[ ]] <!DOCTYPE html> <html lang"en"> <head>…...

Viobot基本功能使用及介绍

设备拿到手当然是要先试一下效果的&#xff0c;这部分可以参考本专栏的第一篇 Viobot开机指南。 接下来我们就从UI开始熟悉这个产品吧&#xff01; 1.状态 设备上电会自动运行它的程序&#xff0c;开启了一个服务器&#xff0c;上位机通过连接这个服务器连接到设备&#xff0c…...

《PMBOK指南》第七版12大原则和8大绩效域

《PMBOK指南》第七版12大原则 原则1&#xff1a;成为勤勉、尊重和关心他人的管家 原则2&#xff1a;营造协作的项目团队环境 原则3&#xff1a;有效地干系人参与 原则4&#xff1a;聚焦于价值 原则5&#xff1a;识别、评估和响应系统交互 原则6&#xff1a;展现领导力行为…...

docker 启动命令

cd /ycw/docker docker build -f DockerFile -t jshepr:1.0 . #前面测试docker已经介绍过该命令下面就不再介绍了 docker images docker run -it -p 7003:9999 --name yyy -d jshepr:1.0 #上面运行报错 用这个 不报错就不用 docker rm yyy docker ps #查看项目日志 docker …...

C++ DAY7

一、类模板 建立一个通用的类&#xff0c;其类中的类型不确定&#xff0c;用一个虚拟类型替代 template<typename T> 类template ----->表示开始创建模板 typename -->表明后面的符号是数据类型&#xff0c;typename 也可以用class代替 T ----->表示数据类型…...

Vue2 使用插件 Volar 报错:<template v-for> key should be placed on the <template> tag.

目录 问题描述 版本描述 问题定位 问题解决 VS Code 插件地址 问题描述 在 VS Code 上使用插件 Volar 开发 Vue3 项目&#xff0c;然后去改 Vue2 项目时&#xff0c;对没有放在<template v-for> 元素上的 :key&#xff0c;会提示 <template v-for> key should…...

启动线程方法 start ()和 run ()有什么区别

在Java中,线程可以通过调用start()方法或者直接调用run()方法来执行。这两种方式有着重要的区别: start() 方法:当你调用线程的start()方法时,它会使线程进入就绪状态,等待系统调度。系统会为该线程分配资源,并在合适的时机执行线程的run()方法。实际上,start()方法会启…...

Java的全排列模板

c有全排列函数&#xff0c;我们Java没有&#xff0c;所以我们只能自己手写了。 模板一&#xff1a;&#xff08;不去重&#xff09; import java.util.ArrayList; import java.util.Random; import java.util.Scanner; public class liyunpeng {public static void main(Stri…...

读书笔记——《万物有灵》

前言 上一本书是《走出荒野》&#xff0c;太平洋步道女王提到了这本书《万物有灵》&#xff0c;她同样是看一点撕一点的阅读。我想&#xff0c;在她穿越山河森林&#xff0c;听见鸟鸣溪流的旅行过程中&#xff0c;是不是看这本描写动物有如何聪明的书——《万物有灵》&#xf…...

面试现场表现:展示你的编程能力和沟通技巧

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…...

34亿的mysql表如何优雅的扩字段长度兵并归档重建

业务背景&#xff1a; 该系统有一张表数据量已达到34亿&#xff0c;并且有个字段长度不够&#xff0c;导致很多数据无法插入。因为业务只要保留近2个月数据即可&#xff0c;所以需要接下来需要做2点&#xff1a;1&#xff0c;扩字段长度 2&#xff0c;只保留近2个月的数据。 …...

C#_进程单例模式.秒懂Mutex

什么是Mutex? 可以定义调用线程是否具有互斥性&#xff0c;程序创建者拥有控制权&#xff0c;相反只能引用程序。 参数1&#xff1a;如果是程序创建者&#xff0c;就获得控制权。 参数2&#xff1a;名称&#xff0c;可使用GUID生成。 参数3&#xff1a;out 返回值&#xf…...

AcWing 5050. 排序 (每日一题)

题目描述 给定一个长度为 n 的由小写字母构成的字符串。 请你按照 a∼z 的顺序&#xff0c;对字符串内的字符进行重新排序&#xff0c;并输出重新排序后的字符串。 输入格式 第一行包含整数 T &#xff0c;表示共有 T 组测试数据。 每组数据第一行包含整数 n 。 第二行包…...

【TypeScript】proxy 和 Reflect

proxy &#xff08;代理&#xff09; 和 Reflect &#xff08;反射&#xff09; 参数一样。 基本用法 let person { name: xx, age: 20} // proxy 支持对象&#xff0c;数组&#xff0c;函数&#xff0c;set&#xff0c;map // 代理对象的常用模式&#xff1a;前面一个对象&…...

STM32f103入门(5)定时器中断

STM32 TIM&#xff08;定时器/计数器&#xff09;模块的中断流程如下&#xff1a; 配置TIM寄存器&#xff1a;首先&#xff0c;通过配置TIM相关的寄存器来设置计时器的基本参数&#xff0c;例如预分频系数、计数模式、计数器周期等。 使能TIM中断&#xff1a;使用TIM_ITConfig函…...

Mybatis查询数据

上一篇我们介绍了在pom文件中引入mybatis依赖&#xff0c;配置了mybatis配置文件&#xff0c;通过读取配置文件创建了会话工厂&#xff0c;使用会话工厂创建会话获取连接对象读取到了数据库的基本信息。 如果您需要对上面的内容进行了解&#xff0c;可以参考Mybatis引入与使用…...

【前车之鉴】: 2023最新教程-将java程序打包到maven私服的正确打开方式,详细流程介绍不怕你掌握不了

文章目录 为什么看这篇整体流程1. 注册账号【首次需要】2. 工单申请【新项目必须】3. 项目配置【新项目必须】4. 授权认证【新项目必须】5. 一键发布 最后也很重要 为什么看这篇 一是当前网络上一些博客有遗漏部分&#xff0c;这里做补充&#xff0c;二是网上思路没错&#xff…...

如何理解IaaS、PaaS、SaaS?盘点受欢迎的八大SaaS平台!

如何理解IaaS、PaaS、SaaS&#xff1f;他们各自的优势在哪里&#xff1f;我们又应该如何选择&#xff1f;本篇将带大家深入浅出的了解IaaS、PaaS、SaaS&#xff0c;并为大家盘点2023最受欢迎的SaaS平台。 通过本文你将了解&#xff1a; IaaS、PaaS、SaaS分别是什么我们该如何理…...

立创EDA专业版的原理图上器件有一个虚线框

立创EDA专业版的原理图上器件有一个虚线框解决方法 问题分析&#xff1a; 在使用立创EDA专业版 设计电路原理图时&#xff0c;中途莫名其妙就给我的元件添加了下面图片所示的虚线外框。看着就很别扭的样子&#xff0c;而且工程大了和器件稍微布局比较密的时候就导致整体很难看…...

JUC并发编程--------基础篇

一、多线程的相关知识 栈与栈帧 我们都知道 JVM 中由堆、栈、方法区所组成&#xff0c;其中栈内存是给谁用的呢&#xff1f;其实就是线程&#xff0c;每个线程启动后&#xff0c;虚拟 机就会为其分配一块栈内存。 每个栈由多个栈帧&#xff08;Frame&#xff09;组成&#xf…...

秒懂算法2

视频链接 : 希望下次秒懂的是算法题_哔哩哔哩_bilibili P1094 [NOIP2007 普及组] 纪念品分组 原题链接 : [NOIP2007 普及组] 纪念品分组 - 洛谷 思路 : 排序 贪心 双指针首先先对输入进来的数组进行排序(由小到大)运用贪心的思想 : 前后结合,令l1,rn,若a[l]a[r]<w…...

隐秘的角落:Java连接Oracle提示Connection timed out

前言 这个报错相信各位后端开发都不陌生&#xff0c;大体的原因就那么几种&#xff1a; 检查网络连接&#xff1a;确保您的计算机与数据库服务器之间的网络连接正常。尝试通过其他方式验证您的网络连接是否正常。 检查数据库服务器状态&#xff1a;确保数据库服务器正在运行&…...

基于微信小程序的餐厅预订系统的设计与实现(论文+源码)_kaic

摘 要 随着消费升级&#xff0c;越来越多的年轻人已经开始不再看重餐饮等行业的服务&#xff0c;而是追求一种轻松自在的用餐、购物环境。因此&#xff0c;无人餐厅、无人便利店、无人超市等一些科技消费场所应势而生。餐饮企业用工荒已成为不争的事实。服务员行业的低保障、低…...

科技政策 | 四川省科学技术厅关于发布2024年第一批省级科技计划项目申报指南的通知

原创 | 文 BFT机器人 近日&#xff0c;四川省科学技术厅发布了2024年第一批省级科技计划项目申报指南&#xff1b;其中包括自然科学基金项目、重点研发计划、科技成果转移转化引导计划、科技创新基地&#xff08;平台&#xff09;和人才计划。 01 自然科学基金项目 实施周期 …...

深入了解Webpack:特性、特点和结合JS混淆加密的实例

Webpack是现代前端开发中最受欢迎的构建工具之一&#xff0c;其强大的特性和灵活性使得开发者能够更有效地管理和优化项目资源。在本文中&#xff0c;我们将深入探讨Webpack的特性和特点&#xff0c;并结合实例演示如何使用Webpack与JS混淆加密相结合。Webpack的特性和特点 1.…...

2023-08-23力扣每日一题

链接&#xff1a; 1782. 统计点对的数目 题意&#xff1a; 给n个点和m条无向边&#xff08;可重复&#xff09;&#xff0c;q个查询 定义edge[a]为一个点是a的边数量&#xff0c;定义ret[a,b]是edge[a]edge[b]-&#xff08;a与b的边&#xff09; q个查询q个答案&#xff0…...

分发饼干【贪心算法】

分发饼干 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&#xff1b;并且每块饼干 j&#xff0c;都有一个…...

为什么网络互联地址设置为30位地址

对于点对点链路&#xff0c;为了节约IPv4地址&#xff0c;一般为其分配/30地址块&#xff0c;这样包含4个地址&#xff1a;最小地址作为网络地址&#xff0c;最大地址作为广播地址&#xff0c;剩余两个可分配地址&#xff0c;分配给链路两端的接口&#xff0c;这是最普遍的方法…...

青少年棒球锦标赛发展·棒球1号位

青少年棒球锦标赛发展 1. 青少年棒球锦标赛简介 青少年棒球锦标赛是一个令人兴奋的国际性比赛&#xff0c;每年都有来自世界各地的优秀青少年棒球选手参加。这个锦标赛旨在提供一个展示青少年棒球选手的技能和才华的平台&#xff0c;同时也是为了推动棒球在全球范围内的普及和…...