JAVA EE 知识总结(三)Spring AOP_aop框架有哪两个_一条小橘猫的博客-程序员秘密

技术标签: spring  经验分享  java  Spring AOP  JAVA EE  java-ee  

目录

3.1  Spring AOP简介

3.1.1什么是AOP?

3.1.2 AOP术语

3.2  动态代理

3.2.1 JDK动态代理

3.2.2 CGLIB代理

3.3  基于代理类的AOP实现

3.3.1 Spring的通知类型

3.3.2 ProxyFactoryBean

3.4 AspectJ开发

3.4.1 基于XML的声明式AspectJ


3.1  Spring AOP简介

3.1.1什么是AOP?

AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。

在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率。

为了解决这一问题,AOP思想随之产生。AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充

类与切面的关系

 

AOP的使用,使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多的关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性

目前最流行的AOP框架有两个:Spring AOP和AspectJ。

Spring AOP:使用纯Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类织入增强的代码。

AspectJ:是一个基于Java语言的AOP框架,从spring2.0开始,Spring Aop引入了对AspectJ的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供了横向代码的织入。

3.1.2 AOP术语

AOP的专业术语:

(1)Aspect(切面):封装的用于横向插入系统功能的类(如事务、日志等)。该类要被Spring容器识别为切面,需要在配置文件中通过<bean>元素指定。

(2)Joinpoint(连接点):在程序执行过程中的某个阶段点,它实际上是对象的一个操作(例如方法的调用或异常的抛出)。在Spring AOP中,连接点就是指方法的调用

(3)Pointcut(切入点):切面与程序流程的交叉点,即那些需要处理的连接点。通常在程序中,切入点指的是类或者方法名。(如:某个通知要应用到所有以add开头的方法中,那么满足这一规则的方法都是切入点)。

(4)Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法。是切面的具体实现。

(5)Target Object(目标对象):指所有被通知的对象也被称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象。

(6)Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。

(7)Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

3.2  动态代理

AOP中的代理就是由AOP框架动态生成的一个对象,该对象可以作为目标对象的使用。

3.2.1 JDK动态代理

JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。JDK动态代理案例:

(1)创建web项目chapter03,导入所需jar包

(2)创建包com.jdk,在包中创建接口UserDao,并编写添加,删除方法。

public interface UserDao {
	public void addUser();
	public void deleteUser();
}

(3)创建UserDao接口的实现类UserDaoImpl

//目标类(需要对addUser()和deleteUser()方法进行增强)
public class UserDaoImpl implements UserDao{
	@Override
	public void addUser() {
		System.out.println("添加用户");
	}
	@Override
	public void deleteUser() {
		System.out.println("删除用户");
	}
}

(4)创建一个包com.aspect,并创建切面类MyAspect

/*
 * 切面类MyAspect:可以存在多个通知Advice
 * (定义一个模拟权限检查的方法 和 一个模拟记录日志的方法,这两个方法就表示切面中的通知)
 */
public class MyAspect {
	public void check_Permissions() {
		System.out.println("模拟检查权限...");
	}
	public void log() {
		System.out.println("模拟记录日志...");
	}
}

(5)在com.jdk中,创建代理类JdkProxy,该类需要实现接口InvocationHandler,并编写代理方法。

/**
 *代理类:需要实现InvocationHandler接口,并编写代理方法
 *		在代理方法中,需要通过Proxy类实现动态代理
 */
public class JdkProxy implements InvocationHandler{
	//声明目标类接口
	private UserDao userDao;
	//创建代理方法
	public Object createProxy(UserDao userDao) {
		//给已经有的userDao进行赋值
		this.userDao = userDao;
		//1.类加载器
		ClassLoader classLoader = JdkProxy.class.getClassLoader();
		//2.被代理对象实现的所有接口
		Class[] clazz = userDao.getClass().getInterfaces();
		/*
		 * 3.使用代理类,进行增强,返回的是代理后的对象
		 * 		classLoader:当前类的类加载器
		 * 		clazz:被代理对象实现的所有接口
		 * 		this:指代代理类JdkProxy本身
		 */
		return Proxy.newProxyInstance(classLoader, clazz, this);//newProxyInstance创建代理对象
	}
	/*
	 *所有动态代理类的方法调用,都会交由invoke()方法去处理
	 *proxy:被代理后的对象
	 *method:将要被执行的方法信息(反射)
	 *args:执行方法时需要的参数
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//声明切面
		MyAspect myAspect = new MyAspect();
		//前增强
		myAspect.check_Permissions();
		//在目标类上调用方法,并传入参数
		Object obj = method.invoke(userDao, args);
		//后增强
		myAspect.log();
		return obj;
	}
}

(6)创建测试类JdkTest

public class JdkTest {
	public static void main(String[] args) {
		//创建代理对象
		JdkProxy jdkProxy = new JdkProxy();
		//创建目标对象
		UserDao userDao = new UserDaoImpl();
		//从代理对象中获取增强后的目标对象
		UserDao userDao2 = (UserDao) jdkProxy.createProxy(userDao);
		//执行方法
		userDao2.addUser();
		userDao2.deleteUser();
	}
}

3.2.2 CGLIB代理

通过前面的学习可知,JDK的动态代理用起来非常简单,但它是有局限性的,使用动态代理的对象必须实现一个或多个接口。

那么,如何代理没有实现接口的类?——CGLIB代理

CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。

案例代码:

(1)创建目标类UserDao

//目标类
public class UserDao {
	public void addUser() {
		System.out.println("添加用户");
	}
	public void deleteUser() {
		System.out.println("删除用户");
	}
}

(2)创建代理类CglibProxy

//代理类
public class CglibProxy implements MethodInterceptor{

	//代理方法
	public Object createProxy(Object target) {
		//创建一个动态类对象
		Enhancer enhancer = new Enhancer();
		//确定需要增强的类,设置其父类
		enhancer.setSuperclass(target.getClass());
		//添加回调函数
		enhancer.setCallback(this);
		//返回创建的代理类
		return enhancer.create();
	}
	
	/**
	 * proxy:CGLIB根据指定父类生成的代理对象
	 * method:拦截的方法
	 * args:拦截方法的参数数组
	 * methodProxy:方法的代表的对象,用于执行父类的方法
	 */
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		//创建切面类
		MyAspect myAspect = new MyAspect();
		//前增强
		myAspect.check_Permissions();
		//目标方法执行
		Object obj = methodProxy.invokeSuper(proxy, args);
		//后增强
		myAspect.log();
		return obj;
	}
	
}

(3)创建测试类

public class CglibTest {
	public static void main(String[] args) {
		//创建代理对象
		CglibProxy cglibProxy = new CglibProxy();
		//创建目标对象
		UserDao userDao = new UserDao();
		//获取增强前的目标对象
		UserDao userDao2 = (UserDao) cglibProxy.createProxy(userDao);
		//执行方法
		userDao2.addUser();
		userDao2.deleteUser();
	}
}

3.3  基于代理类的AOP实现

3.3.1 Spring的通知类型

Spring按照通知在目标类方法的连接点位置,可以分为5种类型,具体如下:org.springframework.aop.MethodBeforeAdvice(前置通知)     在目标方法执行前实施增强,可以应用于权限管理等功能。org.springframework.aop.AfterReturningAdvice(后置通知)     在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除     临时文件等功能。org.aopalliance.intercept.MethodInterceptor(环绕通知)     在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。org.springframework.aop.ThrowsAdvice(异常抛出通知)     在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。org.springframework.aop.IntroductionInterceptor(引介通知)     在目标类中添加一些新的方法和属性,可以应用于修改老版本程序。

3.3.2 ProxyFactoryBean

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

ProxyFactoryBean类中的常用可配置属性如下:

环绕通知案例:

(1)创建项目,导入jar包:spring-aop-4.3.6.RELEASE.jar 和 aopalliance-1.0.jar

(2)在com.itheima.factorybean包中创建切面类MyAspect

// 切面类
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中,创建配置文件applicableContext.xml,并指定代理对象

<!-- 1 目标类 -->
	<bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
	<!-- 2 切面类 -->
	<bean id="myAspect" class="com.itheima.factorybean.MyAspect" />
	<!-- 3 使用Spring代理工厂定义一个名称为userDaoProxy的代理对象 -->
	<bean id="userDaoProxy" 
            class="org.springframework.aop.framework.ProxyFactoryBean">
		<!-- 3.1 指定代理实现的接口-->
		<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>

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

// 测试类
public class ProxyFactoryBeanTest {
	public static void main(String args[]) {
	   String xmlPath = "com/itheima/factorybean/applicationContext.xml";
	   ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
	   // 从Spring容器获得内容
	   UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");
	   // 执行方法
	   userDao.addUser();
	   userDao.deleteUser();
	}
}

3.4 AspectJ开发

    AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring 2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。新版本的Spring框架,也建议使用AspectJ来开发AOP。    使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ

3.4.1 基于XML的声明式AspectJ

    基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。<aop:config>元素及其子元素如下:

小提示:图中灰色部分标注的元素即为常用的配置元素

XML文件中常用元素的配置方式如下:

<!-- 1.配置切面Bean -->
<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
<aop:config>
	<!-- 2.配置切面 -->
	<aop:aspect  id="aspect"  ref="myAspect">
		<!-- 3.配置切入点 -->
		<aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))" id="myPointCut" />
		<!-- 前置通知 -->
		<aop:before method="myBefore" pointcut-ref="myPointCut" />
		<!-- 后置通知 -->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" 
returning="returnVal" />
		<!-- 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut" />
		<!-- 异常通知 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e" />
		<!-- 最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
    </aop:aspect>
</aop:config

配置内容进行详细讲解:

1.配置切面

在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean。

配置<aop:aspect>元素时,通常会指定id和ref两个属性。

2.配置切入点

当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效

在定义<aop:pointcut>元素时,通常会指定id和expression两个属性。

切入点表达式:

execution(* com.itheima.jdk.*.*(..)) 是定义的切入点表达式,该切入点表达式的意思是匹配com.jdk包中任意类的任意方法的执行。切入点表达式的基本格式:

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

3.配置通知

使用<aop:aspect>的子元素可以配置5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性,其常用属性及其描述如下:

 

案例代码

接下来,就通过一个案例来演示如何在Spring中使用基于XML的声明式AspectJ,具体实现步骤请参见教材3.4.1小节。

(1)导入AspectJ相关的jar包spring-aspects-4.3.6.RELEASE.jar和aspectjweaver-1.8.10.jar

(2)创建包:com.aspect.xml   创建切面类MyAspecy.java

/**
 *切面类:在此类中编写通知
 */
public class MyAspecy {
	//前置通知
	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("最终通知:模拟方法结束后的释放资源...");
	}
}

 (3)在包com.aspect.xml中,创建配置文件applicationContext.xml

<!-- 1.目标类 -->
<bean id="userDao" class="com.jdk.UserDaoImpl"/>
<!-- 2.切面 -->
<bean id="myAspect" class="com.aspect.xml.MyAspecy"/>
<!-- 3.aop编程 -->
<aop:config>
	<!-- 配置切面 -->
<aop:aspect ref="myAspect">
		<!-- 3.1配置切入点,通知最后增强哪些方法 -->
			<aop:pointcut expression="execution(* com.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抛出通知 -->
			<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" 
throwing="e"/>
			<!-- 3.2.5最终通知 -->
			<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>

(4)在包com.aspect.xml中,创建测试类TestXmlAspectj.java

public class TestXmlAspectj {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("com/aspect/xml/applicationContext.xml");
		// 1 从spring容器获得内容
		UserDao userDao = (UserDao) ac.getBean("userDao");
		
		userDao.addUser();
		userDao.deleteUser();
	}
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/WHT869706733/article/details/124480561

智能推荐

SQL PROFILE的用法_sql profile搜索_郭一军_guoyJoe的博客-程序员秘密

一般只需要步骤三、四就可以完成执行计划的修改和固定,而outline和baseline则需要N多个步骤。SQL PROFILE使用简单,不区分大小写,回车,空格,但是对DBA写HINT的能力要求比较高,因为SQL PROFILE要求HINT必须写明查询块名,SQL PROFILE还有其他一些牛逼的特性。我的示例里教了大家偷懒的做法,但是有时间我们还是最好认真把query block

ubuntu18安装python3-pip opencv-python_kuochu'ng的博客-程序员秘密

[email protected]:/# apt-get install python3-dev aptitude [email protected]:/# pip3 --versionpip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)[email protected]:/# apt-get ins...

友盟 NoClassDefFoundError: com.umeng.analytics.d 导致5.0以下app崩溃的问题_yh16266的博客-程序员秘密

如题,此问题是由于在build.gradle中配置了分包,就是multiDexEnabled true,三种解决方案:1、不配置分包(这个好像不太可能,既然配置了肯定是超了65535)2、自定义的application不要继承Application,而是继承MultiDexApplication。3、仍然继承Application,然后复写Application

数据包络分析DEA_dea怎么算lambda_烤冷面加芝士加芝士的博客-程序员秘密

一些好文:https://zhuanlan.zhihu.com/p/390594011多属性决策采取加法或者乘法等模式,计算方案之加总价值,假设方案之价值衡量在每个属性上都是越大越好。好文:https://blog.csdn.net/qq_44431690/article/details/108198631进行信息集结的方法:加权算术平均(WAA)算法、加权几何平均(WGA)算子、有序加权平均(OWA)算法。由于不同属性指标的值大小有所不同,为了消除不同物理量纲对决策结果的影响,决策时可对.

Leetcode_3_Math_165比较版本号_小小布丁--pudding的博客-程序员秘密

自己写滴~一开始老出错,原来是Java中split()里面的 "."必须加双斜杠。。。。。。"\\."class Solution { public int compareVersion(String version1, String version2) { String[] v1=version1.split("\\."); String[...

随便推点

RocketMq-入门篇_rocketmq入门_小王师傅66的博客-程序员秘密

你在工作中是否遇到过这些问题?系统平时访问流量平平,在某个时间段好像决堤似的突增?上下游执行链路长,重要和不重要的执行内容混在一起,层层嵌套?面对高并发时,下游系统无法承载海量的调用量,影响上游响应……如果你也有,那就和我一起来学习rocketMq吧,据说rocketMq能帮我们很好的解决这些问题。......

使用MyEclipse2017进行web开发_myeclipse新建没有jsp_wangchuang2017的博客-程序员秘密

三)MyEclipse 2017 CI 新建Web工程MyEclipse是一个非常强大的开发工具,使用它可以很方便地进行Java Web开发。1. 在MyEclipse中,JSP页面是以Web项目的形式组织起来的。所以要创建JSP页面之前,必须要创建一个Web项目。File-&gt;New-&gt;Web Project这是主机安转的JavaJDK版本1.8下图是主机安装的Tomcat,版本为9.0Project命名为TestWeb,Javaversion选择主机安装..

springboot 设置model.addAttribute(dto);HTML页面无法获取值_model.addattribute报红_长沙郭富城的博客-程序员秘密

conttroller [email protected]@RequestMapping(&amp;quot;/web/member&amp;quot;)public class MerchantWebController { @RequestMapping(value = &amp;quot;/update&amp;quot;) public String update(Model model, Integer id) { Mer...

I2C接口_朝辞暮见的博客-程序员秘密

一、I2C总线协议内容1. I2C总线引脚定义SDA (I2C数据引脚)CLK (I2C数据引脚) 2. I2C总线物理连接I2C总线物理连接如下图所示,SDA和CLK连接线上连有两个上拉电阻,当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低。 二、I2C总线的数据传送1. 数据位的有效性规定I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保...

推荐文章

热门文章

相关标签