part1-简介:
- AOP(Aspect Oriented Programming):面向切面编程
- OOP(Object Oriented Programming):面向对象编程
- Aspect:方面
- Orinted:面向,面对
- 面向切面编程:基于OOP基础上新的编程思想;
指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式,面向切面编程
@AOP场景:
例:计算器运行计算方法的时候进行日志记录:
- 加日志记录:
- 直接编写在方法内部,不推荐,修改维护麻烦。
- 日志记录:系统的辅助功能;业务逻辑查看(核心功能);耦合;
- 我们希望的是:
- 业务逻辑:(核心功能);日志模块;在核心功能运行期间,自己动态的加上。
- 运行的时候,日志功能可以加上。
1.动态代理:
- 代理对象:就像对象的替身,用代理对象去执行实际方法。
- 例:(我们是想去记录日志和业务逻辑的信息)
//写一个接口 package com.atguigu.inter; public interface Calculator { public int add(int i, int j); public int sub(int i, int j); public int mul(int i, int j); public int div(int i, int j); }
//该接口的实现类 package com.atguigu.impl; import com.atguigu.inter.Calculator; public class MyMathCalculator implements Calculator { @Override public int add(int i, int j) { int result = i + j; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; } }
//测试 import com.atguigu.impl.MyMathCalculator; import com.atguigu.inter.Calculator; import com.atguigu.proxy.CalculatorProxy; import org.junit.Test; public class AOPTest { @Test public void test(){ // Calculator calculator = new MyMathCalculator(); // calculator.add(1, 2); //如果拿到了这个对象的代理对象,代理对象来执行方法。 Calculator calculator = new MyMathCalculator(); Calculator proxy = CalculatorProxy.getProxy(calculator); int result = proxy.add(2, 1); System.out.println(result); } }
//第一种:写一个日志工具类,在方法执行前后执行,不可取 package com.atguigu.utils; public class LogUtils { public void logStart(Object... objects){ System.out.println("【xxx】方法开始了,它使用的参数是["+ objects +"]"); } }
//动态代理的方法 package com.atguigu.proxy; import com.atguigu.inter.Calculator; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 帮Calculator生成代理对象的类。 * 对象方法是:Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) * Calculator calculator:传入的是被代理对象 */ public class CalculatorProxy { //拿到代理对象的方法,为传入的对象生成一个代理对象 public static Calculator getProxy(final Calculator calculator){ //被代理对象的类加载器 ClassLoader loader = calculator.getClass().getClassLoader(); //被代理对象所实现的接口 Class<?>[] interfaces = calculator.getClass().getInterfaces(); //目标方法执行器,帮我们目标对象执行目标方法 InvocationHandler h = new InvocationHandler() { /** * * @param o:这是代理对象,这是给jdk使用的,任何时候都不要动这个对象 * @param method:当前将要执行的目标对象的方法 * @param objects:这个方法调用时,外界传入的参数值 * @return * @throws Throwable */ @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("这是动态代理将要执行该方法!!!"); //利用反射执行目标方法,result是目标方法执行后的返回值 Object result = method.invoke(calculator, objects); return result; } }; //Proxy为目标对象创建代理对象 Object proxy = Proxy.newProxyInstance(loader, interfaces, h); return (Calculator) proxy; } }
2.动态代理加日志总结:
- 例:日志的工具类,加上动态代理
//工具类 package com.atguigu.utils; import java.lang.reflect.Method; import java.util.Arrays; public class LogUtils { //日志记录 public static void logStart(Method method, Object[] objects){ System.out.println("[" + method.getName() + "]" + "方法开始执行了,用的参数列表是:[" + Arrays.asList(objects) + "]"); } public static void logReturn(Method method, Object[] objects){ System.out.println("[" + method.getName() + "]" + "方法正常执行完成了。"); } public static void logException(Method method, Exception e){ System.out.println("[" + method.getName() + "]" + "出现了异常,异常原因是:" + e.getCause()); } public static void logEnd(Method method){ System.out.println("[" + method.getName() + "]" + "方法执行结束了。"); } }
//代理类的改进 package com.atguigu.proxy; import com.atguigu.inter.Calculator; import com.atguigu.utils.LogUtils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; /** * 帮Calculator生成代理对象的类。 * 对象方法是:Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) * Calculator calculator:传入的是被代理对象 */ public class CalculatorProxy { //拿到代理对象的方法,为传入的对象生成一个代理对象 public static Calculator getProxy(final Calculator calculator){ //被代理对象的类加载器 ClassLoader loader = calculator.getClass().getClassLoader(); //被代理对象所实现的接口 Class<?>[] interfaces = calculator.getClass().getInterfaces(); //目标方法执行器,帮我们目标对象执行目标方法 InvocationHandler h = new InvocationHandler() { /** * * @param o:这是代理对象,这是给jdk使用的,任何时候都不要动这个对象 * @param method:当前将要执行的目标对象的方法 * @param objects:这个方法调用时,外界传入的参数值 * @return * @throws Throwable */ @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("这是动态代理将要执行该方法!!!"); //利用反射执行目标方法,result是目标方法执行后的返回值 Object result = null; try { // System.out.println("[" + method.getName() + "]" + "方法开始执行了,用的参数列表是:[" + Arrays.asList(objects) + "]"); LogUtils.logStart(method, objects); result = method.invoke(calculator, objects); // System.out.println("[" + method.getName() + "]" + "方法正常执行完成了。"); LogUtils.logReturn(method, objects); } catch (Exception e) { // System.out.println("[" + method.getName() + "]" + "方法出现异常,异常原因是" + e.getCause()); LogUtils.logException(method, e); } finally { // System.out.println("[" + method.getName() + "]" + "方法执行结束了。"); LogUtils.logEnd(method); } return result; } }; //Proxy为目标对象创建代理对象 Object proxy = Proxy.newProxyInstance(loader, interfaces, h); return (Calculator) proxy; } }
- 动态代理也是有缺陷的:
- jdk默认的动态代理,如果目标对象没有实现任何接口,是无法为目标对象创建任何代理对象的。
- 代理对象和被代理对象唯一能产生的关联,就是实现了同一接口。
- 动态代理已经可以看成是切面编程了。
- 但是Spring实现以上的动态代理难,所以Spring出现了AOP功能,AOP底层就是动态代理,可以利用Spring一句代码都不写,就可以创建动态代理。
- AOP实现简单,没有强制要求目标对象必须实现接口。
3.AOP简单总结:
- 指在程序运行期间,将某段代码(日志)动态的切入到指定方法(加减乘除)的指定位置(方法开始之前或者方法开始之后)进行运行的这种编程方式,称为面向切面编程。(Spring可以简化面向切面编程)
part2-专业术语:
- 横切关注点:
- 通知方法:(就像日志工具类LogUtils里面的LogStart、LogReturn等等方法)
- 切面类:(就像日志工具类LogUtils)
- 连接点:每一个方法的每一个位置就是一个连接点。
- 切入点:红色部分,我们真正需要执行日志记录的地方。
- 图见:
part3-简单配置:
1、AOP加日志保存到数据库中;
2、AOP做权限验证;
3、AOP还可以做安全检查;
4、AOP做事务控制;
part4-使用场景:
1、导包:
- 注意除了基础包,spring支持面向切面编程的包也需要。
- 注意:除了基础的面向切面编程的aspect包,还有加强版的包(3个),加强版的包的意思是即 使目标对象没有实现任何接口也能创建动态代理。
2、写配置:
- 将目标类(例:MyMathCalculator类)和切面类(封装了通知方法(在目标方法执行前后执行的方法)的类,例:LogUtils类)加入到IOC容器中。
- 还应该告诉Spring那个是切面类。@Aspect.
- 告诉Spring这些方法何时何地运行就额可以了。
package com.atguigu.utils;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Component
@Aspect
public class LogUtils {
//日志记录
/**
* 告诉spring每个方法都什么时候运行这些方法。
* @Before:在目标方法之前运行;前置通知。
* @After:在目标方法运行结束之后执行;后置通知。
* @AfterReturning:在目标方法正常返回之后;返回通知。
* @AfterThrowing:在目标方法抛出异常之后运行;异常通知;
* @Around:环绕;环绕通知。
*
* 例:
* try{
* @Before
* method.invoke(obj, args);
* @AfterReturning
* }catch(e){
* @AfterThrowing
* }finally{
*@After
* }
*/
//第一个方法:想在执行目标方法之前运行,写切入点表达式
//execution(访问权限符 返回值类型 方法签名)
@Before("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public static void logStart(){
System.out.println("[]" + "方法开始执行了,用的参数列表是:[]");
}
//想在目标方法正常执行完成之后执行
@AfterReturning("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public static void logReturn(){
System.out.println("[]" + "方法正常执行完成了。");
}
//想在目标方法异常时执行
@AfterThrowing("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public static void logException(){
System.out.println("[" + "出现了异常,异常原因是:" );
}
//想在目标方法执行完成之后执行
@After("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public static void logEnd(){
System.out.println("]" + "方法执行结束了。");
}
}
- 开启基于注解的AOP功能。注意:头名称空间的配置信息,不要少了。
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
- 完成的头名称空间代码如下:
<?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: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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!--开启基于注解的aop功能,aop名称空间-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
3、测试:
import com.atguigu.impl.MyMathCalculator;
import com.atguigu.inter.Calculator;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AOPTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void test(){
//1.从ioc容器中拿到目标对象
//注意:如果想要用类型获取,一定要用接口类型,不要用它的本类
Calculator bean = ioc.getBean(Calculator.class);
bean.add(2, 1);
}
}