Spring开发

Posted 灵犀一指

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring开发相关的知识,希望对你有一定的参考价值。

没有状态变化的对象(无状态对象):应当做成单例。

Spring-framework的下载:http://repo.spring.io/release/org/springframework/spring/

配置Spring环境(Spring Context)所需要的jar包,以及它们之间的相互依赖关系:

Maven会自动管理依赖,这就比较简单了:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.9.RELEASE</version>
</dependency>

Spring基础配置

  1. IoC容器 —— ApplicationContext、WebApplicationContext:控制反转(原理是依赖注入DI),即容器IoC管理bean的创建和注入。Spring有xml、注解、Java、groovy,四种方式配置IoC —— 对应有2种解析容器:xml —— ClassPathXmlApplication、注解+Java —— AnnotationConfigApplicationContext(如果是 Spring MVC 项目:AnnotationConfigWebApplicationContext)。
  2. AOP面向切面编程:分离横切逻辑。

IoC容器 —— 控制反转(容器接管对象的管理以及对象间的依赖关系):

(1) 容器的配置,属于元数据配置,Spring的容器解析这些元数据配置,以进行对bean的管理。(2) 注入:将已经存在的bean注入到某个bean —— xml 方式必须通过ref,指定需要注入的bean;annotation 方式只需要@Autowired就可以注入已有的bean。

1、Xml配置(<beans>的名称空间必须添加,不然会报错,如下):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
        
    <bean id="ctest" class="test.CTest">
        <property name="value" value="10000" />
    </bean>
    <bean id="mytest" class="test.MyTest">
        <property name="it" ref="ctest" />
    </bean>
</beans>

IoC依赖注入 —— 通过容器,根据配置注入依赖属性:

测试代码:

beans(接口与实现):

//接口
package test;

public interface ITest {
    public void print();
}
//接口实现
package test;

public class CTest implements ITest {

    private int value;
    
    @Override
    public void print() {
        System.out.println(value);
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

依赖注入 —— 获取bean(有两种方法):

package test;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    
    private static ApplicationContext context = null;
    //方法一:通过配置依赖关系,然后用getter、setter操作bean(这种方式只适用于static bean)
    //这里就体现了IoC的解耦和,即依赖关系由IoC管理。
    //分离了关注点:分开了,接口的实现和具体使用哪个接口的选择,以及其使用无需在此关心接口采用的哪一个实现
    private static ITest it = null;
    
    public ITest getIt() {
        return it;
    }
    public void setIt(ITest it) {
        this.it = it;
    }
    
    @BeforeClass
    public static void setUpBeforeClass() {
        System.out.println("hello");
        context = new ClassPathXmlApplicationContext("app.xml");
    }
    @AfterClass
    public static void tearDownAfterClass() {
        System.out.println("goodbye");
        if(context instanceof ClassPathXmlApplicationContext) {
            ((ClassPathXmlApplicationContext) context).destroy();
        }
    }
    /**
     * 测试获取bean的第一种方法:
     */
    @Test
    public void testOne() {
        it.print();
    }
    /**
     * 测试第二种方法:getBean
     */
    @Test
    public void testTwo() {
        //方法二:直接通过getBean方法,获得某个bean
//        ITest itest = context.getBean(CTest.class);
        ITest itest = (ITest) context.getBean("ctest");
        itest.print();
    }
}

2、注解配置:

  • 声明式bean注解(声明某个类是Spring管理的Bean):@Component、@Service、@Repository、@Controller。四者效果相同,都是声明bean。区别在于,会在不同的层次中使用:普通组件(普通bean)、业务逻辑层(service层)、数据持久层(dao层)、。声明式bean:需要配置类使用@ComponentScan("包名")注解,才能把声明的bean,真正的注册为bean。
  • 注入式bean注解(用于将某个bean注入到某个类中,可注解在set方法,或者直接注解在属性上):@Autowired、@Inject、@Resource。也具有相同效果,都是注入bean。区别在于提供者不同:Spring提供、JSR-330提供、JSR-250提供。

测试代码:

两个bean:

package com.test.service;

import org.springframework.stereotype.Service;

@Service
public class FunctionService {
    public String sayHello(String word) {
        return "Hello " + word + "!";
    }
}

//注入上面的bean
package com.test.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UseFunctionService {
    
    @Autowired
    FunctionService functionService;//注入
    
    public String sayHello(String word) {
        return functionService.sayHello(word);
    }
}

配置类:之后会,使用配置类完成IoC容器的创建:

package com.test.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.test")
public class DiConfig {
    
}

开始测试:

package com.test.AnnotationSpring;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.test.config.DiConfig;
import com.test.service.UseFunctionService;

public class AppTest {
    
    private static ApplicationContext context = null;
    
    @BeforeClass
    public static void setUpBeforeClass() {
        context = new AnnotationConfigApplicationContext(DiConfig.class);
    }
    @AfterClass
    public static void teatDownAfterClass() {
        if(context instanceof AnnotationConfigApplicationContext) {
            ((AnnotationConfigApplicationContext) context).destroy();
        }
    }
    @Test
    public void test() {
        UseFunctionService useFunctionService = context.getBean(UseFunctionService.class);
        System.out.println(useFunctionService.sayHello("DI"));
    }
}

3、Java配置(Spring 4.x 和 Srping Boot 推荐使用的配置方式):

  • @Configuration(查看源码,可知配置类也是Bean):声明当前类,是一个配置类。其作用相当于 一个配置Spring的xml文件。
  • @Bean:注解在方法上,声明当前方法返回一个bean(即将返回值注册为一个bean:bean的名称不是返回值的名称,而是方法名) —— 之前的做法是将某个类声明为一个bean,然后指定包扫描,找到所有的bean。

测试 Java 配置 IoC:

不再需要声明bean:

package com.test.service;

public class FunctionService {
    public String sayHello(String word) {
        return "Hello " + word + "!";
    }
}

package com.test.service;

public class UseFunctionService {
    
    FunctionService functionService;
    
    public void setFunctionService(FunctionService functionService) {
        this.functionService = functionService;
    }
    
    public String sayHello(String word) {
        return functionService.sayHello(word);
    }
}

统一在配置类中注册:

package com.test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.test.service.FunctionService;
import com.test.service.UseFunctionService;

/*
 * 此处没有使用包扫描,是因为所有的bean,都在此类中定义了
 * 是因为:容器注入bean时,调用方法返回并注册。例如注入FunctionService的bean时,直接调用functionService方法
 */
@Configuration
public class JavaConfig {
    @Bean
    public FunctionService functionService() {
        return new FunctionService();
    }
    @Bean
    public UseFunctionService useFunctionService() {
        UseFunctionService useFunctionService = new UseFunctionService();
        useFunctionService.setFunctionService(functionService());
        return useFunctionService;
    }
    //写法二(两种方法,任选一种):
    //原理是:若Spring容器中,已存在某个bean,则可以直接作为参数在另一个bean方法中写入,然后通过setter注入
//    @Bean
//    public UseFunctionService useFunctionService(FunctionService functionService) {
//        UseFunctionService useFunctionService = new UseFunctionService();
//        useFunctionService.setFunctionService(functionService);
//        return useFunctionService;
//    }
}

开始测试(测试代码和注解配置的测试,完全一样):

package com.test.AnnotationSpring;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.test.config.JavaConfig;
import com.test.service.UseFunctionService;

public class AppTest {
    
    private static ApplicationContext context = null;
    
    @BeforeClass
    public static void setUpBeforeClass() {
        context = new AnnotationConfigApplicationContext(JavaConfig.class);
    }
    @AfterClass
    public static void teatDownAfterClass() {
        if(context instanceof AnnotationConfigApplicationContext) {
            ((AnnotationConfigApplicationContext) context).destroy();
        }
    }
    @Test
    public void test() {
        UseFunctionService useFunctionService = context.getBean(UseFunctionService.class);
        System.out.println(useFunctionService.sayHello("java config"));
    }
}

通常混合使用Java配置、注解配置:面向全局的配置,使用Java配置(如数据库的数据源配置、MVC配置)。而业务bean采用注解配置,即@Component、@Service、@Repository、@Controller。

Spring AOP面向切面编程(Spring 支持 AspectJ 的注解式切面编程):

  • 使用@Aspect声明某个类是一个切面。
  • 使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点),作为参数。
  • 建言的参数,拦截规则(切点):使用@PointCut专门定义拦截规则,使得切点可以复用。

直接用Maven添加Spring Aop支持,以及AspectJ 依赖:

<!-- spring aop 支持 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.7.RELEASE</version>
</dependency>
<!-- aspectj 依赖 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.10</version>
</dependency>

Spring 常用配置

bean的Scope,即 Spring 容器创建 bean 的方式(Scope范围:创建方式不同,决定使用范围也不同):

  • Singleton:单例模式,即一个 Spring 容器中只有一个 bean 的实例,全容器共享同一个实例。此为Spring的默认配置。
  • Prototype:原型,每次使用bean,Spring都会提供一个新建bean的实例,相当于每次都new。
  • Request:在Web项目中,给每个http request新建一个bean实例。
  • Session:给每个http session新建一个bean实例。
  • GlobalSession:只在portal应用中有用,给每一个global http session新建一个bean实例。

此外,在Spring Batch中,还有一个注解是@StepScope。

测试Singleton和Prototype —— 判断创建的bean到底是不是单例;是不是原型:

声明两个bean:

//单例bean
package com.test.service;

import org.springframework.stereotype.Service;

@Service
public class DemoSingletonService {

}

//原型bean
package com.test.service;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Scope("prototype")
@Service
public class DemoPrototypeService {

}

配置bean的位置:

//配置类
package com.test.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.test")
public class ScopeConfig {

}

开始测试:

package com.test.CommonSpring;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.test.config.ScopeConfig;
import com.test.service.DemoPrototypeService;
import com.test.service.DemoSingletonService;

public class AppTest {
    
    private static ApplicationContext context = null;
    
    @BeforeClass
    public static void setUpBeforeClass() {
        context = new AnnotationConfigApplicationContext(ScopeConfig.class);
    }
    @AfterClass
    public static void tearDownAfterClass() {
        if(context instanceof AnnotationConfigApplicationContext) {
            ((AnnotationConfigApplicationContext) context).destroy();
        }
    }
    @Test
    public void test() {
        DemoSingletonService singletonOne = context.getBean(DemoSingletonService.class);
        DemoSingletonService singletonTwo = context.getBean(DemoSingletonService.class);
        
        DemoPrototypeService prototypeOne = context.getBean(DemoPrototypeService.class);
        DemoPrototypeService prototypeTwo = context.getBean(DemoPrototypeService.class);
        
        System.out.println(singletonOne == singletonTwo);
        System.out.println(prototypeOne == prototypeTwo);
    }
}

输出结果:

Spring EL 和资源调用

Sping EL支持在 xml 和在注解,以及在 html 中使用表达式,类似于 JSP 的 EL 表达式。

Spring EL主要在注解 @Value 的参数中使用表达式,通过表达式可注入各种资源到属性:普通字符、操作系统属性、表达式运算结果、bean的属性、文件内容、URI的内容、配置文件属性

测试示例:

为简化文件操作,使用 apache 的 commons-io 将文件转换成字符串。如果用 maven 构建依赖,可能需要注意:commons-io在中央仓库,已经由 "Commons IO" move 到了 "Apache Commons IO",而阿里云现目前还没有更改,因此需要根据自己本机的环境决定使用哪个依赖。

Commons IO 的依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
</dependency>

Apache Commons IO的依赖(现目前阿里云的镜像还没有):

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

在项目的类路径classpath下,新建 test.txt 和 test.properties 文件,txt内容随意,配置文件如下(呃,尊重原作):

book.author=wangyunfei
book.name=spring boot

两个Bean:

package com.qfedu.CommonSpring.service;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class DemoService {
    
    @Value("bean-demoService 的属性")
    private String another;
    
    public String getAnother() {
        return another;
    }

    public void setAnother(String another) {
        this.another = another;
    }
}

package com.qfedu.CommonSpring.service;

import java.io.IOException;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

@Service
@PropertySource("classpath:test.properties")
public class ELService {

    @Value("Normal String")
    private String normal;
    
    @Value("#{ systemProperties[\'os.name\'] }")
    private String osName;
    
    @Value(" #{ T(Math).random() * 100.0 }")
    private double randomNumber;
    
    @Value("#{ demoService.another }")    //直接从单例bean中拿
    private String fromAnother;
    
    @Value("classpath:test.txt")
    private Resource testFile;
    
    @Value("https://www.baidu.com")        //需要网络,且获取其内容会阻塞
    private Resource testUrl;
    
    //不能空格,会报错,如@Value("${ book.name }")
    @Value("${book.name}")
    private String bookName;
    
    @Autowired
    private Environment environment;
    
    public void outputResource() {
        try {
            System.out.println(normal);
            System.out.println(osName);
            System.out.println(randomNumber);
            System.out.println(fromAnother);
            
            System.out.println(IOUtils.toString(testFile.getInputStream()));
            System.out.println("\\n" + IOUtils.toString(testUrl.getInputStream()) + "\\n");
            
            System.out.println(bookName);
            System.out.println(environment.getProperty("book.author"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

获取配置文件的属性:首先要给任意的Bean注解@PropertySource,指定文件位置。1、从Environment中获取;2、注册一个PropertySourcesPlaceholderConfigurer的bean,然后通过@Value("${属性名}")注入

配置类:

package com.qfedu.CommonSpring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

@Configuration
@ComponentScan("com.qfedu.CommonSpring")
public class ELConfig {
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyConfigure() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

单元测试:

package com.qfedu.CommonSpring;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.qfedu.CommonSpring.config.ELConfig;
import com.qfedu.CommonSpring.service.ELService;

public class AppTest {
    
    private static ApplicationContext context = null;
    
    @BeforeClass
    public static void setUpBeforeClass() {
        context = new AnnotationConfigApplicationContext(ELConfig.class);
    }
    @AfterClass
    public static void tearDownAfterClass() {
        if(context instanceof AnnotationConfigApplicationContext) {
            ((AnnotationConfigApplicationContext) context).destroy();
        }
    }
    @Test
    public void test() {
        ELService elService = context.getBean(ELService.class);
        elService.outputResource();
    }
}

Bean的初始化和销毁

在实际开发中,经常会在bean使用之前或之后,做一些必要的操作,而Spring对Bean生命周期的操作提供了支持。可以使用两种方式操作:

  1. 使用Java配置(返回注册Bean):使用@Bean的 initMethod 和 destroyMethod (相当于 xml 配置的 inti-method 和 destroy-method)。
  2. 使用JSR250提供的注解方式:JSR-250的 @PostConstruct 和 @PreDestroy。

测试示例:

添加JSR250支持:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>jsr250-api</artifactId>
    <version>1.0</version>
</dependency>

使用JSR250注解的bean:

package com.test.service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class JSR250WayService {
    @PostConstruct    //构造函数执行完之后执行
    public void init() {
        System.out.println("JSR250-init-method");
    }
    public JSR250WayService() {
        System.out.println("constructor-JSR250WayService");
    }
    @PreDestroy        //bean销毁之前执行
    public void destroy() {
        System.out.println("JSR250-destroy-method");
    }
}

使用Java配置的bean:

package com.test.service;

public class BeanWayService {
    public void init() {
        System.out.println("@Bean-init-method");
    }
    public BeanWayService() {
        System.out.println("constructor-BeanWayService");
    }
    public void destroy() {
        System.out.println("@Bean-destroy-method");
    }
}

配置类:

package com.test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.test.service.BeanWayService;
import com.test.service.JSR250WayService;

@Configuration
@ComponentScan("com.test")
public class PrePostConfig {
    
    // post and pre
    @Bean(initMethod = "init", destroyMethod = "destroy")
    BeanWayService beanWayService() {
        return new BeanWayService();
    }
    
    @Bean
    JSR250WayService jsr250WayService() {
        return new JSR250WayService();
    }
    
}

单元测试:

package com.test.CommonSpring;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.test.config.PrePostConfig;
import com.test.service.BeanWayService;
import com.test.service.JSR250WayService;

public class AppTest {
    
    private static ApplicationContext context = null;
    
    @BeforeClass
    public static void setUpBeforeClass() {
        context = new AnnotationConfigApplicationContext(PrePostConfig.class);
    }
    @AfterClass
    public static void tearDownAfterClass() {
        if(context instanceof AnnotationConfigApplicationContext) {
            ((AnnotationConfigApplicationContext) context).destroy();
        }
    }
    @Test
    public void test() {
        //这两个bean,容器始终是会根据配置类创建的,此处至于要不要getBean都没影响
//        JSR250WayService jsr250WayService = context.getBean(JSR250WayService.class);
//        BeanWayService beanWayService = context.getBean(BeanWayService.class);
    }
}

测试结果:

依次创建、销毁bean,post 和 pre 也如期所至。

Profile:

Profile为在不同环境下,使用不同的配置提供了支持(一般开发环境和生产环境的配置肯定是不相同的,例如数据库的配置)。

由于Profile的配置类中,有用于不同环境的不同配置,使用时需要选择某一种配置,故不能像之前那样,直接用配置类初始化容器,而是要设定容器使用哪一种Profile,然后容器根据配置类来选择使用不同的配置 —— 有以下几种设定方式:

  • 通过设置容器的Enviroment的ActiveProfiles,来设定当前容器需要的配置环境。使用@Profile对类或方法进行注解,以实现不同的情况,实例化不同的bean。
  • 通过设置JVM的环境参数(spring.profiles.active),来设定配置环境。
  • 在Web项目中,设置在前端控制器DispatcherServlet(在Spring web mvc的jar包下)的context parameter中。

在Servlet 2.5及以下,通过xml配置: