文章目录

  • 一、Spring是什么?
    • 1. Spring定义
    • 2. Spring的优点
    • 3. Spring的核心结构
    • 4. Spring的核心思想
      • 4.1 IOC和DI
      • 4.2 AOP
  • 二、IOC和AOP的案例实现
    • 1. 案例需求
    • 2. 案例实现
    • 3. 案例问题
    • 4. 解决思路
    • 5. 代码改造
      • 5.1手动添加自定义ioc容器
      • 5.2 手动实现转账事务管理
  • 三、使用Spring实现IOC和AOP的案例改造
    • 1. IOC基础
      • 1.1 BeanFactory和ApplicationContext的区别
      • 1.2 基础使用
    • 2. 案例改造
      • 2.1 xml改造
      • 2.2 xml + 注解改造 (在纯xml配置的基础上做修改,第三方jar包中定义的bean使用xml,自定义bean使用注解形式)
      • 2.3 纯注解改造 (在xml + 注解 的工程上改造)
    • 3. AOP基础
      • 3.1 Aop基本介绍
      • 3.2 基础使用
    • 4. spring声明式事务
      • 4.1 事务概念
      • 4.2 事务的四大特性
      • 4.3 事务的隔离级别
      • 4.4 事务的传播行为
      • 4.5 spring声明式事务是什么
    • 5. 案例改造(添加spring事务管理)
      • 5.1纯xml改造(在springioc的实现案例上改造)
      • 5.2 xml + 注解改造
      • 5.3 纯注解改造
  • 总结

一、Spring是什么?

1. Spring定义

Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 Spring MVC 和业务层事务管理等众多的企业级应⽤技术,还能整合开源世界众多著名的第三方框架和类库,已 经成为使⽤最多的 Java EE 企业应⽤开源框架。

2. Spring的优点

  • 避免硬编码造成代码过度耦合。用户不必再为单例模式类、属性文件解析等底层需求编写代码,专注于上层应用。
  • AOP面向切面编程,支持横切逻辑插入
  • 声明式事务支持,极大方便和灵活的进行事务管理
  • 方便集成各种优秀框架

3. Spring的核心结构

Spring是⼀个分层⾮常清晰并且依赖关系、职责定位⾮常明确的轻量级框架,主要包括⼏个⼤模块:数 据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块 和 Test 模块,如下图所示,Spring依靠这些基本模块,实现了⼀个令⼈愉悦的融合了现有解决方案的零侵入的轻量级框架。

  • Spring核⼼容器(Core Container):容器是Spring框架最核⼼的部分,它管理着Spring应⽤中 bean的创建、配置和管理。在该模块中,包括了Spring bean⼯⼚,它为Spring提供了DI的功能。 基于bean⼯⼚,我们还会发现有多种Spring应用上下文的实现。所有的Spring模块都构建于核心容器之上。

  • ⾯向切⾯编程(AOP)/Aspects:Spring对⾯向切⾯编程提供了丰富的⽀持。这个模块是Spring应用系统中面向切⾯开发的基础,与DI⼀样,AOP可以帮助应⽤对象解耦。

  • 数据访问和集成(Data Access/Integration)

    Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专 注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外,Spring AOP为数据访问 提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、MyBatis等。该模块由 JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。

  • Web 该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅ 案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平。

  • Test 为了使得开发者能够很⽅便的进⾏测试,Spring提供了测试模块以致⼒于Spring应⽤的测 试。 通过该模块,Spring为使⽤Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。

4. Spring的核心思想

4.1 IOC和DI

  1. IOC和DI

    IoC :Inversion of Control (控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现

    • 描述的事情:Java开发领域对象的创建,管理的问题
    • 和传统创建对象的方式对比
      • 传统开发⽅式:⽐如类A依赖于类B,往往会在类A中new⼀个B的对象
      • IoC思想下开发⽅式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对 象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可

    DI:Dependancy Injection(依赖注⼊) ,指的是A对象依赖于B对象时,需要把B的实例对象注入给A

  2. 为什么使用IOC?

    当我们需要使用对象时,向Spring的IOC容器获取即可,这样的好处是解决了对象之间的耦合问题

    • 当需要使用bean对象时,如果使用多态创建对象,接口对象指定自己new的对象,有强耦合,若需要修改,则需要修改每一处创建当前对象的源代码。
    • 若使用IOC,则切换实现类只需要在IOC容器中替换注入的bean即可。

4.2 AOP

  1. 什么是AOP?

    AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程

    AOP解决的是多个方法中相同的位置出现代码重复的问题。比如:需要查询多个方法的执行时间,需要在方法执行前记录时间,方法结束时记录时间。这种代码就被称为横切逻辑代码。类似可以插入横切逻辑的地方有如:方法开始时、结束时、返回时、异常时等。

  2. 为什么使用AOP?

在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复


二、IOC和AOP的案例实现

1. 案例需求

实现简单的转账案例,从付款账户减去转账金额,收款账户添加转账金额

2. 案例实现

  1. 转账界面搭建

    代码:

    <!doctype html>
    <html>
    <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>转账汇款</title><script type="text/javascript" src="js/jquery-3.4.1.min.js"></script><style type="text/css">body {/*background-color:#00b38a;*/text-align:center;}.lp-login {position:absolute;width:500px;height:300px;top:50%;left:50%;margin-top:-250px;margin-left:-250px;background: #fff;border-radius: 4px;box-shadow: 0 0 10px #12a591;padding: 57px 50px 35px;box-sizing: border-box}.lp-login .submitBtn {display:block;text-decoration:none;height: 48px;width: 150px;line-height: 48px;font-size: 16px;color: #fff;text-align: center;background-image: -webkit-gradient(linear, left top, right top, from(#09cb9d), to(#02b389));background-image: linear-gradient(90deg, #09cb9d, #02b389);border-radius: 3px}input[type='text'] {height:30px;width:250px;}span {font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-variant-numeric: normal;font-variant-east-asian: normal;font-weight: normal;font-stretch: normal;font-size: 14px;line-height: 22px;font-family: "Hiragino Sans GB", "Microsoft Yahei", SimSun, Arial, "Helvetica Neue", Helvetica;}</style><script type="text/javascript">$(function(){$(".submitBtn").bind("click",function(){var fromAccount = $("#fromAccount").val();var toAccount = $("#toAccount").val();var money = $("#money").val();if(money == null || $.trim(money).length == 0){alert("sorry,必须输入转账金额~");return;}$.ajax({url:'/transferServlet',type:'POST',    //GETasync:false,    //或false,是否异步data:{fromCardNo:fromAccount.split(' ')[1],toCardNo:toAccount.split(' ')[1],money:money},timeout:5000,    //超时时间dataType:'json', //返回的数据格式:json/xml/html/script/jsonp/textsuccess:function(data){if("200" == data.status){alert("转账成功~~~");}else{alert("转账失败~~~,message:" + data.message);}}})})})//检查输入值是否为整数function checkFormat(obj){var reg = /^[0-9]+[0-9]*]*$/;if($.trim($(obj).val()).length>0){if(!reg.test($(obj).val())){alert("输入格式错误!请输整数!");$(obj).val("");}else{$(obj).val(parseInt($(obj).val()));}}}</script>
    </head>
    <body><form><table class="lp-login"><tr><td align="right"><span>收款账户</span></td><td align="center"><input type="text" id="toAccount" value="韩梅梅 6029621011001" disabled></input></td></tr><tr><td align="right"><span>付款账户</span></td><td align="center"><input type="text" id="fromAccount" value="李大雷 6029621011000" disabled></input></td></tr><tr><td align="right"><span>转账金额</span></td><td align="center"><input type="text" id="money" onblur="checkFormat(this)"></input></td></tr><tr align="center"><td colspan="2"><a href="javasrcipt:void(0)" class="submitBtn"><span>转 出</span></a></td></tr></table>
    </form></body>
    </html>

    注意:需要导入jquery包

  2. 创建数据库

  3. 创建maven的web工程,引入pom依赖

        <!-- mysql数据库驱动包 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.35</version></dependency><!--druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.21</version></dependency><!-- servlet --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!-- jackson依赖 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.6</version></dependency>
    
  4. 创建pojo类,包含Account类 和 Result类,Account对应数据库的字段,Result类对应返回结果 和 异常时输出的信息

    Account类:

    package com.lb.pojo;public class Account {private String cardNo;private String name;private int money;public String getCardNo() {return cardNo;}public void setCardNo(String cardNo) {this.cardNo = cardNo;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getMoney() {return money;}public void setMoney(int money) {this.money = money;}@Overridepublic String toString() {return "Account{" +"cardNo='" + cardNo + '\'' +", name='" + name + '\'' +", money=" + money +'}';}
    }
    

    Result类:

    package com.lb.pojo;public class Result {private String status;private String message;public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overridepublic String toString() {return "Result{" +"status='" + status + '\'' +", message='" + message + '\'' +'}';}
    }
    
  5. 创建DruidUtils工具类,获取数据库连接

    package com.lb.utils;import com.alibaba.druid.pool.DruidDataSource;public class DruidUtils {private DruidUtils(){}private static DruidDataSource druidDataSource = new DruidDataSource();static {druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");druidDataSource.setUrl("jdbc:mysql:///bank");druidDataSource.setUsername("root");druidDataSource.setPassword("root");}public static DruidDataSource getInstance(){return druidDataSource;}}
  6. 创建AccountDao层接口 及 基于jdbc的实现类,实现通过卡号获取数据库中的钱数、通过卡号更新钱数

    接口:

    package com.lb.dao;import com.lb.pojo.Account;public interface AccountDao {Account queryAccountByCardNo(String cardNo) throws Exception;int updateAccountByCardNo(Account account) throws Exception;
    }
    

    实现类:

    package com.lb.dao.impl;import com.alibaba.druid.pool.DruidPooledConnection;
    import com.lb.dao.AccountDao;
    import com.lb.pojo.Account;
    import com.lb.utils.DruidUtils;import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;public class JdbcAccountDaoImpl implements AccountDao {@Overridepublic Account queryAccountByCardNo(String cardNo) throws Exception {Connection connection = DruidUtils.getInstance().getConnection();String sql = "select * from account where cardNo = ?";PreparedStatement preparedStatement = connection.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();connection.close();return account;}@Overridepublic int updateAccountByCardNo(Account account) throws Exception {Connection connection = DruidUtils.getInstance().getConnection();String sql = "update account set money = ? where cardNo = ?";PreparedStatement preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1, account.getMoney());preparedStatement.setString(2, account.getCardNo());int i = preparedStatement.executeUpdate();preparedStatement.close();connection.close();return i;}
    }
    
  7. 创建TransferService接口 及 实现类,实现转账的逻辑

    接口:

    package com.lb.service;public interface TransferService {public void transfer(String fromCardno, String toCardNo, int money) throws Exception;}
    

    实现类:

    package com.lb.service.impl;import com.lb.dao.AccountDao;
    import com.lb.dao.impl.JdbcAccountDaoImpl;
    import com.lb.pojo.Account;
    import com.lb.service.TransferService;public class TransferServiceImpl implements TransferService {private AccountDao accountDao = new JdbcAccountDaoImpl();@Overridepublic 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);}
    }
    
  8. 创建TransferServlet 及 json工具类JsonUtils ,实现获取界面输入的双方转账数目,通过调用TransferService的实现类实现转账功能

    package com.lb.servlet;import com.lb.pojo.Result;
    import com.lb.service.TransferService;
    import com.lb.service.impl.TransferServiceImpl;
    import com.lb.utils.JsonUtils;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 {private TransferService transferService = new TransferServiceImpl();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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));}
    }
    
  9. 导入本地tomcat依赖,启动

3. 案例问题

  • 问题1:service层使用dao层接口的实现类时,直接new了实现类对象,若需要修改dao层的实现类,必须修改源代码,不符合面向接口开发的最优原则
  • 问题2:service层没有事务控制,若转账过程中发生异常,可能导致数据库数据错误

4. 解决思路

  • 问题1解决:

    将bean对象的实现类配置在xml文件中,结合工厂模式生产出保存所有bean的map容器进行统一管理,当程序启动时,容器就把对应的bean对象初始化管理起来。这样当要切换实现类时,只需要修改配置即可。

  • 问题2解决:

    • 将获取的数据库连接绑定到线程上,一个线程一个连接,用JDBC的Connection控制当前线程的事务执行。
    • 使用动态代理,管理Service层的事务

5. 代码改造

5.1手动添加自定义ioc容器

  1. 添加dom4j、xpath表达式的依赖

        <!--dom4j依赖--><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><!--xpath表达式依赖--><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency>
    
  2. 添加beans.xml,注册bean的全限定类名、属性等信息

    <?xml version="1.0" encoding="UTF-8" ?>
    <beans><bean id = "transferService" class = "com.lb.service.impl.TransferServiceImpl"><property name = "AccountDao" ref = "accountDao"></property></bean><bean id = "accountDao" class="com.lb.dao.impl.JdbcAccountDaoImpl"></bean></beans>
    
  3. 添加BeanFactory类,工厂方式实现bean对象的创建

    package com.lb.factory;import org.dom4j.Document;
    import org.dom4j.DocumentException;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;public class BeanFactory {//1、加载解析xml,读取xml中的bean信息,通过反射技术实例化bean对象,然后放⼊map待⽤//2、提供接⼝⽅法根据id从map中获取beanprivate 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 classPath = element.attributeValue("class");Class<?> aClass = Class.forName(classPath);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())){//获取依赖的beanObject propertyObject = map.get(ref);//反射调用set方法,注入beanmethod.invoke(parentObject, propertyObject);}}//维护依赖关系后,重新将bean放入mapmap.put(parentId, parentObject);}} catch (Exception e) {e.printStackTrace();}}public static Object getBean(String id){return map.get(id);}
    }
  4. 修改TransferServlet,从BeanFactory获取所需bean对象

    package com.lb.servlet;import com.lb.factory.BeanFactory;
    import com.lb.pojo.Result;
    import com.lb.service.TransferService;
    import com.lb.service.impl.TransferServiceImpl;
    import com.lb.utils.JsonUtils;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 {//private TransferService transferService = new TransferServiceImpl();private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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));}
    }
  5. 修改TransferServiceImpl,通过set方法实现dao层实例注入

    package com.lb.service.impl;import com.lb.dao.AccountDao;
    import com.lb.dao.impl.JdbcAccountDaoImpl;
    import com.lb.pojo.Account;
    import com.lb.service.TransferService;public class TransferServiceImpl implements TransferService {private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;}@Overridepublic 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);}
    }

5.2 手动实现转账事务管理

  1. 创建ConnectionUtils工具类,获取与当前线程绑定的数据库连接,用来事务管理。若当前线程存在,则直接获取;若不存在,则从连接池获取一个和当前线程绑定。

    package com.lb.utils;import java.sql.Connection;
    import java.sql.SQLException;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;}
    }
  2. 创建事务管理类,封装事务相关的开启、提交、回滚操作

    package com.lb.utils;import java.sql.SQLException;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();}}
  3. 创建代理工厂类,获取目标对象的动态代理

    package com.lb.factory;import com.lb.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 getProxy(Object target){return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object res = null;try{//开启事务transactionManager.beginTransaction();//调用原方法res = method.invoke(target, args);//提交事务transactionManager.commit();}catch (Exception e){e.printStackTrace();//回滚事务transactionManager.rollback();//向上抛出异常,便于Servlet捕获throw e.getCause();}return res;}});}}
  4. 修改TransferServlet,从bean工厂获取代理工厂的bean,从代理工厂获取transferService的jdk动态代理对象

    package com.lb.servlet;import com.lb.factory.BeanFactory;
    import com.lb.factory.ProxyFactory;
    import com.lb.pojo.Result;
    import com.lb.service.TransferService;
    import com.lb.service.impl.TransferServiceImpl;
    import com.lb.utils.JsonUtils;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 {//private TransferService transferService = new TransferServiceImpl();//private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");private TransferService transferService = (TransferService) proxyFactory.getProxy(BeanFactory.getBean("transferService"));@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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));}
    }
  5. 修改JdbcAccountDaoImpl,注入ConnectionUtils依赖,从ConnectionUtils获取当前线程绑定的数据库连接。连接需要复用,取消连接关闭操作

    package com.lb.dao.impl;import com.alibaba.druid.pool.DruidPooledConnection;
    import com.lb.dao.AccountDao;
    import com.lb.pojo.Account;
    import com.lb.utils.ConnectionUtils;
    import com.lb.utils.DruidUtils;import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;public class JdbcAccountDaoImpl implements AccountDao {private ConnectionUtils connectionUtils;public void setConnectionUtils(ConnectionUtils connectionUtils) {this.connectionUtils = connectionUtils;}@Overridepublic Account queryAccountByCardNo(String cardNo) throws Exception {//Connection connection = DruidUtils.getInstance().getConnection();Connection connection = connectionUtils.getCurrentThreadConn();String sql = "select * from account where cardNo = ?";PreparedStatement preparedStatement = connection.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();//connection.close();return account;}@Overridepublic int updateAccountByCardNo(Account account) throws Exception {//Connection connection = DruidUtils.getInstance().getConnection();Connection connection = connectionUtils.getCurrentThreadConn();String sql = "update account set money = ? where cardNo = ?";PreparedStatement preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1, account.getMoney());preparedStatement.setString(2, account.getCardNo());int i = preparedStatement.executeUpdate();preparedStatement.close();//connection.close();return i;}
    }
  6. 修改beans.xml,添加ConnectionUtils、TransactionManager、ProxyFactory三个bean,并将ConnectionUtils的bean注入JdbcAccountDaoImpl

    <?xml version="1.0" encoding="UTF-8" ?>
    <beans><bean id = "transferService" class = "com.lb.service.impl.TransferServiceImpl"><property name = "AccountDao" ref = "accountDao"></property></bean><bean id = "accountDao" class="com.lb.dao.impl.JdbcAccountDaoImpl"><property name="ConnectionUtils" ref="connectionUtils"></property></bean><!--三个新增bean--><bean id="connectionUtils" class="com.lb.utils.ConnectionUtils"></bean><bean id="transactionManager" class="com.lb.utils.TransactionManager"><property name="ConnectionUtils" ref="connectionUtils"></property></bean><bean id="proxyFactory" class="com.lb.factory.ProxyFactory"><property name="TransactionManager" ref="transactionManager"></property></bean>
    </beans>
    

三、使用Spring实现IOC和AOP的案例改造

1. IOC基础

1.1 BeanFactory和ApplicationContext的区别

  • BeanFactory是Spring框架中IoC容器的顶层接口,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,而ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。

  • 通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接口,比 BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等。

1.2 基础使用

  1. 引入spring的jar包依赖

    <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.12.RELEASE</version>
    </dependency>
    
  2. 添加applicationContext.xml,配置bean的三种方式:

    • 纯xml配置

      1. 添加xml文件头;

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd">
        
      2. 启动IOC容器的方式:

        • javaSE启动IOC容器,通过ClassPathXmlApplicationContext

          ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
          
        • web环境下启动IOC容器,在web.xml中配置监听器,当监听器启动时,classpath下applicationContext.xml会被加载,从而获取对应的bean存入容器

          从xml下启动:

          <!DOCTYPE web-app PUBLIC
          "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
          "http://java.sun.com/dtd/web-app_2_3.dtd" >
          <web-app><display-name>Archetype Created Web Application</display-name><!--配置Spring ioc容器的配置⽂件--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!--使⽤监听器启动Spring的IOC容器--><listener><listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass></listener>
          </web-app>
          

          从配置类下启动:

          <!DOCTYPE web-app PUBLIC
          "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
          "http://java.sun.com/dtd/web-app_2_3.dtd" >
          <web-app><display-name>Archetype Created Web Application</display-name><!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器--><context-param><param-name>contextClass</param-name><paramvalue>org.springframework.web.context.support.AnnotationConfigWebAppli
          cationContext</param-value></context-param><!--配置启动类的全限定类名--><context-param><param-name>contextConfigLocation</param-name><param-value>com.lagou.edu.SpringConfig</param-value></context-param><!--使⽤监听器启动Spring的IOC容器--><listener><listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass></listener>
          </web-app>
          
      3. 对象实例化:

        • 使用无参构造函数创建对象,只需要添加对应bean和id、class信息

          添加DemoBean类:

          public class DemoBean {private String name;private int id;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}@Overridepublic String toString() {return "DemoBean{" +"name='" + name + '\'' +", id=" + id +'}';}
          }
          

          applicationContext.xml中添加bean标签:

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="demoBean" class="com.lb.pojo.DemoBean"></bean></beans>
          
        • 使用静态方法创建,创建工厂类的bean,添加生产bean对象的静态方法,在工厂bean配置中添加factory-method配置生产bean的静态方法

          添加生产bean的工厂类:

          public class CreateBeanFactory {public static DemoBean getInstance(){return new DemoBean();}
          }
          

          applicationContext.xml添加工厂bean的配置信息:

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!--    <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean></beans>
          

          应用场景:

          在实际开发中,尤其早期的项⽬没有使⽤Spring框架来管理对象的创建,但是在设计时使⽤了 ⼯⼚模式 解耦,那么当接⼊spring之后,⼯⼚类创建对象就具有和上述例⼦相同特征,即可采⽤ 此种⽅式配置。

        • 使用实例化方法创建,用于非静态方法创建bean对象,由于是非静态方法,需要先创建生产bean的对象,然后再创建bean

          CreateBeanFactory类添加普通生产bean方法:

          public class CreateBeanFactory {public static DemoBean getInstance(){return new DemoBean();}public DemoBean getInstance2(){return new DemoBean();}
          }
          

          修改applicationContext.xml信息:

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!--    <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean></beans>
          

          应用场景:

          在早期开发的项⽬中,⼯⼚类中的⽅法有可能是静态的,也有可能是非静态⽅法,当是非静态方法时,即可 采⽤此配置方式。

      4. bean对象的其他标签属性:

        • scope:singleton单例(注入IOC容器,生命周期由容器管理),prototype(多例,生命周期不受容器管理)

          创建SingletonBean类,xml中配置方式:

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!--    <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean><bean id="singletonBean" class="com.lb.pojo.SingletonBean" scope="singleton"/></beans>
          
        • init-method/destory-method:bean的初始化时 和 销毁时 执行的方法,需注意只有scope为singleton时才会执行destory属性指定的方法

          xml中配置方式:

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!--    <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean><bean id="singletonBean" class="com.lb.pojo.SingletonBean" scope="singleton" init-method="init" destroy-method="destory"/></beans>
          

          测试代码:

          public class IocTest {@Testpublic void test(){ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");DemoBean demoBean = (DemoBean) applicationContext.getBean("demoBean");System.out.println(demoBean);applicationContext.close();}
          }
          

          测试结果:

      5. bean对象的依赖注入方式

        • 构造函数注入

          1. 创建bean对象,生成带参构造函数

            public class ConstructBean {private int id;private String name;private DemoBean demoBean;public ConstructBean(int id, String name, DemoBean demoBean) {this.id = id;this.name = name;this.demoBean = demoBean;}@Overridepublic String toString() {return "ConstructBean{" +"id=" + id +", name='" + name + '\'' +", demoBean=" + demoBean +'}';}
            }
            
          2. 在xml文件中对应的bean配置内添加constructor-arg配置,name为set方法后的参数名,ref为当注入是bean对象时所对应的唯一id,value为注入的基本数据类型或String的值

            xml修改:

            <?xml version="1.0" encoding="UTF-8"?>
            <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!--    <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean><bean id="singletonBean" class="com.lb.pojo.SingletonBean" scope="singleton" init-method="init" destroy-method="destory"/><!--构造函数注入属性--><bean id="constructBean" class="com.lb.pojo.ConstructBean"><constructor-arg name="id" value="1"/><constructor-arg name="name" value="zhangsan"/><constructor-arg name="demoBean" ref="demoBean"/></bean></beans>
            

            测试结果:

        • set方法注入

          1. 在xml文件中对应的bean配置内添加property配置,name为set方法后的参数名,ref为当注入是bean对象时所对应的唯一id,value为注入的基本数据类型或String的值

            xml修改:

            <?xml version="1.0" encoding="UTF-8"?>
            <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!--    <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean><bean id="singletonBean" class="com.lb.pojo.SingletonBean" scope="singleton" init-method="init" destroy-method="destory"/><!--构造函数注入属性--><bean id="constructBean" class="com.lb.pojo.ConstructBean"><constructor-arg name="id" value="1"/><constructor-arg name="name" value="zhangsan"/><constructor-arg name="demoBean" ref="demoBean"/></bean><!--set方法注入属性--><bean id="setBean" class="com.lb.pojo.SetBean"><property name="id" value="2"></property><property name="name" value="lisi"></property><property name="demoBean" ref="demoBean"></property></bean></beans>
            

            测试结果:

          2. 注入复杂数据类型 --> 集合 类型,包括List 和 Map两种

            xml修改:

            <?xml version="1.0" encoding="UTF-8"?>
            <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!--    <bean id="demoBean" class="com.lb.pojo.DemoBean"></bean>--><!--静态方法获取bean--><!-- <bean id="demoBean" class="com.lb.factory.CreateBeanFactory" factory-method="getInstance"></bean>--><!--实例化工厂类,然后创建对应的bean--><bean id="createBeanFactory" class="com.lb.factory.CreateBeanFactory"></bean><bean id="demoBean" factory-bean="createBeanFactory" factory-method="getInstance2"></bean><bean id="singletonBean" class="com.lb.pojo.SingletonBean" scope="singleton" init-method="init" destroy-method="destory"/><!--构造函数注入属性--><bean id="constructBean" class="com.lb.pojo.ConstructBean"><constructor-arg name="id" value="1"/><constructor-arg name="name" value="zhangsan"/><constructor-arg name="demoBean" ref="demoBean"/></bean><!--set方法注入属性--><bean id="setBean" class="com.lb.pojo.SetBean"><property name="id" value="2"></property><property name="name" value="lisi"></property><property name="demoBean" ref="demoBean"></property></bean><!--复杂数据类型注入--><bean id="complexDataBean" class="com.lb.pojo.ComplexDataBean"><property name="list"><array><value>1</value><value>2</value><value>3</value></array></property><property name="set"><set><value>set1</value><value>set2</value><value>set3</value></set></property><property name="map"><map><entry key="key1" value="1"></entry><entry key="key2" value="2"></entry></map></property><property name="properties"><props><prop key="prop1">value1</prop><prop key="prop2">value2</prop></props></property></bean></beans>
            

            测试:

    • xml + 注解(springIOC容器的启动仍从加载xml开始;第三方jar中的bean定义在xml,比如德鲁伊数据库连接池,自定定义的bean用注解)

      1. xml中标签与注解的对应关系;

        xml标签 对应注解形式
        bean标签 @Component(“accountDao”),注解加在类上 bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名首字母小写; 另外,针对分层代码开发提供了@Componenet的三种别名@Controller、 @Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这 四个注解的用法完全⼀样,只是为了更清晰的区分⽽已
        标签的 scope属 性 @Scope(“prototype”),默认单例,注解加在类上
        标签的 initmethod 属性 @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法
        标签的 destorymethod 属性 @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法

        创建一个使用注解的类:

        @Component("demoBean1")
        @Scope("singleton")
        public class AnnoDemoBean {@PostConstructpublic void init(){System.out.println("PostConstuct...");}@PreDestroypublic void destory(){System.out.println("PreDestory...");}
        }
        

        测试代码:

        AnnotationConfigApplicationContext applicationContext1 = new AnnotationConfigApplicationContext("com.lb");DemoBean demoBean = (DemoBean) applicationContext1.getBean("demoBean");
        System.out.println(demoBean);
        applicationContext1.close();
        

        测试结果:

        创建一个多例对象,测试每次生成实例是否为同一个。

        多例类代码:

        @Component("prototypeBean")
        @Scope("prototype")
        public class PrototypeBean {}
        

        测试代码:

        PrototypeBean prototypeBean1 = (PrototypeBean) applicationContext1.getBean("prototypeBean");
        System.out.println(prototypeBean1);
        PrototypeBean prototypeBean2 = (PrototypeBean) applicationContext1.getBean("prototypeBean");
        System.out.println(prototypeBean2);
        

        测试结果:

      2. 依赖注入的实现方式1:@AutoWired --> 按照类型注入,若同一类型有多个bean值时,需要配合@Qualifier指定id来告诉Spring具体装配哪个对象。

        创建接口类及其实现类

        public interface FlyAble {public void fly();
        }
        
        @Component
        public class Bird implements FlyAble {@Overridepublic void fly() {System.out.println("鸟在天空飞...");}
        }
        
        @Component
        public class Plane implements FlyAble {@Overridepublic void fly() {System.out.println("飞机在天空飞...");}
        }
        

        创建demo类自动注入接口类,当不指定具体哪个实现时:

        @Component
        public class AutoWiredDemo {@Autowiredpublic FlyAble flyAble;}
        

        测试结果

        当指定具体实现时:

        @Component
        public class AutoWiredDemo {@Autowired@Qualifier("plane")public FlyAble flyAble;}
        

        测试代码:

        AutoWiredDemo autoWiredDemo = (AutoWiredDemo) applicationContext1.getBean("autoWiredDemo");
        autoWiredDemo.flyAble.fly();
        

        测试结果:

      3. 依赖注入的实现方式2:@Resource (在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包

        • 如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不 到则抛出异常。
        • 如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异 常。
        • 如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个, 都会抛出异常。
        • 如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配。

        测试类代码:

        @Component
        public class ResourceDemo {@Resource(name = "bird", type = FlyAble.class)public FlyAble flyAble;
        }
        

        测试代码:

        ResourceDemo resourceDemo = (ResourceDemo) applicationContext1.getBean("resourceDemo");
        resourceDemo.flyAble.fly();
        

        测试结果:

    • 纯注解方式

      1. 从配置类启动容器,在web中需要使用监听器启动IOC容器,告诉监听器使用注解方式启动IOC容器,配置启动类的全限定类名
      2. 根据注解 和 xml中标签的对应关系,去除applicationContext.xml文件,改为注解配置

2. 案例改造

引入spring的jar包依赖

<!--引入Spring IoC容器功能-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.12.RELEASE</version>
</dependency>
<!--引入spring web功能-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.1.12.RELEASE</version>
</dependency>

2.1 xml改造

  • 去除BeanFactory类,所有bean从Spring容器中获取

  • 改造web.xml,使用监听器启动IOC容器

    <!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--配置SpringIoc容器的配置文件--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!--监听器启动IOC容器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener></web-app>
    
  • 将beans.xml 改名为 applicationContext.xml,并添加标签头

    <?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd
    "><bean id = "transferService" class = "com.lb.service.impl.TransferServiceImpl"><property name = "AccountDao" ref = "accountDao"></property></bean><bean id = "accountDao" class="com.lb.dao.impl.JdbcAccountDaoImpl"><property name="ConnectionUtils" ref="connectionUtils"></property></bean><!--三个新增bean--><bean id="connectionUtils" class="com.lb.utils.ConnectionUtils"></bean><bean id="transactionManager" class="com.lb.utils.TransactionManager"><property name="ConnectionUtils" ref="connectionUtils"></property></bean><bean id="proxyFactory" class="com.lb.factory.ProxyFactory"><property name="TransactionManager" ref="transactionManager"></property></bean>
    </beans>
    
  • 修改TransferServlet,重写init方法,web容器启动时,init方法会执行一次,此时可以从spring容器中获取TransferService实例。

@WebServlet(name = "transferServlet", urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {//private TransferService transferService = new TransferServiceImpl();//private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");/*    private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");private TransferService transferService = (TransferService) proxyFactory.getProxy(BeanFactory.getBean("transferService"));*/private TransferService transferService;@Overridepublic void init() throws ServletException {WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());transferService = (TransferService) webApplicationContext.getBean("transferService");}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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));}
}

2.2 xml + 注解改造 (在纯xml配置的基础上做修改,第三方jar包中定义的bean使用xml,自定义bean使用注解形式)

  • 开启注解扫描,扫描指定包下是否有springbean需要生成

        <!--开启注解扫描,base-package指定扫描的包路径--><context:component-scan base-package="com.lb"/>
    
  • 第三方jar包的bean只有Druid连接池,需要引入外部资源文件,定义数据库的相关属性,再将第三方jar中的bean定义在xml中

        <!--引入外部资源文件--><context:property-placeholder location="classpath:jdbc.properties"/><!--第三方jar中的bean定义在xml中--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean>
    
  • 对应applicationContext.xml中自定义bean,添加注解

2.3 纯注解改造 (在xml + 注解 的工程上改造)

  • 对应applicationContext.xml,创建配置类,配置第三方bean,然后删除applicationContext.xml

    @Configuration
    @ComponentScan("com.lb")
    @PropertySource({"classpath:jdbc.properties"})
    public class DataSourceConfig {@Value("${jdbc.driver}")private String driverClassName;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Bean("dataSource")public DataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}}
    
  • 修改web.xml,告诉监听器使用注解的方式启动IOC容器

    <!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器--><context-param><param-name>contextClass</param-name><param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value></context-param><!--配置启动类的全限定类名--><context-param><param-name>contextConfigLocation</param-name><param-value>com.lb.config.DataSourceConfig</param-value></context-param><!--监听器启动IOC容器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener></web-app>

3. AOP基础

3.1 Aop基本介绍

  • 在SpringAop中包含切入点,方位点和横切逻辑,此三部分可以锁定在哪个地方插入什么横切逻辑代码。

    在xml配置文件中的对应关系

  • 关于切入点表达式的说明(指横切逻辑需要插入的具体哪个方法)

    表达式语法说明

    示例:public * com.lb.dao.impl.UserDaoImpl.findUserById(java.lang.Integer))

访问修饰符 返回值 包名 类名和方法名 参数列表
可省略 返回值可以使⽤*,表示任意返回值 1、 包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个点 2、 包名可以使⽤…表示当前包及其⼦包 类名和⽅法名,都可以使⽤.表示任意类,任意⽅法 参数列表,可以使⽤具体类型 基本类型直接写类型名称 : int 引⽤类型必须写全限定类名:java.lang.String 参数列表可以使⽤*,表示任意参数类型,但是必须有参数 参数还可以使用…,表示有无参数均可,有参数可以是任意类型
  • 关于SpringAop的代理方式

    Spring 实现AOP思想使⽤的是动态代理技术。默认情况下,Spring会根据被代理对象是否实现接口来选择使⽤JDK还是CGLIB。当被代理对象没有实现 任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过 我们可以通过配置的⽅式,让Spring强制使用CGLIB。

3.2 基础使用

  1. 引入aop相关jar包

  2. 纯xml实现AOP,创建测试相关代码

    • pojo类 及 查询User接口和查询实现类

      User类

      public class User {private int id;
      }
      

      IUserDao接口

      public interface IUserDao {public User findUserById(Integer id);
      }
      

      UserDaoImpl实现类

      public class UserDaoImpl implements IUserDao {@Overridepublic User findUserById(Integer id) {System.out.println("查找中...");//int i = 1/0;return new User();}
      }
      
    • 横切逻辑类LogUtils

      public class LogUtils {public void printLogBef(JoinPoint joinPoint){Object[] args = joinPoint.getArgs();System.out.println("前置通知,参数是:" + Arrays.toString(args));}public void printLogReturning(Object returnValue){System.out.println("正常返回通知,返回值是:" + returnValue);}public void printLogException(Throwable e){System.out.println("异常通知,异常是:" + e);}public void printLogFinally(){System.out.println("最终通知...");}//环绕通知可以随意在方法执行的任何时机定义横切逻辑public Object printLogAround(ProceedingJoinPoint proceedingJoinPoint){//定义返回值Object returnValue = null;try {System.out.println("前置通知...");//1、获取参数Object[] args = proceedingJoinPoint.getArgs();//2、执行切入点方法returnValue = proceedingJoinPoint.proceed();System.out.println("后置通知...");}catch (Throwable t){//异常通知System.out.println("异常通知");t.printStackTrace();}finally {//最终通知System.out.println("最终通知");}return returnValue;}
      }
      
    • applicationContext.xml配置文件编写

      <beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="userDao" class="com.lb.dao.impl.UserDaoImpl"></bean><!--注入横切逻辑类的bean--><bean id="logUtils" class="com.lb.utils.LogUtils"></bean><!--aop配置开始--><aop:config><!--配置横切逻辑类--><aop:aspect id="logAdvice" ref="logUtils"><!--配置前置通知--><aop:pointcut id="pt" expression="execution(public * com.lb.dao.impl.UserDaoImpl.findUserById(java.lang.Integer))"/><aop:before method="printLogBef" pointcut-ref="pt"></aop:before><!--配置正常返回通知--><aop:after-returning method="printLogReturning" returning="returnValue" pointcut-ref="pt"></aop:after-returning><!--配置异常通知--><aop:after-throwing method="printLogException" throwing="e" pointcut-ref="pt"></aop:after-throwing><!--配置返回前通知--><aop:after method="printLogFinally" pointcut-ref="pt"></aop:after><!--配置环绕通知--><!--<aop:around method="printLogAround" pointcut-ref="pt"></aop:around>--></aop:aspect></aop:config></beans>
      
    • 测试代码

      public class Aoptest {@Testpublic void test(){ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");userDao.findUserById(1);}
      }
      
    • 正常测试

    • 异常测试

    • 环绕通知测试

    spring在纯xml模式下配置强制使用cglib动态代理的方式(需导入cglib的jar包)

    <aop:config proxy-target-class="true">
    
  3. xml + 注解 实现AOP,修改相关代码

    • 对应关系

    • 在IUserDao中添加方法

      public User findUserByIdXmlAnno(Integer id);
      
    • 在UserDaoImpl中添加方法

      @Override
      public User findUserByIdXmlAnno(Integer id) {System.out.println("查找中...");return new User();
      }
      
    • 添加横切逻辑类

      @Component
      @Aspect
      public class LogUtilsXmlAnno {@Pointcut("execution(* com.lb.dao.impl.UserDaoImpl.findUserByIdXmlAnno(..))")public void pointcut(){}@Before("pointcut()")public void printLogBef(JoinPoint joinPoint){Object[] args = joinPoint.getArgs();System.out.println("前置通知,参数是:" + Arrays.toString(args));}@AfterReturning(value = "pointcut()", returning = "returnValue")public void printLogReturning(Object returnValue){System.out.println("正常返回通知,返回值是:" + returnValue);}@AfterThrowing(value = "pointcut()", throwing = "e")public void printLogException(Throwable e){System.out.println("异常通知,异常是:" + e);}@After("pointcut()")public void printLogFinally(){System.out.println("最终通知...");}//环绕通知可以随意在方法执行的任何时机定义横切逻辑@Around("pointcut()")public Object printLogAround(ProceedingJoinPoint proceedingJoinPoint){//定义返回值Object returnValue = null;try {System.out.println("前置通知...");//1、获取参数Object[] args = proceedingJoinPoint.getArgs();//2、执行切入点方法returnValue = proceedingJoinPoint.proceed();System.out.println("后置通知...");}catch (Throwable t){//异常通知System.out.println("异常通知");t.printStackTrace();}finally {//最终通知System.out.println("最终通知");}return returnValue;}
      }
      
    • XML 中开启 Spring 对注解 AOP 的⽀持,开启包扫描 ,注释掉其他配置,将UserDao用@Component注解标注

      <!--开启spring对注解aop的⽀持-->
      <aop:aspectj-autoproxy/><context:component-scan base-package="com.lb"/>
      
    • 测试代码:

      @Test
      public void test2(){ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");userDao.findUserByIdXmlAnno(1);
      }
      
    • 测试结果:

      spring在xml+注解方式下配置强制使用cglib动态代理的方式(需导入cglib的jar包)

      <aop:aspectj-autoproxy proxy-target-class="true"/>
      
  4. 纯注解 实现AOP,修改相关代码

    使用纯注解开发,需要用注解替换掉applicationContext.xml中的注解支持的配置。

    注释xml文件,创建SpringConfiguration 配置类

    @Configuration
    @ComponentScan("com.lagou")
    @EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
    public class SpringConfiguration {}
    

    测试代码:

        @Testpublic void test3(){AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");userDao.findUserByIdXmlAnno(1);}
    

    测试结果:

spring在纯注解方式下配置强制使用cglib动态代理的方式(需导入cglib的jar包)

在配置类上@EnableAspectJAutoProxy注解后标注

@EnableAspectJAutoProxy(proxyTargetClass = true)

4. spring声明式事务

4.1 事务概念

事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从⽽确保了数 据的准确与安全。

编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务

声明式事务:通过xml或者注解配置的⽅式达到事务控制的目的,叫做声明式事务

4.2 事务的四大特性

  • 原子性(Atomicity):事务是一个不可分割的工作单位,事务中操作要么都发生,要么都不发生。

  • 一致性(Consistency):事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态

    例如转账前A有1000,B有1000。转账后A+B也得是2000。

  • 隔离性(Isolation): 事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务, 每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

    比如:事务1给员⼯涨⼯资2000,但是事务1尚未被提交,员⼯发起事务2查询⼯资,发现⼯资涨了2000 块钱,读到了事务1尚未提交的数据(脏读)

  • 持久性(Durability) :指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障 也不应该对其有任何影响。

4.3 事务的隔离级别

错误情况

  • 脏读:⼀个线程中的事务读到了另外⼀个线程中未提交的数据。
  • 不可重复读:⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据(前后内容不⼀样)
  • 虚读(幻读):⼀个线程中的事务读到了另外⼀个线程中已经提交的insert或者delete的数据(前后条数不⼀样)

对应的隔离级别

  • Serializable(串⾏化): 都可避免
  • Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。
  • Read committed(读已提交):可避免脏读情况发⽣。
  • Read uncommitted(读未提交): 所有情况都无法避免

级别越高,效率越低

4.4 事务的传播行为

事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B,A和B⽅法本 身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播行为。

A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为

PROPAGATION_REQUIRED 如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中, 加⼊到这个事务中。这是最常⻅的选择。
PROPAGATION_SUPPORTS ⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。
PROPAGATION_MANDATORY 使⽤当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则 执⾏与PROPAGATION_REQUIRED类似的操作。

4.5 spring声明式事务是什么

声明式事务要做的就是使⽤Aop(动态代 理)来将事务控制逻辑织⼊到业务代码

Spring不实现事务,只定义了事务实现的接口,具体实现由相应的jar包负责。

如下为Spring提供的事务接口:

public interface PlatformTransactionManager {TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;//提交事务void commit(TransactionStatus var1) throws TransactionException;//回滚事务void rollback(TransactionStatus var1) throws TransactionException;
}

举例:

  • Spring有内置的一些具体策略:DataSourceTransactionManager , HibernateTransactionManager 等 等。
  • 其他的策略:Spring JdbcTemplate(数据库操作⼯具)、Mybatis(mybatis-spring.jar)–> DataSourceTransactionManager

5. 案例改造(添加spring事务管理)

添加pom依赖

<!--spring aop的jar包支持-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.1.12.RELEASE</version>
</dependency><!--第三方的aop框架aspectj的jar-->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.13</version>
</dependency><!--引入spring声明式事务相关-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.12.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.1.12.RELEASE</version>
</dependency>

5.1纯xml改造(在springioc的实现案例上改造)

  1. 删除ConnectionUtils、TransactionManager、ProxyFactory三个自定义事务相关的类

  2. 删除applicationContext.xml中相关的bean注入

  3. 从DruidUtils类中直接获取数据源dataSource的bean,使用静态方法获取

    <bean id="dataSource" class="com.lb.utils.DruidUtils" factory-method="getInstance"></bean>
    
  4. 将accountDao中对ConnectionUtils的依赖注入 改为 直接注入dataSource

    <bean id = "accountDao" class="com.lb.dao.impl.JdbcAccountDaoImpl"><property name="dataSource" ref="dataSource"></property>
    </bean>
    
  5. 修改JdbcAccountDaoImpl类代码,改为直接注入dataSource

    public class JdbcAccountDaoImpl implements AccountDao {private DataSource dataSource;public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}@Overridepublic Account queryAccountByCardNo(String cardNo) throws Exception {Connection connection = dataSource.getConnection();String sql = "select * from account where cardNo = ?";PreparedStatement preparedStatement = connection.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;}@Overridepublic int updateAccountByCardNo(Account account) throws Exception {//Connection connection = DruidUtils.getInstance().getConnection();Connection connection = dataSource.getConnection();String sql = "update account set money = ? where cardNo = ?";PreparedStatement preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1, account.getMoney());preparedStatement.setString(2, account.getCardNo());int i = preparedStatement.executeUpdate();preparedStatement.close();//connection.close();return i;}
    }
    
  6. applicationContext.xml中配置横切逻辑类(PlatformTransactionManager接口的实现类)、事务属性定义、然后配置aop属性,将事务的横切逻辑织入Service代码中做事务管理

    <!--定义横切逻辑类-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean><!--事务属性定义-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager"><!--定制事务细节,传播行为、隔离级别等--><tx:attributes><!--一般性配置--><tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/><!--针对查询的覆盖性配置--><tx:method name="query*" read-only="true" propagation="SUPPORTS"/></tx:attributes></tx:advice><aop:config><aop:advisor advice-ref="txAdvice" pointcut="execution(* com.lb.service.impl.TransferServiceImpl.*(..))"/>
    </aop:config>
    

5.2 xml + 注解改造

  1. 在applicationContext.xml添加对注解事务的支持

    <!--配置事务管理器的具体实现(横切逻辑类)-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property>
    </bean><!--开启spring对注解事务的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
  2. 在接口、类或者方法上添加@Transactional注解

@Transactional(readOnly = false, propagation = Propagation.REQUIRED)

5.3 纯注解改造

Spring基于注解驱动开发的事务控制配置,只需要把 xml 配置部分改为注解实现。只是需要⼀个注解替换掉xml配置⽂件中的配置。

在 Spring 的配置类上添加 @EnableTransactionManagement 注解即可

@EnableTransactionManagement//开启spring注解事务的⽀持
public class SpringConfiguration {}

总结

  • Spring框架的核心思想就是IOC和AOP,主要解决了工程的对象创建和管理问题、横切逻辑代码重复问题。
  • 本文从Spring的定义、基本特性、核心思想、简单案例实现、自定义IOC和AOP,再到Spring 的IOC和AOP使用介绍,最后到使用Spring进行案例改造,逐步推进,由浅入深,完成对Spring框架的学习。
  • 关于spring的延迟加载、FactoryBean,可以查看下面两个博客:
    spring的延迟加载介绍
    spring关于FactoryBean的基本介绍

关于spring框架,看这一篇就够了~~~相关推荐

  1. Java 集合框架看这一篇就够了

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:都说了多少遍,不要再学 JSP 了!个人原创100W+访问量博客:点击前往,查看更多 话不多说,直接上图: Ja ...

  2. 学习Spring框架有这一篇就够了

    目录 一.Spring框架 1.什么是Spring 2. 为什么使用spring 3.程序的耦合 4.解决办法IOC控制反转 二. 如何使用spring 1.Spring程序开发步骤 2.使用spri ...

  3. 不必东奔西走,Java 集合框架看这一篇就够了

    Java 集合,也称作容器,主要是由两大接口 (Interface) 派生出来的:Collection 和 Map 顾名思义,容器就是用来存放数据的. 那么这两大接口的不同之处在于: Collecti ...

  4. 聊聊Java8之后的JDK升级内容(看这一篇就够了)

    聊聊Java8之后的JDK升级内容(看这一篇就够了) 背景 从 JDK 8 到 JDK 17 的新特性 JDK8 回顾 JDK9 JDK10 JDK11 JDK12 JDK13 JDK14 JDK15 ...

  5. python装饰器功能是冒泡排序怎么做_传说中Python最难理解的点|看这完篇就够了(装饰器)...

    https://mp.weixin.qq.com/s/B6pEZLrayqzJfMtLqiAfpQ 1.什么是装饰器 网上有人是这么评价装饰器的,我觉得写的很有趣,比喻的很形象 每个人都有的内裤主要是 ...

  6. 如何应对大数据分析工程师面试Spark考察,看这一篇就够了

    作者丨斌迪.HappyMint 来源丨大数据与人工智能(ID:ai-big-data) [导读]本篇文章为大家带来spark面试指南,文内会有两种题型,问答题和代码题,题目大部分来自于网络上,有小部分 ...

  7. Fortran保姆级教学——考试所有知识点看这一篇就够了

    Fortran保姆级教学--考试所有知识点看这一篇就够了 临近期末本人复习的同时将整个fortran课堂知识整理了下来,希望学弟学妹们今后学这门课的时候不至于在csdn找不到系统的教程,也希望能帮到需 ...

  8. 17万字 JUC 看这一篇就够了(三) (精华)

    今天我们继续来学习Java并发编程 Juc框架 ,把剩余部分学习完 17万字 JUC 看这一篇就够了(一) (精华) 17万字 JUC 看这一篇就够了(二) (精华) 文章目录 非公原理 加锁 解锁 ...

  9. 面试被问到 ConcurrentHashMap答不出 ,看这一篇就够了!

    本文汇总了常考的 ConcurrentHashMap 面试题,面试 ConcurrentHashMap,看这一篇就够了!为帮助大家高效复习,专门用"★ "表示面试中出现的频率,&q ...

  10. 阿里大师总结的Web安全超全知识点,看这一篇就够了

    安全是互联网公司的生命,也是每一位网民的基本需求. 但根据<2021上半年中国互联网安全报告>,我国Web攻击.恶意爬虫攻击量连年翻倍增长,Web安全根本不能得到正常保障. 由此可见,We ...

最新文章

  1. 兵团教师计算机水平考试免考条件,兵团职称计算机考试政策.doc
  2. python循环生成二维数组_嵌套循环二维数组的计算与构造 - python
  3. python测试代码怎么写_Python 单元测试
  4. 如何学习开源系统有感(一)
  5. docker: docker安装和镜像下载
  6. java 手动线程调度_Java Thread 多线程 操作线程
  7. Docker仓库搭建
  8. 如何在 Eclipse 中使用命令行
  9. linux下进程调度模拟程序,linux认证辅导:linux进程调度模拟怎么做?
  10. leetCode 204. Count Primes 哈希 求素数
  11. 2021年中国超声波织物切割机市场趋势报告、技术动态创新及2027年市场预测
  12. 使用LIstView和自定义Adapter完成列表信息显示
  13. matlab中结构体使用方法
  14. osgEarth 加载矢量shp数据
  15. 基于微信小程序的记账系统
  16. JavaScript debugger调试
  17. [完全图解].NET Croe 使用JWT验证签名
  18. 小米路由器开启DDNS并支持二级路由
  19. Windows技巧:右键文件打开方式,该文件没有与之关联来执行该操作
  20. 3D-3D:ICP_SVD

热门文章

  1. 问题百钱买百鸡的随手笔记
  2. 2016年上半年信息系统项目管理师真题之上午题小虎趣味解答第26-30题
  3. 苏嵌实训——day18
  4. Video process: 视频剪辑和格式转换 (windows)
  5. SEO呼叫中心解决方案有哪些?
  6. Swift中的subscript
  7. 分段与分页的定义与区别
  8. 逆战d3dx10_43.dll文件加载失败及dll文件缺失损坏修复解决方案
  9. Typecho——简介及安装
  10. SELECT SINGLE 和 SELECT ENDSELECT的区别!