1. AOP就是面向切面编程
面向切面是面向对象中的一种方式而已。在代码执行过程中,动态嵌入其他代码,叫做面向切面编程。常见的使用场景:
i :日志
ii: 事务
iii:数据库操作
....
2. 面向切面编程的几个核心概念
IOC/DI 本质是就是Java反射+XML解析。
AOP本质上Java动态代理
i:切点:要添加代码的地方称作切点
ii:切面:切点+通知
iii:通知(增强):向切点插入的代码称为通知Advice
iv:连接点:切点的定义
首先介绍什么是动态代理,举一个计算器的例子
1、相关类 没有引用依赖
2、定义接口
public interface Calculator {
int add(int a,int b);
void min(int a,int b);
}
3、创建接口的实现类
public class CalcultorImpl implements Calculator{
@Override
public int add(int a, int b) {
System.out.println(a+"+"+b+"="+(a+b));
return a+b;
}
@Override
public void min(int a, int b) {
System.out.println(a-b);
}
}
4、定义计算器的代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class proxy {
public Object getInstance(Object obj){
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("add")) {
//加日志
} else {
//不加日志
}
long start = System.nanoTime();
//Object invoke = method.invoke(obj,new Object[]{3,3}); //直接将3,3赋值过去了,main里的值被覆盖
Object invoke = method.invoke(obj, args);//获得main里面的值
long end = System.nanoTime();
System.out.println(end - start);
return invoke;
}
});
}
}
5、结果
public class main {
public static void main(String[] args) {
//获取代理对象
Calculator instance = (Calculator) new proxy().getInstance(new CalcultorImpl());
instance.add(9,9);
instance.min(6,7);
}
}
6、结果
9+9=18
459534
-1
236532
spring中AOP的实现
通过注解定义拦截规则
1、引入的依赖以及相关类
2、定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAction {
}
3、定义创建实体类并创建getset方法
import org.springframework.stereotype.Component;
@Component
public class MyCalcultor {
@MyAction
public int add(int a, int b){
//int a = 1/0; 测试异常
System.out.println("add在执行啊---");
return a+b;
}
// @MyAction
public String sayHello(String name) {
return "hello " + name + " !";
}
@MyAction
public void min(int a, int b) {
}
}
4、java配置
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @EnableAspectJAutoProxy注解表示开启Java自动代理
*/
@Configurable
@EnableAspectJAutoProxy
@ComponentScan("com.tang")
public class JavaConfig {
}
实现
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 这个类就是切面,该类中的内容包含两大块:切点+通知
*
* @Componet只是将该类注册到Spring容器中
* @Aspect表示该类是一个切面
*/
@Component
@Aspect
public class LogAspect {
/**
* 可以单独定义一个方法,专门用来定义切点,然后在其他方法中引用
*/
@Pointcut("@annotation(MyAction)")
public void pointcut() {
}
/**
* @param point 参数是指连接点,从这个参数中,可以获取方法名方法参数等信息
* @Before注解表示这是一个前置通知,该通知会在目标方法执行之前执行
*/
@Before("pointcut()")
public void before(JoinPoint point) {
String name = point.getSignature().getName();
System.out.println(name + "方法开始执行了...");
}
/**
* 这是一个后置通知,后置通知在目标方法执行结束后执行
* @param point
*/
@After("pointcut()")
public void after(JoinPoint point) {
System.out.println(point.getSignature().getName() + "方法执行结束了...");
}
/**
* 这是一个返回通知,可以获取目标方法的返回值
*
* 方法中的参数名和注解中的参数名是对应的,参数值就是目标方法的返回值
*
* 需要注意返回值的类型,返回值的类型必须是参数的类或者子类
*/
@AfterReturning(value = "pointcut()",returning = "result")
public void returning(JoinPoint point, Object result) {
System.out.println(point.getSignature().getName() + "方法的返回值是:" + result);
}
/**
* 这是一个异常通知,该通知在方法执行抛出异常时执行
*
* 如果目标方法没有异常,则通知不会被触发
*/
@AfterThrowing(value = "pointcut()",throwing = "e")
public void throwing(JoinPoint point,Exception e) {
System.out.println(">>>>>"+e.getMessage());
}
/**
* 环绕通知是上面四种通知的集大成者,因为这个通知中包含了上面四种通知的功能
*
* @param pjp 这个参数类似于Method对象
* @return
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) {
//pjp.proceed() 就类似于method.invoke()
try {
//写在这个位置的,就是前置通知
Object proceed = pjp.proceed();
//写在这个位置的,就是后置通知
return proceed;
} catch (Throwable throwable) {
//写在这个位置的,就是异常通知
throwable.printStackTrace();
}
return null;
}
}
调用方法
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
MyCalcultor bean = ctx.getBean(MyCalcultor.class);
bean.add(7, 8);
//bean.sayHello("zhangsan");
//bean.min(9, 3);
}
}
结果
add方法开始执行了...
add在执行啊---
add方法执行结束了...
add方法的返回值是:15
5、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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="com.tang.MyCalcultor" id="myCa"/>
<bean class="com.tang.LogAspect2" id="logAspect2"/>
<aop:config>
<aop:aspect ref="logAspect2" ><!--依赖-->
<aop:pointcut id="pc1" expression="execution(* com.tang.*.*(..))"/>
<aop:before method="before" pointcut-ref="pc1"/>
<aop:after method="after" pointcut-ref="pc1"/>
<aop:after-returning method="returning" pointcut-ref="pc1" returning="result"/>
</aop:aspect>
</aop:config>
</beans>
实现
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 这是一个Advice
*/
public class LogAspect2 {
public void before(JoinPoint point) {
String name = point.getSignature().getName();
System.out.println(name + "方法开始执行了...");
}
public void after(JoinPoint point) {
System.out.println(point.getSignature().getName() + "方法执行结束了...");
}
public void returning(JoinPoint point, Object result) {
System.out.println(point.getSignature().getName() + "方法的返回值是:" + result);
}
public void throwing(JoinPoint point, Exception e) {
System.out.println(">>>>>" + e.getMessage());
}
public Object around(ProceedingJoinPoint pjp) {
//pjp.proceed() 就类似于method.invoke()
try {
//写在这个位置的,就是前置通知
Object proceed = pjp.proceed();
//写在这个位置的,就是后置通知
return proceed;
} catch (Throwable throwable) {
//写在这个位置的,就是异常通知
throwable.printStackTrace();
}
return null;
}
}
调用方法进行测试
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main2 {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
MyCalcultor myCalcultor = (MyCalcultor) ctx.getBean("myCa");//与xml中id保持相同
myCalcultor.add(1,2);
}
}
结果
add方法开始执行了...
add在执行啊---
add方法执行结束了...
add方法的返回值是:3
Process finished with exit code 0
注意点
这种方式实际上破坏了最小侵入式原则。
通过如下方法简化切点的定义:
/**
* 可以单独定义一个方法,专门用来定义切点,然后在其他方法中引用
*/
@Pointcut("@annotation(MyAction)")
public void pointcut() {
}
通过方法路径定义拦截规则,实际上只是切点的定义方式变了,其他的还是一样的。
/**
* 可以单独定义一个方法,专门用来定义切点,然后在其他方法中引用
* 第一个* 表示目标方法的返回值,可以是任意类型,*表示任意类型
* 第二个*表示类中的任意方法
* 两个.表示方法的参数任意(可有可无,类型任意)
*
* 例如,想精确定位到add方法,步骤如下:
* execution(int com.itbaizhan.service.MyCalcultor.add(int,int))
*/
@Pointcut("execution(* com.itbaizhan.service.*.*(..))")
public void pointcut() {
}
使用表达式,可以较好的满足最小侵入式原则。