Spring初学

一.什么是Spring

Spring主要两个有功能为我们的业务对象管理提供了非常便捷的方法:
DI(Dependency Injection,依赖注入)
AOP(Aspect Oriented Programming,面向切面编程)

依赖注入(DI或者IOC): 简单来说,一个系统中可能会有成千上万个对象。如果要手工维护它们之间的关系,这是不可想象的。我们可以在Spring的XML文件描述它们之间的关系,由Spring自动来注入它们——比如A类的实例需要B类的实例作为参数set进去。也就是说Spring是一个容器,容器在启动的时候,会根据你在配置文件里的配置类装配你的类,以及处理各个类实例之间的依赖关系,给我们的感觉就是不用我们自己去new一个对象,直接去Spring容器中get就行。

面向切面编程(AOP): Java里是利用反射机制实现,也可以说是动态代理主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰。以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关的“方面”的代码(比如:我们玩的游戏需要更新是就是采用了静态织入的方式,不可能每次更新完都需要从新下载吧?而是每次只要更新那一部分并不需要去从新下载游戏)。Sping具体中可以利用aop在某个方法前添加什么代码,这个功能主要是给应用程序提供特定的服务的,比如:日志服务,事务服务等。有了这个你就可以通过配置来定制服务,而不用在一开始就在类中写上日志管理,事务处理等代码。这样提高了代码的简洁性以及组件的可重用性。


二.Spring--DI(依赖注入)

依赖注入(Dependency Injection)是用于实现控制反转(Inversion of Control)的最常见的方式之一。IOC:控制反转:将对象的创建权,由Spring管理. DI(依赖注入):在Spring创建对象的过程中,把对象依赖的属性注入到类中

Spring IOC 也是一个java对象,在某些特定的时间被创建后,可以进行对其他对象的控制,包括初始化、创建、销毁等。简单地理解,在上述过程中,我们通过配置文件配置了xxxDaoImpl实现类的完全限定名称,然后利用反射在运行时为xxxDao创建实际实现类,包括xxxServiceImpl的创建,Spring的IOC容器都会帮我们完成,而我们唯一要做的就是把需要创建的类和其他类依赖的类以配置文件的方式告诉IOC容器需要创建那些类和注入哪些类即可。我们可以把IoC模式看做是工厂模式的升华,可以把IoC看作是一个大工厂,只不过这个大工厂里要生成的对象都是在配置文件(XML)中给出定义的,然后利用Java的反射技术,根据XML中给出的类名生成相应的对象。

依赖注入有很多种:
1.setter注入:被注入的属性需要有set方法,Setter注入支持简单类型和引用类型,Setter注入时在bean实例创建完成后执行的
<!-- 声明accountDao对象并交给spring创建 -->
<bean name="accountDao" class="com.dao.impl.AccountDaoImpl"/>
<!-- 声明accountService对象,交给spring创建 -->
<bean name="accountService" class="com.service.impl.AccountServiceImpl">
      <!-- 通过setter注入accountDao对象,对象注入使用ref-->
      <property name="accountDao" ref="accountDao"/>
</bean>

2.构造函数注入:构造注入也就是通过构造方法注入依赖,构造函数的参数一般情况下就是依赖项,spring容器会根据bean中指定的构造函数参数来决定调用那个构造函数。

<bean name="accountDao" class="com.dao.impl.AccountDaoImpl"/>
<!-- 通过构造注入依赖 -->
<bean name="accountService" class="com.service.impl.AccountServiceImpl">
    <!-- 构造方法方式注入accountDao对象,-->
    <constructor-arg  ref="accountDao"/>
</bean>

基于这几种注入有两种方式:xml的自动装配和注解注入。

xml自动装配:

自动装配又有byType(根据类型),byName(根据名字),constructor(根据构造函数)。用在配置文件中autowire来指定。

在byTpye模式中,Spring容器会基于反射查看bean定义的类,然后找到与依赖类型相同的bean注入到另外的bean中,这个过程需要借助setter注入来完成,因此必须存在set方法

<bean id="userDao"  class="com.dao.impl.UserDaoImpl" />
<!-- byType 根据类型自动装配userDao-->
<bean id="userService" autowire="byType" class="com.service.impl.UserServiceImpl>

事实上byType模式可能存一种注入失败的情况,由于是基于类型的注入,因此当xml文件中存在多个相同类型名称不同的实例Bean时,Spring容器依赖注入仍然会失败。这个时候可以用byName模式,此时Spring只会尝试将属性名与bean名称进行匹配。

<bean id="userDao"  class="com.dao.impl.UserDaoImpl" />
<bean id="userDao2" class="com.dao.impl.UserDaoImpl" />

<!-- byName 根据名称自动装配,找到UserServiceImpl名为 userDao属性并注入-->
<bean id="userService" autowire="byName" class="com.service.impl.UserServiceImpl>

对于constructor模式,在该模式下Spring容器同样会尝试找到那些类型与构造函数相同匹配的bean然后注入。

<bean id="userDao"  class="com.dao.impl.UserDaoImpl" />
<!-- constructor自动装配userDao-->
<bean id="userService" autowire="constructor" class="com.service.impl.UserServiceImpl>


基于注解注入方式:

注解有这三种:@Autowired  @Resource   @Value。

1.@Autowired

<!-- 使用注解时必须启动注解驱动 -->
<context:annotation-config />

@Autowired的使用标注到成员变量时不需要有set方法,请注意@Autowired 默认按类型匹配的。

@Autowired
    public void setXXX() {
    }

在@Autowired中还传递了一个required=false的属性,false指明当xxxDao实例存在就注入不存就忽略,如果为true,就必须注入,若xxxDao实例不存在,就抛出异常。由于默认情况下@Autowired是按类型匹配的(byType),如果需要按名称(byName)匹配的话,可以使用@Qualifier注解与@Autowired结合

     @Autowired
    @Qualifier("userDao1")
    private UserDao userDao;

2.@Resource

默认byName模式,可以标注在成员变量和set方法上,但无法标注构造函数。@Resource有两个中重要的属性:name和type。Spring容器对于@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。因此使用name属性,则按byName模式的自动注入策略,如果使用type属性则按 byType模式自动注入策略。倘若既不指定name也不指定type属性,Spring容器将通过反射技术默认按byName模式注入

@Resource(name=“userDao”)
private UserDao  userDao;//用于成员变量

//也可以用于set方法标注
@Resource(name=“userDao”)
public void setUserDao(UserDao userDao) {
   this.userDao= userDao;
}

3.@Value

上述两种自动装配的依赖注入并不适合简单值类型,如int、boolean、long、String以及Enum等,对于这些类型,Spring容器也提供了@Value注入的方式

    @Value("${jdbc.url}")
    private String url;
    //SpEL表达方式,其中代表xml配置文件中的id值configProperties
    @Value("#{configProperties['jdbc.username']}")
    private String userName;



三.Spring--AOP(面向切面编程)

aop核心概念

(1)通知(增强)Advice

  通知定义了切面是什么以及何时使用,应该应用在某个方法被调用之前?之后?还是抛出异常时?等等。

(2)连接点 Join point

  连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时,抛出异常时,甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。

(3)切点 Pointcut

  切点有助于缩小切面所通知的连接点的范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”,切点会匹配通知所要织入的一个或多个连接点,一般常用正则表达式定义所匹配的类和方法名称来指定这些切点。

(4)切面 Aspect

  切面是通知和切点的结合。通知和切点定义了切面的全部内容——它是什么,在何时何处完成其功能。

(5)引入 Introduction

  引入允许我们向现有的类添加新方法或属性,从而无需修改这些现有类的情况下,让他们具有新的行为和状态。

(6)织入 Weaving

  在过去我常常把织入与引入的概念混淆,我是这样来辨别的,“引入”我把它看做是一个定义,也就是一个名词,而“织入”我把它看做是一个动作,一个动词,也就是切面在指定的连接点被织入到目标对象中。

通知包含了需要用于多个应用对象的横切行为;连接点是程序执行过程中能够应用通知的所有点;切点定义了通知被应用的具体位置(在哪些连接点)。其中关键的概念是切点定义了哪些连接点会得到通知(增强)。创建切点来定义切面所织入的连接点是AOP框架的基本功能。

使用通知注解:

前置通知:通过@Before注解进行标注,并可直接传入切点表达式的值,该通知在目标函数执行前执行,注意JoinPoint,是Spring提供的静态变量,通过joinPoint 参数,可以获取目标对象的信息,如类名称,方法参数,方法名称等

后置通知:通过@AfterReturning注解进行标注,该函数在目标函数执行完成后执行,并可以获取到目标函数最终的返回值returnVal,当目标函数没有返回值时,returnVal将返回null,必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同

异常通知 :通过@AfterThrowing注解进行标注,该通知只有在异常时才会被触发,并由throwing来声明一个接收异常信息的变量,同样异常通知也用于Joinpoint参数

最终通知 :通过@After注解进行标注,该通知有点类似于finally代码块,只要应用了无论什么情况下都会执行。

环绕通知:通过@Around注解进行标注, 环绕通知既可以在目标方法前执行也可在目标方法之后执行,更重要的是环绕通知可以控制目标方法是否指向执行,但即使如此,我们应该尽量以最简单的方式满足需求,在仅需在目标方法前执行时,应该采用前置通知而非环绕通知


@Aspect
public class MyAspect {

    /**
     * 前置通知
     */
    @Before("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
    public void before(){
        System.out.println("前置通知....");
    }

    /**
     * 后置通知
     * returnVal,切点方法执行后的返回值
     */
    @AfterReturning(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))",returning = "returnVal")
    public void AfterReturning(Object returnVal){
        System.out.println("后置通知...."+returnVal);
    }


    /**
     * 环绕通知
     * @param joinPoint 可用于执行切点的类
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知前....");
        Object obj= (Object) joinPoint.proceed();
        System.out.println("环绕通知后....");
        return obj;
    }

    /**
     * 抛出通知
     * @param e
     */
    @AfterThrowing(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))",throwing = "e")
    public void afterThrowable(Throwable e){
        System.out.println("出现异常:msg="+e.getMessage());
    }

    /**
     * 无论什么情况下都会执行的方法
     */
    @After(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
    public void after(){
        System.out.println("最终通知....");
    }
}

使用@Pointcut注解:

/**
 * 使用Pointcut定义切点
 */
@Pointcut("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut(){}

/**
 * 应用切入点函数
 */
@After(value="myPointcut()")
public void afterDemo(){
    System.out.println("最终通知....");
}

用@Pointcut注解进行定义,应用到通知函数afterDemo()时直接传递切点表达式的函数名称myPointcut()。

SpringAOP提供了匹配表达式,这些表达式也叫切入点指示符:

1 ..:匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包

//任意返回值,任意名称,任意参数的公共方法
execution(public * *(..))
//匹配com.dao包及其子包中所有类中的所有方法
execution(com.dao..*) 

2  +:匹配给定类的任意子类

//匹配实现了DaoUser接口的所有子类的方法
execution(com.zejian.dao.DaoUser+) 

3 * :匹配任意数量的字符

//匹配com.service包及其子包中所有类的所有方法
execution(com.service..*)
//匹配以set开头,参数为int类型,任意返回值的方法
execution(* set*(int)) 


最后来张xml配置的代码

<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.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=""-->

    <!-- 定义目标对象 -->
    <bean name="productDao" class="com.spring.springAop.dao.daoimp.ProductDaoImpl" />

    <!-- 定义切面 -->
    <bean name="myAspectXML" class="com.spring.springAop.AspectJ.MyAspectXML" />
    <!-- 配置AOP 切面 -->
    <aop:config>
        <!-- 定义切点函数 -->
        <aop:pointcut id="pointcut" expression="execution(* com.spring.springAop.dao.ProductDao.add(..))" />

        <!-- 定义其他切点函数 -->
        <aop:pointcut id="delPointcut" expression="execution(* com.spring.springAop.dao.ProductDao.delete(..))" />

        <!-- 定义通知 order 定义优先级,值越小优先级越大-->
        <aop:aspect ref="myAspectXML" order="0">
            <!-- 定义通知
            method 指定通知方法名,必须与MyAspectXML中的相同
            pointcut 指定切点函数
            -->
            <aop:before method="before" pointcut-ref="pointcut" />

            <!-- 后置通知  returning="returnVal" 定义返回值 必须与类中声明的名称一样-->
            <aop:after-returning method="afterReturn" pointcut-ref="pointcut"  returning="returnVal" />

            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pointcut"  />

            <!--异常通知 throwing="throwable" 指定异常通知错误信息变量,必须与类中声明的名称一样-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="throwable"/>

            <!--
                 method : 通知的方法(最终通知)
                 pointcut-ref : 通知应用到的切点方法
                -->
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

猜你喜欢

转载自blog.csdn.net/qq_38682952/article/details/80860511