Spring 简介

1. Spring简介

1.1 Spring 核心设计思想

1.1.1 Spring 是什么?
  • Spring 是包含了众多具法的 IoC 容器。
  • Spring 指的是 Spring Framework(Spring 框架),它是个开源框架,Spring 持泛的应场景,它可以让 Java 企业级的应程序开发起来更简单。
1.1.1.1 什么是IoC容器?
  • 容器是来容纳某种物品的装置。
  • IoC = Inversion of Control 翻译成中是"控制反转"的意思,控制权发的反转,不再是上级对象创建并控制下级对象了,是下级对象把注将当前对象中,下级的控制权不再由上级类控制了,这样即使下级类发任何改变,当前类都是不受影响的,这就是 IoC 的实现思想。
  • Spring具备两个核心功能:将对象存到容器,从容器中取出对象。对象的创建和销毁的权利都交给 Spring 来管理了,它本身具备了存储对象和获取对象的能。
1.1.1.2 DI 概念说明

DI 是 Dependency Injection 的缩写,翻译成中是"依赖注"的意思。依赖注就是由 IoC 容器在运期间,动态地将某种依赖关系注到对象之中。所以,依赖注(DI)和控制反转(IoC)是从不同的度的描述的同件事情,就是指通过引 IoC 容器,利依赖关系注的式,实现对象之间的解耦,IoC 是"标"也是种思想,标和思想只是种指导原则,最终还是要有可的落地案, DI就属于具体的实现。

1.1.1.3 IoC容器和普通程序开发的区别
  • 将对象存储在 IoC 容器相当于将以后可能的所有具制作好都放到仓库中,需要的时候直接取就了,完再把它放回到仓库。
  • new 对象的式相当于,每次需要工具了才现做,用完扔掉了也不会保存,下次再用的时候还得重新做。

1.2 Spring 创建和使用

1.2.1 创建 Spring 项目

使 Maven 式来创建个 Spring 项,创建 Spring 项和 Servlet 类似。

  1. 创建个 Maven 项

    然后跳转到了这个页面:

  2. 添加 Spring 框架持

    在项的 pom.xml 中添加 Spring 框架的持spring-context(spring 上下)和spring-beans(管理对 象的模块),xml 配置如下:

    org.springframework spring-context 5.2.3.RELEASE

    复制代码
    <dependency> 
        <groupId>org.springframework</groupId> 
        <artifactId>spring-beans</artifactId> 
        <version>5.2.3.RELEASE</version> 
    </dependency>
  3. 添加启动类

    最后在创建好的项 java 件夹下创建个启动类,包含 main 法即可:

    public class App {

    public static void main(String[] args) {

    复制代码
    }

    }

到此为止,一个spring项目就搭建好了。

1.2.2 存储 Bean 对象

Bean 就是 Java 语中的个普通对象,例如:

复制代码
public class User {
   public String sayHi(String name) { 
       return name + " hello!";
   }
}
  1. 创建 Bean

    public class User {

    public String sayHi(String name) {

    return name + " hello!";

    }

    }

  2. 将 Bean 注册到容器

    <<1>>在创建好的项中添加 Spring 配置件 spring-config.xml,将此件放到 resources 的根录下, 如下图所示:

    <<2>> spring-config.xml 配置件的固定格式为以下内容,复制粘贴即可

    <?xml version="1.0" encoding="UTF-8"?>

<<3>> 将 User 对象注册到 Spring 中就可以,在 标签中添加配置 ,格式如下:

复制代码
<beans>
  <bean id="user" class="com.bit.User"></bean>
</beans>
1.2.3 获取 Bean 对象
  1. 创建 Spring 上下对象

    <<1>>使 ApplicationContext

    ApplicationContext context = new ClassPathXmlApplicationContext("spring-con fig.xml");

<<2>>使 BeanFactory

复制代码
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));

ApplicationContext VS BeanFactory

  • 继承关系和功能:Spring 容器有两个顶级的接:BeanFactory 和 ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能, ApplicationContext 属于 BeanFactory 的类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性, 还添加了对国际化持、资源访问持、以及事件传播等的持。
  • 性能:ApplicationContext 是次性加载并初始化所有的 Bean 对象, BeanFactory 是需要那个才去加载那个,因此更加轻量。
    <<3>>使用ClassPathXmlApplicationContext
    ClassPathXmlApplicationContext 属于 ApplicationContext 的类,拥有 ApplicationContext 的所有功能,通过 xml 的配置来获取所有的 Bean 容器。
  1. 获取指定的 Bean 对象

    // 1.得到 Spring 上下?对象

    ApplicationContext context = new ClassPathXmlApplicationContext("spring-con fig.xml");

    // 2.加载某个 bean

    User user = (User) context.getBean("user");

Bean 的 Id 要对应,如下图所示:

getBean 法的更多法

  • 根据类型获取 Bean:

    UserController user = context.getBean(UserController.class);

  • 名称 + 类型获取 Bean:

    UserController user = context.getBean("user", UserController.class);

当有个类型被重复注册到 spring-config.xml 中时,只能使根据名称获取了,如以 下场景就会导致程序报错:

1.2.4 使用 Bean
复制代码
public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下?对象
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml"); 
        // 2.加载某个 bean
        User user = (User) context.getBean("user");
        // 3.调?相应的?法
        System.out.println(user.sayHi("Java"));
    }
}
1.2.4 使用注解读取和存储对象
  1. 配置扫描路径

配置下存储对象的扫描包路径,只有在被配置的包下且添加了注解的类才能被正确的识别并保存到 Spring 中。

在 spring-config.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:content="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">
 
 <content:component-scan base-package="com.bit.service"></content:component-scan>
 
</beans>
  1. 添加注解存储 Bean 对象
    将对象存储在 Spring 中,有两种注解类型可以实现:
  • 类注解:@Controller(表示业务逻辑层)、@Service(服务层)、@Repository(持久层)、@Component、@Configuration(配置层)。
  • 法注解:@Bean
    类注解是添加到某个类上的,法注解是放到某个法上的。

Bean 命名规则:

通常 bean 使的都是标准的驼峰命名,读取的时候字写:

当字和第个字都是写时,不能正常读取到 bean :

Spring中 bean 存储时成的命名规则:

<1> 在 Idea 中使搜索关键字"beanName"可以看到以下内容:

它使的是 JDK Introspector 中的 decapitalize 法,源码如下:

复制代码
public static String decapitalize(String name) {
   if (name == null || name.length() == 0) {
       return name;
   }
   // 如果第?个字?和第?个字?都为?写的情况,是把 bean 的?字?也?写存储了 
   if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && 
       Character.isUpperCase(name.charAt(0))){
       return name;
   }
	// 否则就将?字??写
   char chars[] = name.toCharArray();
   chars[0] = Character.toLowerCase(chars[0]);
   return new String(chars);
}

所以对于上报错的代码,只要改为以下代码就可以正常运了:

类注解使用示例:

  • @Controller(控制器存储)

    使 @Controller 存储 bean:

    @Controller // 将对象存储到 Spring 中

    public class UserController {

    public void sayHi(String name) {

    System.out.println("Hi," + name);

    }

    }

先使之前读取对象的式读取上的 UserController 对象:

复制代码
public class Application {
	public static void main(String[] args) {
		// 1.得到 spring 上下?
		ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		// 2.得到 bean
		UserController userController = (UserController) context.getBean("userController");
		// 3.调? bean ?法
		userController.sayHi("Bit");
	}
}
  • @Service(服务存储)

    使 @Service 存储 bean

    @Service

    public class UserService {

    public void sayHi(String name) {

    System.out.println("Hi," + name);

    }

    }

读取 bean

复制代码
class App {
	public static void main(String[] args) {
		 // 1.得到 spring 上下?
		 ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		 // 2.得到 bean
		 UserService userService = (UserService) context.getBean("userService");
		 // 3.调? bean ?法
		 userService.sayHi("Bit");
	}
}
  • @Repository(仓库存储)

    使 @Repository 存储 bean

    @Repository

    public class UserRepository {

    public void sayHi(String name) {

    System.out.println("Hi," + name);

    }

    }

读取 bean

复制代码
class App {
	public static void main(String[] args) {
		// 1.得到 spring 上下?
		ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		// 2.得到某个 bean
		UserRepository userRepository = (UserRepository) context.getBean("userRepository");
		// 3.调? bean ?法
		userRepository.sayHi("Bit");
	}
}
  • @Component(组件存储)

    使 @Component 存储 bean

    @Component

    public class UserComponent {

    public void sayHi(String name) {

    System.out.println("Hi," + name);

    }

    }

读取 bean

复制代码
class App {
	public static void main(String[] args) {
		// 1.得到 spring 上下?
		ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		// 2.得到某个 bean
		UserComponent userComponent = (UserComponent) context.getBean("userComponent");
	 	// 3.调? bean ?法
		userComponent.sayHi("Bit");
	 }
}
  • @Configuration(配置存储)

    @Configuration

    public class UserConfiguration {

    public void sayHi(String name) {

    System.out.println("Hi," + name);

    }

    }

读取 bean

复制代码
class App {
	public static void main(String[] args) {
		// 1.得到 spring 上下?
		ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		// 2.得到某个 bean
		UserConfiguration userConfiguration = (UserConfiguration) context.getBean("userConfiguration");
		// 3.调? bean ?法
		userConfiguration.sayHi("Bit");
	 }
}

为什么有这么多功能一样的注解:

它们的功能都是一样的,让程序员看到类注解之后,就能直接了解当前类的途。

程序的程分层,调流程:

查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现,这些注解都有个注解 @Component,说明它们本身就是属于 @Component 的"类"。

法注解@Bean 使用示例:

法注解要配合类注解使

在 Spring 框架的设计中,法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中

  • 使用@Bean存储 bean

    @Component

    public class Users {

    @Bean

    public User user1() {

    User user = new User();

    user.setId(1);

    user.setName("Java");

    return user;

    }

    }

读取bean

复制代码
public class Application {
   public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); 
       User user = (User) context.getBean("user1"); 
       System.out.println(user.toString());
   }
}
  • 重命名 Bean

    可以通过设置 name 属性给 Bean 对象进重命名。重

    @Component

    public class Users {

    @Bean(name = {"u1"})

    public User user1() {

    User user = new User();

    user.setId(1);

    user.setName("Java");

    return user;

    }

    }

命名的 name 其实是个数组,个 bean 可以有多个名字。

复制代码
@Bean(name = {"u1", "us1"}) public User user1() { 
 User user = new User(); 
 user.setId(1); 
 user.setName("Java"); 
 return user;
}

name={} 可以省略

复制代码
@Bean({"u1", "us1"})
public User user1() { 
  User user = new User(); 
  user.setId(1); 
  user.setName("Java"); 
  return user;
}

此时使 u1/us1 就可以获取到 User 对象了

复制代码
class App {
   public static void main(String[] args) {
       // 1.得到 spring 上下?
       ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); 
       // 2.得到某个 bean
       User user = (User) context.getBean("u1");
       // 3.调? bean ?法
       System.out.println(user);
   }
}
  1. 对象装配

获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注。

<1> 属性注

属性注使 @Autowired 实现。

属性注的优点是简洁,使便;缺点是只能于 IoC 容器,如果是 IoC 容器不可,并且只 有在使的时候才会出现 NPE(空指针异常)。

示例:

Service 类的实现代码如下:

复制代码
import org.springframework.stereotype.Service;

@Service
public class UserService {

   /**
    * 根据 ID 获取?户数据
    *
    * @param id
   * @return
    */
   public User getUser(Integer id) { 
       // 伪代码,不连接数据库 
       User user = new User(); 
       user.setId(id); 
       user.setName("Java-" + id); 
       return user;
   }
}
  • Controller 类的实现代码如下:

    import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller;

    @Controller

    public class UserController {

    // 注??法1:属性注?

    @Autowired

    private UserService userService;

    public User getUser(Integer id) {

    return userService.getUser(id);

    }

    }

核实现:

获取 Controller 中的 getUser 法:

复制代码
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserControllerTest {
   public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); 
       UserController userController = context.getBean(UserController.cla ss);
   		System.out.println(userController.getUser(1).toString());
   }
}

运行结果:

<2>构造法注

构造法注是在类的构造法中实现注。

构造法注是 Spring 推荐的注式,它的缺点是如果有多个注会显得较臃肿,但出现这 种情况应该考虑下当前类是否符合程序的单职责的设计模式,优点是通性,在使之前定能把保证注的类不为空。

示例:

复制代码
@Controller
public class UserController2 {

   // 注??法2:构造?法注? 
   private UserService userService;     // 创建userService引用

   @Autowired 
   public UserController2(UserService userService) {   // 注入并通过构造方法让上面的引用指向注入进来的这个对象
       this.userService = userService;
  }

   public User getUser(Integer id) { 
       return userService.getUser(id); 
   }
}

<<1>> 如果只有个构造法,那么 @Autowired 注解可以省略

<<2>>如果类中有多个构造法,那么需要添加上 @Autowired 来明确指定到底使哪个构造法,否则 程序会报错

<3> Setter 注

Setter 注和 构造方法注入 实现类似,只不过在设置 set 法的时候需要加上 @Autowired 注解。

Setter 式是 Spring 前期版本推荐的注式,但通性不如构造法,所有 Spring 现版本已 经推荐使构造法注的式来进类注了。

示例:

复制代码
@Controller
public class UserController3 {

   // 注??法3:Setter注? 
   private UserService userService;

   @Autowired
   public void setUserService(UserService userService) { 
       this.userService = userService;
   }

   public User getUser(Integer id) { 
       return userService.getUser(id); 
   }
}

<4> @Resource注入

@Autowired 和 @Resource 的区别:

  • @Autowired 来于 Spring, @Resource 来于 JDK 的注解
  • 相于 @Autowired ,@Resource 持更多的参数设置,例如 name 设置,根据名称获取 Bean。
  • @Autowired 可于 Setter 注、构造方法注和 属性注。 @Resource 只能于 Setter 注和 属性注 不能于构造函数注。

<5>使用同类型多个 Bean 报错处理,当出现以下多个 Bean,返回同对象类型时程序会报错。

示例:

复制代码
@Component
public class Users {
    
   // 第一个bean
   @Bean
   public User user1() { 
       User user = new User(); 
       user.setId(1); 
       user.setName("Java"); 
       return user;
   }

   // 第二个bean
   @Bean
   public User user2() { 
       User user = new User(); 
       user.setId(2); 
       user.setName("MySQL"); 
       return user;
   }
}

在另个类中获取 User 对象:

复制代码
@Controller
public class UserController4 {

   // 注? 
   @Resource 
   private User user;

   public User getUser() { 
       return user;
   }
}

执结果:

解决方法:

<<1>>使 @Resource(name="XXX")来指定bean

复制代码
@Controller
class UserController4 { 
   // 注?
   @Resource(name = "user1") 
   private User user;

   public User getUser() { 
       return user;
   }
}

<2> 使 @Qualifier来指定bean

复制代码
@Controller
public class UserController5 { 
    // 注?
    @Autowired 
    @Qualifier(value = "user2") 
    private User user;

    public User getUser() { 
        return user;
    }
}

2. Bean 详解

2.1 Bean的作用域

限定程序中变量的可范围叫做作域,或者说在源代码中定义变量的某个区域就叫做作域。

Bean 的作域是指 Bean 在 Spring 整个框架中的某种为模式。

Bean 有 6 种作域:singleton(单例作域),prototype(原型作域/多例作域), request(请求作域),session(回话作域),application(全局作域),websocket(HTTP WebSocket 作域)。后 4 种状态是 Spring MVC 中的值,在普通的 Spring 项中只有前两种。

2.1.1 singleton

Spring默认选择该作域,该作域下的Bean在IoC容器中只存在个实例:获取Bean(即通过applicationContext.getBean等法获取)及装配Bean(即通过@Autowired等注)都是同个对象。通常状态的Bean(Bean对象的属性状态不需要更新)使该作域,单例可以很程度上提性能。

示例:

公共bean

复制代码
@Component
public class Users {
 	@Bean
 	public User user1() {
 		User user = new User();
 		user.setId(1);
 		user.setName("Java"); // 【重点:名称是 Java】
 		return user;
 	}
}

A 户使时,进了修改操作:

复制代码
@Controller
public class BeanScopesController {
	 @Autowired
 	 private User user1;
	 public User getUser1() {
		 User user = user1;
		 System.out.println("Bean 原 Name:" + user.getName());
		 user.setName("悟空"); // 【重点:进?了修改操作】
		 return user;
	 }
}

B 户再去使公共 Bean :

复制代码
@Controller
public class BeanScopesController2 {
	 @Autowired
	 private User user1;
	 public User getUser1() {
		 User user = user1;
		 return user;
	 }
}

查看 A 户和 B 户公共 Bean 的值:

复制代码
public class BeanScopesTest {
 public static void main(String[] args) {
	 ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
 
	 BeanScopesController beanScopesController = context.getBean(BeanScopesController.class);
	 System.out.println("A 对象修改之后 Name:" + beanScopesController.getUser1().toString());
 
	 BeanScopesController2 beanScopesController2 = context.getBean(BeanScopesController2.class);
	 System.out.println("B 对象读取到的 Name:" + beanScopesController2.getUser1().toString());
 }
}

执结果:

2.1.2 prototype

每次对该作域下的Bean的请求都会创建新的实例:获取Bean(即通过applicationContext.getBean等法获取)及装配Bean(即通过@Autowired注)都是新的对象实例。通常有状态(Bean对象的属性状态需要更新)的Bean使该作域。

2.1.3 request

每次http请求会创建新的Bean实例,类似于prototype。用于次http的请求和响应的共享Bean,限定SpringMVC中使。

2.1.4 session

在个http session中,定义个Bean实例,用于用户回话的共享Bean,如:记录个户的登陆信息。限定SpringMVC中使。

2.1.5 application

在个http servlet Context中,定义个Bean实例,用于Web应的上下信息,如:记录个应的共享信息。限定SpringMVC中使。
application 是 Spring Web 中的作域,作于 Servlet 容器。singleton 是 Spring Core 的作域,singleton 作于 IoC 的容器。

2.1.6 websocket

在个HTTP WebSocket的命周期中,定义个Bean实例,用于WebSocket的每次会话中,保存了个Map结构的头信息,将来包裹客户端消息头。第次初始化后,直到WebSocket结束都是同个Bean。限定Spring WebSocket中使。

2.2 设置Bean的作用域

使 @Scope 标签就可以来声明 Bean 的作域。

<1> 直接设置值:@Scope("prototype")

<2> 使枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

示例;

复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
public class Users {
	 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
	 @Bean(name = "u1")
	 public User user1() {
		 User user = new User();
		 user.setId(1);
		 user.setName("Java"); // 【重点:名称是 Java】
		 return user;
	 }
}

2.2 Bean的生命周期

2.2.1 Spring 执行流程
2.2.2 Bean 的执行流程

启动 Spring 容器 --> 实例化 Bean(分配内存空间,从到有) --> Bean注册到Spring中(存操作) --> 将Bean装配到需要的类中(取操作)。

2.2.3 Bean的生命周期

命周期指的是个对象从诞到销毁的整个命过程,我们把这个过程就叫做个对象的命周期。

  1. 实例化 Bean(为 Bean 分配内存空间)
  2. 设置属性(Bean 注和装配)
  3. Bean 初始化
    3.1 实现了各种 Aware 通知的法,如 BeanNameAware、BeanFactoryAware ApplicationContextAware >的接法;
    3.2 执 BeanPostProcessor 初始化前置法;
    3.3 执 @PostConstruct 初始化法,依赖注操作之后被执;
    3.4 执指定的 init-method 法(如果有指定的话);
    3.5 执 BeanPostProcessor 初始化后置法。
  4. 使 Bean
  5. 销毁 Bean。执行销毁容器的各种法,如 @PreDestroy、DisposableBean 接法、destroy-method。
  • 实例化和初始化的区别:
    实例化和属性设置是 Java 级别的系统"事件",其操作过程不可预和修改;初始化是给开发者 提供的,可以在实例化之后,类加载完成之前进定义"事件"处理。
  • 如何理解Bean生命周期:
    Bean 的命流程看似繁琐,但咱们可以以活中的场景来理解它,如我们现在需要买栋房,那么我们的流程是这样的:
  1. 先买房(实例化,从到有);
  2. 装修(设置属性);
  3. 买家电,如洗机、冰箱、电视、空调等([各种]初始化);
  4. 住(使 Bean);
  5. 卖出去(Bean 销毁)。
相关推荐
devlei4 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
pshdhx_albert5 小时前
AI agent实现打字机效果
java·http·ai编程
沉鱼.445 小时前
第十二届题目
java·前端·算法
努力的小郑6 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
赫瑞6 小时前
数据结构中的排列组合 —— Java实现
java·开发语言·数据结构
Victor3567 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3567 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁7 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp7 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
周末也要写八哥7 小时前
多进程和多线程的特点和区别
java·开发语言·jvm