前言
本文将分别对IoC和AOP的内容进行一定的扩展,使开发更加简便,体验Spring框架的灵活与强大。这些不同的实现方式都有不同的适应场合,大家可根据实际场景进行比较,做到灵活运用。
一、IoC扩展
扩展1——构造注入
在之前,我们通过属性的setter方法对类的属性进行注入赋值,这种方式叫设值注入。Spring还提供了通过构造函数为属性赋值的方式,称为构造注入。
使用<constructor-arg>元素表示构造方法的一个参数,使用时不区分先后顺序(类型不重复的情况下)。当构造函数的参数中出现混淆、无法区分时,比如两个String类型的参数,这时我们可以指定<constructor-arg>元素的index属性来标明此元素代表构造函数中的第几个参数,index下标从0开始。<constructor-arg>元素还可指定type属性来指定参数的类型,避免参数之间的混淆。
构造注入的时效性较好,在对象实例化时就得到所依赖的对象,便于在构造函数中(初始化对象的过程中)使用所依赖的对象,但是灵活性不足。设值注入的方式较灵活,但时效性不足,并且大量属性的setter方法增加了类的复杂性。Spring并不倾向或提倡哪种注入方式,大家根据应用场景灵活选择。
扩展2——使用P命名空间实现属性注入
Spring从2.0版本开始采用schema形式的配置文件,使用不同的命名空间管理不同类型的配置,使得配置文件更灵活、更具扩展性。Spring基于schema的配置方案提供了简便的配置方法,大大简化了配置的工作量。接下来我们介绍一下比较常用的P命名空间。
P命名空间的特点是使用<bean>元素的属性而非子节点的形式配置Bean的属性,从而简化配置。例如
对于一个类中的普通类型字段,比如String、int什么的,我们可以直接p:属性名="值"的方式进行赋值,这样就不用写<property>子元素了,大大简化了配置。对于复杂类型,比如引用了其他的实体类(Bean组件),通过p:属性名-ref="所引用Bean的id"的方式进行赋值。
注:不用忘了在Spring配置文件中引入P命名空间
xmlns:p="http://www.springframework.org/schema/p"
扩展3——注入不同数据类型
Spring提供了不同的标签来实现各种不同类型参数的注入,这些标签对于设值注入、构造注入都适用。在接下来的介绍中,只以设值注入的方式演示,对于构造注入,是一样的,只需要把这些标签应用到<constructor-arg>元素中即可。
1、注入直接量(基本数据类型、字符串)
<property name="属性名" value="值" />
或者
<property name="属性名">
<value>值</value>
</property>
如果属性值中包含了XML中的特殊字符(&、<、>等),则注入时需要进行处理。可使用<![CDATA[特殊符号]]>进行标记,也可将特殊字符进行转义引用。XML中特殊字符的转义请自行查阅资料。
2、引用其他Bean组件
Spring中定义的Bean组件可以相互引用,从而建立依赖关系。只不过是将<property>元素的value换成ref。
<property name="属性名" ref="其他Bean的id" />
或者
<property name="属性名">
<ref bean="其他Bean的id" />或者<ref local="其他Bean的id" />
</property>
通过<ref />子标签的方式引用,可指定local也可指定bean,二者都是用来引用其他Bean组件的id的。区别在于,local属性只在当前配置文件中查找Bean的id,而bean属性可以在其他配置文件中检索Bean的id,范围更广。
3、定义内部Bean
如果一个Bean组件仅在一处使用,并且我们不希望它被其他Bean引用,那么我们可以将这个Bean组件定义在某个包含它的组件的内部,作为内部Bean存在。内部Bean只能在包含它的Bean组件中使用,无法被其他Bean访问。
<bean id="printer" class="Print.Printer">
<property name="ink">
<bean class="Print.ColorInk"/>
</property>
<property name="paper" ref="paperA4"/>
</bean>
4、注入集合类型的属性
对于List或数组类型的属性,可以使用<list>标签进行注入,<value>子标签指定其值。如果List集合的泛型是实体类,同样可以引用其他Bean组件,此时将<value>标签换为<ref />即可。
<bean id="myStudent" class="Entity.Student">
<property name="stringList">
<list>
<value>小明</value>
<value>小张</value>
</list>
</property>
</bean>
对于Set类型的属性,可以使用<set>元素进行注入,同<list>元素的用法,<value>标签、<ref />标签随意嵌套使用。
对于Map类型的属性,用以下方式注入
<property name="map">
<map>
<entry>
<key><value>one</value></key>
<value>1</value>
</entry>
<entry>
<key><value>two</value></key>
<value>2</value>
</entry>
</map>
</property>
其中<key>代表元素的键,直属<value>是元素的值。如果元素的键或值是其他Bean组件,将<value>换成<ref />即可。
5、注入Properties类型的属性
<property name="proTest">
<props>
<!--定义Properties中的键值对,一般都是字符串类型-->
<prop key="football">足球</prop>
<prop key="basketball">篮球</prop>
</props>
</property>
6、注入空字符串或null
<value></value>指定空字符串,什么都不写
<null />指定为null
扩展4——使用注解实现IoC的配置
在之前我们学习了多种和Spring IoC相关的配置技巧,这些技巧都是基于XML形式的配置文件进行的。除了XML配置文件,Spring从2.0版本开始引入注解的配置方式,将Bean组件的配置信息与Bean实现类结合在一起,减少配置文件的代码量。
使用注解标注Bean,需要在Spring配置文件中引入context命名空间,然后使用<component-scan>标签扫描指定包下的类,base-package属性指定要扫描的包,多个包用逗号隔开,Spring会扫描这些包并获取使用注解标注的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:p="http://www.springframework.org/schema/p"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="DaoImpl,ServiceImpl"/>
</beans>
1、使用注解定义Bean
@Component("userDao") //通过注解定义了一个userDao的bean组件,相当于<bean id='userDao' class='DaoImpl.UserDaoImpl'/>
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("数据层插入成功");
}
}
以上代码通过注解定义了一个Bean组件,@Component("")就相当于在XML配置文件中的<bean>元素,引号中定义的名字即为该Bean的id,而被注解的类就是该Bean的类型。
@Component是把普通的POJO实例化到Spring容器中,可以说是万能的,但是为了规范和更加清晰,Spring针对不同的层提供了特殊的注解:
@Repository用于标注DAO层的类
@Service用于标注业务层的类
@Controller用于标注控制器类
使用特定的注解使组件的用途更清晰,查看起来也比较方便,并且Spring可能在以后的版本中会对不同的注解添加不同的特殊功能,所以推荐使用特定的注解来标注特定的实现类。
2、实现注解装配Bean组件
Spring提供了@Autowired注解实现Bean组件的装配,示例如下
@Service("userService") //定义一个服务层的bean
public class UserServiceImpl implements UserService {
@Autowired //使用此注解为属性userDao注入依赖
private UserDao userDao;
@Override
public void addUser() {
System.out.println("业务逻辑层的插入");
userDao.addUser();
}
}
以上代码我们用@Service注解标注了一个业务层的Bean,然后使用@Autowired注解为userDao属性注入所依赖的对象,此时Spring将直接对userDao属性进行赋值,可省略setter方法。
@Autowired采用按类型匹配的方式为属性自动装配合适的依赖对象,即它会在Spring容器中自动查找类型是UserDao的Bean组件,然后注入给userDao属性。若容器中有多个Bean的类型可匹配,或者我们想直接通过Bean组件的id定位到这个Bean并且注入给我们的属性,可以使用@Qualifier注解指定所需Bean的名称,如下
@Service("userService") //定义一个服务层的bean
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("userDao") //将Spring容器中名为userDao的bean组件注给此属性
private UserDao userDao;
@Override
public void addUser() {
System.out.println("业务逻辑层的插入");
userDao.addUser();
}
}
3、对方法的参数使用@Autowired注解
@Autowired注解不仅可以对类的属性进行标注,还可以对方法的入参进行标注,实现对参数的装配,比如应用在构造函数上,相当于构造注入。
@Service("userService") //定义一个服务层的bean
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public UserServiceImpl(@Qualifier("userDao") UserDao userDao){
this.userDao=userDao;
}
@Override
public void addUser() {
System.out.println("业务逻辑层的插入");
userDao.addUser();
}
}
4、@Autowired注解使用扩展
在我们使用@Autowired注解进行装配时,如果Spring容器中找不到匹配的Bean组件,则会抛出异常。此时如果这个依赖不是必须的,为了避免异常,可将required属性设置为false。此属性的默认值是true,认为必须找到相关的Bean完成装配,否则抛出异常。
@Autowired(required = false)
另外,如果对集合类型的属性或者方法参数使用@Autowired注解进行装配,Spring会将容器中所有和集合元素类型相匹配的Bean组件注入给该集合。如以下示例,Spring会将所有Student类型的Bean注入给studentList属性。
@Autowired(required = false)
private List<Student> studentList;
5、使用Java标注注解完成装配
除了使用@Autowired注解完成装配,Spring还支持使用JSR中定义的@Resource注解实现组件的装配,该标准注解也能对类的成员变量或者setter方法入参进行注入。
JSR全称是Java Specification Requests,即Java规范提案,用来规范Java语言功能和接口的标准,详情请自行查阅资料。
@Resource有一个name属性,Spring将这个name属性值解释为要注入的Bean的id,即指定某个Bean的id。
@Service("userService") //定义一个服务层的bean
public class UserServiceImpl implements UserService {
@Resource(name="userDao") //采用标准注解完成装配 name即为bean组件的名称
private UserDao userDao;
@Override
public void addUser() {
System.out.println("业务逻辑层的插入");
userDao.addUser();
}
}
如果没有显式指定要注入的Bean的id,@Resource注解将根据字段名或setter方法名产生一个默认的名称。如果此注解用于字段,则使用该字段名作为name属性值;如果注解应用于setter方法,则使用setter方法得到的属性名,如下
@Resource //查找id为userDao的Bean组件
private UserDao userDao;
@Resource //查找名为userDao的Bean
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
如果没有指定name属性值,并且无法以默认的name属性值找到相匹配的Bean组件,@Resource注解将转换为按类型匹配的方式进行装配,就像@Autowired一样。
二、AOP使用扩展
扩展1——使用注解定义切面
使用注解定义切面,就要先介绍一下AspectJ。AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP语法,它有一个专门的编译器用来生成遵守字节编码规范的Class文件,能够在编译期提供代码的织入。
@Aspect注解是AspectJ 5推出的功能,使用JDK5.0注解技术和正规的AspectJ切点表达式语言描述切面。所以,使用注解定义切面,你的JDK版本必须是5.0或以上版本。Spring通过集成AspectJ实现了以注解的方式定义切面,大大减少了配置文件的工作量。下面通过一篇代码举例说明。
package AOP;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect //使用@Aspect定义一个切面
public class MyLogger {
private static final Logger log=Logger.getLogger(MyLogger.class);
@Before("execution(public void *(..))") //使用@Before注解定义前置增强
public void before(JoinPoint joinPoint){
log.info("在"+joinPoint.getTarget()+"的"+joinPoint.getSignature().getName()+"方法之前执行了before()方法");
}
@AfterReturning(pointcut = "execution(public void *(..))",returning = "result") //使用@AfterReturning注解定义后置增强
public void afterReturning(JoinPoint joinPoint,Object result){
log.info("在"+joinPoint.getTarget()+"的"+joinPoint.getSignature().getName()+"方法之后执行了afterReturning()方法");
log.info("方法返回值是"+result);
}
}
首先,我们需要使用@Aspect注解将一个类定义成切面,然后使用@Before注解将某个方法定义为前置增强,可在括号内指定切入点表达式。使用@AfterReturning注解定义后置增强,同样可以指定切入点表达式。如果后置增强需要获取目标方法的返回值,那么我们就在这个方法中多声明一个参数,然后在注解中指定returning属性的值为这个参数的名称,Spring就会将目标方法的返回值赋给指定名称的参数,如以上示例我们将目标方法的返回值赋给了参数result。
在以上示例中,我们对@Before注解和@AfterReturning注解分别指定了各自的切入点,对于相同的切入点,我们可以统一定义,然后在注解中调用,比如以下代码。
package AOP;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect //使用@Aspect定义一个切面
public class MyLogger {
private static final Logger log=Logger.getLogger(MyLogger.class);
@Pointcut("execution(public void *(..))") //定义一个切入点,作为切入点签名的方法返回值必须是void类型
public void pointcut(){}
@Before("pointcut()") //使用@Before注解定义前置增强,并将切入点签名指定为方法pointcut()
public void before(JoinPoint joinPoint){
log.info("在"+joinPoint.getTarget()+"的"+joinPoint.getSignature().getName()+"方法之前执行了before()方法");
}
@AfterReturning(pointcut = "pointcut()",returning = "result") //使用@AfterReturning注解定义后置增强
public void afterReturning(JoinPoint joinPoint,Object result){
log.info("在"+joinPoint.getTarget()+"的"+joinPoint.getSignature().getName()+"方法之后执行了afterReturning()方法");
log.info("方法返回值是"+result);
}
}
切入点表达式用@Pointcut注解来表示,而切入点签名由一个普通的方法定义来提供。需要注意的是,作为切入点签名的方法,其返回值类型必须是void。
使用注解定义好切面以后,只需在Spring配置文件中引入aop命名空间,并添加<aop:aspectj-autoproxy />元素,就可启用对于AspectJ的支持,Spring将为匹配的Bean创建代理。
注:虽然是通过注解定义的切面,但是依然需要在容器中引入包含增强方法的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:p="http://www.springframework.org/schema/p"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="myLogger" class="AOP.MyLogger"/>
<aop:aspectj-autoproxy />
</beans>
扩展2——使用注解定义其他类型的增强
与前置增强、后置增强的定义方法一样,只不过使用的注解不一样,不再一一举例。
异常抛出增强:@AfterThrowing
最终增强:@After
环绕增强:@Around
总结
1、Spring提供了设值注入、构造注入等依赖注入方式;
2、使用P命名空间可以简化属性注入;
3、Spring提供的增强处理类型有:前置增强、后置增强、异常抛出增强、环绕增强、最终增强等;
4、通过Schema形式将Bean的方法配置成切面,所用标签有:<aop:config>、<aop:pointcut>、<aop:aspect>、<aop:before>、<aop:after-returning>、<aop:after>、<aop:around>、<aop:after-throwing>等;
5、用来定义Bean组件的注解包括:@Component、@Repository、@Service、@Controller;
6、在Spring配置文件中使用<context:component-scan>元素指定包,扫描包含注解的类,完成初始化,将Bean放到容器中;
7、使用注解定义切面常用的注解:@Aspect、@Before、@After、@AfterReturning、@Pointcut、@Around等;
8、在Spring中引入aop命名空间,添加<aop:aspectj-autoproxy />元素,就可启用对AspectJ的支持。