学习资源来自:https://www.tutorialspoint.com/spring/spring_hello_world_example.htm
第一部分:建一个完整的spring项目
1.创建一个maven项目, 通过ClassPathXmlApplicationContext手动加载Bean并用getBean手动调用Bean,
2.pom.xml 加入下面的dependencies部分,并点击IDEA窗口右边的刷新按钮启动maven下载依赖包spring-webmvc.jar
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>springlearning</groupId>
<artifactId>springlearning-1</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.1.6.RELEASE</version>
</dependency>
</dependencies>
</project>
点击刷新按钮下载jar包:
3.编写HelloWorld.java和MainApp.java
//注意把src目录标志成source root(右击src目录,选择mark directory as -> source root), 这样加载的时候直接写Beans.xml就可以在当前目录下(source root)被找到
/**
* Created by vickyy on 12/25/2018.
*/
public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by vickyy on 12/25/2018.
*/
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); //注意Beans.xml要在src下面,并且把src标志成source root, 这样Beans.xml就可以在当前目录下(source root)被找到
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
4.编写Beans.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-3.0.xsd">
<bean id = "helloWorld" class = "HelloWorld"> <--编译完后HelloWorld.class和Beans.xml在同一个目录target/classes/下面-->
<property name = "message" value = "Hello World!!!"/> <--这里有点像给HelloWorld的属性message重新赋值-->
</bean>
</beans>
5.运行
第二部分 接下来详细说下Beans.xml
4.1 Bean的定义:这里要用<bean> 来定义一个个bean, 这也是spring前期比较麻烦的地方,bean里必须包含最基本的id 和 class, 前者用于之后的搜索查询,后者指定类的路径或者类名,可以不用加.java,但必须要能够在路径下被找到。
4.2Bean的scope https://www.tutorialspoint.com/spring/spring_bean_scopes.htm, scope可以有很多种,
比较常用的是singleton和prototype, 前者表示Spring IoC container只会为这个bean创建一个实例,后者表示只要有请求这个bean的地方Spring IoC container每次都会返回一个新的实例:
<bean id = "helloWorld" class = "HelloWorld" scope="singleton">,
<bean id = "helloWorld" class = "HelloWorld" scope="prototype">, 详见scope测试截图。
4.3<property name = "message" value = "xxx" > 指的是这里可以为bean里的属性赋值,当你运行的时候,输出的信息来自这里。
接下来这个例子来验证下scope=singleton和scope=prototype的不同:
HelloWorld.java的代码不变,但是我们要修改下Beans.xml里helloWorld的scope,并且要修改下MainApp.java
Beans.xml改成这样:
<bean id="helloWorld" class="HelloWorld" scope="singleton">
</bean>
MainApp.java改成这样,加了一个HelloWorld的实例objB,此时bean scope 用的是singleton,spring ioc container只会返回一个实例,也就是说即使objB请求再生出一个HelloWorld, ioc也只会返回前面生成的objA:
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.setMessage("I am object A");
objA.getMessage();
HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
objB.getMessage();
}
}
结果:
Your Message : I am object A
Your Message : I am object A
接下来把Beans.xml改成这样:
<bean id="helloWorld" class="HelloWorld" scope="prototype">
</bean>
由于是prototype, 因此objB是一个新的实例,不与objA共用实例,所以再运行下MainApp.java,结果是:
Your Message : I am object A
Your Message : null
4.4 Bean的生命周期,有很多,但是这里只讲设置bean的init方法和destroy方法
为什么会有这两个属性呢?想下,Bean的初始化之前,可能要做一些额外的工作来让Bean可以注册入IOC 以供使用,同样的道理,Bean在被销毁之前,可能也需要做一些回收的工作。
虽然可以通过实现接口org.springframework.beans.factory.InitializingBean 来定义一个bean的初始化方法,通过实现接口org.springframework.beans.factory.DisposableBean来定义一个bean的终结方法,但是这里我只讲通过Beans.xml的配置来实现。
初始化,用init-method属性来指定,销毁,用destroy-method属性来指定。注意,destroy-method的定义只在scope=singleton的时候有用,scope=prototype的时候,destroy-method是不会被调用的。这个后面会分析。
先来看看验证的代码:
HelloWorld.java里加入init() 和destory()
public class HelloWorld {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("Your Message : " + message);
}
public void init(){
System.out.println("Bean is going through init.");
}
public void destroy() {
System.out.println("Bean will destroy now.");
}
}
MainApp.java, 使用AbstractApplicationContext 替代ApplicationContext, 前者是后者的子类,有一个doClose()方法,这个方法正是销毁Bean的方法,Spring不会主动销毁一个bean.
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.getMessage();
context.registerShutdownHook();
}
}
执行结果:
Bean is going through init.
Your Message : null
Bean will destroy now.
接着分析下为什么scope=prototype, destroy-method就不被调用,
从源码来分析:new ClassPathXmlApplicationContext("Beans.xml");触发了refresh()方法进而调用createBean()来注册bean进来, createBean()里有这么一句代码:this.registerDisposableBeanIfNecessary(beanName, bean, mbd);
这个方法是:
otected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
AccessControlContext acc = System.getSecurityManager() != null?this.getAccessControlContext():null;
//这里就已经排除了prototype的方法
if(!mbd.isPrototype() && this.requiresDestruction(bean, mbd)) {
if(mbd.isSingleton()) { //如果这个方法是singleton,那么就注册disposableBean, 这个注册就是写入一个disposableBeanMap。
this.registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, mbd, this.getBeanPostProcessors(), acc));
} else {
Scope scope = (Scope)this.scopes.get(mbd.getScope());
if(scope == null) {
throw new IllegalStateException("No Scope registered for scope \'" + mbd.getScope() + "\'");
}
scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(bean, beanName, mbd, this.getBeanPostProcessors(), acc));
}
这是前期工作,那么当调用doClose()的时候,当执行到
public void destroySingletons() {
if(this.logger.isDebugEnabled()) {
this.logger.debug("Destroying singletons in " + this);
}
Map disposableBeanNames = this.singletonObjects;
synchronized(this.singletonObjects) {
this.singletonsCurrentlyInDestruction = true;
}
Map i = this.disposableBeans;
String[] var8;
synchronized(this.disposableBeans) { // prototype没有注册disposableBeans,所以这里为空,下面的for就进不去,也就无法进入destroy方法的调用
var8 = StringUtils.toStringArray(this.disposableBeans.keySet());
}
for(int var9 = var8.length - 1; var9 >= 0; --var9) {
this.destroySingleton(var8[var9]);
}
4.5 Bean属性继承, 这与java的类继承不一样
Bean有个属性 “parent=”,只要指定parent, 当前的bean就会继承来自parent的属性,而如果child和parent定义同样的属性,child的值会覆盖parent的值。
看下面例子,parent = helloWorld, 所以helloWorld的init-method, destory-method, scope都会被child:helloInida继承,但因为HelloIndia.java并没有定义init()和destroy(),所以运行会报错,因此这里要删掉init-method, 和destroy-method, 而helloIndia的property message1的值则会覆盖parent message1的值:
//HelloIndia.java
public class HelloIndia {
private String message1;
private String message2;
private String message3;
public void setMessage1(String message) {
this.message1 = message;
}
public void setMessage2(String message) {
this.message2 = message;
}
public void setMessage3(String message) {
this.message3 = message;
}
public void getMessage1() {
System.out.println("Your Message : " + message1);
}
public void getMessage2() {
System.out.println("Your Message : " + message2);
}
public void getMessage3() {
System.out.println("Your Message : " + message3);
}
}
//HelloWorld.java
/**
* Created by vickyy on 12/25/2018.
*/
public class HelloWorld {
private String message1;
private String message2;
public void setMessage1(String message) {
this.message1 = message;
}
public void setMessage2(String message) {
this.message2 = message;
}
public void getMessage1() {
System.out.println("Your Message : " + message1);
}
public void getMessage2() {
System.out.println("Your Message : " + message2);
}
public void init(){
System.out.println("Bean is going through init.");
}
public void destroy() {
System.out.println("Bean will destroy now.");
}
}
//MainApp.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by vickyy on 12/25/2018.
*/
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.getMessage1();
objA.getMessage2();
HelloIndia objB = (HelloIndia) context.getBean("helloIndia");
objB.getMessage1();
objB.getMessage2();
objB.getMessage3();
context.registerShutdownHook();
}
}
//Beans.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-3.0.xsd">
<bean id="helloWorld" class="HelloWorld" ~~删除线~~init-method = "init" destroy-method = "destroy"~~删除线~~ scope="singleton" >
<property name = "message1" value = "Hello World!"/>
<property name = "message2" value = "Hello Second World!"/>
</bean>
<bean id ="helloIndia" class = "HelloIndia" parent = "helloWorld">
<property name = "message1" value = "Hello India!"/>
<property name = "message3" value = "Namaste India!"/>
</bean>
<bean class="InitHelloWorld"/>
</beans>
因此,你可以设置一个beanTemplate,beanTemplate的属性设置如<bean id="beanTemplate" abstract="true">, 不能有class,但是要有abstract=true, 比如:
<?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-3.0.xsd">
<bean id="beanTemplate" abstract="true">
<property name="message1" value="Hello World!"/>
<property name="message2" value="Hello Second World!"/>
</bean>
<bean id="helloWorld" class="HelloWorld" parent="beanTemplate">
</bean>
<bean id="helloIndia" class="HelloIndia" parent="helloWorld">
</bean>
<bean class="InitHelloWorld"/>
</beans>
4.6 关于Bean注入
IOC框架的一个重要的功能是为bean做初始化工作,因此,如果一个类的属性没有默认的初始化值,而又在程序里被调用,那么在Beam 配置(Beans.xml)中就要通过<property>来做初始化。如果属性是一个自定义的类,那么这个自定义的类也要作为Bean注入,而且要作为调用Bean的内在bean。而且只要在Beans.xml里用<property>来声明的属性在其声明的类里必须要有对应的setxxx()方法否则会报类似如下错误。
BeanCreationException: Error creating bean with name 'textEditor' defined in class path resource [Beans.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'spellChecker' of bean class [TextEditor]: Bean property 'spellChecker' is not writable or has an invalid setter method. Did you mean 'spellChecker1'?
用下面这个例子来验证下:
//TextEditor.java
public class TextEditor {
private SpellChecker spellChecker;
private String str;
// a setter method to inject the dependency.
public void setSpellChecker(SpellChecker spellChecker) {
System.out.println("Inside setSpellChecker." );
this.spellChecker = spellChecker;
}
// a getter method to return spellChecker
public SpellChecker getSpellChecker() {
return spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}
//MainApp.java
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
TextEditor te = (TextEditor)context.getBean("textEditor");
te.spellCheck();
}
}
//Beans.xml部分
<bean id="textEditor" class="TextEditor">
<property name="spellChecker">
<bean id="spellChecker" class="SpellChecker">
</bean>
</property>
<property name="str" value = "hello"/> 这里设置了str这个属性,但是TextEditor.java里却没有为这个str编写setStr()方法,所以会报错。
</bean>
PropertyDescriptor.java里会去拼凑这个方法名,用的就是set + 首字母大写的属性变量名!
if (writeMethodName == null) {
// SET_PREFIX = “set”
writeMethodName = Introspector.SET_PREFIX + getBaseName();
}
Class<?>[] args = (type == null) ? null : new Class<?>[] { type };
writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
if (writeMethod != null) {
if (!writeMethod.getReturnType().equals(void.class)) {
writeMethod = null;
}
}
try {
setWriteMethod(writeMethod);
} catch (IntrospectionException ex) {
// fall through
}
....
// Calculate once since capitalize() is expensive.
String getBaseName() {
if (baseName == null) {
baseName = NameGenerator.capitalize(getName()); //获取首字母大写的变量名
}
return baseName;
}
bean信息除了以XML配置出现,还可以用java 注解配置。
在一个类前用@Configuration,表示这个类可以被spring ioc容器作为bean的定义来源进行bean注入。如果一个方法前用@Bean表示这个方法返回的对象(这个方法一定要有返回对象)应该要被当成一个bean注入spring application context。
例子:这里就不需要Beans.xml 了!也就不需要getXX(), setXX();
注意: Because you are using Java-based annotations, so you also need to add CGLIB.jar from your Java installation directory and ASM.jar library which can be downloaded from asm.ow2.org.
@Configuration
public class HelloWorldConfig {
@Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}
MainApp.java
//注意下,这里注入Bean的方法由原来的ClassPathXmlApplicationContext()变为AnnotationConfigApplicationContext(), 而且AnnotationConfigApplicationContext()还具有一次性加载多个configuration class的能力,看接下来的代码
ApplicationContext ctx = new AnnotationConfigApplicationContext(HelloWorldConfig.class);
//因为没有定义bean id, 所以这里的getBean 用的是HelloWorld.class
HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
helloWorld.setMessage("Hello world");
helloWorld.getMessage();
AnnotationConfigApplicationContext()还具有一次性加载多个configuration class的能力:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
//手动触发refresh(),
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
对于嵌套依赖的bean又该如何写config呢?
在XML里,用的<bean>里的<property>里再写<bean>, 如果用java config, 只要把生成被依赖的bean的方法以参数的形式传入调用bean生成的方法就可以,但前提是调用bean必须有一个以这个bean作为参数的构造函数:
TextEditor.java
public class TextEditor {
private SpellChecker spellChecker;
public void spellCheck() {
spellChecker.checkSpelling();
}
//必须要有这个构造函数,它以SpellCheker作为参数, 在IOC注入的时候,做spellChecker这个属性的初始化工作
public TextEditor(SpellChecker spellChecker){
this.spellChecker = spellChecker;
}
}
TextEditorConfig.java
@Configuration
public class TextEditorConfig {
@Bean
public TextEditor textEditor(){
//要表示TextEditor依赖spellCheker,只要把被依赖的SpellChecker bean生成的方法当做参数传入TextEditor bean生成的方法就可以,但前提是TextEditor有个构造函数是以SpellChecker为参数
return new TextEditor(spellChecker());
}
@Bean
public SpellChecker spellChecker(){
return new SpellChecker();
}
}