Spring框架——AOP

代理模式

代理模式创建代理对象,让代理对象控制目标对象的访问,并且可以在不改变目标对象的情况下添加一些额外的功能

不破坏原来代码的业务逻辑结构

静态代理

代理对象与被代理对象必须实现同一接口,在代理对象中实现日志

实现

  • 被代理对象

public class UserServiceImpl implements UserService {
	
	public boolean login(String name, String password){
		if(name.equals("adi") && password.equals("123")){
			System.out.println("业务逻辑:用户adi登录成功");
			return true;
		}else{
			System.out.println("业务逻辑:用户adi登录失败");
			return false;
		}
	}
}

  • 代理对象

package staticproxy;

import java.util.Date;

public class UserServiceStaticProxy implements UserService {

	private UserService userService;

	public UserServiceStaticProxy(UserService userService) {
		this.userService = userService;
	}

	@Override
	public boolean login(String name, String password) {
		boolean isLogin = userService.login(name, password);
		System.out.println("日志:" + name + "于" + new Date().toLocaleString() + "登录");
		return isLogin;
	}
}

  • 使用

UserServiceStaticProxy proxy = new UserServiceStaticProxy(new UserServiceImpl());
proxy.login("xxx","xxx");

缺点:

  1. 代理对象的一个接口只服务于一种类型的对象
  2. 如果要代理的方法很多,要为每种方法进行代理
  3. 静态代理在程序规模稍大时无法胜任

动态代理

  1. InvocationHandler接口,
  2. 操作代理对象时会执行invoke()方法,
  3. 使用Proxy.newProxyInstance()静态方法建立一个代理类对象(必须告知要代理的接口

实现

public class LoggerHandler implements InvocationHandler {

	private Object delegate;

	public Object bind(Object delegate) {
		this.delegate = delegate;
		return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object result = null;
		System.out.println("方法名是:"+method.getName());
		result = method.invoke(delegate, args);
		System.out.println("日志:" + args[0] + "于" + new Date().toLocaleString() + "登录");
		return result;
	}

}

LoggerHandler lh = new LoggerHandler();
UserService us = (UserService) lh.bind(new UserServiceImpl());
us.login("adi", "123");

AOP (Aspect-Oriented Programmin) 面向切面编程

独立于Spring框架,但Spring实现了AOP。

  • 提供一种新的组织程序结构的思路
  • OOP补充,可OOP一起使用
  • OOP核心单位为类、AOP核心单位为切面

概念

  • 关注点:特定的问题、概念、或成需要达到的目标。
  • 横切关注点:一个关注点被多个类或者方法引用
  • 切面:一个切面是对一个横切关注点的模块化,(实现关注点的方法)
  • 连接点:程序执行过程中的某个点,方法调用或者抛出异常
  • 通知:在特定连接点应执行动作(定义何时)
  • 切入点:什么地方植入通知(何地
  • 目标对象:被切面所通知的对象(被代理的对象
  • 织入:将切面应用到目标对象,从而创建新的代理对象

通知类型

  • 前置通知(Before advice): 在某连接点之前执行的通知
  • 后置通知(After returning advice): 在某连接点正常完成后执行的通知
  • 异常通知(After throwing advice): 在方法抛出异常退出时执行的通知
  • 最终通知(After finally advice): 当某连接点退出的时候执行的通知
  • 环绕通知(Around advice): 包围一个连接点的通知,这是最强大的一种通知类型

AOP实现方式

SpringAPI传统方式

前置通知 MethodBeforeAdvice接口

public class Md5Advice implements MethodBeforeAdvice {

	@Override
	public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
		System.out.println(arg0.getName());
		System.out.println(arg1[1]);
		System.out.println(arg2.getClass().getName());
		String newPwd = Md5Encode.getMD5(arg1[1].toString().getBytes());
		arg1[1] = newPwd;
	}
}

后置通知 AfterReturningAdvice接口

public class LogAdvice implements AfterReturningAdvice {

	@Override
	public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
		System.out.println(arg0);
		System.out.println(arg3);
		System.out.println(arg2[0]+"在"+new Date().toLocaleString()+"登录");
		arg0=false;
	}

}

环绕通知 MethodInterceptor接口

也可决定方法是否执行。

public class TimeAdvice implements MethodInterceptor {

	@Override
	public Object invoke(MethodInvocation arg0) throws Throwable {
		System.out.println("方法开始执行时间:"+new Date().toLocaleString());
		
		Object result = arg0.proceed();
		
		System.out.println("方法结束执行时间:"+new Date().toLocaleString());
		return result;
	}
}

异常通知 ThrowsAdvice接口

public class RegistExceptionAdvice implements ThrowsAdvice {
	
	public void afterThrowing(Exception e) {
		System.out.println("异常通知发生异常"+e.getMessage());
	}
}

xml配置代理模式

<!--注入代理的接口-->
<!--注入通知,list配置-->
<!--注入代理目标-->
<bean>
	<bean id="UserProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="proxyInterfaces" value="com.user.UserService"></property>
		<property name="interceptorNames">
			<list>
				<value>Md5Advice</value>
				<value>TimeAdvice</value>
			</list>
		</property>
		<property name="target" ref="UserServiceImpl"></property>
	</bean>
</beans>

使用

ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
//接口获取代理对象
UserService us = (UserService)ctx.getBean("UserProxy");
us.regist("adi", "123");
boolean bool = us.login("adi", "123");
System.out.println(bool);

纯POJO类-基于xml

  • 通知类java

  1. 方法名自己写
public class MyAdvice {
	
	public void before(JoinPoint joinPoint) {
		System.out.println("前置通知");
	}
	//参数和配置名一致
	public void afterReturning(JoinPoint joinPoint, Object result) {
		System.out.println("后置通知");
	}
	
	public void after(JoinPoint joinPoint) {
		System.out.println("最终通知");
	}
	
	public Object around(ProceedingJoinPoint joinPoint) {
		System.out.println("环绕通知");
		Object[] args = joinPoint.getArgs();
		Object result = null;
		try {
			result = joinPoint.proceed(args);
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("环绕通知");
		return result;
	}
	
	public void throwMethod(Exception ex) {
		System.out.println("异常通知");
	}

}
  • xml配置

    • 导入aop的命名空间
    • 配置bean元数据
    • 配置<aop:config>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	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.3.xsd
	http://www.springframework.org/schema/context 
	http://www.springframework.org/schema/context/spring-context-4.3.xsd
	http://www.springframework.org/schema/aop  
	http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
	">
	<!-- 业务逻辑 -->
	<bean id="UserServiceImpl" class="com.user.UserServiceImpl"></bean>
	<!-- advices -->
	<bean id="MyAdvice" class="com.advices.MyAdvice"></bean>
	<aop:config>
		<!--
                定义切点
		<aop:pointcut expression="execution(* com.user.*.*(..))" id="mypc"/>
                切面
		<aop:aspect id="MyAspect" ref="MyAdvice" order="2"> 
                        通知
			<aop:before method="before" pointcut-ref="mypc"/>
		</aop:aspect>
		-->
		
		<aop:aspect id="MyAspect" ref="MyAdvice" order="2">
			<aop:before method="before" pointcut="execution(* com.user.*.*(..))" />
			<aop:after-returning method="afterReturning" pointcut="execution(* com.user.*.*(..))" returning="result"/>
			<aop:around method="around" pointcut="execution(* com.user.*.*(..))" />
			<!--aop:after-throwing method="throwExMethod" pointcut="execution(* service.*.*(..))" throwing="ex"/-->
			<aop:after method="after" pointcut="execution(* com.user.*.*(..))" />
		</aop:aspect>
	</aop:config>
</beans>

执行顺序

前置通知
环绕通知
业务执行....
最终通知
环绕通知
后置通知

纯POJO类-基于注解驱动

  • xml配置

    • aop自动代理
    • 注解配置
      <aop:aspectj-autoproxy />属性proxy-target-class
    • false ,jdk动态代理织入增强-代理模式
    • true,cglib动态代理织入增强-代理模式(不用加接口)
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	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.3.xsd
	http://www.springframework.org/schema/context 
	http://www.springframework.org/schema/context/spring-context-4.3.xsd
	http://www.springframework.org/schema/aop  
	http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
	">
	<aop:aspectj-autoproxy />
	<context:annotation-config />
	<context:component-scan base-package="com" />
</beans>

注解配置

  • 组件注解
  • 切面注解
  • 前置通知注解
  • 后置通知注解
  • 环绕通知注解
  • 最终通知注解
  • 异常通知注解
  • @Order(int)同类型通知排序(针对类),在类上加
@Component
@Aspect
public class MyAdvice {
	
//	@Before("execution(* com.user.*.*(..))")
	public void before(JoinPoint joinPoint) {
		System.out.println("前置通知");
		
	}
	
//	@AfterReturning(pointcut = "execution(* com.user.UserServiceImpl.*(..))", returning="result")
	public void afterReturning(JoinPoint joinPoint, Object result) {
		System.out.println("后置通知");
	}
	
//	@After("execution(* com.user.*.*(..))")
	public void after(JoinPoint joinPoint) {
		System.out.println("最终通知");
	}
	
	@Around("execution(* com.user.*.*(..))")
	public Object around(ProceedingJoinPoint joinPoint) {
		System.out.println("环绕通知");
		Object[] args = joinPoint.getArgs();
		Object result = null;
		try {
			result = joinPoint.proceed(args);
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("环绕通知");
		return result;
	}
	
//	@AfterThrowing(value = "execution(* com.user.*.*(..))", throwing="ex")
	public void throwMethod(Exception ex) {
		System.out.println("异常通知");
	}

}

通知顺序

环绕通知
前置通知
业务执行,登录成功
环绕通知
最终通知
后置通知

后置通知和最终通知的区别

后置通知如果出现异常,无法返回数据时,后置通知无法执行,但是最终通知可以执行。

不管有没有异常,最终通知一定执行,
没有异常,返回值正常时,后置通知才执行。

使用AspectJ切面

  • 自己找素材看

每日英语

proxy 代理
Invocation 求助,调用
Handler 组织者、操作者
pointcut 切点

posted @ 2020-08-26 00:28  不爱学习的小策  阅读(211)  评论(0编辑  收藏  举报