springAOP

Spring的AOP实现原理

动态代理

  • JDK动态代理
    只能对实现了接口的类产生代理
  • Cglib动态代理
    类似于javassist第三方代理技术,对没有实现接口的类产生代理对象,生成子类对象

相关术语

class UserDao{
    public void save(){};
    public void find(){};
    public void delete(){}
}
  • JoinPoint: 连接点,也就是可以被连接的的的点,比如上面代码的几个方法都是可以被增强的,这些方法也就是叫做连接点。
  • Pointcut:切入点,也就是真正被拦截的点,上面的连接点可以说是理论上,而pointcut就是则是实际中的,比如对save方法进行增强那么save就可以被称为切入点。
  • advice: 通知,也可以说是增强,比如现在在save方法之前需要进行权限校验,那么权限校验的那个方法就是通知。
  • target:被增强的对象,比如上面代码中是对UserDao进行增强,因此UserDao就是目标。
  • weaving:织入,将通知(advice)应用到(target)的过程就是织入
  • aspect:切面,多个通知和多个切入点结合就是切面。

入门案例

所必需jar包

aopalliance-1.0   	  spring的aop只是实现了别人的定好的规范,所以需要这个包
aspectjweaver-1.8.0   spring-aop通过aspectj来实现的,所以需要这个包
spring-aop
spring-aspects    spring与aspectj整合的包
spring-expression
spring-beans
spring-context
spring-core

配置文件

约束文件

spring-framework-4.3.9.RELEASE\docs\spring-framework-reference\html\xsd-configuration.html文件里面的aop schema那部分

spring整合Junit

此时还需要添加一个jar包:spring-test

案例代码

接口

package top.twolovelypig.demo;
public interface UserDao {
	public void save();
	public void find();
	public void delete();
	public void update();
}

实现类

package top.twolovelypig.demo;
public class UserDaoImpl implements UserDao {
	@Override
	public void save() {
		System.out.println("保存");
	}
	//省略了另外几个方法
}

配置文件

<?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">

	<bean id="userDao" class="top.twolovelypig.demo.UserDaoImpl"></bean>
</beans>

测试代码

package top.twolovelypig.demo;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestDemo {

	@Resource(name="userDao")
	private UserDao UserDao;
	@Test
	public void test() {
		UserDao.delete();
	}	
}

平时想要获取bean需要从context容器中获取,但是对于使用了junit测试可以使用注解来完成这个一步,注意点如下:

  • 在类上面需要使用@RunWith(SpringJUnit4ClassRunner.class),这是固定的
  • @ContextConfiguration("classpath:applicationContext.xml")这个注解是用来加载配置文件的,上面的案例中的配置文件是在classpath下(src目录下)。
  • 在类上面使用了上面两个注解之后可以在对象上可以使用@Resource注解即可使用该对象。

通过xml形式配置aop

切面代码

package top.twolovelypig.demo;
public class MyAspect {
	public void check() {
		System.out.println("校验");
	}
}

此时的配置文件

<?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">

	<bean id="userDao" class="top.twolovelypig.demo.UserDaoImpl"></bean>
	<!-- 将切面类交给spring管理 -->
	<bean id="myAspect" class="top.twolovelypig.demo.MyAspect"></bean>
	
	<!--配置切面:通过aop的配置完成对目标类完成代理  -->
	<aop:config>
		<!--切入点的配置,id是任意值,表达式配置的是拿些类那些方法需要增强  -->
		<aop:pointcut expression="execution(* top.twolovelypig.demo.UserDaoImpl.save(..))" id="checkSave"/>
		
		<!-- 切面的配置: ref中的值是配置的切面类的bean的id -->
		<aop:aspect ref="myAspect">
			<!-- 通知的配置,method是配置的切面类里面的方法,pointcut-ref里面的值是切点的id -->
			<aop:before method="check" pointcut-ref="checkSave"/>
		</aop:aspect>
	</aop:config>
</beans>

此时在测试代码岁执行userDao.save()时就会先执行切面类的check()方法。

spring通知类型

前置通知

  • 可以获得切入点的信息(在切面的方法里面传入JoinPoint参数即可)
  • 上面的案例中就是使用的前置通知(aop:before)

后置通知

  • 可以获取切入点的信息
  • 可以获取方法的返回值

具体配置如下

配置文件

<?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">

	<bean id="userDao" class="top.twolovelypig.demo.UserDaoImpl"></bean>
	<!-- 将切面类交给spring管理 -->
	<bean id="myAspect" class="top.twolovelypig.demo.MyAspect"></bean>
	
	<!--配置切面:通过aop的配置完成对目标类完成代理  -->
	<aop:config>
		<!--切入点的配置,id是任意值,表达式配置的是拿些类那些方法需要增强  -->
		<aop:pointcut expression="execution(* top.twolovelypig.demo.UserDaoImpl.save(..))" id="checkSave"/>
		<!-- 配置后置通知的切点信息  -->
		<aop:pointcut expression="execution(* top.twolovelypig.demo.UserDaoImpl.delete(..))" id="checkAfter"/>
		
		<!-- 切面的配置: ref中的值是配置的切面类的bean的id -->
		<aop:aspect ref="myAspect">
			<!-- 通知的配置,method是配置的切面类里面的方法,pointcut-ref里面的值是切点的id -->
			<aop:before method="check" pointcut-ref="checkSave"/>
			<!-- 配置后置通知的切面使用的是after-returing而不是after -->
			<aop:after-returning method="afterCheck" pointcut-ref="checkAfter" returning="result"/>
		</aop:aspect>
	</aop:config>
</beans>

需要注意的是后置通知使用使用的是after-returing而不是after,在后置通知中返回值配置时通过returning=“rsult”,这里的result是可以随意写的一个字符串,不一定就是result

切面类

package top.twolovelypig.demo;
public class MyAspect {

	/**
	 * 前置通知的方法
	 */
	public void check() {
		System.out.println("校验");
	}
	
	/**
	 * 后置通知的方法
	 * @param result
	 */
	public void afterCheck(Object result) {
		System.out.println("后置通知");
		System.out.println(result);
	}
}

切面类里面的这个后置通知方法当中有一个参数是Objcet类型,此值可以接受任意类型的返回值,不过需要注意的是形参在这里必须是result,因为在配置文件当中配置的是result,也就是说这两个位置的值必须是一致的。

环绕通知

环绕通知可以控制切点方法的执行与否,因为环绕通知在切点方法前后都需要执行,所以能够控制切点方法是否执行。

配置文件信息

<!--配置切面:通过aop的配置完成对目标类完成代理  -->
	<aop:config>
		<!--切入点的配置,id是任意值,表达式配置的是拿些类那些方法需要增强  -->
		<aop:pointcut expression="execution(* top.twolovelypig.demo.UserDaoImpl.save(..))" id="checkSave"/>
		<!-- 配置后置通知的切点信息  -->
		<aop:pointcut expression="execution(* top.twolovelypig.demo.UserDaoImpl.delete(..))" id="checkAfter"/>
		<!-- 配置环绕通知的切点信息 -->
		<aop:pointcut expression="execution(* top.twolovelypig.demo.UserDaoImpl.update(..))" id="checkAround"/>
		
		<!-- 切面的配置: ref中的值是配置的切面类的bean的id -->
		<aop:aspect ref="myAspect">
			<!-- 通知的配置,method是配置的切面类里面的方法,pointcut-ref里面的值是切点的id -->
			<aop:before method="check" pointcut-ref="checkSave"/>
			<!-- 配置后置通知的切面使用的是after-returing而不是after -->
			<aop:after-returning method="afterCheck" pointcut-ref="checkAfter" returning="result"/>
			<!-- 配置环绕通知 -->
			<aop:around method="arround" pointcut-ref="checkAround"/>
		</aop:aspect>
	</aop:config>

切面代码

/**
 * 环绕通知
 * @param proceedingJoinPoint
 * @throws Throwable 
 */
public Object arround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		System.out.println("前置通知");
		//这一句就是用来控制目标程序的执行与否,在此之前执行的相当于是前置通知,在此之后执行的是后置通知,目标程序可能有返回值,所以需要使用Object来接收
		Object object = proceedingJoinPoint.proceed();
		System.out.println("后置通知");
    	return object;
	}

异常抛出通知

配置文件

<!--配置切面:通过aop的配置完成对目标类完成代理  -->
	<aop:config>
		<!--切入点的配置,id是任意值,表达式配置的是拿些类那些方法需要增强  -->
		<aop:pointcut expression="execution(* top.twolovelypig.demo.UserDaoImpl.save(..))" id="checkSave"/>
		<!-- 配置后置通知的切点信息  -->
		<aop:pointcut expression="execution(* top.twolovelypig.demo.UserDaoImpl.delete(..))" id="checkAfter"/>
		<!-- 配置环绕通知的切点信息 -->
		<aop:pointcut expression="execution(* top.twolovelypig.demo.UserDaoImpl.update(..))" id="checkAround"/>
		<!-- 配置异常通知的切点信息 -->
		<aop:pointcut expression="execution(* top.twolovelypig.demo.UserDaoImpl.find(..))" id="checkException"/>
		
		<!-- 切面的配置: ref中的值是配置的切面类的bean的id -->
		<aop:aspect ref="myAspect">
			<!-- 通知的配置,method是配置的切面类里面的方法,pointcut-ref里面的值是切点的id -->
			<aop:before method="check" pointcut-ref="checkSave"/>
			<!-- 配置后置通知的切面使用的是after-returing而不是after -->
			<aop:after-returning method="afterCheck" pointcut-ref="checkAfter" returning="result"/>
			<!-- 配置环绕通知 -->
			<aop:around method="arround" pointcut-ref="checkAround"/>
			<!-- 配置异常通知,异常通知是可以获取异常的信息的 -->
			<aop:after-throwing method="afterThrowing" pointcut-ref="checkException" throwing="ex"/>
		</aop:aspect>
	</aop:config>

切面代码

/**
 * 异常通知
 * @param ex
 */
public void afterThrowing(Throwable ex) {
	System.out.println("异常通知"+ ex.getMessage());
}

在上面的配置文件当中配置了一个throwing=“ex”,这个参数是为了获取异常的信息来配置的,至于里面的ex是可以随意取的,但是必须与切面类里面的形参保持一致。

最终通知

无论代码是否有异常,里面的代码总是会执行,最终通知配置的时候使用的是after

spring的切入点表达式的写法

语法格式

  • 基于execution的函数完成的
  • [访问修饰符] 方法返回值 包名.类名.方法名(参数)
    • public void top.twolovelypig.UserDao.save(..) 最后面方法里面的两个点表示是任意形参
    • * *.*.*.Dao.save(..) 这里表示的是任意返回值,要三层包,类名以Dao结尾的所有save()方法(方法参数任意),也就是execution表达式是可以使用通配符的。

spring-aop注解形式

spring的基于aspectJ的注解形式的aop开发

引入的jar包

使用spring基本的6个jar包
beans包
expression包
core包
context包
common.logging日志包
log4j包

aop开发的jar包
aop包
aspectj包
aspect.weaver包
aopaliance包(定义aop规范的包)

配置文件

<?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"
	xmlns:tx="http://www.springframework.org/schema/tx"
	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
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx.xsd">
        
	<!-- 在配置文件中开启注解的aop的开发 -->        
	<aop:aspectj-autoproxy />
        
	<bean id="userDao" class="top.twolovelypig.demo.UserDao"></bean>
	
	<bean id="myAspect" class="top.twolovelypig.demo.MyAspetAnno"></bean>
        
</beans>

配置文件头部依然是可以使用aop的,有一个需要注意的点就是在配置文件中需要开启注解形式的aop的开发。也就是这一句 <aop:aspectj-autoproxy />

操作类

业务类(没有接口)

这里的也是类之所以不创建接口时为了使用cglib形式的代理,如果业务类是含有接口的那么就会使用jdk动态代理

package top.twolovelypig.demo;
public class UserDao {
	public void save() {
		System.out.println("保存数据");
	}
	public void delete() {
		System.out.println("删除数据");
	}
	public void update() {
		System.out.println("更新数据");
	}
	public void find() {
		System.out.println("查找数据");
	}
}

切面类
package top.twolovelypig.demo;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * 切面类
 * @author lg
 */
@Aspect
public class MyAspetAnno {

	@Before(value="execution(* top.twolovelypig.demo.UserDao.save(..))")
	public void before() {
		System.out.println("前置通知");
	}
}

在切面类中需要注意以下几点:

  • 类中需要使用@Aspect注解。该注解标识该类是一个切面类。

  • 对于通知需要使用@Before(前置通知),@AfterReturning(后置通知)等类标识通知类型,在这些注解后面的括号里面的value写上execution表达式,对于后置通知有返回值时可以按照如下方式来写:

    @AfterReturning(value="execution(* top.twolovelypig.demo.UserDao.update(..))", returning="result")
    public void afterReturning(Object result) {
        System.out.println("后置通知"+result);
    }
    
    

    此时需要注意的是returning里面的值必须与后置通知方法里面的形参一致。

    对于异常通知可以获取异常信息,此时可以按照如下方式来写:

    @AfterThrowing(value="execution(* top.twolovelypig.demo.UserDao.delete(..))", throwing="e")
    public void afterThrowing(Throwable e) {
        System.out.println("后置通知"+e.getMessage());
    }
    
    

    此时需要注意的是注解里面的throwing=“e”这里的e必须与异常通知方法里面的形参名称保持一致

    对于环绕通知是可以掌握切入点方法的执行与否,具体配置如下:

    @Around(value="execution(* top.twolovelypig.demo.UserDao.find(..))")
    public Object around(ProceedingJoinPoint joinPoint) {
        System.out.println("在切点前面执行的相当于是前置通知");
        Object object = joinPoint.proceed();//有这一句就相当于是切点方法执行
        System.out.println("在切点后面执行的相当于是后置通知");
        return object;
    }
    
    

    此时需要注意的是对于环绕通知方法是含有返回值Object的,因为环绕通知需要控制切入点方法的执行,切点方法可能有返回值,所以需要使用object类接收并返回

测试类
package top.twolovelypig.demo;

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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestDemo {
	
	@Resource(name="userDao")
	private UserDao userDao;
	
	@Test
	public void testAspectJ() {
		userDao.save();
		userDao.delete();
		userDao.update();
		userDao.find();
	}
}

spring的aop注解切入点的配置

在使用注解配置切面方法的时候都是向下面这样的方式来配置的。

@Before(value="execution(* top.twolovelypig.demo.UserDao.save(..))")
public void before() {
    System.out.println("前置通知");
}

如果切面很好还好,但是如果很多的话就比较麻烦,所以可以先将切点配置出来,然后在切面里面直接引用即可。具体配置如下:

package top.twolovelypig.demo;

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

/**
 * 切面类
 * @author lg
 */
@Aspect
public class MyAspetAnno {

	@Before(value="MyAspetAnno.pointCut1()")
	public void before() {
		System.out.println("前置通知");
	}
	
	@AfterReturning(value="MyAspetAnno.pointCut2()")
	public void afterReturning() {
		System.out.println("后置通知");
	}
	
	@Pointcut(value="execution(* top.twolovelypig.demo.UserDao.delete(..))")
	private void pointCut1() {}
	
	@Pointcut(value="execution(* top.twolovelypig.demo.UserDao.save(..))")
	private void pointCut2() {}
}

可以看到此时切面方法里面配置的execution可以提取出来单独配置,然后在切面方法里面去引用即可,至于引用方法则是类名.方法名

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值