base黑马,视频链接:https://www.bilibili.com/video/BV1WZ4y1P7Bp?t=94.4
此系列文章可以当做视频的配套笔记,也可以当做自学SSM框架的教程。
aop简介-什么是aop
java代码要想运行,会经过编译和运行阶段。
AOP是运行期间执行的一种技术。
AOP技术的底层是通过动态代理技术来完成的。
动态代理,在不修改源码的情况下,对目标方法进行相应的增强。
动态代理的作用:完成程序功能之间的松耦合。
动态代理它的作用可以等同于AOP的作用,Spring不用自己再去写动态代理(内部进行了封装)。
比如,我有A功能,也有B功能,我想让A功能和B功能结合到一起,我不把A功能和B功能的代码写到一起,因为写到一起,A和B功能就耦合了。我可以通过AOP技术,让A和B功能在运行期间去动态的进行结合,但是A和B在代码这个层次角度上,它没有结合到一起。
这个就是松耦合。
比如现在有A、B、C、D四个功能,B、C、D都想具有A功能,最终会通过配置的方式让B、C、D都具备A功能。从而提高程序的可重用性。
aop简介-aop的作用及其优势
目前只要想完成松耦合一般就是通过配置的方式。
AOP就是完成这样一个动态结合的技术。面向切面编程。🤔
什么是切面?
功能–save(user) 和 目标–日志控制,结合到一起的方法称之为一个切面。(这样解释可以但不是100%的正确)
aop简介-aop的底层实现
- JDK代理:缺点是目标对象必须得有接口
- cglib代理:第三方动态代理小工具,不需要接口是基于父类的
- cglib技术是为目标对象动态的生成一个子对象,这样子对象和父对象的方法就一致了。子对象的方法、功能比父对象的功能要强大。
- 上面的示意图表面上像继承,但是底层的实现代码并不是继承。是动态的生成一个对象,这个对象是基于目标对象的。
- 调用的时候,调用代理对象,代理对象内部再调用目标对象,只不过在调用目标对象之前或者之后进行一些其他功能代码的介入,从而对目标对象进行增强。
aop简介-基于jdk的动态代理
需要一个接口,这个接口得有一个实现,这个实现就是目标对象,最终对目标对象中的某个方法进行增强。要增强的方法最终可以写到动态代理内部,写一个打印就可以。
接口:TargetInterface
package com.lyh.proxy.jdk;
public interface TargetInterface {
public void save();
}
目标类:Target
package com.lyh.proxy.jdk;
public class Target implements TargetInterface{
public void save() {
System.out.println("save running......");
}
}
增强类:Advice
package com.lyh.proxy.jdk;
//单独的建一个增强的方法
public class Advice {
public void before(){
System.out.println("前置增强......");
}
public void afterReturning(){
System.out.println("后置增强......");
}
}
测试类:ProxyTest
package com.lyh.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//这个不用导入任何的包
//本身属于jdk内部的一部分
//反射包
//得先写一个目标对象
final Target target = new Target();
//增强对象
Advice advice = new Advice();
//返回值 就是动态生成的代理对象
//动态生成的代理对象 是 接口的实现
TargetInterface proxy= (TargetInterface)Proxy.newProxyInstance(
//字节码对象 类加载器
target.getClass().getClassLoader(),//目标对象的类加载器
//因为很有可能目标对象不是一个接口,因为java是单继承多实现
//可能会有多个接口,多个接口的字节码对象我都要
target.getClass().getInterfaces(),
new InvocationHandler() {
//调用代理对象的任何方法 实质执行的都是invoke方法
//代表动态代理的invoke 代理对象 要执行目标方法的字节码对象 传递的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这段代码是aop底层的实现,这些代码不用自己去写,要完成解耦合aop会给你配置
//配置完之后,内部动态的生成这些代码,不用自己管
//前置增强
advice.before();
//代表反射时,反射的invoke
//传递的参数传递两个,第一个代表执行谁的,第二个代表实际参数
Object invoke = method.invoke(target, args);//执行目标方法
//后置增强
advice.afterReturning();
return invoke; //没有返回值,返回的是null
}
}
);
//调用代理对象的方法
//接口有什么方法,代理对象就有什么方法
proxy.save();
}
}
aop简介-基于cglib的动态代理
cglib是第三方的,需要导入一些东西,但是现在不用再导入一些东西了,因为现在的Spring中都已经集成了cglib的一些东西了。
创建目标对象:Target
package com.lyh.proxy.cglib;
public class Target{
public void save() {
System.out.println("==save running......");
}
}
创建增强对象:Advice
package com.lyh.proxy.cglib;
//单独地建一个增强的方法
public class Advice {
public void before(){
System.out.println("===前置增强......");
}
public void afterReturning(){
System.out.println("==后置增强......");
}
}
代理测试:ProxyTest
package com.lyh.proxy.cglib;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyTest {
public static void main(String[] args) {
//目标对象
final Target target = new Target();
//增强对象
Advice advice = new Advice();
//返回值 就是动态生成的代理对象 基于cglib,cglib是基于父的
//围绕父产生一个子,这个子就是一个代理对象
//下面的代码目前来说是不用掌握
//步骤:
//1、创建增强器(这个增强器是cglib提供的)
Enhancer enhancer = new Enhancer();
//2、设置父类(父类 就是目标)
enhancer.setSuperclass(Target.class);
//3、设置回调 内部的参数需要一个Callback的接口
//在这里可以给一个Callback接口,可以new一个匿名内部类
//一般在设置的时候不会new Callback,会new Callback下面的子接口MethodInterceptor
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//intercept方法 相当于内部的 invoke方法
advice.before();//执行前置
Object invoke = method.invoke(target, args);//执行目标(还是用反射)
advice.afterReturning();//执行后置
return invoke;
}
});
//4、创建代理对象
//Object o = enhancer.create();
//Object就是代理对象,但是不能用Object去接,用Target接
//因为现在的代理对象是基于父的,代理对象就是Target的子,所以可以用它的父亲接
Target poxy = (Target)enhancer.create();
//调用代理对象的save方法,就相当于是调用目标对象的save方法,并且对目标方法进行了增强
poxy.save();
}
}
效果:
aop简介-aop相关概念
目标对象:要被增强的对象,目标对象内部的方法就是要被增强的目标方法。
点就可以等同于方法,所谓的连接点就是被拦截到的方法。可以被增强的方法叫做连接点。
切入点简称“切点”,正真配置被增强的连接点称之为切入点
连接点的范围大,切入点是其中的一部分
通知/增强,是方法
目标方法+增强,就是**切面,**术语说就是:切点+增强
Weaving(织入)是**动词,将切点与增强结合的过程就是织入,**通过配置进行体现。
aop简介-aop开发明确的事项和知识要点
Spring在设计的时候,是把增强的方法设计到切面类内部了。
如果目标对象有接口就使用JDK动态代理,如果没有接口就使用cglib动态代理。
xml方式实现aop-快速入门
AOP是一种思想,Spring框架对AOP进行了实现。aspectj(小框架)也对AOP进行了实现,并且aspectj在AOP配置方面的易用性要比Spring好一些。Spring本身也是一个轻量级框架,但在AOP配置方面,没有aspectj轻。Spring不排斥任何优秀的框架,在后期的Spring官方文档中,主张使用aspectj进行aop的配置,与此同时Spring把aspectj融入到了本身的内部。
Spring想要完成AOP的配置,可以用Spring原生的AOP进行配置,也可以使用Spring集成第三方aspectj的方式进行配置,目前Spring主张使用第三方aspectj这种方式进行配置。
创建接口:TargetInterface
package com.lyh.proxy.aop;
public interface TargetInterface {
public void save();
}
创建接口的实现类:Target
package com.lyh.proxy.aop;
public class Target implements TargetInterface {
public void save() {
System.out.println("save running......");
}
}
切面类:MyAspect
package com.lyh.proxy.aop;
public class MyAspect {
public void before(){
System.out.println("前置增强......");
}
}
pom.xml文件:导入相应的jar包
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>itheima_spring_exception</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>itheima_spring_exception Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
</dependencies>
</project>
配置applicationContext.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--从Spring角度 目标对象和切面对象没有什么区别。-->
<!--目标对象-->
<bean id="target" class="com.lyh.proxy.aop.Target"></bean>
<!--切面对象 这个普通的bean 经过配置 就会变成一个切面-->
<bean id="myAspect" class="com.lyh.proxy.aop.MyAspect"></bean>
<!--配置织入:告诉spring框架,那些方法(切点,目标方法)需要进行那些增强(前置、后置......)-->
<!--首先配置aop的命名空间-->
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<!--切面:切点+通知-->
<!--myAspect这个里面,那些方法是前置增强 对pointcut这里的方法进行增强-->
<aop:before method="before" pointcut="execution(public void com.lyh.proxy.aop.Target.save())"/>
<!--当去访问save()时,save()方法要进行before(前置)增强-->
<!--增强功能的逻辑代码和目标是解耦合的-->
</aop:aspect>
</aop:config>
</beans>
创建测试类:AopTest
package com.lyh.test;
import com.lyh.proxy.aop.Target;
import com.lyh.proxy.aop.TargetInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
效果:
xml方式实现aop-切点表达式的写法
更改后的切点表达式:
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut="execution(* com.lyh.proxy.aop.*.*(..))"/>
</aop:aspect>
</aop:config>
效果:
xml方式实现aop-通知的种类
接口:TargetInterface
package com.lyh.proxy.aop;
public interface TargetInterface {
public void save();
}
目标对象:Target
package com.lyh.proxy.aop;
public class Target implements TargetInterface {
public void save() {
System.out.println("save running......");
//著名的自杀式错误
int i = 1/0;
}
}
切面类:MyAspect
package com.lyh.proxy.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
public void before(){
System.out.println("前置增强......");
}
public void afterReturning(){
System.out.println("后置增强......");
}
//Proceeding JoinPoint:正在执行的 连接点===切点(目标方法)
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前通知......");
//切点方法
Object proceed = pjp.proceed(); //我的理解中间这个相当于是占位符
System.out.println("环绕后通知......");
return proceed;
}
// 得让目标有点异常
public void afterThrowing(){
System.out.println("异常抛出增强......");
}
//最终增强:不管抛不抛出异常,最终增强一定会执行
public void after(){
System.out.println("最终增强......");
}
}
配置文件:applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--从Spring角度 目标对象和切面对象没有什么区别。-->
<!--目标对象-->
<bean id="target" class="com.lyh.proxy.aop.Target"></bean>
<!--切面对象 这个普通的bean 经过配置 就变成了一个切面-->
<bean id="myAspect" class="com.lyh.proxy.aop.MyAspect"></bean>
<!--配置织入:告诉spring框架,那些方法(切点,目标方法)需要进行那些增强(前置、后置......)-->
<!--首先配置aop的命名空间-->
<aop:config>
<!--声明切面-->
<!--切面:切点+通知-->
<aop:aspect ref="myAspect">
<!--增强的功能逻辑代码和目标是解耦合的-->
<aop:before method="before" pointcut="execution(* com.lyh.proxy.aop.*.*(..))"/>
<aop:after-returning method="afterReturning" pointcut="execution(* com.lyh.proxy.aop.*.*(..))"/>
<aop:around method="around" pointcut="execution(* com.lyh.proxy.aop.*.*(..))"/>
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.lyh.proxy.aop.*.*(..))"/>
<aop:after method="after" pointcut="execution(* com.lyh.proxy.aop.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
效果:
xml方式实现aop-切点表达式的抽取
applicationContext.xml相关配置片段:
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<!--抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(* com.lyh.proxy.aop.*.*(..))"/>
<aop:around method="around" pointcut-ref="myPointcut"/>
<aop:after method="after" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
效果:
xml方式实现aop-知识要点
注解方式实现aop-快速入门
接口:TargetInterface
package com.lyh.proxy.anno;
public interface TargetInterface {
public void save();
}
目标类:Target
package com.lyh.proxy.anno;
import org.springframework.stereotype.Component;
@Component("target")
public class Target implements TargetInterface {
public void save() {
System.out.println("save running......");
}
}
切面类:MyAspect
package com.lyh.proxy.anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {
@Before(value = "execution(* com.lyh.proxy.anno.*.*(..))")
public void before(){
System.out.println("前置增强......");
}
}
配置文件:applicationContext-anno.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!-- 组件扫描-->
<context:component-scan base-package="com.lyh.proxy.anno"/>
<!-- AOP自动代理 把这句话加上才会去 识别@Before这个注解-->
<aop:aspectj-autoproxy/>
</beans>
效果:
注解方式实现aop-注解通知种类和切点表达式抽取
注解通知种类:
MyAspect类
package com.lyh.proxy.anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {
//Proceeding JoinPoint:正在执行的 连接点===切点(目标方法)
@Around("execution(* com.lyh.proxy.anno.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前通知......");
//切点方法
Object proceed = pjp.proceed(); //我的理解中间这个相当于是占位符
System.out.println("环绕后通知......");
return proceed;
}
//最终增强:不管抛不抛出异常,最终增强一定会执行
@After("execution(* com.lyh.proxy.anno.*.*(..))")
public void after(){
System.out.println("最终增强......");
}
}
测试类:AopAnnoTest
package com.lyh.test;
import com.lyh.proxy.anno.TargetInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
public class AopAnnoTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
效果:
切点表达式抽取:
MyAspect类
package com.lyh.proxy.anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {
//抽取引入方式一:
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前通知......");
//切点方法
Object proceed = pjp.proceed(); //我的理解中间这个相当于是占位符
System.out.println("环绕后通知......");
return proceed;
}
//抽取引入方式二:
@After("MyAspect.pointcut()")
public void after(){
System.out.println("最终增强......");
}
//定义一个空实现,不用在方法内写东西,因为注解必须需要一个宿主
@Pointcut("execution(* com.lyh.proxy.anno.*.*(..))")
public void pointcut(){}
}
测试类省去,与上面的相同
效果: