IoC和AOP使用扩展

前言

本文将分别对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的支持。

 

猜你喜欢

转载自blog.csdn.net/wzy18210825916/article/details/82528819