AOP
参照《Spring思维导图,让Spring不再难懂(aop篇)》
AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。
面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。
但是人们也发现,在分散代码的同时,也增加了代码的重复性。比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。
也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?
这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。 AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。
OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,
在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则允许你定义从左到右的关系,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
描述AOP常用的一些术语有通知(Adivce)、切点(Pointcut)、连接点(Join point)、切面(Aspect)、引入(Introduction)、织入(Weaving)、通知(Advice)等。
aop使用场景
aop框架种类
AspectJ
JBoss AOP
Spring AOP
使用aop可以做的事情有很多。
性能监控,在方法调用前后记录调用时间,方法执行太长或超时报警。
缓存代理,缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
软件破解,使用AOP修改软件的验证类的判断逻辑。
记录日志,在方法执行前后记录系统日志。
工作流系统,工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务。
权限验证,方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉。
观察一下传统编码方式与使用aop的区别
两种动态代理方式
1:JDK 通过proxy对象和InvocationHandler对象
2:CGlib 通过操纵二进制字节码的方式产生动态代理
Spring默认采取的动态代理机制实现AOP,当动态代理不可用时(代理类无接口)会使用CGlib机制。
Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。
JDK动态代理
JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
CGLib动态代理
CGLib全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。
构建项目
文件目录
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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<context:component-scan base-package="com.neuedu"></context:component-scan>
<!-- 自动生成代理类 -->
<aop:aspectj-autoproxy>
</aop:aspectj-autoproxy>
</beans>
LoggingAspect.java
package com.neuedu.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LoggingAspect {
@Before("execution(public void com.neuedu.service.UserService.save())")
//执行这个注解之后,下面方法就会在一些方法执行之前执行
public void before(){
System.out.print("方法执行之前...");
}
}
UserService.java
package com.neuedu.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void save() {
System.out.println("调用Dao层保存用户信息");
}
}
Test.java
package com.neuedu.aspect;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.neuedu.service.UserService;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService us = ac.getBean(UserService.class);
us.save();
}
}
输出:
修改代码:
UserService.java
package com.neuedu.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void save() {
System.out.println("调用Dao层保存用户信息");
}
public void delete(String username){ //添加代码
System.out.println("删除" + username); //添加代码
}
}
LoggingAspect.java
package com.neuedu.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LoggingAspect {
@Before("execution(public void com.neuedu.service.UserService.*(..))") //修改代码
//执行这个注解之后,下面方法就会在一些方法执行之前执行
public void before(){
System.out.print("方法执行之前...");
}
}
Test.java
package com.neuedu.aspect;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.neuedu.service.UserService;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService us = ac.getBean(UserService.class);
//us.save();
us.delete("zhang"); //修改代码
}
}
输出:
修改代码:新建 GoodsServiec.java
package com.neuedu.service;
import org.springframework.stereotype.Service;
@Service
public class GoodsService {
public void save() {
System.out.println("保存商品信息...");
}
}
Test.java
package com.neuedu.aspect;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.neuedu.service.GoodsService;
import com.neuedu.service.UserService;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService us = ac.getBean(UserService.class);
us.save();
//us.delete("zhang");
GoodsService gs = ac.getBean(GoodsService.class); //新增代码
gs.save(); //新增代码
}
}
输出:
@Aspect //整个叫做切面类
public class LoggingAspect {
@Before("execution(public void com.neuedu.service.UserService.*(..))") //切入点语法 ,确定切入点位置,即所有类的所有方法(before 通知)
@Before("execution(* com.neuedu.service..*(..))") //终版,扫描该包下的所有子包和所有类的所有方法
public void before(){ //这个方法叫做切入点
System.out.print("方法执行之前...");
}
修改代码:
LoggingAspect.java
package com.neuedu.aspect;
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.springframework.stereotype.Component;
@Component
@Aspect //切面类
public class LoggingAspect {
@Before("execution(public void com.neuedu.service.*.*(..))")
//执行这个注解之后,下面方法就会在一些方法执行之前执行
public void before(){
System.out.print("方法执行之前...");
}
@After("execution(public void com.neuedu.service.*.*(..))") //新增代码
public void after() {
System.out.println("执行方法之后...");
}
@AfterReturning("execution(public void com.neuedu.service.*.*(..))") //新增代码
public void afterReturning() {
System.out.println("只有方法正确执行之后才会执行...");
}
@AfterThrowing("execution(public void com.neuedu.service.*.*(..))") //新增代码
public void afterThrow() {
System.out.println("当方法抛出异常时调用的方法");
}
}
UserServiceImpl.java
package com.neuedu.service;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
public void save() {
System.out.println("调用Dao层保存用户信息");
}
public void delete(String username){
System.out.println("删除" + username);
}
@Override
public int div(int a, int b) { //新增代码
int c = a / b;
return c;
}
}
UserService.java
package com.neuedu.service;
import org.springframework.stereotype.Service;
public interface UserService {
public void save() ;
public void delete(String username);
public int div(int a,int b); //新增代码
}
Test.java
package com.neuedu.aspect;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.neuedu.service.GoodsService;
import com.neuedu.service.UserService;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService us = ac.getBean(UserService.class);
//us.save();
//us.delete("zhang");
GoodsService gs = ac.getBean(GoodsService.class);
gs.save();
us.div(2, 1); //新增代码
}
}
输出:
@Before("execution(public void com.neuedu.service.*.*(..))")
//执行这个注解之后,下面方法就会在一些方法执行之前执行
@After("execution(public void com.neuedu.service.*.*(..))")
//执行这个注解之后,下面方法就会在一些方法执行之后执行
@AfterReturning("execution(public void com.neuedu.service.*.*(..))")
//执行这个注解之后,下面方法只有方法正确执行之后才会执行
@AfterThrowing("execution(public void com.neuedu.service.*.*(..))")
//执行这个注解之后,下面方法只有当方法抛出异常时才会执行