在JavaEE分层开发中,哪个层次对于我们来讲最重要?
DAO --> Service --> Controller
JavaEE分层开发中,最为重要的是Service层。
Service层中包含了哪些代码?
Service层 = 核心功能(几十上百行代码) + 额外功能(附加功能)
核心功能:业务运算、DAO调用
额外功能:不属于业务、可有可无、代码量很小(如事务、日志、性能、…)
额外功能书写在Service层中好不好?
Service层的调用者的角度(Controller):需要在Service层书写额外功能(事务)。
软件设计者的角度:Service层不需要额外功能,因为不好维护。
通过代理类,为原始类(目标)增加额外的功能。
好处:利于原始类(目标)的维护。
代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口(为了与目标类(原始类)的方法保持一致)
静态代理:为每一个原始类,手动创建一个代理类(.java .class)
//代理类实现与原始类相同的接口,保证方法一致
public class UserServiceProxy implements UserService {
//原始类
private UserServiceImpl userService = new UserServiceImpl();
@Override
public void register(User user) {
//额外功能
System.out.println("-----log-----");
//调用原始类中的方法
userService.register(user);
}
@Override
public boolean login(String name, String password) {
//额外功能
System.out.println("-----log-----");
//调用原始类中的方法
return userService.login(name, password);
}
}
对于每个Service原始类都需要一个对应的代理类,代码冗余。
概念:通过代理类,为原始类(目标)增加额外的功能。
好处:利于原始类(目标)的维护。
引入jar包
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.1.14.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>1.8.9version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.9version>
<scope>runtimescope>
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;
}
}
<bean id="userService" class="com.angenin.proxy.UserServiceImpl"/>
额外功能
Spring提供了MethodBeforeAdvice接口,我们需要把额外功能书写在此接口的实现类的before方法中,这个实现类的before方法会在原始方法执行前先执行。
public class Before implements MethodBeforeAdvice {
//书写运行在原始方法之前的额外功能
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
//额外功能
System.out.println("Before...before...");
}
}
<bean id="before" class="com.angenin.dynamic.Before"/>
定义切入点
切入点:额外功能加入的位置。
目的:由程序员根据自己的需要,决定额外功能加入给哪个原始方法。
简单的测试:所有方法都作为切入点,都加入额外功能。
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
aop:config>
组装(2和3的整合)
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
aop:config>
调用
获得Spring工厂创建的动态代理对象,并进行调用。此时,通过原始对象的id值获得的就是代理对象,生成的对象用原始类的接口类型进行接收。
打断点debug后可以看到获得的userService对象为代理对象。
Spring创建的动态代理类在哪里?
Spring框架在运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。
什么叫动态字节码技术?
通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虛拟机结束,动态字节码跟着消失。
结论:动态代理不需要定义类文件,都是JⅥM运行过程中动态创建的,所以不会造成静态代理,类文件数量过多,影晌项目管理的问题。
动态代理编程简化代理的开发。
在额外功能不改变的前提下,创建其他目标类(原始类)的代理对象时,只需要指定原始(目标)对象即可。
动态代理额外功能的维护性大大增强。
public class Before implements MethodBeforeAdvice {
/*
作用:书写运行在原始方法之前的额外功能
参数:
Method:额外功能所增加给的那个原始方法(如:login方法、register方法)
Object[]:额外功能所增加给的那个原始方法的参数(如login方法的String name, String password)
Object:额外功能所增加给的那个原始对象(如UserServiceImpl)
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
//额外功能
System.out.println("Before...before...");
}
}
MethodBeforeAdvice --> 运行在原始方法之前
MethodInterceptor接口:额外功能可以根据需要运行在原始方法执行前、后、前后以及原始方法执行出现异常时执行,MethodInterceptor接口功能比MethodBeforeAdvice接口更加强大,所以更推荐使用MethodInterceptor接口。
//这里的MethodInterceptor是aop包下的那个
public class Around implements MethodInterceptor {
/*
invoke方法的作用:额外功能书写在invoke
额外功能执行的时机 1. 原始方法之前
2. 原始方法之后
3. 原始方法之前 和 之后
4. 原始方法执行异常时
调用参数methodInvocation的proceed()方法,可以让原始方法执行,所以我们可以在执行前写额外功能,也可以在之后写,或者都写
参数:
methodInvocation:额外功能所增加给的那个原始方法
返回值:
Object:原始方法执行后的返回值
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//额外功能1
System.out.println("额外功能在原始方法执行前执行-------");
//原始方法
Object ret = methodInvocation.proceed();
//额外功能2
//System.out.println("额外功能在原始方法执行后执行-------");
//返回的是原始方法执行后的返回值
return ret;
}
}
<bean id="around" class="com.angenin.dynamic.Around"/>
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<aop:advisor advice-ref="around" pointcut-ref="pc"/>
aop:config>
什么的额外功能需要在原始方法运行的前后都运行呢?事务等。
额外功能运行在原始方法抛出异常时
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//额外功能1
//System.out.println("额外功能在原始方法执行前执行-------");
Object ret = null;
try {
//原始方法
ret = methodInvocation.proceed();
} catch (Throwable throwable) {
System.out.println("额外功能在原始方法抛出异常时执行-------");
throwable.printStackTrace();
}
//额外功能2
//System.out.println("额外功能在原始方法执行后执行-------");
return ret;
}
具体额外功能执行的时机,需要按照需求进行合理的安排。
MethodInterceptor影响原始方法的返回值
原始方法的返回值,直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值。
不返回原始方法的返回值,就会影响到结果。
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object ret = methodInvocation.proceed();
//return 1;
return false;
}
切入点决定额外功能加入的位置(这里的位置指的是方法,切入哪些方法)。
execution(* *(..)):匹配了所有方法
execution():切入点函数
* *(..)
* *(..) --> 所有方法
* --> 修饰符 返回值
* --> 方法名
() --> 参数表
.. --> 对参数没有要求(参数有没有、有几个、什么类型都行)
* login(..)
* login(String,String)
注意:
非java.lang包中的类型,必须写全限定类名
如:* register(com.angenin.proxy.User)
..可以和具体的参数类型连用
如:* login(String,..)
第一个参数为String类型的login方法
修饰符/返回值 包.类.方法(参数)
* com.baizhiedu.a.UserServiceImpl.login(String,String)
精准到a包下的UserServiceImpl的第一个login方法
指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能。
a包下UserServiceImpl类中的所有方法都加入额外功能
* com.baizhiedu.a.UserServiceImpl.*(..)
忽略包
1. 类只存在一级包 com.UserServiceImpl
* *.UserServiceImpl.*(..)
2. 类存在多级包 com.baizhiedu.a.UserServiceImpl
* *..UserServiceImpl.*(..)
指定包作为额外功能加入的位置,自然包中的所有类及其方法都会加入额外的功能。
切入点包中的所有类,下面这条表达式包括的类必须在proxy包中,即使在proxy的子包中也无法生效。
* com.baizhiedu.proxy.*.*(..)
切入点当前包及其子包都生效(多加一个点)
* com.baizhiedu.proxy..*.*(..)
包切入点在实战中使用得更多,把相加额外功能的类放到同个包下即可。切入点函数:用于执行切入点表达式。
execution
最为重要的切入点函数,功能最全。
可以执行方法切入点表达式、类切入点表达式、包切入点表达式。
弊端:execution执行切入点表达式,书写麻烦。
注意:其他的切入点函数只是简化了execution书写的复杂度,在功能上完全一致。
args
作用:主要用于函数(方法)参数的匹配,关注点在于参数,忽略包、类和方法名。
如:切入点为方法参数必须得是2个字符串类型的参数。
execution(* *(String,String))
替换后
args(String,String)
within
作用:主要用于进行类、包切入点表达式的匹配,关注点在于包名、类名,忽略方法名和参数。
如:切入点为UserServiceImpl类。
execution(* *..UserServiceImpl.*(..))
替换后
within(*..UserServiceImpl)
如:切入点为proxy包下及其子包的所有类
execution(* com.baizhiedu.proxy..*.*(..))
替换后
within(com.baizhiedu.proxy..*)
@annotation
作用:为具有特殊注解的方法加入额外功能。
自定义一个Log注解。
@Target(ElementType.METHOD) //决定在什么位置起作用(这里是在方法上起作用)
@Retention(RetentionPolicy.RUNTIME) //决定在什么时候起作用(这里在运行时起作用)
public @interface Log {}
为UserServiceImpl类的register方法加上@Log注解。
<aop:config>
<aop:pointcut id="pc" expression="@annotation(com.angenin.proxy.Log)"/>
<aop:advisor advice-ref="around" pointcut-ref="pc"/>
aop:config>
指的是 整合多个切入点函数一起配合工作,进而完成更为复杂的需求。
and 与操作
案例:方法名为login,并且参数为2个字符串。
execution(* login(String,String))
替换后
execution(* login(..)) and args(String,String)
注意:与操作不能用于同种类型的切入点函数,因为and需要同时满足。
如:login方法和register方法都作为切入点(这样写是错误的,可以换成or)
execution(* login(..)) and execution(* register(..))
or 或操作
案例:login方法和register方法都作为切入点
execution(* login(..)) or execution(* register(..))
AOP(Aspect Oriented Programing)面向切面编程 = Spring动态代理开发
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。
切面 = 切入点 + 额外功能OOP(Object Oritened Programing)面向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建。POP(Procedure Oriented Programing)面向过程(方法、函数)编程 C语言
以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建。
AOP的概念:
- 本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能。
- 好处:利于原始类的维护。
- 注意:AOP是基于OOP的,所以AOP不可能取代OOP,AOP是OOP为了解耦而做的补充。
- 原始对象
- 额外功能(MethodInterceptor)
- 切入点
- 组装切面(额外功能 + 切入点)
切面 = 切入点 + 额外功能
几何学
面 = 点 + 形同的性质
Proxy.newProxyInstance方法参数详解
关于类加载器:
首先得了解类加载器的作用,及其对象生成的过程。
根据老师讲的内容画的图,如果画得不对不好,麻烦各位请指出来。
>
因为代理类没有字节码文件,所以JVM没有给它生成它的类加载器,需要借用其他类的类加载器,所以Proxy.newProxyInstance的第一个参数为其他类的类加载器(原始类或者其他类都可以)。
InvocationHandler的invoke与MethodInterceptor的invoke对比:
代码实现
public class TestJdkProxy {
public static void main(String[] args) {
//代理创建3要素:1.原始对象;2.额外功能;3.代理对象和原始对象实现相同的接口。
// 1.创建原始对象
UserServiceImpl userService = new UserServiceImpl();
// 2.JDK创建动态代理
//这里为了下面来起来整洁一点,单独拿开,平时用匿名内部类即可
InvocationHandler handler = new InvocationHandler() {
/*
InvocationHandler的invoke方法:
作用:用于书写额外功能,额外功能运行在原始方法执行前、后、前后、抛出异常
参数:
proxy:忽略即可,代表的是代理对象
method:原始方法(增加额外功能的那个原始方法)
args:原始方法的参数
method.invoke方法:执行原始方法,返回原始方法执行后的结果,
第一个参数为原始对象
第二个参数为原始方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("----proxy log-----");
//传入原始对象和原始方法的参数
//这里需要注意,内部类使用外部类的变量时,外部类的变量需要用final来修饰
Object ret = method.invoke(userService, args);
//返回原始方法执行后的结果
return ret;
}
};
/*
第一个参数为借用的类加载器(任意类都可以)
第二个参数为原始类实现的接口,通过获取原始对象的class再获取接口(userService.getClass().getInterfaces())
第三个参数为InvocationHandler接口,在此接口的invoke方法中书写额外功能
*/
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(), handler);
//因为InvocationHandler接口是一个函数式接口,所以可以使用Lambda表达式,代码变得更加简洁(因为main方法的参数同样为args,所以需要改一下)
// UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
// userService.getClass().getInterfaces(), (proxy, method, argss) -> {
// System.out.println("----log-----");
// return method.invoke(userService, argss);
// });
userServiceProxy.login("angenin", "123456");
userServiceProxy.register(new User());
}
}
CGlib创建动态代理的原理:父子继承创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者方法一致,同时在代理类中提供新的实现(额外功能 + 原始方法)。
public class TestCglib {
public static void main(String[] args) {
// 1.创建原始对象
UserService userService = new UserService();
/*
2. 通过Cglib方式创建动态代理对象
JDK动态代理:Proxy.newProxyInstance(classloader,interface,invocationhandler)
Cglib动态代理:
设置类加载器: Enhancer.setClassloader() --> 同样,借用任意一个类的类加载器
设置父类: Enhancer.setSuperClass()
设置额外功能: Enhancer.setCallback() --> 实现接口MethodInterceptor
Enhancer.create() ---> 创建代理
*/
Enhancer enhancer = new Enhancer();
//设置类加载器
enhancer.setClassLoader(userService.getClass().getClassLoader());
//设置父类
enhancer.setSuperclass(userService.getClass());
//这里的MethodInterceptor接口是spring的cglib包下的那个
//可以用匿名内部类,这里只是为了看起来简洁
MethodInterceptor interceptor = new MethodInterceptor() {
//这里的intercept方法 等同于 InvocationHandler的invoke方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("------cglib log------");
//这里调用method的invoke和前面的JDK动态代理一样
Object ret = method.invoke(userService, args);
return ret;
}
};
//设置额外功能
enhancer.setCallback(interceptor);
//创建代理
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.login("angenin", "123456");
userServiceProxy.register(new User());
}
}
1. JDK动态代理 Proxy.newProxyInstance() 通过接口创建代理的实现类
2. Cglib动态代理 Enhancer 通过继承父类创建的代理类
代理都是为了面向切面服务的。
思路分析
BeanPostProcessor在上一篇文章里讲到。
编码实现:
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//返回生成的理对象
return Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), (proxy, method, args) -> {
System.out.println("----new log-----");
//返回原始类的执行结果
return method.invoke(bean, args);
});
}
}
<bean id="userService" class="com.angenin.factory.UserServiceImpl"/>
<bean id="proxyBeanPostProcessor" class="com.angenin.factory.ProxyBeanPostProcessor"/>
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext4.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.login("angenin", "123456");
userService.register(new User());
}
}
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;
}
}
/*
1. 原版的额外功能
public class MyAround implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) {
return invocation.proceed();
}
}
2. 切入点
*/
@Aspect //加上@Aspect注解代表这个类是切面类
public class MyAspect {
//加上@Around注解后,此时的 around方法 相当于 MethodInterceptor接口的invoke方法
//方法名和任意起,这里的参数 ProceedingJoinPoint 对应 原先的参数 MethodInvocation
@Around("execution(* login(..))") //对应aop:pointcut标签
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----log-----");
Object ret = joinPoint.proceed();
return ret;
}
}
<bean id="userService" class="com.angenin.aspect.UserServiceImpl"/>
<bean id="around" class="com.angenin.aspect.MyAspect"/>
<aop:aspectj-autoproxy/>
public class TestAspectProxy {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext4.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.login("angenin", "123456");
userService.register(new User());
}
}
切入点复用:在切面类中定义一个空方法,在其上面加上@Pointcut
注解,通过这种方式定义切入点表达式,后续更加有利于切入点的复用。
@Aspect
public class MyAspect {
//创建空方法,提取出切入点表达式
@Pointcut("execution(* login(..))")
public void myPointcut(){}
@Around(value = "myPointcut()") //引用空方法的切入点函数
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----log-----");
Object ret = joinPoint.proceed();
return ret;
}
@Around("myPointcut()") //引用空方法的切入点表达式
public Object around2(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----tx-----");
Object ret = joinPoint.proceed();
return ret;
}
}
AOP底层实现的2种代理创建方式:
切换为Cglib:
aop:aspectj-autoproxy
标签中加入proxy-target-class="true"
即可。<aop:aspectj-autoproxy proxy-target-class="true"/>
aop:config
标签上加上proxy-target-class="true"
即可,只是加的位置不同而已,属性都是同一个。<aop:config proxy-target-class="true">
...
aop:config>
坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才是加入了额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)。如果想让内层的方法也调用代理对象的方法,需要实现AppicationContextAware接口,通过其setApplicationContext方法获得工厂对象,进而获得代理对象,调用方法,加入额外功能。
原始类:
public class UserServiceImpl implements UserService, ApplicationContextAware {
private ApplicationContext ctx;
//通过实现 ApplicationContextAware 接口,通过 setApplicationContext方法 获取项目中的IOC工厂对象
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register (业务运算+DAO调用)");
// 模拟同一个业务类不同的业务方法间相互调用的情况
// 此时调用的是原始对象的login,不是代理对象,所以只有核心功能,没有额外功能
// 但是我们在这里调用的目的是要有额外功能+核心功能的
// 由于IOC工厂是重量级资源,一个应用最好只创建一个,所以不能在这里再创建一个工厂
// 实现 ApplicationContextAware 接口,可以获取到项目中的IOC工厂对象
//this.login("angenin", "11");
//通过工厂对象创建代理对象,再调用login方法,实现额外功能+核心功能
UserService userService = (UserService) ctx.getBean("userService");
userService.login("angenin", "11");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}
下一篇:Spring5学习笔记(四、持久层整合与事务处理)
学习视频(p63-p107):https://www.bilibili.com/video/BV185411477k?p=63