Spring:AOP

来自Wikioe
跳到导航 跳到搜索


关于AOP

Aspect Oriented Programming:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的。

  • AOP是OOP的延续。
  • 可以在不修改源码的情况下,对程序进行增强;
  • 应用如:权限校验,日志记录,性能监控,事务控制;

AOP 实现

Spring 的AOP 的底层用到两种代理机制:

  • JDK 动态代理:(实现了接口)使用动态代理创建接口实现类代理对象
  • CGLIB 动态代理:(没有实现接口)使用动态代理创建类的子类代理对象;(在子类中调用父类的方法,来完成增强)
    • 应用的是底层的字节码增强的技术生成当前类的子类对象;

JDK 动态代理

public class MyJDKProxy implements InvocationHandler {
	private UserDao userDao;
	
	public MyJDKProxy(UserDao userDao) {
		this.userDao = userDao;
	}
	
	// 编写工具方法:生成代理:
	public UserDao createProxy() {
		UserDao userDaoProxy = (UserDao)Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
																userDao.getClass().getInterfaces(), this);
		return userDaoProxy;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if("save".equals(method.getName())) {
			System.out.println("权限校验================");
		}
		return method.invoke(userDao, args);
	}
}

CGLIB 动态代理

public class MyCglibProxy implements MethodInterceptor {
	private CustomerDao customerDao;
	
	public MyCglibProxy(CustomerDao customerDao) {
		this.customerDao = customerDao;
	}
	
	// 生成代理的方法:
	public CustomerDao createProxy() {
		// 创建Cglib 的核心类:
		Enhancer enhancer = new Enhancer();
		// 设置父类:
		enhancer.setSuperclass(CustomerDao.class);
		// 设置回调:
		enhancer.setCallback(this);
		// 生成代理:
		CustomerDao customerDaoProxy = (CustomerDao) enhancer.create();
		return customerDaoProxy;
	}
	
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		if("delete".equals(method.getName())) {
			Object obj = methodProxy.invokeSuper(proxy, args);
			System.out.println("日志记录================");
			return obj;
		}
		return methodProxy.invokeSuper(proxy, args);
	}
}

关于AspectJ

AspectJ,(Eclipse AspectJ)是一个易用的功能强大的AOP框架;可以单独使用(需要专门的编译器“ajc”),也可以整合到其它框架中。

  • 官网:“http://www.eclipse.org/aspectj/”;
  • AspectJ是AOP编程的完全解决方案,可以做Spring AOP干不了的事情;
  • AspectJ不是spring一部分,和spring一起使用进行aop操作(Spring2.0以后新增了对AspectJ支持);

关于织入

AOP编程,存在三种织入方式:

  1. 编译期织入(Compile-time weaving):(编译期、编译后)在Java编译期,采用特殊的编译器,将切面织入到Java类中;
    • 编译后:对已经生成的“.class”文件、“jar”包进行织入;
  2. 类加载期织入(Load-time weaving):通过特殊的类加载器,在类字节码加载到JVM时,织入切面;
    • 或在JVM启动的时候指定 AspectJ 提供的“agent:-javaagent:xxx/xxx/aspectjweaver.jar”;【?】
  3. 运行期织入:采用CGLib工具JDK动态代理进行切面的织入;

AspectJ 与 Spring AOP

Spring AOP 和 AspectJ 之间的关键区别:

AspectJ Spring AOP
静态织入
  • 支持编译时、编译后和加载时织入
运行期织入
支持所有切入点
  • (即:可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等编织)
仅支持方法执行切入点
  • (即:仅支持方法级编织)
使用 Java 编程语言的扩展实现
  • 除非设置LTW(Load Time Weaving,加载时织入),否则需要 AspectJ 编译器 (ajc)
在纯 Java 中实现
  • (不需要单独的编译过程)
更好的性能 较 AspectJ 慢
  • Spring AOP使用了Aspect的Annotation,但是并没有使用它的编译器和织入器;
    “@Before”、“@After”等是Aspect的Annotation;
  • AspectJ在实际运行之前就完成了织入,所以其生成的类没有额外运行时开销。

Spring:AOP

相关术语

  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。【可以被切入的方法】
    • 在 Spring 中,这些点指的是方法,因为,Spring 只支持方法类型的连接点;
  • Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint 进行拦截的定义;【用来切入的方法】
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint 之后所要做的事情就是通知;【切面处理】
    1. “前置通知”:;
    2. “后置通知”:;
    3. “异常通知”:;
    4. “最终通知”:无论程序是否正常执行,最终通知的代码会得到执行;
    5. “环绕通知”:;
  • Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field;
  • Target(目标对象):代理的目标对象;
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程;【应用增强生成代理对象】
    • spring采用“动态代理织入”,而AspectJ采用“编译期织入”和“类装载期织入”;【?】
  • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类;
  • Aspect(切面): 是 Pointcut(切入点)和 Advice(增强)/Introduction(引介)的结合;

jar包

spring 的传统 AOP 的开发的包:
   “spring-aop-4.2.4.RELEASE.jar”
   “com.springsource.org.aopalliance-1.0.0.jar”

aspectJ 的开发包:
   “com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar”
   “spring-aspects-4.2.4.RELEASE.jar”

配置文件

需要在“applicationContext.xml”中引入 AOP 相关约束:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   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/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

切入点表达式

注意:Spring 中,切入点是“方法”,而非“类”


execution([方法访问修饰符] <方法返回类型> <包名.类名.方法名>(<方法的参数>) [异常])

定义切点:

  1. 通过方法签名
    示例:
    execution(public * *(..)):匹配所有目标类的 public方法;
    execution(* *To(..)):匹配目标类所有以To为后缀的方法;
  2. 通过
    示例:
    execution(* com.baobaotao.Waiter.*(..)):匹配Waiter接口的所有方法;
    execution(* com.baobaotao.Waiter+.*(..)):匹配Waiter接口及其所有实现类的方法;
  3. 通过类包
    • “.”:表示包下的所有类;
    • “..”:表示包、子孙包下的所有类;
    示例:
    execution(* com.baobaotao.*(..)):匹配com.baobaotao包下所有类的所有方法;
    execution(* com.baobaotao..*(..)):匹配com.baobaotao包、子孙包下所有类的所有方法;
    execution(* com...*Dao.find(..)):匹配:前缀为com的任何包下,后缀为Dao的任何类中,以find为前缀的方法;
  4. 通过方法入参
    • “”:表示任意类型的参数;
    • “..”:表示任意类型、任意个数的参数;
    • 如果参数类型是“java.lang”包下的类,可以直接使用类名;否则必须使用全限定类名;
    示例:
    execution(* joke(String,int))):匹配第一参数是 String,第二个参数是 int 的 joke()方法;
    execution(* joke(String,*))):
    execution(* joke(String,..))):
    execution(* joke(Object+))):匹配参数是“Object”或“Object”子类 的 joke()方法;

基于AspectJ:XML

步骤:

  1. 配置Bean对象;
  2. 配置aop操作:
    1. 配置切入点;
    2. 配置切面;(AspectJ)


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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	
	<!-- 1  配置对象 -->
	<bean id="book" class="cn.itcast.aop.Book"></bean>
	<bean id="myBook" class="cn.itcast.aop.MyBook"></bean>
	
	<!-- 2 配置aop操作 -->
	<aop:config>
		<!-- 2.1 配置切入点 -->
		<aop:pointcut expression="execution(* cn.itcast.aop.Book.*(..))" id="pointcut1"/>
		
		<!-- 2.2 配置切面 -->
		<aop:aspect ref="myBook">
			<!-- 配置增强(“method”:增强;“pointcut-ref”:切入点)-->
			<aop:before method="before1" pointcut-ref="pointcut1"/>
			
			<aop:after-returning method="after1" pointcut-ref="pointcut1"/>
			
			<aop:around method="around1" pointcut-ref="pointcut1"/>
		</aop:aspect>
	</aop:config>
</beans>
  1. 切入点“execution(* cn.itcast.aop.Book.*(..))”匹配到“book”类中的所有方法;(切入点是方法,而非类)
  2. 增强类“myBook”的“before1”、“after1”、“around1”分别被织入到切入点的前、后、环绕;

增强类示例:

public class MyBook {

	public void before1() {
		System.out.println("前置增强......");
	}
	
	public void after1() {
		System.out.println("后置增强......");
	}
	
	//环绕通知
	public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		//方法之前
		System.out.println("方法之前.....");
		
		//执行被增强的方法
		proceedingJoinPoint.proceed();
		
		//方法之后
		System.out.println("方法之后.....");
	}
}
  1. “环绕通知”中,调用执行切入点;(切入点是方法,而非类)

基于AspectJ:注解

步骤:

  1. 创建对象;
  2. 开启aop操作(“applicationContext.xml”中);
    	<!-- 1  配置对象 -->
    	<bean id="book" class="cn.itcast.aop.Book"></bean>
    	<bean id="myBook" class="cn.itcast.aop.MyBook"></bean>
        
       <!-- 2 开启aop操作 -->
    	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  3. 在增强类中使用注解;
    1. @Aspect”:注解增强类;
    2. @Before”、“@After”、“@Around”、“@AfterReturning”、“@AfterThrowing”:注解增强;
      • 需要配置“切入点表达式”(value属性中);
    @Aspect
    public class MyBook {
    
    	//在方法上面使用注解完成增强配置
    	@Before(value="execution(* cn.itcast.aop.Book.*(..))")
    	public void before1() {
    		System.out.println("before..............");
    	}
    }
    

事务增强

	<!-- dataSource -->
    ...

	<!-- 第一步 配置事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入dataSource -->
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 第二步 配置事务增强 -->
	<tx:advice id="txadvice" transaction-manager="transactionManager">
		<!-- 做事务操作 -->
		<tx:attributes>
			<!-- 设置进行事务操作的方法匹配规则  -->
			<tx:method name="account*" propagation="REQUIRED"/>
			<!-- <tx:method name="insert*" /> -->
		</tx:attributes>
	</tx:advice>
	
	<!-- 第三步 配置切面 -->
	<aop:config>
		<!-- 切入点 -->
		<aop:pointcut expression="execution(* cn.itcast.service.OrdersService.*(..))" id="pointcut1"/>
		<!-- 切面 -->
		<aop:advisor advice-ref="txadvice" pointcut-ref="pointcut1"/>
	</aop:config>