AOP解决的问题:把横切关注点与业务逻辑相分离,即可以实现横切关注点与他们所影响的对象之间的解耦。
什么是横切关注点呢?它可以被描述为影响应用多个地方的功能。亦或者理解为辅助功能。比如日志、安全、缓存和事务管理。
1、看一下AOP的术语:
String切面可以应用5种类型的通知:前置通知,后置通知,返回通知,异常通知,环绕通知。定义了切面的什么、何时
//前置通知
public void before(){
System.out.println("这是前置通知!!");
}
//后置通知
public void afterReturning(){
System.out.println("这是后置通知(如果出现异常不会调用)!!");
}
//环绕通知
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("这是环绕通知之前的部分!!");
Object proceed = pjp.proceed();//调用目标方法
System.out.println("这是环绕通知之后的部分!!");
return proceed;
}
//异常通知
public void afterException(){
System.out.println("出事啦!出现异常了!!");
}
//后置通知
public void after(){
System.out.println("这是后置通知(出现异常也会调用)!!");
}
连接点:切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。定义了切面的“何处”。
切点:有助于缩小切面通知的连接点的范围。
切面:通知和切点的结合。通知和切点定义了切面的全部内容。
引入:向现有的类添加新的方法和属性。
织入:把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多个点可以进行织入:编译期、类加载期、运行 期。
2、Spring提供的4种类型的AOP支持:
- 基于代理的经典SpringAOP
- 纯POJO切面
- @AspectJ注解驱动的切面
- 注入式AspectJ切面
基于代理的经典SpringAOP,但是显得非常笨重和复杂。
动态代理:代理的对象必须要实现接口
只能对实现了接口的类生成代理,而不是针对类,该目标类型实现的接口都将被代理。原理是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。步骤如下:
1. 定义一个实现接口InvocationHandler的类
2. 通过构造函数,注入被代理类
3. 实现invoke( Object proxy, Method method, Object[] args)方法
4. 在主函数中获得被代理类的类加载器
5. 使用Proxy.newProxyInstance( )产生一个代理对象
6. 通过代理对象调用各种方法
public class UserServiceProxyFactory implements InvocationHandler{
//使用构造方法必须把us传进来
public UserServiceProxyFactory(UserService us) {
super();
this.us = us;
}
private UserService us;
public UserService getUserServiceProxy() {
//生成动态接口代理
UserService usProxy = (UserService) Proxy.newProxyInstance(UserServiceProxyFactory.class.getClassLoader(),
UserServiceImpl.class.getInterfaces(), this);
return usProxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("打开事务");
Object invoke = method.invoke(us, args);
System.out.println("提交事务");
return invoke;
}
}
@Test
public void fun1() {
UserService us = new UserServiceImpl();
UserServiceProxyFactory factory = new UserServiceProxyFactory(us);
UserService userServiceProxy = factory.getUserServiceProxy();
userServiceProxy.delete();
}
CGLib代理: 实现原理通过继承目标对象来达到代理的目的,被final修饰的类不能够被代理。
public class UserServiceProxyFactory2 implements MethodInterceptor{
public UserService getUserServiceProxy() {
Enhancer en = new Enhancer();sCVZCZ
en.setSuperclass(UserServiceImpl.class);
en.setCallback(this);
UserService us = (UserService) en.create();
return us;
}
@Override
public Object intercept(Object proxyobj, Method menhtod, Object[] arg2, MethodProxy methodProxy) throws Throwable {
// 打开事务
System.out.println("打开事务");
// 调用原有方法
Object invokeSuper = methodProxy.invokeSuper(proxyobj, arg2);
// 提交事务
System.out.println("提交事务");
return invokeSuper;
}
}
@Test
public void fun2() {
UserService us = new UserServiceImpl();
UserServiceProxyFactory2 factory = new UserServiceProxyFactory2();
UserService userServiceProxy = factory.getUserServiceProxy();
userServiceProxy.delete();
}
编写切点:execution 是Spring中使用最主要的AspectJ指示器。可以使用within联用&&、||、!符号来限制匹配。
使用AspectJ切点表达式选择执行的方法,<aop:pointcut expression="execution(* Lamb_quan.service.*ServiceImpl.*(..))" id="shu"/>
在切点中选择bean: execution(* Lamb_quan.service.*ServiceImpl.*(..)) and bean(liuyifei)
execution(* Lamb_quan.service.*ServiceImpl.*(..)) and !bean(liuyifei)
定义切面:@Aspect注解的类不仅仅是一个普通的POJO,还是一个切面。@Pointcut注解能够在一个@Aspect切面内定义可重用的切点。
Spring使用AspectJ注解来声明通知方法:@After @AfterReturning @AfterThrowing @Around @Before
@Pointcut注解声明频繁使用切点表达式:
@Aspect
public class Audience{
@Pointcut("execution(* Lamb_quan.service.perform(..))")
public void performance () {} //扩展切点表达式语言
@Before("performance () ")
public viod otherMethod () {}
}
创建环绕通知:
@Aspect
public class Audience{
@Pointcut("execution(* Lamb_quan.service.perform(..))")
public void performance () {} //扩展切点表达式语言
@Around("performance () ")
public viod otherMethod (ProceedingJiontPoint jp) {
try{ 前置通知 jp.proceed(); 后置通知}catch(Throwable e){ 异常通知}
}
}
处理通知中的参数:execution(* Lamb_quan.service.perform(int)) &&args(指定参数)
通过注解引入新功能:@DeclareParents注解
3、在XML中声明切面
<!-- 准备工作: 导入aop(约束)命名空间 -->
<!-- 1.配置目标对象 -->
<bean name="userService" class="xxx.service.UserServiceImpl" ></bean>
<!-- 2.配置通知对象 -->
<bean name="myAdvice" class="xxx.d_springaop.MyAdvice" ></bean>
<!-- 3.配置将通知织入目标对象 -->
<aop:config>
<!-- 配置切入点
public void xxx.service.UserServiceImpl.save()
-->
<aop:pointcut expression="execution(* xxx.service.*ServiceImpl.*(..))" id="pc"/>
<aop:aspect ref="myAdvice" >
<!-- 指定名为before方法作为前置通知 -->
<aop:before method="before" pointcut-ref="pc" />
<!-- 后置 -->
<aop:after-returning method="afterReturning" pointcut-ref="pc" />
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pc" />
<!-- 异常拦截通知 -->
<aop:after-throwing method="afterException" pointcut-ref="pc"/>
<!-- 后置 -->
<aop:after method="after" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
为切面传递参数、通过切面引入新的功能。
4、Spring注入AspectJ切面(功能更加强大)