Spring-AOP
1.AOP简介:
(一)简介
aop思想:基于代理思想,对原来目标创建代理对象,在不修改原对象代码情况,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强.
(二)为什么使用AOP:
AOP可以对传给你续进行增强(不修改源代码的情况下),可以进行权限校验,日志记录,性能监控,事务控制。正常程序执行流程都是纵向执行流程,面向切面编程,在原有纵向流程中添加横切面(例如性能监视,事务管理,安全检查,缓存,日志记录)
不需要修改源代码
原有功能相当于释放了部分逻辑,让职责更加明确
1.代码混乱:越来越多的非业务需求(日志和验证等)加入后, 原有的业务方法急剧膨胀. 每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点.
2.代码分散: 以日志需求为例, 只是为了满足这个单一需求, 就不得不在多个模块(方法)里多次重复相同的日志代码. 如果日志需求发生变化, 必须修改所有模块.
(三)AOP底层实现
代理机制:
*Spring的AOP的底层用到两种代理机制:
*JDK动态代理:针对实现了接口的类产生代理
*Cglib的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术 生成当前类的子类对象。
2.AOP关键术语:
连接点(Joinpoint)
: 是指那些被拦截到的点,再Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
切点(pointcut)
:每个类都拥有多个连接点。 ClientServiceImpl中所有的方法都是切点。AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。 简单来说:Advice定义了切面要发生“故事”和时间,那么切入点就定义了“故事”发生的地点。例如某个类或者方法名,Spring中允许我们使用正则来指定
通知(Advice)
: 是指拦截到Joinpoint之后要做的事情就是通知,分类前置通知,后置通知,异常通知,最终通知,环绕通知。
目标(Target)
: 被通知的对象,也就是目标对象。
切面(Aspect)
: 由横切关注点构成的特殊对象。
织入(Weaving)
:将增强添加到目标类具体连接点上的过程。AOP有三种织入的方式:编译期织入、类装载期织入、动态代理织入(spring采用动态代理织入)
代理(Proxy)
: 向目标对象应用通知之后创建的对象。
3.通知类型:
前置通知,在目标方法执行之前执行 @Before("execution(*.*)")
后置通知,在目标方法执行之后执行 @After("execution(*.*)")
异常通知,目标对象出现异常是执行 @AfterThrowing(value =("execution(*.*)", throwing = "e")
返回通知,无论是否出现异常,最终通知都会执行 @AfterReturning("execution(*.*)", returning = "sb")
环绕通知,在目标方法执行前后执行 @Around("execution(*.*)")
4.SpringAOP实现步骤:
•AspectJ:Java 社区里最完整最流行的 AOP 框架.
•在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP
使用步骤:
1.aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
2.spring配置文件中添加:xmlns:aop=“http://www.springframework.org/schema/aop”
3.要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 aop:aspectj-autoproxy 。使AspectJ相关注解生效:当调用目标方法,跟Aspect中声明的方法相匹配的时候,AOP框架会自动的为目标方法所在的类创建代理对象
1.基于注解的方式实现springAOP:
1.创建切面类(LoggingAspect)。
@Aspect
:声明当前类为切面类。
@Component
:交给springIOC容器进行管理。
@Before
:前置通知。
需要使用切点表达式:@Befort("execution(* com.client.service.impl.*.*(..))")
,指定需要在哪个方法执行之前执行该方法(通知)。其中第一个*
表示匹配所有访问权限修饰符以及返回值类型,第二个*
表示匹配当前包下所有的类,第三个*
表示匹配所有的方法,..
则表示匹配当前方法中所有的参数。其中(Joinpoint)
对象中封装了目标方法的一些信息,例如获取目标方法名称,获取目标方法参数等。
@Order(number)
:可以配置切面的优先级。
@After
:后置通知。使用方法与前置通知相同。注意:后置通知即使方法异常也会成功执行,但是后置通知无法拿到目标方法的返回结果。需要返回通知。
@AfterReturnning
:返回通知,在方法正常之后之后执行的通知,可以拿到目标方法的返回结果。使用返回通知需要注意的是:指定returnning=“result”,afterReturnningAdvice(JoinPoint joinpoint,Object result)
与方法入参位置的对象名称一致,否则会产生异常。
@AfterThrowing
:异常通知,方法产生异常的时候,可以拿到异常信息。同样需要注意的是:指定throwing=“e”,与afterThrowingAdvice(JoinPoint joinpoint,Exception e)
方法入参位置的异常对象名称一致。
@Around
:环绕通知,如下所示,是不是更像动态代理了呢?
2.基于XML文件的方式实现springAOP:
正常情况下, 基于注解的声明要优先于基于 XML 的声明. 通过 AspectJ 注解, 切面可以与 AspectJ 兼容, 而基于 XML 的配置则是 Spring 专有的. 由于 AspectJ 得到越来越多的 AOP 框架支持, 所以以注解风格编写的切面将会有更多重用的机会. "
<!-- 配置切面的bean. -->
<bean id="checkAspect" class="com.client.aspect.CheckAspect"></bean>
<bean id="loggingAspect" class="com.client.aspect.LoggingAspect"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切点表达式。 -->
<aop:pointcut expression="execution(* com.client.service.impl.*.*(..))"
id="pointcut"/>
<!-- 配置切面以及通知。 -->
<aop:aspect ref="checkAspect" order="1">
<aop:before method="checkBeforeAdvice" pointcut-ref="pointcut"/>
</aop:aspect>
<aop:aspect ref="loggingAspect" order="2">
<!-- <aop:before method="beforeLog" pointcut-ref="pointcut"/>
<aop:after method="afterLog" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturnningAdvice" pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pointcut" throwing="e"/> -->
<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
5.SpringAOP实现原理:
1.JDK动态代理(JDK提供,只能代理接口)。
使用动态代理可以为一个或多个接口在运行期动态生成实现对象,生成的对象中实现接口的方法时可以添加增强代码,从而实现AOP。缺点是只能针对接口进行代理,另外由于动态代理是通过反射实现的,有时可能要考虑反射调用的开销。
//JDK 提供的动态代理 ,只能代理接口 ,不能代理类 。
public class DynamicProxy implements InvocationHandler {
//声明一个对象,这个对象就是被代理的类
private Object obj;
//传入被代理对象
public Object getProxy(Object obj){
this.obj=obj;
//生成代理对象
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
check();
beforeLogging();
Object returnValue = method.invoke(obj, args);
afterLoggging();
return returnValue;
}
public void beforeLogging(){
System.out.println("事前日志 。");
}
public void afterLoggging(){
System.out.println("事后日志。");
}
public void check(){
System.out.println("安全检查。");
}
}
2.CGlib动态代理: (适用CGlib工具)。
采用动态的字节码生成技术,运行时动态生成指定类的一个子类对象,并覆盖其中特定方法,覆盖方法时可以添加增强代码,从而实现AOP 。
public class ProxyFactory implements MethodInterceptor{
private Object target;//维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}
//为目标对象生成代理对象
public Object getProxyInstance() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类对象代理
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
check();
beforeLogging();
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
afterLoggging();
return returnValue;
}
public void beforeLogging(){
System.out.println("事前日志 。");
}
public void afterLoggging(){
System.out.println("事后日志。");
}
public void check(){
System.out.println("安全检查。");
}
}