代理模式:在不修改源代码的基础上新增业务逻辑
静态代理:代理一个对象
动态代理:代理多个对象
jdk自带的动态代理:代理和被代理对象必须实现相同的接口
cglib实现的动态代理:可以代理没有接口的类
AOP 简介
AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充.
AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点.
在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里.
AOP 的好处:每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级业务模块更简洁, 只包含核心业务代码.
AOP术语
切面对象(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(切面对象里面的具体方法)(Advice): 切面必须要完成的工作
目标(Target): 被通知的对象
代理对象(Proxy): 向目标对象应用通知之后创建的对象
连接点(Joinpoint): 程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
切点(pointcut): 每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
AOP开发步骤
1.导包
spring核心 + springAOP模块
2.开启AOP注解开发功能
<!-- 开启aop注解开发功能 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3.编写切面对象
@Component
@Aspect
public class MyAspect {
@Before("execution(* com.zl.service..*.*(..))")
/*
* 前置通知:在方法执行之前执行
*/
public void before(JoinPoint jp) {
System.out.println("前置通知"+jp.getSignature().getName()+":方法前执行的业务逻辑,执行时间为:"+new Date());
}
4.切点表达式
execution(* com.zl.dao..*.*(..))
第一个*:代表任意修饰符任意返回值
第二个*:代表任意类
第三个*:任意方法
第一个…:代表包以及其子包
第二个…:任意参数
5.通知类型
@Before("execution(* com.zl.service..*.*(..))")
/*
* 前置通知:在方法执行之前执行
*/
public void before(JoinPoint jp) {
System.out.println("前置通知"+jp.getSignature().getName()+":方法前执行的业务逻辑,执行时间为:"+new Date());
}
/*
* 后置通知(最终通知):
* 1. 不管方法有没有正确执行,后置通知都会执行(类似于finally)
*/
@After("execution(* com.zl.service..*.*(..))")
public void after(JoinPoint jp) {
System.out.println("后置通知"+jp.getSignature().getName()+":方法执行完成,执行完成的时间为:"+new Date());
}
/*
* 返回通知:
* 1. 只有方法正确执行完成以后才会执行返回通知
* 2. 可以在通知中拿到方法的返回值,如果方法没有返回值那么在通知中获取的值为null,可以通过获取的返回值进行先一步操作
*/
@AfterReturning(pointcut="execution(* com.zl.service..*.*(..))",returning="obj")
public void afterRrturn(JoinPoint jp,Object obj) {
if(obj!=null) {
int flag=(int)obj;
if(flag>0) {
System.out.println("修改成功...");
}else {
System.out.println("修改失败..。");
}
}
System.out.println("返回通知"+jp.getSignature().getName()+":方法执行完成,执行完成的时间为:"+new Date());
}
/*
* 异常通知:
* 1. 只有方法有异常的时候才会触发异常通知,并且在通知中可以获取异常类型
*/
@AfterThrowing(pointcut="execution(* com.zl.service..*.*(..))",throwing="e")
public void AfterThrowing(JoinPoint jp,Exception e) {
if(e instanceof ArithmeticException) {
System.out.println("这是算数异常,做.......");
}else if(e instanceof ClassCastException) {
System.out.println("这是类型转换异常,做.......");
}
System.out.println("异常通知"+jp.getSignature().getName()+":方法抛出异常,抛出异常的时间为:"+new Date());
}
/*
* 环绕通知:
* 1. 可以拿到方法返回值,并且可以更改方法返回值
* 2. 可以控制目标方法是否继续执行
* 3. 相当于以上通知的合集
* 4. proceed()该方法不调用最终的效果是环绕通知执行了但是目标方法没有执行
*/
@Around("execution(* com.zl.service..*.*(..))")
public Object around(ProceedingJoinPoint pjp) {
Object obj=null;
try {
System.out.println("环绕通知方法前....");
//相当于执行目标方法
obj=pjp.proceed();
obj=100;
System.out.println("环绕通知中获取的方法返回值为:"+obj);
System.out.println("环绕通知方法后....");
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
切点表达式的逻辑运算符
@before("execution(* com.zl.service.impl.UserServiceImpl.add*()) || execution(* com.zl.service.impl.UserServiceImpl.delete*())")
基于配置文件的AOP配置
1.声明切面
当使用 XML 声明切面时, 需要在 根元素中导入 aop Schema
在 Bean 配置文件中, 所有的 Spring AOP 配置都必须定义在 aop:config 元素内部. 对于每个切面而言, 都要创建一个 aop:aspect 元素来为具体的切面实现引用后端 Bean 实例.
切面 Bean 必须有一个标示符, 供 aop:aspect 元素引用
2.声明切点表达式
切入点使用 aop:pointcut 元素声明
切入点必须定义在 aop:aspect 元素下, 或者直接定义在 aop:config 元素下.
定义在 aop:aspect 元素下: 只对当前切面有效
定义在 aop:config 元素下: 对所有切面都有效
基于 XML 的 AOP 配置不允许在切入点表达式中用名称引用其他切入点.
3.声明通知
在 aop Schema 中, 每种通知类型都对应一个特定的 XML 元素.
通知元素需要使用 来引用切入点, 或用 直接嵌入切入点表达式. method 属性指定切面类中通知方法的名称.
案例:
<!-- 第一步:通过注解把切面对象放到IOC容器中 -->
@Component
public class MyAspect {
....
}
<!-- 第二步:配置AOP相关的配置 -->
<aop:config>
<!-- 配置切面表达式 -->
<aop:pointcut expression="execution(* com.zl.dao..*.*(..))" id="myPointCut1"/>
<aop:pointcut expression="!execution(* com.zl.dao..*.add*(..))" id="myPointCut2"/>
<!-- 配置好的切面表达式作用在切面对象的那个通知上面 -->
<aop:aspect ref="myProxy">
<aop:after method="after" pointcut-ref="myPointCut1"/>
<aop:before method="before" pointcut-ref="myPointCut1"/>
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut1" returning="obj"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut1" throwing="e"/>
<aop:around method="around" pointcut-ref="myPointcut1"/>
</aop:aspect>
</aop:config>