Spring 笔记分享
整体架构
1.1 概述
Spring框架可在任何类型的部署平台上为基于Java的企业应用程序提供全面的编程和配置模型。
Spring的一个关键元素是在应用程序级别的基础框架支持:Spring专注于企业应用程序的“探索”,以便于团队可以专注于应用程序级别的业务逻辑,而不必处理特定的部署环境问题。
Spring可以理解为框架粘合剂,大部分的框架都是不可以相互作用的。Spring提供了这样一个平台,框架只需要向Spring靠拢即可。最终结果就是基于Spring实现了不同框架技术的整合。而且Spring提供了对各大框架的支持,我们可以更加方便的使用。
1.2 优点
Spring的核心是IOC和AOP功能,基于IOC实现对象的依赖和管理,基于AOP实现了事务的控制和管理。
简化开发、功能解耦
通过IOC容器,那么对象直接的依赖关系交给Spring管理与控制,而且基于面向接口编程可以使得代码解耦,改变实现类不需要修改引用。
AOP编码支持
通过AOP功能,可以面向切面编程,传统的基于OOP实现的功能可以通过AOP轻松搞定。
声明式事务
@Transactional 事务注解可以帮助我们实现事务控制与管理,而不需要手动进行事务管理。是的事务与代码解耦,声明式事务可以非常灵活的配置,提供开发的效率和质量。
方便程序测试
Spring提供了对测试的支持,可以非常方便的构建测试。
方便集成框架
前面说过Spring其实是一个粘合剂,可以非常轻松的将各种框架整合到项目中,而且还提供了更加简便的操作方式。
降低JavaEE API的使用难度
Spring内部其实对很多的JavaEE API进行了封装,是的我们面的JavaEE编程的时候更加方便。如JDBC、JavaMail等功能。
1.3 核心思想功能
1.3.1 IoC
IOC:Inversion of Control(控制反转),这是一种技术思想。主要是解决Java开发领域的对象创建和管理问题。
传统对象管理(手动创建对象和依赖)
IoC管理(由IoC创建并管理对象)
IoC主要解决对象之间的耦合问题,我们不关心对象的创建和如何依赖,只需要在使用的地方@Autowired注入即可。IoC会帮助我们注入和管理所需要的Bean
IoC和DI的关系
DI:Dependancy Injection(依赖注入)
IoC是控制反转,DI是依赖注入。它们共同完成了对象管理这一件事情。
1.3.2 AOP
AOP:Aspect oriented Programming面向切面编程
AOP其实是OOP思想的延续和扩展。OOP是一种垂直结构,AOP是一种横向结构。
OOP的体系开发模式
AOP切面编程
如何理解切面
1.4 基于IOC和AOP代码实现
在2.2.3部分,我们已经对IoC和AOP思想有了大致的了解,但是这都是基于概念和思想上的。下面我们通过一个《银行转账》的基础案例分析其中的问题,然后基于IoC和AOP思想慢慢的改造解决痛点问题并加深我们的理解和认识。
开发过程:转账操作可以写html页面发起请求,也可以使用功能http模拟请求。
银行转账:
A用户发起向B用户转账100元人民币请求A用户账户扣款100元(当然要校验账户余额,这里省略)B用户账户加上100元最终A账户少100元,而B用户多100元
数据库SQL准备
SET NAMES utf8mb4
;
SET FOREIGN_KEY_CHECKS
= 0;
DROP TABLE IF EXISTS `account
`;
CREATE TABLE `account
` (
`cardNo
` varchar(20) CHARACTER SET utf8
COLLATE utf8_general_ci
NOT NULL,
`name
` varchar(5) CHARACTER SET utf8
COLLATE utf8_general_ci
NOT NULL,
`money
` int(11) NULL DEFAULT 0
) ENGINE = InnoDB CHARACTER SET = utf8
COLLATE = utf8_general_ci ROW_FORMAT
= Dynamic
;
INSERT INTO `account
` VALUES ('1006029621011001', '张三', 100000);
INSERT INTO `account
` VALUES ('2006029621011000', '李四', 100000);
SET FOREIGN_KEY_CHECKS
= 1;
Maven依赖
<dependencies>
<dependency>
<groupId>junit
</groupId>
<artifactId>junit
</artifactId>
<version>4.12
</version>
</dependency>
<dependency>
<groupId>mysql
</groupId>
<artifactId>mysql-connector-java
</artifactId>
<version>5.1.35
</version>
</dependency>
<dependency>
<groupId>com.alibaba
</groupId>
<artifactId>druid
</artifactId>
<version>1.1.21
</version>
</dependency>
<dependency>
<groupId>javax.servlet
</groupId>
<artifactId>javax.servlet-api
</artifactId>
<version>3.1.0
</version>
<scope>provided
</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core
</groupId>
<artifactId>jackson-databind
</artifactId>
<version>2.9.6
</version>
</dependency>
<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>cglib
</groupId>
<artifactId>cglib
</artifactId>
<version>2.1_2
</version>
</dependency>
</dependencies>
1.4.1 调用流程
1.4.2 基础版本
基础实体
public class Account {
private String cardNo
;
private String name
;
private int money
;
@Override
public String
toString() {
return "Account{" +
"cardNo='" + cardNo
+ '\'' +
", name='" + name
+ '\'' +
", money=" + money
+
'}';
}
}
public class Result {
private String status
;
private String message
;
@Override
public String
toString() {
return "Result{" +
"status='" + status
+ '\'' +
", message='" + message
+ '\'' +
'}';
}
}
基础工具类
public class DruidUtils {
private DruidUtils() {}
private static DruidDataSource druidDataSource
= new DruidDataSource();
static {
druidDataSource
.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource
.setUrl("jdbc:mysql://localhost:3306/lagou_spring");
druidDataSource
.setUsername("root");
druidDataSource
.setPassword("root");
}
public static DruidDataSource
getInstance() {
return druidDataSource
;
}
}
TransferServlet
@WebServlet(name
= "transferServlet", urlPatterns
= "/transferServlet")
public class TransferServlet extends HttpServlet {
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 {
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
));
}
}
TransferService接口及实现
public interface TransferService {
void transfer(String fromCardNo
, String toCardNo
, int money
) throws Exception
;
}
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(to
);
accountDao
.updateAccountByCardNo(from
);
}
}
AccountDao接口和实现类
public interface AccountDao {
Account
queryAccountByCardNo(String cardNo
) throws Exception
;
int updateAccountByCardNo(Account account
) throws Exception
;
}
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)new关键字代码耦合问题?对象的依赖关系如何确定?什么时候实例化对象?
解决方案:
使用反射技术实例化对象。对象的依赖关系可以通过xml配置文件的方式确定。对象的实例化可以使用工厂模式,在项目启动的时候实例化对象并解决依赖的Bean注入问题(使用set方式注入)。
2)没有事务控制,无法保证数据库操作的原子性?
解决方案:
基于JDBC实现事务控制,事务配置在Service层。使用本地线程绑定Connection事务,这样就可以保证数据库都是同一个事务。
1.4.3 解决问题一
beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="accountDao" class="com.zyj.dao.impl.JdbcAccountDaoImpl"></bean>
<bean id="transferService" class="com.zyj.service.impl.TransferServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
</beans>
增加BeanFactory解析beans.xml创建对象
public class BeanFactory {
private static Map
<String, Object> beans
= new HashMap<>();
public static Object
getBean(String name
) {
return beans
.get(name
);
}
static {
try {
InputStream resourceAsStream
= BeanFactory
.class.getClassLoader().getResourceAsStream("beans.xml");
SAXReader saxReader
= new SAXReader();
Document document
= saxReader
.read(resourceAsStream
);
Element rootElement
= document
.getRootElement();
List
<Element> elementList
= rootElement
.selectNodes("//beans/bean");
for (Element element
: elementList
) {
String id
= element
.attributeValue("id");
String clazz
= element
.attributeValue("class");
Class
<?> aClass
= Class
.forName(clazz
);
Object instance
= aClass
.newInstance();
beans
.put(id
, instance
);
}
List
<Element> propertyList
= rootElement
.selectNodes("//bean/property");
for (Element element
: propertyList
) {
String name
= element
.attributeValue("name");
String ref
= element
.attributeValue("ref");
Element parentElement
= element
.getParent();
String parentId
= parentElement
.attributeValue("id");
Object o
= beans
.get(parentId
);
Method
[] methods
= o
.getClass().getMethods();
for (Method method
: methods
) {
if (method
.getName().equalsIgnoreCase("set" + name
)) {
method
.invoke(o
, beans
.get(ref
));
}
}
}
System
.out
.println(beans
);
} catch (Exception e
) {
e
.printStackTrace();
}
}
}
修改TransferServlet、TransferServiceImpl从BeanFactory中获取对象
@WebServlet(name
= "transferServlet", urlPatterns
= "/transferServlet")
public class TransferServlet extends HttpServlet {
private TransferService transferService
= (TransferService
) BeanFactory
.getBean("transferService");
}
public class TransferServiceImpl implements TransferService {
private AccountDao accountDao
;
public void setAccountDao(AccountDao accountDao
) {
this.accountDao
= accountDao
;
}
}
1.4.4 解决问题二
增加ConnectionUtils管理数据库连接
public class 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
;
}
}
增加TransactionManager类管理事务
package com
.zyj
.utils
;
import java
.sql
.Connection
;
import java
.sql
.SQLException
;
public class TransactionManager {
private ConnectionUtils connectionUtils
;
public void setConnectionUtils(ConnectionUtils connectionUtils
) {
this.connectionUtils
= connectionUtils
;
}
public void start() throws SQLException
{
connectionUtils
.getCurrentThreadConn().setAutoCommit(false);
}
public void commit() throws SQLException
{
connectionUtils
.getCurrentThreadConn().commit();
}
public void rollback() throws SQLException
{
connectionUtils
.getCurrentThreadConn().rollback();
}
}
增加ProxyFactory代理工厂类,生成Service代理实现事务控制
package com
.zyj
.factory
;
import com
.zyj
.utils
.TransactionManager
;
import java
.lang
.reflect
.InvocationHandler
;
import java
.lang
.reflect
.Method
;
import java
.lang
.reflect
.Proxy
;
public class ProxyFactory {
private TransactionManager transactionManager
;
public void setTransactionManager(TransactionManager transactionManager
) {
this.transactionManager
= transactionManager
;
}
public Object
getJdkProxy(Object obj
) {
return Proxy
.newProxyInstance(obj
.getClass().getClassLoader(), obj
.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object
invoke(Object proxy
, Method method
, Object
[] args
) throws Throwable
{
Object result
= null
;
try {
transactionManager
.start();
method
.invoke(obj
, args
);
transactionManager
.commit();
} catch (Exception e
) {
transactionManager
.rollback();
throw e
;
}
return result
;
}
});
}
}
修改beans.xml,增加事务管理器、代理对象、数据库连接工具对象
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="connectionUtils" class="com.zyj.utils.ConnectionUtils"></bean>
<bean id="accountDao" class="com.zyj.dao.impl.JdbcAccountDaoImpl">
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<bean id="transferService" class="com.zyj.service.impl.TransferServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<bean id="transactionManager" class="com.zyj.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<bean id="proxyFactory" class="com.zyj.factory.ProxyFactory">
<property name="transactionManager" ref="transactionManager"/>
</bean>
</beans>
修改JdbcAccountDaoImpl类
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
= 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();
return account
;
}
@Override
public int updateAccountByCardNo(Account account
) throws Exception
{
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();
return i
;
}
}
修改TransferServlet从代理工厂获取有事务控制的Service
@WebServlet(name
= "transferServlet", urlPatterns
= "/transferServlet")
public class TransferServlet extends HttpServlet {
private ProxyFactory proxyFactory
= (ProxyFactory
) BeanFactory
.getBean("proxyFactory");
private TransferService transferService
= (TransferService
) proxyFactory
.getJdkProxy(BeanFactory
.getBean("transferService"));
}
总结
截止目前,我们已经完成了《银行转账》案例基础到问题分析,然后基于IoC和AOP思想的改造。从而实现了对象统一管理和依赖注入、事务管理控制的问题。其实这些就是Spring框架的IoC和AOP原理。
挑战
上诉的银行转账案例是基于XML配置方式实现的,你可以挑战一下将其改造成基于注解方式实现。你了解了Spring的IoC和AOP核心思想,那么是否可以读懂了Spring框架源码呢?
1.5 Spring学习思维导图(提供参考)
知春秋
认证博客专家
博客专家
Java高级研发
不忘初心,方得始终。初心易得,始终难守。