学习目标
通过第5~7章的学习,同学们已经对Spring框架有了较为深入的了解,具备了使用Spring+MyBatis框架开发项目的基本能力。(如果没有了解可以去我主页看看 第一至七章的内容来学习)本章将带你们学习更多Spring框架的实用配置技巧,帮助大家进一步巩固Spring框架与MyBatis框架的集成开发技能。
8.1 配置数据源
第七章使用Spring框架整合MyBatis框架后,我们把数据源的相关配置从MyBatis配置文件转移到了Spring配置文件中。在实际开发中,数据源还有一些其他配置方式。
8.1.1 使用properties文件配置数据源
使用properties文件配置数据源时,可以把数据源的相关配置信息单独放到properties文件中进行维护。
在Java中,使用properties文件来配置数据源是一种常见的做法,这有助于将配置信息(如数据库URL、用户名、密码等)与代码分离,从而更容易地管理和修改这些配置。以下是一个基本的示例,展示了如何使用properties文件来配置数据源,并通过Java代码读取这些配置来创建数据源。
- : 创建properties文件
首先,你需要创建一个properties文件(例如database.properties),并在其中定义数据库连接所需的属性。
# database.properties driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/yourdatabase?serverTimezone=UTC username=yourusername password=yourpassword initialSize=5 maxActive=10 maxIdle=5 minIdle=2 acquireIncrement=1
- : 读取properties文件
在Java代码中,你可以使用java.util.Properties类来加载并读取这个properties文件。
import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; public class DataSourceConfig { private static Properties properties = new Properties(); static { try (FileInputStream in = new FileInputStream("database.properties")) { properties.load(in); } catch (IOException ex) { ex.printStackTrace(); throw new ExceptionInInitializerError(ex); } } public static String getProperty(String key) { return properties.getProperty(key); } }
- : 使用properties配置创建数据源
一旦你有了读取properties文件的方法,你就可以使用这些配置来创建数据源了。这里以Apache Commons DBCP(Database Connection Pooling)为例,但请注意,根据你的项目需求,你可能会选择其他数据源实现(如HikariCP、Tomcat JDBC Pool等)。
import org.apache.commons.dbcp2.BasicDataSource; public class DataSourceFactory { public static BasicDataSource createDataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName(DataSourceConfig.getProperty("driverClassName")); dataSource.setUrl(DataSourceConfig.getProperty("url")); dataSource.setUsername(DataSourceConfig.getProperty("username")); dataSource.setPassword(DataSourceConfig.getProperty("password")); dataSource.setInitialSize(Integer.parseInt(DataSourceConfig.getProperty("initialSize"))); dataSource.setMaxActive(Integer.parseInt(DataSourceConfig.getProperty("maxActive"))); dataSource.setMaxIdle(Integer.parseInt(DataSourceConfig.getProperty("maxIdle"))); dataSource.setMinIdle(Integer.parseInt(DataSourceConfig.getProperty("minIdle"))); dataSource.setAcquireIncrement(Integer.parseInt(DataSourceConfig.getProperty("acquireIncrement"))); return dataSource; } }
- : 使用数据源
现在,你可以在你的应用程序中通过调用DataSourceFactory.createDataSource()来获取配置好的数据源,并使用它来执行数据库操作了。
public class Application { public static void main(String[] args) { BasicDataSource dataSource = DataSourceFactory.createDataSource(); // 使用dataSource执行数据库操作... } }
请注意,这个例子使用了Apache Commons DBCP作为数据源实现,但在实际项目中,你可能需要根据项目的具体需求选择最适合你的数据源实现。此外,确保你的项目中已经包含了相应的依赖库。
知识扩展
在Spring配置文件中加载properties文件的方式很多,比较常用的有使用context标签进行加载,代码如下所示。
<context:property-placeholder ignore-unresolvable=“true”
location=“classpath:database.properties”>
使用context标签加载数据源配置文件时,数据源的Bean配置方式相同,不需要修改。
8.1.2 使用JNDI配置数据源
如果应用程序所部署的服务器(如Tomcat、WebLogic等)提供了数据源服务,应用程序也可以直接使用这些数据源。服务器数据源是使用JNDI方式,Spring框架为此专门提供了引用JNDI资源的JndiObjectFacto1yBean类供开发者使用。
若使用JNDI方式配置数据源,则需要把数据源信息配置到应用服务器上。以Tomcat为例,首先,需要把数据库驱动文件放到Tomcat的lib目录下,然后,把数据源信息配置到Tomcat的conf目录下的context.xml文件中,并修改Spring配置文件为通过JNDI方式配置数据源。
在Java中,使用JNDI(Java Naming and Directory Interface)配置数据源是一种常见的做法,特别是在Java EE环境中,如Tomcat、JBoss、WebLogic等服务器。JNDI提供了一种将对象(如数据源)绑定到命名服务中的方法,这样应用程序就可以通过名称查找这些对象,而不需要知道它们的具体实现或位置。
以下是一个基本的示例,展示了如何在Tomcat服务器中使用JNDI配置数据源,并在Java代码中通过JNDI查找这个数据源。
- : 在Tomcat中配置数据源(context.xml)
首先,你需要在Tomcat的conf/context.xml文件或应用的META-INF/context.xml文件中配置数据源。
<Context> <!-- 数据源配置 --> <Resource name="jdbc/MyDataSource" auth="Container" type="javax.sql.DataSource" maxTotal="100" maxIdle="30" maxWaitMillis="10000" username="yourusername" password="yourpassword" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/yourdatabase?serverTimezone=UTC"/> </Context>
注意:如果你是在应用的META-INF/context.xml中配置,这个配置将仅对该应用有效。
- : 在web.xml中声明资源引用
虽然对于JNDI数据源来说,直接在context.xml中配置通常就足够了,但在某些情况下,你可能还需要在WEB-INF/web.xml中声明一个资源引用,尽管这不是必需的。
<web-app> ... <resource-ref> <description>DB Connection</description> <res-ref-name>jdbc/MyDataSource</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> ... </web-app>
- : 在Java代码中通过JNDI查找数据源
现在,你可以在Java代码中通过JNDI查找这个数据源了。
import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; public class DataSourceLookup { public static DataSource getDataSource() throws NamingException { Context initContext = new InitialContext(); Context envContext = (Context)initContext.lookup("java:/comp/env"); DataSource ds = (DataSource)envContext.lookup("jdbc/MyDataSource"); return ds; } public static void main(String[] args) { try { DataSource ds = getDataSource(); // 使用数据源执行数据库操作... System.out.println("DataSource: " + ds.getConnection().getMetaData().getURL()); } catch (NamingException | SQLException e) { e.printStackTrace(); } } // 注意:main方法在这里仅用于演示目的,实际中你应该在Servlet或其他Web组件中调用这个方法 }
注意:上面的main方法示例仅用于演示如何在Java代码中查找数据源。在实际的Web应用程序中,你通常会在Servlet、EJB或Spring管理的Bean中执行这种查找。
此外,请确保你的Tomcat服务器已经启动了JNDI服务,并且已经包含了必要的JDBC驱动程序。如果你使用的是Maven或Gradle等构建工具,确保将JDBC驱动作为依赖项添加到你的项目中。
最后,请记住,JNDI查找数据源的具体实现可能会根据你使用的Java EE服务器或框架的不同而有所差异。上面的示例是针对Tomcat服务器的。如果你使用的是其他服务器(如JBoss、WebLogic等),请查阅相应的文档以获取正确的配置和查找方法。
8.2 拆分Spring配置文件
如果选择使用XML配置文件的方式开发Spring项目,则当项目规模越来越大时,项目中的Bean也越来越多,配置文件将变得越来越臃肿,可读性和维护性都会降低;多人修改同一配置文件时也容易发生冲突,影响开发效率。因此,Spring框架提供了可以将配置文件拆分处理的功能,可以将一个大的配置文件分解成多个小配置文件。
在Spring框架中,拆分配置文件是一种常见的做法,特别是在大型项目中,它有助于保持配置的模块化和可管理性。尽管传统的做法是使用XML配置文件进行拆分,但在现代Spring应用程序中,越来越多的人选择使用基于Java的配置(也称为JavaConfig),因为它提供了更强的类型安全和更灵活的配置选项。
以下是如何使用Java代码来拆分Spring配置的一个基本示例:
- 创建配置类
首先,你需要创建几个配置类,每个类都使用@Configuration注解来标记,并包含使用@Bean注解的方法,这些方法会返回要注册到Spring应用上下文中的bean。
// 第一个配置类 @Configuration public class DataSourceConfig { @Bean public DataSource dataSource() { // 配置并返回DataSource实例 // 例如:使用HikariCP, Apache DBCP, C3P0等 return DataSourceBuilder.create() .url("jdbc:mysql://localhost:3306/mydb") .username("user") .password("pass") .driverClassName("com.mysql.cj.jdbc.Driver") .build(); } } // 第二个配置类 @Configuration @EnableTransactionManagement public class TransactionManagerConfig { @Autowired private DataSource dataSource; @Bean public PlatformTransactionManager transactionManager() { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); return transactionManager; } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); vendorAdapter.setGenerateDdl(true); LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan(new String[]{"com.example.model"}); em.setJpaVendorAdapter(vendorAdapter); em.setJpaProperties(additionalProperties()); return em; } private final Properties additionalProperties() { Properties properties = new Properties(); properties.setProperty("hibernate.hbm2ddl.auto", "update"); properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect"); return properties; } } // 你可以继续创建更多的配置类...
- 使用@Import注解合并配置
然后,你可以创建一个主配置类,使用@Import注解来合并上述配置类。这样,当你启动Spring应用时,所有这些配置都会被加载。
@Configuration @Import({DataSourceConfig.class, TransactionManagerConfig.class}) public class AppConfig { // 这里可以添加一些全局配置或bean }
- 启动Spring应用
最后,你可以通过Spring的AnnotationConfigApplicationContext或在一个Spring Boot应用中自动扫描主配置类来启动Spring应用。
如果你是在Spring Boot项目中工作,通常你不需要直接创建AnnotationConfigApplicationContext。相反,你可以将@SpringBootApplication注解添加到包含@Import的主配置类上,或者简单地让Spring Boot通过组件扫描自动发现你的配置类。
@SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); // Spring Boot将自动扫描并注册AppConfig中的配置 } }
使用Java配置而不是XML配置文件的好处之一是它提供了更强的类型检查和更灵活的代码重构能力。你可以利用Java的所有特性(如继承、接口和泛型)来构建更强大、更易于维护的配置。
8.3 Bean的自动装配
学习@Autowired或@Resource注解的时候大家已经对Spring框架的自动装配功能有了一定的了解。Spring框架的自动装配是指在没有显式指定所依赖的Bean组件id的情况下,可以自动地将于属性类型相符的Bean注入相应属性的功能(对于@Resource注解而言,还会尝试id和属性名相符的情况),这在很大程度上简化了配置。
在Spring框架中,Bean的自动装配是一种让Spring容器自动注入Bean之间的依赖关系的技术,从而减少了手动配置的需要。Spring提供了多种自动装配的方式,包括基于注解的自动装配。下面是一个使用Java配置和注解来实现Bean自动装配的示例。
- 定义Bean
首先,定义一些需要被Spring容器管理的Bean类。这些类可以是简单的POJO(Plain Old Java Object)。
public class UserService { private UserRepository userRepository; // 通过构造函数注入UserRepository public UserService(UserRepository userRepository) { this.userRepository = userRepository; } // 其他业务方法... } public class UserRepository { // 数据访问逻辑... }
- 配置类
接下来,创建一个配置类,使用@Configuration注解标记,并在其中定义Bean。对于自动装配,我们不需要显式地在配置类中编写Bean之间的依赖关系,因为Spring会根据注解自动处理这些关系。
@Configuration @ComponentScan(basePackages = "com.example.demo") // 扫描指定包下的组件 public class AppConfig { // 这里不需要显式定义UserService和UserRepository的Bean, // 因为@ComponentScan会扫描带有@Component、@Service、@Repository等注解的类, // 并自动注册为Bean。 // 如果需要,你也可以在这里显式定义Bean,并使用@Autowired等注解进行自动装配, // 但这通常不是必需的,因为Spring会自动处理这些依赖。 // 例如,如果你想要通过Java代码显式注册Bean,可以这样做: // @Bean // public UserRepository userRepository() { // return new UserRepository(); // } // @Bean // public UserService userService(UserRepository userRepository) { // return new UserService(userRepository); // } // 但请注意,上面的显式注册是可选的,并且如果你已经使用了@ComponentScan, // 并且UserRepository和UserService类上分别使用了@Repository和@Service注解, // 那么上面的显式注册就是多余的。 }
- 使用@Autowired等注解
在上面的UserService类中,我们已经通过构造函数自动装配了UserRepository。Spring还支持其他几种自动装配方式,包括使用@Autowired注解在字段、构造函数或setter方法上进行自动装配。
如果你选择不在配置类中显式注册Bean,而是让Spring通过@ComponentScan自动扫描并注册它们,那么你的UserRepository和UserService类应该像下面这样使用注解:
@Repository public class UserRepository { // ... } @Service public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } // ... }
- 启动Spring应用
最后,确保你的Spring Boot应用或Spring应用上下文能够扫描到上述配置类和Bean类。在Spring Boot中,这通常是通过在主类上使用@SpringBootApplication注解(它包含了@ComponentScan)来实现的。
@SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); // Spring Boot将自动扫描并注册AppConfig中的配置以及带有@Component、@Service、@Repository等注解的Bean } }
这样,Spring就会根据你的配置和注解自动装配Bean之间的依赖关系。
8.4 Bean的作用域
在Spring框架中定义Bean,除可以创建Bean实例并对Bean的属性进行注入外,还可以为所定义的Bean指定一个作用域,这个作用域的取值决定了Spring框架创建该组件实例的策略,进而影响程序的运行效率和数据安全。在Spring及之后的版本中,Bean的作用域被划分为以下五种。
- singleton:默认值。以单例模式创建Bean的实例,即容器中该Bean的实例只会被创建一个。
- prototype:每次从容器中获取Bean时,都会创建一个新的实例。
- request:用于Web应用环境,针对每次HTTP请求都会创建一个实例。
- Session:用于Web应用环境,同一个会话共享同一个实例,不同的会话使用不同的实例。
- global session:仅在Portlet的Web应用中使用,同一个全局会话共享一个实例。对于非Poltlet环境,等同于session。
在Spring框架中,Bean的作用域(Scope)定义了Bean的生命周期和可见性。Spring提供了几种内置的作用域,包括单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)和应用程序(Application)。这些作用域决定了Spring容器如何创建和管理Bean的实例。
在Java配置中,你可以通过@Scope注解来指定Bean的作用域。以下是一些示例代码,展示了如何在Java配置中设置Bean的不同作用域。
- 单例(Singleton)
单例作用域是Spring的默认作用域。这意味着Spring容器为给定的Bean定义只创建一个实例,并且这个实例在整个Spring应用上下文中是唯一的。
@Configuration public class AppConfig { // 默认就是单例作用域,所以这里可以省略@Scope注解 @Bean public MyBean mySingletonBean() { return new MyBean(); } // 显式指定为单例作用域(可选) @Bean @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) public AnotherBean anotherSingletonBean() { return new AnotherBean(); } }
- 原型(Prototype)
原型作用域意味着每次请求该Bean时,Spring都会创建一个新的实例。
@Configuration public class AppConfig { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public MyPrototypeBean myPrototypeBean() { return new MyPrototypeBean(); } }
- 请求(Request)和会话(Session)
请求和会话作用域主要用于Web应用程序。这些作用域仅在Web环境中可用,并且需要RequestContextListener(对于请求作用域)和HttpSessionListener(对于会话作用域)的支持。
由于这些作用域在Java配置中不直接配置(它们依赖于Servlet环境),你通常会在XML配置或通过实现WebApplicationContextInitializer来配置它们。但是,为了说明目的,以下是如何在假设的上下文中指定它们(请注意,这实际上不是Java配置的直接方式):
// 假设的示例,实际上在Java配置中不直接这样做 // @Bean // @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) // public MyRequestBean myRequestBean() { // return new MyRequestBean(); // } // @Bean // @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) // public MySessionBean mySessionBean() { // return new MySessionBean(); // } // 注意:上面的代码是假设的,并不直接应用于Java配置。 // 对于请求和会话作用域,你通常需要在Spring的Web环境中配置, // 并且可能需要通过XML或实现特定的Spring接口来启用这些作用域。
对于请求和会话作用域,在Java配置中,你通常会依赖于Spring的Web支持来自动配置这些作用域,而不是在配置类中直接指定它们。这些作用域通常与Spring MVC的控制器和视图层集成。
- 应用程序(Application)
应用程序作用域并不是Spring框架内置的一个标准作用域。但是,有些开发者或框架可能会实现自己的作用域来模拟类似的行为。在标准的Spring上下文中,如果你需要跨多个用户会话共享数据,通常会使用单例作用域,并通过其他机制(如数据库、缓存等)来管理数据的状态。
对于自定义作用域,你可以通过实现Scope接口来创建自己的作用域。但是,这通常是在需要非常特定的行为时才进行的。
8.4.1 使用注解指定Bean的作用域
在Spring框架中,使用注解来指定Bean的作用域是一种常见且方便的做法。Spring提供了@Scope注解,用于在Bean的声明上指定其作用域。以下是一些使用@Scope注解来指定Bean不同作用域的Java代码示例。
- 单例(Singleton)
虽然单例是Spring的默认作用域,但你可以显式地通过@Scope注解来指定它(尽管这通常是多余的)。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @Configuration public class AppConfig { // 显式指定为单例作用域(可选) @Bean @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) public MySingletonBean mySingletonBean() { return new MySingletonBean(); } }
- 原型(Prototype)
原型作用域意味着每次请求该Bean时,Spring都会创建一个新的实例。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @Configuration public class AppConfig { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public MyPrototypeBean myPrototypeBean() { return new MyPrototypeBean(); } }
- Web作用域(请求和会话)
对于Web作用域(如请求和会话),你需要在Spring的Web环境中配置,并且这些作用域通常与Spring MVC一起使用。不过,你可以在Java配置中通过@Scope注解来指定它们,但需要注意这些作用域依赖于Servlet上下文。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.web.context.WebApplicationContext; @Configuration public class WebAppConfig { // 请求作用域 @Bean @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) public MyRequestBean myRequestBean() { return new MyRequestBean(); } // 会话作用域 @Bean @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) public MySessionBean mySessionBean() { return new MySessionBean(); } }
注意,在请求和会话作用域中,proxyMode = ScopedProxyMode.TARGET_CLASS(或ScopedProxyMode.INTERFACES,取决于你的Bean实现的接口)是必需的,因为Spring需要为这些Bean创建代理,以便在每次请求或会话中返回不同的实例。
注意
- 要使用Web作用域(如请求和会话),你的应用必须是一个Web应用,并且Spring的Web支持(如DispatcherServlet)必须已经配置好。
- 在使用Web作用域时,请确保你的Spring配置类(或XML配置文件)位于Spring的Web上下文中,这样Spring才能正确地识别和处理这些作用域。
- 自定义作用域也是可能的,但通常不需要,除非你有非常特定的需求。如果你需要自定义作用域,你需要实现Scope接口,并在Spring配置中注册它。