1.AOP概述
- 面向切面编程,在不修改源代码的情况下利用AOP对业务逻辑的各个部分进行隔离,达到低耦合性的效果
- Spring框架一般是基于aspectJ实现AOP操作,Aspectj也是框架,但一般把两者结合进行AOP操作
1.1切入点表达式
- 其作用是为了明确哪个类里面的哪个方法进行增强
- 语法结构:execution([权限修饰符][返回值类型][类全路径][方法名称][参数列表])
1 2 3 4
| excution(* com.aop.Book.add(..)) excution(* com.aop.Book.*(..)) excution(* com.aop.*.*(..))
|
1.2动态代理
- 创建一个接口定义一组方法及它的实现类
- 通过Proxy的newProxy方法获取代理类对象
- 定义一个类实现InvocationHandler接口
- 创建的是谁的对象,把谁传递进来,通过有参构造的方式
- 把需要增强的逻辑写在重写的invoke方法里
- 返回被增强的方法
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ClassLoader clr = 本类类名称.class.getClassLoader();
Class[] interfaces = {UserDao.class};
UserDao userDao = new UserDaoImpl();
UserDaoProxy udp = new UserDaoProxy(userDao);
UserDao result = (UserDao) Proxy.newProxyInstance(clr, interfaces, udp); System.out.println("result:"+result.add(1, 2));
|
代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class UserDaoProxy implements InvocationHandler{ private Object obj; public UserDaoProxy(Object obj){ this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args){ return method.invoke(obj, args); }
|
1.3操作术语
- 连接点:类中可以增强的方法
- 切入点:实际被增强的方法
- 通知(增强):实际增强的逻辑部分
- 分为:前置、后置、环绕、异常、最终通知
- 切面:通知应用到切入点的过程
2.AOP操作
2.1通知注解
- 有异常也执行(最终通知)
1
| @After(value = "切入点表达式")
|
- 有异常就不执行了,方法返回结果后执行(后置通知)
1
| @AfterReturning(value = "切入点表达式")
|
- 有异常时才执行(异常通知)
1
| @AfterThrowing(value = "切入点表达式)")
|
- 配合ProceedingJoinPoint方法(环绕通知)
1 2 3 4 5 6 7
| @Around(value = "切入点表达式") public void Around(ProceedingJoinPoint joinPoint){ System.out.println("环绕前通知"); joinPoint.proceed(); System.out.println("环绕后通知"); }
|
2.2基于注解
配置准备
- 创建xml文件引入名称空间
- 在xml中开启注解扫描和aspectJ生成代理对象
- 创建被增强类与增强类,添加@compment与@Aspect(仅增强类添加)
- 在增强类中创建方法,根据需求添加通知注解,并填入切入点表达式
- 使用junit进行测试
xml配置
1 2 3 4 5 6 7 8
| //xml文件配置 //1.引入名称空间 xmlns:context xmlns:aop //2.开启注解扫描 <context:component-scan base-package="类路径"/> //3.开启aspectJ生成代理对象 <aop:aspectj-autoproxy/>
|
实体类配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Compment public void User(){show方法}
@Compment @Aspect public void UserProxy{ @Before(value = "execution(* com.User.show(..))") public void before(){ System.out.println("前置通知"); } }
public class StudentProxy { @Before(value = "execution(* com.User.show(..))") @Order(0) public void show(){ System.out.println("我使用了order我优先"); } }
|
切入点抽取
- 当多个通知注解相同时可以使用切入点抽取注解
- 在通知注解value填入抽取注解的方法即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Pointcut(value ="execution(* com.User.show(..))") public void userPoint(){};
@After(value = "userPoint()") public void after(){ System.out.println("最终通知"); }
@AfterReturning(value = "userPoint()") public void AfterReturning(){ System.out.println("后置通知"); }
|
3.JDBCTemplete
- org.springframework.jdbc.core.JdbcTemplate类是JDBC核心包中的中心类
- 它简化了JDBC的使用,并有助于避免常见的错误
- 它执行核心JDBC工作流,留下应用程序代码来提供SQL并提取结果
3.1依赖准备
Druid线程池和JDBC的Maven依赖
1 2 3 4 5 6 7 8 9 10 11
| <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
|
xml配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 1.引入名称空间context 2.开启组件扫描 <context:component-scan base-package="包路径"/> 3.引入数据库连接 <bean id="druid" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/bank"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> 4.jdbcTemplate对象 <bean id="database" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="druid"/> </bean>
|
3.2创表语句
1 2 3 4 5 6 7
| CREATE TABLE `book_info` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(30) DEFAULT NULL, `money` DECIMAL(9,2) DEFAULT NULL, `password` VARCHAR(60) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8
|
3.3增加数据
DAO
1 2 3 4 5 6 7 8 9
| void addBook(Book book); DAOImpl类 @Override public void addBook(Book book) { String sql = "insert into book_info values(?,?,?,?)"; int add= jdbcTemplate.update(sql, book.getId(), book.getName(), book.getMoney(), book.getPassword()); System.out.println(sql); System.out.println("添加成功:影响行数"+add); }
|
Service类
1 2 3 4 5 6 7 8 9 10 11
| @Resource private BookDao bookDao; public void addBook(Book book){ bookDao.addBook(book);} 功能Junit测试 final Book book = new Book(); book.setId(8); book.setName("背包十年"); book.setMoney(39.5); book.setPassword("123456"); bs.addBook(book);
|
读取xml文件建立数据库连接(增删改查都需要)
1 2 3 4
| ApplicationContext context = new ClassPathXmlApplicationContext("druidConfig.xml"); final BookService bs = context.getBean("bookService", BookService.class);
|
3.4删除数据
DAO 根据id删除
1 2 3 4 5 6 7 8
| void delBook(String id); DaoImpl类 @Override public void delBook(String id) { String sql = "delete from book_info where id=?"; final int del = jdbcTemplate.update(sql, id); System.out.println("删除成功:影响行数"+del); }
|
Service类
1 2 3 4 5 6 7
| @Resource private BookDao bookDao; public void delBook(String id){ bookDao.delBook(id); } Junit测试 bs.delBook("8");
|
3.5修改数据
DAO
1 2 3 4 5 6 7 8
| void updateBook(Book book); DaoImpl类 @Override public void updateBook(Book book) { String sql = "update book_info set name=?,money=?,password=? where id =?"; final int update = jdbcTemplate.update(sql, book.getName(), book.getMoney(), book.getPassword(), book.getId()); System.out.println("修改成功:影响行数"+update); }
|
Service类
1 2 3 4 5 6 7 8 9 10 11 12
| @Resource private BookDao bookDao; public void updateBook(Book book){ bookDao.updateBook(book) } Junit测试 final Book book2 = new Book(); book2.setId(8); book2.setName("骆驼祥子"); book2.setMoney(19.5); book2.setPassword("123456"); bs.updateBook(book2);
|
3.6查询表记录数
DAO
1 2 3 4 5 6 7
| int queryCount();
@Override public int queryCount() { String sql = "select count(*) from book_info"; return jdbcTemplate.queryForObject(sql, Integer.class); }
|
Service
1 2 3 4 5 6
| public void queryCount(){ final int count = bookQuery.queryCount(); System.out.println("数据库共存在"+count+"条数据"); }
bs.queryCount();
|
3.7查询指定数据
BeanPropertyRowMapper
- 将数据库查询结果转换为Java类对象
- 常应用于使用Spring的JdbcTemplate查询数据库
- 获取List结果列表,数据库表字段和实体类自动对应
DAO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| Book findBookInfo(String id);
List<Book> findAll();
@Override public Book findBookInfo(String id) { String sql = "select * from book_info where id=?"; final Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id); return book; } @Override public List<Book> findAll() { String sql = "select * from book_info;"; final List<Book> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class)); return query; }
|
Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public Book findOne(String id){ return bookQuery.findBookInfo(id); } public List<Book> findAll(){ return bookQuery.findAll(); }
final Book one = bs.findOne("2"); System.out.println(one);
final List<Book> allBook = bs.findAll(); allBook.forEach(System.out::println);
|
4.事务操纵
- 事务的操纵一般写在Service层
- 事务的管理操纵分为编程式和声明式
- 声明式为常用方式其又分为注解方式和xml方式
- Spring中事务的底层使用AOP原理
4.1事务API
- 提供了一个接口,代表事务管理器
- 此接口针对不同框架类型提供了不同的实现类
- 针对jdbc使用DataSourceTransactionManager类
4.2基于注解
Bean文件
1 2 3 4 5 6 7 8 9 10
| //基本配置:名称空间tx,context,aop,druid数据库连接池 //1.创建事务管理器 <bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> // <property name="dataSource" ref="druid"/> </bean> //2.开启事务注解 <tx:annotation-driven transaction-manager="TransactionManager"/> //3.在service层使用@Trasactional注解开启事务 //写在类面或方法名上,写在方法名上代表开启此方法事务
|
事务java类
1 2 3
| @Service @Transactional public class UserService {}
|
4.3基于xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| //基本配置:名称空间tx,context,aop,druid数据库连接池 //1.创建事务管理器 <bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> //2.注入数据源 <property name="dataSource" ref="druid"/> </bean> //3.配置通知 <tx:adviceid="txadvice">
<tx:attributes>
<tx:methodname="transfer"propagation="REQUIRED"/> </tx:attributes> </tx:advice>
<aop:config>
<aop:pointcutid="cut"expression="execution(*affairs.service.UserService.*(..))"/>
<aop:advisoradvice-ref="txadvice"pointcut-ref="cut"/> </aop:config>
|
5.事务参数配置
在service添加@Transactional,所有配置都在此注解内
1 2
| @Transactional(timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
|
5.1传播行为
- Propagation即为事务的传播行为
- 当一个事务方法被另一个事务方法调用时候,这个事务方法如何进行。
5.2隔离级别
- 事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
- 有三个读问题:脏读、不可重复读、虚(幻)读
- 脏读:一个未提交事务读取到另一个未提交事务的数据
- 不可重复读:一个未提交事务读取到另一提交事务修改数据
- 虚读:一个未提交事务读取到另一提交事务添加数据
- 解决:通过设置事务隔离级别,解决读问题
5.3其他配置
Timeout
事务需要在一段时间内进行提交,如果不提交进行回滚
默认值为-1,设置单位为s
ReadOnly
默认为false,可以指定为true代表只读,不可做增删改查
RollbackFor 出现哪些异常回滚
NoRollbackFor 出现哪些异常不回滚
6.新特性
- Spring5框架基于JDK8
- 自带通用日志封装,官网建议使用log4j2
- @Nullable注解,可用在方法,属性或参数上,表示可为空
- 支持函数式风格GenericApplicationContext
- 整合Junit测试
6.1log4j2日志
- 导入log4j依赖
- 创建log4j2.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?xml version="1.0" encoding="UTF-8"?> <configuration status="INFO"> <appenders> <console name="Console" target="SYSTEM_OUT"> <patternlayout pattern="%d{yyyy-MM--dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </console> </appenders>
<loggers> <root level="info"> <appender-ref ref="Console"/> </root> </loggers> </configuration>
|
6.2函数式风格
- 创建一个java类
- 创建测试方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| GenericApplicationContext cont = new GenericApplicationContext();
cont.refresh();
cont.registerBean("ltd",Lambda.class, Lambda::new);
Lambda ltd = (Lambda) cont.getBean("ltd"); ltd.info();
|
@Nullable1 2
| void message(@Nullable String value){}
|
6.3Junit4整合
通过Junit整合配置Auntowired注解可以减少代码量
1 2 3 4 5 6 7 8 9 10 11
| @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:Bean1.xml") public class JTest4 { @Autowired private UserService us; @Test public void test1(){ us.transfer(); } }
|
6.4Junit5整合
1 2 3 4 5 6 7 8 9 10 11
|
@SpringJUnitConfig(locations = "classpath:Bean1.xml") public class JTest5 { @Autowired private UserService us; @Test public void test1(){ us.transfer(); } }
|