目录
Spring概述
Spring是什么
Spring是Java的一个设计层面的开发框架,它将对象的管理权力移交给Spring容器,以达到降低设计出来的程序耦合度的目标
在Spring中用bean来表示一个对象
Bean是什么
Bean就是Spring中的一个对象,在Spring中配置后可以在Java代码中获取这个bean来获取对象,一个bean的生命周期如下
bean创建 - 设置bean属性 - bean后置处理器 - bean对象初始化 - bean后置处理器 - 对象创建完成 - bean对象销毁 - 关闭IoC容器
Spring的优势
1. 使用Spring设计出来的代码耦合度低。将对象之间的依赖关系以及管理交给Spring处理,避免开发者在编码过程中导致的过度耦合
2. AOP支持。Spring支持AOP,能够很方便地进行面向切面的编程,例如性能检测、事务管理、日志等等
3. 具有很多的API以供集成其他框架
4. 低侵入式设计,代码的可读性好、污染性低
在继续讲Spring之前,有两个概念需要先提到,一个是IoC-控制反转,一个是DI-依赖注入
IoC
IoC - Inversion of Control,中文翻译为控制反转,这是一种设计思想,也就是将对象实体交给容器控制,这个容器在Spring中就是一个Map
在传统的设计模式下,开发者需要自己创建业务需要的类以及进行相关属性的注入,但是当有了IoC后,开发者可以将管理对象的权力移交给IoC容器,则不需要主动去创建这些对象,只需要从IoC容器中拿取所需要的对象即可
利用IoC设计出的程序具有松耦合的特性,同时便于测试和复用,并且使得整个体系变得更加灵活!
DI
DI - Dependency Injection,中文翻译为依赖注入,就是由IoC容器动态地将某个依赖关系注入到组件中,只需要通过简单的配置,就可以指定目标需要的资源,完成业务需求
在Spring下,程序需要的资源都由IoC来提供,也就是说程序依赖IoC容器,IoC容器可以注入程序中某个对象所需要的资源,比如对象、字面量等
接下来就讲讲使用Spring的正确姿势
Spring中的IoC和DI
Spring通过IoC容器管理所有Java对象的实例化和初始化,可以通过.getBean("objname")从IoC容器中获取对象,在创建对象过程中将对象依赖属性通过配置进行注入,可以说IoC是一种思想,而DI是具体实现方法
Spring环境搭建
添加Spring相关依赖
在新建的Maven工程中添加使用Spring所需的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
等待Maven将依赖下载、配置完成后即可使用Spring进行开发了!
Spring - IoC 基于xml进行管理
首先为了演示方便,编写一个User类,其中只有一个方法add
public class User {
public void add(){
System.out.println("add...");
}
}
配置文件
配置bean.xml文件,在其中有spring的一些规范,以及用到的命名空间
在配置文件中加入与我们刚刚创建的User类对应的bean,其中id是自定义的一个唯一标识这个bean的,class是你这个bean对应的Java代码中的类的类全路径名
<bean id="user" class="com.aki.sping6.User"></bean>
获取对象
在测试类中添加如下代码,这里的getBean是用id来获取,如果同类下有唯一的bean,也可以直接使用该类.class来获取
public class TestUser {
@Test
public void test_user(){
//加载spring配置文件,对象创建
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//从容器中获取创建的对象
User user = (User) context.getBean("user");
System.out.println(user);
user.add();
}
}
这里是一个最简单的Spring IoC的例子,创建context时就已经将bean创建了,后面只是从其中拿出这个bean来获取对象,这是单实例的情况
如果要有多实例,则可以通过scope="prototype"来配置,每次获取都创建一个不同对象
<bean id="user" class="com.aki.spring6iocxml.bean.User" scope="prototype"></bean>
Spring - DI 基于xml进行依赖注入
为了演示方便,这里先创建一个Book类,其中有属性也有构造器也有set方法
public class Book {
private String book_name;
private String author;
public void setBook_name(String book_name) {
this.book_name = book_name;
}
public void setAuthor(String author) {
this.author = author;
}
public Book() {
}
public Book(String book_name, String author) {
this.book_name = book_name;
this.author = author;
}
}
Setter方法注入
这种情况下需要在类中添加属性的setter方法,例如Book类中的setBook_name
随后在xml配置文件中添加这个类,并且用<property>标签进行set方法的依赖注入
<bean id="book" class="com.aki.spring6iocxml.di.Book">
<!--1. set 方法依赖注入 <property>-->
<property name="book_name">
<value><![CDATA[a<b]]></value>
</property>
<property name="author" value="aki"></property>
</bean>
在property中name与要注入的属性名称一致,value内容是你要注入的内容
这里用到了![CDATA[...]],由于xml有一些符号是关键字,因此要使用这些符号时,除了用转义,也可以使用cdata区,其中...是用户自定义的,可以直接使用xml的关键字
构造器注入
这种情况下需要在类中添加构造器
随后在xml配置文件中添加这个类,并且用<constructor-arg>标签进行注入
<bean id="bookgoucaoqi" class="com.aki.spring6iocxml.di.Book">
<!--2. constructor 方法依赖注入 <constructor-arg>-->
<constructor-arg name="book_name" value="Java2"></constructor-arg>
<constructor-arg name="author" value="aki2 <"></constructor-arg>
</bean>
注入完成后在getBean时获取到的对象就是已经注入属性的对象了!
这里的两个例子都是常规类型的注入,如果碰到了数组或者对象,要如何注入呢?
创建一个Employee和Department类
public class Employee {
private String ename;
private int age;
private String[] hobby;
private Department department;
public void work(){
System.out.println(ename+" "+age+" is working loving "+Arrays.toString(hobby));
}
}
public class Department {
private String dname;
private List<Employee> emplist;
private Map<String,Employee> map;
public void info(){
System.out.println(dname);
for(Employee emp:emplist){
System.out.println(emp.getEname());
}
}
public void map(){
map.get("2").work();
}
}
数组注入
在配置文件中添加bean,并且在hobby这个property中加入array标签,将需要的依赖注入即可
<!--数组注入 <array>
<value>a</value>
</array>
-->
<bean id="emp" class="com.aki.spring6iocxml.di_obj.Employee">
<property name="ename" value="lucy"></property>
<property name="age" value="40"></property>
<property name="department" ref="dept"></property>
<property name="hobby">
<array>
<value>"a"</value>
<value>b</value>
<value>'c'</value>
</array>
</property>
</bean>
集合注入
在配置文件中添加bean,并且在emplist集合property中加入list标签,其中map比较特殊,需要用<entry>标签代表一个kv,并且在entry下用key和value描述kv值
<!--
注入list
<property>
<list>
<ref bean="..."></ref> 集合中是 类
<value>...</value> 集合中是 常规类型
</list>
</property>
注入map
<property>
<map>
<entry>
<key>
<value>...</value>
</key>
<value>...</value> v是 常规类型
<ref bean="..."></ref> v是 类
</entry>
</map>
</property>
-->
<bean id="department" class="com.aki.spring6iocxml.di_obj.Department">
<property name="dname" value="tech department"></property>
<property name="emplist">
<list>
<ref bean="emp1"></ref>
<ref bean="emp2"></ref>
</list>
</property>
<property name="map" ref="map"></property>
</bean>
1. 除了直接这样硬写,还可以用util来进行注入,需要加入util的命名空间
xmlns:util="http://www.springframework.org/schema/util<util:map id="map"> <entry> <key> <value>2</value> </key> <ref bean="emp2"></ref> </entry> </util:map>
这样在外部定义了id为"map"的map后,可以在property中用ref属性直接引用这个map
2. 此外,还可以用p命名空间进行注入,同样需要引入p的命名空间
xmlns:p="http://www.springframework.org/schema/p"<bean id="dpt_p" class="com.aki.spring6iocxml.di_obj.Department" p:dname="ppppp" p:map-ref="map"> </bean>
在p:属性名后就可以直接用了!,p命名空间下同样可以引用util定义的集合,使用-ref
对象注入
对象的注入也很简单,有三种方法
1. 引入外部的bean,这种方法最好理解,直接上代码
<!-- 注入对象变量
一、引入外部bean
1. 创建两个类对象 department 和 employee
2. 在employee的bean中 用property引入dept的bean
-->
<bean id="department" class="com.aki.spring6iocxml.di_obj.Department">
<property name="dname" value="fire department"></property>
</bean>
<bean id="employee" class="com.aki.spring6iocxml.di_obj.Employee">
<property name="ename" value="aki"></property>
<property name="age" value="20"></property>
<!--对象类型属性 private Department department 注入
用ref="..."
其中的 ... 是在xml文件中创建的该对象的bean的id
-->
<property name="department" ref="department"></property>
</bean>
这样在employee对象中的department属性就被注入了上面定义的department对象!
2. 用内部bean,这就相当于在Java中的 在内部直接new一个对象出来
<!-- 注入对象变量
二、用内部bean
在bean的property中 写内部bean
其中这个内部bean也可以直接拿来用
-->
<bean id="department2" class="com.aki.spring6iocxml.di_obj.Department">
<property name="dname" value="police department"></property>
</bean>
<bean id="employee2" class="com.aki.spring6iocxml.di_obj.Employee">
<property name="ename" value="bully"></property>
<property name="age" value="30"></property>
<!-- 内部bean -->
<property name="department">
<bean id="department2" class="com.aki.spring6iocxml.di_obj.Department">
<property name="dname" value="police department"></property>
</bean>
</property>
</bean>
这种方法在对象的property标签内bean了一个department对象
3. 级联赋值,这种方法可以修改对象的值,利用对象.属性可以修改!
<!-- 注入对象变量
三、级联赋值
property下可用 对象.属性 进行值的修改和赋值
-->
<bean id="department3" class="com.aki.spring6iocxml.di_obj.Department">
<property name="dname" value="medical department"></property>
</bean>
<bean id="employee3" class="com.aki.spring6iocxml.di_obj.Employee">
<property name="age" value="80"></property>
<property name="ename" value="cindy"></property>
<property name="department" ref="department3"></property>
<property name="department.dname" value="医疗"></property>
</bean>
引入外部文件
在上面的处理过程中,各种属性的值都是我们在配置文件中手写的,如果要更改则需要该很多地方,导致耦合度高,我们可以使用引入外部文件的方式来降低耦合度
这里利用context命名空间,同样需要引入
xmlns:context="http://www.springframework.org/schema/context
然后利用context引入外部文件
<!--利用context 引入外部文件-->
<context:property-placeholder
location="classpath:jdbc.properties"></context:property-placeholder>
引入这个外部文件后就可以使用其中的值了! 注意格式 value="${...}",...是文件中的名称
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${url}"></property>
</bean>
自动装配 - autowire
之前讲到的对象都是手动装配,很麻烦,spring提供了一种自动装配办法,在bean标签内的autowire属性可以实现对象的自动装配
1. autowire = "byType" 根据类型装配,如果在IoC中没有能够匹配的类型,最终装配为null,如果在IoC中有多个能够匹配的bean,则会报错
2. autowire = "byName" 根据名称装配,这种情况下<bean id>中的id要对应java代码中装配目标的属性的名称,否则报错
例如,在UserController中有UserService属性,UserService中有UserDao属性,则可以自动装配,如下配置xml文件,实现对象的自动装配
<bean id="controller" class="com.aki.spring6iocxml.auto_bean.controller.UserController" autowire="byType">
</bean>
<bean id="service" class="com.aki.spring6iocxml.auto_bean.service.UserServiceImpl" autowire="byName">
</bean>
<bean id="userDao" class="com.aki.spring6iocxml.auto_bean.dao.UserDaoImpl"></bean>
到这里都是通过xml文件进行依赖注入,这很麻烦,因此spring提供了注释管理IoC
Spring 注释管理
添加context命名空间
Spring可以通过注释进行依赖注入,首先要引入context命名空间,这个在上面讲过了,就不再赘述了
开启组件扫描
利用context:component-scan开启组件扫描,这里需要指定扫描的包,基本写法会扫描包内全部的注释,在下面给出了排除或者只选择某些的写法,可以规定要哪些注释或者要哪些类
<!-- 基本写法-->
<context:component-scan base-package="com.aki.spring6.bean"></context:component-scan>
<!-- 有排除或者只选择某些的写法-->
<context:component-scan base-package="com.aki">
<!-- <context:exclude-filter type="" expression=""/>
type:设置排除或包含的依据
type="annotation" 根据注解排除 expression中设置要排除的注解的全类名
下面Controller这个注解不会扫描
type="assignable" 根据类型排除 expression中设置要排除的类型的全类名
下面com.aki.spring6.User这个类的注解不会扫描
<context:include-filter> 同理
-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!-- <context:exclude-filter type="assignable" expression="com.aki.spring6.bean.User"/>-->
</context:component-scan>
使用注释定义bean
这里有四种定义bean的注释,其功能都是一样的,只是名字不同
@Component 泛化的概念
@Repository 数据访问层
@Service 业务层
@Controller 控制层
在UserController类上添加注释@Controller,则可以扫描这个注释创建UserController的一个对象,不需要再进行配置文件的编写了!
@Controller
public class UserController {
private UserService userService;
}
同样的,可以再UserService类上添加注释@Service
@Service
public class UserServiceImpl implements UserService{
private UserDao userDao;
}
Autowire进行依赖注入
@Autowire可以添加在属性、setter方法、构造方法、形参上,都可以达到自动装配
//注入service
@Autowired //1. 属性注入 根据类型找到对应对象 完成注入
private UserService userService;
//2. set方法注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
//3. 构造方法注入
private UserDao userDao;
@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
//4. 形参上注入 在参数前加入@autowired,如果有多个参数 写一个就可以
private UserDao userDao;
private UserController userController;
public UserServiceImpl(@Autowired UserDao userDao, UserController userController) {
this.userDao = userDao;
this.userController = userController;
}
Autowired 默认根据类型进行装配,如果想要根据名称进行装配,需要搭配@Qualifier注释
@Autowired
@Qualifier(value = "dao3")
private UserDao userDao;
其中@Qualifier的value要与目标类的创建bean注释中的 value一致
如果只有一个有参数的构造函数,则无需注解,可以省略@Autowired;如果有多个构造函数,需要注解在构造方法上,而不是在形参上,并且只有一个构造方法可以被注解!
Resource注解
除了Autowired,JDK还提供了Resource注解进行依赖注入
默认根据名称装配,未指定name时使用属性名为name,当name找不到时用类型装配,可以用于属性和setter方法上
使用@Resource需要引入依赖
<!-- @Resource依赖-->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.0</version>
</dependency>
引入依赖后就可以直接使用了,可以达到与autowired一样的效果
@Resource
private ResourceTest resourceTest;
Spring 纯注解
Spring可以实现纯注解,只要定义一个配置类即可,其中的@Configuration表示这是一个配置类,@ComponentScan表示了扫描空间
//配置类 可以全注解开发
@Configuration
@ComponentScan("com.aki.spring6")
public class SpringConfig {
}
随后在获取contex时使用的是AnnotationConfigApplicationContext(SpringConfig.class),用这个配置类为参数来获取context
public class TestConfig {
public static void main(String[] args) {
//加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserController userController = context.getBean(UserController.class);
userController.add();
}
}
至此,Spring的IoC和DI就已经学习完毕啦!