aop前奏
AOP前奏(动态代理)
实现计算器业务功能
- 需求,在实现加减乘除核心业务同时,添加日志及数据校验功能。
- 问题
1核心业务代码,与非核心业务代码(日志代码)耦合度太高。
2代码相对比较分散,也比较混乱。 - 解决
1提取非核心业务代码(日志代码)
2将非核心业务,动态作用(织入)核心业务中。(动态代理实现) - 使用动态代理,(实现将非核心业务,动态织入核心业务中)
- 动态代理的实现方式
1基于接口实现动态代理(JDK)
2基于(继承)类实现动态代理(Cglib\Javassist) - 基于接口实现动态代理的步骤(JDK)
1定义目标对象
2定义获取代理对象方法
3有参构造器() - 补充:兄弟关系,不能相互转换。(Dog不能转换Cat)
- 总结,问:代理对象是否可以强制转换为实现类对象?
答:不可以(com.sun.proxy.$Proxy0 cannot be cast to com.atguigu.spring.aop.CalcImpl)
因为:代理对象与实现类对象,是兄弟关系。
AOP&AspectJ
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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.atguigu.spring.aopimpl"/>
<aop:aspectj-autoproxy/>
</beans>
AOP
概述
- Aspect-Oriented Programming,面向切面编程,
- 面向切面的编程设计,可以理解为是面向对象编程设计的一种补充。
OOP:面向对象编程,纵向(实现接口|继承类)关系
AOP:面向切面编程,横向(提取-非核心业务)关系
术语
- 横切关注点:非核心业务(日志功能)
- 切面:非核心业务提取类中,当前类称之为:切面。(MyLogging)
- 通知:将非核心业务,提取到切面后,横切关注点更名为:通知
- 目标:需要被代理的类,称之为目标类(CalcImpl)
- 代理:自定义的CalcImplProxy类称之为代理类,
通过该类中的方法【getProxyObject()】获取的对象,称之为代理对象。 - 连接点:需要被通知的位置(需要将非核心业务作用回核心业务中的位置)
- 切入点:被通知后的连接点,更名为:切入点。
应用(AspectJ)
- AspectJ简介
- 是java社区目前最主流的AOP框架
- 不是spring体系内的框架,但spring2.0后对AspectJ做了支持
- Spring开启AspectJ步骤
- 导入jar包
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar - 在核心配置文件中,添加:
<aop:aspectj-autoproxy>
- 一旦spring容器识别到该标签,就会为指定的切面(@Aspect)动态生成代理对象。
- 为java类添加注解:@Aspect,此时该类称之为切面。
- 实现通知(5种)
- 前置通知
- 在切入点表达式指定的方法之前,执行。(如果有异常,依然会执行)
@Before(value="execution(public double com.atguigu.spring.aopimpl.CalcImpl.add(double,double))")
public void methodBefore(JoinPoint jp) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("==>日志:" + methodName + "()之前,参数args:" + Arrays.toString(args));
}
- 后置通知
- 在切入点表达式指定的方法之后,执行。(如果有异常,依然会执行)
@After(value="execution(* com.atguigu.spring.aopimpl.*.*(..))")
public void methodAfter(JoinPoint jp){
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("==>日志:" + methodName + "()之后,参数args:" + Arrays.toString(args));
}
- 返回通知
- 在切入点表达式指定的方法返回结果,执行。(如果有异常,则不执行)
@AfterReturning(value="puintCut()", returning = "data")
public void methodAfterReturning(JoinPoint jp, Object data){
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("==>日志:" + methodName + "()之后,结果:" + data);
}
- 注意:两个data必须一致。
- 异常通知
- 在切入点表达式指定的方法出现异常,执行。(如果有异常,则执行,无异常,不执行)
@AfterThrowing(value="puintCut()", throwing = "ex")
public void methodAfterThrowing(JoinPoint jp, Exception ex){
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("==>日志:" + methodName + "()之后,结果:" + ex);
}
- 注意:两个ex必须一致。
- 环绕通知
@Around(value="puintCut()")
public Object methodAround(ProceedingJoinPoint pjp){
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
Object proceed = null;
try {
System.out.println("==>日志:" + methodName + "()之前,参数args:" + Arrays.toString(args));
proceed = pjp.proceed();
System.out.println("==>日志:" + methodName + "()之后,结果:" + proceed);
} catch (Throwable throwable) {
System.out.println("==>日志:" + methodName + "()之后,结果:" + throwable);
throwable.printStackTrace();
}finally {
System.out.println("==>日志:" + methodName + "()之后,参数args:" + Arrays.toString(args));
}
return proceed;
}
- 环绕通知必须使用JoinPoint子接口:ProceedingJoinPoint,因为需要我们
使用pjp.proceed(),手动调用代理对象的目标方法,
- 环绕通知,必须将目标方法的结果,返回(必须有返回值类型)
切入点表达式
- 语法: execution(public double com.atguigu.spring.aopimpl.CalcImpl.add(double,double))
- " * "的用法
" * "可以代表,任意的访问修饰符及返回值类型
" * "可以代表,任意类的全类名
" * "可以代表,任意方法名称 - "…"的用法
"…"在方法的参数中使用,表示任意数据类型,任意数量的参数列表。 - 重(重复)用切入点表达式
- 提取相同的切入点表达式,如下:
@Pointcut(value="execution(* com.atguigu.spring.aopimpl.*.*(..))")
public void puintCut(){}
- 在指定的通知内,引入提取后的切入点表达式
@Before(value="myPointCut()")
切面的优先级
- 语法:@Order(int num)
num 数值越小,优先级越高。
num默认值为int类型的最大值。
基于XML配置AspectJ的applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="calcImpl" class="com.atguigu.spring.aopxmlimpl.CalcImpl"></bean>
<bean id="myLogging" class="com.atguigu.spring.aopxmlimpl.MyLogging"></bean>
<bean id="myValidata" class="com.atguigu.spring.aopxmlimpl.MyValidata"></bean>
<aop:config>
<aop:pointcut id="myPointCut" expression="execution(* com.atguigu.spring.aopxmlimpl.*.*(..))"/>
<aop:aspect id="myLoggingAspect" ref="myLogging" order="2">
<aop:before method="methodBefore" pointcut-ref="myPointCut"></aop:before>
<aop:after method="methodAfter" pointcut-ref="myPointCut"></aop:after>
<aop:after-returning method="methodAfterReturning" pointcut-ref="myPointCut" returning="data"></aop:after-returning>
<aop:after-throwing method="methodAfterThrowing" pointcut-ref="myPointCut" throwing="ex"></aop:after-throwing>
</aop:aspect>
<aop:aspect id="myValidataAspect" ref="myValidata" order="1">
<aop:before method="methodBeforeValidata" pointcut-ref="myPointCut"></aop:before>
</aop:aspect>
</aop:config>
</beans>