文章目录

  • 一、今日内容
  • 二、转账代码(初步编码+回顾IOC)
    • 1、引入依赖
    • 2、实体类
    • 3、持久层
    • 4、业务层
    • 5、配置文件
    • 6、测试
    • 7、发现问题
  • 三、解决转账问题
    • 1、引入工具类
    • 2、修改业务层
    • 3、发现新问题
  • 四、动态代理回顾
    • 1、jdk动态代理
    • 2、cglib动态代理
  • 五、动态代理解决新问题(自己写底层代理代码)
    • 1、jdk动态代理解决问题
    • 2、cglib动态代理解决问题
  • 六、什么是AOP
    • 概念
    • 相关术语
  • 七、AOP的xml配置
    • 1、依赖
    • 2、service
    • 3、Log
    • 4、配置
    • 5、测试
  • 八、AOP的注解方式
  • 九、核心总结
  • -------------------------------------------------------
  • 作业:用工具类+AOP 管理事务
    • xml配置AOP
      • 类结构图
      • 1、依赖
      • 2、domain
      • 3、dao
      • 4、service
      • 5、utils
      • 6、配置
      • 7、测试
    • 注解实现AOP

一、今日内容

1、转账编码
2、解决转账问题
3、动态代理回顾
4、解决转账问题
5、什么是AOP
6、AOP的xml配置
7、AOP的注解配置

二、转账代码(初步编码+回顾IOC)

1、引入依赖

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.36</version></dependency><dependency><groupId>commons-dbutils</groupId><artifactId>commons-dbutils</artifactId><version>1.6</version></dependency><dependency><groupId>c3p0</groupId><artifactId>c3p0</artifactId><version>0.9.1.2</version></dependency></dependencies>

2、实体类

public class Account {private Integer id;private String name;private Float money;
}

3、持久层

AccountDao

package cn.ahpu.dao;import cn.ahpu.Account;/*** @author 寒面银枪* @create 2020-02-15 23:04*/
public interface AccountDao {/*** 根据账户名查找账户* @param fromName* @return*/Account findByName(String fromName);/*** 更新账户* @param fromAccount*/void update(Account fromAccount);
}

AccountDaoImpl

package cn.ahpu.dao.impl;import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;import java.sql.SQLException;/*** @author 寒面银枪* @create 2020-02-15 23:06*/
@Repository
public class AccountDaoImpl implements AccountDao {@AutowiredQueryRunner queryRunner;/*** 根据账户名查找账户*/@Overridepublic Account findByName(String name) {String sql="select * from account where name = ? ";try {return queryRunner.query(sql,new BeanHandler<Account>(Account.class),name);} catch (SQLException e) {e.printStackTrace();}return null;}/*** 更新账户*/@Overridepublic void update(Account account) {String sql="update account set money =? where name=?";try {queryRunner.update(sql,account.getMoney(),account.getName());} catch (SQLException e) {e.printStackTrace();}}
}

4、业务层

AccountService

package cn.ahpu.service;/*** @author 寒面银枪* @create 2020-02-15 22:53*/
public interface AccountService {/*** @param fromName 从哪个账户转出* @param toName   转入到哪个账户* @param money    本次转账金额*/public void transfer(String fromName,String toName,Float money);}

AccountServiceImpl

package cn.ahpu.service.impl;import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import cn.ahpu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @author 寒面银枪* @create 2020-02-15 22:56** 一个事务必须在一个connection中完成* ThreadLocal:线程绑定*  绑定connection对象*  业务层和持久层需要connection时从ThreadLocal中获取即可**/
@Service
public class AccountServiceImpl implements AccountService {@AutowiredAccountDao accountDao;/*** @param fromName 从哪个账户转出* @param toName   转入到哪个账户* @param money    本次转账金额*/@Overridepublic void transfer(String fromName, String toName, Float money) {Account fromAccount=accountDao.findByName(fromName);Account toAccount=accountDao.findByName(toName);fromAccount.setMoney(fromAccount.getMoney()-money);toAccount.setMoney(toAccount.getMoney()+money);accountDao.update(fromAccount);System.out.println(1/0);//异常accountDao.update(toAccount);}  }

5、配置文件

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"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!--扫描包 创建对象--><context:component-scan base-package="cn.ahpu"></context:component-scan><!--创建queryRunner对象:构造方法中需要DataSource--><bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"><constructor-arg name="ds" ref="dataSource"></constructor-arg></bean><!--创建连接池:ComboPooledDataSource--><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="jdbcUrl" value="${jdbc.url}"></property><property name="driverClass" value="${jdbc.driver}"></property><property name="user" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property></bean><!--引入外部属性文件--><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder></beans>

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring331
jdbc.username=root
jdbc.password=root

6、测试

package cn.ahpu;import cn.ahpu.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/*** @author 寒面银枪* @create 2020-02-15 23:24*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestTrans {@AutowiredAccountService accountService;@Testpublic void test(){accountService.transfer("aaa","bbb",100f);}}

7、发现问题

两次更新之间一旦有异常,就出错,A钱少了,B钱没加,银行越来越富,老百姓越来越穷
解决:添加事务处理
AccountServiceImpl

package cn.ahpu.service.impl;import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import cn.ahpu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @author 寒面银枪* @create 2020-02-15 22:56** 一个事务必须在一个connection中完成* ThreadLocal:线程绑定*  绑定connection对象*  业务层和持久层需要connection时从ThreadLocal中获取即可**/
@Service
public class AccountServiceImpl implements AccountService {@AutowiredAccountDao accountDao;/*** @param fromName 从哪个账户转出* @param toName   转入到哪个账户* @param money    本次转账金额*/@Overridepublic void transfer(String fromName, String toName, Float money) {try {//事务1:开启事务 conn.setAutoCommit(false)//查询要转出的账户Account fromAccount=accountDao.findByName(fromName);//查询转入的账户Account toAccount=accountDao.findByName(toName);//修改要转出账户的余额:假设余额充足fromAccount.setMoney(fromAccount.getMoney()-money);//修改要转入账户的余额toAccount.setMoney(toAccount.getMoney()+money);//持久化到数据库accountDao.update(fromAccount);System.out.println(1/0);//异常accountDao.update(toAccount);//事务2:提交事务 conn.commit} catch (Exception e) {//事务3:回滚事务 conn.rollbacke.printStackTrace();} finally {//事务4:还原状态 conn.setAutoCommit(true)}}/* 初版代码就这么简单Account fromAccount=accountDao.findByName(fromName);Account toAccount=accountDao.findByName(toName);fromAccount.setMoney(fromAccount.getMoney()-money);toAccount.setMoney(toAccount.getMoney()+money);accountDao.update(fromAccount);accountDao.update(toAccount);*/}

三、解决转账问题

此次代码实在上述代码基础上修改

1、引入工具类

新建包utils,复制两个工具类:

ConnectionUtil.java(保证当前线程获取的connection是同一个)

package cn.ahpu.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;/*** 一个管理连接的工具类,用于实现连接和线程的绑定* 保证当前线程获取的connection是同一个* @author 黑马程序员* @Company http://www.ithiema.com* @Version 1.0*/
@Component
public class ConnectionUtil {private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();//获得当前线程@Autowiredprivate DataSource dataSource;public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}/*** 获取当前线程上绑定的连接* @return*/public Connection getThreadConnection() {try {//1.先看看线程上是否绑了Connection conn = tl.get();if(conn == null) {//2.从数据源中获取一个连接conn = dataSource.getConnection();//3.和线程局部变量绑定tl.set(conn);}//4.返回线程上的连接return tl.get();} catch (SQLException e) {throw new RuntimeException(e);}}/*** 把连接和当前线程解绑*/public void remove() {tl.remove();}
}

TransactionManager.java(封装一下事务4步)

package cn.ahpu.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.sql.SQLException;/*** 事务管理器* @author 黑马程序员* @Company http://www.ithiema.com ** @Version 1.0*/
@Component
public class TransactionManager {@Autowiredprivate ConnectionUtil connectionUtil;public void setConnectionUtil(ConnectionUtil connectionUtil) {this.connectionUtil = connectionUtil;}//开启事务public void beginTransaction() {//从当前线程上获取连接,实现开启事务try {connectionUtil.getThreadConnection().setAutoCommit(false);} catch (SQLException e) {e.printStackTrace();}}//提交事务public void commit() {try {connectionUtil.getThreadConnection().commit();} catch (SQLException e) {e.printStackTrace();}}//回滚事务public void rollback() {try {connectionUtil.getThreadConnection().rollback();} catch (SQLException e) {e.printStackTrace();}}//释放连接public void release() {try {connectionUtil.getThreadConnection().setAutoCommit(true);//关闭连接(还回池中)connectionUtil.getThreadConnection().close();//解绑线程:把连接和线程解绑connectionUtil.remove();} catch (SQLException e) {e.printStackTrace();}}
}

注意两个工具类都有@Component注解 服务器启动时spring都会创建唯一单例类到容器中,也即两个工具类都可以自动注入了

2、修改业务层

加获取connection
AccountDaoImpl.java

package cn.ahpu.dao.impl;import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import cn.ahpu.utils.ConnectionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;import java.sql.Connection;
import java.sql.SQLException;/*** @author 寒面银枪* @create 2020-02-15 23:06*/
@Repository
public class AccountDaoImpl implements AccountDao {@AutowiredQueryRunner queryRunner;@AutowiredConnectionUtil connectionUtil;/*** 根据账户名查找账户*/@Overridepublic Account findByName(String name) {String sql="select * from account where name = ? ";try {//从线程中获取一个连接对象Connection conn = connectionUtil.getThreadConnection();return queryRunner.query(conn,sql,new BeanHandler<Account>(Account.class),name);} catch (SQLException e) {e.printStackTrace();}return null;}/*** 更新账户*/@Overridepublic void update(Account account) {String sql="update account set money =? where name=?";try {Connection conn = connectionUtil.getThreadConnection();queryRunner.update(conn,sql,account.getMoney(),account.getName());} catch (SQLException e) {e.printStackTrace();}}
}

修改处:

AccountServiceImpl

package cn.ahpu.service.impl;import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import cn.ahpu.service.AccountService;
import cn.ahpu.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @author 寒面银枪* @create 2020-02-15 22:56** 一个事务必须在一个connection中完成* ThreadLocal:线程绑定*  绑定connection对象*  业务层和持久层需要connection时从ThreadLocal中获取即可**/
@Service
public class AccountServiceImpl implements AccountService {@AutowiredAccountDao accountDao;@AutowiredTransactionManager txManager;    /*** 假设需要事务管理  此方法仅为体现问题 不用* @param account*/public void update(Account account){try {//事务1:开启事务 conn.setAutoCommit(false)txManager.beginTransaction();accountDao.update(account);//本来一行的事//事务2:提交事务 conn.committxManager.commit();} catch (Exception e) {//事务3:回滚事务 conn.rollbacktxManager.rollback();e.printStackTrace();} finally {//事务4:还原状态 conn.setAutoCommit(true)txManager.release();}}/*** @param fromName 从哪个账户转出* @param toName   转入到哪个账户* @param money    本次转账金额*/@Overridepublic void transfer(String fromName, String toName, Float money) {try {//事务1:开启事务 conn.setAutoCommit(false)txManager.beginTransaction();//查询要转出的账户Account fromAccount=accountDao.findByName(fromName);//查询转入的账户Account toAccount=accountDao.findByName(toName);//修改要转出账户的余额:假设余额充足fromAccount.setMoney(fromAccount.getMoney()-money);//修改要转入账户的余额toAccount.setMoney(toAccount.getMoney()+money);//持久化到数据库accountDao.update(fromAccount);System.out.println(1/0);//异常accountDao.update(toAccount);//事务2:提交事务 conn.committxManager.commit();} catch (Exception e) {//事务3:回滚事务 conn.rollbacktxManager.rollback();e.printStackTrace();} finally {//事务4:还原状态 conn.setAutoCommit(true)txManager.release();}}}

此时再测试就没问题了!

3、发现新问题

问题:

1. 重复代码(每一个业务需求函数都要加那4步代码)
2. 代码臃肿问题(本来更新account一行即可 转账业务也6行即可 现在try-catch-事务等弄了一大堆)
3. 技术与业务整合到一起了(业务逻辑代码里冲刺着大量技术相关代码(事务处理))

解决思路:

1. 提取重复的代码
2. 业务层中不需要技术代码
3. 不修改业务层源码的情况下,技术增强
4. 使用动态代理

动态代理

特点:随用随创建,随用随加载
不修改原来代码的基础上,对原来的代码增强

四、动态代理回顾


准备工作:
pom.xml:

<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version></dependency><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</version></dependency></dependencies>

ProductFactory

public class ProductFactory {/*** 制造产品*/public void make(){System.out.println("生产了一个产品!");}}

OldSale

/*** 建立了一个销售的网点:直营*  厂家直营 没有中间商赚差价* @author 寒面银枪* @create 2020-02-16 22:33*/
public class OldSale {public void sale(Float money){System.out.println("正在以"+money+"价格卖出");}
}

NewSale

package cn.ahpu.sale;/*** 总经销商* @author 寒面银枪* @create 2020-02-16 22:39*/
public interface NewSale {/*** 卖商品* @param money*/public void sale(Float money);
}

NewSaleImpl

public class NewSaleImpl implements NewSale {/*** 卖商品* @param money*/@Overridepublic void sale(Float money) {System.out.println("正在以"+money+"价格卖出");}
}

1、jdk动态代理

jdk动态代理: 基于接口的动态代理
必须实现接口 必须有一个统一接口

TestProxy

 @Testpublic void testJdkProxy(){//真实的对象NewSale newSale=new NewSaleImpl();//创建代理对象 本质也是NewSale接口的一个实现类 不过是在已有类的基基础上新建的一个实现类//参数1:类加载器//参数2:类实现的接口//参数3:真实对象的增强部分 即:实现了InvocationHandler接口的类(此处就用匿名内部类)NewSale sale= (NewSale) Proxy.newProxyInstance(newSale.getClass().getClassLoader(), newSale.getClass().getInterfaces(), new InvocationHandler() {/*** 增强内容* @param proxy 代理对象:增强后的对象* @param method 代理的方法:未增强的方法* @param args 代理方法的参数* @return    代理方法的返回值* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//生成产品ProductFactory productFactory = new ProductFactory();productFactory.make();//开始销售:通过反射执行真实对象的方法//参数1: 真实对象//参数2: 方法参数method.invoke(newSale,args);//就是代理的方法 原来的方法 也可以可以获取方法名然后判断是否增强/*主要就是在method.invoke()前后进行增强 ★*///判断是否挣钱//卖的价格 args[0]  假设成本1500if((Float)args[0]>1500){//卖的价格高于成本 可买System.out.println("卖的价格高于成本 可买");}else{//赔了System.out.println("卖的价格低于成本 不可买");}return null;}});sale.sale(2000f);}

2、cglib动态代理

记得引入cglibjar包

cglib动态代理: 基于类的动态代理
第三方jar cglib-2.2.2.jar
代理的类不能用final修饰

/*** cglib动态代理*  代理对象是真实对象的一个子类(★ 写个子类增强父类方法 多简单)*/@Testpublic void testCglibProxy(){//真实对象OldSale oldSale = new OldSale();//创建cglib代理对象//1.创建增强类对象Enhancer enhancer = new Enhancer();//2.指定代理对象的父类enhancer.setSuperclass(oldSale.getClass());//3.指定增强内容// MethodInterceptor接口其实是个方法拦截器enhancer.setCallback(new MethodInterceptor() {/**** @param o            代理对象,增强后的对象* @param method       被代理的方法 未增强的方法* @param objects      代理方法的参数* @param methodProxy  代理方法   增强后的方法* @return* @throws Throwable*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {//生产产品new ProductFactory().make();//开始销售:执行真实对象的内容method.invoke(oldSale,objects);//判断是否挣钱了if((Float)objects[0]>1500){System.out.println("卖的价格高于成本 可买");}else{System.out.println("卖的价格低于成本 不可买");}return null;}});//4.创建代理对象OldSale sale = (OldSale) enhancer.create();sale.sale(1000f);}

五、动态代理解决新问题(自己写底层代理代码)

上述三代码基础上再修改,两个工具类都还用,不过事务处理不写在service层了,而是测试时用动态代理加事务,一加service层所有方法就都添加上了事务

AccountServiceImpl.java

package cn.ahpu.service.impl;import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import cn.ahpu.service.AccountService;
import cn.ahpu.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @author 寒面银枪* @create 2020-02-15 22:56* <p>* 一个事务必须在一个connection中完成* ThreadLocal:线程绑定* 绑定connection对象* 业务层和持久层需要connection时从ThreadLocal中获取即可*/
@Service
public class AccountServiceImpl implements AccountService {@AutowiredAccountDao accountDao;/*** 更新账户 假设需要事务* @param account*/@Overridepublic void update(Account account){accountDao.update(account);}/*** @param fromName 从哪个账户转出* @param toName   转入到哪个账户* @param money    本次转账金额*/@Overridepublic void transfer(String fromName, String toName, Float money) {//查询要转出的账户Account fromAccount = accountDao.findByName(fromName);//查询转入的账户Account toAccount = accountDao.findByName(toName);//修改要转出账户的余额:假设余额充足fromAccount.setMoney(fromAccount.getMoney() - money);//修改要转入账户的余额toAccount.setMoney(toAccount.getMoney() + money);//持久化到数据库accountDao.update(fromAccount);System.out.println(1 / 0);//异常accountDao.update(toAccount);}}

1、jdk动态代理解决问题

//jdk动态代理@Testpublic void testJDKProxyService() {//创建业务层代理对象AccountService service= (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {//开启事务txManager.beginTransaction();//执行真实对象的方法method.invoke(accountService,args);//提交事务txManager.commit();} catch(Exception e) {//事务回滚txManager.rollback();e.printStackTrace();} finally {//还原状态txManager.release();}return null;}});//AccountService的所有方法就都加上事务了service.transfer("aaa","bbb",100f);}

2、cglib动态代理解决问题

//cglib动态代理@Testpublic void testCglibProxy(){Enhancer enhancer = new Enhancer();enhancer.setSuperclass(accountService.getClass());enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {try {//开启事务txManager.beginTransaction();//执行方法method.invoke(accountService,objects);//提交事务txManager.commit();} catch (Exception e) {//回滚事务txManager.rollback();e.printStackTrace();} finally {//还原状态txManager.release();}return null;}});//创建代理对象AccountService service= (AccountService) enhancer.create();//AccountService的所有方法就都加上事务了service.transfer("aaa","bbb",100f);}

六、什么是AOP

概念

AOP:全称是Aspect Oriented Programming即:面向切面编程。

简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

相关术语

Joinpoint(连接点):

所谓连接点是指那些被拦截到的点。 在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

Pointcut(切入点):

所谓切入点是指我们要对哪些Joinpoint进行拦截的定义

Advice(通知/增强):

所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

Introduction(引介):

引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。

Target(目标对象):

代理的目标对象。

Weaving(织入):

是指把增强应用到目标对象来创建新的代理对象的过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

Proxy(代理):

一个类被AOP织入增强后,就产生一个结果代理类。

Aspect(切面):

是切入点和通知(引介)的结合。

七、AOP的xml配置

AOP写日志,以理解AOP
新建项目

1、依赖

<dependencies><!--spring核心包--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.7.RELEASE</version></dependency><!--spring测试包--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version></dependency><!--引入单元测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version></dependency><!--aop配置 必备包 切面 版本必须1.8.7以上--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version></dependency></dependencies>

2、service

UserService.java

public interface UserService {public void print();
}

AccountService.java

public interface AccountService {public void transfer(String name);
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {@Overridepublic void print() {System.out.println("service执行了...........");System.out.println(1/0);}
}

AccountServiceImpl.java

@Service
public class AccountServiceImpl implements AccountService {@Overridepublic void transfer(String name) {System.out.println("service层转账");}
}

3、Log

Logger.java

package cn.ahpu.log;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;/*** 日志类*   记录什么时间访问什么类,什么方法,操作什么功能* @author 寒面银枪* @create 2020-02-18 0:07*/
public class Logger {/*** @param joinPoint 连接点--拦截到的方法(注意P大写的)*/public void before(JoinPoint joinPoint){//被代理的对象Object target = joinPoint.getTarget();//拦截的类的名称String className = target.getClass().getName();//方法对象Signature signature = joinPoint.getSignature();//方法名String methodName = signature.getName();System.out.println("拦截到"+className+"."+methodName);System.out.println("前置通知:可以开启事务");}public void afterReturn(){System.out.println("后置通知:可以提交事务");}public void after(){System.out.println("最终增强:可以还原事务状态");}public void afterThrowing(Exception e){System.out.println("执行的方法的异常:"+e);System.out.println("异常通知:可以回滚事务");}//最牛环绕增强/*** ProceedingJoinPoint 可以执行拦截到的方法的连接点对象* @param joinPoint*/public void around(ProceedingJoinPoint joinPoint){try {System.out.println("前置通知:可以开启事务");//执行原来的方法 可以获取方法的返回值oObject o = joinPoint.proceed();System.out.println("后置通知:可以提交事务");} catch (Throwable e) {//Throwable比exception还要大  错误还是异常都捕捉e.printStackTrace();System.out.println("异常通知:可以回滚事务");} finally {System.out.println("最终增强:可以还原事务状态");}}
}

4、配置

spring第三天讲义
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"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--扫描包,查找@Component,@Controller,@Service,@Repository 创建bean对象--><context:component-scan base-package="cn.ahpu"></context:component-scan><!--通知对象:拦截到方法时,通知执行的对象--><!--通知的类型:前置通知:在拦截到的方法之前执行后置通知:在拦截到的方法执行完之后执行-返回之前执行-如有异常,则不执行最终通知:方法执行完后总会执行-相当于finally异常通知:方法出现异常则执行环绕通知:前置通知+后值通知+最终通知+异常通知(一个代表4个 4处都执行)--><bean id="logger" class="cn.ahpu.log.Logger"></bean><!--配置AOP--><aop:config><!--配置切面=切入点(对哪些方法进行拦截)+通知(通知对象上面已经创建)--><aop:aspect ref="logger"><!--通知对象这里指定--><!--配置切入点id:唯一的标志expression:表达式eg:* cn.ahpu.service.impl.*.*(..)第一个*:代表方法返回值类型任意第二个*:类名任意 即:impl包中所有的类第三个*:任意方法名(..):参数任意,个数和类型和顺序都任意其他的配置方式:public void cn.ahpu.service.impl.UserServiceImpl.findAll()void cn.ahpu.service.impl.UserServiceImpl.findAll()  (即:public可以省略)* cn.ahpu.service..UserServiceImpl.findAll()  (..表示service包及其所有子包)注: 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。execution(* cn.ahpu.service.impl.*.*(..))execution(* cn.ahpu.service..*.*(..))--><aop:pointcut id="pointcut" expression="execution(* cn.ahpu.service.impl.*.*(..))"></aop:pointcut><!--织入:告诉通知对象logger 具体执行哪个方法两个属性:切入点(拦截到的方法)  和  对应方法(需要执行的方法)--><!--前置通知--><!--<aop:before method="before" pointcut-ref="pointcut"></aop:before>--><!--后置通知--><!--<aop:after-returning method="afterReturn" pointcut-ref="pointcut"></aop:after-returning>--><!--最终通知--><!--<aop:after method="after" pointcut-ref="pointcut"></aop:after>--><!--异常通知 throwing="e" 参数名--><!--<aop:after-throwing throwing="e" method="afterThrowing" pointcut-ref="pointcut"></aop:after-throwing>--><!--前四个都注释(ctrl+/即可) 只留最后一个--><!--环绕通知--><aop:around method="around" pointcut-ref="pointcut"></aop:around></aop:aspect></aop:config></beans>

5、测试

TestAOPXml.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAOPXml {@AutowiredUserService userService;@AutowiredAccountService accountService;@Testpublic void test(){userService.print();System.out.println("---------------------------------------");accountService.transfer("tom");//所有service层的方法都被增强了}
}

八、AOP的注解方式

相对于xml仅applicationContext.xml和Logger.java俩文件内容不同
xml里配置一行开启aop注解代理即可
Logger.java里多了一大堆注解

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"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--扫描包,查找@Component,@Controller,@Service,@Repository 创建bean对象--><context:component-scan base-package="cn.ahpu"></context:component-scan><!--开启AOP自动代理-注解方式代理--><aop:aspectj-autoproxy></aop:aspectj-autoproxy><!--通知对象:直接类上面@Component即可--><!--剩下的配置全部在log类内以注解方式配好了--></beans>

Logger.java

package cn.ahpu.log;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component  //创建类对象
@Aspect     //配置该类为切面 即:切入点+通知
public class Logger {/*** 配置切入点*/@Pointcut("execution(* cn.ahpu.service.impl.*.*(..))")public void ponintcut(){};//方法名和参数无所谓 因为相当于xml中的id/*** @param joinPoint 连接点--拦截到的方法(注意P大写的)*/
//    @Before("ponintcut()")public void before(JoinPoint joinPoint){//被代理的对象Object target = joinPoint.getTarget();//拦截的类的名称String className = target.getClass().getName();//方法对象Signature signature = joinPoint.getSignature();//方法名String methodName = signature.getName();System.out.println("拦截到"+className+"."+methodName);System.out.println("前置通知:可以开启事务");}//    @AfterReturning("ponintcut()")public void afterReturn(){System.out.println("后置通知:可以提交事务");}//    @After("ponintcut()")public void after(){System.out.println("最终增强:可以还原事务状态");}//    @AfterThrowing(value = "ponintcut()",throwing = "e")public void afterThrowing(Exception e){System.out.println("执行的方法的异常:"+e);System.out.println("异常通知:可以回滚事务");}//最牛环绕增强/*** ProceedingJoinPoint 可以执行拦截到的方法的连接点对象* @param joinPoint*/@Around("ponintcut()") //开此注解时注释掉了前4个注解  1个抵4个public void around(ProceedingJoinPoint joinPoint){try {System.out.println("前置通知:可以开启事务");//执行原来的方法 可以获取方法的返回值oObject o = joinPoint.proceed();System.out.println("后置通知:可以提交事务");} catch (Throwable e) {//Throwable比exception还要大  错误还是异常都捕捉e.printStackTrace();System.out.println("异常通知:可以回滚事务");} finally {System.out.println("最终增强:可以还原事务状态");}}
}

测试代码也同上

九、核心总结

1. aop什么是aop:对方法进行增强面向切面编程面向多个对象管理面向对象编程1) 连接点:spring中方法就是连接点2) 切面: 切入点  + 通知(增强) ===> 织入3) 切入点:定义切入(拦截)哪个点 ,切入点表达式:* cn.ahpu.service.impl.*.*(..)* cn.ahpu.service..*.*(..)4) 通知:拦截到方法后进行的增强内容通知的类型   前置通知:后置通知:最终通知: finally ,释放资源异常通知:环绕通知:5) 织入 : 切入点 和通知 织入到一起6)target :目标对象, 真实的对象 7) 代理对象:增强之后的对象

-------------------------------------------------------

作业:用工具类+AOP 管理事务

xml配置AOP

类结构图

1、依赖

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version></dependency><!--AOP--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.36</version></dependency><dependency><groupId>commons-dbutils</groupId><artifactId>commons-dbutils</artifactId><version>1.6</version></dependency><dependency><groupId>c3p0</groupId><artifactId>c3p0</artifactId><version>0.9.1.2</version></dependency></dependencies>

2、domain

public class Account {private Integer id;private String name;private Float money;
}

3、dao

AccountDao.java

public interface AccountDao {Account findByName(String name);void update(Account account);
}

AccountDaoImpl.java

package cn.ahpu.dao.impl;import cn.ahpu.dao.AccountDao;
import cn.ahpu.domain.Account;
import cn.ahpu.utils.ConnectionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;import java.sql.Connection;
import java.sql.SQLException;/*** @author 寒面银枪* @create 2020-02-18 21:08*/
@Repository
public class AccountDaoImpl implements AccountDao {@AutowiredConnectionUtil connectionUtil;@AutowiredQueryRunner queryRunner;@Overridepublic Account findByName(String name) {String sql="select * from account where name=?";try {return queryRunner.query(connectionUtil.getThreadConnection(),sql,new BeanHandler<>(Account.class),name);} catch (SQLException e) {e.printStackTrace();}return null;}@Overridepublic void update(Account account) {String sql="update account set money=? where name=?";try {queryRunner.update( connectionUtil.getThreadConnection(),sql,account.getMoney(),account.getName());} catch (SQLException e) {e.printStackTrace();}}
}

4、service

AccountService.java

public interface AccountService {public void transfer(String fromName,String toName,Float money);
}

AccountServiceImpl.java

package cn.ahpu.service.impl;import cn.ahpu.dao.AccountDao;
import cn.ahpu.domain.Account;
import cn.ahpu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @author 寒面银枪* @create 2020-02-18 21:01*/
@Service
public class AccountServiceImpl implements AccountService {@AutowiredAccountDao accountDao;@Overridepublic void transfer(String fromName, String toName, Float money) {//查询转出的账户Account fromAccount=accountDao.findByName(fromName);//查询接收的账户Account toAccount=accountDao.findByName(toName);//开始转账fromAccount.setMoney(fromAccount.getMoney()-money);toAccount.setMoney(toAccount.getMoney()+money);//持久化到数据库accountDao.update(fromAccount);System.out.println(1/0);accountDao.update(toAccount);}
}

5、utils

ConnectionUtil.java

package cn.ahpu.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;/*** 一个管理连接的工具类,用于实现连接和线程的绑定* 保证当前线程获取的connection是同一个* @author 黑马程序员* @Company http://www.ithiema.com* @Version 1.0*/
@Component
public class ConnectionUtil {private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();//获得当前线程@Autowiredprivate DataSource dataSource;public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}/*** 获取当前线程上绑定的连接* @return*/public Connection getThreadConnection() {try {//1.先看看线程上是否绑了Connection conn = tl.get();if(conn == null) {//2.从数据源中获取一个连接conn = dataSource.getConnection();//queryRunner内不必再获取连接了 可以检测到你获取没有//3.和线程局部变量绑定tl.set(conn);}//4.返回线程上的连接return tl.get();} catch (SQLException e) {throw new RuntimeException(e);}}/*** 把连接和当前线程解绑*/public void remove() {tl.remove();}
}

TransactionManager.java

package cn.ahpu.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.sql.SQLException;/*** 事务管理器: 是AOP通知对象* @author 黑马程序员* @Company http://www.ithiema.com ** @Version 1.0*/
@Component
public class TransactionManager {@Autowiredprivate ConnectionUtil connectionUtil;public void setConnectionUtil(ConnectionUtil connectionUtil) {this.connectionUtil = connectionUtil;}//开启事务public void beginTransaction() {//从当前线程上获取连接,实现开启事务try {connectionUtil.getThreadConnection().setAutoCommit(false);} catch (SQLException e) {e.printStackTrace();}}//提交事务public void commit() {try {connectionUtil.getThreadConnection().commit();} catch (SQLException e) {e.printStackTrace();}}//回滚事务public void rollback() {try {connectionUtil.getThreadConnection().rollback();} catch (SQLException e) {e.printStackTrace();}}//释放连接public void release() {try {connectionUtil.getThreadConnection().setAutoCommit(true);//关闭连接(还回池中)connectionUtil.getThreadConnection().close();//解绑线程:把连接和线程解绑connectionUtil.remove();} catch (SQLException e) {e.printStackTrace();}}
}

6、配置

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"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--开启注解 扫描包--><context:component-scan base-package="cn.ahpu"></context:component-scan><!--dao层需要注入的类 queryRunner--><bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"></bean><!--连接池 dataSource--><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="jdbcUrl" value="${jdbc.url}"></property><property name="driverClass" value="${jdbc.driver}"></property><property name="user" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property></bean><!--引入外部property--><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><!--使用AOP解决事务问题事务管理器就是通知对象--><aop:config><!--配置切面 默认类名--><aop:aspect ref="transactionManager"><!--切入点--><aop:pointcut id="pointcut" expression="execution(* cn.ahpu.service.impl.*.*(..))"></aop:pointcut><!--织入--><!--前置通知:开启事务--><aop:before method="beginTransaction" pointcut-ref="pointcut()"></aop:before><!--后置增强:提交事务--><aop:after-returning method="commit" pointcut-ref="pointcut()"></aop:after-returning><!--异常通知:回滚事务--><aop:after-throwing method="rollback" pointcut-ref="pointcut()"></aop:after-throwing><!--最终通知:还原事务状态--><aop:after method="release" pointcut-ref="pointcut()"></aop:after></aop:aspect></aop:config></beans>

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring331
jdbc.username=root
jdbc.password=root

7、测试

TestTransfer.java

package cn.ahpu;import cn.ahpu.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/*** @author 寒面银枪* @create 2020-02-18 21:30*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestTransfer {@AutowiredAccountService accountService;@Testpublic void test(){accountService.transfer("aaa","bbb",100f);}
}

注解实现AOP

xml基础上仅applicationContext.xml和TransactionManager不同

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"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--开启注解 扫描包--><context:component-scan base-package="cn.ahpu"></context:component-scan><!--dao层需要注入的类 queryRunner--><bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"></bean><!--连接池 dataSource--><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="jdbcUrl" value="${jdbc.url}"></property><property name="driverClass" value="${jdbc.driver}"></property><property name="user" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property></bean><!--引入外部property--><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><!--开启AOP自动代理注解--><aop:aspectj-autoproxy></aop:aspectj-autoproxy></beans>

TransactionManager.java

package cn.ahpu.utils;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.sql.SQLException;/*** 事务管理器: 是AOP通知对象* @author 黑马程序员* @Company http://www.ithiema.com ** @Version 1.0*/
@Component
@Aspect
public class TransactionManager {@Autowiredprivate ConnectionUtil connectionUtil;public void setConnectionUtil(ConnectionUtil connectionUtil) {this.connectionUtil = connectionUtil;}//切入点/* @Pointcut("execution(* cn.ahpu.service.impl.*.*(..))")public void pointcut(){ }*//*注解方式要使用环绕增强  分开4不无法控制顺序 不可行*///可以省略切入点定义 每个方法上都要自己写切入点表达式了 更灵活也更麻烦@Around("execution(* cn.ahpu.service.impl.*.*(..))") //织入 拦截到哪些方法时触发事件public void around(ProceedingJoinPoint joinPoint){try {//开启事务beginTransaction();//执行原来的方法joinPoint.proceed();//提交commit();} catch (Throwable throwable) {//回滚rollback();throwable.printStackTrace();} finally {//还原release();}}//开启事务public void beginTransaction() {//从当前线程上获取连接,实现开启事务try {connectionUtil.getThreadConnection().setAutoCommit(false);} catch (SQLException e) {e.printStackTrace();}}//提交事务public void commit() {try {connectionUtil.getThreadConnection().commit();} catch (SQLException e) {e.printStackTrace();}}//回滚事务public void rollback() {try {connectionUtil.getThreadConnection().rollback();} catch (SQLException e) {e.printStackTrace();}}//释放连接public void release() {try {connectionUtil.getThreadConnection().setAutoCommit(true);//关闭连接(还回池中)connectionUtil.getThreadConnection().close();//解绑线程:把连接和线程解绑connectionUtil.remove();} catch (SQLException e) {e.printStackTrace();}}
}

spring-day03-底层事务、AOP相关推荐

  1. Spring(四)——AOP、Spring实现AOP、Spring整合Mybatis、Spring中的事务管理

    文章目录 1. 什么是AOP 2. 使用Spring实现AOP 2.1 使用Spring的API 接口实现 2.2 自定义实现 2.3 使用注解实现 3. 整合MyBatis 3.1 MyBatis- ...

  2. spring tx:advice 和 aop:config 配置事务

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u010741376/article/details/46584463 spring tx:advic ...

  3. spring声明式事务管理方式( 基于tx和aop名字空间的xml配置+@Transactional注解)

    1. 声明式事务管理分类 声明式事务管理也有两种常用的方式, 一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解. 显然基于注解的方式更简单易用,更清爽. ...

  4. Spring ORM示例 - 带有AOP事务管理

    Spring ORM示例 - 带有AOP事务管理 这是一个非常简单的Spring ORM示例,向您展示如何使用Spring配置应用程序 依赖注入(@Autowired annotation), JPA ...

  5. Spring中的事务管理详解

    在这里主要介绍Spring对事务管理的一些理论知识,实战方面参考上一篇博文: http://www.cnblogs.com/longshiyVip/p/5061547.html 1. 事务简介: 事务 ...

  6. Spring配置文件详解三:Spring声明式事务管理

    1.声明式事务管理 Spring提供了声明式事务管理,这是通过Spring AOP实现的. 原理:Spring中进行事务管理的通常方式是利用AOP(面向切片编程)的方式,为普通java类封装事务控制, ...

  7. Spring 详解(五):Spring声明式事务

    事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性.本文主要讲解事务涉及到一些概念以及spring中事务的使用. 1. 事务 数据库事务(Database Transaction) ...

  8. Spring总结之事务

    Spring事务 1)定义 事务是指多个操作单元组成的集合,多个操作单元是整体不可分割的,要么都成功,要么都不成功.必须遵守四个原则(ACID) ●原子性(Atomicity):即事务是不可分割的最小 ...

  9. Spring JDBC-Spring对事务管理的支持

    概述 事务管理关键抽象 Spring事务管理的实现类 Spring JDBC 和MybBatis的事务管理器的配置 JPA的事务管理器的配置 Hibernate的事务管理器的配置 JTA 的事务管理器 ...

  10. Spring框架的事务管理及应用

    Spring框架简介 Spring框架是一个2003年2月才出现的开源项目,该开源项目起源自Rod Johnson在2002年末出版的<Expert One-on-One J2EE Design ...

最新文章

  1. 2018这一年或平淡或不凡,2019看更大的世界
  2. 用Delphi进行word开发
  3. matlab 实现 stacked Autoencoder 解决图像分类问题
  4. 前端学习(1293):系统模块path路径操作
  5. 【英语学习】【WOTD】minion 释义/词源/示例
  6. [C#]加密解密 MD5、AES
  7. SpringMVC第六篇【校验、统一处理异常】
  8. Unity Physics.Raycast踩坑
  9. bootsect.exe linux,bootsect.exe
  10. js 串口通信mscomm接收undefined_串口通信帧的同步方法(识别一帧数据的起始结束)42...
  11. hdu1428漫步校园( 最短路+BFS(优先队列)+记忆化搜索(DFS))
  12. 数字地球与计算机技术联系,数字地球与地球空间信息科学的关系
  13. oracle游标特点,oracle游标应用 sys_refcursor 和 cursor比较
  14. Virtual Studio 2010介绍及下载
  15. matlab figure函数怎么用,Matlab学习笔记 figure函数
  16. PixiJS学习(9)动画序列帧
  17. 触摸屏计算机技术参数,触摸屏显示器
  18. android 友盟统计功能,Android应用中添加友盟统计
  19. 智慧城市物联网主要技术路线
  20. 'ContactForm' object has no attribute 'cleaned_data'

热门文章

  1. linux进阶52——pthread_cond_t
  2. PostgreSQL空间回收利器——pg_repack
  3. 2021-2027全球与中国5G核心网市场现状及未来发展趋势
  4. JS 统计字符串中大小写字母个数
  5. 微信小程序导航栏怎么写
  6. 2013年上海市居住证新政策解读
  7. 并行查询的执行计划解读
  8. Check Point R81.10 - 下一代防火墙 (NGFW)
  9. 苹果照片未删却不见了_手机删除的照片如何恢复?不得不说这方法好!
  10. 北大清华联手开设通用人工智能实验班,「顶级AI科学家」朱松纯领衔