Spring专题(七)-Spring 中AOP的实现

1.AOP相关术语

  • Joinpoint(连接点):
    所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的
    连接点。
  • Pointcut(切入点):
    所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
  • Advice(通知/增强):
    所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
    通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
  • Introduction(引介):
    引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方
    法或 Field。
    Target(目标对象):
    代理的目标对象。
  • Weaving(织入):
    是指把增强应用到目标对象来创建新的代理对象的过程。
    spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
  • Proxy(代理):
    一个类被 AOP 织入增强后,就产生一个结果代理类。
    Aspect(切面):
    是切入点和通知(引介)的结合。
    具体可以根据下面这张图来理解:
    在这里插入图片描述

2.学习 spring 中的AOP要明确的事

a、开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。

b、运行阶段(Spring 框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对
象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

3.关于代理的选择

在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

4.基于XML 的AOP配置

4.1 项目准备

  • 项目名: spring-03-aop
  • 导入jar包:
        
		<dependency>
			<groupId>junitgroupId>
			<artifactId>junitartifactId>
			<version>4.12version>
		dependency>

		<dependency>
			<groupId>aopalliancegroupId>
			<artifactId>aopallianceartifactId>
			<version>1.0version>
		dependency>

		
		<dependency>
			<groupId>org.aspectjgroupId>
			<artifactId>aspectjweaverartifactId>
			<version>1.8.10version>
		dependency>
		
		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-aspectsartifactId>
			<version>4.3.8.RELEASEversion>
		dependency>

		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-aopartifactId>
			<version>4.3.8.RELEASEversion>
		dependency>
		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-context-supportartifactId>
			<version>4.3.8.RELEASEversion>
		dependency>

		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-contextartifactId>
			<version>4.3.8.RELEASEversion>
		dependency>
		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-coreartifactId>
			<version>4.3.8.RELEASEversion>
		dependency>

		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-beansartifactId>
			<version>4.3.8.RELEASEversion>
		dependency>

		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-expressionartifactId>
			<version>4.3.8.RELEASEversion>
		dependency>

		<dependency>
			<groupId>commons-logginggroupId>
			<artifactId>commons-loggingartifactId>
			<version>1.1.2version>
		dependency>

		<dependency>
			<groupId>log4jgroupId>
			<artifactId>log4jartifactId>
			<version>1.2.14version>
		dependency>

		<dependency>
			<groupId>mysqlgroupId>
			<artifactId>mysql-connector-javaartifactId>
			<version>5.1.38version>
		dependency>

		
		<dependency>
  • 引入日志:log4j.properties
log4j.rootLogger=ERROR,A1
log4j.logger.org.mybatis = ERROR
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
  • 创建配置文件:applicationContext-aop.xml
  • 创建测试代码:AopTest.java

4.2 准备操作对象

  • 先创建UserService接口:
public interface UserService {

	// 添加 user
	public void addUser(User user);

	// 删除 user
	public void deleteUser(int uid);
}
  • 实现类
public class UserServiceImpl implements UserService {

	public void addUser(User user) {
		System.out.println("增加 User");
	}

	public void deleteUser(int uid) {
		System.out.println("删除 User");
	}
}

4.3 增强类

public class MyAdivce {

	/**
    //前置通知:目标方法运行之前调用
    //后置通知(如果出现异常不会调用):在目标方法运行之后调用
    //环绕通知:在目标方法之前和之后都调用
    //异常拦截通知:如果出现异常,就会调用
    //后置通知(无论是否出现 异常都会调用):在目标方法运行之后调用
    */

	// 前置通知
	public void before() {
		System.out.println("这是前置通知");
	}

	// 后置通知
	public void afterReturning() {
		System.out.println("这是后置通知(方法不出现异常)");
	}

	public Object around(ProceedingJoinPoint point) throws Throwable {
		System.out.println("这是环绕通知之前部分!!");
		Object object = point.proceed(); // 调用目标方法
		System.out.println("这是环绕通知之后的部分!!");
		return object;
	}

	public void afterException() {
		System.out.println("异常通知!");
	}

	public void after() {
		System.out.println("这也是后置通知,就算方法发生异常也会调用!");
	}
}

4.4 织入目标对象(xml)

在applicationContext-aop.xml 中配置
注意:添加了 aop命名空间


<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 ">
       
	<bean name="userService" class="com.bruceliu.service.UserServiceImpl" />
	
	
	<bean name="myAdvice" class="com.bruceliu.dao.MyAdivce" />
	
	
	<aop:config>
		  
		<aop:pointcut id="pc" expression="execution(* com.bruceliu.service.*ServiceImpl.*(..))" />
		<aop:aspect ref="myAdvice">
			<aop:before method="before" pointcut-ref="pc" />
			<aop:after-returning method="afterReturning" pointcut-ref="pc" />
			<aop:around method="around" pointcut-ref="pc" />
			<aop:after-throwing method="afterException" pointcut-ref="pc" />
			<aop:after method="after" pointcut-ref="pc" />
		aop:aspect>
	aop:config>
beans>

4.5 切入点表达式说明

一个常见的切点表达式如下图:
在这里插入图片描述
访问修饰符可以省略。

表达式以“*”号开始,表明了可以返回任何数据类型。对于参数列表使用两个点号(…),表明切点要选择任意的perform方法,不关心入参。
在这里插入图片描述

常见的切入点表达式写法:
任意公共方法的执行:
execution(public * (…))
任何一个以“set”开始的方法的执行:
execution(
set* (…))AccountService
接口的任意方法的执行:
execution(* com.xyz.service.AccountService.* (…))
定义在service包里的任意方法的执行:
execution(* com.xyz.service.. (…))
定义在service包或者子包里的任意方法的执行:
execution(* com.xyz.service…. (…))
在service包里的任意连接点(在Spring AOP中只是方法执行) :
within(com.xyz.service.)
在service包或者子包里的任意连接点(在Spring AOP中只是方法执行) :
within(com.xyz.service…
)
实现了 AccountService 接口的代理对象的任意连接点(在Spring AOP中只是方法执行) :
this(com.xyz.service.AccountService)

4.6 测试

public class TestUser4 {

	@Test
	public void test1() {
		// TODO 测试切面引入
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-aop.xml");
		UserService userServiceImpl = context.getBean("userService", UserService.class);
		userServiceImpl.addUser(null);
		userServiceImpl.deleteUser(11);
	}
}

5.基于注解的AOP配置

在使用@Aspect之前,首先必须保证所使用的Java是 5.0 以上版本,否则无法使用注解技术.
Spring 在处理@Aspect注解表达式时,需要将Spring的asm模块添加到类路径中,asm是轻量级的字节码处理框架,因为Java的反射机制无法获取入参名,Spring利用asm处理@Aspect中所描述的方法入参名.
此外,Spring采用AspectJ提供的@Aspect注解类库及相应的解析类库,需要在pom.xml文件中添加aspectj.weaver和aspectj.tools类包的依赖.

5.1 创建配置文件

class-path路径下创建applicationContext-aop-annotation.xml
配置目标对象,配置通知对象,开启注解


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

	<bean name="userService" class="com.bruceliu.service.UserServiceImpl" />

	
	<bean name="myAdvice" class="com.bruceliu.dao.MyAdivce" />

	
	<aop:aspectj-autoproxy>aop:aspectj-autoproxy>

beans>

5.2 修改增强类

  • 完整定义增强类
@Aspect
public class MyAdivce1 {

	/**
	 * //前置通知:目标方法运行之前调用 //后置通知(如果出现异常不会调用):在目标方法运行之后调用 //环绕通知:在目标方法之前和之后都调用
	 * //异常拦截通知:如果出现异常,就会调用 //后置通知(无论是否出现 异常都会调用):在目标方法运行之后调用
	 */

	@Pointcut()
	public void pc() {

	}

	// 前置通知
	@Before("MyAdivce1.pc()")
	public void before() {
		System.out.println("这是前置通知");
	}

	// 后置通知
	@AfterReturning("execution(* com..*ServiceImpl.*(..))")
	public void afterReturning() {
		System.out.println("这是后置通知(方法不出现异常)");
	}

	@Around("execution(* com..*ServiceImpl.*(..))")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		System.out.println("这是环绕通知之前部分!!");
		Object object = point.proceed(); // 调用目标方法
		System.out.println("这是环绕通知之后的部分!!");
		return object;
	}

	@AfterThrowing("execution(* com..*ServiceImpl.*(..))")
	public void afterException() {
		System.out.println("异常通知!");
	}

	@After("execution(* com..*ServiceImpl.*(..))")
	public void after() {
		System.out.println("这也是后置通知,就算方法发生异常也会调用!");
	}

}
  • 测试类
public class TestUser5 {

	@Test
	public void test1() {
		// TODO 测试切面引入
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-aop-annotation.xml");
		UserService userServiceImpl = context.getBean("userService", UserService.class);
		userServiceImpl.addUser(null);
		userServiceImpl.deleteUser(11);
	}
}