实现第一个 Spring HelloWorld 程序
接着上一次笔记,上次我们讲了使用 Spring 框架需要做的一些准备工作。现在,我们就来实现我们的第一个 Spring HelloWorld 程序,先不管为什么,我们先看效果。通过程序的效果,我们再进一步探知 Spring IoC 容器的工作原理。
首先我们需要新建一个 Spring 配置文件:在src下右键 new —> other —> 找到 Spring文件夹,点击 Spring Bean Configurations File,起名字叫 ApplicationContext.xml,编写如下内容
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 5 <!-- class属性值需要写完整的类全名 --!> 6 <bean id="helloworld" class="com.bupt.springtest.beanXml.HelloWorld"/> 7 8 </beans>
1 package com.bupt.springtest.beanXml;
2
3 import org.springframework.context.ApplicationContext;
4 import org.springframework.context.support.ClassPathXmlApplicationContext;
5
6 //新建一个测试类
7 public class HelloWorld
8 {
9 public void greet()
10 {
11 System.out.println("Hello World!");
12 }
13
14 public static void main(String[] args)
15 {
16 ApplicationContext ctx = new ClassPathXmlApplicationContext("HelloWorld.xml");
17
18 HelloWorld hw = (HelloWorld) ctx.getBean("helloworld");
19
20 hw.greet();
21 }
22 }
以上程序我们可以得到结果:Hello World!
最后的结果 Hello World! 是怎么来的,很显然是调用了hw.greet(),而 hw 明显是 HelloWorld这个类的一个实例。但hw这个实例怎么来的?程序并没有为我们 new 出来一个HelloWorld实例。我们再来看上面两行代码,new ClassPathXmlApplicationContext("HelloWorld.xml") 这段代码,学过XML解析的同学应该会眼熟,这很像XML解析时的代码,同时结合到我们在xml文件中定义的一个 HelloWorld 类型的 bean 元素,以及ctx.getBean("helloworld")获取的对象,与 xml 中定义的 bean 的 id 值一样,我们应该可以大概得出结论:
解析xml文件后,将得到的各种 bean 放置到 ApplicationContext 容器中,容器对象再通过getBean()方法根据 bean id 获取对应的bean,接着就可以操作 bean 对象了。在这整个过程中,作为程序员的我们并没有任何创建对象的动作,创建 bean 实例的整个过程完全交由 Spring 框架来为我们实现,它何时创建,怎么创建我们一概不知。这时,我们再来看看IoC/DI的概念。
IoC(Inversion of Contorl)一般翻译成 "控制反转",其思想是反转资源获取的方向。传统的资源查找方式要求组件项容器发起请求查找资源。作为回应,容器适时的返回资源。而应用了 IoC 之后,则是容器主动地将资源推送给它所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源。
DI(Dependncy Injection)依赖注入:它是IoC的另一种表述方式,即组件以一些预先定义好的方式(如:setter方法)接受来自如容器的资源注入,相较于IoC而言,这种表述更直接。
通过概念的解释我们应该更好理解 IoC/DI 了吧,IoC就是一个容器,它为我们获取 bean 资源提供了一种方式。让我们不再手动的去 new 一个对象,而是容器自己会为我们自动生成好对象,只要把配置文件配置好。如此一来,大大降低了模块与模块之间的耦合性。因为当一个类需要持有另一类的对象时,不再需要去手动地实例化这个类,只要通过 Spring 框架为我们注入即可。当一个模块需求改变时,也不会涉及到对其他类的修改。
现在我们通过分析代码来实现自己的IoC容器,模拟整个 IoC/DI 过程。由前面我们知道,IoC容器会解析 xml 文件生成 bean 实例放置在容器中,xml 文件中配置有值为全类名的 class 属性。我们应该可以想到,这些 bean 实例是通过 反射生成的。所以,我们的 IoC 容器应该是:解析 xml 文件,将里面配置的 bean id 和通过反射生成的 bean 实例作为键值对放入一个Map对象中,这样我们才能通过 id 属性来获取 bean 实例。同时,我们还需要通过反射来为类中的属性赋值。下面来看代码
1 package com.bupt.test;
2
3 import java.lang.reflect.Field;
4 import java.lang.reflect.Method;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import org.dom4j.Document;
9 import org.dom4j.Element;
10 import org.dom4j.io.SAXReader;
11
12 /**
13 * 模拟Spring的ClassPathXmlApplicationContext对象
14 */
15 public class ClassPathXmlApplicationContext
16 {
17 //创建Map对象用来存放beans.xml中配置的对象名(id)和对象(class.newInstance())的键值对
18 private Map<String, Object> beans = new HashMap<>();
19
20 public ClassPathXmlApplicationContext(String filePath) throws Exception
21 {
22 //获取jom4j解析器
23 SAXReader sax = new SAXReader();
24 //获取根节点
25 Document doc = sax.read(filePath);
26 //获取根元素节点beans
27 Element rootElement = doc.getRootElement();
28 //获取根节点元素下所有的子元素bean
29 List<Element> elementList = rootElement.elements("bean");
30
31 for(Element e : elementList)
32 {
33 //获取bean的id和class属性
34 String id = e.attributeValue("id");
35 String className = e.attributeValue("class");
36
37 //反射获取class属性对应类的实例
38 Object clazz = Class.forName(className).newInstance();
39
40 beans.put(id, clazz);
41
42 //获取类中定义的所有属性
43 Field[] field = clazz.getClass().getDeclaredFields();
44 //Map存放的是属性名和属性的class对象的键值对
45 Map<String, Class<?>> map = new HashMap<>();
46 //遍历属性,将属性名和属性的Class对象放入map
47 for(Field f : field)
48 {
49 map.put(f.getName(), f.getType());
50 }
51
52 //获取所有叫property的子元素
53 List<Element> propertyElement = e.elements("property");
54
55 for(Element ele : propertyElement)
56 {
57 //获取property的name属性
58 String name = ele.attributeValue("name");
59 //拼接方法名如setName()
60 String methodName = "set" + name.substring(0, 1).toUpperCase()+ name.substring(1);
61 Method m = null;
62
63 //获取property的bean和value属性
64 String beanProperty = ele.attributeValue("bean");
65 String valueProperty = ele.attributeValue("value");
66
67 //若第二个属性为另一个bean的引用
68 if(null != beanProperty)
69 {
70 //反射获取setXxx方法对象
71 m = clazz.getClass().getMethod(methodName, new Class[]{beans.get(beanProperty).getClass().getInterfaces()[0]});
72 //执行setXxx方法
73 m.invoke(clazz, beans.get(beanProperty));
74
75 }
76 //若第二个属性为value
77 if(null != valueProperty)
78 {
79 //根据value属性值,从map中获取setXxx方法的参数的Class对象
80 m = clazz.getClass().getMethod(methodName, new Class[]{map.get(name)});
81
82 //判断参数类型,进行相应的类型变换,再将转换完后的参数传入invoke方法
83 if((String.class).equals(map.get(name)))
84 {
85 m.invoke(clazz, valueProperty);
86 }
87 else if((int.class).equals(map.get(name)))
88 {
89 m.invoke(clazz, Integer.parseInt(valueProperty));
90 }
91 else if((double.class).equals(map.get(name)))
92 {
93 m.invoke(clazz, Double.parseDouble(valueProperty));
94 }
95 }
96 }
97 }
98 }
99
100 //通过id值返回自动生成的对象
101 public Object getBean(String id)
102 {
103 return beans.get(id);
104 }
105 }
下面编写测试类
1 package com.bupt.test;
2
3 public interface UserDAO
4 {
5 public void save();
6 }
1 package com.bupt.test;
2
3 public class UserDAOImpl implements UserDAO
4 {
5 @Override
6 public void save()
7 {
8 System.out.println("a user saved!");
9 }
10 }
1 package com.bupt.test;
2
3 public class UserService
4 {
5 //持有对UserDAo的一个引用
6 private UserDAO userDAO;
7 private String name;
8 private int age;
9 private double height;
10
11 public double getHeight()
12 {
13 return height;
14 }
15
16 public void setHeight(double height)
17 {
18 this.height = height;
19 }
20
21 public String getName()
22 {
23 return name;
24 }
25
26 public void setName(String name)
27 {
28 this.name = name;
29 }
30
31 public int getAge()
32 {
33 return age;
34 }
35
36 public void setAge(int age)
37 {
38 this.age = age;
39 }
40
41 public void add()
42 {
43 userDAO.save();
44 }
45
46 public UserDAO getUserDAO()
47 {
48 return userDAO;
49 }
50
51 public void setUserDAO(UserDAO userDAO)
52 {
53 this.userDAO = userDAO;
54 }
55 }
src下创建一个 bean.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 把被引用的bean写在前面 -->
<bean id="u" class="com.bupt.test.UserDAOImpl">
</bean>
<bean id="userService" class="com.bupt.test.UserService">
<property name="userDAO" bean="u"/>
<property name="name" value="tom"/>
<property name="age" value="19"/>
<property name="height" value="178.8"/>
</bean>
</beans>
编写执行测试类
package com.bupt.test;
public class ApplicationContextTest
{
public static void main(String[] args) throws Exception
{
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("src/beans.xml");
UserService service =(UserService)ctx.getBean("userService");
service.add();
System.out.println(service.getName() + ", " + service.getAge() + ", " + service.getHeight() + ", " + service.getUserDAO());
}
}
/**
* 结果为
* a user saved!
* tom, 19, 178.8, com.bupt.test.UserDAOImpl@128cdfa
*/
这就是模拟 IoC 容器的代码,希望通过这段代码,能加深大家对 IoC 容器的理解
关于 Spring 面试时常问的关于 IoC/DI 的问题
spring 的优点?
1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦
2.可以使用容易提供的众多服务,如事务管理,消息服务等
3.容器提供单例模式支持
4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能
5.容器提供了众多的辅助类,能加快应用的开发
6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等
7.spring属于低侵入式设计,代码的污染极低
8.独立于各种应用服务器
9.spring的DI机制降低了业务对象替换的复杂性
10.Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可以自由选择spring的部分或全部
什么是DI机制?
依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色
需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中
创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者
因此也称为依赖注入。
spring以动态灵活的方式来管理对象 , 注入的两种方式,设置注入和构造注入。
设置注入的优点:直观,自然
构造注入的优点:可以在构造器中决定依赖关系的顺序。
什么是AOP?
面向切面编程(AOP)完善spring的依赖注入(DI),面向切面编程在spring中主要表现为两个方面
1.面向切面编程提供声明式事务管理
2.spring支持用户自定义的切面
面向切面编程(aop)是对面向对象编程(oop)的补充,
面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面。
AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象,
是对应用执行过程中的步骤进行抽象,,从而获得步骤之间的逻辑划分。
aop框架具有的两个特征:
1.各个步骤之间的良好隔离性
2.源代码无关性
来源:https://www.cnblogs.com/2015110615L/p/5566399.html