Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IOC 和 AOP 为内核,提供了展现层 Spring
MVC 和业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,已
经成为使用最多的 Java EE 企业应用开源框架
我们经常说的 Spring 其实指的是Spring Framework(spring 框架)。
整个 Spring 优势,传达出一个信号,Spring 是一个综合性,且有很强的思想性框架,每学习一天,就能体会到它的一些优势。
Spring是一个分层异常清晰并且依赖关系、职责定位异常明确的轻量级框架,主要包括几个大模块:数
据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块
和 Test 模块,如下图所示,Spring依靠这些基本模块,实现了一个令人愉悦的融合了现有解决方案的零
侵入的轻量级框架。
注意:IOC和AOP不是spring提出的,在spring之前就已经存在,只不过更偏向于理论化,spring在技
术层次把这两个思想做了非常好的实现(Java)
IoC Inversion of Control (控制反转/反转控制),注意它是一个技术思想,不是一个技术实现
传统开发方式:比如类A依赖于类B,往往会在类A中new一个B的对象
IoC思想下开发方式:我们不用自己去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对
象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可
我们丧失了一个权利(创建、管理对象的权利),得到了一个福利(不⽤考虑对象的创建、管理等一系列
事情)
为什么叫做控制反转?
控制:指的是对象创建(实例化、管理)的权利
反转:控制权交给外部环境了(spring框架、IoC容器)
DI:Dependancy Injection(依赖注入)
怎么理解:
IOC和DI描述的是同一件事情,只不过⻆度不一样罢了
AOP: Aspect oriented Programming 面向切面编程/面向接口编程
AOP是OOP的延续,从OOP说起
OOP三个特征:封装、继承和多态
oop是一种垂直继承体系
OOP编程思想可以解决大多数的代码重复问题,但是有一些情况是处理不了的,比如下,面的在顶级父类Animal中的多个方法中相同位置出现了重复代码,OOP就解决不了
横切逻辑代码存在什么问题:
AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
代码拆分容易,那么如何在不改变原有业务逻辑的情况下,悄无声息的把横切逻辑代码应用到原有的业
务逻辑中,达到和原来一样的效果,这个是⽐较难的
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以面向横切逻辑
「面」:横切逻辑代码往往要影响的是很多个方法,每一个方法都如同一个点,多个点构成面,有一个
面的概念在里面
上⼀部分我们理解了 IoC 和 AOP 思想,我们先不考虑 Spring 是如何实现这两个思想的,此处准备了一个『银行转账』的案例,请分析该案例在代码层次有什么问题 ?分析之后使⽤我们已有知识解决这些问
题(痛点)。其实这个过程我们就是在一步步分析并⼿写实现 IoC 和 AOP。
package com.lagou.edu.servlet;
import com.lagou.edu.service.impl.TransferServiceImpl;
import com.lagou.edu.utils.JsonUtils;
import com.lagou.edu.pojo.Result;
import com.lagou.edu.service.TransferService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 1. 实例化service层对象
private TransferService transferService = new TransferServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
// 设置请求体的字符编码
req.setCharacterEncoding("UTF-8");
String fromCardNo = req.getParameter("fromCardNo");
String toCardNo = req.getParameter("toCardNo");
String moneyStr = req.getParameter("money");
int money = Integer.parseInt(moneyStr);
Result result = new Result();
try {
// 2. 调⽤service层⽅法
transferService.transfer(fromCardNo,toCardNo,money);
result.setStatus("200");
} catch (Exception e) {
e.printStackTrace();
result.setStatus("201");
result.setMessage(e.toString());
}
// 响应
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(JsonUtils.object2Json(result));
}
}
package com.lagou.edu.service;
public interface TransferService {
void transfer(String fromCardNo,String toCardNo,int money) throws Exception;
}
package com.lagou.edu.service.impl;
import com.lagou.edu.dao.AccountDao;
import com.lagou.edu.dao.impl.JdbcAccountDaoImpl;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.service.TransferService;
/**
* @author 应癫
*/
public class TransferServiceImpl implements TransferService {
private AccountDao accountDao = new JdbcAccountDaoImpl();
@Override
public void transfer(String fromCardNo, String toCardNo, int money)
throws Exception {
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
from.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
accountDao.updateAccountByCardNo(from);
accountDao.updateAccountByCardNo(to);
}
}
//AccountDao层接口及基于Jdbc的实现类
package com.lagou.edu.dao;
import com.lagou.edu.pojo.Account;
public interface AccountDao {
Account queryAccountByCardNo(String cardNo) throws Exception;
int updateAccountByCardNo(Account account) throws Exception; }
}
package com.lagou.edu.dao.impl;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.dao.AccountDao;
import com.lagou.edu.utils.DruidUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author yecxf
*/
public class JdbcAccountDaoImpl implements AccountDao {
@Override
public Account queryAccountByCardNo(String cardNo) throws Exception {
//从连接池获取连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "select * from account where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setString(1,cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
while(resultSet.next()) {
account.setCardNo(resultSet.getString("cardNo"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getInt("money"));
}
resultSet.close();
preparedStatement.close();
con.close();
return account;
}
@Override
public int updateAccountByCardNo(Account account) throws Exception {
//从连接池获取连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "update account set money=? where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1,account.getMoney());
preparedStatement.setString(2,account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
con.close();
return i;
}
}
(1)问题一:在上述案例实现中,service 层实现类在使用 dao 层对象时,直接在
TransferServiceImpl 中通过 AccountDao accountDao = new JdbcAccountDaoImpl() 获得了 dao层对
象,然而一个 new 关键字却将 TransferServiceImpl 和 dao 层具体的一个实现类
JdbcAccountDaoImpl 耦合在了一起,如果说技术架构发生一些变动,dao 层的实现要使用其它技术,
比如 Mybatis,思考切换起来的成本?每一个 new 的地方都需要修改源代码,重新编译,面向接口开发
的意义将大打折扣?
(2)问题二:service 层代码没有竟然还没有进行事务控制 ?!如果转账过程中出现异常,将可能导致
数据库数据错乱,后果可能会很严重,尤其在金融业务。
针对问题一思考:
考虑使用设计模式中的工厂模式解耦合,另外项目中往往有很多对象需要实例化,那就在工厂中使
用反射技术实例化对象,工厂模式很合适
更进一步,代码中能否只声明所需实例的接⼝类型,不出现 new 也不出现工厂类的字眼,如下
图? 能!声明一个变量并提供 set 方法,在反射的时候将所需要的对象注入进去吧
(1)针对问题一的代码改造
<beans>
<bean id="transferService"
class="com.lagou.edu.service.impl.TransferServiceImpl">
<property name="AccountDao" ref="accountDao">property>
bean>
<bean id="accountDao"
class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">
bean>
beans>
package com.lagou.edu.factory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author yecxf
*/
public class BeanFactory {
/**
* 接口类的两个任务
* 任务一:加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放入map待用
* 任务二:提供接口方法根据id从map中获取bean(静态方法)
*/
private static Map<String,Object> map = new HashMap<>();
static {
InputStream resourceAsStream =
BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("//bean");
// 实例化bean对象
for (int i = 0; i < list.size(); i++) {
Element element = list.get(i);
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
Class<?> aClass = Class.forName(clazz);
Object o = aClass.newInstance();
map.put(id,o);
}
// 维护bean之间的依赖关系
List<Element> propertyNodes =
rootElement.selectNodes("//property");
for (int i = 0; i < propertyNodes.size(); i++) {
Element element = propertyNodes.get(i);
// 处理property元素
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");
String parentId =
element.getParent().attributeValue("id");
Object parentObject = map.get(parentId);
Method[] methods = parentObject.getClass().getMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if(("set" + name).equalsIgnoreCase(method.getName()))
{
// bean之间的依赖关系(注入bean)
Object propertyObject = map.get(ref);
method.invoke(parentObject,propertyObject);
}
}
// 维护依赖关系后重新将bean放入map中
map.put(parentId,parentObject);
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public static Object getBean(String id) {
return map.get(id);
}
}
(2)针对问题二的改造
package com.lagou.edu.utils;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author yecxf
*/
public class ConnectionUtils {
/*private ConnectionUtils() {
}
private static ConnectionUtils connectionUtils = new
ConnectionUtils();
public static ConnectionUtils getInstance() {
return connectionUtils;
}*/
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); //存储当前线程的连接
/**
* 从当前线程获取连接
*/
public Connection getCurrentThreadConn() throws SQLException {
/**
* 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取⼀个连接绑定到
当前线程
*/
Connection connection = threadLocal.get();
if(connection == null) {
// 从连接池拿连接并绑定到线程
connection = DruidUtils.getInstance().getConnection();
// 绑定到当前线程
threadLocal.set(connection);
}
return connection;
}
}
package com.lagou.edu.utils;
import java.sql.SQLException;
/**
* @author yecxf
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
// 开启事务
public void beginTransaction() throws SQLException {
connectionUtils.getCurrentThreadConn().setAutoCommit(false);
}
// 提交事务
public void commit() throws SQLException {
connectionUtils.getCurrentThreadConn().commit();
}
// 回滚事务
public void rollback() throws SQLException {
connectionUtils.getCurrentThreadConn().rollback();
}
}
package com.lagou.edu.factory;
import com.lagou.edu.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author yecxf
*/
public class ProxyFactory {
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager
transactionManager) {
this.transactionManager = transactionManager;
}
public Object getProxy(Object target) {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
// 开启事务
transactionManager.beginTransaction();
// 调⽤原有业务逻辑
result = method.invoke(target,args);
// 提交事务
transactionManager.commit();
}catch(Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 异常向上抛出,便于servlet中捕获
throw e.getCause();
}
return result;
}
});
}
}
<bean id="accountDao"
class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">
<property name="ConnectionUtils" ref="connectionUtils"/>
bean>
<bean id="transferService"
class="com.lagou.edu.service.impl.TransferServiceImpl">
<property name="AccountDao" ref="accountDao">property>
bean>
<bean id="connectionUtils"
class="com.lagou.edu.utils.ConnectionUtils">bean>
<bean id="transactionManager"
class="com.lagou.edu.utils.TransactionManager">
<property name="ConnectionUtils" ref="connectionUtils"/>
bean>
<bean id="proxyFactory" class="com.lagou.edu.factory.ProxyFactory">
<property name="TransactionManager" ref="transactionManager"/>
bean>
beans>
package com.lagou.edu.dao.impl;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.dao.AccountDao;
import com.lagou.edu.utils.ConnectionUtils;
import com.lagou.edu.utils.DruidUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author yecxf
*/
public class JdbcAccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public Account queryAccountByCardNo(String cardNo) throws Exception {
//从连接池获取连接
// Connection con = DruidUtils.getInstance().getConnection();
Connection con = connectionUtils.getCurrentThreadConn();
String sql = "select * from account where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setString(1,cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
while(resultSet.next()) {
account.setCardNo(resultSet.getString("cardNo"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getInt("money"));
}
resultSet.close();
preparedStatement.close();
//con.close();
return account;
}
@Override
public int updateAccountByCardNo(Account account) throws Exception {
// 从连接池获取连接
// 改造为:从当前线程当中获取绑定的connection连接
//Connection con = DruidUtils.getInstance().getConnection();
Connection con = connectionUtils.getCurrentThreadConn();
String sql = "update account set money=? where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1,account.getMoney());
preparedStatement.setString(2,account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
//con.close();
return i;
}
}
package com.lagou.edu.servlet;
import com.lagou.edu.factory.BeanFactory;
import com.lagou.edu.factory.ProxyFactory;
import com.lagou.edu.service.impl.TransferServiceImpl;
import com.lagou.edu.utils.JsonUtils;
import com.lagou.edu.pojo.Result;
import com.lagou.edu.service.TransferService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author yecxf
*/
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 1. 实例化service层对象
//private TransferService transferService = new TransferServiceImpl();
//private TransferService transferService = (TransferService)
//BeanFactory.getBean("transferService");
// 从⼯⼚获取委托对象(委托对象是增强了事务控制的功能)
// ⾸先从BeanFactory获取到proxyFactory代理⼯⼚的实例化对象
private ProxyFactory proxyFactory = (ProxyFactory)
BeanFactory.getBean("proxyFactory");
private TransferService transferService = (TransferService)
proxyFactory.getJdkProxy(BeanFactory.getBean("transferService")) ;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
// 设置请求体的字符编码
req.setCharacterEncoding("UTF-8");
String fromCardNo = req.getParameter("fromCardNo");
String toCardNo = req.getParameter("toCardNo");
String moneyStr = req.getParameter("money");
int money = Integer.parseInt(moneyStr);
Result result = new Result();
try {
// 2. 调⽤service层⽅法
transferService.transfer(fromCardNo,toCardNo,money);
result.setStatus("200");
} catch (Exception e) {
e.printStackTrace();
result.setStatus("201");
result.setMessage(e.toString());
}
// 响应
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(JsonUtils.object2Json(result));
}
}
BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽
ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能
的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的一级接口,比
BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等等
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.3.16.RELEASEversion>
dependency>
启动 IoC 容器的方式
// 从类路径下加载配置文件
@Test
public void testClasspathSpringIOC(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
TransferMoney transferMoney = (TransferMoney) applicationContext.getBean("transferMoney");
transferMoney.transferMoney();
}
// 从文件路径下加载配置文件
@Test
public void testFilePathSpringIOC(){
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("C:\\Users\\Administrator\\IdeaProjects\\SpringDemo\\SpringIOC\\src\\main\\resources\\applicationContext.xml");
TransferMoney transferMoney = (TransferMoney) applicationContext.getBean("transferMoney");
transferMoney.transferMoney();
}
<web-app>
<display-name>Archetype Created Web Applicationdisplay-name>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:applicationContext.xmlparam-value>
context-param>
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListenerlistenerclass>
listener>
web-app>
<web-app>
<display-name>Archetype Created Web Applicationdisplay-name>
<context-param>
<param-name>contextClassparam-name>
<paramvalue>org.springframework.web.context.support.AnnotationConfigWebAppli
cationContextparam-value>
context-param>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>com.lagou.edu.SpringConfigparam-value>
context-param>
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListenerlistenerclass>
listener>
web-app>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
实例化Bean的三种方式
方式一:使用无参构造函数
在默认情况下,它会通过反射调用无参构造函数来创建对象。如果类中没有参构造函数,将创建
失败。
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
bean>
例如,我们在做Jdbc操作时,会⽤到java.sql.Connection接⼝的实现类,如果是mysql数据库,那
么⽤的就 是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = new
JDBC4Connection() ,因 为我们要注册驱动,还要提供URL和凭证信息,
用 DriverManager.getConnection 方法来获取连接
那么在实际开发中,尤其早期的项目没有使用Spring框架来管理对象的创建,但是在设计时使用了
工厂模式 解耦,那么当接入spring之后,工厂类创建对象就具有和上述例子相同特征,即可采用
此种方式配置。
<bean id="userService" class="com.lagou.factory.BeanFactory"
factory-method="getTransferService">bean>
<bean id="beanFactory" class="com.lagou.factory.instancemethod.BeanFactory">bean>
<bean id="transferService" factory-bean="beanFactory" factorymethod="getTransferService">bean>
在上图中提供的这些选项中,我们实际开发中用到最多的作用范围就是singleton(单例模式)和
prototype(原型模式,也叫多例模式)。配置方式参考下面的代码:
<bean id="transferService"
class="com.lagou.service.impl.TransferServiceImpl" scope="singleton">
bean>
factory-bean属性:用于指定创建当前bean对象的工厂bean的唯一标识。当指定了此属性之后,
class属性失效。
factory-method属性:用于指定创建当前bean对象的工厂方法,如配合factory-bean属性使用,
则class属性失效。如配合class属性使用,则方法必须是static的。
scope属性:用于指定bean对象的作⽤范围。通常情况下就是singleton。当要用到多例模式时,
可以配置为prototype。
init-method属性:用于指定bean对象的初始化方法,此方法会在bean对象装配后调用。必须是
一个无参方法。
destory-method属性:用于指定bean对象的销毁方法,此方法会在bean对象销毁前执行。它只
能为scope是singleton时起作用。
注意:
1)实际企业开发中,纯xml模式使用已经很少了
2)引入注解功能,不需要引入额外的jar
3)xml+注解结合模式,xml文件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
4)哪些bean的定义写在xml中,哪些bean的定义使用注解
第三方jar中的bean定义在xml,比如德鲁伊数据库连接池
自己开发的bean定义使用注解
xml形式 | 对应的注解形式 |
---|---|
标签 | @Component(“accountDao”),注解加在类上bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名首字母小写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的方法完全一样,只是为了更清晰的区分而已 |
标签的scope属性 | @Scope(“prototype”),默认单例,注解加在类上 |
标签的 | |
init method属性 | @PostConstruct,注解加在方法上,该方法就是初始化后调用的方法 |
标签的destory method属性 | @PreDestory,注解加在方法上,该⽅法就是销毁前调用的方法 |
在使⽤构造函数注⼊时,涉及的标签是 constructor-arg ,该标签有如下属性:
name:用于给构造函数中指定名称的参数赋值。
index:用于给构造函数中指定索引位置的参数赋值。
value:用于指定基本类型或者String类型的数据。
ref:用于指定其他Bean类型的数据。写的是其他bean的唯一标识。
依赖注入的配置实现之se方法注入
顾名思义,就是利用字段的se方法实现赋值的注入的放式。此方式在实际开发中是使用最多的注
入方式。
注意:
1)实际企业开发中,纯xml模式使用已经很少了
2)引入注解功能,不需要引用额外的jar
3)xml+注解结合模式,xml文件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
xml形式 | 对应的注解形式 |
---|---|
标签 | @Component(“accountDao”),注解加在类上bean的id属性内容直接配置在注解后,如果不配置,默认定义这个bean的id为类的类名字首字母小写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别用于控制层类、服务层类、dao层类的bean定义,这四个注解的方法完全一样,只是为了更清晰的区分而已 |
标签的scope属 性 | @Scope(“prototype”),默认单例,注解加在类上标签的 |
initmethod属性 | @PostConstruct,注解加在方法上,该方法就是初始化后调用的方法 |
destorymethod属性 | @PreDestory,注解加在方法上,该方法就是销毁前调用的方法 |
DI 依赖注入的注解实现方式
@Autowired(推荐使用)
@Autowired为Spring提供的注解,需要导入包
org.springframework.beans.factory.annotation.Autowired
@Autowired采取的策略为按照类型注入。
public class TransferServiceImpl {
@Autowired
private AccountDao accountDao; }
如上代码所示,这样装配回去spring容器中找到类型为AccountDao的类,然后将其注入进来。这
样会产一个问题,当一个类型有多个bean值的时候,会造成无法选择具体注入哪一个的情况,
这个时候我们需要配合着@Qualifier使用。
@Qualifier告诉Spring具体去装配哪个对象。
public class TransferServiceImpl {
@Autowired
@Qualifier(name="jdbcAccountDaoImpl")
private AccountDao accountDao; }
这个时候我们就可以通过类型和名称定位到我们想注入的对象。
@Resource
@Resource 注解由 J2EE 提供,需要导入包 javax.annotation.Resource。
@Resource 默认按照 ByName 自动注入。
public class TransferService {
@Resource
private AccountDao accountDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}
注意:
@Resource 在 Jdk 11中已经移除,如果要使用,需要单独引用jar包
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动
对应注解
@Configuration 注解,表示当前类是一个配置类
@ComponentScan 注解,替代 context:component-scan
@PropertySource,引入外部属性配置文件
@Import 引入其他配置类
@Value 对变量赋值,可以直接赋值,也可以使用 ${} 读取资源配置文件中的信息
@Bean 将方法返回对象加入 SpringIOC 容器
Bean的延迟加载(延迟创建)
ApplicationContext 容器的默认行为是在启动服务器时将所有 singleton bean 提前进行实例化。提前
实例化意味着作为初始化过程的一部分,ApplicationContext 实例会创建并配置所有的singleton
bean。
比如:
该bean默认的设置为:
lazy-init=“false”,立即加载,表示在spring启动时,即刻进行实例化。
如果不想让某个singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean
设置为延迟实例化。
设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,而是第一次向容器
通过 getBean 索取 bean 时实例化的。
如果一个设置了立即加载的 bean1,引用了一个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例
化,而bean2 由于被 bean1 引用,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤
时才被实例化的规则。
也可以在容器层次中通过在 元素上使用 “default-lazy-init” 属性来控制延时初始化。如下⾯配置:
如果一个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不
会实例化bean,而是调用 getBean 方法实例化的。
应用场景
(1)开启延迟加载⼀定程度提⾼容器启动和运转性能(非主要)
(2)对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从一开始该 Bean 就占
用资源
BeanFactory接口是容器的顶级接口,定义了容器的一些基础行为,负责生产和管理Bean的一个工厂,
具体使用它下面的子接口类型,比如ApplicationContext;
此处我们重点分析FactoryBean
Spring中Bean有两种,一种是普通Bean,一种是工厂Bean(FactoryBean),FactoryBean可以生成
某一个类型的Bean实例(返回给我们),也就是说我们可以借助于它自定义Bean的创建过程。
Bean创建的三种方式中的静态方法和实例化方法和FactoryBean作用类似,FactoryBean使用较多,尤
其在Spring框架一些组件中会使用,还有其他框架和Spring框架整合时使用
// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
@Nullable
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器
的单例对象缓存池中Map
T getObject() throws Exception;
@Nullable
// 返回FactoryBean创建的Bean类型
Class<?> getObjectType();
// 返回作⽤域是否单例
default boolean isSingleton() {
return true;
}
}
Company类
package com.lagou.edu.factorybean;
/**
* author yecxf
*/
public class Company {
private String name; // 名字
private Integer age; // 创建时间
private String scale; // 规模
public Company(){};
public Company(String name, Integer age, String scale) {
this.name = name;
this.age = age;
this.scale = scale;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getScale() {
return scale;
}
public void setScale(String scale) {
this.scale = scale;
}
}
CompanyFactoryBean类
package com.lagou.edu.factorybean;
import org.springframework.beans.factory.FactoryBean;
public class CompanyFactory implements FactoryBean<Company> {
private String name;
private int age;
private String scale;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getScale() {
return scale;
}
public void setScale(String scale) {
this.scale = scale;
}
/**
* 创建复杂Bean逻辑
* @return
* @throws Exception
*/
@Override
public Company getObject() throws Exception {
return new Company(name,age,scale);
}
@Override
public Class<?> getObjectType() {
return Company.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
xml配置
<bean id="companyBean" class="com.lagou.edu.factorybean.CompanyFactory">
<property name="name" value="叶存星福的公司">property>
<property name="age" value="12">property>
<property name="scale" value="123124">property>
bean>
测试:
@Test
public void testFactoryBean(){
System.out.println(companyBean.toString());
}
测试,获取FactoryBean,需要在id之前添加“&"
@Test
public void testGetFactory(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
CompanyFactory bean = (CompanyFactory) classPathXmlApplicationContext.getBean("&companyBean");
System.out.println(bean);
}
Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和
BeanFactoryPostProcessor,两者在使用上是有所区别的。
工厂初始化(BeanFactory)—> Bean对象
在BeanFactory初始化之后可以使用BeanFactoryPostProcessor进行后置处理做一些事情
在Bean对象实例化(并不是Bean的整个生命周期完成)之后可以使用BeanPostProcessor进行后置处
理做一些事情
注意:对象不一定是springbean,但springbean一定是个对象
SpringBean的生命周期
2.3.1 BeanPostProcessor
BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean.
该接⼝提供了两个方法,分别在Bean的初始化方法前和初始化方法后执行,具体这个初始化方法指的是
什么方法,类似我们在定义bean时,定义了init-method所指定的方法
定义一个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进行处理。如果要对
具体的某个bean处理,可以通过方法参数判断,两个类型参数分别为Object和String,第一个参数是每
个bean的实例,第二个参数是每个bean的name或者id属性的值。所以我们可以通过第二个参数,来判
断我们将要处理的具体的bean。
注意:处理是发生在Spring容器的实例化和依赖注入之后
2.3.2 BeanFactoryPostProcessor
BeanFactory级别的处理,是针对整个Bean的工厂进行处理,典型应
用:PropertyPlaceholderConfigurer
此接口只提供了一个方法,方法参数为ConfigurableListableBeanFactory,该参数类型定义了一些方法
其中有个方法名为getBeanDefinition的方法,我们可以根据此方法,找到我们定义bean 的
BeanDefinition对象。然后我们可以对定义的属性进行修改,以下是BeanDefinition中的方法
方法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿
到BeanDefinition对象时,我们可以手动修改bean标签中所定义的属性值。
BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,
这个JavaBean 就是 BeanDefinition
注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成
BeanDefinition对象
IoC容器是Spring的核心模块,是抽象了对象管理、依赖关系管理的框架解决方案。Spring 提供了很多
的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IoC 容器 必须遵从
的一套原则,具体的容器实现可以增加额外的功能,比如我们常看到的ApplicationContext,其下更具
体的实现如 ClassPathXmlApplicationContext 包含了解析 xml 等一系列的内容,
AnnotationConfigApplicationContext 则是包含了注解解析等⼀系列的内容。Spring IoC 容器继承体系
非常聪明,需要使用哪个层次调用哪个层次即可,不必使用功能大而全的。
BeanFactory 顶级接口方法栈如下
BeanFactory 容器继承体系
通过其接口设计,我们可以看到我们一贯使用的 ApplicationContext 除了继承BeanFactory的⼦接⼝,
还继承了ResourceLoader、MessageSource等接⼝,因此其提供的功能也就更丰富了。
下面我们以 ClasspathXmlApplicationContext 为例,深入源码说明 IoC 容器的初始化流程
**思路:**创建一个类 LagouBean ,让其实现一个特殊的接⼝,并分别在接口实现的构造器、接口方法中断点,观察线程调用栈,分析出 Bean 对象创建和管理关键点的触发时机。
LagouBean类
public class LagouBean implements InitializingBean {
public LagouBean() {
System.out.println("LagouBean 构造器...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("LagouBean afterPropertiesSet");
}
MyBeanFactoryPostProcessor类
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public MyBeanFactoryPostProcessor() {
System.out.println("MyBeanFactoryPostProcessor的实现类构造函数...");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("postProcessBeanFactory的实现方法调用中...");
}
}
MyBeanPostProcessor类
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if("lagouBean".equals(beanName)){
System.out.println("MyBeanPostProcessor 实现类" +
"postProcessBeforeInitialization 接口方法被调用中......");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if("lagouBean".equals(beanName)){
System.out.println("MyBeanPostProcessor 实现类" +
"postProcessAfterInitialization 接口方法被调用中......");
}
return bean;
}
applicationContext.xml
<bean id="lagouBean" class="com.lagou.edu.source.LagouBean"/>
<bean id="myBeanPostProcessor" class="com.lagou.edu.source.MyBeanPostProcessor"/>
<bean id="myBeanFactoryPostProcessor" class="com.lagou.edu.source.MyBeanFactoryPostProcessor"/>
(1)分析 Bean 的创建是在容器初始化时还是在 getBean 时
根据断点调试,我们发现,在未设置延迟加载的前提下,Bean 的创建是在容器初始化过程中完成的。
(2)分析构造函数调用情况
通过如上观察,我们发现构造函数的调⽤时机在AbstractApplicationContext类refresh方法的
finishBeanFactoryInitialization(beanFactory)处;
(3)分析 InitializingBean 之 afterPropertiesSet 初始化方法调用情况
通过如上观察,我们发现 InitializingBean中afterPropertiesSet 方法的调用时机也是在
AbstractApplicationContext类refresh方法的finishBeanFactoryInitialization(beanFactory);
(4)分析BeanFactoryPostProcessor 初始化和调用情况
分别在构造函数、postProcessBeanFactory 方法处打断点,观察调用栈,发现
BeanFactoryPostProcessor 初始化在AbstractApplicationContext类refresh方法的
invokeBeanFactoryPostProcessors(beanFactory);
postProcessBeanFactory 调用在AbstractApplicationContext类refresh方法的
invokeBeanFactoryPostProcessors(beanFactory);
(5)分析 BeanPostProcessor 初始化和调用情况
分别在构造函数、postProcessBeanFactory 方法处打断点,观察调方栈,发现
BeanPostProcessor 初始化在AbstractApplicationContext类refresh方法的
registerBeanPostProcessors(beanFactory);
postProcessBeforeInitialization 调用在AbstractApplicationContext类refresh方法的
finishBeanFactoryInitialization(beanFactory);
postProcessAfterInitialization 调用在AbstractApplicationContext类refresh方法的
finishBeanFactoryInitialization(beanFactory);
(6)总结
根据上面的调试分析,我们发现 Bean对象创建的几个关键时机点代码层级的调用都在
AbstractApplicationContext 类 的 refresh 方法中,可见这个方法对于Spring IoC 容器初始化来说相当
关键,汇总如下:
关键点 | 触发代码 |
---|---|
构造器 | refresh#finishBeanFactoryInitialization(beanFactory) |
BeanFactoryPostProcessor 初始化 | refresh#invokeBeanFactoryPostProcessors(beanFactory) |
BeanFactoryPostProcessor 方法调用 | refresh#invokeBeanFactoryPostProcessors(beanFactory) |
BeanPostProcessor 初始化 | registerBeanPostProcessors(beanFactory) |
BeanPostProcessor 方法调用 | refresh#finishBeanFactoryInitialization(beanFactory) |
1.3 Spring IoC容器初始化主流程
由上分析可知,Spring IoC 容器初始化的关键环节就在 AbstractApplicationContext#refresh() 方法中
,我们查看 refresh 方法来俯瞰容器创建的主体流程,主体流程下的具体子流程我们后面再来讨论。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 第一步:刷新前的预处理
prepareRefresh();
/*
第二步:
获取BeanFactory;默认实现是DefaultListableBeanFactory
加载BeanDefition 并注册到 BeanDefitionRegistry
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 第三步:BeanFactory的预准备操作(BeanFactory进行一些设置,比如context的类加载器等)
prepareBeanFactory(beanFactory);
try {
// 第四步:BeanFactory准备工作完成后进行的后置处理操作
postProcessBeanFactory(beanFactory);
// 第五步:实例化并调用实现了BeanFactoryPostProcessor接口的Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行
registerBeanPostProcessors(beanFactory);
// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
initMessageSource();
// 第八步:初始化事件派发器
initApplicationEventMulticaster();
// 第九步:子类重写这个方法,在容器刷新的时候可以自定义逻辑
onRefresh();
// 第十步:注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean
registerListeners();
/*
第十一步:
初始化所有剩下的非懒加载的单例bean
初始化创建非懒加载方式的单例Bean实例(未设置属性)
填充属性
初始化方法调用(比如调用afterPropertiesSet方法、init-method方法)
调用BeanPostProcessor(后置处理器)对实例bean进行后置处
*/
finishBeanFactoryInitialization(beanFactory);
/*
第十二步:
完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事
件 (ContextRefreshedEvent)
*/
finishRefresh();
}
......
}
}
(1)该子流程涉及到如下几个关键步骤
Resource定位: 指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML文件,并将其封装成Resource对象。
BeanDefinition载入 : 把用户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。
注册BeanDefinition到 IoC 容器
(2)过程分析
Step 1: 子流程入口在 AbstractRefreshableApplicationContext#refreshBeanFactory 方法中
Step 2: 依次调用多个类的 loadBeanDefinitions 方法 —> AbstractXmlApplicationContext —>
AbstractBeanDefinitionReader —> XmlBeanDefinitionReader 一直执行到
XmlBeanDefinitionReader 的 doLoadBeanDefinitions 方法
Step 3: 我们重点观察XmlBeanDefinitionReader 类的 registerBeanDefinitions 方法,期间产生了多
次重载调用,我们定位到最后一个
此处我们关注两个地方:一个createRederContext方法,一个是
DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法,先进入
createRederContext 方法看看
我们可以看到,此处 Spring 首先完成了 NamespaceHandlerResolver 的初始化。
我们再进入 registerBeanDefinitions 方法中追踪,调用了
DefaultBeanDefinitionDocumentReader#registerBeanDefinitions 方法
进入 doRegisterBeanDefinitions 方法
进入 parseBeanDefinitions 方法
进入 parseDefaultElement 方法
进入 processBeanDefinition 方法
至此,注册流程结束,我们发现,所谓的注册就是把封装的 XML 中定义的 Bean信息封装为
BeanDefinition 对象之后放入一个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition
的。
可以在DefaultListableBeanFactory中看到此Map的定义
public void preInstantiateSingletons() throws BeansException {
// 所有beanDefinition集合
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// 触发所有⾮懒加载单例bean的初始化
for (String beanName : beanNames) {
// 获取bean 定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判断是否是 FactoryBean
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>)getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof
SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
}else {
/*
如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑
和
懒加载时 context.getBean("beanName") 所触发的逻辑是⼀样的
*/
getBean(beanName);
}
}
}
}
-总结
循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。比如A
依赖于B,B依赖于C,C又依赖于A。
注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结
条件。
Spring中循环依赖场景有:
- 构造器的循环依赖(构造器注入)
- Field 属性的循环依赖(set注入)
其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决
属性循环依赖时,spring采用的是提前暴露对象的方法。
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>)
curVal).contains(beanName))));
}
在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进行标记
这个beanName正在被创建,等创建结束之后会删除标记
try {
//创建原型bean之前添加标记
beforePrototypeCreation(beanName);
//创建原型bean
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//创建原型bean之后删除标记
afterPrototypeCreation(beanName);
}
总结:Spring 不支持原型 bean 的循环依赖。
Spring 的循环依赖的理论依据基于 Java 的引用传递,当获得对象的引用时,对象的属性是可以延
后设置的,但是构造器必须是在获取引用之前
Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露一个ObjectFactory对
象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调用ClassA的setClassB方法
之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//将初始化后的对象提前已ObjectFactory对象注⼊到容器中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}