一、AOP概念
AOP(Aspect-Oriented Programming: 面向切面编程):将那些与业务无关,却为业务模块所共同调用的逻辑(例如事务处理、日志管理、权限控制等)封装抽取成一个可重用的模块,这个模块被命名为“切面”(Aspect),便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 基于动态代理实现:
- 如果被代理的对象,已经实现某个接口,则 Spring AOP 会使用 JDK Proxy(反射),基于接口的方式,创建代理对象(JDK动态代理的核心是InvocationHandler接口和Proxy类);
- 如果被代理的对象,没有实现某个接口,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib,基于继承的方式,生成一个被代理对象的子类来作为代理(Cglib动态代理的核心是MethodInterceptor接口和Enhancer类);
二、AOP通知类型与相关名词
AOP将抽取出来的共性功能称为通知。
通知类型:以通知在上下文中的具体位置作为划分
- 前置通知(Before)
- 后置通知(After)
- 返回通知(After-returning)
- 异常通知(After-throwing)
- 环绕通知(Around)
名词:
- AOP连接点(Join point):AOP将所有的方法都视为连接点,不管是接口里面的抽象方法,还是实现类里面的重写方法,都是连接点。
- AOP切点(Pointcut):AOP将可能被抽取共性功能的方法称为切入点。切入点是连接点的子集。
- AOP目标对象(Target): 就是挖掉功能的方法对应的类生的对象,这种对象是无法直接完成最终工作的。
- AOP织入(Weaving):就是将挖掉的功能回填的动态过程。
- AOP切面:切点+通知
三、切点表达式配置语法
execution(修饰符 返回值 包名称.类名称.方法名称(参数列表))
eg:
execution(public void com.apesource.service.ServiceImp.findAll())
1. 修饰符可以省略代表任意
execution(返回值 包名称.类名称.方法名称(参数列表))
2. 返回值可以使用“*”代表任意
execution(* 包名称.类名称.方法名称(参数列表))
3. 包名可以使用“*”代表任意名称
execution(* *.*.*.类名称.方法名称(参数列表))
4. 包名可以使用“..”代表任意个数
execution(* *...类名称.方法名称(参数列表))
5. 类名与方法名可以使用“*”代表任意
execution(* *...*.*(参数列表))
6. 参数列表可以使用".."代表任意个数任意类型
execution(* *...*.*(..))
如果有参数
int======>int
String===>java.lang.String
四、xml使用AOP
4.1 日志
AOP工具类:
public class AopUtil {
//前置通知
public void beforeMethod(){
System.out.println("前置通知=====>"+new Date());
}
//返回通知
public void afterRrturnMethod(){
System.out.println("返回通知=====>"+new Date());
}
//异常通知
public void throwMethod(){
System.out.println("异常通知=====>"+new Date());
}
//后置通知
public void afterMethod(){
System.out.println("后置通知=====>"+new Date());
}
//环绕通知
public Object arroundMethod(ProceedingJoinPoint point){
Object returnObj = null;//执行切点方法
try {
System.out.println("环绕通知====>前置通知");
//切点
Object[] obj = point.getArgs();//切点方法的参数
returnObj = point.proceed(obj);
System.out.println("环绕通知====>返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知====>异常通知");
} finally {
System.out.println("环绕通知====>后置通知");
}
return returnObj;//切点方法的返回值
}
}
xml中的aop设置:
<aop:config>
<!-- 切面-->
<aop:aspect id="main" ref="aopUtil">
<!-- 切点-->
<aop:pointcut id="dian" expression="execution(* com.service.ServiceImp.*(..))"/>
<!-- 前置通知-->
<aop:before method="beforeMethod" pointcut-ref="dian"></aop:before>
<!--前置通知-->
<aop:before method="beforeMethod" pointcut-ref="dian"></aop:before>
<!--返回通知-->
<aop:after-returning method="afterRrturnMethod" pointcut-ref="dian"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="throwMethod" pointcut-ref="dian"></aop:after-throwing>
<!--后置通知-->
<aop:after method="afterMethod" pointcut-ref="dian"></aop:after>
<!-- 环绕通知 -->
<aop:around method="arroundMethod" pointcut-ref="dian"/>
</aop:aspect>
</aop:config>
4.2 事务管理
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/my_bank?serverTimezone=GMT"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice transaction-manager="transactionManager" id="transactionInterceptor">
<tx:attributes>
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="dian" expression="execution(* com.service.AcServiceImp.*(..))"/>
<aop:advisor advice-ref="transactionInterceptor" pointcut-ref="dian"></aop:advisor>
</aop:config>
五、注解AOP
5.1 日志
AOP工具类:
@Component
@Aspect
public class AopUtil {
//切点
@Pointcut(value = "execution(* com.service.ServiceImp.*(..))")
public void dian(){}
//前置通知
@Before("dian()")
public void beforeMethod(){
System.out.println("前置通知=====>"+new Date());
}
//返回通知
@AfterReturning("dian()")
public void afterRrturnMethod(){
System.out.println("返回通知=====>"+new Date());
}
//异常通知
@AfterThrowing("dian()")
public void throwMethod(){
System.out.println("异常通知=====>"+new Date());
}
//后置通知
@After("dian()")
public void afterMethod(){
System.out.println("后置通知=====>"+new Date());
}
@Around("dian()")
public Object aroundMethod(ProceedingJoinPoint point){
Object obj = null;
try {
System.out.println("前置通知");
Object[] args = point.getArgs();//获得切点参数
obj = point.proceed(args);//执行切点方法
System.out.println("返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常通知");
} finally {
System.out.println("后置通知");
}
return obj;
}
}
xml中的aop设置:
<context:component-scan base-package="com"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
5.2 事务管理
@Transactional(readOnly=false,propagation= Propagation.SUPPORTS,isolation = Isolation.DEFAULT)
<!-- 配置一个事务管理器 -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入DataSource -->
<property name="dataSource" ref="data"/>
</bean>
<!-- 开启spring对注解事务的支持 -->
<tx:annotation-driven transaction-manager="tm"/>
六、总结AOP开发过程
●开发阶段(开发者完成)
- 正常的制作程序
- 将非共性功能开发到对应的目标对象类中,并制作成切入点方法
- 将共性功能独立开发出来,制作成“通知”
- 在配置文件中,声明“切入点”
- 在配置文件中,声明"切入点"与"通知"间的关系(含通知类型),即"切面"
●运行阶段(AOP完成)
- Spring容器加载配置文件,监控所有配置的“切入点”方法的执行
- 当监控到“切入点”方法被运行,使用“代理”机制,动态创建“目标对象”的“代理对象”,根据“通知类别”,在“代理对象”的对应位置将“通知”对应的功能“织入”,完成完整的代码逻辑并运行