aop与切面编程

 

aop技术使用了23中设计模式中的代理模式,UML图如下

,代理对象除可实现与目标对象完全相同的行为外,还可以在目标方法执行前后进行额外的操作,比如记录日志

java中分为静态代理与动态代理两种模式,前者使用专门的编译器来生成遵守java字节码编码规范的Class文件,代表框架AspectJ,它定义了Aop语法,这种方式也称作编译时增强;后者指在运行时在内存中生成动态代理类,也被称为运行时增强。

Spring aop与Aspectj的关系,前者动态代理最终是用的JDK api或者CGLIB来实现的;只是spring支持了Aspect的注解标签,没有依赖原生的aspect编译器;

Aop技术实现主要有Spring AOP与AspectJ两种方式,spring 实现了Aop技术的20%,支撑了80%的需求

AOP术语:

1) Aspect :切面,切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;

2) Join point :连接点,也就是可以进行横向切入的位置;

3) Advice :通知,切面在某个连接点执行的操作(分为五大通知: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );

4) Pointcut :切点,符合切点表达式的连接点,也就是真正被切入的地方;

项目中spring作为全能框架被用的最多,而spring aop主要用的动态代理,因此我们先来介绍动态代理,先看其实现方式

动态代理主要有两种方式,一种是通过jdk原生方式实现,该种方式要求代理类只能代理接口,生成的代理类要利用接口承接,若使用目标对象本身则会出错,如下,将变量p改为AopTest修饰则会出现ClassCastException异常


public class AopTest implements InvocationHandler , TargetOperate{
	AopTest op;
	public AopTest() {}
	public AopTest(AopTest op) {
		this.op = op;
	}
	
	public static void main(String[] args) {
		AopTest t= new AopTest();
		TargetOperate p = (TargetOperate) (Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), 
				t.getClass().getInterfaces(),
				new AopTest(t)));
		p.execute("ett");
	}

	/**
	    * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
	    * method:我们所要调用某个对象真实的方法的Method对象
	    * args:指代代理对象方法传递的参数
	    */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("before execute");
		op.execute(args[0].toString());
		System.out.println("after execute");
		return null;
	}

	@Override
	public void execute(String name) {
		System.out.println(name);
	}
}

另一种实现aop的方式则是使用cglib动态代理,该种方式不需要实现特定接口,避免了jdk aop方式的侵入性

t java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibTest implements MethodInterceptor {
	CglibTest target;
	public void execute() {
		System.out.println("execute");
	}
	public static void main(String[] args) {
		CglibTest test = new CglibTest();
		test.target = test;

//		生成代理类
		Enhancer enhancer = new Enhancer();
		// 设置要代理的目标类
		enhancer.setSuperclass(CglibTest.class);
		// 设置要代理的拦截器
		enhancer.setCallback(test);
		CglibTest proxy = (CglibTest)enhancer.create();
		proxy.execute();;
	}
	/**
	 * 拦截方法
	 */
	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("before execute");
		this.target.execute();
		System.out.println("after execute");
		return null;
	}
}

上面无论是jdk还是cglib的代理都是直接拦截目标对象的所有方法,且只有一个拦截器,我们一般不用拦截所有的,spring对此进行的处理方法是使用多种拦截器,来实现五大通知的特性。spring aop使用方式有配置文件与注解两种使用方式,我们先看配置文件方式(Spring与jdk或者cglib的底层交互在此不做详解,有意者可去看下源码)

<?xml version="1.0" encoding="UTF-8"?>
<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="proxy" class="com.test.SpringProxy"></bean>
	<bean name="target" class="com.test.SpringTarget"></bean>
	 <aop:config>
		<aop:pointcut expression="execution(* com.test.SpringTarget.*(..))" id="pc"/>
		 <aop:aspect ref="proxy">
			<aop:before method="before" pointcut-ref="pc"/>
			<aop:after method="after" pointcut-ref="pc"/>
			<aop:around method="around" pointcut-ref="pc"/>
			<aop:after-returning method="afterReturning" pointcut-ref="pc" returning="val"/>
			<aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pc"/>
		</aop:aspect> 
	</aop:config> 
</beans>

返回值与异常通知时可指定参数进入拦截的方法里面,但参数名要保持一致,否则会抛出异常

package com.test;

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

public class SpringProxy {
	public void before() {
		System.out.println("before");
	}

	public void after() {
		System.out.println("after");
	}

	/**
	 *JoinPoint 
	 java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; 
	 Signature getSignature() :获取连接点的方法签名对象; 
	 java.lang.Object getTarget() :获取连接点所在的目标对象; 
	 java.lang.Object getThis() :获取代理对象本身; 
	2)ProceedingJoinPoint  只支持around 通知,其他通知会报错,只能用JointPoint
		ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法: 
	 java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法; 
	 Java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。
	 * @param pjp
	 * @return
	 * @throws Throwable
	 */
	public Object around(ProceedingJoinPoint pjp) throws Throwable {
		System.out.println("around");
		Object ar = pjp.proceed();
		System.out.println("around");
		return ar;
	}

	public void afterException(Exception pjp) throws Throwable {
		System.out.println("出事了,出现异常了" );
	}

	// 后置通知
	public Object afterReturning(JoinPoint point, Object val){
		System.out.println("这是后置通知(如果出现异常不会调用!!)" + val);
		return null;
		
	}
	
	public void afterThrowing (Exception e) {
		System.out.println("抛出的异常为"+ e);
	}
}
package com.test;

public class SpringTarget {
	String execute(String name) {
		System.out.println("execute:" + name);
		if(name.equals("test")) {
			throw new RuntimeException("test");
		}
		return name;
	}
}

public class SpringTest {

	public static void main(String[] args) {
		@SuppressWarnings("resource")
		ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
		SpringTarget t = (SpringTarget)ctx.getBean("target");
		String n = t.execute("test1");
		System.out.println("end " + n);
	}

}

使用xml配置当对象很多的话会很繁琐,于是spring后续提供的注解方式很好的解决了这个问题,aop只需要在配置文件中开启注解扫描,加上这一行

<?xml version="1.0" encoding="UTF-8"?>
<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="proxy" class="com.test.SpringProxy"></bean>
	<bean name="target" class="com.test.SpringTarget"></bean>
	<aop:aspectj-autoproxy/>
</beans>

java bean中加上注解@Aspect即可,此时效果与xml一个个配置拦截器效果相同,当一个目标对象有多个代理时执行顺序就不好控制了,此时我们可以使用注解@Order,value的值越小代表优先级越高


@Aspect
public class SpringProxy {
	
	@Pointcut("execution(* com.test.SpringTarget.*(..))")
	public void declareJoinPointExpression(){}

	@Before("declareJoinPointExpression()")
	public void before() {
		System.out.println("before");
	}

	public void after() {
		System.out.println("after");
	}

而到了springboot时代连配置文件也不用明文开启注解扫描了,直接引入aop-start,我们只需要写好代理类就好了,代理绑定工作spring会帮我们代劳一切

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值