Spring AOP编程
第一章 静态代理设计模式
1.1 为什么需要代理设计模式
在JavaEE分层开发中,最为重要的是Service层。
- Service层中包含了哪些代码?
- 核心功能:业务运算+DAO调用
- 额外功能:不属于业务,可有可无,代码量很小,如:事务、日志、性能等
- 额外功能书写在Service层中的弊端:
- 额外功能是可有可无的,书写在Service层中,如需改动则需要修改源码,这会很麻烦。
1.2 代理设计模式
-
概念:通过代理类,为原始类(目标类)增加额外的功能。
- 好处:利于原始类(目标类)的维护。
-
名词解释:
-
目标类(原始类):指的是 业务类(核心功能 --> 业务运算、DAO调用);
-
目标方法(原始方法):目标类(原始类)中的方法就是 目标方法(原始方法);
-
额外功能(附加功能):主要以日志、事务、性能为代表。
-
1.3 代理开发的核心要素
代理类 = 目标类(原始类)+ 额外功能 + 实现目标类(原始类)相同的接口
1.4 编码
-
创建原始类(目标类)实现核心功能:
public class UserServiceImpl implements UserService { private UserDao userDao =(UserDao) BeanFactory.getBean("userDao"); @Override public void register(User user) { userDao.save(user); } @Override public void login(String name, String password) { userDao.queryUserByUsernameAndPassword(name,password); } }
-
代理类:
//实现目标类相同的接口 public class UserServiceProxy implements UserService{ //目标类 private UserService userService = new UserServiceImpl(); @Override public void register(User user) { //额外功能 System.out.println("--------log-------"); //目标类的核心功能 userService.register(user); } @Override public void login(String name, String password) { //额外功能 System.out.println("--------log-------"); //目标类的核心功能 userService.login(name,password); } }
1.5 静态代理的问题
- 静态代理的代码量过大,不利于项目管理。
- 额外功能的维护性差
第二章 Spring的动态代理开发
-
概念:通过代理类,为原始类(目标类)增加额外的功能。
- 好处:利于原始类(目标类)的维护。
-
搭建开发环境:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.6</version> </dependency>
2.1 Spring动态代理的开发步骤
-
创建原始类对象(目标类对象)
public class UserServiceImpl implements UserService { private UserDao userDao =(UserDao) BeanFactory.getBean("userDao"); @Override public void register(User user) { userDao.save(user); } @Override public void login(String name, String password) { userDao.queryUserByUsernameAndPassword(name,password); } }
<bean class="com.itheima.basic.UserServiceImpl" id="userService"></bean>
-
额外功能:
- 实现MethodBeforeAdvice接口,额外的功能书写在接口的实现中,会在原始方法实现之前运行额外功能。
public class Before implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("--------log--------"); } }
<bean class="com.itheima.basic.Before" id="before"></bean>
-
定义切入点:
-
切入点:额外功能加入的位置。
-
目的:程序员根据自己的需要,决定额外功能加入给哪个原始方法。
<!-- 定义额外功能加入到com.itheima.basic包下的所有类的所有方法 --> <aop:config> <aop:pointcut id="pt1" expression="execution(* com.itheima.basic.*.*(..))"/> </aop:config>
-
-
组装(2、3步骤)
组装:把 切入点 和 额外功能 进行整合。
<aop:config> <aop:pointcut id="pt1" expression="execution(* com.itheima.basic.*.*(..))"/> <aop:advisor advice-ref="before" pointcut-ref="pt1"></aop:advisor> </aop:config>
-
调用
目的:获得Spring工厂创建的动态代理对象,并进行调用。
ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext3.xml"); UserService userService = context.getBean("userService", UserService.class); userService.login("aaa","sss"); //--------log-------- //query User name = aaa,password = sss
注意:
- Spring工厂通过原始对象的id值获得的是代理对象;
- 获得代理对象可以通过声明的接口类型,进行对象的存储。
2.2 动态代理细节分析
-
Spring创建的动态代理类在哪里?
- Spring框架在运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等待程序结束后,会和JVM一起消失。
-
什么叫动态字节码技术?
- 通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象。当虚拟机关闭,动态字节码即消失。
-
动态代理技术的好处:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理类文件数量过多,影响项目管理的问题。
-
动态代理编程会简化代理的开发。
- 在额外功能不改变的前提下,创建其他目标类(原始类)的代理对象时,只需要指定目标(原始)对象即可。
-
动态代理的额外功能的可维护性大大增强。
第三章 Spring动态代理的详解
3.1 额外功能的详解
- MethodBeforeAdvice接口的分析:
public class Before implements MethodBeforeAdvice {
/**
作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
方法的参数:
method:额外功能所增加给的那个原始方法。
objects:额外功能所增加给的那个原始方法参数数组。
o:代表原始对象。
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("--------log--------");
}
}
- before方法的三个参数如何使用?
- before方法的参数会根据需要使用。(基本不会使用)
- MethodInterceptor接口(方法拦截)的分析:
public class MyInterceptor implements MethodInterceptor {
/*
invoke方法的作用:额外功能书写在invoke方法中,可以执行在原始方法之前、之后、或者环绕都可以
参数:
MethodInvocation:额外功能要增加的那个原始方法。
- 使用methodInvocation.proceed()使原始方法运行.
返回值:原始方法的返回值
- methodInvocation.proceed()的返回值就是原始方法的返回值
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object object = null;
try{
System.out.println("前置方法");
object = methodInvocation.proceed();
System.out.println("后置方法");
}catch (Exception e){
System.out.println("原始方法抛出异常时需要执行的方法");
e.printStackTrace();
}
return object;
}
}
- MethodInterceptor影响原始方法的返回值:
- 原始方法的返回值,直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值。
- MethodInterceptor若想影响原始方法的返回值,不要直接返回原始方法的返回值即可。
3.2 切入点详解
切入点决定了额外功能加入的位置(方法)。
<aop:pointcut id="pt1" expression="execution(* *(..))"/>
<!--
execution(): 切入点函数
* *(..): 切入点表达式
-->
3.2.1 切入点表达式
* *(..) --> 所有方法
含义:
* --> 修饰符 返回值
* --> 方法名
() --> 参数表
.. --> 对于参数没有要求(参数有没有,参数有几个都行,参数是什么类型都行)
-
定义login方法作为切入点:
<aop:pointcut id="pt1" expression="execution(* com.itheima.basic.UserServiceImpl.login(String,String))"/> 注意: 1. 对于非java.lang包下的类,必须要写全限定类名; 2. ..是可以和具体的参数类型联用的。
-
类切入点表达式:
- 指定特定类作为额外功能的切入点,自然这个类中的所有方法,都会加上对应的额外方法。
语法1:定义com.itheima.basic包下的UserServiceImpl这个类的所有方法的切入点: <aop:pointcut id="pt1" expression="execution(* com.itheima.basic.UserServiceImpl.*(..))"/> 语法2:定义任意包下的UserServiceImpl类的所有方法的切入点: <aop:pointcut id="pt1" expression="execution(* *..UserServiceImpl.*(..))"/> 注意:切入点表达式中要用*..来表示多级包
-
包切入点表达式(更具实战价值):
- 指定特定包下的所有类的所有方法,都加上对应的额外方法:
切入点包中的所有类,必须在basic包中,不能在basic包的子包中: <aop:pointcut id="pt1" expression="execution(* com.itheima.basic.*.*(..))"/> 切入点当前包及其子包都生效: <aop:pointcut id="pt1" expression="execution(* com.itheima.baisc..*.*(..))"/>
3.2.2 切入点函数
切入点函数:用于执行切入点表达式。
-
execution:最为重要的切入点函数,功能最完整;
- 可以执行:方法切入点表达式、类切入点表达式、包切入点表达式。
- 弊端:execution在执行切入点表达式时,书写麻烦。
-
args:主要用于函数或者方法参数的匹配;
-
比如:方法参数必须得是2个字符串类型的参数:
<aop:pointcut id="pt1" expression="args(String,String)"/>
-
-
within:主要用于进行类或者包切入点表达式的匹配;
-
比如:UserServiceImpl这个类的所有方法:
<aop:pointcut id="pt1" expression="within(*..UserServiceImpl)"/>
-
比如指定basic包下的所有方法:
<aop:pointcut id="pt1" expression="within(com.itheima.basic..*)"/>
-
-
@annotation:为具有特殊注解的方法加入额外功能;
-
创建自定义注解:
package com.itheima.basic; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { }
-
在要增加额外功能的方法上加上该注解。
-
在切入点函数中指定@annotation的切入点表达式:
<aop:pointcut id="pt1" expression="@annotation(com.itheima.basic.Log)"/>
-
-
切入点函数的逻辑运算:整合多个切入点函数一起工作,进而完成更为复杂的需求。
-
and与操作
案例:login方法 同时要求有两个字符串参数 execution(* login(String,String)) 使用and与 --> execution(* login(..)) and args(String,String)
注意:与操作不能用于同类型的切入点函数。
-
or或操作
案例:register方法 与 login方法 作为切入点 execution(* login(..)) or execution(* register(..))
-
第四章 AOP编程
4.1 AOP编程的概念
AOP(Aspect Oriented Programing),面向切面编程:以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。切面 = 切入点 + 额外功能。
AOP的概念:本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护。
注意:AOP不能取代OOP,只是对OOP编程进行有利的补充。
4.2 AOP编程的开发步骤
- 原始对象;
- 额外功能(实现MethodInterceptor接口,重写invoke方法);
- 切入点;
- 组装切面。
4.3 切面的名词解释
切面:是由切入点+额外功能组成的。
第五章:AOP的底层实现原理
5.1 核心问题
- AOP如何创建动态代理类(动态字节码技术)?
- Spring工厂是如何加工创建代理对象的?
5.2 动态代理类的创建
5.2.1 JDK的动态代理
JDK的动态代理是基于接口创建动态代理对象的,当原始类实现了某接口,那么就可以使用 JDK的动态代理创建动态代理对象:
public class TestJDKProxy {
public static void main(String[] args) {
//创建原始对象
UserService userService = new UserServiceImpl();
//JDK创建动态代理对象
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
//参数1:任意借用一个类加载器,使得代理类加载进JVM
TestJDKProxy.class.getClassLoader()
//参数2:原始对象所实现的接口
, userService.getClass().getInterfaces()
//参数3:额外功能的方法
, new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//参数1:Object proxy,代表代理对象,基本不用
//参数2:Method method,代表原始方法
//参数3: Object[] args,代表原始方法的参数
//返回值:Object,原始方法的返回值
//增加额外功能
System.out.println("-------log--------");
//实现原始对象的方法
Object ret = method.invoke(userService, args);
return ret;
}
});
userServiceProxy.login("suns","123456");
}
}
5.2.2 cglib的动态代理
当要增加额外功能的原始类没有实现任何接口,那就可以使用cglib的动态代理(基于子类)创建动态代理对象。
-
cglib创建动态代理的原理:父子继承关系创建动态代理对象,原始类作为父类,代理类作为子类,这样既可以2者保证方法一致,也可以在代理类中提供新的实现。(额外功能+原始方法)
-
代码实现:
- 首先创建一个没有实现任何接口的原始类:
public class UserService { private UserDao userDao =(UserDao) BeanFactory.getBean("userDao"); public void register(User user) { userDao.save(user); } public void login(String name, String password) { userDao.queryUserByUsernameAndPassword(name,password); } }
- 在测试类中使用cglib创建代理类对象,并测试:
public class TestCglibProxy { public static void main(String[] args) { //1.创建原始对象 UserService userService = new UserService(); //2.通过cglib的方式创建动态代理对象 //cglib提供了一个Enhancer类 Enhancer enhancer = new Enhancer(); //需要设置Enhancer类对象的三个属性 //设置代理类的父类 enhancer.setSuperclass(UserService.class); //设置代理类字节码的类加载器 enhancer.setClassLoader(TestCglibProxy.class.getClassLoader()); //设置代理类增加的额外功能: //一般在CallBack接口的子接口MethodInterceptor的内部实现类中定义额外方法 MethodInterceptor methodInterceptor = new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("----------proxy log-----------"); method.invoke(userService,objects); return null; } }; enhancer.setCallback(methodInterceptor); //使用Enhancer类对象的create方法创建代理类,由于代理类是UserService的子类,所以类型也可以声明为UserService UserService userServiceProxy = (UserService)enhancer.create(); userServiceProxy.login("aaa","sss"); } }
5.2.3 动态代理底层创建的总结
-
JDK提供的动态代理:
Proxy.newProxyInstance():通过实现接口来创建代理的实现类
-
Cglib动态代理:
Enhancer:通过继承父类来实现的代理类
5.3 Spring工厂如何加工创建代理对象
Spring创建代理对象,是通过BeanPostProcessor指定的:
- 创建BeanPostProcessor的实现类:
public class MyAOP implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof OrderService){
//创建该类的代理对象
OrderService proxyInstance =(OrderService) Proxy.newProxyInstance(MyAOP.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增加的额外功能!");
Object ret = method.invoke(bean, args);
return ret;
}
});
return proxyInstance;
}
return bean;
}
}
- 在配置文件中配置:
<bean id="aop" class="com.itheima.basic.aop.MyAOP"></bean>
<bean class="com.itheima.basic.aop.OrderServiceImpl" id="orderService"></bean>
第六章 基于注解的AOP编程
6.1 基于注解的AOP编程开发步骤
- 原始对象;
- 额外功能;
- 切入点;
- 组装切面。
-
原始对象:
<!-- 构建原始对象 --> <bean class="com.itheima.basic.aspect.OrderServiceImpl" id="orderService"></bean>
-
定义切入点及额外方法:
/** * 切面都得有两个固定的组成: * 1.切入点 * 2.额外功能 */ @Aspect//表示该类为切面类 public class MyAspect { //将切入点定义为切面类的方法,实现切入点复用 //切入点表达式之间用||、&&进行与或运算 @Pointcut(value = "execution(* com.itheima.basic.aspect.OrderServiceImpl.*(..)) || execution(* *(..))") public void pt1(){ } //定义前置额外功能 @Before("pt1()") public void before(){ System.out.println("前置方法!"); } //定义后置方法 @After("pt1()") public void after(){ System.out.println("后置方法!"); } //定义异常后抛出的额外功能方法 @AfterThrowing("pt1()") public void afterThrowing(){ System.out.println("异常后执行"); } //最终方法(finally中执行的方法) @AfterReturning("pt1()") public void afterReturning(){ System.out.println("最终方法"); } //环绕方法 @Around("pt1()") public Object around(ProceedingJoinPoint proceedingJoinPoint){ Object proceed = null; try { System.out.println("前置方法!"); Object[] args = proceedingJoinPoint.getArgs(); proceed = proceedingJoinPoint.proceed(args); System.out.println("后置方法!"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("异常时额外方法"); }finally { System.out.println("最终方法!"); } return proceed; } }
-
在配置文件中配置该类,告知Spring基于注解进行aop开发:
<bean class="com.itheima.basic.aspect.MyAspect" id="aspect"></bean> <!-- 告知Spring基于注解进行aop开发 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
6.2 Spring对于JDK与Cglib之间的切换
Spring中AOP编程默认使用的是JDK的动态代理模式,若要切换成Cglib动态代理模式,则需要在配置文件对proxy-target-class进行设置:
-
对于传统的配置文件进行aop开发:
<aop:config proxy-target-class="true"> ... </aop:config>
-
对于基于注解的方式进行aop开发:
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
第七章 AOP开发中的一个坑
在同一个业务类中,尽心业务方法的相互调用。只有最外层的方法,才是加入了额外功能的(内部的方法,通过普通的方式调用,都是调用的原始方法)。如果想让内层的方法也调用代理对象的方法,可以通过让业务类实现ApplicationContextAware接口,实现其方法,获得ApplicationContext对象,以此来获得业务类的代理对象,将内层方法替换为代理类的方法,就可以获得加入额外功能的方法。
public class OrderServiceImpl implements OrderService, ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
@Override
public void save() {
System.out.println("保存对象!");
}
@Override
public void login() {
context.getBean("orderService",OrderService.class).save();
}
}
第八章 AOP阶段知识总结
-
AOP编程(Spring的动态代理开发)
- 概念:通过代理类为原始类增加额外功能
- 好处:利于原始类的维护
-
AOP编程的开发步骤(动态代理的开发步骤):
- 原始对象;
- 额外功能;
- 切入点;
- 组装切面。
-
基于XML方式的AOP开发:
-
在Spring配置文件中创建原始对象:
<bean class="com.itheima.basic.UserServiceImpl" id="userService"></bean>
-
额外功能:
创建类实现MethodInterceptor接口,重写invoke方法,在其中增加额外功能 并在配置文件中配置该类: <bean class="com.itheima.basic.MyInterceptor" id="before"></bean>
-
切入点:
<aop:pointcut id="pt1" expression="execution(* login(..)) and args(String) and within(*..UserServiceImpl)"/>
-
组装切面
<aop:config proxy-target-class="true"> <aop:advisor advice-ref="before" pointcut-ref="pt1"></aop:advisor> </aop:config>
-
-
基于注解方式的AOP开发
-
在Spring配置文件中创建原始对象:
<bean class="com.itheima.basic.UserServiceImpl" id="userService"></bean>
-
定义切面类,并在切面类中定义切入点,额外功能,组装切面:
@Aspect//表示该类为切面类 public class MyAspect { //将切入点定义为切面类的方法 @Pointcut(value = "execution(* com.itheima.basic.aspect.OrderServiceImpl.*(..)) || execution(* *(..))") public void pt1(){ } //环绕方法 @Around("pt1()") public Object around(ProceedingJoinPoint proceedingJoinPoint){ Object proceed = null; try { System.out.println("前置方法!"); Object[] args = proceedingJoinPoint.getArgs(); proceed = proceedingJoinPoint.proceed(args); System.out.println("后置方法!"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("异常时额外方法"); }finally { System.out.println("最终方法!"); } return proceed; } }
-
在配置文件中配置切面类,并指定利用注解开发AOP:
<bean class="com.itheima.basic.aspect.MyAspect" id="aspect"></bean> <!-- 告知Spring基于注解进行aop开发 --> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
-
-
AOP的底层实现
-
JDK动态代理(Spring默认方式):基于原始类的接口 创建代理对象的实现
Proxy.newProxyInstance(classLoader,interfaces,invokeHandler)
-
Cglib动态代理:把原始类作为代理类的父类 通过继承的关系创建代理对象
Enhancer enhancer = new Enhancer(); //需要设置Enhancer类对象的三个属性 //设置代理类的父类 enhancer.setSuperclass(UserService.class); //设置代理类字节码的类加载器 enhancer.setClassLoader(TestCglibProxy.class.getClassLoader()); //设置代理类增加的额外功能: //一般在CallBack接口的子接口MethodInterceptor的内部实现类中定义额外方法 MethodInterceptor methodInterceptor = new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy ... } }; enhancer.setCallback(methodInterceptor); //使用Enhancer类对象的create方法创建代理类,由于代理类是UserService的子类,所以类型也可以声明为UserService UserService userServiceProxy = (UserService)enhancer.create();
-
两种动态代理的切换:
基于xml: <aop:config proxy-target-class="true"> ... </aop:config> 基于注解: <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
-
Spring底层基于BeanPostProcessor完成对象的加工,创建动态代理对象。
-