Skip to content

Spring快速入门

Spring IOC

application.xml配置文件

xml
<?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:mvc="http://www.springframework.org/schema/mvc"
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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
  
    <!-- id不能重复,class对应的就是实体类 -->
    <bean id="user" class="com.spring.User"></bean>

</beans>

User对象

java
@Data
public class User {
    private String name;
    private int age;
}

利用Spring容器获取类

java
/*
 * 根据spring配置文件得到ioc容器对象
 * ApplicationContext:代表ioc容器
 * ClassPathXmlApplicationContext:代表当前应用的xml配置文件在classpath下
 * 容器中对象的创建在容器创建完成的时候就已经创建好了
 * 同一对象在ioc容器中是单例的
 */
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

//这个也是可以获取的
User user = ctx.getBean("user",User.class);

//通过ID获取
User user = (User)ctx.getBean("user");
user.setName("uu");
user.setAge(18);
System.out.println(ToStringBuilder.reflectionToString(user));

ApplicationContext下常用的三个实现类

  • ClassPathXmlApplicationContext(常用):它可以加载路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。
  • FileSystemApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限,因为某些磁盘是禁止访问的)
  • AnnotationCofigApplicationContext:它是用于读取注解创建容器的。

BeanFactory和ApplicationContext的区别

  • ApplicationContext(单例对象适用):它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。只要一读取完配置文件马上就创建配置文件中配置的对象。就是只要读取xml文件以后,通过反射的方式对象就已经创建好了。
  • BeanFactory(多例对象适用):它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。什么时候根据id获取对象了,什么时候才真正创建对象。
java
Resource resource = new ClassPathResource("xxx.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
beanFactory.getBean("");

Spring中Bean的细节之三种创建Bean对象的方式

  1. 使用默认构造函数创建。在spring的配置文件中适用bean标签,配以id和class属性之后,且没有配置其他属性和标签时, 采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。<bean id="user" class="com.hadoop.practice.Singleton"></bean>
  2. 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器中),也就是有个类,里面有一个方法,这个方法返回了一个其他对象,并不是自己。<bean id="user" class="com.hadoop.practice.Singleton"></bean> 通过id为user的bean中的getName方法来获取getName方法返回的其他类对象。
  3. 使用静态工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spspring容器),getValues是Singleton类中的一个静态方法。<bean id="user" class="com.hadoop.practice.Singleton" factory-method="getValues"></bean>

Bean对象的作用范围

Bean标签的scope属性来调整bean的作用范围,取值:

  • singleton(默认单例)
  • session(作用于web应用的会话范围)
  • request(作用于web应用的请求范围)
  • prototype(多例)

Bean对象的声明周期

单例对象

  • 出生:当容器创建时对象出生。
  • 活着:只要容器还在,对象一直存活。
  • 消亡:容器销毁,对象消亡。 总结:对象的生命周期和容器相同。init-method:初始化执行的方法。destroy-method:销毁时执行的方法。手动调用ClassPathXmlApplicationContext中的close,手动执行关闭容器。 <bean id="user" class="com.hadoop.practice.Singleton" scope="singleton" init-method="init" destroy-method="over"></bean>

多例对象

  • 出生:当我们使用对象时spring为我们创建。
  • 活着:对象只要在使用过程中就一直存活。
  • 消亡:Spring不会帮着销毁,因为它不知道何时使用完这个对象。可通过垃圾回收来销毁对象。 总结:当对象长时间不用,且没有别的对象引用时,由Java垃圾回收器回收。

Spring的依赖注入

依赖注入:Dependency Injection。 IOC作用:降低程序间的耦合(依赖关系) 依赖关系管理:以后都交给spring管理。 在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明。 依赖关系维护:成为依赖注入。 注入的数据分3类:

  1. 基本类型和String。
  2. 其他的bean类型(在配置文件中或者注解配置过bean)。
  3. 复杂类型/集合类型。

注入的方式

java
1.构造注入
//实体类
public class User {
	//如果是一个经常变化的数据,并不适合使用注入的方式,比如实体类赋值存入数据库等等。
    //这里只是方便演示所以随便建了一个类,实际上该类并不适用注入。
    private String name;
    private Integer age;
    private Date birthday;
    public User(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }
}
//构造注入使用constructor-arg标签
属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中的某个或某些参数的数据类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置从0开始
name(常用、方便):用于指定给构造函数中指定名称的参数赋值
value:用于给基本类型和String类型的数据进行赋值
ref:用于指定其他的bean类型数据。它指的就是spring核心容器中出现过的bean对象
<bean id="user" class="com.hadoop.practice.User">
    <constructor-arg name="name" value="张三"></constructor-arg>
    <constructor-arg name="age" value="25"></constructor-arg>
    <!-- 注意这里的日期格式,ref是引用 -->
    <constructor-arg name="birthday" ref="bir"></constructor-arg>
</bean>
<!-- 配置一个日期对象 -->
<bean id="bir" class="java.util.Date"></bean>
构造注入的优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
构造注入的弊端:
改变了bean对象的实例化方式,使在创建对象时,如果用不到这些数据,也必须提供。

2.set方法注入
使用property标签
name:用于指定注入时所调用的set方法名称
value:用于给基本类型和String类型的数据进行赋值
ref:用于指定其他的bean类型数据。它指的就是spring核心容器中出现过的bean对象
<bean id="user" class="com.hadoop.practice.User">
    <property name="name" value="张三"></property>
    <property name="age" value="25"></property>
    <property name="birthday" ref="bir"></property>
</bean>
<bean id="bir" class="java.util.Date"></bean>
set注入的优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
set注入的弊端:
如果有某个成员必有值,则获取对象时,有可能set方法没有执行。

3.注解注入
首先要指定spring要扫描的包,不然spring也不知道你在哪写了注解。也就是告知spring在创建容器时
要扫描的包,配置所需要的标签不是在beans约束中,而是一个名称为context名称空间和约束中。
<context:component-scan base-package="com.hadoop.practice"></context:component-scan>

(1)用于创建对象的:作用和在xml配置文件中编写一个<bean>标签功能是一样的。
@Component(value = "user"):用于把当前类对象存入spring容器中。
value属性用于指定bean的id。如果不写,默认是当前类名且首字母变为小写。
与@Component作用和属性一模一样的注解还有3个,都是spring提供的:
@Controller(表现层)、@Service(业务层)、@Repository(持久层)。这3个注解是spring框架
提供的三层使用的注解,使三层对象更加清晰。

(2)用于注入数据的:作用就和在xml配置文件中<bean>标签写一个<property>标签作用是一样的
@Autowired:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就能成功注入。
它修饰的位置可以是变量上,也可以是方法上。
如果ioc容器中没有任何bean的类型和要注入的变量类型(对象)相匹配,则报错。
如果ioc容器中有多个bean类型和要注入的变量类型(对象)相匹配,则会以变量名进行匹配,举例:
将user交给spring管理
@Component(value = "user")
注入user,当有多个匹配时,会根据user变量名与@Component的value匹配,如果一直,也会注入成功。
@Autowired
private User user;
实际上ioc管理对象就是一个map,key是id,value是对象。那么这种情况怎么解决?当然是使用@Qualifier指定了
@Qualifier:不能单独使用,要结合@Autowired一起使用。但在给方法参数注入时可以。比如:
public void setName(@Qualifier("username") String name) {}
那么每次要使用两个注解?当然不是。
@Resource(name = "user")
@Resource:直接按照bean的id注入。他可以独立使用。它的name属性,直接指定id。
以上3个注解只能注入bean类型,基本类型和String类型无法使用。
@Value:用于注入基本类型和String类型
value属性用于指定数据的值。它可以使用spring中的Spel(spring的EL表达式),写法${xxx.xxx}

(3)用于改变作用范围的:作用就和在xml配置文件中使用scope属性实现的功能是一样的。
@Scope(value = "singleton"),写在类上边。
作用:用于指定bean的作用范围。
属性:value,指定范围的取值。常用取值:singleton单例、prototype多例。

(4)和声明周期相关:作用就和在<bean>标签中使用init-method和destroy-method的作用是一样的。
@PostConstruct:用于指定初始化方法
@PreDestroy:用于指定销毁方法

Spring注解配置

java
@Configuration:写在类上,用于表示当前类是个配置类。
@ComponentScan:写在类上,用于通过注解指定spring在创建容器时要扫描的包。
@ComponentScan(basePackages = "xxx.xxx.xxx")等同于在xml文件配置的bean
扫描多个basePackages = {"xxx.xxx.xxx","xxx.xxx.xxx"}

@Bean:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:@Bean(name = "user")
name/value(name和value表达的意思一样):bean的id。默认是当前方法名。

注意:
当使用注解配置时,如果方法有参数,spring会去容器中查找看有没有可用bean对象。
查找的方式和Autowired注解的作用是一样的。

AnnotationCofigApplicationContext使用
ApplicationContext a = new AnnotationConfigApplicationContext(User.class);
传入一个被注解过的类

@Import:写在类上,用于导入其他配置类。写了它那么配置类就可以不用写@Configuration
@Import({User.class,CarFactory.class})
当使用Import注解后,有Import注解的类就是父配置类,而导入的都是子配置类。

@PropertySource:用于指定properties文件的位置。结合@Value注解实现参数注入
@PropertySource("classpath:jdbc.properties")
classpath表示类路径下,如果有其他路径可写成:classpath:com/xxxx/jdbc.properties

Spring AOP

动态代理

java
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:
1.基于接口的动态代理 - JDK动态代理
如何创建代理对象?
使用Proxy类中的newProxyInstence方法,创建代理对象的要求是被代理类最少实现一个接口,如果没有不能使用
newProxyInstence方法参数:
ClassLoader:类加载器。用于加载代理对象字节码。和被代理对象使用相同的加载器。代理谁就写谁的ClassLoader
Class[]:字节码数组。用于让代理对象和被代理对象有相同的方法。代理谁就写谁。
InvocationHandler:用于提供增强代码。让我们写如何代理。
一般都是写一个该接口的实现类,通常都是匿名内部类,但不是必须的。此接口的实现类谁用谁写。 
接口:
public interface   Singleton {
    void setMoney();
}
实现类:
public class Impl implements Singleton {
    @Override
    public void setMoney() {
        System.out.println("xxx");
    }
}
代理:
public static void main(String[] args) {

    Singleton singleton = new Impl();

    Singleton s2 = (Singleton) Proxy.newProxyInstance(singleton.getClass().getClassLoader(), singleton.getClass().getInterfaces(), new InvocationHandler() {
        /**
         * 方法增强。
         * 执行被代理对象的任何接口方法都会经过该方法
         * @param proxy - 代理对象的引用
         * @param method - 当前执行的方法
         * @param args - 当前执行方法所需的参数
         * @return - 和被代理对象方法有相同的返回值
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = null;
            //此处可写增强的代码 - 增强前 - 方法执行前
            //......省略代码
            result = method.invoke(singleton,args);
            //此处可写增强的代码 - 增强前 - 方法执行后
            //......省略代码
            return result;
        }
    });

    //执行方法
    s2.setMoney();
}

2.基于子类的动态代理 - CGLIB动态代理 - 需要第三方jar包
基于子类的动态代理,就没有要求说至少实现一个方法。但他要求被代理类不能是最终类。
代码省略。。。

AOP相关术语

  • JoinPoint(连接点):指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。连接点不被增强。
  • Pointcut(切入点):指我们要对那些JoinPoint进行拦截的定义。也就是指被增强的方法。
  • Advice(通知/增强):指拦截到JoinPoint之后所要做的事情就是通知。通知的类型:前置通知(方法执行前)、后置通知(方法执行后)、异常通知(异常时)、最终通知(finally中)、环绕通知(包含所有)。
  • Introduction(引介):特殊通知。在不修改代码的前提下,Introduction可以在运行期为类动态地增加一些方法或Field(字段)。
  • Target(目标对象):代理的目标对象。
  • Weaving(织入):指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载织入。
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
  • Aspect(切面):切入点和通知(引介)的结合。

XML配置AOP

xml
<!-- aop依赖 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.1</version>
</dependency>

<!-- aop:config标签:aop配置开始 -->
<aop:config>
    <!-- aop:aspect标签:配置切面,id属性表示给切面提供一个唯一标识;ref属性表示通知类bean的id -->
    <aop:aspect id="logAdvice" ref="logger">
        <!-- aop:before前置通知。method属性表示Logger类中哪个方法是前置通知;pointcut属性指定切入点表达式,该表达式是指对业务层的哪些方法增强 -->
        <!-- 切入点表达式:execution(表达式):访问修饰符-返回值-包名.包名...类名.方法名(参数列表)
             标准写法:public void com.hadoop.practice.service.Impl.save()
             访问修饰符可以省略
             返回值可以使用通配符返回任意值
             包名可以使用通配符表示任意包,有几级包就要写几个*.
             包名可以使用..表示当前包及其子包
             类名和方法名都可以使用*来通配
             参数可以直接写数据类型:基本类型直接写名称、引用类型写包名.类名,如java.lang.String
             参数类型可以使用通配符*表示任意类型,可以使用..来表示有无参数均可,有参数可以是任意类型
             全通配写法:* *..*.*(..)
             实际开发中:通常都切到业务层实现类下的所有方法。service
        -->
        <aop:before method="beforeLog" pointcut="execution(public void com.hadoop.practice.service.Impl.save())"></aop:before>
        <!-- aop:after-returning后置通知 -->
        <aop:after-returning method="afterReturningLog" pointcut="execution(public void com.hadoop.practice.service.Impl.save())"></aop:after-returning>
        <!-- aop:after-throwing异常通知:产生异常才执行 -->
        <aop:after-throwing method="afterThrowingLog" pointcut="execution(public void com.hadoop.practice.service.Impl.save())"></aop:after-throwing>
        <!-- aop:after最终通知:切入点方法无论是否正常执行都会在其后面执行 -->
        <aop:after method="afterLog" pointcut="execution(public void com.hadoop.practice.service.Impl.save())"></aop:after>
  
        <!-- 配置切入点表达式;id属性用于指定表达式唯一表示。expression属性用于指定表达式内容 -->
        <!-- 上面的可以直接使用 pointcut-ref 引用即可 -->
        <!-- 该标签写在aspect里,表示只能当前切面可用,也可放在aspect外面作为公共的使用,但是放在外面必须放在切面之前。 -->
        <aop:pointcut id="point" expression="execution(public void com.hadoop.practice.service.Impl.save())"/>  
  
    </aop:aspect>
</aop:config>

<bean id="logger" class="com.hadoop.practice.Logger"></bean>

环绕通知:
spring框架为我们提供一个接口:ProceedingJoinPoint。该接口有一个方法proced()。
此方法相当于明确调用切入点方法。改接口可以作为环绕通知的方法参数,程序执行时,spring框架
会为我们提供该接口的实现类供我们使用。

public Object around(ProceedingJoinPoint pjp){
    Object result = null;
    try {

        Object [] args = pjp.getArgs();//得到方法执行所需的参数

        System.out.println("proceed方法之前,前置通知。");

        result = pjp.proceed(args);//明确调用业务层方法,也叫切入点方法

        System.out.println("proceed方法之后,后置通知。");

        return result;
    }catch (Throwable t){
        //此处是Throwable
        System.out.println("异常通知。");
        throw new RuntimeException(t);
    }finally {
        System.out.println("最终通知。");
    }
}

基于注解的AOP配置

java
xml中配置spring开启注解aop的支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

//表示一个切面类
@Aspect
public class AopConfig {

    @Pointcut("execution(* com.hadoop.practice.service.Impl.*.*(..))")
    public void pointCut(){}

    @Before("pointCut()")
    public void before(){
        System.out.println("前置通知");
    }
    @AfterReturning("pointCut()")
    public void afterRunning(){
        System.out.println("后置通知");
    }
    @AfterThrowing("pointCut()")
    public void throwing(){
        System.out.println("异常通知");
    }
    @After("pointCut()")
    public void endFinally(){
        System.out.println("最终通知");
    }

    //环绕通知可省略以上四种通知
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp){
        Object result = null;
        try {

            Object [] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("proceed方法之前,前置通知。");

            result = pjp.proceed(args);//明确调用业务层方法,也叫切入点方法

            System.out.println("proceed方法之后,后置通知。");

            return result;
        }catch (Throwable t){
            //此处是Throwable
            System.out.println("异常通知。");
            throw new RuntimeException(t);
        }finally {
            System.out.println("最终通知。");
        }
    }

}

Spring事务

PlatformTransactionManager接口

它提供了事务操作的方法,包含了3个具体操作。平时使用都是使用它的实现类DataSourceTransactionManager.

  1. 获取事务状态信息 TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
  2. 提交事务 void commit(TransactionStatus status) throws TransactionException;
  3. 回滚事务 void rollback(TransactionStatus status) throws TransactionException;

TransactionDefinition接口

java
//获取事务对象名称
String getName();
//获取事务是否只读
boolean isReadOnly();
//获取事务超时时间
int getTimeout();
//获取事务隔离级别
int getIsolationLevel();
//获取事务传播行为
int getPropagationBehavior();

XML配置声明式事务

xml
步骤:
1、配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

2、配置事务通知(tx:advice)
3、配置事务属性
<!-- id:唯一标识。transaction-manager:此处引用上面的transactionManager,提供一个管理器引用 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<!-- 配置事务属性
    	 isolation:指定事务隔离级别,默认值是defulat,表示使用数据库默认的隔离级别。
    	 read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认是false表示读写。
    	 propagation:用于指定事务的传播行为,默认值是REQUIRED,表示一定会有事务
         timeout:用于指定事务的超时时间,默认值-1,表示永不超时。如果指定了数值,以秒为单位。
         rollback-for:用于指定一个异常,当产生该异常时事务回滚,产生其他异常事务不会滚,无默认值,表示任何异常都回滚。
         no-rollback-for:用于指定一个异常,当产生该异常时事务不会滚,产生其他异常事务回滚,无默认值,表示任何异常都回滚。
    -->
    <tx:attributes>
        <tx:method name="find*" read-only="true"/>
        <tx:method name="load*" read-only="true"/>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="search*" read-only="true"/>
        <tx:method name="save*" propagation="REQUIRED"/>
        <tx:method name="update*" propagation="REQUIRED"/>
        <tx:method name="delete*" propagation="REQUIRED"/>
        <tx:method name="add*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

4、配置aop中的通用切入点表达式
5、建立事务通知和切入点表达式的对应关系
<aop:config>
    <aop:pointcut id="pointcut" expression="execution(* cn.javatest.service.*.*(..))"/>
    <!-- 建立切入点表达式和事务通知的对应关系,也就是将他们连接起来 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>

注解配置声明式事务

java
步骤:
1、配置事务管理器
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

2、开启spring对注解事务的支持
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

3、在需要使用事务支持的地方使用@Transactional注解
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)

纯注解声明式事务配置

java
配置代码
@Bean(name = "transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
    return new DataSourceTransactionManager(dataSource);
}

需要加入注解
@EnableTransactionManagement