AOP相关简介
AOP:预编译,在运行期执行的。
动态代理是在不修改源码的情况下,对代码进行相应的增强。可以完成程序功能上松耦合。
AOP和动态代理功能差不多,且不要自己写动态代理的代码了。
AOP松耦合,抽取功能,减少重复代码,提高开发效率,且便于维护。
Spring采用动态代理技术。AspectJ采用编译器植入和类装载期织入。
AOP底层:spring集成了jdk代理技术和gclib代理技术。
jdk代理:目标对象基于目标接口生成代理对象放入运行时的内存中。
cglib代理:为目标对象动态生成子对象,但这个子对象比父对象功能更强大,不是继承是动态生成基于目标对象的对象,放入运行时内存中。
在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式(jdk\cglib)。
AOP相关术语
Target(目标对象:代理的目标对象
Proxy(代理):一个类被AOP织入增强后,禅城一个结果代理类
JoinPoint(连接点):被拦截到的点(方法)。可以被增强的方法叫做连接点。
Pointcut(切入点):即我们要对哪些JoinPoint进行拦截。
注:切入点是真正被增强的方法,被选中增强的连接点才是切入点。连接点和切入点的关系就像公民和人大代表一样。
Advice(通知/增强):即拦截到JoinPoint后要做的事情。即用于增强功能额外功能的代码。
Aspect(切面):切入点和通知的结合。切面=目标方法+增强方法。
Weaving(织入):把增强用于目标对象来创建新的代理对象的过程。织入是个动词,可以理解为切点和增强(通知)结合的过程就是织入。
AOP技术实现内容
Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,则使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
举例
比如日志控制
传统的抽取日志控制代码:在业务方法中还需要引用如save(user)、update(user)之类调用抽取出来的日志控制代码,还是出现耦合。
使用AOP:
将业务方法作为切入点,将日志控制代码作为增强,通过对业务方法使用配置文件或注解,使得在运行业务方法(切点)前,通过spring动态代理技术对切点进行增强的织入,基于目标对象生成代理对象。运行时代理对象的根据增强类型先后,调用增强代码和目标对象的切点方法即可。这样,目标方法和日志控制完全解耦,仅在运行时结合在一起。
快速入门
基于jdk的动态代理
创建一个mavenWeb项目(cglib需要web环境)。
com.kdy.proxy.jdk包中创建TargetInterface目标类接口
public interface TargetInterface {
public void save();
}
com.kdy.proxy.jdk包中创建Target(目标类接口实现类)实现TargetInterface接口
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("save running...");
}
}
com.kdy.proxy.jdk包中创建Advice增强类
public class Advice {
//前置增强
public void before(){
System.out.println("before...");
}
//后置增强
public void afterReturning(){
System.out.println("afterReturning...");
}
}
com.kdy.proxy.jdk包中创建测试类ProxyTest
public class ProxyTest {
public static void main(String[] args) {
//目标对象
final Target target = new Target();
//增强对象
final Advice advice = new Advice();
//基于目标对象动态生成代理对象:返回值 就是动态生成的代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(),//目标对象类加载器
target.getClass().getInterfaces(),//目标对象相同的接口字节码对象数组
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.before();//前置增强
Object invoke = method.invoke(target, args);//执行目标方法
advice.afterReturning();//后置增强
return invoke;
}
}
);
proxy.save();
}
}
运行main方法即可。
基于cglib的动态代理
上面项目pom我呢见引入spring依赖(spring依赖中包含cglib包)
<dependency><!--spring依赖包含cglib包-->
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
com.kdy.proxy.jdk包中创建Target(目标类)
public class Target {
public void save(){
System.out.println("save running......");
}
}
com.kdy.proxy.jdk包中创建Advice增强类
public class Advice {
//前置增强
public void before(){
System.out.println("before...");
}
//后置增强
public void afterReturning(){
System.out.println("afterReturning...");
}
}
com.kdy.proxy.jdk包中创建测试类ProxyTest
public class ProxyTest {
public static void main(String[] args) {
//目标对象
final Target target = new Target();
//增强对象
final Advice advice = new Advice();
//返回值 就是动态生成的代理对象、基于cglib
//1、创建增强器
Enhancer enhancer = new Enhancer();
//2、设置父类(目标)
enhancer.setSuperclass(Target.class);
//3、设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
advice.before();//执行前置
Object invoke = method.invoke(target, args);//执行目标
advice.afterReturning();//执行后置
return invoke;
}
});
//创建代理对象
Target proxy = (Target)enhancer.create();
proxy.save();
}
}
运行main方法即可。
面向切面编程-AOP开发
基于XML的AOP开发
快速入门
创建mavenWeb项目
引入spring、aspectJ、junit、spring-test依赖
<dependencies>
<dependency><!--spring依赖包含cglib包-->
<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.9.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
</dependencies>
在com.kdy.aop包中创建TargetInterface接口和Target目标类
public class Target implements TargetInterface{
public void save(){
System.out.println("save running...");
// int i = 1/0;//异常抛出
}
}
在com.kdy.aop包中创建MyAspect切面类
public class MyAspect {
//前置增强
public void before() {
System.out.println("before...");
}
//后置增强
public void afterReturning() {
System.out.println("afterReturning...");
}
//环绕增强
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("before around...");//环绕前增强
Object proceed = pjp.proceed();//切点方法
System.out.println("after around...");//环绕后
// 增强
return proceed;
}
//异常抛出增强
public void afterThrowing() {
System.out.println("afterThrowing...");
}
//最终增强
public void after() {
System.out.println("after...");
}
}
resource下applicationContext配置文件创建目标类的bena和切面类的bean,并配置织入
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--目标对象-->
<bean id="target" class="com.kdy.aop.Target"></bean>
<!--切面对象-->
<bean id="myAspect" class="com.kdy.aop.MyAspect"></bean>
<!--配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强(前置、后置...)-->
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<!--切面:切点+通知--><!--method="before"表示增强/通知,aop:before表示需要前置增强操作
pointcut="execution(public void com.kdy.aop.Target.save())为切点,execution为切点表达式-->
<!-- <aop:before method="before" pointcut="execution(public void com.kdy.aop.Target.save())"/>-->
<aop:before method="before" pointcut="execution(* com.kdy.aop.*.*(..))"/>
<aop:after-returning method="afterReturning" pointcut="execution(* com.kdy.aop.*.*(..))"/>
<aop:around method="around" pointcut="execution(* com.kdy.aop.*.*(..))"/>
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.kdy.aop.*.*(..))"/>
<aop:after method="after" pointcut="execution(* com.kdy.aop.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
切点表达式:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
修饰符可省略,返回值类型、包名、类名、方法名可用*代表任意
包名和类名之间一个点代表当前包下的类,两个点..代表当前包及其子包下的类
参数列表可使用两个点..表示任意个数,任意类型的参数列表
xml中的切点表达式除了写全,还可简写举例如下:
execution(void com.kdy.aop.Target.*(..)) execution(* com.kdy.aop.*.*(..))
execution(* com.kdy.aop..*.*(..)) execution(* com.kdy.aop.*.*(..)) execution(* *..*.*(..))
一般常用 execution(* com.kdy.aop.*.*(..))
通知类型:
前置通知:<aop:before> 用于配置前置通知。指定增强的方法在切入点方法之前执行。
后置通知:<aop:after-returning>配置后置通知。指定增强的方法在切入点方法之后执行。
环绕通知:< aop:around>用于环绕通知。指定增强的方法在切入点方法之前和之后都执行。
异常抛出通知:<aop:throwing>配置异常抛出通知。指定增强的方法在出现异常时执行。
最终通知:<aop:after>用于配置最终通知。无论增强方式执行是否有异常都会执行。
在test目录中写测试类,注入目标类,直接调用目标类的方法,运行测试用例即可
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
/*
* 注:这里要注入接口TargetInterface,因为xml中声明的是Target实现类的bean,但经过织入生成代理对象的bean是最终要注入进来的
* 因为Target有接口所以spring基于jdk来动态代理生成代理对象,这个代理对象也是TargetInterface接口类型的,
* 所以spring生命周期中最终通过TargetInterface注入时找到的是动态代理对象的bean,将其注入进来
* */
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
注:@Autowired注入的类型为接口,就是考虑到spring中aop,虽然类型为实现类也行,但aop的时候必须注入类型为接口,所以通常平时都注入接口类型。若某个注入接口有多实现类,可用配合@Qualify或直接用@Resource。
执行顺序增强类型都是相对切点方法的先后属顺序。
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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--目标对象-->
<bean id="target" class="com.kdy.aop.Target"></bean>
<!--切面对象-->
<bean id="myAspect" class="com.kdy.aop.MyAspect"></bean>
<!--配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强(前置、后置...)-->
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<!--抽取切点表达式-->
<aop:pointcut id="myPoint" expression="execution(* com.kdy.aop.*.*(..))"/>
<aop:before method="before" pointcut-ref="myPoint"/>
<aop:after-returning method="afterReturning" pointcut-ref="myPoint"/>
<aop:around method="around" pointcut-ref="myPoint"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPoint"/>
<aop:after method="after" pointcut-ref="myPoint"/>
</aop:aspect>
</aop:config>
</beans>
基于注解的AOP开发
快速入门
mavenWeb项目,pom文件引入spring、aspectJ、junit、spring-test依赖
<dependencies>
<dependency><!--spring依赖包含cglib包-->
<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.9.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
</dependencies>
在com.kdy.anoo包中创建Target目标类实现目标类接口TargetInterface,并使用spring注解声明bean。
@Component("target")
public class Target implements TargetInterface {
public void save(){
System.out.println("save running...");
// int i = 1/0;//异常抛出
}
}
在com.kdy.anoo中创建MyAspect,并使用spring注解声明bean,并使用@Aspect声明为切面类
@Component("myAspect")
@Aspect//切面类
public class MyAspect {
//抽取切点表达式
@Pointcut("execution(* com.kdy.anno.*.*(..))")
public void myPointcut(){}
//前置增强
@Before("myPointcut()")//使用上面抽取的切点表达式
public void before() {
System.out.println("before...");
}
//后置增强
@AfterReturning("MyAspect.myPointcut()")//使用上面抽取的切点表达式
public void afterReturning() {
System.out.println("afterReturning...");
}
//环绕增强
@Around("execution(* com.kdy.anno.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("before around...");//环绕前增强
Object proceed = pjp.proceed();//切点方法
System.out.println("after around...");//环绕后
// 增强
return proceed;
}
//异常抛出增强
@AfterThrowing("execution(* com.kdy.anno.*.*(..))")
public void afterThrowing() {
System.out.println("afterThrowing...");
}
//最终增强
@After("execution(* com.kdy.anno.*.*(..))")
public void after() {
System.out.println("after...");
}
}
resource下创建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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://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 http://www.springframework.org/schema/context/spring-context.xsd
">
<!--配置spring组件扫描-->
<context:component-scan base-package="com.kdy.anno"/>
<!--配置aop自动代理,@Aspect注解才会被扫描到-->
<aop:aspectj-autoproxy/>
</beans>
test目录下的测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
/*
* 注:这里要注入接口TargetInterface,因为xml中声明的是Target实现类的bean,但经过织入生成代理对象的bean是最终要注入进来的
* 因为Target有接口所以spring基于jdk来动态代理生成代理对象,这个代理对象也是TargetInterface接口类型的,
* 所以spring生命周期中最终通过TargetInterface注入时找到的是动态代理对象的bean,将其注入进来
* */
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
其实也可以创建如applicationContext-anno.xml,并复制以上applicationContext.xml中的内容,并在test的测试类中上方的改成@ContextConfiguration("classpath:applicationContext-anno.xml")。