Spring
简介
Spring是一个IOC(DI)和AOP容器框架
IOC(DI):依赖注入
AOP:面向切面编程
Spring Framework 系统架构
Text:Spring的单元测试模块
Core Container:核心容器(IOC);黑色代表这部分的功能由哪些jar包组成
AOP+Aspects(面向切面编程模块)
ORM(Object Relation Mapping):对象关系映射
Transaction:事务
IOC & DI
简介
IOC:(Inversion(反转) Of Control):控制反转
DI:(Dependency Injection)依赖注入
通过IOC容器创建对象,并为属性赋值
<!--一个Bean标签可以注册一个组件(类、对象)-->
<!-- class:组件的全类名 id:唯一标识 -->
<bean id="person1" class="com.bean.Person">
<!--使用property标签为Person对象的属性赋值-->
<!-- name="" : 指定属性名 value="" :指定属性的值-->
<property name="personName" value="张三" ></property>
<property name="personAge" value="18" ></property>
</bean>
public void test(){
//ApplicationContext 代表ioc容器
//ClassPathXmlApplicationContext:当前应用的xml配置文件在ClassPath下
//根据配置文件得到ioc容器对象
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
Person person = (Person)ioc.getBean("person1");
}
注意!
容器中对象的创建在容器创建的时候就已经创建好了
同一个组件在ioc容器中是单实例的
ioc容器在创建这个组件对象的时候,会利用setter方法为javabean的属性进行赋值
javaBean的属性名是由getter/setter方法决定的
根据bean的类型从IOC容器中获取bean的实例
<bean id="person1" class="com.bean.Person">
<property name="personName" value="张三" ></property>
<property name="personAge" value="18" ></property>
</bean>
<bean id="person2" class="com.bean.Person">
<property name="personName" value="小花" ></property>
<property name="personAge" value="18" ></property>
</bean>
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
//如果ioc容器中这个类型的bean有多个,查找就会报错
Person person = ioc.getBean(Person.class);
//这样使用 即便ioc容器中这个类型的bean有多个,查找也不会报错
Person person = ioc.getBean("person1",Person.class);
}
通过构造器为bean的属性赋值
调用有参构造器创建对象并赋值
<bean id="person1" class="com.bean.Person">
<constructor-arg name="personName" value="张三"></constructor-arg>
<constructor-arg name="personAge" value="18"></constructor-arg>
<!--此处可以省略name属性,但需要按照构造器参数的顺序指定value值-->
<constructor-arg value="张三"></constructor-arg>
<constructor-arg value="18"></constructor-arg>
<!--index="0" 为参数指定索引 从0开始-->
<constructor-arg value="张三" index="0"></constructor-arg>
<constructor-arg value="18" index="1"></constructor-arg>
<!--如果有多个有参构造器 使用type指定参数类型-->
<constructor-arg value="张三" index="0"></constructor-arg>
<constructor-arg value="18" index="1" type="java.lang.Integer"></constructor-arg>
</bean>
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
Person person = ioc.getBean("person1");
}
通过p名称空间为bean赋值
名称空间:在xml中名称空间是用来防止标签重复的
<!--使用p名称空间赋值时,需先导入p名称空间-->
<bean id="person1" class="com.bean.Person" p:personName="张三" p:personAge="18">
</bean>
为复杂类型的属性赋值
//实体类
public class Person{
private String name;
private int age;
private Car car;//Car是一个实体类
private List<Book> books;//Book是一个实体类
private Map<String,Object> maps;
private Properties properties;
}
<bean id="car" class="com.bean.Car">
<property name="carNmae" value="宝马"></property>
</bean>
<bean id="book" class="com.bean.Book">
<property name="bookNmae" value="西游记"></property>
</bean>
<bean id="person1" class="com.bean.Person">
<!--赋值为null-->
<property name="name">
<null/>
</property>
<!--ref="car" 这是一个严格的引用 person中的car跟 直接从容器中获取的car是一样的-->
<property name="car" ref="car"></property>
<!--为list类型赋值-->
<property name="books">
<list>
<!--内部bean 写id和不写id是一样的 外部获取不到-->
<bean id="book" class="com.bean.Book" p:bookName="西游记"></bean>
<ref bean="book"/>
</list>
</property>
<!--为map类型赋值-->
<bean id="maps">
<!--底层用的是LinkedHashMap-->
<map>
<!--一个entry代表一个键值对-->
<entry key="key1"value="value1"></entry>
<entry key="key2"value="value2"></entry>
<entry key="key3"value-ref="book"></entry>
<entry key="key4">
<!--内部bean无法用id获取 无论是否有id-->
<bean id="" class="com.bean.Car">
<property name="carName" value="宝马"></property>
</bean>
</entry>
</map>
</bean>
<!--为Properties类型赋值-->
<bean name="properties">
<!--properties 里面 所有的k=v都是String类型-->
<props>
<prop key="username">root</prop>
<prop key="username">123456</prop>
</props>
</bean>
</bean>
级联属性赋值
<bean id="car01" class="cam.bean.Car">
<property name="carName" value="宝马"></property>
</bean>
<bean id="person" class="cam.bean.Person">
<property name="car" ref="car01"></property>
<property name="car.carName" value="奔驰"></property>
</bean>
注意
- 级联属性可以修改属性的属性,但是原来的bean值将会被修改
通过继承实现bean配置信息的重用
<bean id="person1" class="com.bean.Person">
<property name="perName" value="张三"></property>
<property name="perAge" value="15"></property>
<property name="perGender" value="男"></property>
</bean>
<!--parent="" : 指定当前的bean的配置信息继承于哪个bean class=""可以省略不写 -->
<bean id="person2" class="com.bean.Person" parent="person1">
<property name="perName" value="张三"></property>
</bean>
<!--abstract="true" 表示这个bean只能被继承 不可以获取-->
<bean id="person3" class="com.bean.Person" parent="person1" abstract="true">
<property name="perGender" value="男"></property>
</bean>
IOC容器中改变bean的创建顺序
<!--原来是按照配置的顺序创建bean-->
<bean id="person" class="com.bean.Person"></bean>
<bean id="car" class="com.bean.Car"></bean>
<bean id="book" class="com.bean.Book"></bean>
<!-- depends-on="car,book" 改变bean的创建顺序-->
<bean id="person" class="com.bean.Person" depends-on="car,book"></bean>
<bean id="car" class="com.bean.Car"></bean>
<bean id="book" class="com.bean.Book"></bean>
bean的作用域
<bean id="book" class="com.bean.Book" scope=""></bean>
scope="" 设置作用域 默认所有的bean都是单实例的
-
prototype:多实例的
多实例的bean,容器启动默认不会去创建多实例的bean
只有在获取的时候才会创建这个bean
每次获取都会创建一个新的对象
-
singleton:单实例的 默认的
单实例的bean在容器启动完成之前就已经创建好对象,并保存在容器中了
单实例的bean从始至终 获取到的都是之前创建好的那个对象
request:在web环境下,同一个请求创建一个Bean实例(没用)
session:在web环境下,同一次会话创建一个Bean实例(没用)
静态工厂和实例工厂创建bean
静态工厂:工厂本身不用创建对象,通过静态方法调用,对象 = 工厂类.工厂方法名();
public class AirPlaneStaticFactory{
//这个方法是静态方法
public static AirPlane getAirPlane(String planeName){
AirPlane airplane = new AirPlane();
airplane.setPlaneName(planeName);
return airPlane;
}
}
<!--静态工厂(不需要创建工厂本身)
1.class指定静态工厂全类名,
2.factory-method 指定工厂方法,
3.constructor-arg 可以为方法传参 -->
<bean id="airPlane" class="com.factory.AirPlaneStaticFactory" factory-method="getAirPlane">
<constructor-arg name="planeName" value="大飞机1号" ></constructor-arg>
</bean>
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
//获取到的是飞机 并不是工厂
AirPlane airplane = ioc.getBean("airPlane");
}
实例工厂:工厂本身需要创建对象,
工厂类 工厂对象 = new 工厂类();
对象 =工厂对象.工厂方法名();
public class AirPlaneInstanceFactory{
//这个方法不是静态方法
public AirPlane getAirPlane(String planeName){
AirPlane airplane = new AirPlane();
airplane.setPlaneName(planeName);
return airPlane;
}
}
<!--实例工厂(需要创建工厂本身)-->
<bean id="airPlaneInstanceFactory" class="com.factory.AirPlaneInstanceFactory">
</bean>
<!--factory-bean="" 指定当前对象由哪个工厂创建 factory-method=""指定工厂方法-->
<bean id="airPlane" class="com.bean.AirPlane" factory-bean="airPlaneInstanceFactory" factory-method="getAirPlane">
<constructor-arg name="planeName" value="大飞机2号" ></constructor-arg>
</bean>
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
//获取到的是飞机 并不是工厂
AirPlane airplane = ioc.getBean("airPlane");
}
实现FactoryBean接口的工厂
FactoryBean 是Spring规定的一个接口,只要是这个接口的实现类 ,spring都认为是一个工厂,Spring会自动调用工厂方法创建实例
第一步:需要写一个实现了FactoryBean接口的类
public class MyFactoryBeanImpl implements FactoryBean<Book>{
//getObject:工厂方法 返回创建的对象
@Override
public Book getObject() throws Exception{
Book book = new Book();
book.setId(1);
return book;
}
//返回 创建的对象的类型
@Override
public Class<?> getObjectType(){
return Book.class;
}
//返回 是否是单例
//false : 不是单例 true: 是单例
@Override
public boolean isSingleton(){
return false;
}
}
第二步:在配置文件中进行注册
<!-- 注意!:无论 isSingleton()这个方法返回值是什么 ioc容器启动的时候不会创建这个实例-->
<bean id="myFactoryBeanImpl" class="com.factory.MyFactoryBeanImpl"></bean>
**注意 获取到的是book对象 **
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
//获取到的是book对象
Book book = ioc.getBean("myFactoryBeanImpl");
}
创建带有生命周期方法的bean
生命周期:bean的创建到销毁,我们可以自定义一些生命周期方法,spring在创建或销毁的时候会调用指定的方法
自定义初始化方法和销毁方法,但是不可以有参数 可以抛异常
-
单例Bean的生命周期
- (容器启动)构造器——>>>初始化方法——>>>(容器关闭)销毁方法
<bean id="book" class="com.bean.Book" destory-method="" init-method="" >
</bean>
-
多例Bean的生命周期
- (获取bean)构造器——>>>初始化方法——>>>(容器关闭)不会调用bean的销毁方法
<bean id="book" class="com.bean.Book" destory-method="" init-method="" scope="protorype" >
</bean>
spring管理连接池
数据库链接池作为单实例是最好的,一个项目就一个连接池,连接池里面管理很多链接,链接是直接从链接池里面拿
可以让Spring帮我们创建连接池对象 管理连接池
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
</bean>
spring管理连接池引用外部配置文件
jdbc.properties
#username=root
jdbc.username=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/test
jdbc.driverClass=com.mysql.jdbc.Driver
在ApplicationContext.xml中配置时 需要引入context命名空间
注意!
username 是spring中的一个关键字 在这里不可以使用username**
value="${jdbc.username}" 引号前后不能有空格
<!--加载外部配置文件的 classpath: 表示引用类路径下的配置文件-->
<context:property-placeholder location="classpath:jdbc.properties" />
<!--username 是spring中的一个关键字 在这里不可以使用username-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--<property name="user" value="${username}"></property>-->
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
基于xml的自动装配
**注意!自动装配仅限于自定义类型的属性 **
<bean id="car" class="com.bean.Car">
<property name="carName" value="宝马"></property>
<property name="color" value="白色"></property>
</bean>
<!--
autowire=""
default/no:不自动装配,不自动为car属性赋值
byName:以属性名作为id去容器中找到一个组件,给他赋值,如果找不到就赋值null
byType:以属性的类型作为查找依据去容器中找到这个组件,如果容器中有多个这样的类型会报错
constructor:按照有参构造器为car赋值
1.先按照有参构造器参数类型进行装配,没有就直接为组件装配null
2.如果按照类型找到了多个bean:以参数的名作为id继续装配,找不到就null
假设有一个List<Book> books属性,容器可以把容器中所有的book封装进list
-->
<bean id="person" class="com.bean.Person" autowire="default"></bean>
SpEL(Spring Expression Language)spring表达式语言
表达式语言
字面量:#{125}*
引用其他bean的某个属性值:#{car.carName}
引用其他bean:#{car}
调用非静态方法:#{对象.方法名(arg1,arg2)} eg:#{car.getCarName()}
调用静态方法:#{T(全类名).静态方法名(arg1,arg2)} eg: #{T(java.util.UUID).randomUUID().toString()}
<bean id="car" class="com.bean.Person">
<property name="carName" value="宝马"></property>
</bean>
<bean id="person" class="com.bean.Person">
<property name="age" value="#{12*5}"></property>
<property name="perName" value="#{car.carName}"></property>
<property name="car" value="#{car}"></property>
<property name="email" value="#{T(java.util.UUID).randomUUID().toString()}"></property>
<property name="testName" value="#{car.getCarName()}"></property>
</bean>
通过注解分别创建Controller、Dao、Service
spring有四个注解某个类上注解任何一个都可以讲这个组件加入到ioc容器中
-
@Controller
- 推荐给控制器层(servlet)的组件加这个注解
-
@Service
- 推荐给业务逻辑层的组件加这个注解 Service层
-
@Repository
- 推荐给数据库层(持久化层,dao层)的组件添加这个注解
-
@Component
- 给不属于以上几层的组件添加这个注解
使用注解将组价快速加入到容器中需要几步
给要添加的组件上标上以上四个注解之一
告诉spring,自动扫描加了注解的组件,依赖context命名空间
<!--自动组件扫描-->
<!--base-package="" 指定扫描的基础包-->
<context:component-scan base-package=""></context:component-scan>
-
使用context:exclude-filter指定扫描包时不包含的类
扫描的时候可以排除一些不要的组件
-
type="annotation":按照注解进行排除-->标注了指定注解的组建不要
expression="":注解的全类名
-
type="assignable":按照类进行排除
expression="":类的全类名
type="aspectj":aspectj表达式(很少用)
type="custom":自定义一个TypeFilter;自己写代码决定哪些使用(很少用)
type="regex":正则表达式(很少用)
<context:component-scan base-package="">
<!--表示标注了@controller的注解不进行扫描-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--表示com.controller.BookController这个类不进行扫描-->
<context:exclude-filter type="assignable" expression="com.controller.BookController"/>
</context:component-scan>
-
使用context:include-filter指定扫描包时包含的类
指定只扫描哪些组件,
注意!需要使用 use-default-filters="false" 取消默认行为
-
type="annotation":按照注解进行排除-->标注了指定注解的组建不要
expression="":注解的全类名
-
type="assignable":按照类进行排除
expression="":类的全类名
type="aspectj":aspectj表达式(很少用)
type="custom":自定义一个TypeFilter;自己写代码决定哪些使用(很少用)
type="regex":正则表达式(很少用)
<context:component-scan base-package="" use-default-filters="true">
<!--表示只有标注了@controller的注解进行扫描-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--表示只有com.controller.BookController这个类进行扫描-->
<context:include-filter type="assignable" expression="com.controller.BookController"/>
</context:component-scan>
- 一定要导入AOP包 支持加注解模式
这里bean的id默认就是类名首字母小写
使用注解加入到容器中的组件,和使用配置加入到容器中的组件行为都是默认一样的:
组件的id,默认就是类名首字母小写
组件的作用域,默认就是单例的
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
Object bean = ioc.getBean("");
}
注意,如果想要修改默认行为
- 修改bean的Id
@Repository("你想要的id名") 四个注解都是一样的
- 修改作用域
@Scope(value="prototype")
使用@Autowired注解实现自动装配
@Autowired为bookservice自动赋值
@Controller
public class BookServlet{
@Autowired
private BookService bookService;
}
@Autowired原理
先按照类型去容器中找到对应的组件;bookService = ioc.getBean("BookService.class");
如果找到了就直接赋值
没找到就抛出异常
-
如果找到多个,就按照变量名作为id继续匹配;
如果匹配上就赋值
如果没有匹配上,就抛出异常。
注意!这里可以使用@Qualifier注解 作用是可以指定一个名称作为id,取消spring使用变量名作为id的行为。eg:@Qualifier(“newbookservice“)
如果找到就装配
如果找不到,就抛出异常
注意!
可以使用required=false 来解决 如果@Autowired找不到指定bean 就赋值为null
@Autowired(required=false)
在方法上使用@Autowired注解并在形参位置使用@Qualifier
-
方法上有@Autuwired
这个方法会在容器创建的时候自动运行
这个方法的每一个参数都会自动注入值
@Autowired
public void methods(@Qualifier("newbookservice")BookService bookservice){
System.out.println("")
}
@Autowired 和 @Resource的区别
- @Autowired 和 @Resource 和 @Inject都是自动装配的意思
@Autowired最强大:Spring自己的注解
@Resource:j2ee java的标准 ,扩展性更强 因为如果我们切换另外一个容器框架@Resource还是可以使用,但是@Autowired离开了spring 就不能使用了
Spring的单元测试
导包 导入spring-test-4.0.0
@ContextConfiguration使用这个注解来指定spring的配置文件的位置
@RunWith()指定用哪种驱动进行单元测试。默认就是junit
@RunWith(SpringJUnit4ClassRunner.class)
使用spring的单元测试模块来执行标注了@Test注解的测试方法
以前的@Test只是由JUnit执行
@ContextConfiguration(locations="classpath:spring.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest{
ApplicationContext ac = null;
@Autowired
BookServlet bookServlet;
}
泛型依赖注入
泛型依赖注入,注入一个组件的时候,他的泛型也是参考标准
- 首先BookDao和UserDao都继承BaseDao<T>这个抽象类并实现抽象方法
public abstract class BaseDao<T>{
public abstract void save();
...
}
@Repository
public class BookDao extends BaseDao<Book>{
@Override
public void save(){
...
}
...
}
@Repository
public class UserDao extends BaseDao<User>{
@Override
public void save(){
...
}
...
}
- 然后BookService和UserService都继承了BaseService<T>,并且在继承的时候,确定了泛型的类型。
@Service
public class BookService extends BaseService<Book>{
...
}
@Service
public class UserService extends BaseService<User>{
...
}
public class BaseService<T>{
@Autowired
private BaseDao<T> baseDao;
public void test(){
baseDao.save();
}
...
}
流程分析
- 当在BookService的对象调用test()方法的时候,虽然BaseService没有在容器中,但是BookService在容器中,所以继承了BaseService的BookService会自动为baseDao属性赋值,此时相当于BookService中 有一个 BaseDao<Book> baseDao; 它便会去容器中找一个BaseDao<Book>类型的bean为baseDao赋值。所以将容器中的BookDao赋值给baseDao,当BookService调用test()方法时其实调用的就是bookDao中的save()方法。
@Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
BookService bookService = ioc.getBean(BookService.class);
bookService.test();
}
IOC总结
-
IOC是一个容器,帮我们管理所有的组件
依赖注入:@Autowired:自动注入
某个组件要使用spring提供的更多功能就必须要加入容器中
容器启动,创建所有单实例bean
@Autowired自动装配的时候,是从容器中找到这些符合要求的bean