先上代码:项目源码下载(软件:IDEA):
基于XML的AOP配置
链接:https://pan.baidu.com/s/1wSbq5jYeykOxTUmjEvmz5A
提取码:9sox
基于注解的AOP配置
链接:https://pan.baidu.com/s/17QO6XzA5UGPHcW8JBrcHeg
提取码:tw1g
AOP相关术语
- Joinpoint(连接点)
横切程序执行的特定位置,比如类开始初始化前,类初始化之后,类中某个方法调用前、调用后,方法抛出异常后等,这些代码中的特定点就称为“连接点”。
Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。
我们知道黑客攻击系统需要找到突破口,没有突破口就无法进行攻击,从这一角度上来说,AOP是一个黑客(因为它要向目前类中嵌入额外的代码逻辑),连接点就是AOP向目标类打入楔子的候选点。
2.Pointcut(切入点)
一个类中可以有很多个方法,每个方法又有多个Joinpoint,在这么多个方法中,如何定位到自己感兴趣的方法呢?靠的是切点
注意:切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息
比如:如果把一个方法理解成数据表中的一条记录的话,那么切入点就好比你select语句的where条件 ,就可以定位到你感兴趣的方法
3.Advice(通知/增强)
增强的第一层意思就是你的横切逻辑代码(增强逻辑代码)
在Spring中,增强除了用于描述横切逻辑外,包含一层意思就是横切逻辑执行的方位信息。刚刚说了切点只能定位到方法,在进一步使用方位信息就可以定位到我们感兴趣的连接点了(方法调用前、方法调用后还是方法抛出异常时等)。
4.Target(目标对象)
增强逻辑的织入目标类。比如未添加任何事务控制的AccountServiceImplNoTcf类
5.Weaving(织入)
织入是将增强逻辑/横切逻辑添加到目标类具体连接点上的过程,AOP像一台织布机,将目标类、增强或者引介通过AOP(其实就是动态代理技术)这台织布机天衣无缝地编织到一起。
Spring采用动态代理织入。
6.Proxy(代理)
一个类被AOP织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。
7.Aspect(切面)
切面由切点和增强(引介)组成。
切面=切点+增强
=切点+方位信息+横切逻辑
=连接点+横切逻辑
最终切面完成:把横切逻辑织入到哪些方法的方法前/后等
本质:把横切逻辑增强到连接点(切点和方位信息都是为了确定连接点)上
Spring关于JDK/CGLIB动态代理的选择
Spring发现涉及到接口那就使用JDK动态代理,如果不涉及接口就使用CGLIB动态代理
AOP:日志、性能监控、事务、权限控制
基于XML的AOP配置
需求:在Service层代码的不同方法的不同连接点JoinPoint织入日志
把Account表的service层进行crud模拟(dao层就不需要了)
引入POM坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
applicationContext.xml配置
<aop:config> 所有aop配置的根标签
<aop:aspect id="logAspect" ref="log"> 配置切面对象,id自定义,ref横切逻辑类的bean标签id值
<aop:pointcut> 配置切入点
属性值:id自定义
属性值:expression 匹配方法的表达式
<aop:before> 被切入的方法前执行
属性method:printBeforeMethod切入的方法名
属性pointcut-ref:切入点标签的id属性值
<?xml version="1.0" encoding="UTF-8"?>
<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/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="com.itheima"></context:component-scan>
<!-- 横切逻辑配置-->
<bean id="log" class="com.itheima.utils.LogUtil">
</bean>
<!-- 配置aop的根标签-->
<aop:config>
<!-- 切面配置-->
<aop:aspect id="logAspect" ref="log">
<!--
配置切入点
before:方法执行之前
method:切入的方法名
pointcut:感兴趣的方法,被切入的方法
-->
<!--<aop:before method="printBeforeMethod"
pointcut="execution( int com.itheima.service.AccountService.saveAccount(com.itheima.pojo.Account))"></aop:before>-->
<!--
表达式配置
方法参数:* 匹配任意参数,必须有参数
.. 匹配任意参数,有无参数均可
方法名:*
返回值:*
-->
<aop:pointcut id="point1" expression="execution( * com.itheima.service.AccountService.*(..))"></aop:pointcut>
<aop:before method="printBeforeMethod" pointcut-ref="point1"></aop:before>
<aop:after-returning method="pringAfterReturn" pointcut-ref="point1"></aop:after-returning>
<aop:after-throwing method="printAfterThrowing" pointcut-ref="point1"></aop:after-throwing>
<aop:after method="printAfterMethod" pointcut-ref="point1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
日志对象:横切逻辑
public class LogUtil {
//方法执行之前
public void printBeforeMethod(){
System.out.println("方法之前执行");
}
//方法执行之后打印
public void printAfterMethod(){
System.out.println("方法执行之后");
}
//方法异常执行打印
public void printAfterThrowing(){
System.out.println("方法异常时");
}
//方法正常执行打印
public void pringAfterReturn(){
System.out.println("方法正常时");
}
}
service层
@Service("accountServiceImpl")
public class AccountServiceImpl implements AccountService {
public int saveAccount(Account account) {
System.out.println("模拟保存账户");
return 0;
}
@Override
public int updateAccountById(Account account) {
System.out.println("模拟更新账户");
return 0;
}
@Override
public int deleteAccountById(int id) {
System.out.println("模拟删除账户");
return 0;
}
@Override
public Account queryAccountById(int id) {
System.out.println("模拟查询账户");
return null;
}
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MainTest {
@Autowired
@Qualifier("accountServiceImpl")
private AccountService accountService;
@Test
public void testAop(){
accountService.saveAccount(new Account());
// accountService.queryAccountById(1);
}
}
环绕通知
它是spring为我们提供的一种可以在代码中手动控制增强方法何时执行的方式,灵活度比较高,设置可以控制原业务逻辑是否执行。
注意:通常情况下,环绕通知都是独立使用的,不要和上面的四种通知类型混合使用
<aop:around method="printRound" pointcut-ref="point1"></aop:around> 环绕通知配置
ProceedingJoinPoint进程切入点对象,执行我们的业务逻辑方法
方法:proceed() 执行我们自己的业务逻辑方法
applicationContext.xml配置
<aop:around method="printRound" pointcut-ref="point1"></aop:around>
日志对象:横切逻辑
//环绕通知
public Object printRound(ProceedingJoinPoint proceedingJoinPoint){
Object result = null;
try{
System.out.println("方法前执行");
result = proceedingJoinPoint.proceed();
System.out.println("方法正常执行");
}catch (Throwable ex){
ex.printStackTrace();
System.out.println("方法异常执行");
}
System.out.println("方法后执行");
return result;
}
基于注解的AOP配置
创建框架启动配置类
@Component
@ComponentScan("com.itheima")
//启用动态代理
@EnableAspectJAutoProxy
public class SpringConfig {
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class MainTest {
@Autowired
@Qualifier("accountServiceImpl")
private AccountService accountService;
@Test
public void testAop(){
accountService.saveAccount( new Account());
}
}
学习spring中的AOP时要明确的事
- AOP的使用场景
AOP的应用场景往往是受限的,它一般只适合于那些具有横切逻辑的应用场合:如性能监测、访问控制、事务管理以及日志记录等。不过,这丝毫不影响AOP作为一种新的软件开发思想在软件开发领域所占有的地位。
2.开发阶段(我们完成)
编写核心业务代码
大部分程序员来做,要求熟悉业务需求。
抽取公用代码制作成通知,进行AOP配置
一般由专门的AOP编程人员来做
3.运行阶段(Spring框架完成)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,Spring框架使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。