Spring 是分层的Java SE/EE 应用full-stack(全栈式)轻量级的开源框架。 full-stack 全栈式:它对各种主流技术和框架进行了整合,同时对三层架构提供了解决方案。 提供了表现层SpringMvc和持久层Spring JDBC Template以及业务层Spring核心:事物和对象管理等企业级应用技术。 Spring包括两大核心:IOC(Inverse Of Control : 控制反转 把对象的创建权交给spring管理)和 AOP(Aspect Oriented Programming:面向切面编程 在不修改源码的情况下对方法进行增强) 为内核
轻量级:轻量级和重量级的划分主要依据:使用了多少服务、启动时需要加载多少资源以及耦合度等等。
Spring的优势:
- 方便解耦,简化开发:Spring就是一个容器,可以将所有对象创建和关系维护交给Spring管理。什么是耦合度?对象之间的关系,通常当一个模块更改时也需要更改其他模块,这就是耦合
- AOP 编程的支持:Spring 提供面向切面编程,方便实现程序进行权限拦截,运行监控等功能。
- 声明式事物支持:通过配置完成事物的管理,无序手动编程
- 方便测试,降低Java EE API的使用:Spring对Junit4支持,可以使用注解测试
- 方便集成各种优秀框架
IOC 控制反转:是一种设计思想。
- 控制:在Java中指的是对象的控制权限(创建、销毁)
- 反转:指的是对象控制权由原来的开发者在类中手动创建,变成了由Spring容器进行创建
例如:
- 传统方式:之前需要一个userDao的实例,就需要我们手动new UserDaoImpl()
- IOC 方式:需要一个userDao实例,直接从Spring 的 IOC容器中获得,对象的创建权交给了Spring
自定义IOC容器实现
通过自定义IOC容器实现:service与dao层的代码解耦合
创建maven项目,引入依赖
<dependencies>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
编写dao与service层,传统方式开发,耦合度太高了,如果UserDao删掉了,那么在编译器UserService就会报错 ```java public interface IUserDao { void save(); }
public class UserDaoImpl implements IUserDao { @Override public void save() { System.out.println(“保存成功”); } }
public interface IUserService { void save(); }
public class UserServiceImpl implements IUserService {
IUserDao userDao = new UserDaoImpl();
@Override
public void save() {
userDao.save();
}
} @org.junit.Test public void testCustom() { IUserService service = new UserServiceImpl(); service.save(); }
3. 改造使用反射,获取userDao实例,去掉new关键字
```java
public class UserServiceImpl implements IUserService {
// IUserDao userDao = new UserDaoImpl();
@Override
public void save() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// userDao.save();
IUserDao userDao = (IUserDao) Class.forName("com.example.ioc.dao.impl.UserDaoImpl").newInstance();
userDao.save();
}
}
上述还是存在硬编码的问题:”
com.example.ioc.dao.impl.UserDaoImpl
“,硬编码的问题使用配置文件去解决准备一个配置文件:
beans.xml
<?xml version="1.0" encoding="UTF-8" ?> <beans> <!-- id:表示 class:要生成的实例类的全路径 --> <bean id="userDao" class="com.example.ioc.dao.impl.UserDaoImpl"> </bean> </beans>
编写一个工厂类,使用
dom4j
解析配置文件,存储配置信息public class BeanFactory { private static Map<String, Object> iocMap = new HashMap<>(); //程序启动时 初始化对象实例 static { //1. 读取配置文件 ClassLoader classLoader = BeanFactory.class.getClassLoader(); //2. 加载配置文件 InputStream stream = classLoader.getResourceAsStream("beans.xml"); //3. 解析xml SAXReader reader = new SAXReader(); try { Document document = reader.read(stream); //4. 编写xpath表达式 String xpath = "//bean";//获取bean标签 //根据xpath获取到所有bean标签 List<Element> list = document.selectNodes(xpath); //5. 遍历并反射对象实例 for (Element element : list) { String id = element.attributeValue("id"); String className = element.attributeValue("class"); //使用反射生成实例对象 Object o = Class.forName(className).newInstance(); //保存到Map - IOC 容器 iocMap.put(id, o); } } catch (DocumentException | ClassNotFoundException | IllegalAccessException | InstantiationException e) { e.printStackTrace(); } } public static Object getBean(String beanId) { return iocMap.get(beanId); } }
使用反射,创建实例,存到map中(map就是IOC容器) ```java public class UserServiceImpl implements IUserService {
// IUserDao userDao = new UserDaoImpl();
@Override
public void save() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// userDao.save(); // IUserDao userDao = (IUserDao) Class.forName(“com.example.ioc.dao.impl.UserDaoImpl”).newInstance(); IUserDao userDao = (IUserDao) BeanFactory.getBean(“userDao”); userDao.save(); } }
> BeanFactory其实就是一个简单的Spring的IOC容器所具备的功能,需要实例直接从IOC容器获取即可。
> 解耦的思路:反射+配置文件
<a name="DI3ur"></a>
## Spring 基础
首先,先实现Spring的简单实现,借助springIOC 实现service层和dao层解耦合
1. 引入spring IOC 容器依赖 : spring-context
```xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
配置
applicationContext.xml
文件其实就是上述的beans.xml
的一样的功能 ```xml <?xml version=”1.0” encoding=”UTF-8” ?>
3. service层调用dao层
```java
//1. 获取到spring上下文对象,借助上下文对象可以获取到IOC容器中的bean对象
// 这句代码其实就是:
// - 加载配置文件
// - 解析配置文件
// - 创建bean对象
// - 在IOC容器中存储
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
//传递id值
//直接从IOC容器中取出bean对象
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
Spring 相关API
BeanFactory
BeanFactory 是IOC容器的核心接口,它定义了IOC的基本功能 特点:在第一次调用getBean()方法时,创建指定对象的实例
//BeanFactory 是核心接口
//这句代码执行的时候:不会创建bean对象 存到IOC容器中
BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
//在调用getBean的才真正创建bean对象 存到容器中,再根据key取出bean对象
UserDao userDao = (UserDao) xmlBeanFactory.getBean("userDao");
userDao.save();
ApplicationContext
代表应用上下文对象,可以获得spring IOC容器中的bean对象 特点:在Spring容器启动时,加载并创建所有对象实例
常见的实现类有:
- ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件 推荐使用
- FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置
- AnnotationConfigApplicationContext:当使用注解配置容器对象时,需要使用此类创建Spring容器。用它来读取注解
常用的几个方法如下://1. 获取到spring上下文对象,借助上下文对象可以获取到IOC容器中的bean对象 // 这句代码其实就是: // - 加载配置文件 // - 解析配置文件 // - 创建bean对象 // - 在IOC容器中存储 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
通过bean id 获取bean
//根据bean id 获取bean //直接从IOC容器中取出bean对象 UserDao userDao = (UserDao) applicationContext.getBean("userDao");
通过类型获取bean
//根据类型 获取bean UserDao userDao = applicationContext.getBean(UserDao.class);
但是这种方式存在着一个问题 :例如在上述中我们实现了UserDaoImpl,这时候又创建了一个UserDao的实现了
UserDaoImpl2
,在applicationContext.xml
<bean id="userDao" class="com.prim.dao.impl.UserDaoImpl"></bean> <!-- 共同实现了相同的接口 --> <bean id="userDao2" class="com.prim.dao.impl.UserDaoImpl"></bean>
这是在使用类型获取bean的话spring就会抛出一个错误
通过bean id 和 class 一起查找bean
//根据bean id 和 class 一起获取bean UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
Spring 配置文件
bean 标签的配置
<!-- id:唯一标识 class:类全路径 bean 必须有无参构造函数,如果没有无参构造函数 那么bean对象是无法创建成功的 scope:对象的作用范围 singleton: 默认值 单例的 每次从容器中取出的是一个对象 prototype: 多例的 每次从容器中取出该对象,那么该对象就会重新创建 request:在web 项目中,对象存储到request域中 session:在web 项目中,对象会存到session域中 global session:在web项目中,应用在Portlet环境,如果没有该环境就会存储在session域中 --> <bean id="userDao" class="com.prim.dao.impl.UserDaoImpl" scope="singleton"></bean>
bean的生命周期:
当scope:singleton 时
Bean的实例化个数:1个
Bean实例化的时机:当spring核心文件被加载时,实例化配置的bean实例
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了
对象运行:只要容器在,对象一直存活
对象销毁:当应用卸载,销毁容器时,对象就被销毁了
- 当scope:prototype 时
Bean的实例化个数:多个
Bean实例化的时机:当调用getBean()方法时,实例化Bean
Bean的生命周期:
对象创建:当使用对象时,创建新的对象实例
对象运行:只要对象在使用中,就一直存活
对象销毁:当对象长时间不用时,被java的垃圾回收器销毁
bean 生命周期配置
创建 bean生命周期调用的方法:
public class UserDaoImpl implements UserDao {
public void init(){
System.out.println("初始化方法执行了");
}
@Override
public void save() {
System.out.println("保存成功");
}
public void destroy(){
System.out.println("销毁方法执行了");
}
}
在Bean配置文件中,配置init-method
和 destroy-method
创建实例和销毁实例分别调用的方法。
init-method: 创建实例时,调用的方法
destroy-method:销毁实例时,调用的方法
-->
<bean id="userDao" class="com.prim.dao.impl.UserDaoImpl" scope="singleton"
init-method="init" destroy-method="destroy">
</bean>
Bean实例化的三种方式
- 无参构造方式实例化,其实就是上述的配置
- 工厂静态方法实例化
应用场景:依赖的jar包有个A类,A类中有个静态方法m1,m1方法的返回值是一个B对象。如果我们频繁的使用B对象,此时我们可以将B对象的创建权交个IOC容器,以后我们在使用B对象的时候,无需从A类中的m1方法获取,直接从IOC容器获得。
常见静态工厂:
public class StaticFactoryBean {
public static UserDao createUserDao() {
//假设:这个对象在jar包中的一个对象
return new UserDaoImpl();
}
}
Beans文件配置:
<bean id="factoryUserDao"
class="com.prim.factory.StaticFactoryBean"
factory-method="createUserDao">
</bean>
- 普通工厂方法实例化
应用场景:和静态工厂方法实例化一样
Beans文件配置:
<!-- 普通工厂实例化 -->
<!-- 先生成工厂类的实例 - 存到IOC容器中 -->
<bean id="dynamicFactory" class="com.prim.factory.DynamicFactoryBean"/>
<!-- 再生产UserDao的实例 - 存到IOC容器中 -->
<bean id="dynamicUserDao" factory-method="createUserDao" factory-bean="dynamicFactory"/>
重点Bean 依赖注入
依赖注入 DI (Dependency Injection):它是Spring框架核心IOC的具体实现。
在我们编写程序时,通过控制反转,把对象的创建交给Spring,但是代码中不可能出现没有依赖的情况。IOC解耦知识降低他们的依赖关系,但不会消除。
例如:业务层仍会调用持久层方法,那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。通过框架把持久层对象传入业务层
,而不用我们自己去获取。
如下例子:业务层来调用持久层例子
public class UserServiceImpl implements UserService {
@Override
public void save() {
//根据之前将的IOC容器中获取 dao的实例类
//将下面的关系,其实可以由Spring框架来维护 持久层和业务层的依赖关系 而不需要我们手动去维护
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
}
}
然后,在Beans的配置文件中,添加业务层由IOC来创建业务层的实例
<bean id="userDao"
class="com.prim.dao.impl.UserDaoImpl">
</bean>
<!-- 配置userService -->
<bean id="userService" class="com.prim.service.impl.UserServiceImpl"/>
模拟Web层调用业务层的代码:
@Test
public void testService() {
// UserService service = new UserServiceImpl();
//
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = (UserService) applicationContext.getBean("userService");
service.save();
}
上述的代码其实别没有彻底的解耦,UserServiceImpl中还需要从IOC容器中获取DAO层的实例,业务层和持久层的依赖关系并没有彻底消除,IOC容器只是帮助我们去创建实例。
业务层和持久层的依赖关系,其实可以由Spring框架来进行维护。通过Spring框架,将持久层对象自动注入到业务层,只需要在配置文件配置持久层对象注入到哪个业务层对象。
- 构造方法
service层的修改
public class UserServiceImpl implements UserService {
//注入UserDao对象
private UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
//根据之前将的IOC容器中获取 dao的实例类
//将下面的关系,其实可以由Spring框架来维护 持久层和业务层的依赖关系 而不需要我们手动去维护
// ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
}
}
beans配置文件的修改:
<!-- userDao 对象 -->
<bean id="userDao"
class="com.prim.dao.impl.UserDaoImpl">
</bean>
<!-- 配置userService 有参构造实例对象 -->
<bean id="userService" class="com.prim.service.impl.UserServiceImpl">
<!-- index: UserServiceImpl的构造参数第一个参数进行赋值
type: 参数的类型
ref: 引用IOC容器中的对象,其实就是和bean标签配置的id值一样
-->
<!-- <constructor-arg index="0" ref="userDao" type="com.prim.dao.UserDao"></constructor-arg>-->
<!--
第二种写法
name: 构造参数的参数名
-->
<constructor-arg name="userDao" ref="userDao"/>
</bean>
测试代码:通过IOC容器获取到UserService实例对象,调用save方法。
@Test
public void testService2() {
//取出Service的实例
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = (UserService) applicationContext.getBean("userService");
service.save();
}
- set方法
service层的代码修改:
public class UserServiceImpl implements UserService {
//注入UserDao对象
private UserDao userDao;
// public UserServiceImpl(UserDao userDao) {
// this.userDao = userDao;
// }
//创建setter方法 注入UserDao对象
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
//根据之前将的IOC容器中获取 dao的实例类
//将下面的关系,其实可以由Spring框架来维护 持久层和业务层的依赖关系 而不需要我们手动去维护
// ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
}
}
beans配置文件修改代码:通过property标签,设置name:就是setter方法的属性名,ref:就是引用对象,也就是要注入的对象
<!-- userDao 对象 -->
<bean id="userDao"
class="com.prim.dao.impl.UserDaoImpl">
</bean>
<!-- 配置userService set方法构造实例对象 -->
<bean id="userService" class="com.prim.service.impl.UserServiceImpl">
<!--
name: set的属性名称
ref: 引用对象
value: 普通类型的数据
-->
<property name="userDao" ref="userDao"/>
</bean>
- P命名空间注入
P命名空间注入本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件。 使用的不多,不推荐使用。
带入如下:
首先引入P命名空间
xmlns:p="http://www.springframework.org/schema/p"
service的配置
<!-- 配置userService set方法构造实例对象 -->
<bean id="userService" class="com.prim.service.impl.UserServiceImpl" p:userDao-ref="userDao">
</bean>
:::tips 依赖注入主要就有两种:构造方法注入、set方法注入。最常用的就是set方法注入 :::
Bean 依赖注入的数据类型
注入的数据的三种数据类型:
- 普通数据类型
- 引用数据类型
- 集合数据类型
引用数据类型,就是上述的操作都是引用数据类型,下面看一下普通数据类型和集合数据类型
- 普通数据类型
注入username和age的普通数据类型
public class UserDaoImpl implements UserDao {
private String username;
private Integer age;
public void setAge(Integer age) {
this.age = age;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public void save() {
System.out.println(username + ":" + age);
System.out.println("保存成功");
}
}
配置文件
<bean id="userDao" class="com.prim.dao.impl.UserDaoImpl"> <property name="username" value="jakeprim"/> <property name="age" value="18"/> </bean>
注入集合数据类型
List集合注入
private List<Object> list; public void setList(List<Object> list) { this.list = list; }
配置文件:
<!-- user 对象 --> <bean id="user" class="com.prim.domin.User"> <property name="username" value="jakeprim"/> <property name="age" value="28"/> </bean> <!-- userDao 对象 --> <bean id="userDao" class="com.prim.dao.impl.UserDaoImpl"> <property name="username" value="jakeprim"/> <property name="age" value="18"/> <property name="list"> <list> <value>aa</value> <!-- 将user对象注入到list集合中 --> <ref bean="user"></ref> </list> </property> </bean>
Set集合注入
Set集合其实和List集合的配置差不多是一样的。
<!-- set集合注入 -->
<property name="set">
<set>
<value>aa</value>
<ref bean="user"></ref>
</set>
</property>
Array数组注入 ```xml private Object[] array;
public void setArray(Object[] array) {
this.array = array;
}
<!-- 数组注入 -->
<property name="array">
<array>
<value>aa</value>
<ref bean="user"></ref>
</array>
</property>
- Map集合注入
```xml
<!-- Map集合注入 -->
<property name="map">
<map>
<entry key="k1" value="kkk"></entry>
<entry key="k2" value-ref="user"></entry>
</map>
</property>
- Properties配置注入
properties都是由String字符串的key和value组成的. {p2=v2, p1=v1}
<!-- properties注入 -->
<property name="properties">
<props>
<prop key="p1">v1</prop>
<prop key="p2">v2</prop>
</props>
</property>
配置文件模块化
在实际开发中,spring的配置内容非常多,这就导致spring配置很繁杂并且体积很大。可以将部分配置拆解到其他配置文件中,就是
配置文件模块化
- 并列多个配置文件
将配置文件按层分,service层配置,dao层配置
public void testService2() {
//取出Service的实例
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml",
"applicationContext-service.xml","applicationContext-dao.xml");
UserService service = (UserService) applicationContext.getBean("userService");
service.save();
}
- 主从配置文件
在applicationContext
的主配置文件,引入子配置文件
<!-- import标签 设置加载的配置文件 -->
<import resource="classpath:applicationContext-user.xml"/>
<import resource="classpath:applicationContext-product.xml"/>
:::tips 注意多个配置文件,出现相同id的bean不会报错,但是后加载的bean会覆盖前面加载的bean :::
<bean>
标签:创建对象并放到spring的IOC容器
id: 在容器中Bean实例的唯一标识,不允许重复
class:要实例化的Bean的全限定名
scope:Bean的作用范围,常用:singleton(默认)和prototype
<constructor-arg>
标签 :属性注入
name:属性名称
value: 注入的普通属性值
ref: 注入的对象引用值
<property>
标签:属性注入
name : 属性名称
value: 注入的普通属性值
ref: 注入的对象引用值
<import>
标签:导入其他的spring的配置分文件
IOC 实战
Spring-context整合DbUtils来实现对数据库的操作。
1.引入依赖:
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
- 创建DAO层和Service层,这里代码还是比较简单的
DAO 层代码实现如下:通过Spring注入QueryRunner对象
public class AccountDaoImpl implements AccountDao {
//通过Spring 注入QueryRunner对象
private QueryRunner queryRunner;
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
@Override
public List<Account> findAll() {
List<Account> accounts = null;
try {
accounts = queryRunner.query("select * from account", new BeanListHandler<>(Account.class));
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return accounts;
}
@Override
public Account findById(Integer id) {
String sql = "select * from account where id = ?";
Account account = null;
try {
account = queryRunner.query(sql, new BeanHandler<>(Account.class), id);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return account;
}
@Override
public void save(Account account) {
String sql = "insert into account(name,money) values(?,?)";
try {
int update = queryRunner.update(sql, account.getName(), account.getMoney());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
@Override
public void update(Account account) {
String sql = "update account set name=?,money=? where id = ?";
try {
int update = queryRunner.update(sql, account.getName(), account.getMoney(), account.getId());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
@Override
public void delete(Integer id) {
String sql = "delete from account where id = ?";
try {
int update = queryRunner.update(sql, id);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
Service层代码实现如下:
将DAO的实例注入
public class AccountServiceImpl implements AccountService {
//通过Spring 注入AccountDao实例
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAll() {
return accountDao.findAll();
}
@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}
@Override
public void save(Account account) {
accountDao.save(account);
}
@Override
public void update(Account account) {
accountDao.update(account);
}
@Override
public void delete(Integer id) {
accountDao.delete(id);
}
}
配置数据库数据properties文件
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db?characterEncoding=utf-8 jdbc.username=root jdbc.password=123456
配置
applicationContext.xml
Spring配置
- 加载数据库配置文件
- 创建数据源对象
- 创建
QueryRunner
对象 - 创建DAO对象
创建Service对象 ```xml <?xml version=”1.0” encoding=”UTF-8” ?>
<!-- 创建 DataSource 数据源对象 -->
<!-- 抽取数据库配置文件 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 创建 QueryRunner 对象 -->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<!-- 数据源对象DataSource 通过构造函数的方式注入 -->
<constructor-arg name="ds" ref="dataSource"/>
</bean>
<!-- DAO层bean实例化 -->
<bean id="accountDao" class="com.example.dao.impl.AccountDaoImpl">
<!-- set方式 注入QueryRunner对象 -->
<property name="queryRunner" ref="queryRunner"/>
</bean>
<!-- service层bean实例化 -->
<bean id="accountService" class="com.example.service.impl.AccountServiceImpl">
<!-- set 方式 注入AccountDao对象 -->
<property name="accountDao" ref="accountDao"/>
</bean>
> 上述的代码,就将DbUtils和Spring IOC 整合了在一起,这样确实大大简化了我们的开发
测试:通过`ClassPathXmlApplicationContext` 获取IOC容器,拿到Service层的实例,进行操作
```java
@org.junit.Test
public void test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService service = (AccountService) context.getBean("accountService");
List<Account> accounts = service.findAll();
System.out.println(accounts);
}
Spring 注解开发
Spring是轻代码而重配置的框架,配置比较繁重同样也会影响开发效率。所以spring 注解开发来代替xml配置文件简化配置,提高开发效率
Spring 常用注解
spring常用的注解主要替代
<bean>
的配置。
注解 | 说明 |
---|---|
@Component | 使用在类上用于实例化bean |
@Controller | 使用在web层类上用于实例化bean |
@Service | 使用在service层类上用于实例化bean |
@Repository | 使用在dao层类上用于实例化bean |
@Autowired | 使用在字段上用于根据类型依赖注入,如果使用类型匹配到多个实例会根据变量名去匹配实例,如果没有该实例就会报错。 |
@Qualifier | 结合@Autowired一起使用,根据名称ID进行依赖注入。注意这个注解是不能单独使用的 |
@Resource | 相当于@Autowired+@Qualifier,按照名称进行注入。注意在JDK11以后完全移除了javax扩展导致不能使用@Resource注解。如果要使用需要maven引入依赖:<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency> |
@Value | 注入普通属性 |
@Scope | 标注bean的作用范围 |
@PostConstruct | 使用在方法上标注该方法是bean的初始化方法 |
@PreDestroy | 使用在方法上标注该方法是bean的销毁方法 |
:::tips 注意:使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,指定哪个包及其子包下的bean需要进行扫描以识别使用注解配置的类、字段和方法。 :::
<context:component-scan base-package="com.example"></context:component-scan>
常用注解的示例代码:
我们将上述的Service层的代码,配置文件移除配置通过注解来进行配置
//@Service == <bean> 标签的配置,默认的id class = AccountServiceImpl
@Service("accountService")//value == id 配置就是id
@Scope("singleton")//作用域配置为单例
public class AccountServiceImpl implements AccountService {
//通过Spring 注入AccountDao实例
// @Autowired //根据类型进行注入
// @Qualifier("accountDao")//根据类型匹配到多个实例时,会根据设置的值进行二次匹配
@Resource(name = "accountDao") // 根据name值,直接从IOC容器中进行匹配
private AccountDao accountDao;
@Value("注入普通属性")
private String str1;
@Value("${jdbc.driverClassName}") //注入properties文件中的配置,因为我们已经在applicationContext.xml中加载了该文件可以直接使用
private String driver;
@PostConstruct
private void init() {
System.out.println("方法初始化");
}
@PreDestroy
private void destroy() {
System.out.println("方法销毁");
}
@Override
public List<Account> findAll() {
System.out.println(str1);//注入普通属性
System.out.println(driver);//com.mysql.jdbc.Driver
return accountDao.findAll();
}
@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}
@Override
public void save(Account account) {
accountDao.save(account);
}
@Override
public void update(Account account) {
accountDao.update(account);
}
@Override
public void delete(Integer id) {
accountDao.delete(id);
}
}
DAO层的代码配置,从applicationContext.xml
中移除DAO的层的配置,通过注解实现
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
//通过Spring 注入QueryRunner对象
@Autowired
private QueryRunner queryRunner;
@Override
public List<Account> findAll() {
List<Account> accounts = null;
try {
accounts = queryRunner.query("select * from account", new BeanListHandler<>(Account.class));
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return accounts;
}
@Override
public Account findById(Integer id) {
String sql = "select * from account where id = ?";
Account account = null;
try {
account = queryRunner.query(sql, new BeanHandler<>(Account.class), id);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return account;
}
@Override
public void save(Account account) {
String sql = "insert into account(name,money) values(?,?)";
try {
int update = queryRunner.update(sql, account.getName(), account.getMoney());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
@Override
public void update(Account account) {
String sql = "update account set name=?,money=? where id = ?";
try {
int update = queryRunner.update(sql, account.getName(), account.getMoney(), account.getId());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
@Override
public void delete(Integer id) {
String sql = "delete from account where id = ?";
try {
int update = queryRunner.update(sql, id);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
spring核心配置文件修改如下:
DAO层和Service层的配置就不需要了,通过注解可以大大的减少配置文件的配置。 注意:一定要开启包注解扫描。
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.example"></context:component-scan>
<!-- 引入jdbc.properties
注意:如果要引入外部的文件都需要加上classpath:这个前缀
-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 创建 DataSource 数据源对象 -->
<!-- 抽取数据库配置文件 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 创建 QueryRunner 对象 -->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<!-- 数据源对象DataSource 通过构造函数的方式注入 -->
<constructor-arg name="ds" ref="dataSource"/>
</bean>
<!-- DAO层bean实例化 -->
<!-- <bean id="accountDao" class="com.example.dao.impl.AccountDaoImpl">-->
<!-- <!– set方式 注入QueryRunner对象 –>-->
<!-- <property name="queryRunner" ref="queryRunner"/>-->
<!-- </bean>-->
<!-- <bean id="accountDao2" class="com.example.dao.impl.AccountDaoImpl"></bean>-->
<!-- service层bean实例化 -->
<!-- <bean id="accountService" class="com.example.service.impl.AccountServiceImpl">-->
<!-- <!– set 方式 注入AccountDao对象 –>-->
<!-- <property name="accountDao" ref="accountDao"/>-->
<!-- </bean>-->
</beans>
思考:在spring的核心配置文件中,还存在着一些配置,并没有全部用注解代替,那么能否基于纯注解的方式呢?
使用上面的注解还不能全替代xml配置文件,还需要使用注解替代的配置:
- 非自定义的Bean的配置
- 加载properties文件的配置
- 组件扫描的配置
- 引入其他文件
注解 | 说明 |
---|---|
@Configuration | 用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解 |
@Bean | 使用在方法上,标注将该方法的返回值存储到Spring容器中 |
@PropertySource | 用于加载properties文件中的配置 |
@CommponentScan | 用于指定Spring在初始化容器时要扫描的包 |
@import | 用于导入其他配置类 |
创建配置类:
@Configuration //标记该类为核心配置类
@ComponentScan("com.example") // 开启扫描包及子包注解
@PropertySource("classpath:jdbc.properties")// 加载properties文件
public class SpringConfig {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name = "dataSource") //如果不写id 默认为方法名
public DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean(name = "queryRunner")
public QueryRunner getQueryRunner(@Autowired DataSource dataSource) {
return new QueryRunner(dataSource);
}
}
测试:AnnotationConfigApplicationContext
加载注解方式的核心配置类
//纯注解的方式 加载核心配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService service = (AccountService) context.getBean("accountService");
List<Account> accounts = service.findAll();
System.out.println(accounts);
context.close();
配置类拆分,在上述代码中,我们的核心配置类寄存在其他配置和数据库的配置,那么如何将数据库的配置进行拆分呢?和xml方法的配置文件模块化类似。
如下代码:拆分一个数据库配置类
@PropertySource("classpath:jdbc.properties")// 加载properties文件
public class DataSourceConfig {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name = "dataSource") //如果不写id 默认为方法名
public DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
核心配置类,通过@Import
引入其他的子配置类
@Configuration //标记该类为核心配置类
@ComponentScan("com.example") // 开启扫描包及子包注解
@Import({DataSourceConfig.class}) // 引入子配置类
public class SpringConfig {
@Bean(name = "queryRunner")
public QueryRunner getQueryRunner(@Autowired DataSource dataSource) {
return new QueryRunner(dataSource);
}
}
Spring 整合Junit
在之前的代码中,通过Junit进行编写测试代码,都需要创建spring的API对象,那么有什么简洁的方法,来方便测试spring的相关代码。
- 导入spring-test依赖
spring5及以上版本要求Junit版本在4.12及以上
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
- 使用
@Runwith
注解替换原来的运行器 - 使用
@ContextConfiguration
指定配置文件或配置类 使用
@Autowired
注入需要测试的对象//SpringJUnit4ClassRunner是spring提供的作为junit运行环境的类 @RunWith(SpringJUnit4ClassRunner.class) //设置核心配置类 @ContextConfiguration(classes = {SpringConfig.class}) //设置核心配置文件 //@ContextConfiguration({"classpath:applicationContext.xml"}) public class Test { @Autowired //注入依赖 AccountService accountService; @org.junit.Test public void test() { //XML的方式 加载核心配置文件 // ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-anno.xml"); //纯注解的方式 加载核心配置类 // AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); // AccountService service = (AccountService) context.getBean("accountService"); List<Account> accounts = accountService.findAll(); System.out.println(accounts); } }
知识总结和复习:
- 需要掌握IOC容器的实现原理
- Spring相关的API以及
标签的相关配置以及bean的生命周期配置 - Bean的依赖注入 xml方式需要知道
- Spring核心配置文件常用的标签以及配置文件模块化
- Spring 注解开发中,常用的注解一定要掌握以及核心配置类中使用到的注解了解即可
- 实际开发中Spring IOC 通常会采用XML+注解的方式