3. AOP
3.1 入门
- What is AOP
面向切面编程(Aspect Oriented Programming)
AOP是OOP的延续。它可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。
人话:不通过修改源代码方式,在主干功能里添加新的功能。
例如:一个登录功能——
原始基本功能:输入用户名密码--查询数据库--判断--成功则跳转页面,失败则返回再次输入。
现在我们希望在此之上添加新的功能,权限管理。如果按照原始方式,我们需要对源代码进行修改来实现。而AOP的作用就在这里,它可以将这个新功能做成一个独立的模块,需要的话就加进去生效,不需要的话也可以完全剔除而不影响原始功能。
3.2 底层原理
-
AOP底层使用动态代理
- 动态代理有两种情况。
case1,有接口情况,使用JDK动态代理(spring已经封装好)
创建接口实现类的代理对象,增强类的方法(增加功能)。
case2,无接口情况,使用CGLIB动态代理
创建当前类子类的代理对象。
3.3 JDK动态代理
3.3.1 简述
使用Proxy类里面的方法创建代理对象
java.lang.reflect里有一个类
Class Proxy
java.lang.reflect.Proxy
类里的方法:newProxyInstance
三个参数:
- ClassLoader,类加载器
- Interface,增强方法所在的类,这个类实现的接口。支持多个接口。
- InvocationHandler,实现这个接口,创建代理对象,写增强的方法。
3.3.2 代码
-
创建接口,定义方法
-
创建类,实现方法
-
使用Proxy类创建接口的代理对象(增强过程)
注意,这里是底层实现原理。在spring中我们已经有封装好的可以用。
// JDKProxy.java
package com.phsite;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class JDKProxy {
public static void main(String[] args) {
// 创建[接口实现类]代理对象
Class[] interfaces = {UserDao.class};
// Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// return null;
// }
// });
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao =
(UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1, 2);
System.out.println("result:" + result);
}
}
// 创建代理对象代码
class UserDaoProxy implements InvocationHandler {
// 1. 创建的是谁的代理对象,就把谁传递过来。
// 有参数构造传递
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
// 增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法之前做处理
System.out.println("在方法之前执行。。。" + method.getName() + " :传递的参数。。" + Arrays.toString(args));
// 被增强的方法执行
Object res = method.invoke(obj, args);
// 方法之后被处理
System.out.println("方法之后执行" + obj);
// 如何判断增强什么方法?对不同的方法add和update做不同的处理。
// 细节:注意method.getName可以得到方法名。可以依此进行判断。
return res;
}
}
// userDaoImpl.java
package com.phsite;
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
System.out.println("add方法执行了");
return a + b;
}
@Override
public String update(String id) {
System.out.println("update方法执行了");
return id;
}
}
3.4 AOP术语
class User {
add();
update();
select();
delete();
}
-
连接点
- 类里面哪些方法可以被增强,那么这些方法就成为连接点。
-
切入点
- 实际被真正增强的方法,就成为切入点。
-
通知(增强)
- 实际增强的逻辑部分,称为通知(增强)。比如权限管理的判断。
- 通知有多种类型。(执行的时机位置)
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
-
切面
- 是动作。把通知应用到切入点的过程。
3.5 AOP操作
准备工作:
- Spring框架中,一般基于基于AspectJ来实现AOP操作。
what is AspectJ?
- 它并不是Spring组成部分,是一个独立的AOP框架。一般把AspectJ和Spring框架一起使用,进行AOP操作。
-
基于AspectJ实现AOP操作
- 基于XML配置文件实现
- 基于注解方式实现(一般用这种)
-
在项目工程引入AOP相关依赖
spring-aop
spring-aspects等包。
去maven仓库里找,然后把dependency复制过来,用maven下载
- 切入点表达式
-
切入点表达式作用:知道对哪个类里面的哪个方法进行增强
-
语法结构:
execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
例子1. 对com.phsite.dao.BookDao里面的add进行增强
execution(* com.phsite.dao.BookDao.add(..))
*表示所有,返回类型可以不写
例子2. 对应~里所有方法进行增强
execution(* com.phsite.dao.BookDao.*(..))
例子3. 对com.phsite.dao包里所有类的所有方法进行增强。
execution(* com.phsite.dao.*.*(..))
注:这里参数列表的两个点,意味着它的参数???
我们要做的,就是配置上面提到的五种通知类型。
3.5.1 AspectJ注解
-
创建类,在类里面定义方法
-
创建增强类(编写增强逻辑)
- 在增强类里创建方法,让不同的方法代表不同的通知类型
-
进行通知的配置
- 在spring配置文件中,开启注解扫描
- 使用注解创建User和UserProxy对象
- 在增强类上面添加注解@Aspect
- 在spring配置文件中开启生成代理对象
-
配置不同类型的通知
在增强类的里面,在座位通知方法上面添加通知类型注解,使用切入点表达式配置
// 增强的类
@Component
@Aspect
public class UserProxy {
// 前置通知
// Before注解表示作为前置通知
@Before(value = "execution(* com.phsite.aopanno.User.add(..))")
public void before() {
System.out.println("before");
}
}
问题:proceedingJoinPoint找不到。明明我已经导入了相关依赖包,版本也和视频里的一样。
- 相同的切入点抽取后版本
// 增强的类
@Component
@Aspect
public class UserProxy {
// 相同切入点抽取
@Pointcut(value = "execution(* com.phsite.aopanno.User.add(..))")
public void pointDemo() {
}
// 前置通知
// Before注解表示作为前置通知
@Before(value = "pointDemo()")
public void before() {
System.out.println("before");
}
@After(value = "pointDemo()")
public void after() {
System.out.println("after");
}
// 异常通知
@AfterThrowing(value = "pointDemo()")
public void afterThrowing() {
System.out.println("afterThrowing");
}
@AfterReturning(value = "pointDemo()")
public void afterReturn() {
System.out.println("afterreturing");
}
// @Around(value = "pointDemo()")
// public void around() {
// System.out.println("环绕之前");
// // 被增强的方法执行
// proceedingJoinPoint.proceed();
// System.out.println("环绕之后");
// }
}
- 有多个增强类对同一个方法进行增强,设置增强类优先级
在增强类上面添加注解@Order(数值),数字类型值越小,优先级越高。
3.5.2 AspectJ配置文件方式
(仅作了解)
4. JDBCTemplate
4.1 入门
- what is JdbcTemplate?
Spring框架对JDBC进行了封装,使用JDBCTemplate可以很方便地对数据库操作。
- 准备工作
- 引入相关jar包。
spring-jdbc
spring-orm
spring-tx
mysql-connector-java
druid
-
在spring配置文件中配置连接池
-
配置JdbcTemplate对象,注入DataSource
-
创建service类,创建dao类,在dao注入jdbcTemplate对象
// service里注入dao
// dao里注入jdbcTemplate
@Service
public class BookService {
// 注入Dao
@Autowired
private BookDao bookDao;
}
@Repository
public class BookDaoImpl implements BookDao {
// 注入jdbctemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
<!--<!– 开启注解扫描–>-->
<context:component-scan base-package="com.phsite"></context:component-scan>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- 创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 需要注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 操作数据库
创建实体类
编写service和dao
在dao进行数据库添加操作
调用jdbcTemplate的update方法,实现添加操作
两个参数:
- 第一个参数:sql语句
- 第二个参数:可变参数(sql语句中的值)
@Repository
public class BookDaoImpl implements BookDao {
// 注入jdbctemplate
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void add(Book book) {
// 创建sql
String sql = "insert into t_book values(?,?,?)";
// 数组形式的可变参数。
Object[] args = {book.getBookId(), book.getBookname(), book.getBookstatus()};
int update =jdbcTemplate.update(sql, args);
System.out.println(update);
}
}
public class TestBook {
@Test
public void testJdbcTemplate() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
// 为了临时测试,这里book暂时还用老办法new
// Book book = new Book();
// book.setBookId("1");
// book.setBookname("java");
// book.setBookstatus("a");
// 修改操作
// Book book = new Book();
// book.setBookId("1");
// book.setBookname("javaupup");
// book.setBookstatus("atph");
// 删除
bookService.deleteBook("1");
}
}
查询的方法
jdbcTemplate.queryForObject(sql, class)
返回值。
jdbcTemplate.queryForObject(sql, RowMapper, ...args);
返回对象
第二个参数:RowMapper,是接口,针对你返回的不同类型数据,使用这个接口里的实现类可以完成数据的封装,封装成java里对象实例。
jdbcTemplate.query(sql, RowMapper, ..args);
返回集合。
批量操作:
操作表里的多条记录。
- 实现批量添加
- 实现批量修改
- 实现批量删除
可以体会jdbc的语法糖。
考虑之后mybatis的使用,这里使用原生sql的写法可以看phSpringStart5项目。
5. 事务
什么是事务?
事务是数据库操作最基本单元,是逻辑上的一组操作。要么都成功,如果有一个失败则所有操作都失败。
事务四个特性ACID
原子性、一致性、隔离性、持久性
5.1 Spring事务管理简介
-
事务一般添加到JavaEE三层结构里面的Service业务逻辑层。
-
Spring进行事务管理两种方式:
- 编程式事务管理
- 声明式事务管理
// 转账方法
public void accountMoney() {
try {
// 1. 开启事务
// 2. 进行业务操作
// lucy少100
userDao.reduceMoney();
// 模拟异常,出了异常,lucy少了但mary没多。
int i = 10 / 0;
// mary多100
userDao.addMoney();
// 3. 没有异常,提交事务
} catch (Exception e) {
// 4. 出现异常,事务回滚
}
}
这个就是编程式。不方便。
一般用声明式事务管理。
- 声明式事务管理两种方式:
- 基于注解方式
- 基于xml配置文件方式
-
在Spring进行声明式事务管理,底层使用AOP
-
Spring事务管理API
一个接口,代表事务管理器,该接口针对不同的框架提供了不同的实现类。(这里的不同框架:jdbcTemplate、Hibernate、myBatis)
5.2 声明式事务管理(注解)
- 配置事务管理器
<!-- 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 开启事务注解
-
引入名称空间tx
-
开启事务注解
xmlns:aop="http://www.springframework.org/schema/aop"
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 在Service类上面添加事务注解
@Transactional,可以加类上面,也可以加方法上面。如果加在类上面,就是类里所有方法都添加事务。如果加在方法上面,则仅为这个方法添加事务。
5.3 参数配置
- propagation:事务传播行为
多事务方法之间进行调用,这个过程中事务是如何进行管理的。
事务方法:对数据库表数据进行变化的操作。
Spring框架事务传播行为有7种。
REQUIRED:如果有事务在运行,该方法就在事务内运行;否则就启动一个新的事务,并在自己的事务内运行。
REQUIRED_NEW:该方法必须启动新事务,并在自己的事务内运行。如果有事务正在运行则将其挂起。
……
- isolation:事务隔离级别
事务有特性称为隔离性,多事务操作之间不会产生影响。
三个读问题:脏读、不可重复读、虚(幻)读
- 脏读:一个未提交的事务读取到另一个未提交事务的数据。
- 不可重复读:一个未提交事务读取到另一提交事务修改数据。
- 虚读:一个未提交的事务读到另一个提交事务添加数据。
通过设置事务隔离级别,解决读问题
4个:
| 脏读 | 不可重复读 | 幻读 | |
|---|---|---|---|
| READ UNCOMMITED读未提交 | 1 | 1 | 1 |
| READ COMMITED读已提交 | 0 | 1 | 1 |
| REPEATABLE READ可重复读 | 0 | 0 | 1 |
| SERIALIZABLE串行化 | 0 | 0 | 0 |
mysql默认是可重复读。
- timeout:超时时间
事务需要在一定时长内进行提交,如果不提交就回滚。
默认值是-1(表示无超时限制),设置数字单位是秒。
- readOnly: 是否只读
读:查询
写:添加修改删除
默认值false,表示可以查询,也可以添加修改删除
设置为true的话,只能查询。
- rollbackFor: 回滚
设置出现哪些异常进行事务回滚
- noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
5.4 完全注解声明式
@Configuration
@ComponentScan(basePackages = "com.phsite")
@EnableTransactionManagement
public class TxConfig {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://49.235.49.250:3306/user_db");
dataSource.setUsername("phuser");
dataSource.setPassword("Ph@092700");
return dataSource;
}
// jdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
// 到ioc容器种根据类型找到dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入datasource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 事务管理器对象
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}