10、AOP
1、概念
十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。
让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
使用代理前:
后:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1opsMR0o-1666095427394)(D:\typora\java路线学习\SSM\图片\image-20221012202714165.png)]
即中间商,中介
2、静态代理
public class CalculatorStaticProxy implements Calculator {
private CalculatorImpl target;
public CalculatorStaticProxy(CalculatorImpl target) {
this.target = target;
}
@Override
public int add(int i, int j) {
System.out.println("日志,方法:add,参数:"+i+","+j);
int result = target.add(i, j);
System.out.println("日志,方法:add,结果:"+result);
return result;
}
}
静态代理实现了解耦,但是代码不灵活,就拿日志功能来 说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代 码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理 类来实现。这就需要使用动态代理技术了。
3、动态代理
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy(){
/**
* ClassLoader loader:指定加载动态生成的代理类的类加载器
* Class[] interfaces:获取目标对象实现的所有接口的class对象的数组
* InvocationHandler h:设置代理类中的抽象方法如何重写
*/
ClassLoader classLoader = this.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("日志,方法:"+method.getName()+",参数:"+ Arrays.toString(args));
//proxy表示代理对象,method表示要执行的方法,args表示要执行的方法到的参数列表
result = method.invoke(target, args);
System.out.println("日志,方法:"+method.getName()+",结果:"+ result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("日志,方法:"+method.getName()+",异常:"+ e);
} finally {
System.out.println("日志,方法:"+method.getName()+",方法执行完毕");
}
return result;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, h);
}
}
4、AOP概念及术语
1、概述
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面 向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况 下给程序动态统一添加额外功能的一种技术。
2、术语
①横切关注点
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方 法进行多个不同方面的增强。
②通知
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
- 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所 有位置(以上四个或这个选一个就行)
③切面
封装通知方法的类。
④目标
被代理的目标对象。
⑤代理
向目标对象应用通知之后创建的代理对象。
⑥连接点
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉 点就是连接点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CEB5lfAV-1666095427394)(D:\typora\java路线学习\SSM\图片\image-20221012203641745.png)]
⑦切入点
定位连接点的方式。
如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
Spring 的 AOP 技术可以通过切入点定位到特定的连接点。 切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条 件。
3、作用
- 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能, 提高内聚性。
- 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就 被切面给增强了
5、基于注解的AOP
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XlwACtya-1666095427395)(D:\typora\java路线学习\SSM\图片\image-20221012203950538.png)]
1、技术说明
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
- cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
- AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
2、配置
maven依赖
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
配置文件:
- 切面类和目标类都需要交给IOC容器管理
- 切面类必须通过@Aspect注解标识为一个切面
<!--
AOP的注意事项:
切面类和目标类都需要交给IOC容器管理
切面类必须通过@Aspect注解标识为一个切面
在spring的配置文件中设置<aop:aspectj-autoproxy/>标签,开启基于注解的AOP功能
-->
<!--扫描-->
<context:component-scan base-package="com.zylai.spring.aop.annotation"/>
<!--开启基于注解的AOP功能-->
<aop:aspectj-autoproxy/>
3、切面类
1、在切面中,需要通过指定的注解将方法标识为通知方法
@Before:前置通知,在目标方法执行之前执行
@After:后置通知,在目标对象方法的finally子句中执行
@AfterReturning:返回通知,在目标对象方法返回值之后执行
@AfterThrowing:异常通知,在目标对象方法的catch子句中执行
2、重用切入点表达式
①:设置在表示通知的注解的value属性中
* "execution(public int com.zylai.spring.aop.annotation.CalculatorImpl.add(int,int))"
* "execution(* com.zylai.spring.aop.annotation.*.*(..))"
* 第一个*表示任意的访问修饰符和返回值类型
* 第二个*表示包下所有的类
* 第三个*表示类中任意的方法
* ..表示任意的参数列表
②在同一个切面中使用
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
③在不同切面中使用
@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
3、获取通知的相关信息
①获取连接点信息
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
//获取连接点的签名信息
String methodName = joinPoint.getSignature().getName();
//获取目标方法到的实参信息
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
②获取目标方法的返回值
@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值
@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*
(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}
③获取目标方法的异常
@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常
@AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*
(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}
4、环绕通知
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
Object result = null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标方法的执行,目标方法的返回值一定要返回给外界调用者
result = joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
} finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
}
return result;
}
5、优先级
* 可以通过@Order注解的value属性设置优先级,默认值为Integer.MAX
* value值越小优先级越高
6、声名式事务
1、JdbcTemplate
1、准备工作
①加入依赖
②创建jdbc.properties
③配置spring的配置文件
<!--引入jdbc.properties,之后可以通过${key}的方式访问value-->
<!--这里的context标签,注意上面引入的-->
<!--在web项目中,有两种路径,一个是类路径,一个是web资源路径-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 配置数据源 -->
<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}"/>
<!--<property name="initialSize" value="${initialSize}"/>-->
<!--<property name="maxActive" value="${maxActive}"/>-->
</bean>
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 装配数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
2、测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JDBCTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;
}
@Test
//测试增删改功能
public void testUpdate(){
String sql = "insert into t_emp values(null,?,?,?)";
int result = jdbcTemplate.update(sql, "张三", 23, "男");
System.out.println(result);
}
(其他功能自行查找,感觉跟dbutils一模一样)
2、声名式事务概念
1、编程式事务
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
编程式的实现方式存在缺陷:
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复 用。
2、声名式事务
框架将固定模式的代码抽取出来,进行相关的封装。
封装起来的好处:
- 好处1:提高开发效率
- 好处2:消除了冗余的代码
- 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化
编程式:自己写代码实现功能
声明式:通过配置让框架实现功能
1、准备工作
①加入依赖
②创建jdbc.properties
③配置Spring的配置文件
④创建表
⑤创建组件
⑥开启事务的注解驱动
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"/>
</bean>
<!--
开启事务的注解驱动
将使用@Transactional注解所标识的方法或类中所有的方法使用事务进行管理
@Transactional注解在哪,连接点就在哪里
transaction-manager属性设置事务管理器的id
若事务管理器的id默认为transactionManager,则改属性可以不写
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
在需要进行事务操作的类或方法上加上注解即可
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
@Transactional
public void buyBook(Integer userId, Integer bookId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId,price);
}
}
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
@Transactional
public void buyBook(Integer userId, Integer bookId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId,price);
}
}