spring aop
概述
- aop(aspect oriented programming),面向切面编程。通过预编译方式和运行期动态代理实现程序维护的统一编程模式。利用aop可以对业务逻辑的各个部分进行隔离,降低业务各部分的耦合度。
- aop是oop(面向对象编程)的延续
- aop是一个概念,没有特定的语言实现。spring2.0之后,整合了aspectj第三方aop技术框架。
- aspectj定义了aop语法,有一个专门的编译器用来生成遵守java字节编码规范的class文件。
功能
日志记录、性能统计、安全控制、事务处理、异常处理等
术语
- 目标对象 target
需要被增强的对象。spring aop是通过代理模式实现的,这个对象永远是指被代理的对象 - 连接点 join point
被具体拦截到的点。在spring中是指方法,因为spring只支持方法类型的连接点 - 切入点 pointcut
pointcut是指一组join point,这些join point通过某种逻辑关系集中起来。它定义了相应的advice将要发生的地方。 - 通知 advice
advice就是指拦截到join point之后要做的事情,分为前置通知、后置通知、异常通知、最终通知、环绕通知 - 引介 introduction
introduction是一种特殊的通知,在不修改类代码的前提下,introduction可以在运行期动态的为类添加一些方法和属性 - 切面 aspect
切入点pointcut + 通知advice - 织入 weaving
weaving是一个过程,就是将切面aspect应用到目标对象target从而创建出aop代理对象的过程。织入weaving可以在编译器、类装载器、运行期进行。
spring 采用动态织入,aspectj采用静态织入 - 代理proxy
一个类被aop织入增强后,就会产生一个结果代理类
aop实现
aop分为动态aop和静态aop
动态aop是指spring实现的aop,它是将切面代码动态织入到java类文件中。实现的技术:jdk提供的动态代理和cglib(动态字节码增强技术)两种技术。注意:如果目标对象有接口,spring优先使用jdk动态代理;如果目标对象没有接口,使用cglib动态代理
静态aop是指aspectj实现的aop,它是将切面代码直接编译到java类文件中。
动态代理
jdk动态代理
在运行期,在jvm内部动态生成class字节码对象
jdk动态代理,只针对于接口操作
public Object createProxy() {
/**
* 第一个参数:目标类的类加载器对象
* 第二个参数:目标类的实现集合数组class[]
* 第三个参数:invocationHandler,接口,它的作用是是代理实例的调用处理程序
*
* 返回值:一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new
InvocationHandler() {
// 匿名内部类,增强部分
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志操作");
return method.invoke(target, args);
}
});
}
cglib动态代理
cglib,开源,一个强大、高性能、高质量的代码生成类库。它可以在运行期扩展java类和实现java接口。cglib底层是使用一个小而快的字节码处理框架asm
注意:spring-core.jar已经集成了cglib和asm
注意2:cglib既可以为接口,也可以为类进行代理操作
public class CglibProxyFactory implements MethodInterceptor {
/** 目标对象 */
private Object target;
/**
* 使用构造方法传递目标对象
*/
public CglibProxyFactory(Object target) {
this.target = target;
}
/**
* 创建代理对象
*/
public Object createProxy() {
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 传递目标对象
enhancer.setSuperclass(target.getClass());
// 设置回调操作
enhancer.setCallback(this);
// 返回值
return enhancer.create();
}
/**
* 增强操作
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("日志操作");
return method.invoke(target, objects);
/*return methodProxy.invokeSuper(o,objects);*/
}
}
spring aop 传统编程
传统spring aop开发支持五种增强:
前置通知:org.springframework.aop.MethodBeforeAdvice
后置通知:org.springframework.aop.AfterReturningAdvice
环绕通知:org.aopalliance.intercept.MethodInterceptor
异常抛出通知:org.springframework.aop.ThrowsAdvice
引介通知:在目标类添加一些新的方法或者属性,org.aopalliance.intercept.IntroductionInterceptor
基于动态代理的经典aop开发
第一步:目标类
public class UserServiceImpl implements UserService {
@Override
public void login() {
System.out.println("login···");
}
@Override
public void register() {
System.out.println("register···");
}
}
第二步:增强
public class UserHelper implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor {
/**
* 前置通知
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知···");
}
/**
* 通知后置
*/
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("后置通知···");
}
/**
* 环绕通知
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("环绕前···");
Object value = mi.proceed();
System.out.println("环绕后···");
return value;
}
}
配置
<!-- 经典的基于代理的aop开发 -->
<import resource="aop01.xml"/>
<!-- 目标target -->
<bean id="userService" class="cn.ade.service.impl.UserServiceImpl"></bean>
<!-- 通知advice -->
<bean id="userServiceAdvice" class="cn.ade.advice.UserHelper"></bean>
<!-- 切入点pointcut -->
<bean id="userServicePointCut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<!-- 连接点 join point -->
<value>login</value>
<value>register</value>
</list>
</property>
</bean>
<!-- 切面:通知+切点 -->
<bean id="userServiceAspect" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<!-- 通知 -->
<property name="advice" ref="userServiceAdvice"/>
<!-- 切点 -->
<property name="pointcut" ref="userServicePointCut"/>
</bean>
<!-- 手动代理 -->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 目标 -->
<property name="target" ref="userService"/>
<!-- 切面 -->
<property name="interceptorNames" value="userServiceAspect"/>
<!-- 代理接口:有接口写接口,没有接口默认去找cjlib动态代理来实现 -->
<property name="proxyInterfaces" value="cn.ade.service.UserService"/>
</bean>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ProxyAopDepTest {
@Resource(name = "userServiceProxy") // 因为aop.xml中配置了两个userService,所以需要指定名称
private UserService userService;
@Test
public void test() {
userService.login();
userService.register();
}
}
基于aspectj切面的经典aop开发
<!-- 目标target -->
<bean id="userService" class="cn.ade.service.impl.UserServiceImpl"/>
<!-- 通知advice -->
<bean id="userServiceAdvice" class="cn.ade.advice.UserHelper"/>
<!--使用aop标签来声明切面和切点-->
<aop:config>
<!--声明切点,也就是声明对哪些方法进行拦截-->
<!-- 切点表达式 -->
<aop:pointcut id="userServicePointCut" expression="execution(* cn.ade.service.UserService.*(..))"/>
<!--声明传统aop的切面,只能包含一个切点和一个增强-->
<aop:advisor advice-ref="userServiceAdvice" pointcut-ref="userServicePointCut"/>
<!--aspectJ框架的切面,可以包含多个切点和增强-->
<!--<aop:aspect></aop:aspect>-->
</aop:config>
spring 整合 aspectj 实现 aop
spring整合aspectj的aop开发支持六种增强:
前置通知
后置通知
环绕通知
异常抛出通知
引介通知
最终通知:不管异常抛出通知是否执行,最终通知都会执行
基于xml方式
- 创建目标target
public class UserServiceImpl implements UserService {
@Override
public void login() {
System.out.println("login···");
}
@Override
public void register() {
System.out.println("register···");
}
}
- 创建通知advice
public class UserHelper {
// 使用前置通知完成日志记录、权限控制
/**
* 无参的前置通知
*/
public void before() {
System.out.println("前置通知(无参)···");
}
/**
* 有参的前置通知
*/
public void before1(JoinPoint jp) {
System.out.println("拦截的目标类:" + jp.getSignature().getDeclaringTypeName());
System.out.println("拦截的方法名称:" + jp.getSignature().getName());
System.out.println("前置通知(有参)···");
}
/**
* 无参的后置通知
*/
public void afterReturning() {
System.out.println("后置通知(无参)···");
}
/**
* 有参的后置通知
* 注意:需要在配置文件中声明val
*/
public void afterReturning1(JoinPoint jp, Object val) {
// val:目标方法的返回值
System.out.println(val);
System.out.println("后置通知(有参)···");
}
// 应用最多,可以完成日志记录、权限控制、性能监测、事务管理
/**
* 环绕通知
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前···");
Object val = pjp.proceed(); // 执行目标行为
System.out.println("环绕后···");
return val;
}
/**
* 无参的异常抛出通知
*/
public void afterThrowing() {
System.out.println("无参的异常抛出通知···");
}
/**
* 有参的异常抛出通知
* 注意:需要在配置文件中声明ex
*/
public void afterThrowing1(JoinPoint jp, Throwable ex) {
System.out.println(ex); // 接收抛出的异常
System.out.println("有参的异常抛出通知···");
}
// 资源释放
/**
* 无参的最终通知
*/
public void after() {
System.out.println("无参的最终通知···");
}
/**
* 有参的最终通知
*/
public void after1(JoinPoint jp) {
System.out.println(jp.getSignature().getDeclaringTypeName());
System.out.println(jp.getSignature().getName());
System.out.println("有参的最终通知···");
}
}
- 文件配置
<!-- 目标target -->
<bean id="userService" class="cn.ade.service.impl.UserServiceImpl"/>
<!-- 通知advice -->
<bean id="userServiceAdvice2" class="cn.ade.advice.UserHelper2"/>
<!--声明对aop进行配置-->
<!--使用proxy-target-class="true"来确定使用cglib实现代理,false使用proxy代理-->
<aop:config>
<!--设置aspectj框架的切面-->
<aop:aspect ref="userServiceAdvice2">
<!--设置切入点-->
<aop:pointcut id="loginPointCut" expression="execution(* cn.ade.service.UserService.*(..))"/>
<!--设置前置通知(无参)-->
<aop:before method="before" pointcut-ref="loginPointCut"/>
<!--设置前置通知(有参)-->
<aop:before method="before1" pointcut-ref="loginPointCut"/>
<!--设置后置通知(无参)-->
<aop:after-returning method="afterReturning" pointcut-ref="loginPointCut"/>
<!--设置后置通知(有参)-->
<aop:after-returning method="afterReturning1" pointcut-ref="loginPointCut" returning="val"/>
<!--设置环绕通知-->
<aop:around method="around" pointcut-ref="loginPointCut"/>
<!--设置异常抛出通知(无参)-->
<aop:after-throwing method="afterThrowing" pointcut-ref="loginPointCut"/>
<!--设置异常抛出通知(有参)-->
<aop:after-throwing method="afterThrowing1" pointcut-ref="loginPointCut" throwing="ex"/>
<!--设置最终通知(无参)-->
<aop:after method="after" pointcut-ref="loginPointCut"/>
<!--设置最终通知(有参)-->
<aop:after method="after1" pointcut-ref="loginPointCut"/>
</aop:aspect>
</aop:config>
基于annotation方式
- 创建目标target
@Service("customerService")
public class CustomerServiceImpl implements CustomerService {
@Override
public String add() {
System.out.println("add···");
return "返回值:add";
}
@Override
public void show() {
System.out.println("show···");
}
@Override
public void update() {
System.out.println("update···");
}
@Override
public void delete() {
System.out.println("delete···");
}
}
- 创建通知advice
@Component
@Aspect // 声明当前的bean就是一个切面
public class CustomerHelper {
/**
* 前置通知
*
* @param jp
*/
@Before("execution(* cn.ade.service.CustomerService.*(..))")
public void before(JoinPoint jp) {
System.out.println(jp.getSignature().getDeclaringTypeName());
System.out.println(jp.getSignature().getName());
System.out.println("前置通知");
}
/**
* 后置通知
*
* @param jp
* @param value
*/
@AfterReturning(value = "execution(* cn.ade.service.CustomerService.*(..))",returning = "value")
public void afterReturning(JoinPoint jp, Object value) {
System.out.println("目标方法的返回值是:"+value);
System.out.println("后置通知");
}
/**
* 环绕通知
*
* @param pjp
* @return
* @throws Throwable
*/
@Around("execution(* cn.ade.service.CustomerService.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前···");
Object value = pjp.proceed();
System.out.println("环绕后···");
return value;
}
/**
* 异常抛出通知
*
* @param jp
* @param ex
*/
@AfterThrowing(value = "execution(* cn.ade.service.CustomerService.*(..))",throwing = "ex")
public void afterThrowing(JoinPoint jp, Throwable ex) {
System.out.println("抛出的异常:"+ex);
System.out.println("异常抛出通知···");
}
/**
* 最终通知
*/
@After("execution(* cn.ade.service.CustomerService.*(..))")
public void after() {
System.out.println("最终通知");
}
}
- 配置文件
<!--配置注解扫描的位置-->
<context:component-scan base-package="cn.ade"/>
<!--开启aspectj注解自动代理-->
<!--使用proxy-target-class="true"来确定使用cglib实现代理,false使用proxy代理-->
<aop:aspectj-autoproxy proxy-target-class="false" />
- 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AspectJAopDepTest {
@Autowired
private CustomerService customerService;
@Test
public void test01() {
customerService.add();
}
}