赞
踩
笔记整理自 b站 孙哥说Spring5
问题
在JavaEE分层开发开发中,哪个层次对于我们来讲最重要
DAO ---> Service --> Controller
JavaEE分层开发中,最为重要的是Service层
Service层中包含了哪些代码?
Service层中 = 核心功能(几十行 上百代码) + 额外功能(附加功能)
1. 核心功能
业务运算
DAO调用
2. 额外功能
1. 不属于业务
2. 可有可无
3. 代码量很小
事务、日志、性能...
额外功能书写在Service层中好不好?
Service层的调用者的角度(Controller):需要在Service层书写额外功能。
软件设计者:Service层不需要额外功能。
现实生活中的解决方式
租房场景
租房场景改造
既然房东不想做额外功能,但房客还需要这些功能,所以此时需要有一个第三方:中介 负责这些功能,在程序中也就是我们的代理类Proxy
原始(目标)类
➢ 指的是 业务类(核心功能 => 业务运算 DAO调用)
原始(目标)方法
➢ 原始(目标)类中的方法 就是原始(目标)方法
额外功能(附加功能)
➢ 日志、事务、性能
代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口
要求: 代理类的方法名与目标类的方法名要一一对应(即代理类要和目标类实现同一个接口,这也就是为什么我们的service层一定要写一个接口然后写实现类的原因!!!
)
房东 => public interface UserService{
m1
m2
}
UserServiceImpl implements UserService {
m1 => 业务运算 DAO调用
m2
}
UserServiceProxy implements UserService {
m1
m2
}
静态类文件数量过多,不利于项目管理
➢ UserServiceImpl => UserServiceProxy
➢ OrderServiceImpl => OrderServiceProxy
额外功能维护性差
➢ 代理类中 额外功能修改复杂(麻烦)需要修改所有的代理类
基于上面的两个致命缺点,我们可以使用动态代理,解决了上面的两个缺点。
动态代理没有代理类,使用字节码技术生成代理类
额外功能单独抽取
,所有需要此额外功能的目标类都可以使用,所以修改额外功能非常方便<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.1.14.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.8</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.3</version> </dependency>
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 业务运算 + DAO ");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}
配置文件applicationContext.xml
<bean id="userService" class="com.lyc.proxy.UserServiceImpl"/>
MethodBeforeAdvice接口
额外的功能书写在接口的实现中,运行在原始方法执行之前运行额外功能。
public class Before implements MethodBeforeAdvice {
/*
作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----method before advice log------");
}
}
配置文件applicationContext.xml
<bean id="before" class="com.lyc.dynamic.Before"/>
切入点:额外功能加入的位置。
目的:由程序员根据自己的需要,决定额外功能加入给哪个原始方法。
简单的测试:所有方法都做为切入点,都加入额外的功能。
<aop:config>
<!--
aop:pointcut :切入点
expression :切入点表达式
execution(* *(..)) :代表所有方法都作为切入点加入额外功能 login、register
-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
</aop:config>
将
2
2
2、
3
3
3 步整合,嵌套在<aop:config>
标签中,组装成切面
<!--
组装:目的把切入点 与 额外功能 进行整合
表达的含义:所有的方法 都加入 before的额外功能
-->
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
目的:获得Spring工厂创建的动态代理对象,并进行调用。
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
/*
注意:
1. Spring的工厂通过原始对象的id值获得的是代理对象
2. 获得代理对象后,可以通过声明接口类型,进行对象的存储
*/
UserService userService = (UserService) ctx.getBean("userService");
userService.login();
userService.register();
达到效果
Debug调试一下,通过原始对象id获取的对象的确是代理对象Proxy
Spring创建的动态代理类在哪里?
➢ Spring框架在运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。
➢ 什么叫动态字节码技术:通过第三个动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码跟着消失。
➢ 结论:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理 类文件数量过多,影响项目管理的问题。
动态代理编程简化代理的开发
➢ 在额外功能不改变的前提下,创建其他原始类(目标类)的代理对象时,只需指定原始(目标)对象即可。
动态代理额外功能的维护性大大增强
MethodBeforeAdvice
分析(使用较少)
方法中的所有参数都是基于原始类动态变化
的
// 1.MethodBeforeAdvice接口作用:额外功能运行在原始方法执行之前,进行额外功能操作。
public class Before implements MethodBeforeAdvice {
/**
* 作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
* param1 Method method :额外功能所增加给的原始方法(login、register)
* param2 Object[] objects :额外功能所增加给的那个原始方法的参数((String name, String password)、(User user))
* param3 Object o :额外功能所加给的原始对象(UserServiceImpl、OrderServiceImpl)
* 所有参数都是基于原始类动态变化的
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("-----method before advice log------");
}
}
// 2.before方法的3个参数在实战中,该如何使用。
// before方法的参数,在实战中,会根据需要进行使用,不一定都会用到,也有可能都不用。
Servlet{
service(HttpRequest request, HttpResponse response) {
request.getParameter("name") -->
response.getWriter() --->
}
}
MethodInterceptor(方法拦截器)
(使用较多,因为功能强大)
// MethodInterceptor接口:额外功能可以根据需要运行在原始方法执行:前、后、前后。 public class Arround implements MethodInterceptor { /** * invoke()方法作用:额外功能书写在invoke方法中 * 确定:原始方法怎么运行 * @param methodInvocation :额外功能所增加给的那个原始方法(login、register) * methodInvocation.proceed() => (login、register)原始方法运行 * @return Object ret:原始方法的返回值 */ @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { // TODO 原始方法执行之前书写额外功能 Object ret = methodInvocation.proceed(); // TODO 原始方法执行之后书写额外功能 return ret; } }
➢ 额外功能运行在原始方法执行之前
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("=======原始方法执行之前的额外功能=======");
Object ret = methodInvocation.proceed();
return ret;
}
➢ 额外功能运行在原始方法执行之前
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object ret = methodInvocation.proceed();
System.out.println("=======原始方法执行之后的额外功能=======");
return ret;
}
➢ 额外功能运行在原始方法执行之前、之后
// 什么样的额外功能 运行在原始方法执行之前,之后都要添加?
// 事务
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("=======原始方法执行之前的额外功能=======");
Object ret = methodInvocation.proceed();
System.out.println("=======原始方法执行之后的额外功能=======");
return ret;
}
➢ 额外功能运行在原始方法抛出异常的时候
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object ret = null;
try {
ret = methodInvocation.proceed();
} catch (Throwable throwable) {
System.out.println("=======原始方法抛出异常 执行的额外功能=======");
throwable.printStackTrace();
}
return ret;
}
➢ MethodInterceptor影响原始方法的返回值
// 原始方法的返回值,直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值
// MethodInterceptor影响原始方法的返回值:Invoke方法的返回值,不要直接返回原始方法的运行结果即可。
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("-----log-----");
Object ret = invocation.proceed();
return false;
}
切入点决定额外功能加入位置(方法)
<aop:pointcut id="pc" expression="execution(* *(..))"/>
exection(* *(..)) => 匹配了所有方法 a× b√ c× ?
1. execution() 切入点函数
2. * *(..) 切入点表达式
方法切入点表达式
* *(..) => 所有方法
* => 修饰符 返回值
* => 方法名
() => 参数表
.. => 对于参数没有要求(参数有没有都行,参数有几个都行,参数是什么类型的都行)
➢ 定义login
方法作为切入点
* login(..)
# 定义register作为切入点
* register(..)
➢ 定义login方法且login方法有两个字符串类型的参数
作为切入点
* login(String,String)
# 注意:非java.lang包中的类型,必须要写全限定名
* register(com.lyc.proxy.User)
# ..可以和具体的参数类型连用
* login(String,..) => login(String),login(String,String),login(String,com.lyc.proxy.User)
➢ 精准方法切入点限定
修饰符 返回值 包.类.方法(参数)
* com.lyc.proxy.UserServiceImpl.login(..)
* com.lyc.proxy.UserServiceImpl.login(String,String)
类切入点表达式
指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能。
➢ 语法1
# 类中的所有方法加入了额外功能
* com.lyc.proxy.UserServiceImpl.*(..)
➢ 语法2
# 忽略包
1. 类只存在一级包 com.UserServiceImpl
* *.UserServiceImpl.*(..)
2. 类存在多级包 com.lyc.proxy.UserServiceImpl
* *..UserServiceImpl.*(..)
包切入点表达式(实战中用的较多)
指定包作为额外功能加入的位置,自然包中的所有类及其方法都会加入额外的功能。
➢ 语法1
# 切入点包中的所有类,必须在proxy中,不能在proxy包的子包中
* com.lyc.proxy.*.*(..)
➢ 语法2
# 切入点当前包及其子包都生效
* com.lyc.proxy..*.*(..)
切入点函数:用于执行切入点表达式。
execution
➢ 最为重要的切入点函数,功能最全。
➢ 执行:方法切入点表达式、类切入点表达式、包切入点表达式。
➢ 弊端:execution执行切入点表达式 ,书写麻烦:execution(* com.lyc.proxy..*.*(..))
➢ 注意:其他的切入点函数 简化是execution书写复杂度,功能上完全一致。
args
➢ 作用:主要用于函数(方法)参数的匹配
➢ 切入点:方法参数必须得是2个字符串类型的参数
➢ execution(* *(String,String))
➢ args(String,String)
within
➢ 作用:主要用于进行类、包切入点表达式的匹配
➢ 切入点:UserServiceImpl这个类、com.lyc.proxy这个包
➢ execution(* *..UserServiceImpl.*(..))
➢ within(*..UserServiceImpl)
➢ execution(* com.lyc.proxy..*.*(..))
➢ within(com.lyc.proxy..*)
@annotation
➢ 作用:为具有特殊注解的方法加入额外功能
<aop:pointcut id="" expression="@annotation(com.lyc.Log)"/>
切入点函数的逻辑运算
指的是 整合多个切入点函数一起配合工作,进而完成更为复杂的需求。
➢ and与操作
案例:
方法名:login
参数: 2个字符串
1. execution(* login(String,String))
2. execution(* login(..)) and args(String,String)
注意:与操作不能用于同种类型的切入点函数
案例:register方法 和 login方法作为切入点
execution(* login(..)) or execution(* register(..))
➢ or或操作
案例:register方法 和 login方法作为切入点
execution(* login(..)) or execution(* register(..))
AOP(Aspect Oriented Programing) 面向切面编程
切面 = 切入点 + 额外功能
面向切面编程 = Spring动态代理开发
AOP的概念
➢ 本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能。
➢ 好处:利于原始类的维护。
注意:AOP编程不可能取代OOP(面向对象编程),是OOP编程的有意补充。
本质就是Spring的动态代理开发。
切面 = 切入点 + 额外功能
几何学:面 = 点 + 相同的性质
AOP如何创建动态代理类
➢ 动态字节码技术
Spring工厂如何加工创建代理对象
➢ 通过原始对象的id值,获得的是代理对象
Proxy.newProxyInstance()
方法参数详解编码
public class TestJDKProxy { /* 1. 借用类加载器 TestJDKProxy 借哪个都可以 UserServiceImpl 2. JDK8.x前 内部类访问外部类局部变量 要将局部变量声明为final final UserService userService = new UserServiceImpl(); */ public static void main(String[] args) { // 1.创建原始对象 UserService userService = new UserServiceImpl(); // 2.JDK创建动态代理 // 2.1内部类实现InvocationHandler接口 调用invoke方法 InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("=====proxy log====="); // 原始方法运行 Object ret = method.invoke(userService, args); return ret; } }; // 2.2调用Proxy.newProxyInstance()返回代理对象 UserService userServiceProxy = (UserService) Proxy.newProxyInstance( TestJDKProxy.class.getClassLoader(), userService.getClass().getInterfaces(), handler); // 3.执行原始方法 userServiceProxy.login("lyc", "123"); userServiceProxy.register(new User()); } }
Cglib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者方法一致,同时在代理类中提供新的实现(额外功能 + 原始方法)。
Cglib编码
package com.lyc.cjlib; import com.lyc.proxy.User; import org.springframework.cglib.proxy.Enhancer; // 此时用到的MethodInterceptor是cglib包下的 之前用的在aop包下 import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class TestCglib { public static void main(String[] args) { // 1.创建原始对象 UserService userService = new UserService(); /* 2.通过Cglib方式创建动态代理对象 Proxy.newProxyInstance(classloader,interface,invocationhandler) Enhancer.setClassLoader() →↑ Enhancer.setSuperClass() →↑ Enhancer.setCallback() => MethodInterceptor(cglib) →↑ Enhancer.create() => 代理 */ Enhancer enhancer = new Enhancer(); // 2.1借用类加载器 enhancer.setClassLoader(TestCglib.class.getClassLoader()); // 2.2提供原始对象(父类)的class对象 enhancer.setSuperclass(userService.getClass()); // 2.3提供额外功能 MethodInterceptor interceptor = new MethodInterceptor() { // 等同于 InvocationHandler => invoke() @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("----cglib log----"); // 执行原始方法 Object ret = method.invoke(userService, args); return ret; } }; enhancer.setCallback(interceptor); // 3.创建代理 UserService userServiceProxy = (UserService) enhancer.create(); // 4.调用方法 userServiceProxy.login("lyc", "123"); userServiceProxy.register(new User()); } }
总结
➢ JDK动态代理: Proxy类 Proxy.newProxyInstance() 通过接口创建代理的实现类
➢ Cglib动态代理: Enhancer类 Enhancer.setXx() 通过继承父类创建的代理类
再谈BeanPostProcessor
AOP的底层实现
BeanPostProcessor就是对Spring工厂创建的对象进行加工,在前面开发Spring动态代理的过程中,获取到Spring的ApplicationContext工厂后,通过原始对象的id属性实际上获取的是代理对象,实际上Spring就是通过在BeanPostProcessor的After方法中对于传来的bean对象通过代理模式为其生成一个代理对象,最后返回的是这个代理对象,所以我们最后通过原始类的id属性获取的是代理对象。
编码
工厂代理类
public class ProxyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } /* Proxy.newProxyInstance(); */ @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("=====BeanPostProcessor's new log====="); Object ret = method.invoke(bean, args); return ret; } }; return Proxy.newProxyInstance( ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler); } }
配置文件applicationContext.xml
<bean id="userService" class="com.lyc.factory.UserServiceImpl"></bean>
<!--
1. 实现BeanPostProcessor 进行加工
2. 配置文件中对BeanPostProcessor进行配置
-->
<bean id="proxyBeanPostProcessor" class="com.lyc.factory.ProxyBeanPostProcessor"></bean>
测试成功
而AOP的注解编程,将后三步直接整合到了一起
只需要两步
编码
切面类
package com.lyc.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; /* 1.额外功能 public class MyAround implements MethodInterceptor { public Object invoke(MethodInvocation invocation) { Object ret = invocation.proceed(); return ret; } } 2.切入点 <aop:config> <aop:pointcut id="" expression="execution(* login(..))"/> </aop:config> */ // 使用Aspect注解,表示这个类是一个切面类 在切面类里可以定义很多的切入点+额外功能 @Aspect public class MyAspect { // 使用Around注解表示这是额外功能,类似于动态代理的invoke方法,返回值就是原始对象的返回值 // 在注解里面直接定义切入点表达式 // 注意方法的修饰符和返回值必须是 public + Object 方法名可以自定义 // 参数 ProceedingJoinPoint 等同于原来的 MethodInvocation 此自定义方法等同于之前的invoke()方法 @Around("execution(* login(..))") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("====aspect log===="); Object ret = joinPoint.proceed(); return ret; } } /* 通过切面类 定义了 额外功能 @Around 定义了 切入点 @Around("execution(* login(..))") @Aspect 切面类 */
配置文件applicationContext.xml
<bean id="userService" class="com.lyc.aspect.UserServiceImpl"></bean>
<!--
切面
1. 额外功能
2. 切入点
3. 组装切面
-->
<bean id="around" class="com.lyc.aspect.MyAspect"></bean>
<!-- 告知Spring基于注解进行AOP编程 -->
<aop:aspectj-autoproxy/>
测试
public class TestAspectProxy {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext2.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.login("lyc", "csgo");
userService.register(new User());
}
}
只有login方法加入了额外功能
切入点复用
@PointCut注解
把切入点的配置 提取到一个独立的方法上面,打上@Pointcut注解;定义切入点表达式,后续更加有利于切入点复用。
@Aspect public class MyAspect { // 提取切入点至@PointCut注解中 利于代码的后续维护 // 权限修饰符和返回值必须是 public + void 方法名任意 方法体可以为空 @Pointcut("execution(* login(..))") public void myPointCut() {} // 直接引用方法名 @Around("myPointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("====aspect log===="); Object ret = joinPoint.proceed(); return ret; } @Around("myPointCut()") public Object around1(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("====aspect tx===="); Object ret = joinPoint.proceed(); return ret; } }
动态代理的创建方式
AOP底层实现 2种代理创建方式
1. JDK 通过实现接口 做新的实现类方式 创建代理对象
2. Cglib 通过继承父类 做新的子类 创建代理对象
默认情况 AOP编程 底层应用JDK动态代理创建方式
如果切换Cglib
1. 基于注解AOP开发
<aop:aspectj-autoproxy proxy-target-class="true" />
2. 传统的AOP开发
<aop:config proxy-target-class="true">
</aop:config>
在同一个业务类中,业务方法间进行调用,如方法a调用了方法b,两个方法都增加了额外功能,但是最后在实际调用过程中只有a方法的额外功能实现了,b没有实现。
public void a() {
b();
}
public void b() {
...
}
原因:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才加入了额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 业务运算 + DAO ");
// 内部调用login方法
this.login("lyc", "csgo");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}
myPointCut方法的注解需要改成@Pointcut("execution(* *..UserServiceImpl.*(..))")
,UserServiceImpl类中所有的方法都会加上额外功能
测试
public class TestAspectProxy {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext2.xml");
UserService userService = (UserService) ctx.getBean("userService");
//userService.login("lyc", "csgo");
userService.register(new User());
}
}
执行结果,只有register方法加入了额外功能,login方法未加上额外功能:
这是因为在Test中调用register方法的实际是Spring创建的代理对象Proxy
,而在register方法中调用login方法的是this对象UserServiceImpl
,这只是个原始类,只执行核心功能。
实现ApplicationContextAware
接口重写setApplicationContext()
方法,将ApplicationContext
作为成员变量在setApplicationContext()
方法中进行赋值,在调用中使用ApplicationContext
对象获取代理对象执行方法。
注意: 不要在方法中创建ApplicationContext对象,因为这个对象代表了Spring工厂,占用资源多,一个程序中应该只存在一个。
// 如果想让内层的方法也调用代理对象的方法,就要实现AppicationContextAware接口获得工厂,进而获得代理对象 public class UserServiceImpl implements UserService, ApplicationContextAware { private ApplicationContext ctx; // 获取工厂 @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.ctx = applicationContext; } @Override public void register(User user) { System.out.println("UserServiceImpl.register 业务运算 + DAO "); // 调用的是原始对象的login方法 => 核心功能 /* 设计目的:代理对象的login方法 => 额外功能+核心功能 ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext2.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.login(); Spring工厂重量级资源 一个应用中 应该只创建一个工厂 */ UserService userService = (UserService) ctx.getBean("userService"); userService.login("lyc", "csgo"); } @Override public boolean login(String name, String password) { System.out.println("UserServiceImpl.login"); return true; } }
测试
两个方法都加上了额外功能
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。