什么是AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。面向切面编程这个概念一直被很多人诟病,因为它和IoC一样晦涩,不太容易理解。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
现在我举个例子,以前我们学习Servlet的时候,几乎每个Servlet都要编写解决乱码代码,假如有很多Servlet,那么我们就要重复的写多次,然而这些横切重复代码无法通过抽象父类的方式提取出来。
后来,我们学习了Filter,我们知道在访问Servle之前,会先访问自定义配置的Filter,这样我们就可以把解决乱码的代码都放到Filter里,这样既解决了乱码问题,也避免了重复代码,提高了开发效率。
AOP独辟蹊径通过横向抽取机制为这类无法通过纵向继承的重复性代码提供了解决方案,AOP希望将这些分散在业务逻辑中的相同代码,通过横向切割的方式抽取到一个独立模块中,我们知道将这些重复性的横切代码提取出来是很容易的,但如何将这些独立的模块融合到业务逻辑中完成和以前一样的操作,这才是关键,也是AOP解决的主要问题。
AOP术语
- 连接点(Joinpoint):程序执行的某个特定位置:如类初始化前,类初始化后,某个方法调用前,方法调用后,方法抛出异常后。一个类或一段代码拥有一些具有边界性质的特定点,就被称为 “连接点”。Spring仅支持方法的连接点,仅能在方法调用前后、方法抛出异常时这些连接点织入增强。(目标对象中,所有可以增强的方法)
- 切点(Pointcut):每个程序都拥有很多连接点,如有一个两个方法的类,这两个方法都是连接点。如何定位到某个连接点上呢?AOP通过“切点”定位特定的连接点。Spring AOP的解析引擎负责解析切点设定的条件,找到对应的连接点。连接点是方法执行前后等具体程序执行点,而切点只定位到某个方法上,所以如果定位到具体的连接点,还需要提供方位信息,即方法执行前还是执行后。 (目标对象中,已经或者需要增强的方法)
- 增强/通知(Advice):增强是织入到目标类连接点上的一段程序代码。在Spring中,增强除了描述一段程序代码外,还拥有另一个和连接点相关的信息,就是执行点的方位。结合方位信息和切点信息,就可以找到特定的连接点。Spring提供的增强接口都是带方位名的:BeforeAdvice,AfterRetuningAdvice,ThrowsAdvice等。所以只有结合切点和增强,才能确定特定的连接点并织入增强代码
- 目标对象(Target):需要织入增强代码的目标类。
- 织入(Weaving):织入是将增强添加到目标类上具体连接点的过程。
- 代理(Proxy):一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。
- 切面(Aspect):切面由切点和增强组成,既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面,将定义的横切逻辑织入到切面指定的连接点中。
JDK动态代理和CGLib动态代理
AOP的工作重心在于如何将增强应用于目标对象的连接点上,包括两个工作。
第一:如何通过切点和增强定位到连接点
第二,如何在增强中编写切面的代码。
Spring AOP使用动态代理技术在运行期织入增强的代码。Spring AOP使用了两种代理机制,一种是基于JDK的动态代理;另一种是基于CGLib的动态代理,之所以使用两种代理机制,很大程度上是因为JDK本身只提供接口的代理,不支持类的代理
JDK动态代理
演示一个事务操作的例子:
//用户操作接口
public interface UserService {
public void save();
public void delete();
}
//UserService的实现类
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("用户信息保存!");
}
public void delete() {
System.out.println("删除用户信息!");
}
}
//现在我们要开这些操作前添加开启事务和关闭事务。这些动作是相同的,所以我们提取出来
public class MyAspect {
public void startTransaction(){
System.out.println("开启事务...");
}
public void commitTransaction(){
System.out.println("事务提交...");
}
}
//现在我们需要使用动态代理将增强逻辑和方法编织在一起
public class MyProxyFactory {
public static UserService createUserService(){
final UserService us=new UserServiceImpl();
final MyAspect aspect=new MyAspect();
//生成代理对象
UserService usproxy=(UserService) Proxy.newProxyInstance(MyProxyFactory.class.getClassLoader(),
us.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] arg2) throws Throwable {
aspect.startTransaction();
Object obj=method.invoke(us, arg2);
aspect.commitTransaction();
return obj;
}
});
return usproxy;
}
}
//测试
public class TestJDKProxy {
@Test
public void test(){
UserService userService=MyProxyFactory.createUserService();
userService.save();
}
}
CGLib动态代理
使用JDK动态代理有一个很大的限制,就是只能为接口创建代理实例。 对于没有通过接口定义的类,如何动态创建代理实例呢?CGLib填补了这么空缺。
我们还使用上面的小例子,不过要稍作一些修改。
//我们还实现一个UserServiceImpl类,但是不需要实现任何接口
public class UserServiceImpl {
public void save() {
System.out.println("用户信息保存!");
}
public void delete() {
System.out.println("删除用户信息!");
}
}
//把横切逻辑代码提取出来
public class MyAspect {
public void startTransaction(){
System.out.println("开启事务...");
}
public void commitTransaction(){
System.out.println("事务提交...");
}
}
//我们使用CGLib生成代理对象
public class CglibProxy {
public static UserServiceImpl createUserServiceImpl(){
//CGLib核心类
Enhancer enhancer=new Enhancer();
UserServiceImpl us=new UserServiceImpl ();
MyAspect aspect=new MyAspect();
//设置父类,因为CGLib底层是生成需要被代理类的子类
enhancer.setSuperclass(us.getClass());
//设置回调方法,和InvocationHandler起到的效果一样
enhancer.setCallback(new org.springframework.cglib.proxy.MethodInterceptor() {
//intercept方法会拦截所有目标类方法的调用
//proxy表示目标类的实例,method为目标类方法的反射对象,arg2是方法的参数,arg3是代理类实例
@Override
public Object intercept(Object proxy, Method method, Object[] arg2, MethodProxy arg3) throws Throwable {
aspect.startTransaction();
Object obj=method.invoke(us, arg2);
aspect.closeTransaction();
return obj;
}
});
//创建代理对象
UserServiceImpl usProxy=(UserServiceImpl ) enhancer.create();
return usProxy;
}
}
//进行测试
public class TestCGLibProxy {
@Test
public void test(){
UserServiceImpl userServiceImpl =CglibProxy.createUserServiceImpl();
userServiceImpl .run();
}
}
Spring的AOP
我们纯手工的编写创建代理的过程,虽然可以横切逻辑的动态织入,但过程繁琐,为不同类创建代理时,需要分别编写不同的代码,无法做到通用,同时也不利于维护。这时我们就要用到Spring的AOP。
Spring AOP的底层就是通过使用JDK动态代理或者CGLib动态代理技术为目标Bean织入横切逻辑。
Spring AOP通过切点指定在哪些类的哪些方法上织入横切逻辑,通过增强描述横切逻辑代码和方法的具体织入点。Spring通过切面将切点和增强组装起来,通过切面的信息,就可以利用JDK或者CGLib的动态代理技术采用统一通用的方式为Bean创建代理对象。
Spring中增强类型
Spring使用增强类定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括了在方法的哪一点织入横切代码的方位信息,所以增强既包括横切逻辑,还包括连接点的部分信息。
前置增强(Before):表示在目标方法执行前实施增强。
后置增强(AfterReturning):表示在目标方法执行后实施增强。
环绕增强(Around):表示在目标方法的执行前后实施增强。
异常抛出增强(AfterThrowing):表示在目标方法抛出异常后实施增强。
- 最终增强 (After):无论目标方法是否出现异常 最终增强都会执行.
Spring 进行 AOP 开发
我们还是用前面我们用的UserService的例子
//用户操作接口
public interface UserService {
public void save();
public void delete();
}
//UserService的实现类
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("用户信息保存!");
}
public void delete() {
System.out.println("删除用户信息!");
}
}
// 我们使实现方法前置增强接口,并写入我们的横切逻辑代码
public class MyBeforeAdvice implements MethodBeforeAdvice {
// method为目标类的方法,args为目标类方法的所需参数,obj为目标类实例
public void before(Method method, Object[] args, Object obj) throws Throwable {
System.out.println("开启事务...");
}
}
// 我们测试一下
@Test
public void test() {
UserService us = new UserServiceImpl();
BeforeAdvice advice = new MyBeforeAdvice();
// Spring提供的代理工厂
ProxyFactory pf = new ProxyFactory();
// 设置代理目标
pf.setTarget(us);
// 为代理目标添加增强
pf.addAdvice(advice);
// 生成代理对象
UserService proxy = (UserService) pf.getProxy();
proxy.save();
}
ProxyFactory代理工厂将增强Advice织入到目标类us中,ProxyFactory内部就是使用JDK动态代理或者CGLib动态代理技术。
Spring使用配置文件声明代理实现AOP
虽然通过ProxyFactory已经简化了很多操作,但是Spring给我们提供了通过配置文件来声明一个代理来让免去硬编码
<!--实例化目标对象-->
<bean id="target" class="com.pngyul.UserServiceImpl"></bean>
<!--实例化增强对象-->
<bean id="advice" class="com.pngyul.MyBeforeAdvice"></bean>
<!--实例化代理对象-->
<bean id="proxyus" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--指定代理的接口,有多个的话可以使用数组、list等标签-->
<property name="proxyInterfaces" value="com.pngyul.UserService"></property>
<!--指定使用的增强-->
<property name="interceptorNames" value="advice"></property>
<!--指定代理的目标类-->
<property name="target" ref="target"></property>
</bean>
ProxyFactoryBean是FactoryBean接口的实现类,我们前一节中介绍了FactoryBean的功能,它负责实例化一个Bean。ProxyFactoryBean负责为其他Bean创建代理对象,内部使用ProxyFactory来完成。我们来了解一下可配置的属性
target: 指定要代理的目标对象。
proxyInterfaces:代理要实现的接口,可以是多个接口,使用数组、list标签来指定。
interceptorNames:需要织入的增强类。 是String[]类型,接受增强Bean的id而不是实例。
singleton:返回的代理是否是单实例,默认是单实例。
optimize:当设置为true时,强制使用CGLib代理。
proxyTargetClass:是否对类进行代理,设置为true,使用CGLib代理。
//我们测试一下
public class Test {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
UserService usproxy = (UserService ) ac.getBean("proxyus");
proxyus.delete();
}
}
Spring 使用 AspectJ 进行 AOP 开发
AspectJ切点表达式
*匹配任意字符,只匹配一个元素
.. 匹配任意字符,可以匹配多个元素 ,在表示类时,必须和*联合使用
+表示按照类型匹配指定类的所有类,必须跟在类名后面,如com.cad.Car+,表示继承该类的所有子类包括本身
execution(表达式)
表达式 : [方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数)
public void com.pngyul.spring.service.UserServiceImpl.save()
void com.pngyul.spring.service.UserServiceImpl.save()
* com.pngyul.spring.service.UserServiceImpl.save()
* com.pngyul.spring.service.UserServiceImpl.*()
* com.pngyul.spring.service.*ServiceImpl.*(..)
* com.pngyul.spring.service..* ServiceImpl.*(..)
AspectJ基于XML配置切面
在前面4+2的基础上,我们还需要使用AspectJ我们需要的jar包有如下,同时在需要导入aop约束
我们还是用之前那个例子:
//我们先创建UserServiceImpl类
public class UserServiceImpl {
public void save() {
System.out.println("用户信息保存!");
}
public void delete() {
System.out.println("删除用户信息!");
}
}
//我们创建一个增强类
public class AdviceMethod {
public void before(){
System.out.println("开启事务!");
}
}
<!--配置文件-->
<?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" //声明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">
<!-实例UserServiceImpl类和增强类-->
<bean id="us" class="com.pngyul.service.UserServiceImpl"></bean>
<bean id="advices" class="com.pngyul.AdviceMethod"></bean>
<!-配置aop,proxy-target-class属性设定为true时,使用CGLib,为false时,使用JDK动态代理-->
<aop:config proxy-target-class="true">
<!--使用<aop:aspect>标签定义切面,ref引入增强-->
<aop:aspect ref="advices">
<!--通过<aop:before>声明一个前置增强,pointcut属性使用切点表达式,method指定使用增强类中方法-->
<aop:before pointcut="execution(* com.pngyul.service..* ServiceImpl.*(..))" method="before" />
</aop:aspect>
</aop:config>
<!--我们可以外面配置一个切点,使用时直接引用即可-->
<!--
<aop:config proxy-target-class="true">
<aop:aspect ref="advices">
//定义一个切点
<aop:pointcut expression="execution(* com.pngyul.service..* ServiceImpl.*(..))" id="mypointcut"/>
//直接通过id引用即可
<aop:before pointcut-ref="mypointcut" method="before" />
</aop:aspect>
</aop:config>
-->
</beans>
//我们测试一下
public class TestDemo {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl us=(UserServiceImpl )ac.getBean("us");
us.save();
us.delete();
}
}
再用这个例子展示一下环绕增强:
//定义环绕增强方法,参数为ProceedingJoinPoint,返回值为Object,这是连接点信息
public class AdviceMethod {
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕增强前");
Object obj=pjp.proceed();
System.out.println("环绕增强后");
return obj;
}
}
<!--配置环绕增强-->
<aop:config proxy-target-class="true">
<aop:aspect ref="advices">
<aop:pointcut expression="execution(* com.pngyul.service..* ServiceImpl.*(..))" id="mypointcut"/>
<aop:around method="around" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>
//测试
public class TestDemo {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl us=(UserServiceImpl )ac.getBean("us");
us.save();
us.delete();
}
}
其他三个配置起来都大同小异,这里就不再一一演示。
AspectJ基于注解配置切面
使用注解配置切面,也需要导入上面的所说的jar包,除此之外,还需要引入aop和context约束
//先使用注解来实例我们的Bean
@Component("us")
public class UserServiceImpl {
public void save() {
System.out.println("用户信息保存!");
}
public void delete() {
System.out.println("删除用户信息!");
}
}
//实例增强类
@Component("advices")
//使用Aspect
@Aspect
public class AdviceMethod {
//使用环绕增强,里面参数是切点表达式
@Around("execution(* com.pngyul.service..* ServiceImpl.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕增强前");
Object obj=pjp.proceed();
System.out.println("环绕增强后");
return obj;
}
}
<?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.pngyul.*"></context:component-scan>
//使用aspectj自动代理,自动为匹配@AspectJ切面的Bean创建代理
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
PS:可能有些例子不太符合某些知识的应用,这里只是单纯的演示一下,没必要较真!!!
PS : 文章自行总结于<春水上行>,了解更多,please click here,