1、Spring概述 1.1、Spring五大模块
功能模块
功能介绍
Core Container
核心容器,在Spring环境下使用任何功能都必须基于IOC容器
AOP&Aspect
面向切面编程
Testing
提供了对junit或TestNG测试框架的整合
Data Access/Integration
提供了对数据访问/集成的功能
Spring MVC
提供了面向Web应用程序的集成功能
1.2、Spring特性
非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。
控制反转:IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。
面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。
容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。
组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。
声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。
2、IOC IOC :Inversion of Control,翻译过来是控制反转 ,控制反转的思想完全颠覆了应用程序组件获取资源的传统方式,反转了资源的获取方向,容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。
DI :Dependency Injection,翻译过来是依赖注入 。DI 是IOC的另一种表述方式,即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
所以:IOC就是一种控制反转的思想, 而DI是对IOC的一种具体实现。
2.1、IOC在Spring中的实现 Spring的IOC容器就是IOC思想的一个落地的产品实现。IOC容器中管理的组件也叫做bean。在创建bean之前,首先需要创建IOC容器。
Spring提供了IOC容器的两种实现方式 :
① BeanFactory :这是IOC容器的基本实现,是Spring内部使用的接口。面向Spring本身,不提供给开发人员使用。
② ApplicationContext :BeanFactory的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用ApplicationContext 而不是底层的 BeanFactory。
ApplicationContext主要实现类:
类型名
简介
ClassPathXmlApplicationContext
通过读取类路径下的XML格式的配置文件创建IOC容器对象
FileSystemXmlApplicationContext
通过文件系统路径读取XML格式的配置文件创建IOC容器对象
ConfigurableApplicationContext
ApplicationContext的子接口,包含一些扩展方法refresh()和close(),让ApplicationContext具有启动、关闭和刷新上下文的能力。
WebApplicationContext
专门为Web应用准备,基于Web环境创建IOC容器对象,并将对象引入存入ServletContext域中。
2.2、基于xml管理bean 2.2.1、项目环境搭建 ① 引入依赖
在pom.xml引入依赖spring-context,基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.3.20</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.13.2</version > <scope > test</scope > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.24</version > </dependency > </dependencies >
② 创建一个类
创建一个HelloWorld类
1 2 3 4 5 public class HelloWorld { public void sayHello () { System.out.println("Hello Spring!" ); } }
③ 创建配置文件
在resources目录下创建Spring的配置文件,名为applicationContext.xml,并将HelloWorld的对象交给Spring的IOC容器管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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 ="helloWorld" class ="com.dyz.HelloWorld" /> </beans >
④ 编写测试类 1 2 3 4 5 6 7 8 9 @Test public void testHelloWorld () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); HelloWorld helloworld = (HelloWorld) applicationContext.getBean("helloWorld" ); helloworld.sayHello(); }
输出:Hello Spring!
2.2.2、获取bean的方式 注意:Spring底层是通过读取配置文件bean标签中配置的类的全类名,然后通过反射调用其对应的无参构造来创建对象的,因此这个类必须要有无参构造。否则会报错。
① 根据id获取
即通过getBean(String name)方法来获取
1 2 3 4 5 6 7 @Test public void testHelloWorld () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); HelloWorld helloworld = (HelloWorld) applicationContext.getBean("helloWorld" ); helloworld.sayHello(); }
② 根据根据类型获取
即通过getBean(Class requiredType)方法来获取
1 2 3 4 5 6 7 @Test public void testHelloWorld () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); HelloWorld helloworld = applicationContext.getBean(HelloWorld.class); helloworld.sayHello(); }
③ 根据id和类型
即通过getBean(String name, Class requiredType)方法来获取
1 2 3 4 5 6 7 @Test public void testHelloWorld () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); HelloWorld helloworld = applicationContext.getBean("helloWorld" ,HelloWorld.class); helloworld.sayHello(); }
注意点: 当根据类型获取bean 时,要求IOC容器 中指定类型的bean有且只能有一个 。
当IOC容器中一共配置了两个:
1 2 <bean id ="helloWorldOne" class ="com.dyz.HelloWorld" /> <bean id ="helloWorldTwo" class ="com.dyz.HelloWorld" />
报错:NoUniqueBeanDefinitionException : No qualifying bean of type ‘com.dyz.HelloWorld’ available: expected single matching bean but found 2: helloWorldOne,helloWorldTwo
如果组件类实现了接口,可以根据接口类型获取bean吗?
可以,前提是bean唯一
如果一个接口有多个实现类,这些实现类都配置了bean,根据接口类型可以获取bean吗?
不行,因为bean不唯一
结论 :根据类型来获取bean时,在满足bean唯一性的前提下 ,其实只是看:『对象instanceof指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。也就是通过bean的类型、bean所继承的类的类型、bean所实现的接口的类型都可以获取bean。
2.2.3、依赖注入 ① setter注入
创建学生类:Student.java
1 2 3 4 5 6 7 8 9 10 11 12 13 @Data @AllArgsConstructor @NoArgsConstructor public class Student { private Integer sid; private String name; private Integer age; private String gender; }
配置bean,并且为属性赋值
property标签:通过组件类的setXxx()方法给组件对象设置属性。 name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,也就是Xxx小写后的值,和成员变量无关)。 value属性:指定属性值。
1 2 3 4 5 6 <bean id ="student" class ="com.dyz.entity.Student" > <property name ="sid" value ="1001" /> <property name ="name" value ="张三" /> <property name ="age" value ="20" /> <property name ="gender" value ="男" /> </bean >
测试方法:
1 2 3 4 5 6 7 @Test public void testSpring () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); Student student = applicationContext.getBean("student" , Student.class); System.out.println(student); }
输出:Student(sid=1001, name=张三, age=20, gender=男)
② 构造器注入
在类中添加有参构造(因为使用了lombok的@AllArgsConstructor,可以省略该步)
1 2 3 4 5 6 public Student (Integer sid, String name, Integer age, String gender) { this .sid = sid; this .name = name; this .age = age; this .gender = gender; }
配置bean
constructor-arg标签:通过组件类的构造方法给组件对象设置属性。 name属性:构造函数的变量名。 value属性:指定的变量值。
1 2 3 4 5 6 <bean id ="student2" class ="com.dyz.entity.Student" > <constructor-arg name ="sid" value ="1002" /> <constructor-arg name ="name" value ="李四" /> <constructor-arg name ="age" value ="18" /> <constructor-arg name ="gender" value ="女" /> </bean >
测试方法
1 2 3 4 5 6 7 @Test public void testSpring () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); Student student = applicationContext.getBean("student2" , Student.class); System.out.println(student); }
输出:Student(sid=1002, name=李四, age=18, gender=女)
特殊值处理 1、null值
1 2 3 <property name ="name" > <null /> </property >
注意:
1 <property name ="name" value ="null" />
以上写法,为name所赋的值是字符串null
2、xml实体
1 2 3 <property name ="name" value ="a < b" />
3、CDATA节
1 2 3 4 5 6 7 <property name ="name" > <value > <![CDATA[a < b]]></value > </property >
2.2.4、为类类型赋值
创建班级类:Clazz.java
1 2 3 4 5 @Data public class Clazz { private Integer cId; private String cName; }
在学生类Student.java中添加班级属性
1 2 3 4 5 6 7 8 9 @Data public class Student { private Integer sid; private String name; private Integer age; private String gender; private Clazz clazz; }
① 引用外部bean
配置Clazz类型的bean
1 2 3 4 <bean id ="clazz1" class ="com.dyz.entity.Clazz" > <property name ="CId" value ="001" /> <property name ="CName" value ="班级1" /> </bean >
为Student的clazz属性赋值
1 2 3 4 5 6 7 8 <bean id ="student" class ="com.dyz.entity.Student" > <property name ="sid" value ="1001" /> <property name ="name" value ="张三" /> <property name ="age" value ="20" /> <property name ="gender" value ="男" /> <property name ="clazz" ref ="clazz1" /> </bean >
② 使用内部bean 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <bean id ="student" class ="com.dyz.entity.Student" > <property name ="sid" value ="1001" /> <property name ="name" value ="张三" /> <property name ="age" value ="20" /> <property name ="gender" value ="男" /> <property name ="clazz" > <bean class ="com.dyz.entity.Clazz" > <property name ="CId" value ="001" /> <property name ="CName" value ="班级1" /> </bean > </property > </bean >
③ 级联属性赋值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <bean id ="clazz1" class ="com.dyz.entity.Clazz" /> <bean id ="student" class ="com.dyz.entity.Student" > <property name ="sid" value ="1001" /> <property name ="name" value ="张三" /> <property name ="age" value ="20" /> <property name ="gender" value ="男" /> <property name ="clazz" ref ="clazz1" /> <property name ="clazz.CId" value ="001" /> <property name ="clazz.CName" value ="班级1" /> </bean >
2.2.5、为数组类型赋值
在Student.java添加属性
1 2 3 4 5 6 7 8 9 10 11 @Data public class Student { private Integer sid; private String name; private Integer age; private String gender; private Clazz clazz; private String[] hobbies; }
为hobbies赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <bean id ="student" class ="com.dyz.entity.Student" > <property name ="sid" value ="1001" /> <property name ="name" value ="张三" /> <property name ="age" value ="20" /> <property name ="gender" value ="男" /> <property name ="clazz" ref ="clazz1" /> <property name ="hobbies" > <array > <value > 打球</value > <value > 听音乐</value > <value > 打游戏</value > </array > </property > </bean >
2.2.6、为集合类型赋值 ① 为List或Set集合赋值
在Clazz中添加属性:
1 2 3 4 5 6 7 @Data public class Clazz { private Integer cId; private String cName; private List<Student> students; }
为属性赋值
1 2 3 4 5 6 7 8 9 10 11 12 <bean id ="clazz1" class ="com.dyz.entity.Clazz" > <property name ="CId" value ="001" /> <property name ="CName" value ="班级1" /> <property name ="students" > <list > <ref bean ="student1" /> <ref bean ="student2" /> <ref bean ="student3" /> </list > </property > </bean >
另一种方法:配置一个List集合类型的bean,再引用外部bean赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 <util:list id ="students" > <ref bean ="student1" /> <ref bean ="student2" /> <ref bean ="student3" /> </util:list > <bean id ="clazz1" class ="com.dyz.entity.Clazz" > <property name ="CId" value ="001" /> <property name ="CName" value ="班级1" /> <property name ="students" ref ="students" /> </bean >
若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可
② 为Map集合赋值
创建Teacher类
1 2 3 4 5 @Data public class Teacher { private Integer tId; private String tName; }
在Student.java中添加属性
1 2 3 4 5 6 7 8 9 10 @Data public class Student { private Integer sid; private String name; private Integer age; private String gender; private Clazz clazz; private String[] hobbies; private Map<String, Teacher> teacherMap; }
配置Teacher类型的bean
1 2 3 4 5 6 7 8 <bean id ="teacherOne" class ="com.dyz.entity.Teacher" > <property name ="TId" value ="10010" /> <property name ="TName" value ="语文老师" /> </bean > <bean id ="teacherTwo" class ="com.dyz.entity.Teacher" > <property name ="TId" value ="10011" /> <property name ="TName" value ="数学老师" /> </bean >
给属性赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <bean id ="student" class ="com.dyz.entity.Student" > <property name ="sid" value ="1001" /> <property name ="name" value ="张三" /> <property name ="age" value ="20" /> <property name ="gender" value ="男" /> <property name ="clazz" ref ="clazz1" /> <property name ="hobbies" > <array > <value > 打球</value > <value > 听音乐</value > <value > 打游戏</value > </array > </property > <property name ="teacherMap" > <map > <entry value-ref ="teacherOne" key ="10010" /> <entry value-ref ="teacherTwo" key ="10011" /> </map > </property > </bean >
另一种方式:配置一个Map集合类型的bean,再引用外部bean赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <util:map id ="teachers" > <entry value-ref ="teacherOne" key ="10010" /> <entry value-ref ="teacherTwo" key ="10011" /> </util:map > <bean id ="student" class ="com.dyz.entity.Student" > <property name ="sid" value ="1001" /> <property name ="name" value ="张三" /> <property name ="age" value ="20" /> <property name ="gender" value ="男" /> <property name ="clazz" ref ="clazz1" /> <property name ="hobbies" > <array > <value > 打球</value > <value > 听音乐</value > <value > 打游戏</value > </array > </property > <property name ="teacherMap" ref ="teachers" /> </bean >
2.2.7、p命名空间赋值
引入p命名空间后,可以通过p命名空间进行属性赋值
1 2 3 4 5 6 7 <bean id ="stu" class ="com.dyz.entity.Student" p:sid ="1003" p:name ="王五" p:age ="21" p:gender ="男" p:teacherMap-ref ="teachers" />
2.2.8、引入外部数据源 ① 引入依赖 1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.29</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.11</version > </dependency >
② 创建属性文件
在resources目录下创建jdbc.properties
1 2 3 4 jdbc.url =jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC jdbc.driver =com.mysql.cj.jdbc.Driver jdbc.user =root jdbc.password =123456
③ 引入属性文件
在Spring配置文件中引入jdbc.properties
1 2 <context:property-placeholder location ="classpath:jdbc.properties" />
④ 配置bean
配置数据源,使用setter方法依赖注入
1 2 3 4 5 6 <bean id ="druidDataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="url" value ="${jdbc.url}" /> <property name ="driverClassName" value ="${jdbc.driver}" /> <property name ="username" value ="${jdbc.user}" /> <property name ="password" value ="${jdbc.password}" /> </bean >
⑤ 测试连接 1 2 3 4 5 6 7 @Test public void testDataSource () throws SQLException { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); DataSource dataSource = applicationContext.getBean(DataSource.class); Connection connection = dataSource.getConnection(); System.out.println(connection); }
2.2.9、bean的作用域 在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
取值
含义
创建对象的时机
singleton(默认)
在IOC容器中,这个bean的对象始终为单实例
IOC容器初始化时
prototype
这个bean在IOC容器中有多个实例
获取bean时
如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):
取值
含义
request
在一个请求范围内有效
session
在一个会话范围内有效
例如:
1 2 3 <bean id ="stu" class ="com.dyz.entity.Student" scope ="prototype" />
1 2 3 4 5 6 7 8 @Test public void testSpring () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); Student student1 = applicationContext.getBean("stu" , Student.class); Student student2 = applicationContext.getBean("stu" , Student.class); System.out.println(student1 == student2); }
输出为:false,若scope="singleton"
则会输出true
2.2.10、bean的生命周期
bean对象创建(调用无参构造器)
给bean对象设置属性
bean对象初始化之前操作(由bean的后置处理器负责)
bean对象初始化(需在配置bean时指定初始化方法)
bean对象初始化之后操作(由bean的后置处理器负责)
bean对象就绪可以使用
bean对象销毁(需在配置bean时指定销毁方法)
IOC容器关闭
创建User类
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class User { private Integer id; private String username; private String password; private Integer age; public User () { System.out.println("生命周期:1、创建对象(调用空参构造方法)" ); } public User (Integer id, String username, String password, Integer age) { this .id = id; this .username = username; this .password = password; this .age = age; } public Integer getId () { return id; } public void setId (Integer id) { System.out.println("生命周期:2、依赖注入(调用setter方法)" ); this .id = id; } public String getUsername () { return username; } public void setUsername (String username) { this .username = username; } public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } public Integer getAge () { return age; } public void setAge (Integer age) { this .age = age; } public void initMethod () { System.out.println("生命周期:4、初始化(调用自定义的initMethod方法)" ); } public void destroyMethod () { System.out.println("生命周期:7、销毁(调用自定义的destroyMethod方法)" ); } @Override public String toString () { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", age=" + age + '}' ; } }
配置bean
1 2 3 4 5 6 7 8 <bean class ="com.dyz.entity.User" init-method ="initMethod" destroy-method ="destroyMethod" > <property name ="id" value ="001" /> <property name ="username" value ="admin" /> <property name ="password" value ="123456" /> <property name ="age" value ="23" /> </bean >
测试
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void testSpring () { ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); User bean = applicationContext.getBean(User.class); System.out.println(bean); System.out.println("生命周期:6、通过IOC容器获取bean并使用" ); applicationContext.close(); }
输出: 生命周期:1、创建对象(调用空参构造方法) 生命周期:2、依赖注入(调用setter方法) 生命周期:4、初始化(调用自定义的initMethod方法) User{id=1, username=’admin’, password=’123456’, age=23} 生命周期:6、通过IOC容器获取bean并使用 生命周期:7、销毁(调用自定义的destroyMethod方法)
注意:若bean的作用域为单例,生命周期的前三步会在创建IOC容器时就执行,也就是在创建IOC容器时Spring就会将对象创建好放在容器里,若为多例,那么对象会在第一次使用时才会进行创建。
bean的后置处理器: bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MyBeanPostProcess implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { System.out.println("生命周期:3、初始化之前的操作(调用postProcessBeforeInitialization方法)" ); return bean; } @Override public Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException { System.out.println("生命周期:5、初始化之后的操作(调用postProcessAfterInitialization方法)" ); return bean; } }
在IOC容器中配置后置处理器
1 2 <bean id ="myBeanProcessor" class ="com.dyz.process.MyBeanPostProcess" />
测试输出: 生命周期:1、创建对象(调用空参构造方法) 生命周期:2、依赖注入(调用setter方法) 生命周期:3、初始化之前的操作(调用postProcessBeforeInitialization方法) 生命周期:4、初始化(调用自定义的initMethod方法) 生命周期:5、初始化之后的操作(调用postProcessAfterInitialization方法) User{id=1, username=’admin’, password=’123456’, age=23} 生命周期:6、通过IOC容器获取bean并使用 生命周期:7、销毁(调用自定义的destroyMethod方法)
2.2.11、FactoryBean ① 简介 FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。 通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
通俗来说,普通的工厂模式,我们需要先创建工厂对象,然后通过这个工厂对象才能得到这个工厂所生产的对象;而FactoryBean机制允许我们直接将工厂类交给Spring管理,我们不需要手动创建这个工厂了,而是从IOC容器中直接获取这个工厂所生产的对象。
将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public interface FactoryBean <T> { String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType" ; @Nullable T getObject () throws Exception; @Nullable Class<?> getObjectType(); default boolean isSingleton () { return true ; } }
② 举例
创建StudentFactoryBean,实现FactoryBean接口,并实现前两个方法
1 2 3 4 5 6 7 8 9 10 11 public class StudentFactoryBean implements FactoryBean <Student> { @Override public Student getObject () throws Exception { return new Student (); } @Override public Class<?> getObjectType() { return Student.class; } }
配置bean
1 <bean id ="student" class ="com.dyz.factory.StudentFactoryBean" />
使用测试
1 2 3 4 5 6 7 @Test public void testSpring () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); Student student = (Student) applicationContext.getBean("student" ); System.out.println(student); }
输出: Student(sid=null, name=null, age=null, gender=null, clazz=null, hobbies=null, teacherMap=null)
2.2.12、xml自动装配 自动装配:根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值。
① 场景模拟
创建UserDao接口
1 2 3 public interface UserDao { void updateUser () ; }
创建UserDao接口实现类UserDaoImpl
1 2 3 4 5 6 public class UserDaoImpl implements UserDao { @Override public void updateUser () { System.out.println("更新用户信息成功!" ); } }
创建UserService接口
1 2 3 public interface UserService { void updateUser () ; }
创建UserService接口实现类UserServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao (UserDao userDao) { this .userDao = userDao; } @Override public void updateUser () { userDao.updateUser(); } }
创建UserController
1 2 3 4 5 6 7 8 9 10 11 12 public class UserController { private UserService userService; public void setUserService (UserService userService) { this .userService = userService; } public void updateUser () { userService.updateUser(); } }
② 配置bean(手动注入)
这是之前通过setter方法手动注入属性的值
1 2 3 4 5 6 7 8 9 <bean id ="userController" class ="com.dyz.controller.UserController" > <property name ="userService" ref ="userService" /> </bean > <bean id ="userService" class ="com.dyz.service.impl.UserServiceImpl" > <property name ="userDao" ref ="userDao" /> </bean > <bean id ="userDao" class ="com.dyz.dao.impl.UserDaoImpl" />
③ 配置bean(自动装配) 注意,自动装配只针对于类类型的属性或接口类型的属性赋值。
使用bean标签的autowire属性设置自动装配 。autowire有no、default、byType、byType、constructor五个值。
no、default :都表示不装配,即bean中的属性不会自动比配某个bean为其赋值,此时属性使用默认值。
byType :根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值。
byName :将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值。
自动装配:byType:
若在IOC中,没有任何一个类型的bean能够为属性赋值,则该属性不装配,即值为默认值null 若在IOC中,有多个类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException
1 2 3 4 5 <bean id ="userController" class ="com.dyz.controller.UserController" autowire ="byType" /> <bean id ="userService" class ="com.dyz.service.impl.UserServiceImpl" autowire ="byType" /> <bean id ="userDao" class ="com.dyz.dao.impl.UserDaoImpl" />
自动装配:byName:
1 2 3 4 5 <bean id ="userController" class ="com.dyz.controller.UserController" autowire ="byName" /> <bean id ="userService" class ="com.dyz.service.impl.UserServiceImpl" autowire ="byName" /> <bean id ="userDao" class ="com.dyz.dao.impl.UserDaoImpl" />
④ 测试 1 2 3 4 5 6 7 @Test public void testSpring () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("applicationContext.xml" ); UserController userController = applicationContext.getBean(UserController.class); userController.updateUser(); }
输出:更新用户信息成功!
2.3、基于注解管理bean 注解和XML配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记 ,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作 。因此Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。
本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。
2.3.1、常用的bean管理注解
@Component:将类标识为普通组件
@Controller:将类标识为控制层组件
@Service:将类标识为业务层组件
@Repository:将类标识为持久层组件
这四个注解区别:
通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。
因此对于Spring使用IOC容器管理这些组件来说没有区别。所以@Controller、@Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。
注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。
2.3.2、创建组件
控制层组件UserController
1 2 3 @Controller public class UserController {}
创建接口UserService
1 2 public interface UserService {}
业务层组件UserServiceImpl
1 2 3 @Service public class UserServiceImpl implements UserService {}
创建接口UserDao
1 2 public interface UserDao {}
持久层组件UserDaoImpl
1 2 3 @Repository public class UserDaoImpl implements UserDao {}
2.3.3、扫描组件
1、最基本的扫描方式
1 <context:component-scan base-package ="com.dyz" />
2、指定要排除的组件
context:exclude-filter标签:指定排除规则。 type属性:设置排除的依据。 type=”annotation”,根据注解排除,expression中设置要排除的注解的全类名 type=”assignable”,根据类型排除,expression中设置要排除的类型的全类名
1 2 3 4 5 6 <context:component-scan base-package ="com.dyz" > <context:exclude-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
3、仅扫描指定组件
use-default-filters属性:取值false表示关闭默认扫描规则。
context:include-filter标签:指定在原有扫描规则的基础上追加的规则。此时必须设置use-default-filters=”false”,因为默认规则即扫描指定包下所有类 type属性:设置包含的依据。 type=”annotation”,根据注解包含,expression中设置要排除的注解的全类名 type=”assignable”,根据类型包含,expression中设置要排除的类型的全类名
1 2 3 4 5 6 <context:component-scan base-package ="com.dyz" use-default-filters ="false" > <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
2.3.4、bean的id 在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。
1、默认情况:类名首字母小写就是bean的id 。例如:UserController类对应的bean的id就是userController。
2、自定义bean的id:可通过标识组件的注解的value属性设置自定义的bean的id:
1 2 3 @Service("userService") public class UserServiceImpl implements UserService {}
2.3.5、注解自动装配 ① 场景模拟
参考基于xml的自动装配 在UserController中声明UserService对象 在UserServiceImpl中声明UserDao对象
② @Autowired注解 在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。
创建UserDao接口
1 2 3 public interface UserDao { void updateUser () ; }
创建UserDao接口实现类UserDaoImpl
1 2 3 4 5 6 public class UserDaoImpl implements UserDao { @Override public void updateUser () { System.out.println("更新用户信息成功!" ); } }
创建UserService接口
1 2 3 public interface UserService { void updateUser () ; }
创建UserService接口实现类UserServiceImpl
1 2 3 4 5 6 7 8 9 10 public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public void updateUser () { userDao.updateUser(); } }
创建UserController
1 2 3 4 5 6 7 8 9 public class UserController { @Autowired private UserService userService; public void updateUser () { userService.updateUser(); } }
③ @Autowired补充 @Autowired注解还可以标记在构造器和set方法上,效果一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Controller public class UserController { private UserService userService; @Autowired public UserController (UserService userService) { this .userService = userService; } public void updateUser () { userService.updateUser(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Controller public class UserController { private UserService userService; @Autowired public void setUserService (UserService userService) { this .userService = userService; } public void updateUser () { userService.updateUser(); } }
④ @Autowired执行流程
首先根据所需要的组件类型到IOC容器中查找
能够找到唯一的bean:直接执行装配
如果完全找不到匹配这个类型的bean:装配失败
和所需类型匹配的bean不止一个
没有@Qualifier注解:根据@Autowired标记位置成员变量的变量名作为bean的id进行匹配
使用@Qualifier注解:根据@Qualifier注解中指定的名称作为bean的id进行匹配
1 2 3 4 5 6 7 8 9 10 11 @Controller public class UserController { @Autowired @Qualifier("userServiceImpl") private UserService userService; public void updateUser () { userService.updateUser(); } }
@Autowired中有属性required,默认值为true,因此在自动装配无法找到相应的bean时,会装配失败,抛出NoSuchBeanDefinitionException。 可以将属性required的值设置为false,则表示能装就装,装不上就不装,装不上会使用属性的默认值。 但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。