项目开发完成后,在不想修改源码的情况下添加额外的功能,可以采用代理的形式动态添加功能去完成。
优势
AOP的目标 :在不影响源代码的前提下动态附加功能,达到无侵入的,可插拔式的编程
AOP的优势:功能模块解耦,模块职责单一,可重用性和可扩展性高
AOP编程:在实现动态附加功能的过程中,将工作重心从该怎么将功能添加到目标位置转移到该如何实现附加功能。
能使用动态代理,那就意味着它实现了多态,也就是说必然拥有父接口。
StudentServiceImpl sImpl = new StudentServiceImpl();
Log4jTest log = new Log4jTest();
一、非XML和注解的AOP
1、JDK动态代理
代理对象必须是某个接口的实现
通过在运行期间创建一个接口的实现类来完成对目标对象的代理
Proxy: 代理对象
InvocationHandler: 代理对象要执行的行为
/**
* JDK动态代理
*/
@Test
public void JDKTest() {
StudentService studentService = (StudentService)Proxy.newProxyInstance(StudentControler.class.getClassLoader(), StudentServiceImpl.class.getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.start();
Object result = method.invoke(sImpl, args);
log.end();
return result;
}
});
studentService.addStudent();
}
需要注意的是,
代理对象Proxy只能强转为接口类型。
newProxyInstance(1,2,InvocationHandler代理行为对象)方法里。
第一个参数可以相对随便一点,只要获取到一个类加载器给它就行。
第二个参数必须是通过它的实现类获取其父接口来得到该接口。
在InvocationHandler代理行为对象的方法体里,一般执行方法的对象是自己方法外创建的对象,而不是参数里的proxy
如果用的是参数里的proxy参数,那可能会出现方法一直执行的情况
2、CGLIB动态代理:
CGLib采用了非常底层的字节码技术,原理是通过字节码技术为一个类创建子类
在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
Enhancer: 代理对象
MethodInterceptor: 代理对象执行的增强行为
/**
* CGLIB动态代理
*/
@Test
public void CGLIBTest() {
StudentService studentService = (StudentService)Enhancer.create(StudentServiceImpl.class, new MethodInterceptor() {
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
log.start();
Object object = arg3.invokeSuper(arg0, arg2);
log.end();
return object;
}
});
studentService.addStudent();
}
注意:
java没有内置Enhancer,所以需要导入CGLIB的jar包或者spring-core-5.1.9.RELEASE.jar包
Enhancer.create(1,增强行为MethodInterceptor)方法里,
第一个参数必须是接口的实现类。
增强行为MethodInterceptor方法体里
二、基于XML配置的Spring AOP
0、基本概念
关注点:需求所要完成的功能。
切面(Aspect):一个关注点的模块化,这个关注点可能横切多个对象。
连接点(JoinPoint):关注点所分布的位置,或者具体说是切面上的方法。
切入点(Pointcut):匹配连接点的断言,它不是一个点,更像一条线。
通知/增强/拦截器(Advice):在切面的某个特定的连接点上的动作(相当于通知切面执行的时间)。通知有各种类型,如"before"、"after"等。
目标对象(Target Object):被一个或者多个切面(aspect)所通知的对象。在Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理对象。
代理对象(Proxy) : AOP框架创建的对象,用来实现切面契约包括通知方法执行等功能)。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving) : 简单理解,将切面作用到切入点上的过程。 Spring和其他纯Java AOP框架一样,在运行时完成织入。
1、导入aspectj-weave.jar
2、创建目标对象(Target Object)
package service.impl;
import org.springframework.stereotype.Service;
import service.StudentService;
/**
* 面向切面编程
*
*/
@Service
public class StudentServiceImpl implements StudentService {
/**
* 每一个被代理的方法称为切入点
*/
public void addStudent() {
System.out.println("add--------------");
}
public void updateStudent() {
System.out.println("update-----------");
}
public String selectStudent() {
System.out.println("selectStudent-----------");
return "hello world";
}
public void deleteStudent() {
System.out.println("deleteStudent-----------");
throw new RuntimeException();
}
}
3、创建切面(Aspect)
package service.impl;
import org.springframework.stereotype.Component;
/**
* AOP切面编程
*
*整个类就是一个切面
*/
@Component
public class Log4jTest {
/**
* 每个方法都是一个连接点
*/
public void start() {
System.out.println("log4j-------------------start");
}
public void end() {
System.out.println("log4j---------------------end");
}
// 切面里的连接点如果是用于aop:after-returning和aop:after-throwing才能带参数,在前置增强和后置增强不要带参数
public void afterReturning(String var) {
System.out.println("log4j---------------------afterReturning");
}
public void afterThrowing(Throwable tb) {
System.out.println("log4j---------------------afterThrowing");
}
}
4、编写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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="stu" class="service.impl.StudentServiceImpl"></bean>
<bean id="log" class="service.impl.Log4jTest"></bean>
<aop:config proxy-target-class="true">
<aop:aspect ref="log">
该切点指的是service包下的所有类的所有方法
<aop:pointcut expression="execution(* service.*.*(..))" id="pt"/>
该前置增强指的是在service包下的所有类的addStudent方法执行之前织入切面log里的start方法
<aop:before method="start" pointcut="execution(* service.*.addStudent(..))"/>
该后置增强切面采用的时id为pt的切面,意思是在每个切入点执行之后织入切面的log的end方法
<aop:after method="end" pointcut-ref="pt"/>
在每个切入点方法成功执行并且返回一个参数后才执行,参数名字为var(注意:returning="var"里的var可以自己定义,但一定要和连接点上的参数保持一致)
<aop:after-returning method="afterReturning" pointcut="execution(* service.*.*(..))" returning="var"/>
在每个切入点方法执行抛出异常才执行,同after-returning,参数名字throwing="tb"需要与连接点上的参数名字保持一致
<aop:after-throwing method="afterThrowing" pointcut-ref="pt" throwing="tb"/>
</aop:aspect>
</aop:config>
</beans>
5、测试
采用spring整合junit5。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import service.StudentService;
@SpringJUnitConfig(locations = "classpath:spring-config.xml")
public class StudentControler {
@Autowired
StudentService sImpl;
/**
* Spring AOP基于XML
*/
@Test
public void springAOPTest1() {
sImpl.addStudent();
System.out.println();
sImpl.updateStudent();
System.out.println();
sImpl.selectStudent();
System.out.println();
sImpl.deleteStudent();
}
}
三、基于xml+注解的Spring AOP
目标对象还是上例的StudentServiceImpl。
1、Java类定义切面
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component //声明这是一个spring容器管理的bean对象
@Aspect //声明该类是一个切面类对象
public class Log4jAspect {
@Pointcut("execution(* service.*.*(..))")
public void pt() {
}
/**
*
* @param JoinPoint 加上JoinPoint参数可以获取到切入点的相关信息。这个参数可加可不加。
*/
@Before("execution(* service.*.*(..))")
public void start(JoinPoint jp) {
System.out.println(jp.getSignature().getName() + "-------------------start");
}
@After("pt()")
public void end(JoinPoint jp) {
System.out.println(jp.getSignature().getName() + "---------------------end");
}
// 切面里的连接点如果是用于aop:after-returning和aop:after-throwing才能选择是否需要带参数,在前置增强和后置增强最好不要带除JoinPoint外的其他参数
@AfterReturning(pointcut = "pt()", returning = "var")
public void afterReturning(JoinPoint jp, String var) {
System.out.println(jp.getSignature().getName() + "---------------------afterReturning");
}
@AfterThrowing(pointcut = "pt()", throwing = "tb")
public void afterThrowing(JoinPoint jp, Throwable tb) {
System.out.println(jp.getSignature().getName() + "---------------------afterThrowing");
}
}
2、基于XML启用@AspectJ支持。
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="stu" class="service.impl.StudentServiceImpl"></bean>
<bean id="apt" class="service.impl.Log4jAspect"></bean>
<!--
启用aspectj-autoproxy自动代理。
proxy-target-class="true": 代表使用cglib生成代理对象
-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
3、测试
采用spring整合junit5。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import service.StudentService;
@SpringJUnitConfig(locations = "classpath:spring-config.xml")
public class StudentControler {
@Autowired
StudentService sImpl;
/**
* Spring AOP基于XML
*/
@Test
public void springAOPTest1() {
sImpl.addStudent();
System.out.println();
sImpl.updateStudent();
System.out.println();
sImpl.selectStudent();
System.out.println();
sImpl.deleteStudent();
}
}
总结
一些需要注意的点都写着代码里的注释里,需要反复铭记。
XML 擅长于在不接触源代码或重新编译的情况下修改配置
注解 应用在源码中,依赖字节码元数据,但是会使配置变的更短,更简洁。