欢迎光临我的博客[http://poetize.cn],前端使用Vue2,聊天室使用Vue3,后台使用Spring Boot
AOP简介
AOP:
面向切面,作为面向对象的一种补充,
用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,
这个模块被命名为“切面”(Aspect),
减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
可用于权限认证、日志、事务处理。
AOP的关键单元是切面:
AOP可以使用简单可插拔的配置,在实际逻辑执行之前、之后或周围动态添加横切关注点。
如果使用XML来使用切面,要添加或删除关注点,不用重新编译完整的源代码,而仅仅需要修改配置文件就可以了。
AOP主要的的实现技术主要有:
AspectJ:
底层技术是静态代理,
通过编译,生成一个新的代理类,该代理类增强了业务类,这是在编译时增强,编译时增强的性能更好。
Spring AOP:
动态代理,在运行期间对业务方法进行增强,不会生成新类。
Spring AOP是面向切面编程:
是面向对象编程的一种补充,
使用动态代理实现,在内存中临时为目标对象生成一个AOP对象,这个对象包含目标对象的所有方法,在特定的切点做了增强处理,并回调原来的方法。
Spring AOP的动态代理主要有两种方式实现:
JDK动态代理和cglib动态代理。
JDK动态代理:
通过反射来接收被代理的类,但是被代理的类必须实现接口,动态创建一个符合某一接口的的实例。核心是InvocationHandler和Proxy类。
CGLIB动态代理:
CGLIB是一个代码生成的类库,可以在运行时动态生成某个类的子类,并覆盖其中特定方法并添加增强代码,从而实现AOP。
所以,CGLIB是通过继承的方式做的动态代理。
JDK代理
public class ProxyFactory {
/**
* 获取代理对象代理对象
*/
public static Object getProxyObject(final Object realObject, final Object aspectObject) {
final Class<?> realObjectClass = realObject.getClass();
final Class<?> aspectObjectClass = aspectObject.getClass();
return Proxy.newProxyInstance(
realObjectClass.getClassLoader(),
realObjectClass.getInterfaces(),
new InvocationHandler() {
/**
* 模拟简单的@Before日志注解
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 加载切入点信息. 这里的方法Logger被硬编码了, 后期可以根据注解来解决
*/
Method pointCutMethod = aspectObjectClass.getMethod("Logger", new Class[]{});
...
/**
* 执行原始对象的方法
*/
return method.invoke(realObject, args);
}
}
);
}
}
public class ProxyDemo {
public static void main(String[] args){
Target proxyObject = (Target) ProxyFactory.getProxyObject(new TargetImpl(), new Aspect()); //Target是接口
proxyObject.method1();
proxyObject.method2();
}
}
连接点(Joint Point),切入点(Point cut),织入(weaving),增强(Advice),切面(Aspect)
连接点:
一个连接点代表一个目标方法。
切入点:
一个匹配连接点的断言或者表达式
execution(* *.*(..))
织入:
将切面与目标类连接起来以创建通知对象(adviced object)的过程。
可以在编译时(比如使用 AspectJ 编译器)、加载时或者运行时完成。
对方法的增强叫做 Weaving(织入),而对类的增强叫做 Introduction(引入)
增强:
织入到目标类连接点上的一段程序代码
切面:
切面由切点和增强组成
基于代理的AOP实现
切面类:
public class BeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("前置通知");
/**
* arg0 切点的方法
* arg1 切点的方法形参
* arg3 切点所在的对象
*/
...
}
}
public class AroundAdvice implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("环绕通知前");
Object proceed = arg0.proceed();
System.out.println("环绕通知后");
return proceed;
}
}
配置:
<!-- 注册代理目标类 -->
<bean id="service" class="service.ServiceImpl"></bean>
<!-- 注册前置通知 -->
<bean id="beforeAdvice" class="advice.BeforeAdvice"></bean>
<!-- 注册后置通知 -->
<bean id="aroundAdvice" class="advice.AroundAdvice"></bean>
<!-- 注册代理工厂Bean -->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置目标类的接口 -->
<property name="interfaces">
<list>
<value>service.Service</value>
</list>
</property>
<!-- 配置目标类-->
<property name="targetName" value="service"></property>
<!-- 配置拦截时执行的通知 -->
<property name="interceptorNames">
<array>
<value>beforeAdvice</value>
<value>aroundAdvice</value>
</array>
</property>
<!-- 指定使用的代理,proxyTargetClass是问是否代理类,JDK动态代理是代理接口,CGLIB代理是代理类。false即不是代理类,即使用JDK动态代理 -->
<!-- 默认就是false,JDK动态代理。此句配置可缺省 -->
<property name="proxyTargetClass" value="false" />
<!-- 返回的代理类实例是否是单实例,默认为true,单实例。此句配置可缺省 -->
<property name="singleton" value="true" />
</bean>
Spring + AspectJ 基于XML的开发
切面类:
public class Aspect {
public void before() {
System.out.println("前置通知");
}
public void afterReturn(Object returnVal) {
System.out.println("后置通知-->返回值:" + returnVal);
}
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知前");
Object object = joinPoint.proceed();
System.out.println("环绕通知后");
return object;
}
public void afterThrowing(Throwable throwable) {
System.out.println("异常通知:" + throwable.getMessage());
}
public void after() {
System.out.println("最终通知");
}
}
配置文件:
<!-- 定义目标对象 -->
<bean name="productDao" class="dao.ProductDaoImpl"/>
<!-- 定义切面 -->
<bean name="aspect" class="aspect.Aspect"/>
<!-- 配置AOP 切面 -->
<aop:config>
<!-- 定义切点函数 -->
<aop:pointcut id="addPointcut" expression="execution(* dao.ProductDao.add(..))"/>
<!-- 定义其他切点函数 -->
<aop:pointcut id="delPointcut" expression="execution(* dao.ProductDao.delete(..))"/>
<!-- 定义通知:order定义优先级,值越小优先级越大 -->
<aop:aspect ref="aspect" order="0">
<!-- 定义通知
method 指定通知方法名,必须与Aspect中的相同
pointcut 指定切点函数 -->
<aop:before method="before" pointcut-ref="addPointcut"/>
<!-- 后置通知:returning="returnVal" 定义返回值,必须与类中声明的名称一样-->
<aop:after-returning method="afterReturn" pointcut-ref="addPointcut" returning="returnVal"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="delPointcut"/>
<!--异常通知:throwing="throwable"指定异常通知错误信息变量,必须与类中声明的名称一样-->
<aop:after-throwing method="afterThrowing" pointcut-ref="delPointcut" throwing="throwable"/>
<!-- method : 通知的方法(最终通知)
pointcut-ref : 通知应用到的切点方法 -->
<aop:after method="after" pointcut-ref="delPointcut"/>
</aop:aspect>
</aop:config>
Spring + AspectJ 全注解配置
<aop:aspectj-autoproxy proxy-target-class="true"/>:
启动@AspectJ支持。
proxy-target-class="true" 属性,它的默认值是 false:默认只能代理接口(使用 JDK 动态代理),当为 true 时,才能代理目标类(使用 CGLib 动态代理)。
<!-- 定义目标对象 -->
<bean id="targer" class="Targer" />
<!-- 定义aspect类 -->
<!-- <bean name="aspect" class="Aspect"/> -->
@Pointcut("@within(com.spring.annotation.MarkerAnnotation)") //匹配使用了MarkerAnnotation注解的类(注意是类)
@Pointcut("within(dao.*)") //匹配dao包所有类中的所有方法
/**
* @Component: 把该放入Spring IOC容器中
* @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
*/
@Component
@Aspect
public class Aspect {
@Pointcut("execution(* service.Targer.*(..))")
private void pc(){}
/**
* 前置通知: 在目标方法执行之前执行前置通知
*/
@Before("pc()")
public void beforeAdvice(JoinPoint joinPoint){
//获取方法名
String methodName = joinPoint.getSignature().getName();
//通过JoinPoint 获取切点方法的参数列表集合
Object[] args = joinPoint.getArgs();
//获取切点所在的目标对象
Object target = joinPoint.getTarget();
//获取切点的方法的对象
Signature signature = joinPoint.getSignature();
MethodSignature ms = (MethodSignature)signature;
Method method = ms.getMethod();
}
/**
* 后置通知: 在目标方法执行之后执行后置通知(无论是否出现异常)。
*/
@After("pc()")
public void afterAdvice(JoinPoint joinPoint){
//获取方法名
String methodName = joinPoint.getSignature().getName();
}
/**
* 返回通知: 连接点正常结束之后执行(未抛出异常)
*/
@AfterReturning(value = "execution(* service.Targer.*(..))", returning = "result")
public void returnAdvice(JoinPoint joinPoint, Object result){
//获取方法名
String methodName = joinPoint.getSignature().getName();
}
/**
* 异常通知: 在目标方法抛出异常后执行
*/
@AfterThrowing(value = "pc()", throwing= "ex")
public void returnAdvice(JoinPoint joinPoint, Exception ex){
//获取方法名
String methodName = joinPoint.getSignature().getName();
}
/**
* 环绕通知
*/
@Around("pc()")
public Object around(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs();
try {
System.out.println("前置通知");
Object proceed = pjp.proceed(args);
System.out.println("后置通知");
return proceed;
}catch (Throwable e){
System.out.println("异常通知");
throw new RuntimeException(e);
}finally {
System.out.println("最终通知");
}
}
}
使用场景
- Authentication 权限
- Caching 缓存
- Context passing 内容传递
- Error handling 错误处理
- Lazy loading 懒加载
- Debuggin 调试
- logging,tracing,profiling,monitoring 记录跟踪 优化 校准
- Performance optimization 性能优化
- Persistence 持久化
- Resource pooling 资源池
- Synchronization 同步
- Transactions 事务