第3章-第5节
一、知识点
动态代理、jdk动态代理、cglib动态代理、AOP、SpringAOP
二、目标
-
理解什么是动态代理和它的作用
-
学会使用JAVA进行动态代理
-
理解什么是AOP
-
学会使用AOP
-
理解什么是AOP的切入点
三、内容分析
-
重点
-
理解什么是动态代理和它的作用
-
理解什么是AOP
-
学会使用AOP
-
-
难点
-
理解什么是AOP
-
学会使用AOP
-
四、内容
1、动态代理
1.1 什么是代理
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。这样做可以在目标对象实现的基础上,增强额外的功能操作,扩展目标对象的功能
开发系统的时候,第一个版本的系统比较简陋,只需要实现基本功能,程序使用了一段时间后,准备二开,在二开的项目需求中需要对第一个版本的所有操作都做一个日志的记录,以便于后续的系统维护和分析,这个时候就可以用上代理了
1.2 什么是动态代理
-
特点:字节码随用随创建,随用随加
-
作用:不修改源码,对方法增强,在不改变原有代码的情况下,对方法进行增强
-
在程序运行的时候动态生成代理类进行增强操作
2、动态代理的使用
2.1 使用JDK实现动态代理
-
新建动态代理类,实现InvocationHandler接口
public class Proxy1 implements InvocationHandler {private Object obj = new Object();@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 要执行的方法,传入一个对象,所以需要创建一个对象method.invoke(obj);return null;} }
-
使用Proxy类中的newProxyIntance方法,并且被代理类至少实现一个接口,否则不能用
-
三个参数
-
ClassLoader 类加载器,用于加载代理对象类,和被代理对象使用相同的加载器,固定写法
-
Class[] 被代理对象实现的所有接口的字节码数组,用于代理的接口,固定写法
-
InvocationHandler 动态代理方法在执行时,会调用里面的invoke方法去执行
-
public class Proxy1 implements InvocationHandler {private Object obj = new Object();// 实现一个方法,方法名可以自己定// 传入要调用的对象去调用// 调用这个方法,传入要增强的对象public Object newProxyInstance(Object obj) {// 把传进来的对象赋值给成员变量,给invoke使用this.obj = obj;// 使用Proxy类中的newProxyIntance方法return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);}
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 要执行的方法,传入一个对象,所以需要创建一个对象method.invoke(obj);return null;} }
-
-
创建接口并实现
public interface StudentService {void queryAll(); }
-
调用
Proxy1 proxy1 = new Proxy1(); StudentServiceImpl service = new StudentServiceImpl(); // 会调用newProxyInstance返回一个新对象 StudentService service1 = (StudentService) proxy1.newProxyInstance(service); service1.queryAll();
-
重写invoke方法
调用的时候会先执行newProxyInstance方法,返回一个新的对象
调用方法的时候会执行invoke方法,在invoke方法中执行我们调用的方法,所以可以在invoke中编写我们的增强操作
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long start = System.currentTimeMillis();method.invoke(obj);// args可以传递参数// 获取参数// int id = (int)args[0];// method.invoke(obj, args);long end = System.currentTimeMillis();System.out.println(end - start);return null; }
2.2 使用cglib实现动态代理
增强的类不需要实现接口
-
引入包cglib
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version> </dependency>
-
封装代理类
public class Proxy02 implements MethodInterceptor {private Object obj = new Object();// 调用这个方法,传入要增强的对象public Object newProxyInstance(Object obj) {this.obj = obj;// 字节码增强器,用来为无接口的类创建代理Enhancer enhancer = new Enhancer();// 设置要继承的类enhancer.setSuperclass(obj.getClass());// 动态代理方法在执行时,会调用里面的invoke方法去执行enhancer.setCallback(this);// 创建代理return enhancer.create();}@Overridepublic xxx {long start = System.currentTimeMillis();method.invoke(obj, objects);long end = System.currentTimeMillis();System.out.println(end - start);return null;} }
JDK实现动态代理的底层原理是基于反射,创建出来的代理对象和目标对象是兄弟关系
cglib实现动态代理的底层原理是基于继承,创建出来的代理对象和目标对象是父子关系
3、AOP
3.1 什么是AOP
AOP(Aspect Oriented Programming:面向切面编程),在不修改源代码的情况下,给程序动态统一添加功能的一种技术
作用:利用AOP对业务逻辑的各个部分进行隔离,降低业务逻辑的耦合性,提高程序的可重用型和开发效率。
优势:减少重复代码,提高开发效率,维护方便
实现方式:使用动态代理技术
基于动态代理技术来实现功能,将功能功能提取出来,如下图,原本要写四个接口那么验证参数、日志都得在代码中写好,然后再去实现业务代码,比较繁琐,使用切面以后将验证参数、日志等功能提取出来,四个接口我们只需要通过配置就可以直接调用对应的功能,让我们能够直接专注于写业务代码
3.2 AOP相关术语
-
目标对象(Target)
目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。
-
织入(Weaving)
织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成,就是指将切面代码插入到目标对象的过程。
-
切面(Aspect)
切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。 Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
-
通知(Advice)
通知是指拦截到连接点之后要执行的代码,包括了“around”、“before”和“after”等不同类型的通知。Spring AOP框架以拦截器来实现通知模型,并维护一个以连接点为中心的拦截器链。
-
切入点(PointCut)
切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。 一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配连接点,给满足规则的连接点添加通知。可以理解为就是指切面具体织入的方法。
-
连接点(JoinPoint)
连接点(JoinPoint)指可以被切面织入的方法,如方法的调用或特定的异常被抛出。简单来说,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。
3.3 AOP的使用
3.3.1 引入spring依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.11.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version>
</dependency>
3.3.2 使用xml配置实现
-
把Bean交给spring容器管理
<bean id="logService" class="com.company.service.LogService"></bean>
-
使用aop:config标签表明开始AOP的配置
<aop:config></aop:config>
-
使用aop:aspect标签配置切面
id属性:给切面提供一个唯一标识
ref属性:指定通知(切面)类的Bean的id
<aop:config><aop:aspect id="logAdvice" ref="logService"> </aop:aspect> </aop:config>
-
在aop:aspect内部使用对应标签配置通知的类型
<aop:config><aop:aspect id="logAdvice" ref="logService"><aop:before><!-- 前置 --></aop:aspect> </aop:config>
-
切入点配置
method属性:用于指定logService类中哪个方法是前置通知
pointcut属性:切入点,指定切面具体织入的方法
<aop:config><aop:aspect id="logAdvice" ref="logService"><aop:before method="before" pointcut="execution(* *..*.*(..))"><!-- 前置 --></aop:aspect> </aop:config>
-
aop:before 前置通知
-
aop:after 后置通知,有异常也会执行
-
aop:after-returning 后置通知,有异常不执行
-
aop:after-throwing 切入内容抛出异常后处理异常的逻辑
<!-- XML要配置个throwing--> <aop:after-throwing method="test5" throwing="e" pointcut="execution(* *..UserService.*(..))"></aop:after-throwing> // e这个变量名要和xml配置的throwing的值一致 public void test5(JoinPoint point, Exception e) throws Throwable {System.out.println("test5");System.out.println(e.getMessage()); }
-
aop:around 环绕通知
// around 需要做特殊配置 public void test4(ProceedingJoinPoint point) throws Throwable {System.out.println("test4");point.proceed();System.out.println("test4结束"); }
-
-
配置表达式
pointcut属性:用于指定切入点表达式,该表达式指的是对业务层中哪些方法提供增强配置表达式
表达式写法:访问修饰符 返回值 包名.类名.方法名(参数列表)
-
标准的表达式写法
public void 包名.类名.方法名()
-
访问修饰符可以省略
void 包名.类名.方法名()
-
返回值可以使用通配符,表示任意返回
* 包名.类名.方法名()
-
包名可以使用通配符,表示任意包,但是有几级包,就得写几个*
* *.*.*.类名.方法名()
-
包名可以使用*..表示当前包及其子包
* *..类名.方法名()
-
类名和方法名都可以使用通配符
* *..*.*()
-
参数列表
可以直接写数据类型
基本数据类型直接写名称 如int
引用数据类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法
* 包名.*.*(..)
<aop:config><aop:aspect id="logAdvice" ref="logService"><aop:before method="before" pointcut="execution(* *..*.*(..))"></aop:before></aop:aspect> </aop:config>
-
-
提取表达式
多个地方使用同一个表达式的时候可以提取表达式
// 单独配置切入点 <aop:config><aop:pointcut id="logPointcut" expression="execution(* *..*.*(..))"/><aop:aspect id="logAdvice" ref="logService"><aop:before method="before" pointcut-ref="logPointcut"></aop:before></aop:aspect> </aop:config>
3.3.3 使用注解实现
-
创建配置类
@Configuration @ComponentScan("com.cpmpany.aop") @EnableAspectJAutoProxy // 开启注解的方式实现AOP public class SpringConfig {}
-
创建切面类 使用 @Aspect创建切面类 @Order(int) 控制切面类的顺序
@Component @Aspect @Order(1) public class Strong {}
-
配置注解、关键字 excution (表达式)
// 要增强的逻辑 // 第一个表示返回值,*匹配所有的返回值 // 第二个表示要增强到哪个方法上 @Before("execution(* com.cpmpany.aop.UserService.queryAll())") public void start() {System.out.println("开始"); } @After("execution(* com.cpmpany.aop.UserService.queryAll())") public void end() {System.out.println("结束"); }
-
测试类
public class TestAop01 {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);UserService service = ctx.getBean(UserService.class);service.queryAll();} }
4、小结
本章节中我们学了什么是动态代理、动态代理的作用、JAVA如何实现动态代理、理解了什么是SpringAOP以及SpringAOP的使用方式,帮助我们去掌握Spring的知识点,至此,我们的Spring相关的知识点就以及学习完毕了,希望大家可以自己多多动手去学习。
下一节中,我们将会开始SSM的第二个部分SpringMVC的使用,学习什么是SpringMVC,学会如何使用SpringMVC写一个网页接口。