AOP面向切面编程(Aspect )
原理:
动态代理
有接口的情况下,使用jdk动态代理,没有接口情况,使用 cglib动态代理
术语:
- 连接点(Joinpoint) 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强
- 连接点表示具体要拦截的方法,上面切点是定义一个范围,而连接点是具体到某个方法
- 切点(PointCut) 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。比如,连接点--数据库的记录,切点--查询条件
- 切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围
- 增强(Advice) 增强是织入到目标类连接点上的一段程序代码。在Spring中,像BeforeAdvice等还带有方位信息
- 通知是直译过来的结果,我个人感觉叫做“业务增强”更合适 对照代码就是拦截器定义的相关方法,通知分为如下几种:
- 前置通知(before):在执行业务代码前做些操作,比如获取连接对象
- 后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象
- 异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务
- 返回通知(afterReturning),在执行业务代码后无异常,会执行的操作
- 环绕通知(around),这个目前跟我们谈论的事务没有对应的操作,所以暂时不谈
- 目标对象(Target) 需要被加强的业务对象
- 织入(Weaving) 织入就是将增强添加到对目标类具体连接点上的过程。
- 织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
- 代理类(Proxy) 一个类被AOP织入增强后,就产生了一个代理类。
- 切面(Aspect) 切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。
- 比如上文讨论的数据库事务,这个数据库事务代码贯穿了我们的整个代码,我们就可以这个叫做切面。 SpringAOP将切面
execution表达式:
//列如
expression="execution(void com.controller.xxxx.doSome(String,Integer))"
* com.xxxx.xxxx.xxxx.*.*(..)
* 表示任意返回值
com.xxxx.xxxx.xxxx 表示包
第一个.*表示包下的任意类
第二个.*表示包下的任意类的任意方法
(..) 表示有参或者无参或者任意参数
实现aop的三种方法:
1、使用原生动态代理的方式去实现
使用invacationHandler 和proxy 进行实现 (理解就ok)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Human {
void info();
void fly();
}
// 被代理类
class SuperMan implements Human {
public void info() {
System.out.println("我是超人!我怕谁!");
}
public void fly() {
System.out.println("I believe I can fly!");
}
}
class HumanUtil {
public void method1() {
System.out.println("=======方法一=======");
}
public void method2() {
System.out.println("=======方法二=======");
}
}
class MyInvocationHandler implements InvocationHandler {
Object obj;// 被代理类对象的声明
public void setObject(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
HumanUtil h = new HumanUtil();
h.method1();
Object returnVal = method.invoke(obj, args);
h.method2();
return returnVal;
}
}
class MyProxy {
// 动态的创建一个代理类的对象
public static Object getProxyInstance(Object obj) {
MyInvocationHandler handler = new MyInvocationHandler();
handler.setObject(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), handler);
}
}
public class TestAOP {
public static void main(String[] args) {
SuperMan man = new SuperMan();//创建一个被代理类的对象
Object obj = MyProxy.getProxyInstance(man);//返回一个代理类的对象
Human hu = (Human)obj;
hu.info();//通过代理类的对象调用重写的抽象方法
System.out.println();
hu.fly();
//*********
NikeClothFactory nike = new NikeClothFactory();
Object obj1 = MyProxy.getProxyInstance(nike);
ClothFactory cloth = (ClothFactory)obj1;
cloth.productCloth();
}
}
2、使用xml注册文件配置
基础语法:
<aop:config>
<!-- 表达式配置哪些类的哪些方法需要进行增强 -->
<aop:pointcut expression="execution(* com.codehorse.dao.CustomerDaoImpl.add(..))" id="pointcut_add"/>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<aop:before method="myBefore" pointcut-ref="pointcut_add"/>
<aop:around method="myAround" pointcut-ref="pointcut_delete"/>
<aop:after method="myAfter" pointcut-ref="pointcut_find"/>
<aop:after-returning method="myAfterReturning" pointcut-ref="pointcut_update"/>
</aop:aspect>
</aop:config>使用:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userServiceImpl"
class="com.bookmanagesystem.service.impl.UserServiceImpl"></bean>
<bean id="mylogger"
class="com.bookmanagesystem.util.MyLogger">
</bean>
<aop:config>
<aop:pointcut
expression="execution(* com.iflytek.bookmanagesystem.service.impl.*.*(..) )"
id="pc1" />
<!-- 3. 配置切面 -->
<aop:aspect id="logAdvice" ref="mylogger">
<!-- 配置通知的类型 (原始方法) -->
<!-- <aop:before method="printLog" pointcut-ref="pc1" /> <aop:after-returning method="printLog" pointcut-ref="pc1" /> -->
<!-- <aop:before method="printLog" pointcut="execution(public void com.bookmanagesystem.service.impl.UserServiceImpl.delete()
)" /> -->
<!-- 1. 配置前置通知 -->
<!-- <aop:before method="beforePrintLog" pointcut-ref="pc1" /> -->
<!-- 2. 配置后置通知 -->
<!-- <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pc1"
/> -->
<!-- 3. 配置异常通知 -->
<!-- <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pc1"
/> -->
<!-- 4. 配置最终通知 -->
<!-- <aop:after method="afterPrintLog" pointcut-ref="pc1" /> -->
<!-- 5. 配置环绕通知 -->
<aop:around method="aroundPrintLog" pointcut-ref="pc1" />
</aop:aspect>
</aop:config>
</beans>
3、注解开发
- @Aspect :标注在类上,表示当前类是一个切面类;
- @PointCut:切入点;能够在一个@AspectJ切面内定义可重用的切点。被@PointCut标注的方法的实际内容其实并不重要,实际上应该是空的,该方法本身只是一个标识,工@PointCut注解依附。
- @Before:前置通知,即在某个连接点之前执行通知;
- @AfterReturning:后置通知,即在某个连接点正常完成后执行通知,通常在一个匹配的方法返回的时候执行;
- @AfterThrowing:异常通知,即在方法抛出异常退出时执行通知;
- @After:最终通知,即某个连接点退出时执行通知;
- @Around:环绕通知,它是最强大也是最麻烦的通知,它可以在方法调用前后完成自定义的行为,它可以自己选择是否继续执行连接点或者直接返回或者抛出异常来结束执行
xml开启
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:p="http://www.springframework.org/schema/p"
5 xmlns:context="http://www.springframework.org/schema/context"
6 xmlns:aop="http://www.springframework.org/schema/aop"
7 xsi:schemaLocation="
8 http://www.springframework.org/schema/beans
9 http://www.springframework.org/schema/beans/spring-beans.xsd
10 http://www.springframework.org/schema/context
11 http://www.springframework.org/schema/context/spring-context.xsd
12 http://www.springframework.org/schema/aop
13 http://www.springframework.org/schema/aop/spring-aop.xsd">
14
15 <!-- 开启注解扫描 -->
16 <context:component-scan base-package="com.bie.aop"></context:component-scan>
17
18 <!-- 开启aop注解方式,
proxy-target-class属性默认为false
false使用jdk原生去实现动态代理
true时使用 cglib实现动态代理-->
19 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
20
21 </beans>
方法的参数
joinPoint:
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
Signature getSignature() :获取连接点的方法签名对象;
java.lang.Object getTarget() :获取连接点所在的目标对象;
java.lang.Object getThis() :获取代理对象本身;
@Before("xxxxxx") public void before(JoinPoint joinPoint) {
}
ProceedingJoinPoint
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:
- java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法;
- java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。
代码实现
@Aspect
public class LogAspects {
//指定切入点
@Pointcut("execution(int com.test.tornesol.util.spring.spring_aop.MathCalculator.div(int,int))")
public void pointCut() { }
//执行前
@Before("com.test.tornesol.util.spring.spring_aop.LogAspects.pointCut()")
public void logStart(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + " 除法运行,参数是:" + Arrays.asList(joinPoint.getArgs()));
}
//执行后
@After("com.test.tornesol.util.spring.spring_aop.LogAspects.pointCut()")
public void logEnd() {
System.out.println("除法结束");
}
//返回值时
@AfterReturning(value = "com.test.tornesol.util.spring.spring_aop.LogAspects.pointCut())", returning = "result")
public void logReturn2(JoinPoint joinPoint, Object result) {
System.out.println(joinPoint.getSignature().getName() + "除法返回" + result);
}
//异常时
@AfterThrowing(value = "com.test.tornesol.util.spring.spring_aop.LogAspects.pointCut()", throwing = "exception")
public void logException(Exception exception) {
System.out.println("除法异常");
}
}