01-SpringAOP

AOP

Aspect Oriented Programming:面向切面编程

什么时候会出现面向切面编程的需求?

按照软件重构的思想,如果多个类中出现重复的代码,就应该考虑定义一个共同的抽象类,将这些共同的代码提取到抽象类中,比如Teacher,Student都有username,那么就可以把username及相关的get、set方法抽取到SysUser中,这种情况,我们称为纵向抽取。

但是如果,我们的情况是以下情况,又该怎么办?
给所有的类方法添加性能检测,事务控制,该怎么抽取?
PerformanceMonitor
TransactionManager

AOP就是希望将这些分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,让业务逻辑类依然保存最初的单纯。

抽取出来简单,难点就是如何将这些独立的逻辑融合到业务逻辑中,完成跟原来一样的业务逻辑,这就是AOP解决的主要问题。

AOP术语

  • 连接点(Joinpoint)
    • 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强
    • 三个连接点
      • 方法执行之前
      • 方法执行之后
      • 方法出异常
  • 切点(PointCut)
    • 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。
    • 比如,连接点–数据库的记录,切点–查询条件
    • 链接点有很多个,要切入点的点就是切点
  • 增强(Advice)//通知(前置通知,后置通知,环绕通知)
    • 增强是织入到目标类连接点上的一段程序代码。
      在Spring中,像BeforeAdvice等还带有方位信息
  • 目标对象(Target)
    • 需要被加强的业务对象
  • 织入(Weaving)
    • 织入就是将增强添加到对目标类具体连接点上的过程。
  • 代理类(Proxy)
    • 一个类被AOP织入增强后,就产生了一个代理类。
  • 切面(Aspect)
    • 切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。
    • 定义一个范围 给哪些类的哪些方法
    • 增强:加事务的控制

AOP实现者

  • AspectJ
    • AspectJ是语言级的AOP实现,2001发布,扩展了Java语言,定义了AOP语法,能够在编译期通过提供横切代码的织入,所以它有一个专门的编译器用来生成遵守Java字节码规范的class文件
  • SpringAOP
    • SpringAOP使用纯Java实现,在运行期通过代理的方式向目标类织入增强代码。

代理,目标类,增强代码

SpringAOP

SpringAOP代理机制

基于JDK 的动态代理,接口
基于CGlib的动态代理,类

可以通过打印代理类的class看出是利用了什么代理

  • 如果包含enhance就是CGlib
  • 如果包含sun就是jdk
创建增强(通知)

Spring使用增强类定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括了在方法的哪一点加入横切代码的方位信息,所以增强既包含横切逻辑,还包含部分连接点的信息

增强类型

AOP联盟为增强定义了org.aopalliance.aop.Advice接口,Spring支持5种类型的增强(通知)

  • 前置增强:org.springframework.aop.MethodBeforeAdvice代表前置增强,因为Spring只支持方法级的增强,所以MethodBeforeAdvice是目前可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定
  • 后置增强:org.springframework.aop.AfterReturningAdvice代表后增强,表示在目标方法执行后实施增强;
  • 环绕增强:org.aopalliance.intercept.MethodInterceptor代表环绕增强,表示在目标方法执行前后实施增强;
  • 异常抛出增强:org.springframework.aop.ThrowsAdvice代表抛出异常增强,表示在目标方法抛出异常后实施增强;
基本代码

增强的方法

package com.tamakiakoo.advices;

public class TimeManager {

	public void start() {
		System.out.println("方法调用之前记录时间");
	}

	public void end() {
		System.out.println("方法调用之后记录时间");
	}
}

package com.tamakiakoo.advices;

public class TransactionManager {

	public void begin(){
		System.out.println("开启事务");
	}
	public void commit(){
		System.out.println("提交事务");
	}
}

我们先创建一个接口,IUserService

public interface IUserService {

	public void add();
	public void update();
}

创建一个接口的实现类

public class UserServiceImpl implements IUserService {
	
	@Override
	public void add() {
		System.out.println("UserServiceImpl.add()");
	}

	@Override
	public void update() {
		System.out.println("UserServiceImpl.update()");
		
	}


}

AOP联盟的使用

导包

创建一个代理类

前置增强:org.springframework.aop. MethodBeforeAdvice
后置增强:org.springframework.aop.AfterReturningAdvice

package com.tamakiakoo.advices;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;

/**
 * 前置增强
 * @author dqk
 *
 */
public class MyMethodBeforeAdvice implements MethodBeforeAdvice,AfterReturningAdvice {
	private TimeManager tm;
	private TransactionManager tx;
	
	public MyMethodBeforeAdvice(TransactionManager tx,TimeManager tm){
		this.tx = tx;
		this.tm = tm;
	}

	@Override
	public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
		tm.start();
		tx.begin();
	}

	@Override
	public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
		tx.commit();
		tm.end();
		
		
	}

}

测试类

package com.tamakiakoo.service.aop;

import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;

import com.tamakiakoo.advices.MyMethodBeforeAdvice;
import com.tamakiakoo.advices.TimeManager;
import com.tamakiakoo.advices.TransactionManager;
import com.tamakiakoo.service.IUserService;
import com.tamakiakoo.service.impl.UserServiceImpl;

public class AOPTest {

	@Test
	public void test(){

		// 1.创建目标对象
		IUserService userService = new UserServiceImpl();

		// 2.增强
		TransactionManager tx = new TransactionManager();
		TimeManager tm = new TimeManager();
		// 3.创建代理类
		ProxyFactory factory = new ProxyFactory();
		// 3.1.设置目标对象
		factory.setTarget(userService);
		// 3.2.添加增强
		factory.addAdvice(new MyMethodBeforeAdvice(tx, tm));
		//创建代类对象
		IUserService proxy = (IUserService)factory.getProxy();
		
		proxy.add();
		System.out.println("===========================");
		proxy.update();
	}
	
}

控制台

方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间
===========================
方法调用之前记录时间
开启事务
UserServiceImpl.update()
提交事务
方法调用之后记录时间

环绕增强:org.aopalliance.intercept.MethodInterceptor

在接口中增加如下方法

	public String query(String name);

在UserServiceImpl增加方法

@Override
	public String query(String name) {
		System.out.println("UserServiceImpl.query()====name:"+name);
		return "hello";
	}

MyMethodInterceptor

package com.qf.advices;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * 环绕通知
 * @author Windows
 *
 */
public class MyMethodInterceptor implements MethodInterceptor{

	@Override
	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		System.out.println("环绕开始。。。。。");
		Object result = methodInvocation.proceed(); // 往下调用
		System.out.println("环绕结束");
		return result;
	}

}

测试类(可以同时添加多个增强)

@Test
	public void test2(){
		
		TransactionManager tx = new TransactionManager();
		TimeManager tm = new TimeManager();
		
		
		ProxyFactory proxyFactory = new ProxyFactory();
		
		proxyFactory.setTarget(new UserServiceImpl());
		
		proxyFactory.addAdvice(new MyMethodBeforeAdvice(tx, tm));
		proxyFactory.addAdvice(new MyMethodInterceptor());
		
		UserServiceImpl userServiceImpl = (UserServiceImpl)proxyFactory.getProxy();
		userServiceImpl.add();
		System.out.println("==============================");
		userServiceImpl.update();
		System.out.println("=========================");
		String query = userServiceImpl.query("zs");
		System.out.println(query);
	}

控制台

这里的顺序和

proxyFactory.addAdvice(new MyMethodBeforeAdvice(tx, tm));
proxyFactory.addAdvice(new MyMethodInterceptor());

的先后有关

方法调用之前记录时间
开启事务
方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间
提交事务
方法调用之后记录时间
==============================
方法调用之前记录时间
开启事务
方法调用之前记录时间
开启事务
UserServiceImpl.update()
提交事务
方法调用之后记录时间
提交事务
方法调用之后记录时间
=========================
方法调用之前记录时间
开启事务
方法调用之前记录时间
开启事务
UserServiceImpl.query()====name:zs
提交事务
方法调用之后记录时间
提交事务
方法调用之后记录时间
hello
异常抛出增强:org.springframework.aop.ThrowsAdvice
package com.tamakiakoo.advices;

import java.lang.reflect.Method;

import org.springframework.aop.ThrowsAdvice;

/**
 * 抛出异常增强,一般都用做资源的关闭或者事务回滚
 * @author Windows
 *
 */
public class MyThrowsAdvice implements ThrowsAdvice {
	
	
	public void afterThrowing(Method method, Object[] args, Object target, Exception e) throws Throwable {
		System.err.println("抛出异常通知");
		System.out.println("method=" + method.getName());
		System.out.println("抛出异常:" + e.getMessage());
		System.out.println("成功回滚事务");
	}
}
总结

增强(通知)

  • 由AOP联盟提供的常见的增强
    • 前置增强
    • 后置增强
    • 环绕增强
    • 抛出异常增强
  • 如何实现
    • 定义自己的增强
    • 通过ProxyFactory创建代理对象
      • 设置目标对象
      • 设置增强
      • 创建代理类

创建切面

我们可能注意到一个问题:增强被织入到目标类的所有方法中,假设我们希望有选择地织入到目标类某些特定的方法中,就需要使用切点进行目标连接点的定位了。描述连接点是进行AOP编程最主要的工作

增强提供了连接点方位信息:如织入到方法前面、后面等,而切点进一步描述织入到哪些类的哪些方法上。

Spring通过org.springframework.aop.Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的某些特定方法的能力。

Spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含横切代码和连接点信息
从广义上说,增强其实就是一种最简单的切面,它既包括横切代码也包括切点信息,只不过它的切点只是简单的方法相对位置的信息。所以增强一般需要和切点联合才可以表示一个更具实用性的切面。

什么是切面???
包含两个关键信息(切点+增强)

AspectJ(切面)

Spring AOP,它包括基于XML配置的AOP和基于@AspcetJ注解的AOP,这两种方法虽然在配置切面时的表现方式不同,但底层都是采用动态代理技术(JDK代理或CGLib代理)。Spring可以集成AspectJ,但AspectJ本身并不属于Spring AOP的范畴。

Spring在处理@Aspect注解表达式时,需要将Spring的asm模块添加到类路径中。asm是轻量级的字节码处理框架,因为Java的反射机制无法获取入参名,Spring就利用asm处理@AspectJ中所描述的方法入参名。

AspectJ-基础语法

…注解说明
@Before前置增强,相当MethodBeforeAdvice的功能,比如@Around(value=“execution(public * add(…))”)只有匹配了表达式的方法才会增强
@AfterReturning后置增强,相当于AfterReturningAdvice,目标方法中出现异常不执行
@Around环绕增强,相当于MethodInterceptor
@AfterThrowing抛出增强,相当于ThrowsAdvice
@AfterFinal增强,不管是抛出异常或者是正常退出,该增强都会得到执行,该增强没有对应的增强接口,可以把它看成ThrowsAdvice和AfterReturningAdvice的混合物,一般用于释放资源,相当于try{}finally{}
@Pointcut切入点表达式
@Aspect说明这是一个切面类

定义多个切入点

@Pointcut(value="execution(* com.qf.service.IUserService.add(..))")
public void p1(){
}

@Pointcut(value="execution(* com.qf.service.IUserService.update(..))")
public void p2(){
}

@Before(value="p1() || p2()") 
public void begin(){
	tm.start();
	tx.begin();
}
切点表达式函数

AspectJ 5的切点表达式由关键字和操作参数组成,如execution(* add(..))的切点表达式,execution为关键字,而“* add(..)”为操作参数。

常见案例说明
execution(public * *(..))匹配所有目标类的public方法,第一个代表返回类型;第二个代表方法名;而…代表任意入参的方法;
execution(* *To(..))匹配目标类所有以To为后缀的方法。第一个代表返回类型;而To代表任意以To为后缀的方法。
execution(* com.dream.IUserDao.*(..))匹配IUserDao接口的所有方法,第一个*代表返回任意类型;com.dream.IUserDao.*代表IUserDao接口中的所有方法,public可以不写默认就是public
execution(*com.dream.*(..))匹配com. dream包下所有类的所有方法;

表达式:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

有“?”号的部分表示可省略的,

  • modifers-pattern表示修饰符如public、protected等,
  • ret-type-pattern表示方法返回类型,
  • declaring-type-pattern代表特定的类,
  • name-pattern代表方法名称,
  • param-pattern表示参数,
  • throws-pattern表示抛出的异常。

在切入点表达式中,可以使用*来代表任意字符,用…来表示任意个参数

使用方法

首先导入org. org.aspectj下jar包(并不需要配置注解扫描)

使用注解的方式

MyAspectj类
定义一个切面,add,update需要加增强,query不要

package com.tamakiakoo.aspectj;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import com.tamakiakoo.advices.TimeManager;
import com.tamakiakoo.advices.TransactionManager;

/**
 * 定义一个切面,add,update需要加增强,query不要
 * @author dqk
 *
 */
@Aspect
public class MyAspectj {

	private TimeManager tm = new TimeManager();
	private TransactionManager tx = new TransactionManager();
	
	@Pointcut(value = "execution(* com.tamakiakoo.service.*.add(..))")
	public void p1(){}
	
	@Pointcut(value = "execution(* com.tamakiakoo.service.*.updete(..))")
	public void p2(){}
	
	
	@Before(value = "p1() || p2()")
	public void before(){
		tm.start();
		tx.begin();
	}
	
	
	@After(value = "p1() || p2()")
	public void after(){
		tx.commit();
		tm.end();
	}
}

测试类

package com.tamakiakoo.service.aop;

import org.junit.Test;

import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;

import com.tamakiakoo.aspectj.MyAspectj;
import com.tamakiakoo.service.IUserService;
import com.tamakiakoo.service.impl.UserServiceImpl;

public class AspectjTest {

	@Test
	public void test(){
		// 1.创建目标对象
		IUserService userService = new UserServiceImpl();
		// 3.创建代理类
		AspectJProxyFactory factory = new AspectJProxyFactory();
		// 3.1 设置目标对象
		factory.setTarget(userService);
		// 3.2设置切面
		factory.addAspect(new MyAspectj());
		// 3.3创建代理类
		IUserService proxy = (IUserService)factory.getProxy();
		
		proxy.add();
		System.out.println("======================");
		proxy.update();
		System.out.println("======================");
		proxy.query("11");
		
	}
	
}

控制台

方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间
======================
UserServiceImpl.update()
======================
UserServiceImpl.query()====name:11

使用xml的方式

增加约束

xmlns:aop="http://www.springframework.org/schema/aop"

xsi:schemaLocation="
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop.xsd"

在多个表达式之间使用 ||,or表示 或,使用 &&,and表示 与,!表示 非.例如:

<aop:config>
      <aop:pointcut id="pointcut" expression="(execution(* com.ccboy.dao..*.find*(..))) or (execution(* com.ccboy.dao..*.query*(..)))"/>
      <aop:advisor advice-ref="jdbcInterceptor" pointcut-ref="pointcut" />
  </aop:config>

完整的applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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.xsd
         http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- 1.目标对象 -->
	<bean id="userService" class="com.tamakiakoo.service.impl.UserServiceImpl" />

	<!-- 2.创建增强 -->
	<bean id="tm" class="com.tamakiakoo.advices.TimeManager" />
	<bean id="tx" class="com.tamakiakoo.advices.TransactionManager" />

	<!-- 3.创建切面 -->
	<bean id="myAspectj2" class="com.tamakiakoo.aspectj.MyAspectj2">
		<property name="tm" ref="tm"></property>
		<property name="tx" ref="tx"></property>
	</bean>

	<!-- 4.AOP的配置 -->
	<aop:config>
		<aop:pointcut expression="execution(* com.tamakiakoo.service.*.add(..)) or execution(* com.tamakiakoo.service.*.update(..))" id="p1" />
		<aop:aspect ref="myAspectj2"><!-- 引用的是哪个切面 -->
			<aop:after method="after" pointcut-ref="p1" /><!-- 切点只能是唯一的一个不能出现pointcut-ref="p1,p2" 并且pointcut-ref和pointcut=""不能同时出现 -->
			<aop:before method="before"  pointcut-ref="p1"/>
		</aop:aspect>
	</aop:config>

</beans>
package com.tamakiakoo.service.aop;

import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.tamakiakoo.advices.MyMethodBeforeAdvice;
import com.tamakiakoo.advices.MyMethodInterceptor;
import com.tamakiakoo.advices.TimeManager;
import com.tamakiakoo.advices.TransactionManager;
import com.tamakiakoo.service.IUserService;
import com.tamakiakoo.service.impl.UserServiceImpl;

public class AOPXmlTest {

	@Test
	public void test(){
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		IUserService userService = (IUserService) applicationContext.getBean("userService");
		userService.add();
		System.out.println("===================================");
		userService.update();
		System.out.println("===================================");
		userService.query("heheh..");
	}
}

方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间
===================================
方法调用之前记录时间
开启事务
UserServiceImpl.update()
提交事务
方法调用之后记录时间
===================================
UserServiceImpl.query()====name:heheh..

总结
  • xml的方式配置aop的话 只有匹配了表达式就会自动生成代理对象并不需要设置目标对象
  • aop:config
    • aop:pointcut
      • expression:表达式
        • 在多个表达式之间使用 ||,or表示 或,使用 &&,and表示 与,!表示 非
        • 例如:expression="execution(* com.tamakiakoo.service.*.add(..)) or execution(* com.tamakiakoo.service.*.update(..))"
      • id:表达式的唯一表示
    • <aop:aspect ref=“myAspectj2”> ref引用的是哪个切面
      • ref:引向切面
      • aop:after
        • method:切面中的方法名称
        • pointcut-ref:表达式ID
        • 切点只能是唯一的一个不能出现pointcut-ref=“p1,p2”
        • pointcut-ref和pointcut=""不能同时出现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值