Spring AOP

AOP:Aspect-Oriented Programming,面向切面编程。作为OOP的一种补充。
传统的OOP采用的时纵向继承关系,难免会后很多重复的代码。而AOP的思想可以完美解决这个问题,AOP采用横向抽取机制,把重复的代码抽取出来,单独建立一个类,这个类就是切面类,当某个类需要时(目标类),会创建目标类的代理类(Proxy),然后把切面类中的某个方法(Advice),切入到目标类中。

Java中有两种情况的AOP

  1. JDK动态代理

JDK动态代理是基于目标类接口创建的,也就是说目标类必须实现某个接口。

  1. CGLIB代理

CGLIB代理可以直接对某个类进行代理,不需要目标类实现某个接口。

JDK动态代理

JDK动态代理是通过java.lang.reflect包中的Proxy类实现的,通过调用它的方法newProxyInstance()来创建代理对象。此外还需要让代理类实现InvocationHandler接口,该接口的invoke()方法正是执行目标类增强方法的入口。

创建目标类

  1. 创建目标类的接口
	public interface UserDao{
		public void addUser();
		public void deleteUser();
	}
  1. 创建目标类接口的实现类
public class UserDaoImpl implements UserDao {
	
	@Override
	public void addUser() {
		// TODO Auto-generated method stub
		System.out.println("添加用户!");
	}

	@Override
	public void deleteUser() {
		// TODO Auto-generated method stub
		System.out.println("删除用户!");
	}

}

创建切面类

package com.syl.aspect;
/**
 * 	切面类aspect
 * @author x1c
 *
 */
public class MyAspect {
	/*
	 *	两个Advice 
	 */
	public void check_Permissions() {
		System.out.println("模拟权限检查....");
	}
	public void log() {
		System.out.println("模拟记录日志...");
	}
}

创建代理类

  1. createProxy()方法

此方法是自主创建的,通过反射获取实现目标类接口的所有类,然后利用Proxy类的newProxyInstance(classLoader,clazz,this)三个参数分别为本类的加载器、实现目标类接口的实现类,本类。

  1. InvocationHandler接口

该接口有一个invoke()方法,此方法是完成Advice的关键。

package com.syl.jdk;
/**
 * JDK代理类proxy
 */
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.syl.aspect.MyAspect;

public class JdkProxy implements InvocationHandler {
	//声名目标类接口
	private UserDao userDao;
	//创建代理方法
	public Object createProxy(UserDao userDao) {
		this.userDao = userDao;
		//类加载器
		ClassLoader classLoader = JdkProxy.class.getClassLoader();
		// 目标类实现的所有接口即UserDao接口的实现类
		Class[] clazz = userDao.getClass().getInterfaces();
		//返回代理后的对象
		return Proxy.newProxyInstance(classLoader, clazz, this);
	}
	/**
	 * 	createProxy类,只完成了对类的代理
	 * 	具体对目标类的增强还是需要invoke()方法来完成的
	 */
	@Override
	/**
	 * proxy : 被代理后的对象
	 * method: 将要被执行的方法信息
	 * args: 执行方法时需要的参数
	 */
	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;
	}

}

测试

package com.syl.jdk;

public class JdkTest {
	public static void main(String args[]) {
		//创建目标类对象
		UserDao userDao = new UserDaoImpl();
		//创建代理类
		JdkProxy jdk = new JdkProxy();
		//传入目标类对象参数,获取代理类
		UserDao userDaoProxy = (UserDao)jdk.createProxy(userDao);
		//执行增强后的方法
		userDaoProxy.addUser();
		userDaoProxy.deleteUser();
		//执行原本的方法
		userDao.addUser();
		userDao.deleteUser();
	}
}

在这里插入图片描述

CGLIB代理

CGLIB代理是一个高性能开源的代码生成包,对目标类生成一个子类,然后对子类进行加强。

目标类

package com.syl.target;

public class UserDao {
	public void addUser() {
		System.out.println("添加用户!");
	}

	public void deleteUser() {
		System.out.println("删除用户!");
	}
}

切面类

同JDK动态代理

代理类

  1. createProxy()方法

此方法创建动态类对象Enhancer,并将目标类设置其父类。

  1. MethodInterceptor接口

此接口的intercept()方法,完成对目标类方法的增强。

package com.syl.cglib;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import com.syl.aspect.MyAspect;
/**
 * CGLIB代理是创建目标类的子类,对子类进行增强
 * @author x1c
 *
 */
public class CglibProxy implements MethodInterceptor{
	//创建代理类方法
	public Object createProxy(Object target) {
		//创建动态类对象
		Enhancer enhancer = new Enhancer();
		//设置其父类
		enhancer.setSuperclass(target.getClass());
		//回调函数
		enhancer.setCallback(this);
		//创建代理类
		return enhancer.create();
	}
	@Override
	/**
	 * proxy :	CGLIB根据父类生成的代理对象
	 * method:	拦截的方法,即切入点
	 * args :	切入点方法的参数
	 * methodProxy:	方法的代理对象,用于执行父类的方法
	 */
	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;
	}
	
}

测试

package com.syl.cglib;

import com.syl.target.UserDao;

public class Test {
	public static void main(String args[]) {
		//目标对象
		UserDao user = new UserDao();
		//创建代理类
		CglibProxy cg = new CglibProxy();
		//获取代理对象
		UserDao user2 = (UserDao)cg.createProxy(user);
		//执行增强后的方法
		user2.addUser();
		user2.deleteUser();
	}
}

在这里插入图片描述

Spring中的AOP

Spring中的AOP代理默认使用JDK动态代理方式实现。基于ProxyFactoryBean类开发AOP,是创建AOP的最基本方式
另外需要导入Spring如下几个JAR包

在这里插入图片描述

Spring AOP通知类型

  1. org.aopalliance.intercept.MethodInterceptor(环绕通知)
  2. org.springframework.aop.MethodBeforeAdvice(前置通知)
  3. org.springframework.aop.AfterReturningAdvice(后置通知)
  4. org.springframework.aop.ThrowsAdvice(异常通知)
  5. org.springframework.aop.IntroductionInterceptor(引介通知)

ProxyFactoryBean类

ProxyFactoryBean是FactoryBean接口的实现类,前者负责实例化一个Bean,后者负责为其他Bean创建代理实例。
常用属性如下:

属性描述
target代理的目标对象
proxyInterfaces代理需要实现的接口
proxyTargetClasstrue,表示CGLIB代理。默认false
interceptorNames切面类
singleton返回代理是否为单实例,默认true
optimizetrue,强制使用CGLIB代理

目标类

package com.syl.target;

public class UserDao {
	public void addUser() {
		System.out.println("添加用户!");
	}

	public void deleteUser() {
		System.out.println("删除用户!");
	}
}

切面类

Spirng AOP中的通知类型,直接在切面类继承通知类型的接口

package com.syl.springaop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAspect implements MethodInterceptor {
	//invoke()方法用来执行方法
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		check_Permissions();
		//环绕通知,用来执行目标类的方法
		Object obj = mi.proceed();
		log();
		return obj;
	}
	//Advice
	public void check_Permissions() {
		System.out.println("模拟检查权限...");
	}
	public void log() {
		System.out.println("模拟记录日志...");
	}
	

}

基于XML使用ProxyFactoryBean

<?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.xsd">
	<!-- 目标类 -->
	<bean id="userDao"
	    class="com.syl.target.UserDao"/>
	<!-- 切面类 -->
	<bean id="myAspect"
	    class="com.syl.springaop.MyAspect"/>
	<!-- 使用Spring代理工厂创建代理对象 -->
	<bean id="userDaoProxy"
	    class="org.springframework.aop.framework.ProxyFactoryBean">
	    <!-- 设置目标类 -->
	    <property name="target" ref="userDao"	/>
	    <!-- 切面类 -->
	    <property name="interceptorNames" value="myAspect"/>
	    <!-- 是否使用CGLIB代理 -->
	    <property name="proxyTargetClass" value="true"/>
	    <!-- JDK动态代理 -->
	    <!-- <property name="proxyInterfaces" value="目标类接口路径"/> -->
	    <!-- 单实例 -->
	    <!--  <property name="singleton" value="true"/>	-->
	    
	</bean>
</beans>

测试

package com.syl.springaop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.syl.target.UserDao;

public class ProxyFactoryBeanTest {
	public static void main(String args[]) {
		//Bean容器
		ApplicationContext a = 
				new ClassPathXmlApplicationContext("applicationContext.xml");
		//获取Bean
		UserDao userdao = (UserDao)a.getBean("userDaoProxy");
		//执行代理后的对象的方法
		userdao.addUser();
		userdao.deleteUser();
	}
}

在这里插入图片描述

AspectJ

使用AspectJ框架可以更加简答的配置AOP,组件如下

......
......
	<aop:config>
		<!--配置切面-->
		<aop:aspect >
			<!--配置切入点-->
			<aop:poincut>
			<!--配置通知-->
			<!--前置通知-->
			<aop:before />
			<!--后置通知-->
			<aop:after-returning />
			<!--环绕通知-->
			<aop:around />
			<!--异常通知-->
			<aop:after-throwing />
			<!--最终通知-->
			<aop:after />
			</aop:poincut>
		</aop:aspect>
	</aop:config>

配置切面

< aop:aspect >元素用来配置切面,他有如下两个属性

  1. id:该切面唯一标识
  2. ref:引用Bean作为切面

配置切入点(目标类)

< aop:poincut >元素用来配置切入点,他有如下两个属性

  1. id:该切入点唯一标识
  2. expression:指定切入点表达式

expression="execution(* com.syl.jdk. * . * (…))
此切入点表达式:任意方法的返回类型、com.syl.jdk下的任意类、任意方法、任意方法参数

配置通知

配置通知的元素有如下几个元素

  1. < aop:before>:前置通知
  2. < aop:after-returning>:后置通知
  3. < aop:around>:环绕通知
  4. < aop:after-throwing>:异常通知
  5. < aop:after>:最终通知

通知又有如下几个属性

属性描述
pointcut指定切入点完整路径
pointcut-ref指定切入点的Bean
method指定使用切面的哪个方法作为增强通知
throwing异常通知有效。用于指定一个形参名,可通过该形参访问抛出的异常
returning最终通知有效。用于指定一个形参名,可通过该形参访问返回值

基于XML的AspectJ

导入如下JAR包
在这里插入图片描述

  1. 定义切面
package com.syl.aspectJ;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 	切面
 * @author x1c
 *
 */
public class MyAspect {
	//前置通知
	public void myBefore(JoinPoint joinPoint) {
		System.out.println("前置通知:模拟权限检查");
		System.out.println("目标类是:"+joinPoint.getTarget());
		System.out.println("切入点:"+joinPoint.getSignature().getName());
	}
	//后置通知
	public void myAfterReturing(JoinPoint joinPoint) {
		System.out.println("后置通知:模拟记录日志");
		System.out.println("切入点:"+joinPoint.getSignature().getName());
	}
	//环绕通知
	/*
	 * ProceedingJoinPoint 是JoinPoint的子接口
	 * 1. 必须Object返回值类型
	 * 2.必须抛出异常
	 * 3.ProceedingJoinPoint用来执行切入点方法
	 */
	public Object around(ProceedingJoinPoint joinPoint)throws Throwable {
		System.out.println("环绕开始");
		System.out.println("执行目标方法前:模拟开启事务");
		//执行目标方法
		Object obj = joinPoint.proceed();
		System.out.println("环绕结束");
		System.out.println("执行目标方法后:模拟关闭事务");
		return obj;
	}		
	//异常通知
	public void myAfterThrowing(JoinPoint joinPoint,Throwable e) {
		System.out.println("异常通知:	"+"出错了	"+e.getMessage());
		
	}
	//最终通知
	public void myAfter() {
		System.out.println("最终通知:模拟释放资源	");
		
	}
}
  1. xml
	<!-- AspectJ的切面类 -->
	<bean id="myAspectJ"
	    class="com.syl.aspectJ.MyAspect"/>
	<!-- AspectJ -->
	<aop:config>
	    <!-- 切面 -->
	    <aop:aspect	ref="myAspectJ">
	        <!-- 切入点 -->
	        <aop:pointcut expression="execution(* com.syl.target.*.*(..))" id="myPointCut"/>
	        <!-- 前置通知 -->
	        <aop:before method="myBefore" pointcut-ref="myPointCut"/>
	        <!-- 后置通知 -->
	        <aop:after-returning method="myAfterReturing" 
	            	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>

基于注解式的AspectJ

基于注解的AspectJ,切入点表达式直接写在切面类里。有如下几种注解:

  1. @Aspect:定义切面
  2. @Pointcut:定义切入点,需要在切面类中定义一个void返回类型的方法来表示切入点。
  3. @Before:前置通知
  4. @AfterReturning:后置通知
  5. @Around:环绕通知
  6. @AfterThrowing:异常通知
  7. @After:最终通知

切面类

在此定义切面、切入点表达式、通知类型

package com.syl.aspectJ;

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;

/**
 * 切面类
 * @author x1c
 *
 */
@Aspect
@Component//默认将类首字母小写作为Bean实例名
public class MyAspectOnAnnoation {
	//定义切入点表达式
	@Pointcut("execution(* com.syl.target.*.*(..))")
	//空方法表示切入点
	private void myPointCut() {
		
	}
	//前置通知,指定对哪一个切入点有效
	@Before("myPointCut()")
	public void myBefore(JoinPoint joinPoint) {
		System.out.println("前置通知:模拟权限检查...");
		System.out.println("目标类是:"+joinPoint.getTarget());
		System.out.println("增强处理的方法:"+joinPoint.getSignature().getName());
	}
	@AfterReturning("myPointCut()")
	public void myAfterReturning(JoinPoint joinPoint) {
		System.out.println("后置通知:模拟记录日志");
	}
	@Around("myPointCut()")
	public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
		Object obj = joinPoint.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("最终通知");
	}
}

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">
	<!-- AspectJ -->
	<!-- 指定需要扫描的包,普通注解生效 -->
	<context:component-scan base-package="com.syl" />
	<!-- 基于AspectJ的注解生效 -->
	<aop:aspectj-autoproxy />
</beans>

测试

package com.syl.aspectJ;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.syl.target.UserDao;

public class MyAspectJOnAnnotation {
	public static void main(String args[]) {
		ApplicationContext a = 
				new ClassPathXmlApplicationContext("applicationContext.xml");
		//目标类需要使用@Repository注解,首字母小写的Bean
		UserDao user = (UserDao)a.getBean("userDao");
		user.addUser();
	}
}

在这里插入图片描述


@Syl 2021/09/01 周三 22:08 回宿舍,跑会步

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值