Spring 事物与传播行为

  1. 业务bean加上@Transactional默认所有方法都是Propagation.REQUIRED类型,这种类型的情况,当发生unchecked Exception的时候,整个方法的业务都会回滚
  2. 如果要让checked Exception也能回滚,需要设置rollbackForClassName = {“Exception名字”}
    @Transactional(propagation = Propagation.REQUIRED, rollbackForClassName = {"Exception"})
    	public void save(Persons person) throws Exception {
    		template.update("insert into persons(name) values(?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR});
    		throw new Exception();
    	}

    相应的也可以让runtimeException不回滚的属性noRollbackForClassName,比如配上runtimeException,那么runtimeException就不会回滚了
  3. 在增删改里面,特别是一个业务bean里面有多个dao增删改操作是需要使用事物,但在查询方法里面,没有必要使用事物,可以用Propagation.NOT_SUPPORTED表示这个方法不支持事物
  4. readOnly:一般是查询方法使用,用了NOT_SUPPORTED就不需要用这个属性
  5. timeout:事物的超时时间,默认30秒
  6. isolate:数据库的隔离级别

事物的传播属性:

  • REQUIRED:表示如果方法运行时已经在一个事物中,那么就会加入这个事物,如果没有在事物中,就新起一个事物

    比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,

    ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA

    的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

    这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被

    提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚

  • NOT_SUPPORTED:表示方法内不会启动事物,如果在其他事物方法里面调用这个方法,那个事物方法里面的事物将被挂起,直到NOT_SUPPORTED方法完成后才恢复。
  • REQUIRED_NEW:不管是否存在事物,都会开启一个新事物,如果已经开启一个事物,原有的事物会被挂起直到REQUIRED_NEW的方法调用结束原事物猜恢复

    比如:如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,

    那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,

    他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在

    两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,

    如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

  • MANDATORY:MANDATORY方法只能在存在事物的方法中被调用,否则会抛出异常
  • SUPPORTS:业务方法被事物方法调用,业务方法成为原方法事物的一部分,如果被非事物方法调用,则方法在没有事物的环境下执行
  • NEVER:不能在事物方法中被调用,否则会抛出异常
    设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,
    那么ServiceB.methodB就要抛出异常了。
  • NESTED:如果活动的事物存在,会运行在这个嵌套事务中,如果没有活动事物,就按照REQUIRED来执行,内部的这个NESTED事物回滚不影响外部事物,只对DataSourceTransactionManager这个事物配置类起作用
    比如
    update1();
    update2(); — NESTED
    update3();
    如果update2()失败回滚,update1,update3将还是会正常执行

Spring 集成JDBC和事物配置

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
       <context:component-scan base-package="com.practise.spring" />
       <context:property-placeholder location="jdbc.properties"/>
	   <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
	       <property name="driverClassName" value="${driverClassName}" />
	       <property name="url" value="${url}" />
	       <property name="username" value="${username}" />
	       <property name="password" value="${password}" />
	       <property name="initialSize" value="${initialSize}" />
	       <property name="maxActive" value="${maxActive}" />
	       <property name="maxIdle" value="${maxIdle}" />
	       <property name="minIdle" value="${minIdle}" />
	   </bean>
	   
	   <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	       <property name="dataSource" ref="dataSource"></property>
	   </bean>
	   
	   <tx:annotation-driven transaction-manager="txManager" />
</beans>

  • 需要加上事物的命名空间和xsd
  • id=”dataSource”的bean是为apache dbcp里面要使用的BasicDataSource类的装配
    initialSize:连接池启动时的初始值
    maxActive:连接池的最大值
    maxIdle:最大空闲值,经过一个连接高峰后,连接池会逐渐释放连接,这个属性的值表示连接池释放到这个属性配置值结束
    minIdle:最小空闲值,当空闲的连接少于这个配置的数值,连接池就会去申请一些连接达到这个配置的连接数,防止高峰期来不及申请
  • <context:property-placeholder location=”jdbc.properties”/>:可以用这个标签把dataSource里面的属性配置到properties文件引用进来
  • 讲dataSource注入到spring的DataSourceTransactionManager这个类里面,然后通过<tx:annotation-driven transaction-manager=”txManager” />打开annotation,以后在业务bean或者数据访问层加上@Transactional注解事物就配置成功了

@Service("personService")
@Transactional
public class PersonServiceImpl implements IPersonService {
	private JdbcTemplate template;
	
	/**
	 * @param dataSource the dataSource to set
	 */
	@Resource
	public void setDataSource(DataSource dataSource) {
		template = new JdbcTemplate(dataSource);
	}

	@Override
	public void save(Persons person) {
		template.update("insert into persons(name) values(?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR});
	}

	@Override
	public void update(Persons person) {
		template.update("update persons set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER});
	}

	@Override
	public void delete(Integer personId) {
		template.update("delete from persons where id = ?", new Object[]{personId}, new int[]{Types.INTEGER});
	}

	@Override
	public Persons getPerson(Integer personId) {
		return (Persons)template.queryForObject("select * from persons where id = ?", new Object[]{personId}, new int[]{Types.INTEGER}, new PersonRowMapper());
	}

	@Override
	public List<Persons> getPersons() {
		return (List<Persons>)template.query("select * from persons", new Object[]{}, new PersonRowMapper());
	}
}

用JDBCTemplate更方便的对数据库进行操作,注意的是查询的时候需要写一个回调
public class PersonRowMapper implements RowMapper {
	@Override
	public Object mapRow(ResultSet rs, int index) throws SQLException {
		Persons person = new Persons();
		person.setId(rs.getInt("id"));
		person.setName(rs.getString("name"));
		return person;
	}
}

获取查询记录或者记录集合

Spring 配置文件实现AOP

public class MyInterceptorWithoutAnnotation {

	public void beforeAdvice(){
		System.out.println("前置通知");
	}

	public void afterAdvice(){
		System.out.println("后置通知");
	}

	public void finalAdvice(){
		System.out.println("最终通知");
	}

	public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
		Users user = new Users();
		try {
			System.out.println("环绕通知");
			if (!"".equals(user.getName())) {
				return pjp.proceed();
			}
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
}

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
       <!--context:component-scan base-package="com.practise.spring" /-->
       <aop:aspectj-autoproxy />
       <bean id="personDao" class="com.practise.spring.dao.impl.PersonDao" />
       <bean id="personService" class="com.practise.spring.service.impl.PersonServiceImpl">
           <property name="personDao" ref="personDao"></property>
       </bean>
       <bean id="myInterceptor" class="com.practise.spring.interceptor.MyInterceptorWithoutAnnotation" />
       
       <aop:config>
           <aop:aspect id="asp" ref="myInterceptor">
               <aop:pointcut expression="execution(* com.practise.spring.service.impl.PersonServiceImpl.*(..))" id="mycut"/>
               <aop:before pointcut-ref="mycut" method="beforeAdvice"/>
               <aop:after-returning pointcut-ref="mycut" method="afterAdvice"/>
               <aop:after pointcut-ref="mycut" method="finalAdvice"/>
               <aop:around pointcut-ref="mycut" method="aroundAdvice"/>
           </aop:aspect>
       </aop:config>
</beans>

Spring 通过注解实现AOP

要配置aop功能,需要配置如下:

1. 添加aop需要的包aspectjrt.jar, aspectweaver.jar,cglib-nodep-*.jar

2. beans.xml中加入aop的命名空间,加上打开aop功能

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
       <context:component-scan base-package="com.practise.spring" />
       <aop:aspectj-autoproxy />
</beans>

写拦截器并配置
@Aspect
@Component
public class MyInterceptor {
	@Pointcut("execution(* com.practise.spring.service.impl.PersonServiceImpl.*(..))")
	private void anyMethod(){}
	
	@Before("anyMethod() && args(name)")
	public void beforeAdvice(String name){
		System.out.println("前置通知" + name);
	}
	@AfterReturning(pointcut = "anyMethod()", returning = "result")
	public void afterAdvice(String result){
		System.out.println("后置通知" + result);
	}
	@After("anyMethod()")
	public void finalAdvice(){
		System.out.println("最终通知");
	}
	@Around("anyMethod()")
	public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
		Users user = new Users();
		try {
			System.out.println("环绕通知");
			if (!"".equals(user.getName())) {
				return pjp.proceed();
			}
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
}

  1. 写拦截器的时候不要忘了写@Component bean装配注解
  2. 定义切入点方便定义通知的时候引用,用来说明哪些类和方法需要定义切面通知

    切入点的定义规则:

    @Pointcut(“execution(* com.practise.spring.service.impl..*.*(..))”)

    private void anyMethod(){}

    第一个*:返回值为任意值的
                     (1)表示拦截返回值为String的写法:execution(java.lang.String com.practise.spring.service.impl..*.*(..))
                     (2)返回值不为void的:execution(!void com.practise.spring.service.impl..*.*(..))
    ..:表示com.practise.spring.service.impl这个包和他的子包集合

    *.*:表示任意类的任意方法

    (..):所有参数
            (1)表示第一个参数为String的写法:execution(* com.practise.spring.service.impl..*.*(java.lang.String,..))

  3. @Before:前置通知的注解

    @Before(“anyMethod() && args(name)”):如果有参数需要加上args(name)

  4. @AfterReturning:后置通知的注解

    (pointcut = “anyMethod()”, returning = “result”):returning用来在拦截的方法中取到返回值

  5. @After:最终通知
  6. @Around:环绕通知

Spring AOP概念的简单说明

先贴出自己写的Proxy的代码方便说明

public class ProxyFactory implements InvocationHandler {
	private Object target;
	
	public Object createProxyInstance(Object obj){
		this.target = obj;
		return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
	}
	
	@Override // 整个方法就相当于环绕通知
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Users user = new Users();
		try{
			if(user.getName() != null && !"".equals(user.getName())){
				// --advice() 前置通知
				return method.invoke(target, args);
				// --afteradvice() 后置通知
			}else{
				return null;
			}
		}catch(Throwable e){
			// exception advice() 异常通知
		}finally{
			// final advice() 最终通知
		}
	}
	
}

public void test() {
		PersonServiceImpl personService = new PersonServiceImpl();
		ProxyFactory factory  = new ProxyFactory();
		IPersonService service = (IPersonService)factory.createProxyInstance(personService);
		service.save();
	}

  • Aspect(切面):指对我们需要对代理类的原类的方法进行某些横切关注点的抽象,比如要检查原方法是否有权限调用。
  • joinpoint(连接点):连接点就是被拦截的原方法,spring只支持方法的连接点,实际joinpoint还支持Filed和构造方法。
  • Pointcut(切入点):表示对哪些连接点进行拦截
  • Advice(通知):通知类型如上面的注释
  • Target(目标对象):就是我们的PersonServiceImpl这个原类
  • Weave(织如):就是通过代理模式创建的代理业务bean,这个过程就是织入
  • Introduction(引入):在不改变原类代码的情况下,为运行期的类动态添加属性和方法

Spring内部拦截的时候会判断,如果原类实现了接口,就用Proxy来创建代理对象,如果没有实现接口就用CGLib来创建。

 

Spring 自动扫描和管理bean

这个功能主要是因为我们需要在spring配置的xml文件中配置很多<bean>节点来完成bean装配,如果bean很多的情况下,即使光用注解注入还是要配置很多bean,这个功能可以让Spring通过注解寻找到classpath下的bean然后自动装配.

配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd">
       <context:component-scan base-package="com.practise.spring" />
</beans>

@Service("personService")
@Scope("prototype")
public class PersonServiceImpl implements IPersonService {
	@Resource
	private IPersonDao personDao;

	public void setPersonDao(IPersonDao personDao) {
		this.personDao = personDao;
	}

	@PostConstruct
	public void init(){
		System.out.println("初始化了");
	}
	
	public PersonServiceImpl(){
		System.out.println("实例化了");
	}
	
	@Override
	public void save(){
		System.out.println("进入service的save方法");
		personDao.save();
	}
	
	@PreDestroy
	public void close(){
		System.out.println("资源释放了");
	}
}

各注解的使用范围:

  • @Service:标注业务层组件
    如果不加上名称的话,getBean(默认取注入的类名,首字母小写)
    @Scope:可以设置singlton还是prototype
  • @Controller:标注控制层组件(如struts的action)
  • @Repository:标注数据访问层(如dao)
  • @Component:泛指组件,如果不好归类的时候就用这个注解

Spring 不推荐使用的Spring bean自动注入

<bean id="personService" class="com.practise.spring.service.impl.PersonServiceImpl" autowire="byType" />

将会根据personService的属性寻找配置的bean,自动注入

autowire的几种方式:

  • byType: 根据类型自动注入,当发现发现有多个匹配,将报异常
  • byName
  • constructor
  • autodetect

不推荐使用这种方式,因为一旦在用这种方式,会自动为所有属性进行注入,而且多个匹配还会有冲突.

Spring 模拟Spring写简单的自定义注解和注解解析

public class MyClassPathXMLApplication {
	private List<DefinedBean> beanList = new ArrayList<DefinedBean>();
	private List<DefinedProperty> propList = new ArrayList<DefinedProperty>();
	private Map<String, Object> beanMap = new HashMap<String, Object>();
	
	public MyClassPathXMLApplication(String fileName){
		readXML(fileName);
		produceBeans();
		//injecting();
		annotationInjection();
	}

	private void annotationInjection() {
		try {
			Set<Entry<String, Object>> beanSet = beanMap.entrySet();
			for (Entry<String, Object> bean : beanSet) {

				PropertyDescriptor[] pds = Introspector.getBeanInfo(
						bean.getValue().getClass()).getPropertyDescriptors();
				for(PropertyDescriptor pd : pds){
					Method setter = pd.getWriteMethod();
					if(setter != null && setter.isAnnotationPresent(MyResource.class)){
						String name = setter.getAnnotation(MyResource.class).name();
						if(name != null && !"".equals(name)){
							if(beanMap.get(name) != null){
								Object subBean = beanMap.get(name);
								setter.setAccessible(true);
								setter.invoke(bean.getValue(), subBean);
							}
						}else{
							if(name == null || "".equals(name)){
								for(Entry<String, Object> bean2 : beanSet){
									if(pd.getPropertyType().isAssignableFrom(bean2.getValue().getClass())){
										setter.setAccessible(true);
										setter.invoke(bean.getValue(), bean2.getValue());
									}
								}
							}
						}
					}
				}
				
				Field[] fields = bean.getValue().getClass().getDeclaredFields();
				for(Field field : fields){
					if(field.isAnnotationPresent(MyResource.class)){
						String name = field.getAnnotation(MyResource.class).name();
						if(name != null && !"".equals(name)){
							if(beanMap.get(name) != null){
								field.setAccessible(true);
								field.set(bean.getValue(), beanMap.get(name));
							}
						}else{
							if(name == null || "".equals(name)){
								for(Entry<String, Object> bean2 : beanSet){
									if(field.getType().isAssignableFrom(bean2.getValue().getClass())){
										field.setAccessible(true);
										field.set(bean.getValue(), bean2.getValue());
									}
								}
							}
						}
					}
				}
			}
		} catch (IntrospectionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	private void readXML(String fileName) {
		SAXReader reader = new SAXReader();
		URL xmlPath = this.getClass().getClassLoader().getResource(fileName);
		try {
			Document doc = reader.read(xmlPath);
			Map<String, String> nsMap = new HashMap<String, String>();
			nsMap.put("ns", "http://www.springframework.org/schema/beans");
			XPath xpath = doc.createXPath("//ns:beans/ns:bean");
			xpath.setNamespaceURIs(nsMap);
			List<Element> elemList = xpath.selectNodes(doc);
			for(Element elem : elemList){
				DefinedBean bean = new DefinedBean();
				bean.setId(elem.attributeValue("id"));
				bean.setClassName(elem.attributeValue("class"));
				XPath propXpath = elem.createXPath("ns:property");
				propXpath.setNamespaceURIs(nsMap);
				List<Element> propElemList = propXpath.selectNodes(elem);
				propList.clear();
				for(Element propElem : propElemList){
					DefinedProperty prop = new DefinedProperty();
					prop.setName(propElem.attributeValue("name"));
					prop.setRef(propElem.attributeValue("ref"));
					propList.add(prop);
					bean.setProperties(propList);
				}
				beanList.add(bean);
			}
		} catch (DocumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void produceBeans(){
		for(DefinedBean bean : beanList){
			try {
				beanMap.put(bean.getId(), Class.forName(bean.getClassName()).newInstance());
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void injecting(){
		for(DefinedBean bean : beanList){
			Object beanObj = beanMap.get(bean.getId());
			try {
				PropertyDescriptor[] pds = Introspector.getBeanInfo(beanObj.getClass()).getPropertyDescriptors();
				if(bean.getProperties() == null || bean.getProperties().size() == 0){
					continue;
				}
				for(DefinedProperty prop : bean.getProperties()){
					for(PropertyDescriptor pd : pds){
						if(prop.getName().equals(pd.getName())){
							Object propObj = beanMap.get(prop.getRef());
							Method setterMethod = pd.getWriteMethod();
							setterMethod.invoke(beanObj, propObj);
						}
					}
				}
			} catch (IntrospectionException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		}
	}
	
	public Object getBean(String key){
		return beanMap.get(key);
	}
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface MyResource {
	String name() default "";
}

Spring通过注解的方式实现注入

使用注解装配的步骤

  1. 在配置xml中加入spring-context的命名空间
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-2.5.xsd">
  2.  加入标签,这个配置隐式的注册了AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,PresistenceAnnotationBeanPostProcessor,RequiredAnnotationBeanPostProcessor
  3. 在要注入的属性或者set方法上面加上@Resource标签

@Resource,@Autowired说明

  1. @Resource(name=’personDao’),这种情况spring会根据名字找personDao,如果找不到会报错
  2. @Resource,没有配置name属性的情况会先根据属性的名称来找,如果找不到再类型来找,如果类型匹配,也可以实现注入
  3. @Autowired默认是按照类型来寻找,如果希望用名称来装配,可以加上@Qualifier(name=”personDao”)
  4. @Autowired有个required属性,默认为true,表示如果找不到相应的注入类型,将会抛一场,如果设置为false就不会有一场,该等待注入对象为null.