Spring DI
依赖注入
- 三种注入方式介绍 DI (Dependency Injection) 依赖注入
依赖注入,即组件之间的依赖关系由容器在系统运行期来决定,也就是由容器动态的将某种依赖关系的目标对象实例注入到应用程序中各个关联的组件之中简单来讲,所谓的依赖注入其实就是, 在创建对象的同时或之后,如何给对象的属性赋值如果对象由我们自己创建,这一切都变得很简单User user = new User(); User user = new User("大哥" ,20);如果对象由 Spring 创建,那么 Spring 是怎么给属性赋值的? Spring 提供两种方式为属性赋值最常见的:1.Set 方式注入2. 构造方法注入
- set注入
- 普通属性注入
- 创建User类声明name和age属性,并添加get set方法 以及 toString
public class User { private String name; private Integer age; @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
- 在applicatiContext.xml中声明User类的bean实例
<bean id="user" class="com.pojo.User"></bean>
- 创建测试类测试 -- 此时属性显示为null
@Test public void testDI(){ //1.加载配置文件 ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.获取User类的实例 User user = (User) ac.getBean("user"); //3.输出 System.out.println(user); }
- 在applicationContext.xml中为User实例注入属性
<bean id="user" class="com.pojo.User"> <!-- set方式注入 --> <property name="name" value="大哥"></property> <property name="age" value="20"></property> </bean>
其中name属性的值,必须和实体类中所注入属性对应的set方法的名字去掉set后首字母变为小写的名字相同例如User类中age属性赋值,由于name属性对应的set方法名为 setAge当去掉set---Age---首字母小写---age 因此name属性值填age - 运行测试类测试
- 创建User类声明name和age属性,并添加get set方法 以及 toString
- 对象属性注入
-
创建 UserInfo 类
public class UserInfo { }
-
在 User 类中新增属性 userInfo
public class User { private String name; private Integer age; private UserInfo userInfo; @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", userInfo=" + userInfo + '}'; } public UserInfo getUserInfo() { return userInfo; } public void setUserInfo(UserInfo userInfo) { this.userInfo = userInfo; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
-
在配置文件中,声明 UserInfo 类的实例
<!--声明UserInfo实例 --> <bean id="userInfo" class="com.pojo.UserInfo"></bean>
- 在配置文件中,将UserInfo对象作为值,赋值给User对象中的userInfo属性
<bean id="user" class="com.pojo.User"> <!-- set方式注入 --> <property name="name" value="大哥"></property> <property name="age" value="20"></property> <!-- 此处ref引用的值为 id为userInfo的bean对象 此处是将UserInfo对象作为值赋值给另一个对象的属性, 因此ref属性的值为 UserInfo对象bean标签的id值 普通属性通过value注入 对象输入通过ref注入 --> <property name="userInfo" ref="userInfo"></property> </bean>
- 运行测试
-
- 普通属性注入
- 构造方法注入
- 为User类声明构造方法
public User(String name, Integer age, UserInfo userInfo) { this.name = name; this.age = age; this.userInfo = userInfo; }
- 修改配置文件,将set方式修改为构造方法注入
<bean id="user" class="com.pojo.User"> <constructor-arg name="age" value="20"></constructor-arg> <constructor-arg name="name" value="大哥"></constructor-arg> <constructor-arg name="userInfo" ref="userInfo"></constructor-arg> </bean> 其中,constructor-arg 标签的name属性的值必须和构造方法中参数的名字相同,同样普通属性直接通过 value注入即可,对象属性通过ref属性注入
- 运行测试
- 为User类声明构造方法
- p标签注入
p 名命空间注入的特点是使用属性而不是子元素的形式配置 Bean 的属性,从而简化了配置代码在使用 p 标签注入前,应先在 spring 配置文件中引入 p名命空间xmlns:p="http://www.springframework.org/schema/p"
- 创建实体类
public class Emp { private String name; private Integer age; @Override public String toString() { return "Emp{" + "name='" + name + '\'' + ", age=" + age + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
-
在配置文件中利用 p 标签赋值
<bean id="emp" class="com.pojo.Emp" p:name="小薇" p:age="20"> </bean>
- 创建实体类
- 补充:注入其它类型的值
- list
Spring 框架在处理时,默认采用 ArrayList 封装 List 类型的属性值
<bean id="userInfo" class="com.pojo.UserInfo"> <property name="names"> <list> <value>大哥</value> <value>小妹</value> </list> </property> </bean>
private List<String> names; public List<String> getNames() { return names; } public void setNames(List<String> names) { this.names = names; }
-
setSpring 框架在处理时,默认采用 LinkedHashSet 封装 Set 类型的属性值
<bean id="userInfo" class="com.pojo.UserInfo"> <property name="cities"> <set> <value>beijing</value> <value>beijing</value> </set> </property> </bean>
private Set<String> cities; public void setCities(Set<String> cities) { this.cities = cities; }
- 数组
其实在 Spring 框架中,注入 list 集合类型的值,使用 <list> 节点或者<array> 节点都是可以的,即数据是 List 类型,使用 <array> 来配置或者数组类型使用<list> 来配置都是正确的,但是实际应用时,最好区分开
<bean id="userInfo" class="com.pojo.UserInfo"> <property name="numbers"> <array> <value>1</value> <value>2</value> <value>3</value> </array> </property> </bean>
private int[] numbers; public void setNumbers(int[] numbers) { this.numbers = numbers; }
- Map
private Map<String,String> session; public void setSession(Map<String, String> session) { this.session = session; }
<bean id="userInfo" class="com.pojo.UserInfo"> <property name="session"> <map> <entry key="username" value="weiwei"></entry> <entry key="password" value="weiwei"></entry> </map> </property> </bean>
- list
Spring框架源码分析
-
如何理解 Spring 中两个 map
-
一个map用于存储bean的配置信息
-
一个map用来存储bean的实例信息
-
-
代码编写
- 创建BeanDefinition类用来存储配置文件信息
/* * 系统一开始就是通过ClassPathXmlApplicationContext来解析配置文件applicationContext.xml的信息的 * 解析其中的bean的class属性,通过反射创建对象,放到Map中 * */ public class BeanDefinition implements Serializable{ //创建属性分别对应bean里的各个属性 private String id; private String pkgClass; private Boolean lazy; //get set toString }
-
创建配置文件 spring-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <beans> <bean id="date" class="java.util.Date" lazy="true"></bean> <bean id="obj" class="java.lang.Object" lazy="false"></bean> </beans>
-
创建ClassPathXmlApplicationContext 类用于读取配置文件并创建对象
/* * 此类用于读取配置文件并创建对象 * */ public class ClassPathXmlApplicationContext { //1.通过此Map存储配置文件中定义的Bean对象的配置信息 private Map<String,BeanDefinition> beanMap=new HashMap<String,BeanDefinition>(); //2.通过此Map存储bean的实例 private Map<String,Object> instanceMap=new HashMap<String,Object>(); //3.编写构造方法,因为框架就是调用构造方法,传入配置文件的 public ClassPathXmlApplicationContext(String file){ //3.1读取配置文件 getClassLoader()-获取根目录(也就是resources) //通过类信息的类加载器获得流,因为spring框架就是不用new来创建对象了,所以我们也不new InoutStream() InputStream in=getClass().getClassLoader().getResourceAsStream(file); //3.2解析文件 //3.3封装数据 } //4.框架中还有一个getBean()方法来获取对象实例 public <T>T getBean(String key,Class<T> t){ return null; } }
-
成功读取配置文件获取 bean 标签并测试
/* * 此类用于读取配置文件并创建对象 * */ public class ClassPathXmlApplicationContext { //1.通过此Map存储配置文件中定义的Bean对象的配置信息 private Map<String,BeanDefinition> beanMap=new HashMap<String,BeanDefinition>(); //2.通过此Map存储bean的实例 private Map<String,Object> instanceMap=new HashMap<String,Object>(); //3.编写构造方法,因为框架就是调用构造方法,传入配置文件的 public ClassPathXmlApplicationContext(String file){ //3.1读取配置文件 getClassLoader()-获取根目录(也就是resources) //通过类信息的类加载器获得流,因为spring框架就是不用new来创建对象了,所以我们也不new InoutStream() InputStream in=getClass().getClassLoader().getResourceAsStream(file); //3.2解析文件 try { parse(in); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } //3.3封装数据 } //4.框架中还有一个getBean()方法来获取对象实例 public <T>T getBean(String key,Class<T> t){ return null; } //3.2此方法用来解析xml文件 private void parse(InputStream in) throws ParserConfigurationException, IOException, SAXException { //3.2.1.创建解析器对象 DocumentBuilder builder= DocumentBuilderFactory.newInstance().newDocumentBuilder(); //3.2.2.解析流对象,导入w3c下的Document Document doc=builder.parse(in); //3.2.3.处理doc processDocument(doc); } /* * 3.2.3此方法用来读取配置信息 * */ private void processDocument(Document doc){ //3.2.3.1.获取所有bean元素 NodeList list=doc.getElementsByTagName("bean"); //3.2.3.2.迭代bean元素 for(int i=0;i<list.getLength();i++){ Node node=list.item(i);//一个node对象接收一个bean标签 System.out.println(node.getNodeName()); } //3.2.3.3 } }
@Test public void testSpring(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); }
-
得到 bean 标签属性的值,并存到 map 中
/* * 3.2.3此方法用来读取配置信息 * */ private void processDocument(Document doc){ //3.2.3.1.获取所有bean元素 NodeList list=doc.getElementsByTagName("bean"); //3.2.3.2.迭代bean元素 for(int i=0;i<list.getLength();i++){ Node node=list.item(i);//一个node对象接收一个bean标签 System.out.println(node.getNodeName()); //一个Node对象对应一个BeanDefinition对象 BeanDefinition bd=new BeanDefinition(); //获取bean标签里的属性 NamedNodeMap nodeMap = node.getAttributes(); bd.setId(nodeMap.getNamedItem("id").getNodeValue()); bd.setPkgClass(nodeMap.getNamedItem("class").getNodeValue()); bd.setLazy(Boolean.valueOf( nodeMap.getNamedItem("lazy").getNodeValue())); //存储配置信息 beanMap.put(bd.getId(),bd); } System.out.println(beanMap); }
-
判断是否延迟加载,利用反射创建对象
private void processDocument(Document doc)throws Exception{ //3.2.3.1.获取所有bean元素 NodeList list=doc.getElementsByTagName("bean"); //3.2.3.2.迭代bean元素 for(int i=0;i<list.getLength();i++){ Node node=list.item(i);//一个node对象接收一个bean标签 System.out.println(node.getNodeName()); //一个Node对象对应一个BeanDefinition对象 BeanDefinition bd=new BeanDefinition(); //获取bean标签里的属性 NamedNodeMap nodeMap = node.getAttributes(); bd.setId(nodeMap.getNamedItem("id").getNodeValue()); bd.setPkgClass(nodeMap.getNamedItem("class").getNodeValue()); bd.setLazy(Boolean.valueOf( nodeMap.getNamedItem("lazy").getNodeValue())); //存储配置信息 beanMap.put(bd.getId(),bd); //基于配置信息中lazy属性的值,判断是否延迟加载 if(!bd.getLazy()){ Object object = newBeanInstance(bd.getPkgClass()); instanceMap.put(bd.getId(),object); } } System.out.println(beanMap); } /* * 此方法用来反射创建对象 * */ private Object newBeanInstance(String pkgClass) throws Exception{ //获取类信息 Class<?> cls=Class.forName(pkgClass); //找到无参构造方法创建对象 Constructor<?> con= cls.getDeclaredConstructor(null); //如果构造方法私有化也可以去执行 con.setAccessible(true); //通过无参构造方法创建对象 return con.newInstance(); }
-
编写 getBean 方法用来返回对象
//4.框架中还有一个getBean()方法来获取对象实例 public <T>T getBean(String key,Class<T> t) { try { //4.1判断当前instanceMao中是否有bean的实例 Object object=instanceMap.get(key); if(object!=null){ return (T)object; } //4.2实例map中没有对象则创建对象,存储在map中 object=newBeanInstance(t.getName()); System.out.println(t.getName()); instanceMap.put(key,object); return (T)object; } catch (Exception e) { e.printStackTrace(); } return null; }
-
获取对象
@Test public void testSpring(){ ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml"); Object obj= ac.getBean("obj",Object.class); Date date=ac.getBean("date", Date.class); System.out.println(obj); System.out.println(date); }
- 创建BeanDefinition类用来存储配置文件信息
Spring注解
- 通用注解
使用注解的方式来管理对象时,就不必再spring的配置文件中使用节点进行配置了在实例化我们自己创建的类时,通常都采用注解的方式比较方便在整合第三方框架,要创建别人提供的类的对象时,采用节点配置在使用注解前,需要在配置文件中,配置一项 “组件扫描” ,使用Spring知道需要管理的类在哪里完整的 spring 头文件如下,如果导入报错,但是并不会影响正常运行,解决方式,将错误的地址添加到我们idea 的配置中
//完整的 <?xml version="1.0" encoding="UTF-8"?> <beans default-lazy-init="true" xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!--配置组件扫描--> <context:component-scan base-package="com.zb.zj"> </context:component-scan>
@Component//框架扫描到此注解,就代表此注解标识的类交给框架去管理 @Lazy(false)//是否延迟加载 @Scope("prototype") //作用域设置 public class Person { public Person(){ System.out.println("此时Person类被实例化"); } }
也就是说“组件扫描+注解”就可以实现Spring创建对象在配置组件扫描时,base-package表示根包,假设类都在com.zb.zj包中,可以直接配置为这个包,也可以配置为com.zb,甚至可以配置为com都可以 但是一般不推荐使用过于简单的跟包,实际使用com.zb.zj等@Component:通用注解@Controller:添加在控制器之前的注解@Service:添加在业务类之前的注解@Repository :添加在处理持久层的类之前的注解在配置Spring创建和管理对象时,在类之前添加以上4个注解中的任意一个即可,以上4个注解的作用相同,使用方式相同,语义不同 在使用以上注解后,由Spring创建的对象的bean-id默认就是类名称字母小写的名称。也可以自定义bean-id @Repository("p")也可以设置单例模式和延迟加载@Lazy(false) //是否延迟加载@Scope("prototype") //作用域设置 - 自动装配 假设存在以下两个类,这两个类之间有依赖关系
@Repository public class UserDao { public void reg(){ System.out.println("注册DAO"); } }
@Controller public class UserServlet { @Autowired UserDao userDao; public void reg(){ System.out.println("注册servlet"); userDao.reg(); } }
UserServlet需要依赖于UserDao,则在UserServlet中的属性之前添加@Autowired注解即可当前,以上两个类都必须被Spring所管理(组件扫描能扫描到)通过注解实现自动装配时,并不需要属性有SET方法,Spring框架可以将值直接赋值过去