AOP面向切面编程——纵向重复,横向抽取
典型的例子有:过滤器filter、拦截器interceptor、动态代理。
动态代理:通过动态代理可以体现AOP思想;对目标对象中的方法方法进行增强。
Spring能够为容器中管理的对象生成动态代理对象,以前我们要使用动态代理,我们需要自己调用下面这个方法:Proxy.newProxyInstatnce(classLoader , interface, invocationHandler )生成动态代理对象。即Spring能帮我们生成动态代理对象。
一、Spring AOP中的一些基本概念
1.1、代理
1.1.1、动态代理
被代理对象必须要实现接口,才能产生代理对象,如果没有接口将不能使用动态代理技术。
(有接口时,优先使用)
1.1.2、cglib代理
这个代理技术是第三方代理技术,它可以对任何类生成代理,代理的原理是对目标对象进行继承代理,如果目标对象被final修饰,那么该类无法被cglib代理。(没有接口时使用)
1.2、名词解释
JoinPoint(连接点):目标对象中,所有可以增强的方法
Pointcut(切入点):目标对象,已经增强的方法。
Advice(通知/增强):增强的代码
Target(目标对象):被代理的对象
Wearing(织入):将通知应用到切入点的过程
Proxy(代理):将通知织入到目标对象后,形成的代理对象
aspect(切面):切入点+通知
二、Spring中的AOP演示
2.1、导包
一共十个jar包,之前导入了:4个Spring基础jar包+2个日志包,现在我们还要导入aop包和aspect包,还有两个外部依赖包,一个是aopalliance包,还有一个是aspectj-weaver包。
2.2、准备目标对象
定义一个类UserServiceImpl.java实现自定义的IUserService接口:
package com.zl.service;
public class UserServiceImpl implements IUserService {
@Override
public void save() {
System.out.println("保存用户!");
}
@Override
public void delete() {
System.out.println("删除用户!");
}
@Override
public void update() {
System.out.println("更新用户!");
}
@Override
public void find() {
System.out.println("查找用户!");
}
}
2.3、准备通知
package com.zl.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
//前置通知:目标方法运行之前调用
public void before() {
System.out.println("这是前置通知!");
}
//后置通知1(如果出现异常,将不会调用):在目标方法运行之后调用
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("出现异常啦,调用异常拦截通知啦!");
}
//后置通知2(无论是否出现异常都会调用):在目标方法运行之后调用
public void after() {
System.out.println("这是后置通知(出现异常也会被调用)!");
}
}
2.4、配置进行织入,将通知织入目标对象中
新建一个包和一个applicationContext.xml,在之前导入bean约束和context约束的基础上,再添加aop约束:
最后有以下三个约束:
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">
<!-- 1.配置目标对象 -->
<bean name="userServiceTarget" class="com.zl.service.UserServiceImpl"> </bean>
<!-- 2.配置通知对象 -->
<bean name="myAdvice" class="com.zl.aspect.MyAdvice"></bean>
<!-- 3.配置将通知织入目标对象 -->
<aop:config>
<!--配置切入点
execution是表达式,id相当于指定切入点的名称 ,表示对目标对象里的delete方法进行切入/增强
void com.zl.service.UserServiceImpl.delete() public是可以省略的,默认就是public
* com.zl.service.UserServiceImpl.delete() 不对返回值作任何要求
* com.zl.service.UserServiceImpl.*() 给UserServiceImpl类下的所有空参方法
* com.zl.service.UserServiceImpl.*(..) 相比上一种,参数不作要求
* com.zl.service.*ServiceImpl.*(..) 相比上一种,找service下面,以ServiceImpl结尾的类下的所有方法
* com.zl.service..*ServiceImpl.*(..) 相比上一种,service下的子包也会找
-->
<aop:pointcut expression="execution(* com.zl.service.*ServiceImpl.*(..))" id="pointcut"/>
<aop:aspect ref="myAdvice">
<!-- 指定名为before方法作为前置通知 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
<aop:around method="around" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterException" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
为了方便测试,我们将JUnit和Spring整合一下:
需要导入一个test的jar包:
编写Test类:
package com.zl.springaop;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.zl.service.IUserService;
//RunWith指定一个类,通过这个类的某个方法,帮我们创建容器
@RunWith(SpringJUnit4ClassRunner.class)
//指定创建容器时使用哪个配置文件
@ContextConfiguration("classpath:com/zl/springaop/applicationContext.xml")
public class TestAOP {
//将名为userService的对象注入到userService变量中
@Resource(name="userService")
private IUserService userservice;
@Test
public void fun1() {
userservice.delete();
}
}
结果如下:
这个里面没有异常捕获通知,所以在 UserServiceImpl 类的delete( )方法里面末尾添加一个 int i = 1/0; 结果如下:
2.4、注解进行织入,将通知织入目标对象中
MyAdvice类
//表示该类是一个通知类
@Aspect
public class MyAdvice {
//指定该方法是前置通知,指定切入点
@Before("execution(* com.zl.service.*ServiceImpl.*(..) )")
//前置通知:目标方法运行之前调用
public void before() {
System.out.println("这是前置通知!");
}
@AfterReturning("execution(* com.zl.service.*ServiceImpl.*(..) )")
//后置通知1(如果出现异常,将不会调用):在目标方法运行之后调用
public void afterReturning() {
System.out.println("这是后置通知(没有出现异常)!");
}
@Around("execution(* com.zl.service.*ServiceImpl.*(..) )")
//环绕通知:在目标方法之前和之后都会调用
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("这是环绕通知之前的部分!");
Object proceed = pjp.proceed(); //调用目标方法
System.out.println("这是环绕通知之后的部分!");
return proceed;
}
@AfterThrowing("execution(* com.zl.service.*ServiceImpl.*(..) )")
//异常拦截通知:如果出现异常就会调用
public void afterException() {
System.out.println("出现异常啦,调用异常拦截通知啦!");
}
@After("execution(* com.zl.service.*ServiceImpl.*(..) )")
//后置通知2(无论是否出现异常都会调用):在目标方法运行之后调用
public void after() {
System.out.println("这是后置通知(出现异常也会被调用)!");
}
}
下述MyAdvice类,可以看出这个配置每一个里面都有相同的切点表达式 :("execution(* com.zl.service.*ServiceImpl.*(..) )")
下面把切点抽取出来: