您的位置:首页 > 编程语言 > ASP

学习Java框架的笔记(Spring AOP)基于代理类的AOP实现、AspectJ开发(2)、ApectJ注解式声明

2019-03-28 10:27 971 查看

1.基于代理类的AOP实现

Spring AOP框架所需要的基本JAR包下载

前面的描述中,Spring 中的AOP代理默认使用的是 JDK动态代理方式。而在Spring中,使用ProxyFactory才是创建AOP代理的最基本方式。

1.1Spring的通知类型

Spring 中的通知按照在目标类方法的连接点位置,可分为以下5种类型:

  • org.aopalliance.intercept.MethodLntercepter(环绕通知)
    在目标方法执行前后实施增强,可以应用于日志、事务管理等功能(例如:web项目中的fiflter过滤器)。
  • org.springframework.aop.MethodBeforeAdvice(前置通知)
    在目标方法执行前实施增强,可以应用于权限管理等功能。(登录的密码验证)
  • org.springframework.aop.AfterReturningAdvice(后置通知)
    在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。(关闭数据库连接)
  • org.springframework.aop.ThrowsAdvice(异常通知)
    在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。(对异常进行解释)
  • org.springframework.aop.IntroductionInterceptor(引介通知)
    在目标类中添加一些新的方法和属性,可以应用于修改老版本程序。(增强类)

1.2 ProxyFactoryBean

ProxyFactoryBean是FactoryBean接口的实现类。
FactoryBean负责实例化一个Bean, 而ProxyFactoryBean负责为其他Bean创建代理实例。
在Spring中,使用ProxyFactoryBean是创建AOP代理的基本方式。
程序演示:

(1)在src目录下:创建一个com.itheima.jdk包,在该包下创建接口UserDao ,并在其中编写添加和删除的方法

package com.itheima.jdk;
public interface UserDao {
public void addUser();		//添加
public void deleteUser();		//删除
}

(2)在src目录下,创建一个com.itheima.factorybean包,在该包中创建切面类MyAspect 。由于实现环绕通知需要实现org.aopalliance.intercept.MethodInterceptor接口,所以MyAspect需要实现该接口。

package com.itheima.factorybean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
// 切面类(实现环绕通知)
public class MyAspect implements MethodInterceptor {
@Override
//执行被代理的方法
public Object invoke(MethodInvocation mi) throws Throwable {
check_Permissions();         //前
// 执行目标方法
Object obj = mi.proceed();
log();		//后
return obj;
}
public void check_Permissions(){
System.out.println("模拟检查权限...");
}
public void log(){
System.out.println("模拟记录日志...");
}
}

(3)在com.itheima.factorybean包中,创建配置文件applicationContext.xml,并制定代理对象(代理、被代理对象、代理工厂 三个对象通过.xml配置文件在Spring容器中生成)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!-- 1 目标类       id="userDao"  -->
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
<!-- 2 切面类       id="myAspect"  -->
<bean id="myAspect" class="com.itheima.factorybean.MyAspect" />

<!-- 3 使用Spring代理工厂**定义一个**名称为userDaoProxy的**代理对象** -->
<bean id="userDaoProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 3.1 指定代理实现的接口,也就是实现UserDao接口中的内容-->
<property name="proxyInterfaces"
value="com.itheima.jdk.UserDao" />
<!-- 3.2 指定目标对象 -->
<property name="target" ref="userDao" />
<!-- 3.3 指定切面,织入环绕通知 值为切面对象-->
<property name="interceptorNames" value="myAspect" />
<!-- 3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 -->
<property name="proxyTargetClass" value="true" />

</bean>
</beans>

(4)在com.itheima.factorybean包中,创建测试类ProxyFactoryBeanTest 。

package com.itheima.factorybean;
import org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
import com.itheima.jdk.UserDao;
// 测试类
public class ProxyFactoryBeanTest {
public static void main(String args[]) {
String xmlPath = "com/itheima/factorybean/applicationContext.xml";
//生成、加载Spring容器
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(xmlPath);
// 从Spring容器获得内容。获得代理对象userDao
UserDao userDao =
(UserDao) applicationContext.getBean("userDaoProxy");
// 执行方法
userDao.addUser();
userDao.deleteUser();
}
}

控制台输出结果:

2. AspectJ开发

实现AOP(切面编程)的两种方法:

  • 1.通过XML 声明式
  • 2.通过注解 声明式
    在新版本的Spring框架中,也建议使用AspectJ来开发AOP(面向切面编程)

2.1 基于XML的声明式 AspectJ

基于XML的声明式AspectJ是指通过XML文件来定义切面、切入以及通知。
所有的切面、切入点和通知都必须定义在< aop:config >元素类。
先看代码:

<?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-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 1 目标类 (生成目标对象:userDao)-->
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
<!-- 2 切面 (生成切面对象:myAspect)-->
<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
<!-- 3 aop编程 -->
<aop:config>
<!-- 配置切面 (ref="myAspect"意思是:指定的切面对象为myAspect)-->
<aop:aspect ref="myAspect">
<!-- 3.1 配置切入点,通知最后增强哪些方法 -->
<!-- expression="execution(* com.itheima.jdk.*.*(..)的意思是:在com.itheima.jdk包中所有方法都增强 -->
<aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))"
id="myPointCut" />
<!-- 3.2 关联通知Advice和切入点pointCut -->
<!-- 3.2.1 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut" />
<!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值
returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
<aop:after-returning method="myAfterReturning"
pointcut-ref="myPointCut" returning="returnVal" />
<!-- 3.2.3 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut" />
<!-- 3.2.4 抛出通知:用于处理程序发生异常-->
<!-- * 注意:如果程序没有异常,将不会执行增强 -->
<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
<aop:after-throwing method="myAfterThrowing"
pointcut-ref="myPointCut" throwing="e" />
<!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>

1. 配置切面

在Spring 的配置文件中,配置切面使用的是< aop:aspect >元素,该元素将一个定义好的Spring Bean转换成切面Bean,因此要在配置文件中先定义一个普通的Spring Bean(例如:上面的myAspect)。定义完成后,通过< aop:aspect ref=“myAspect” >中的ref属性 即可引用该Bean。
配置< aop:aspect >元素时,通常会指定id 和 ref两个属性。

属性名称 描述
id 用于定义该切面的唯一标识名称
ref 用于引用普通的Spring Bean

2. 配置切入点

在Spring 的配置文件中,切入点是通过< aop:pointcut >元素来定义的。当< aop:pointcut >元素作为< aop:config > 元素的子元素来定义时,表示该切入点是全局切入点,可被多个切面所共享,当< aop:pointcut >元素作为< aop:aspect > 元素的子元素来时,表示该切入点只对当前切面有效。
< aop:pointcut > 元素的属性及其描述

属性名称 描述
id 用于指定该切入点的唯一标识名称
expression 用于指定切入点关联的切入点表达式

在上述配置代码的片段中,execution(* com.itheima.jdk..(…))就是定义切入点表达式,该切入点的意思是:匹配com.itheima.jdk包中,任意类的任意方法的执行。
execution() 是表达式的主体
第一个 * 表示的是返回类型,使用 * 代表所有类型
第二个 * 表示的是类名,使用 * 代表所有类
第三个 * 表示的是方法名, 使用 * 表示所有方法
后面的(…)表示方法的参数,使用“…” 表示任意参数
第一个 * 与包名之间有一个空格

基于XML的生命式 AspectJ的具体代码:
(1)导入 AspectJ 框架相关的JAR包
spring-aspects-4.3.6.RELEASE.jar
aspectjweaver-1.8.10.jar
(2)在src目录下,创建com.itheima.aspectj.xml包,在该包中创建切面类MyAspect(增强类),在类中定义不同类型的通知。

package com.itheima.aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
*切面类,在此类中编写通知
*/
public class MyAspect {
// 前置通知
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知 :模拟执行权限检查...,");
System.out.print("目标类是:"+joinPoint.getTarget() );
System.out.println(",被织入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
// 后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志...," );
System.out.println("被织入增强处理的目标方法为:"
+ joinPoint.getSignature().getName());
}
/**
* 环绕通知
* ProceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法
* 1.必须是Object类型的返回值
* 2.必须接收一个参数,类型为ProceedingJoinPoint
* 3.必须throws Throwable
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
// 开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
// 执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
// 结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
// 异常通知
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:" + "出错了" + e.getMessage());
}
// 最终通知
public void myAfter() {
System.out.println("最终通知:模拟方法结束后的释放资源...");
}
}

在上述代码中定义了5种不同类型的通知,使用了JoinPoint接口及其子接口ProceedingJoinPoint作为参数来获得目标对象的类名、目标方法名和目标方法参数

(3)在com.itheima.aspectj.xml包中,创建配置文件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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 1 目标类 -->
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
<!-- 2 切面 -->
<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
<!-- 3 aop编程 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 3.1 配置切入点,通知最后增强哪些方法 -->
<aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))"
id="myPointCut" />
<!-- 3.2 关联通知Advice和切入点pointCut -->
<!-- 3.2.1 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut" />
<!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值
returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
<aop:after-returning method="myAfterReturning"
pointcut-ref="myPointCut" returning="returnVal" />
<!-- 3.2.3 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut" />
<!-- 3.2.4 抛出通知:用于处理程序发生异常-->
<!-- * 注意:如果程序没有异常,将不会执行增强 -->
<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
<aop:after-throwing method="myAfterThrowing"
pointcut-ref="myPointCut" throwing="e" />
<!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>

在AOP的配置信息中,使用< aop:after-returning >配置后置通知和使用< aop:after >最终通知的区别是,< aop:after-returning >只在目标方法执行成功之后才会织入,而最终通知是无论目标方法如何结束(包括成功执行和异常中止如何)都会织入
(4)在com.itheima.aspectj.xml包中,创建测试类TestXmlAspectj

package com.itheima.aspectj.xml;
import org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
import com.itheima.jdk.UserDao;
// 测试类
public class TestXmlAspectj {
public static void main(String args[]) {
String xmlPath =
"com/itheima/aspectj/xml/applicationContext.xml";
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(xmlPath);
// 1 从spring容器获得内容
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
// 2 执行方法
userDao.addUser();
}
}

2.1 基于注解的声明式 AspectJ

与基于代理类的AOP实现相比,基于XML的声明式ApectJ要便捷很多,但是它也存在一些缺点:要在Spring文件中配置大量的代码信息。为了解决这一问题,ApectJ框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码.
关于ApectJ注解的介绍

注解名称 描述
@Aspect 用于定义一个切面
@Pointcut 用于定义切入点表达式1
@Before 用于定义前置通知
@AfterReturning 用于定义后置通知
@Around 用于定义环绕通知
@After Throwing 用于定义异常通知来处理程序中未处理的异常
@After 用于定义最终final通知,不管是否异常,该通知都会执行
@DeclareParents 用于定义引介通知

(1)在src目录下,创建com.itheima.aspectj.annotation包,

package com.itheima.aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 切面类,在此类中编写通知
*/
@Aspect
@Component
public class MyAspect {
// 定义切入点表达式
@Pointcut("execution(* com.itheima.jdk.*.*(..))")
// 使用一个返回值为void、方法体为空的方法来命名切入点
private void myPointCut(){}
// 前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知 :模拟执行权限检查...,");
System.out.print("目标类是:"+joinPoint.getTarget() );
System.out.println(",被织入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
// 后置通知
@AfterReturning(value="myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志...," );
System.out.println("被织入增强处理的目标方法为:"
+ joinPoint.getSignature().getName());
}
// 环绕通知
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
// 开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
// 执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
// 结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
// 异常通知
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:" + "出错了" + e.getMessage());
}
// 最终通知
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知:模拟方法结束后的释放资源...");
}
}

在上述文件中,首先使用@Aspect注解定义切面类,由于该类在Spring中是作为组件使用的,所以还需要添加@Component注解才能生效。
之后使用了@Pointcut注解来配置切入点表达式,并通过定义方法来表示切入点名称。

(2)在目标类UserDaoImpl中,添加注解@Repository(“userDao”)

package com.itheima.jdk;

import org.springframework.stereotype.Repository;

// 目标类
@Repository("userDao")
public class UserDaoImpl implements UserDao {
public void addUser() {
//		int i = 10/0;
System.out.println("添加用户");
}
public void deleteUser() {
System.out.println("删除用户");
}
}

(3)在com.itheima.aspectj.annotation包下(也就是切面类所在包下),创建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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 指定需要扫描的包,使注解生效 -->
<context:component-scan   base-package="com.itheima" />
<!-- 启动基于注解的声明式AspectJ支持 -->
<aop:aspectj-autoproxy />
</beans>

上述代码中,首先引入了comtext约束信息,然后使用< context >元素设置需要扫描的包,使注解生效。由于目标类处于别的包下,所以设置了base-package的值为com.itheima。最后使用< aop:aspectj-autoproxy />来启动Spring对基于注解的声明式 AspectJ的支持。

(4)在com.itheima.aspectj.annotation包下,创建测试类TestAnnotationAspectj

package com.itheima.aspectj.annotation;
import org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
import com.itheima.jdk.UserDao;
// 测试类
public class TestAnnotationAspectj {
public static void main(String args[]) {
String xmlPath =
"com/itheima/aspectj/annotation/applicationContext.xml";
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(xmlPath);
// 1 从spring容器获得内容
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
// 2 执行方法
userDao.addUser();
}
}

ApectJ注解式声明结果截图:

  1. 在使用时,还需要定义一个方法签名来表示切入点名称。方法签名就是:一个返回值为void,且方法体为空的普通方法。 ↩︎

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: