Spring新手指南(上)
框架:高度抽取可重用代码的一种设计,是多个可重用模块的集合
IOC(Inversion[反转] of Control)
控制
- 主动式:(要什么资源自己创建即可)
- 被动式:(资源的获取不是我们创建,而是交给一个容器来创建和设置)
容器:管理所有的组件(有功能的类)
DI:依赖注入,容器能知道哪个组件(类)运行的时候需要哪个组件(类),通过注入(反射的方式)进行赋值
初体验
- 编写Person类
public class pers.lele.domain.Person {
private String name;
private String gender;
private int age;
private String email;
public pers.lele.domain.Person(){
System.out.println("创建了。。。");
}
public String getName() {
System.out.println("setName方法被调用了。。。");
return name;
}
public void setName(String name) {
this.name = name;
}
//其他属性的getter和setter方法省略
//toString方法省略
}
- 创建配置文件并配置
<!--
一个bean标签注册一个组件
class:所要注册组件的全类名
id:此组件的唯一标识
-->
<bean id="person" class="pers.lele.domain.pers.lele.domain.Person">
<!--
为组件的属性赋值
name:指定属性名
value:指定属性值
-->
<property name="name" value="张三"/>
<property name="gender" value="男"/>
<property name="age" value="18"/>
<property name="email" value="zhangsan@qq.com"/>
</bean>
- 单元测试
/**
* ClassPathXmlApplicationContext:类路径
* FileSystemXmlApplicationContext:文件路径
*/
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
pers.lele.domain.Person person = context.getBean("person", pers.lele.domain.Person.class);
System.out.println(person);
}
总结
-
Person对象是何时创建的?
Person对象并不是使用时才创建,而是当容器初始化的时候就创建了
@Test public void test02(){ //输出person对象创建了 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); }
-
同一个组件在IOC容器中是单例的
@Test public void test03() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); pers.lele.domain.Person person01 = context.getBean("person", pers.lele.domain.Person.class); pers.lele.domain.Person person02 = context.getBean("person", pers.lele.domain.Person.class); System.out.println(person01 == person02); }
-
若容器中不存在指定的组件,则报错
-
IOC容器在创建对象的时候会使用setter方法进行赋值
-
JavaBean的属性名是由getter和setter方法决定的
DI
通过构造函数创建组件
<bean id="person01" class="pers.lele.domain.Person">
<!--
使用constructor-arg调用Person的有参构造器
注意
1.不区分前后顺序
2.name属性值要与构造函数的形参名一致
-->
<constructor-arg name="name" value="老王"/>
<constructor-arg name="age" value="22"></constructor-arg>
<constructor-arg name="email" value="laowang@qq.com"></constructor-arg>
<constructor-arg name="gender" value="男"></constructor-arg>
</bean>
若省略name属性,则constructor-arg需要严格按照构造参数的顺序
<!-- 省略name属性 -->
<bean id="person02" class="pers.lele.domain.Person">
<constructor-arg value="老七"/>
<constructor-arg value="女"/>
<constructor-arg value="88"/>
<constructor-arg value="laoqi@qq.com"/>
</bean>
- 可以通过index属性来调整顺序
- 当发生构造函数重载而导致赋值错误时,可以使用type属性来确定具体的类型
使用p名称空间进行赋值
<bean id="person03" class="pers.lele.domain.Person" p:age="38" p:name="kobe" p:gender="男"
p:email="kobe@qq.com"></bean>
为各种类型赋值
-
简单类型(基本类型)属性的赋值,直接使用value属性进行赋值
-
赋null值
<!-- 赋null值 默认情况下就是null 若属性在有初始值的情况下需要赋值为null时,则需要在property标签内部进行赋值 --> <bean id="person01" class="pers.lele.domain.Person"> <property name="name"><null/></property> </bean>
-
为自定义类型赋值
- 使用ref引用赋值(引用外部bean)
<bean id="car01" class="pers.lele.domain.Car"> <property name="carName" value="奥迪双转"/> <property name="price" value="150"/> <property name="color" value="紫罗兰"/> </bean> <bean id="person02" class="pers.lele.domain.Person"> <property name="car" ref="car01"/> </bean>
注意:person02.getCar与car01是同一个引用
- 在标签体中创建对象(引用内部bean)
<bean id="person03" class="pers.lele.domain.Person"> <property name="car"> <bean class="pers.lele.domain.Car"> <property name="carName" value="宝马"/> <property name="price" value="150"/> <property name="color" value="大红大紫"/> </bean> </property> </bean>
-
list类型属性赋值
<!--list类型赋值--> <bean id="book01" class="pers.lele.domain.Book"> <property name="bookName" value="叽叽叽"></property> <property name="author" value="那个最好的她"></property> </bean> <bean id="person04" class="pers.lele.domain.Person"> <property name="books"> <list> <bean class="pers.lele.domain.Book" p:bookName="水浒" p:author="我自己"></bean> <ref bean="book01"/> </list> </property> </bean>
-
map类型属性赋值
<bean id="person05" class="pers.lele.domain.Person"> <property name="maps"> <map> <entry key="key01" value="张三"></entry> <entry key="key02" value="18"></entry> <entry key="key03" value-ref="book01"></entry> <entry key="key04"> <bean class="pers.lele.domain.Car"> <property name="carName" value="小行星"></property> <property name="price" value="52"></property> <property name="color" value="黄"></property> </bean> </entry> </map> </property> </bean>
-
为properties类型赋值
<bean id="person06" class="pers.lele.domain.Person"> <property name="properties"> <props> <prop key="key01">哈哈</prop> <prop key="key02">呵呵</prop> <prop key="key03">嘿嘿</prop> <prop key="key04">嘻嘻</prop> </props> </property> </bean>
-
utils名称空间的使用
<utils:map id="myMap"> <entry key="a" value="A"/> <entry key="b" value="B"/> <entry key="c" value="C"/> </utils:map> <bean id="person07" class="pers.lele.domain.Person"> <property name="maps" ref="myMap"></property> </bean>
-
级联属性的使用
注意:当修改car.price时,原来的car也会发生变化<bean id="person08" class="pers.lele.domain.Person"> <property name="car" ref="car01"/> <property name="car.price" value="88"/> </bean>
通过继承实现bean配置信息的重用
注意:此处的继承只是配置信息的继承,不存在类继承现象,且book01与book02不是同一个对象
<bean id="book01" class="pers.lele.domain.Book">
<property name="bookName" value="吃瓜群众" ></property>
<property name="author" value="张三"></property>
</bean>
<bean id="book02" class="pers.lele.domain.Book" parent="book01">
<property name="author" value="李四"/>
</bean>
通过abstract户属性创建一个模板bean
<bean id="book03" class="pers.lele.domain.Book" abstract="true">
<property name="bookName" value="八卦G" ></property>
<property name="author" value="张三"></property>
</bean>
<bean id="book04" class="pers.lele.domain.Book" parent="book03">
<property name="author" value="李四"/>
</bean>
通过bean的depends-on属性来改变创建顺序
<!--创建顺序: car01->book05->person04-->
<bean id="person04" class="pers.lele.domain.Person" depends-on="car01,book05"></bean>
<bean id="car01" class="pers.lele.domain.Car"></bean>
<bean id="book05" class="pers.lele.domain.Book"></bean>
测试bean的作用域
<!--测试bean的作用域-->
<!--
singleton : 单实例(默认)
在容器启动完成时就创建好了,保存在容器中
单例模式
-->
<!--prototype : 多实例
在容器启动完成时不会创建,获取的时候才创建,每次获取都创建一个新的bean
-->
<bean id="person05" class="pers.lele.domain.Person" scope="prototype">
<property name="name" value="张三"/>
</bean>
通过静态工厂和动态工厂创建bean
- 静态工厂
<!--静态工厂-->
<!-- factory-method 指定通过工厂的哪个方法来创建对象-->
<bean id="apple01" class="pers.lele.factory.AppleStaticFactory" factory-method="getInstance">
<!-- 使用constructor-arg设置方法参数 -->
<constructor-arg value="红富士"></constructor-arg>
</bean>
- 实例工厂
<!--实例工厂-->
<!--先创建工厂的bean-->
<bean id="appleInstanceFactory" class="pers.lele.factory.AppleInstanceFactory"/>
<!--
factory-bean:使用哪个工厂
factory-method:使用工厂的哪个方法
-->
<bean id="apple02" class="pers.lele.domain.Apple" factory-bean="appleInstanceFactory" factory-method="getInstance">
<constructor-arg value="富士康"></constructor-arg>
</bean>
通过实现FactoryBean接口来创建对象
public class MyFactoryBeanImpl implements FactoryBean<Apple> {
public Apple getObject() throws Exception {
return new Apple("红果果", 88, "绿");
}
public Class<?> getObjectType() {
return Apple.class;
}
//设置是否是单例
public boolean isSingleton() {
return false;
}
}
<!--实现FactoryBean接口的工厂-->
<bean id="myFactoryBeanImpl" class="pers.lele.factory.MyFactoryBeanImpl"></bean>
创建带有生命周期方法的bean
public class Book {
private String bookName;
private String author;
public Book() {
System.out.println("Book被创建了");
}
public void myInit() throws Exception {
System.out.println("Book对象被初始化了");
}
public void myDestroy() {
System.out.println("Book对象被销毁了");
}
//省略
}
<!--创建带有生命周期方法的bean-->
<!--
注意:
init-method和destroy-method方法不能有参数,可以抛出异常
创建的顺序:
单例模式:
因为是单例的,当不用容器中的bean时,也会被创建同时执行init防范,当容器关闭后,bean对象会执行相应的destroy方法
构造器 -> 初始化方法 -> 销毁方法
多例模式:
而对于多例,在使用时才创建对象并执行初始化方法,当容器关闭后,bean对象会执行相应的destroy方法
-->
<!-- <bean id="book01" class="pers.lele.domain.Book" init-method="myInit" destroy-method="myDestroy" scope="singleton"></bean>-->
<bean id="book02" class="pers.lele.domain.Book" init-method="myInit" destroy-method="myDestroy" scope="prototype"></bean>
bean的后置处理器
<!--
实现BeanPostProcessor接口
初始化顺序
构造函数 初始化前置处理器 初始化方法 初始化后置处理器 容器销毁 销毁办法
注意:若没有初始化方法,则也会执行响应的初始化前置处理器 初始化后置处理器
-->
<bean class="pers.lele.processor.MyBeanPostProcessor"/>
<bean id="book03" class="pers.lele.domain.Book" init-method="myInit" destroy-method="myDestroy">
<property name="bookName" value="雪山来客"/>
<property name="author" value="admin"/>
Spring管理连接池
导入druid数据库连接池以及mysql驱动
- 引用命名空间context来引入外部配置文件
- 注意在引用properties文件的时候,数据库的用户名不能和username属性名冲突
<!--引入外部配置文件-->
<context:property-placeholder location="classpath:dbConfig.properties"></context:property-placeholder>
<!--直接使用配置文件的配置信息-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 注意${jdbc.username}空格-->
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<!--username默认获取的是电脑用户名-->
<bean id="car" class="pers.lele.domain.Car">
<property name="carName" value="${username}"></property>
</bean>
基于XML的自动装配
<bean id="car01" class="pers.lele.domain.Car">
<property name="carName" value="ofo"></property>
<property name="price" value="88888888"></property>
<property name="color" value="红"></property>
</bean>
<bean id="car02" class="pers.lele.domain.Car">
<property name="carName" value="青桔单车"></property>
<property name="price" value="88888888"></property>
<property name="color" value="红"></property>
</bean>
<!--
autowire="default" , autowire="no" 不会自动装配,不自动为car属性赋值
autowire="byName":
以属性名(属性名为setter方法)作为id去容器中找到某个组件,然后给他赋值,若不存在此id组件,则赋值为null
autowire="byType"
根据类型去容器中查找,若找不到则赋值为null,若存在多个,则报错:org.springframework.beans.factory.NoUniqueBeanDefinitionException
autowire="constructor"
按照构造器进行赋值,先根据类型进行装配,若没有赋值为null,若有多个,将参数的名作为id继续匹配,若没有,则赋值为null
-->
<bean id="person" class="pers.lele.domain.Person" autowire="constructor">
<!--手动装配-->
<!-- <property name="car" ref="car"></property> -->
</bean>
SpEL(Spring Expression Language)测试
<bean id="car" class="pers.lele.domain.Car">
<property name="carName" value="巴啦啦"></property>
<property name="color" value="乐乐"></property>
</bean>
<!--Spring Expression Language-->
<bean id="person" class="pers.lele.domain.Person">
<!--使用字面常量-->
<property name="salary" value="#{88.88*10}"></property>
<!--引用其他的bean-->
<property name="car" value="#{car}"></property>
<!--引用其他bean的某个属性-->
<property name="name" value="#{car.carName}"></property>
<!--调用非静态方法-->
<property name="gender" value="#{car.getColor()}"></property>
<!--调用静态方法-->
<property name="email" value="#{T(java.util.UUID).randomUUID().toString().substring(0,5)}"></property>
</bean>
使用注解创建DAO、Service、Controller
- @Controller
- @Service
- @Repository
- @Component
如何使用
- 给要添加的组件标上四个注解的任何一个
- 告诉Spring,自动扫描加了注解的组件,依赖context名称空间 <context:component-scan base-package=""/>
- 使用:bean的id是类名首字母小写(注意:要支持注解,要使用到aop包),组件默认是单例的
注意:
- 修改bean的id值,@Controller(“pServlet”)
- 修改单实例与多实例 @Scope(“prototype”)
只扫描与不扫描
<context:component-scan base-package="pers.lele" use-default-filters="false">
<!--
annotation:使用注解进行排除
assignable:使用全类名进行排除
-->
<!--指定哪些不要-->
<!--<context:exclude-filter type="annotation" expression=""/>-->
<!--指定只扫描哪些注解-->
<!--use-default-filters="false" 可以禁用扫描默认规则-->
<!--<context:include-filter type="annotation" expression=""/>-->
</context:component-scan>
AutoWired实现自动装配
以前装配时需要在bean中指定,且还需要getter和setter方法,使用@AutoWired注解可自动赋值装配,装配规则与属性名无关(猜想与类名有关)
装配原理:
- 先按照类型去容器中找对应的组件,若找到一个,则直接赋值,若没找到则报错
- 若找到多个,则根据变量名作为id继续查找,若匹配上直接赋值,否则报错
@Autowired在装配时找不到会报错,可使用required=false来解决不报错,那是很能会出现nullPointerException
Qualifier:使用指定名字作为id进行装配,<@Qualifier(“bookService”)>,否则报错
AutoWired和Qualifier注解作用在方法上
/**
* 被Autowired注解修饰的类,在容器运行时会自动执行且参数会自动装配,
* 装配原则与Autowired一致(现根据classType,若存在多个根据属性名作为id)
* 也可以使用Qualifier注解进行制定
* @param service
*/
@Autowired
public void printInfo(@Qualifier("service")PersonService service) {
System.out.println("printInfo...");
service.printInfo();
}
Autowired注解、Resource注解与@Inject(都是自动装配)
- Autowired :强大的,Spring自己的注解
- Resource: j2ee,java的标准,拓展性强
使用Spring的单元测试
注意:
1. 在maven中test目录下进行单元测试时,若使用自动注入则会出现空指针异常,因为它不和源码在同一路径下,
包扫描扫描不到
2. 若在类路径下进行单元测试,可能会出现重复的执行,因为容易的创建发生了递归,改为static即可
如何使用
/**
* 使用Spring的junit的步骤
* 1. 导入jar包 spring-test
* 2. 使用@ContextConfiguration指定配置文件的位置
* 3. @@RunWith指定使用哪种驱动进行单元测试
*/
@ContextConfiguration(locations = "classpath:IOC_06.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
@Autowired
private BookServlet servlet;
@Test
public void test01(){
System.out.println(servlet.hashCode());
}
}
主要用途:可以在maven的test目录下进行单元测试
泛型依赖注入