「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」
1、概述
AOP:Aspect Oriented Programming 面向切面编程 OOP:Object Oriented Programming 面向对象编程 面向切面编程:基于OOP的新的编程思想,OOP面向的主要对象是类,AOP面向的主要对象则是切面。 AOP是Spring中重要的核心点。作用就是在不修改原有代码的情况下,增强与主要业务没有关系的公共功能代码到之前写好的方法中指定位置。一般在处理日志、安全管理、事务管理等 方面有非常重要的作用。
2、为什么要引入AOP?
@Repository
public class UserDaoImpl implements UserDao {
@Override
public User getUser() {
System.out.println("获取User");
return new User();
}
@Override
public int insertUser() {
System.out.println("新增User");
return 0;
}
@Override
public int deleteUser() {
System.out.println("删除User");
return 0;
}
}
以上的代码很简单,此时如果需要在每个方法执行前后分别打印我的名字,就只能在service层调用dao层方法的前后分别处理一下,代码如下:
@Service
public class UserServiceImpl implements UserService {
@Resource
UserDao userDao;
@Override
public User getUser() {
System.out.println("一右四分之一");
User user = userDao.getUser();
System.out.println("一右四分之一");
return user;
}
@Override
public int insertUser() {
System.out.println("一右四分之一");
int i = userDao.insertUser();
System.out.println("一右四分之一");
return i;
}
@Override
public int deleteUser() {
System.out.println("一右四分之一");
int i = userDao.deleteUser();
System.out.println("一右四分之一");
return i;
}
}
此时如果想在DAO层每个方法执行前后输出我的性别和年龄,则需要在调用方法前后加上输出语句。如果修改输出内容,则会更加麻烦。AOP则可以解决这种麻烦。
3、AOP的原理
AOP的底层用的代理模式。代理又分为静态代理和动态代理。
3.1 静态代理
以微商为例,朋友圈的微商应该是我们最常见的静态代理对象了。她单一地代理了我们某一方面的需求。 现在来用代码表示: 首先要有接口,代理模式的实现基础就是要有一个统一的接口。
public interface IShopper {
//逛街购物
void shopping();
//付款买
void buy();
}
其次需要有实现统一接口的类和一个代理类。
public class Shoppers implements IShopper{
//购物者姓名
private String name="";
public Shoppers(String name) {
this.name = name;
}
@Override
public void shopping() {
System.out.println("逛街选衣服...");
System.out.println(name+"开始试穿");
this.name = name;
}
@Override
public void buy() {
System.out.println(name+"购买了衣服");
}
}
public class ShopperProxy implements IShopper{
private String name;
private Shoppers shoppers;
public ShopperProxy(String name) {
this.name = name;
this.shoppers = new Shoppers(name);
}
@Override
public void shopping() {
System.out.println(name+"获取了需求,出门逛街");
shoppers.shopping();
}
@Override
public void buy() {
System.out.println(name+"找到商品,付款");
shoppers.buy();
}
}
当我们真正要去买衣服时,自己不用出面,使唤代购急可。
public class StaticallyTest {
@Test
public void test(){
IShopper shopper = new ShopperProxy("代购");
shopper.shopping();
shopper.buy();
}
}
但是,代购只能帮助我们买东西,无法解决生活中的其他问题。这也是静态代理的弊端,就是需要为每个被代理的类创建一个代理类,如此会造成类爆炸。
3.2 动态代理(AOP的底层采用了此种代理方式)
3.2.1 jdk动态代理 :必须保证被代理的类实现了接口
以计算器为例,代码如下:
public interface ICalculator {
Integer add(Integer i, Integer j);
Integer sub(Integer i, Integer j);
Integer mul(Integer i, Integer j);
Integer div(Integer i, Integer j);
}
public class Calculator implements ICalculator{
@Override
public Integer add(Integer i, Integer j) {
return i+j;
}
@Override
public Integer sub(Integer i, Integer j) {
return i-j;
}
@Override
public Integer mul(Integer i, Integer j) {
return i*j;
}
@Override
public Integer div(Integer i, Integer j) {
return i/j;
}
}
/**
* 代理类
*/
public class MyInvocationHandler implements InvocationHandler {
//被代理的对象
Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
// 代理类的执行方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
result = method.invoke(target, args);
return result;
}
}
public class DynamicTest {
@Test
public void test(){
ICalculator proxy = (ICalculator) DynamicTest.createProxy(new Calculator());
Integer div = proxy.div(1, 1);
System.out.println(div);
}
/**
* 公共jdk动态代理对象生成
*/
public static Object createProxy(Object needProxy){
//类加载器 ,通常指定的被代理类的接口的类加载器
ClassLoader classLoader = needProxy.getClass().getClassLoader();
//类型, 通常指定被来历类的接口的类型
Class<?>[] interfaces= needProxy.getClass().getInterfaces();
// 传入被代理的对象
InvocationHandler handler=new MyInvocationHandler(needProxy);
// 动态创建代理类
Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
return o;
}
}
上述动态代理的实现方式调用的是jdk的基本实现。它并不完美。如果需要代理的目标对象没有实现任何接口,那么该种方式则无法为其创建代理对象。cglib动态代理可解决这个问题
3.2.2 cglib动态代理 :不需要接口
鉴于本章是AOP的笔记,该段内容不再赘述
3.3 AOP的核心概念及术语
3.3.1 切面(Aspect)
指关注点模块化,这个关注点可能会横切多个对象。日志管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP 中,切面可以使用通用类基于模式的方式(schemabased approach)或者在普通类中以@Aspect注解(@AspectJ 注解方式)来实现。
3.3.2 连接点(Join point)
在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行
3.3.3 通知(Advice)
在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。Spring AOP是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
3.3.4 切点(Pointcut)
匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用 AspectJ切点语义。
3.3.5 引入(Introduction)
声明额外的方法或者某个类型的字段。Spring 允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现 IsModified接口, 以便简化缓存机制(在 AspectJ社区,引入也被称为内部类型声明(inter))。
3.3.6 目标对象(Target object)
被一个或者多个切面所通知的对象。也被 称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现 的,那么这个对象永远是一个被代理(proxied)的对象。 AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约 (aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代 理可以是JDK动态代理或CGLIB代理。
3.3.7 织入(Weaving)
把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象的过程。这个过程可以在编译时(例如使用 AspectJ编译器)、类加载时或运行时中完成。 Spring是在运行时完成织入的。
3.4 AOP的通知类型
3.4.1 前置通知(Before advice)
在连接点之前运行但无法阻止执行流程 进入连接点的通知(除非它引发异常)。
3.4.2 后置返回通知(After returning advice)
在连接点正常完成后执行的通知(例如,当方法没有抛出任何异常并正常返回时)。
3.4.3 后置异常通知(After throwing advice)
在方法抛出异常退出时执行的通知。
3.4.4 后置通知(总会执行)(After (finally) advice)
当连接点退出的时候 执行的通知(无论是正常返回还是异常退出)。
3.4.5 环绕通知(Around Advice)
环绕连接点的通知,例如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的 行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。
3.5 AOP的应用场景
日志管理 权限认证 安全检查 事务控制