Spring容器
大家好呀!我是小笙,接下来我们开始学习spring框架!
Spring Framework
概述
Spring 春天 迎来了程序员的春天!
- 历史由来:Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版
- 开发者:该框架最初由 Rod Johnson 以及 Juergen Hoeller 等人开发
- 概念:Spring 又称 Spring Framework 是java平台上免费开源的全栈应用程序框架和控制反转的容器实现,换句话来说就是Spring Framework 建立在java平台上,并为其使用者提供了应用程序框架(类似于房屋的整体架构)以及一系列底层容器(Spring 是管理框架的框架)
-
目的:解决企业应用开发的复杂性
-
maven>>Spring Core » 5.3.8:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.8</version> </dependency>
-
特征: 轻量 面向切面(AOP) 控制反转(IOC) 容器 框架 MVC
-
组成:Spring Framework Runtime
Spring七大模块
-
优点:
Spring容器
快速入门
案例:通过 Spring 的方式[配置文件,获取 JavaBean: Monster 的对象,并给该的对象属性赋值,输出该对象信息
引入jar包
Monster类对象
package com.Al_tair.spring.bean;
public class Monster {
private int monsterId;
private String name;
private String skill;
// 略get,set方法以及构造器
}
配置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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置javabean -->
<!--
一个bean标签对应一个javabean对象
通过class全类名用指定类的全路径(用于反射创建javabean对象)
通过id可以获取到对象(唯一)
-->
<bean class="com.Al_tair.spring.bean.Monster" id="monster01">
<!-- 给该对象属性赋值 -->
<property name="monsterId" value="1" />
<property name="name" value="狐狸精" />
<property name="skill" value="美人计" />
</bean>
</beans>
public class BeanTeat {
@Test
public void getMonster(){
// 创建容器 ApplicationContext
// 关联对象的bean.xml文件
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
Monster monster = ioc.getBean("monster01", Monster.class);
System.out.println(monster);
// Monster{monsterId=1, name='狐狸精', skill='美人计'}
}
}
底层分析
注意debug时候将null进行隐藏方便观察
分析bean.xml中存储的类和对象
debug
反问1:ClassPathXmlApplicationContext是如何创建bean容器?
尝试手写超简易版的 MyApplicationContext
思路分析
- 通过dom4j解析技术读取bean.xml文件
- 解析得到对象的id,类路径,对象的属性
- 使用反射来创建该对象
- 放入到beanFactory中(管理bean对象的容器)
- 提供一个getBean()方法,通过id来获取对应的bean对象
/**
* 手写超简易版的Spring容器
*/
public class MyApplicationContext {
/**
* 存放id-bean对象的键值对
*/
private static ConcurrentHashMap<String,Object> SingletonObject = new ConcurrentHashMap<>();
/**
* 构造器
* @param iocBeanXmlPath 配置的bean.xml文件的位置
*/
public MyApplicationContext(String iocBeanXmlPath) {
// 资源路径
String path = MyApplicationContext.class.getResource("/").getPath();
// 1.通过dom4j解析技术读取bean.xml文件
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new File(path + iocBeanXmlPath));
Element rootElement = document.getRootElement();
// 2.解析得到对象的id,类路径,对象的属性
// 获取第一个bean对象
Element bean = (Element)rootElement.elements("bean").get(0);
// 获取群路径和id
String beanFullClassPath = bean.attribute("class").getValue();
String id = bean.attribute("id").getValue();
// 获取属性值
List<Element> property = bean.elements("property");
Integer monsterId = null;
String name = null;
String skill = null;
for (Element element : property) {
// 我这里简化判断,因为只取第一个bean对象,最好也用容器来存储管理
if("monsterId".equals(element.attributeValue("name"))){
monsterId = Integer.parseInt(element.attributeValue("value"));
}else if("name".equals(element.attributeValue("name"))){
name = element.attributeValue("value");
}else if("skill".equals(element.attributeValue("name"))){
skill = element.attributeValue("value");
}
}
// 3.使用反射创建对象
// 在容器配置文件(比如 beans.xml)中给属性赋值, 底层是通过 setter 方法完成的
Class<?> monsterClass = Class.forName(beanFullClassPath);
Monster monster = (Monster)monsterClass.newInstance();
monster.setMonsterId(monsterId);
monster.setName(name);
monster.setSkill(skill);
// 4.放入到beanFactory中(管理bean对象的容器)
SingletonObject.put(id,monster);
} catch (Exception e) {
e.printStackTrace();
}
}
// 5.提供一个getBean()方法,通过id来获取对应的bean对象
public static Object getBean(String id){
if(SingletonObject != null &&SingletonObject.containsKey(id)){
return SingletonObject.get(id);
}
return null;
}
}
测试类
public class MyApplicationContextTeat {
@Test
public void test(){
MyApplicationContext myApplicationContext = new MyApplicationContext("bean.xml");
Monster monster01 = (Monster)MyApplicationContext.getBean("monster01");
System.out.println(monster01); // Monster{monsterId=1, name='狐狸精', skill='美人计'}
}
}
反问2:如果创建两个bean对象,都不写id会出现什么情况?
系统会默认分配id,规则:全类名#n (n是第一个bean对象,从0开始)
<bean class="com.Al_tair.spring.bean.Monster">
<!-- 给该对象属性赋值 -->
<property name="monsterId" value="1" />
<property name="name" value="狐狸精" />
<property name="skill" value="美人计" />
</bean>
<bean class="com.Al_tair.spring.bean.Monster">
<!-- 给该对象属性赋值 -->
<property name="monsterId" value="2" />
<property name="name" value="猪八戒" />
<property name="skill" value="九尺钉耙" />
</bean>
测试类
public class BeanTest {
@Test
public void getMonster(){
// 创建容器 ApplicationContext
// 关联对象的bean.xml文件
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
System.out.println(Arrays.toString(beanDefinitionNames));
// 显示 [com.Al_tair.spring.bean.Monster#0, com.Al_tair.spring.bean.Monster#1]
}
}
Spring 管理 Bean-IOC
概述
Bean管理包括:创建bean对象、给bean注入属性
Bean配置方式:基于xml文件配置方式、基于注解方式
基于xml文件配置方式
获取bean对象
1.通过id获取bean对象
public class MyApplicationContextTeat {
@Test
public void test(){
MyApplicationContext myApplicationContext = new MyApplicationContext("bean.xml");
Monster monster01 = (Monster)MyApplicationContext.getBean("monster01");
System.out.println(monster01); // Monster{monsterId=1, name='狐狸精', skill='美人计'}
}
}
2.通过对象类型来获取该bean对象
@Test
public void getMonsterByType(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
Car car = ioc.getBean(Car.class);
System.out.println(car); // Car{id=1, name='奥迪', price='350000'}
}
注意事项:按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常 NoUniqueBeanDefinitionException
应用场景:在一个线程中只需要一个对象实例(单例)的情况
配置bean对象
1.通过构造器配置 bean
解读:通过有参构造器的形式创建对象
<!-- 构造器配置bean对象 -->
<bean class="com.Al_tair.spring.bean.Monster" id="monster03">
<!-- index是按顺序指定的 -->
<constructor-arg value="100" index="0" />
<constructor-arg value="孙悟空" index="1" />
<constructor-arg value="定海神针" index="2" />
</bean>
<bean class="com.Al_tair.spring.bean.Monster" id="monster03">
<!-- 根据name来配置构造器 -->
<constructor-arg value="100" name="monsterId" />
<constructor-arg value="孙悟空" name="name" />
<constructor-arg value="定海神针" name="skill" />
</bean>
<bean class="com.Al_tair.spring.bean.Monster" id="monster03">
<!-- 根据tpye来匹配构造器 -->
<!-- 注意类定义的时候尽量使用Integer(不要使用基本数据类型),不然无法匹配类型 -->
<constructor-arg value="100" type="java.lang.Integer" />
<constructor-arg value="孙悟空" type="java.lang.String" />
<constructor-arg value="定海神针" type="java.lang.String" />
</bean>
2.通过 p 名称空间配置 bean
xmlns:p="http://www.springframework.org/schema/p"
<!--通过 p 名称空间配置 bean-->
<bean class="com.Al_tair.spring.bean.Monster" id="monster05"
p:monsterId="4"
p:name="铁扇公主"
p:skill="芭蕉扇"
/>
@Test
public void getMonsterByPNameSpace(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
Monster monster05 = ioc.getBean("monster05", Monster.class);
System.out.println(monster05); // Monster{monsterId=4, name='铁扇公主', skill='芭蕉扇'}
}
3.通过 reference 来实现 bean 对象的相互引用
解读:属性中引用了其他 bean 对象
依赖注入
<!--通过 reference 来实现 bean 对象的相互引用-->
<bean class="com.Al_tair.spring.dao.MemberDAOImpl" id="memberDAO"/>
<bean class="com.Al_tair.spring.service.MemberServiceImpl" id="memberService">
<!--ref 表示memberDAO这个属性将引用/指向 id = memberDAOImpl 对象-->
<property name="memberDAO" ref="memberDAO"></property>
</bean>
测试类
@Test
public void getMonsterByRef() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
MemberServiceImpl memberService = (MemberServiceImpl) ioc.getBean("memberService");
memberService.add();
}
4.引用/注入内部 bean 对象
解读:用法类似匿名内部类的方式
<bean class="com.Al_tair.spring.service.MemberServiceImpl" id="memberService">
<!--直接在内部配置内部bean对象-->
<property name="memberDAO">
<bean class="com.Al_tair.spring.dao.MemberDAOImpl"/>
</property>
</bean>
5.引用/注入集合/数组类型
Master类用来管理monster类
public class Master {
private String name;
private List<Monster> monsterList;
private Map<String,Monster> monsterMap;
private Set<Monster> monsterSet;
private String[] monstersName;
// Properties是Hashtable的子类,是key-value的形式
private Properties pros;
// ......等等
}
<!--引用/注入集合/数组类型-->
<bean class="com.Al_tair.spring.bean.Master" id="master">
<!--name-->
<property name="name" value="太上老君"/>
<!--List-->
<property name="monsterList">
<list>
<ref bean="monster01"/>
<ref bean="monster02"/>
<ref bean="monster03"/>
</list>
</property>
<!--Map-->
<property name="monsterMap">
<map>
<entry key="monster4" value-ref="monster04" />
<entry key="monster5" value-ref="monster05"/>
</map>
</property>
<!--Set-->
<property name="monsterSet">
<set>
<ref bean="monster01"/>
<ref bean="monster02"/>
</set>
</property>
<!--数值-->
<property name="monstersName">
<array>
<value>小妖怪</value>
<value>大妖怪</value>
</array>
</property>
<!--Properties-->
<!-- key-value均是字符串 -->
<property name="pros">
<props>
<prop key="key01">value01</prop>
<prop key="key02">value02</prop>
<prop key="key03">value03</prop>
</props>
</property>
</bean>
6.通过 util 名称空间创建 list集合
目的是为了减少代码的复用性,提取公共的参数
public class BookStore {
private List<String> bookList;
// ....等等
}
<!--通过 util 名称空间创建 list集合-->
<util:list id="myBookStore">
<value>三国演义</value>
<value>红楼梦</value>
<value>水浒传</value>
<value>西游记</value>
</util:list>
<bean class="com.Al_tair.spring.bean.BookStore" id="bookStore">
<property name="bookList" ref="myBookStore"/>
</bean>
测试类
@Test
public void bookBeanTest(){
ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
BookStore bookStore = ios.getBean("bookStore", BookStore.class);
System.out.println(bookStore); // BookStore{bookList=[三国演义, 红楼梦, 水浒传, 西游记]}
}
7.级联属性赋值
spring 的 ioc 容器, 可以直接给对象属性的属性赋值, 即级联属性赋值
<!--级联属性赋值-->
<bean class="com.Al_tair.spring.bean.Dept" id="dept"/>
<bean class="com.Al_tair.spring.bean.Emp" id="emp">
<property name="name" value="Al_tair"/>
<property name="dept" ref="dept"/>
<!-- 给对象属性的属性赋值 -->
<property name="dept.name" value="Java开发"/>
</bean>
测试类
@Test
public void test(){
ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
Emp emp = ios.getBean("emp", Emp.class);
System.out.println(emp); // Emp{name='Al_tair', dept=Dept{name='Java开发'}}
}
8.通过静态工厂获取对象
工厂类
public class MyStaticFactory {
private static Map<String, Monster> monsterMap;
static {
monsterMap = new HashMap<String, Monster>();
monsterMap.put("monster_01", new Monster(1, "一灯大师", "一阳指"));
monsterMap.put("monster_02", new Monster(2, "如来佛主", "如来神掌"));
}
public static Monster getMonster(String key) {
return monsterMap.get(key);
}
}
<!--通过静态工厂获取对象-->
<bean class="com.Al_tair.spring.factory.MyStaticFactory" id="myStaticFactory" factory-method="getMonster">
<!--方法中传入的实参-->
<constructor-arg value="monster_02"/>
</bean>
测试类
@Test
public void myStaticFactoryTest(){
ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
Monster myStaticFactory = ios.getBean("myStaticFactory", Monster.class);
System.out.println(myStaticFactory); // Monster{monsterId=2, name='如来佛主', skill='如来神掌'}
}
9.通过实例工厂获取对象
public class MyInstanceFactory {
private Map<String, Monster> monsterMap;
{
monsterMap = new HashMap<String, Monster>();
monsterMap.put("monster_03", new Monster(3, "牛魔王", "芭蕉扇"));
monsterMap.put("monster_04", new Monster(4, "孙悟空", "金箍棒"));
}
public Monster getMonster(String key) {
return monsterMap.get(key);
}
}
<!--通过实例工厂获取对象-->
<!-- 初始化该实例 -->
<bean class="com.Al_tair.spring.factory.MyInstanceFactory" id="instanceFactory"/>
<!--factory-bean 指定使用哪个实例工厂对象(事先要创建好该实例工厂对象)-->
<!--factory-method 调用该实例工厂对象的哪个方法-->
<!--constructor-arg 该方法调用所需要的参数-->
<bean id="myInstanceFactory" factory-bean="instanceFactory" factory-method="getMonster">
<constructor-arg value="monster_04"/>
</bean>
测试类
@Test
public void myInstanceFactoryTest(){
ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
Monster myInstanceFactory = ios.getBean("myInstanceFactory", Monster.class);
System.out.println(myInstanceFactory); // Monster{monsterId=4, name='孙悟空', skill='金箍棒'}
}
10.通过 FactoryBean 获取对象
public class FactoryBeanImpl implements FactoryBean<Monster> {
// 获取指定对象的key值
private String keyVal;
private Map<String, Monster> monster_map;
{
monster_map = new HashMap<String, Monster>();
monster_map.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
}
public void setKeyVal(String keyVal) {
this.keyVal = keyVal;
}
@Override
public Monster getObject() throws Exception {
return this.monster_map.get(keyVal);
}
@Override
public Class<?> getObjectType() {
return Monster.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
<!--通过 FactoryBean 获取对象-->
<bean class="com.Al_tair.spring.factory.FactoryBeanImpl" id="factoryBean">
<property name="keyVal" value="monster_02"/>
</bean>
测试类
@Test
public void FactoryBeanTest(){
ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
Monster factoryBean = ios.getBean("factoryBean", Monster.class);
System.out.println(factoryBean); // Monster{monsterId=200, name='九头金雕', skill='如来神掌'}
}
11.bean 配置信息重用(继承)
<!--通过 FactoryBean 获取对象-->
<bean class="com.Al_tair.spring.factory.FactoryBeanImpl" id="factoryBean">
<property name="keyVal" value="monster_02"/>
</bean>
<!--继承父类,同时可以修改属性-->
<bean id="factoryBeanSon" parent="factoryBean">
<property name="keyVal" value="monster_01"/>
</bean>
测试类
@Test
public void FactoryBeanSonTest(){
ApplicationContext ios = new ClassPathXmlApplicationContext("bean.xml");
Monster factoryBean = ios.getBean("factoryBeanSon", Monster.class);
System.out.println(factoryBean); // Monster{monsterId=100, name='黄袍怪', skill='一阳指'}
}
Bean对象创建的顺序
默认按从上到下的顺序进行创建(先创建Bean对象,再建立对象之间的引用)
<bean id="student01" class="com.hspedu.bean.Student" />
<bean id="department01" class="com.hspedu.bean.Department" />
可以通过设置属性 depends-on 来控制创建的顺序
<bean id="student01" class="com.hspedu.bean.Student" depends-on="department01"/>
<bean id="department01" class="com.hspedu.bean.Department" />
Bean 对象的单例和多例
ioc容器, 默认是按照单例创建的,即配置一个 bean对象后,ioc 容器只会创建一个 bean实例
ioc 容器配置的某个 bean 对象,是以多个实例形式创建的则可以通过配置 scope=“prototype” 来指定
<!--Bean 对象的单例-->
<!--scope="singleton"-->
<bean class="com.Al_tair.spring.bean.Car" id="car01">
<!-- 给该对象属性赋值 -->
<property name="id" value="1" />
<property name="name" value="奥迪" />
<property name="price" value="350000" />
</bean>
<!--Bean 对象的多例-->
<!--scope="prototype"-->
<bean class="com.Al_tair.spring.bean.Car" id="car02" scope="prototype">
<!-- 给该对象属性赋值 -->
<property name="id" value="2" />
<property name="name" value="宝马" />
<property name="price" value="260000" />
</bean>
测试类
// 单例模式
@Test
public void test(){
ApplicationContext carIos = new ClassPathXmlApplicationContext("bean.xml");
Car car01 = carIos.getBean("car01", Car.class);
Car car02 = carIos.getBean("car01", Car.class);
System.out.println(car01 == car02); // true
}
// 多例
@Test
public void carTest(){
ApplicationContext carIos = new ClassPathXmlApplicationContext("bean.xml");
// 每次返回一个新的对象
Car car01 = carIos.getBean("car02", Car.class);
Car car02 = carIos.getBean("car02", Car.class);
System.out.println(car01 == car02); // false
}
注意细节
- 默认是单例 singleton,在启动容器时, 默认就会创建,并放入到 singletonObjects 集合;如果希望在 getBean 时才创建 , 可以指定懒加载 lazy-init=“true” (默认是 false)
- 当 < bean scope=“prototype” > 设置为多实例机制后, 该 bean 是在 getBean()时才创建
Bean 的生命周期
bean 对象创建是由 JVM 来完成的,执行顺序如下
- 执行构造器,创建bean对象
- 执行 setXxxx 相关的方法
- 调用 bean 对象的初始化方法(需要配置)
- 通过 getBean() 获取Bean对象并使用
- 当容器关闭时候,调用 Bean 对象的销毁方法(需要配置)
House类
public class House {
private String name;
public House() {
System.out.println("House()构造器");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("House setName()...");
this.name = name;
}
// 不是固定init名字,需要配置为初始化方法
public void init() {
System.out.println("House init()..");
}
// 不是固定destory名字,需要配置为销毁对象
public void destory() {
System.out.println("House destory()..");
}
@Override
public String toString() {
return "House{" +
"name='" + name + '\'' +
'}';
}
}
配置bean对象
<!--Bean 的生命周期-->
<bean class="com.Al_tair.spring.bean.House" id="house" init-method="init" destroy-method="destory">
<property name="name" value="豪宅"/>
</bean>
测试类
@Test
public void beanHouse(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
House house = ioc.getBean("house", House.class);
System.out.println(house);
// 销毁Bean容器
((ConfigurableApplicationContext)ioc).close();
// 控制台输出结果
// 1.House()构造器
// 2.House setName()...
// 3.House init()..
// 4.House{name='豪宅'}
// 5.House destory()..
}
配置 Bean 的后置处理器
该处理器/对象会在 bean 初始化方法调用前和调用后被调用,这个时候 Bean 对象已经创建好了
后置处理器操作需要实现 BeanPostProcessor 接口
/**
* 后置处理器
* 初始化前后传入的Bean对象是同一个
* 可以对 IOC 容器中所有的对象进行统一处理,比如日志处理/权限的校验/安全的验证/事务管理
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 在Bean对象初始化之前调用
* 对传入的Bean对象可以进行对应业务的操作 如日志记录
* @param bean bean 对象
* @param beanName bean 对象对应的 id
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化前面");
return null;
}
/**
* 在Bean对象初始化之后调用
* @param bean bean 对象
* @param beanName bean 对象对应的 id
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化后面");
return null;
}
}
配置 Bean 的后置处理器
<bean class="com.Al_tair.spring.bean.House" id="house" init-method="init" destroy-method="destory">
<property name="name" value="豪宅"/>
</bean>
<!--配置 Bean 的后置处理器-->
<bean class="com.Al_tair.spring.bean.MyBeanPostProcessor" id="myBeanPostProcessor"/>
测试类
@Test
public void MyBeanPostProcessorByHouse(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean2.xml");
House house = ioc.getBean("house", House.class);
System.out.println(house);
((ConfigurableApplicationContext)ioc).close();
// 执行顺序
// House()构造器
// House setName()...
// 初始化前面
// House init()..
// 初始化后面
// House{name='豪宅'}
// House destory()..
}
通过属性文件给 bean 注入值
配置文件
monsterId=10
name=tom
skill=ball
# 如果存在中文 需要进行unicode编码
# 在线转换工具: http://www.esjson.com/unicodeEncode.html
读取配置文件并注入到 Bean 对象
<context:property-placeholder location="classpath:my.properties"/>
<bean class="com.Al_tair.spring.bean.Monster" id="monster">
<property name="monsterId" value="${monsterId}"/>
<property name="name" value="${name}"/>
<property name="skill" value="${skill}"/>
</bean>
测试类
// 通过属性文件给 bean 注入值
@Test
public void getMonsterFromProperties(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean2.xml");
Monster monster = ioc.getBean("monster", Monster.class);
System.out.println(monster); // Monster{monsterId=10, name='tom', skill='ball'}
}
Bean 的自动装配
autowire=“byType”
条件:在这个Bean容器中不能有两个相同的类对象
<!--Bean 的自动装配-->
<!--OrderDao-->
<bean class="com.Al_tair.spring.dao.OrderDao" id="orderDao"/>
<!--OrderService-->
<bean class="com.Al_tair.spring.service.OrderService" id="orderService" autowire="byType"/>
<!--OrderServlet-->
<bean class="com.Al_tair.spring.controller.OrderServlet" id="OrderServlet" autowire="byType"/>
测试类
@Test
public void ServletTest(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean2.xml");
OrderServlet orderServlet = ioc.getBean("OrderServlet", OrderServlet.class);
OrderService orderService = orderServlet.getOrderService();
OrderService orderService2 = ioc.getBean("orderService", OrderService.class);
System.out.println(orderService == orderService2); // true
}
autowire=“byName”
<!--OrderDao-->
<bean class="com.Al_tair.spring.dao.OrderDao" id="orderDao"/>
<!--OrderService-->
<bean class="com.Al_tair.spring.service.OrderService" id="orderService" autowire="byName"/>
<!--OrderServlet-->
<!--会根据 OrderServlet 中的 setOrderService 方法来查找对应的 id 的 bean 对象进行自动装配-->
<bean class="com.Al_tair.spring.controller.OrderServlet" id="OrderServlet" autowire="byName"/>
基于注解配置 bean
概述
概念:基于注解的方式配置 bean, 主要是项目开发中的组件,比如 Controller、Service、 Dao
- @Component 表示当前注解标识的是一个组件
- @Controller 表示当前注解标识的是一个控制器,通常用于 Servlet
- @Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于 Service 类
- @Repository 表示当前注解标识的是一个持久化层的类,通常用于 Dao 类
首先导入 spring-aop-5.3.8.jar
快速入门
@Repository
public class UserDao {}
@Service
public class UserService {}
@Controller
public class UserServlet {}
@Component
public class MyComponent {}
配置扫描包的配置类
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.Al_tair.springComponent.other" />
<!--
可以使用通配符*来指定 ,比如com.Al_tair.springComponent.*表示
component-scan 对指定的包进行扫描,并创建对象放入到容器
base-package 指定要扫面的包,但是该包中的类必须用注解 @Component/@Controller/@Service/@Repository 标注
resource-pattern 表示扫描以User开头的类名的类
-->
<context:component-scan base-package="com.Al_tair.springComponent.*" resource-pattern="User*.class"/>
测试类
@Test
public void setBeanByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean3.xml");
UserDao userDao = ioc.getBean(UserDao.class);
UserService userService = ioc.getBean(UserService.class);
UserServlet userServlet = ioc.getBean(UserServlet.class);
MyComponent myComponent = ioc.getBean(MyComponent.class);
System.out.println(userDao);
System.out.println(userService);
System.out.println(userServlet);
System.out.println(myComponent);
//com.Al_tair.springComponent.dao.UserDao@1e683a3e
//com.Al_tair.springComponent.service.UserService@2053d869
//com.Al_tair.springComponent.controller.UserServlet@7a419da4
//com.Al_tair.springComponent.other.MyComponent@14555e0a
}
提问:指定扫描某个包,会不会去扫描该包下的子包中的类
会的,会扫描所有该包中的类,也会扫描该子包下的类
注意细节
Spring 的 IOC 容器不能检测一个使用了@Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的@Service @Repository 也是一样的道理 [也就是说 spring 的 IOC 容器只要检查到注解就会生成对象, 但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的,但是在springMVC中有细微的区别]
排除某些注解类
<!--补充:排除哪些类,以annotaion注解为例-->
<!--type:指定排除的类型 annotation表示以注解的方式进进行排除-->
<!--expression="org.springframework.stereotype.Service" Service的全路径,用来指定排除Service注解的类-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
指定自动扫描哪些注解类
<!--
1. use-default-filters="false": 表示不使用默认的过滤机制
2. context:include-filter: 表示只是扫描指定的注解的类
3. expression="org.springframework.stereotype.Controller": 注解的全类名
-->
<context:component-scan base-package="com.Al_tair.springComponent.*" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
默认情况:标记注解后,类名首字母小写作为 id 的值; 也可以使用注解的 value 属性指定 id 值(value 可以省略)
@Repository
public class UserDao {}
// value 可以省略,可以写成 @Service("serviceValue")
@Service(value = "serviceValue")
public class UserService {}
测试类
@Test
public void setBeanById(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean3.xml");
// 类名首字母小写作为id的值
UserDao userDao = ioc.getBean("userDao",UserDao.class);
// 但是如果指定了value值的话,id就不能将类名首字母小写作为id的值
// UserService userService = ioc.getBean("userService",UserService.class); error
// 使用注解的 value 属性指定 id
UserService userService = ioc.getBean("serviceValue",UserService.class);
System.out.println(userDao);
System.out.println(userService);
//com.Al_tair.springComponent.dao.UserDao@33990a0c
//com.Al_tair.springComponent.service.UserService@4de4b452
}
手动开发简单的 Spring 基于注解配置的程序
思路分析
对比原生开发和手动开发的区别
自定义注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ComponentScan {
String value() default "";
}
作用类似于bean.xml:指定需要扫描的包
@ComponentScan(value = "com.Al_tair.springComponent")
public class SpringConfig {
}
自定义ioc容器
/**
* 作用类似于Spring的原生ioc容器
*/
public class MyApplicationContext {
private Class springConfig;
private final ConcurrentHashMap<String,Object> ioc = new ConcurrentHashMap<>();
public MyApplicationContext(Class springConfig) {
this.springConfig = springConfig;
// 1.获取扫描包的全路径
ComponentScan componentScan = (ComponentScan)this.springConfig.getDeclaredAnnotation(ComponentScan.class);
String ScanPackage = componentScan.value().replace('.','/');
// 2.得到类加载器
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(ScanPackage);
File file = new File(resource.getFile());
List<String> fullPath = new ArrayList<>();
// 3.扫描包并保存class文件的全类名
if(file.isDirectory()){
File[] files = file.listFiles();
for (File fileItem : files) {
File[] f = fileItem.listFiles();
for (File fileClass : f) {
String absoluterPath = fileClass.getAbsolutePath();
if(absoluterPath.endsWith(".class")){
String substring = absoluterPath.substring(absoluterPath.lastIndexOf("\\com") + 1, absoluterPath.indexOf(".class"));
String str = substring.replace("\\",".");
fullPath.add(str);
}
}
}
}
// 4.遍历所有class文件,进行反射实例化
for (String s : fullPath) {
try {
Class<?> aClass = classLoader.loadClass(s);
if(aClass.isAnnotationPresent(Component.class) || aClass.isAnnotationPresent(Service.class) ||
aClass.isAnnotationPresent(Repository.class) || aClass.isAnnotationPresent(Controller.class)){
Class<?> clazz = Class.forName(s);
Object o = clazz.newInstance();
// 5.放入ioc容器,id为类名首字母小写完成
ioc.put(s.substring(s.lastIndexOf(".") + 1,s.lastIndexOf(".") + 2).toLowerCase()
+ s.substring(s.lastIndexOf(".") + 2),o);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 6.通过getBean方法给外界来获取该bean对象
public Object getBean(String id){
if(ioc.containsKey(id)){
return ioc.get(id);
}else{
return null;
}
}
}
测试类
@Test
public void configTest(){
MyApplicationContext ioc = new MyApplicationContext(SpringConfig.class);
Object userDao = ioc.getBean("userDao");
System.out.println(userDao); // com.Al_tair.springComponent.dao.UserDao@7dc36524
}
自动装配注解类
@AutoWried
规则:
在Ioc容器中查找待装配的组件的类型,如果有唯一的bean的类型,则使用该bean进行装配
@Autowired
private UserService userservice;
注意:
-
在注入的过程中,扫描到公共方法中要注入的bean,并未找到,强行注入就会注入失败。我们又不能单独的去除改方法,所以我们采取的思想就是有bean就注入,没有就不注入。解决办法就是@Autowired(required=false)
-
@Autowired
注释的注入点的类型相同。否则,注入可能会因运行时“未找到类型匹配”错误而失败,如果遇到多个同类型的将无法实现注入,我们就需要用到**@Qualifier**来进行指定名字的注入,例子如下@Autowired @Qualifier(value = "userservice2") private UserService userservice;
@Resource
需要导入注解包
- @Resource(name = “serviceValue”) 表示装配 id = serviceValue 的 bean对象
- @Resource(type= UserService.class) 表示按照 UserService 类型进行装配,这要求容器中的该 bean对象必须是单例对象
- @Resource 如果没有指定 name 或者 type,默认先使用 id = bean对象首字母小写来进行装配,如果不成功则使用类型进行装配,如果都不成功则报异常
public @interface Resource {
String name() default "";
Class<?> type() default Object.class;
// 部分源码...
}
注意:建议选择@Resource进行装配
自动注解 Vs Xml配置
- xml更加强大,适用于任何的范围,维护简单!
- 自动注解适用范围有限,且维护麻烦,分布在各个类
个人觉得最优操作(限于Spring):
-
xml用来创建bean
-
自动注解用来进行属性的注入
但是要记住需要开启注解的支持
泛型依赖注入
- 为了更好的管理有继承和相互依赖的 bean 的自动装配,spring 还提供基于泛型依赖的 注入机制
- 在继承关系复杂情况下,泛型依赖注入就会有很大的优越性
实体类
public class Phone {}
public class Book {}
Dao层
@Repository
public abstract class BaseDao<T> {
public abstract void save();
}
@Repository
public class PhoneDao extends BaseDao<Phone>{
@Override
public void save() {
System.out.println("PhoneDao");
}
}
@Repository
public class BookDao extends BaseDao<Book>{
@Override
public void save() {
System.out.println("BookDao");
}
}
Service层
@Service
public abstract class BaseService<T> {
@Autowired
private BaseDao<T> baseDao;
public void save(){
baseDao.save();
}
}
@Service
public class BookService extends BaseService<Book>{}
@Service
public class PhoneService extends BaseService<Phone>{}
测试
@Test
public void setProByAutowried(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("bean4.xml");
PhoneService bean = ioc.getBean(PhoneService.class);
bean.save(); // PhoneDao
}