动态代理是一种设计模式. 在 Spring 中, 有俩种方式可以实现动态代理 --JDK 动态代理和 CGLIB 动态代理.
JDK 动态代理
首先定义一个人的接口:
- public interface Person {
- void study();
- }
然后接上一个 Student class
- public class Student implements Person{
- @Override
- public void study() {
- System.out.println("学生要学习");
- }
- }
然后我们创建一个动态代理类, 需要实现 InvocationHandler 接口
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- public class AnimalInvocationHandler implements InvocationHandler {
- private Object target;
- public Object bind(Object target) {
- this.target = target;
- return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
- }
- @Override
- public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
- Object result = null;
- System.out.println("---- 调用前处理");
- result = method.invoke(target,args);
- System.out.println("---- 调用后处理");
- return result;
- }
- }
然后给一个 main 方法.
- public class Test {
- public static void main(String[] args) {
- Student dog = new Student();
- AnimalInvocationHandler ani = new AnimalInvocationHandler();
- Person proxy = (Person)ani.bind(dog);
- proxy.study();
- }
- }
运行结果如下.
想要在 student 对象前后加上额外的逻辑, 可以不直接修改 study 方法.
这就是 AOP 实现的基本原理, 只是 Spring 不需要开发人员自己维护.
但是这么实现有个缺点, 那就是必须实现接口. 烦死了. 所以我们要用 CGLIB 了.
CGLIB 动态代理
首先把. 这玩意是个开源包.
给个下载地址:
- https://repo1.maven.org/maven2/cglib/cglib/3.3.0/cglib-3.3.0.jar
- https://repo1.maven.org/maven2/org/ow2/asm/asm/7.0/asm-7.0.jar
下载之后添加到 eclipse 里面.
首先是 Teacher 类
- public class Teacher {
- public void play(){
- System.out.println("老师改作业");
- }
- }
然后是这个, 需要重写 MethodInterceptor
- import java.lang.reflect.Method;
- import.NET.sf.cglib.proxy.MethodInterceptor;
- import.NET.sf.cglib.proxy.MethodProxy;
- public class TeacherMethodInterceptor implements MethodInterceptor {
- @Override
- public Object intercept(Object o,Method method,Object[] objects,MethodProxy methodProxy) throws Throwable{
- System.out.println("调用前...");
- Object obj = methodProxy.invokeSuper(o,objects);
- System.out.println("调用后...");
- return obj;
- }
- }
main 方法如下所示
- import.NET.sf.cglib.proxy.Enhancer;
- public class CglibDemo {
- public static void main(String[] args) {
- Enhancer en = new Enhancer();
- en.setSuperclass(Teacher.class);
- en.setCallback(new TeacherMethodInterceptor());
- Teacher t = (Teacher)en.create();
- t.play();
- }
- }
运行结果如下:
这就实现了横向编程.
AOP
面向切面编程是面向对象编程的一种补充.
以 Java 为例, 提供了封装, 继承, 多态等概念, 实现了面向对象编程. 但是假如我们要实现以下场景.
给每个类设置权限拦截器.
如果不用 AOP 思想, 我们都能疯掉. 因为会有大量代码重用重写. 但是 AOP 的出现提供 "横向" 的逻辑, 将与多个对象有关的公共模块分装成一个可重用模块, 并且将这个模块整合成 Aspect, 即切面.
AOP 的一些概念, 整理成表如下:
名称 | 概念 |
---|---|
横切关注点 | 一个横切需求 (例如日志) |
切面 | 一个横切关注点可能有多个对象 |
连接点 | 一个方法的执行 |
切入点 | AspectJ 的切入点语法 |
通知 | 拦截后的动作 |
目标对象 | 业务中需要增强的对象 |
织入 | 将切面作用到对象 |
引入 | 不用定义接口就能使用其中的方法 |
Spring 的 AOP 实现
由于 Spring framework 的依赖过多, 具体哪个 jar 包缺了啥报啥错啥版本能把我弄吐血.
为了头发, 我这里采用 SpringBoot 来实现 AOP
首先打开 InteliJ
new Project 完之后一直点就行.
啥都不用勾选.
然后我们会发现
启动如果没报错, 那就完事.
报错了去搜搜怎么搭建 Spring-boot. 都是一键生成的.
下面开始敲代码: 注意! 一个东西都不能落下!!
首先我们修改一下 pom 文件
我的 pom 文件如下所示:
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.1.8.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <groupId>com.example</groupId>
- <artifactId>demo1</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>demo1</name>
- <description>Demo project for Spring Boot</description>
- <properties>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
完整路径如下所示:
首先是 Fruit 类
- package com.example.demo1;
- public interface Fruit {
- void eat();
- }
然后是 Apple 类
- package com.example.demo1;
- import org.springframework.stereotype.Component;
- @Component
- public class Apple implements Fruit {
- @Override
- public void eat() {
- System.out.println("吃苹果");
- }
- }
Orange 类
- package com.example.demo1;
- import org.springframework.stereotype.Component;
- @Component
- public class Orange implements Fruit {
- @Override
- public void eat() {
- System.out.println("吃桔子");
- }
- }
然后是 FruitAnnotationHandler 类
@execution 的含义是匹配该包下任意类的任意方法名的任意入参的任意方法返回值.
@Aspect 用来声明这是切面, 注解 "@Before" 用来表明前置通知,"@After 用来表示后置通知"
- package com.example.demo1;
- import org.aspectj.lang.annotation.After;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.aspectj.lang.annotation.Pointcut;
- import org.springframework.stereotype.Component;
- @Component
- @Aspect
- public class FruitAnnotationHandler {
- /**
- * 定义切点
- */
- @Pointcut("execution(* com.example.demo1.*.*(..))")
- public void eatFruit(){
- }
- /**
- * 前置通知
- */
- @Before("eatFruit()")
- public void startEatFruit(){
- System.out.println("要开始吃了");
- }
- /**
- * 后置通知
- */
- @After("eatFruit()")
- public void endEatFruit(){
- System.out.println("吃完了");
- }
- }
最后是 Application 类
- package com.example.demo1;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.context.ApplicationContext;
- @SpringBootApplication
- public class Demo1Application {
- public static void main(String[] args) {
- ApplicationContext App = SpringApplication.run(Demo1Application.class, args);
- Fruit apple = App.getBean(Apple.class);
- Fruit orange = App.getBean(Orange.class);
- apple.eat();
- orange.eat();
- }
- }
然后运行~
运行成功完美!
其实 SpringBoot 默认的 AOP 实现就是使用的 CGLib 代理.
我们并不用定义哪个 Fruit 接口.
但是你如果脾气倔, 非要用 jdk 代理的话.
把这个加上就 OK 了.
如果你没定义接口的话, 下场就是这样.
- Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo1.Apple' available
- at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:346)
- at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:337)
- at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)
- at com.example.demo1.Demo1Application.main(Demo1Application.java:11)
至此我们就完成了 AOP 的入门
来源: https://www.cnblogs.com/godoforange/p/11587321.html