AOP:Aspect Oriented Programming 面向切面编程
AOP,可以通过预编译方式和运行其动态代理实现在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术。也是Spring的两大特色IOC(Inversion of Controler控制反转)和AOP之一.
Spring中提供了四种实现AOP的方式:实现Spring提供的AOP接口
1.JDK提供的代理方式(又分为静态和动态)
2.原生Spring API接口
3.Spring纯配置方式
4.Spring注解
一.使用JDK提供的代理方式(此种方式不依赖于Spring框架)
1.1静态代理
我们通过一个小女孩去看电影的生活案例来说明,上源码~
interface GoOut,先把案例抽象成接口:
package com.ndkj.StaticProxy;
/**女孩看电影的接口类*/
public interface GoOut {
9
public void lookMovie();
}
class AGril,在抽象接口的基础上具体出小女孩的类:
package com.ndkj.StaticProxy;
/**小女孩实现类*/
public class AGirl implements GoOut{
@Override
public void lookMovie() {
System.out.println("我要看电影~!!");
}
}
到这里就是我们完全不使用AOP思想进行类实现的流程,接下来就要开始进行AOP实现了,我们想在不改变AGril类的基础上,在女孩看电影前为女孩加上更加详细的步骤,例如先准备号money,先买一点零食(假设女孩去的电影院可以自己带零食~),先坐公交车…
Class Proxy,创建一个代理类,此类也需要实现小女孩的接口,它将帮助小女孩完成看电影中所有的工作:
package com.ndkj.StaticProxy;
/**代理类*/
public class Proxy implements GoOut{
//通过引入女孩类的方式实现类的关联
private AGirl AGirl;
@Override
public void lookMovie() {
takeMoney();
takeBus();
AGirl.lookMovie();
goHome();
}
//带上钱~
public void takeMoney(){
System.out.println("看电影前带上Money~!");
}
//坐大巴~
public void takeBus(){
System.out.println("坐公交车去看电影~");
}
//买一点吃的!
public void buySemeFood(){
System.out.println("看电影前买一些爆米花!");
}
//高高兴兴回家~~
public void goHome(){
System.out.println("今天的电影真好看~回家!!");
}
public Proxy() {
}
public Proxy(AGirl AGirl) {
this.AGirl = AGirl;
}
}
至此我们的准备工作已经做完了~可以放心让小女孩去看电影了!
Class LetsGo测试类:
package com.ndkj.StaticProxy;
/**测试*/
public class LetsGo {
public static void main(String[] args) {
AGirl AGirl = new AGirl();
//将AGril类导入到代理类中,全程让代理类帮我们实现
Proxy proxy = new Proxy(AGirl);
proxy.lookMovie();
}
}
No Bug,No Error:
2.2动态代理
在上面的静态代理中,每有一个新的类产生,我们都需要写一个代理类来代理,针对静态代理的弊端,我们使用动态代理.动态代理的实际实现类并不需要我们书写,我们通过写代理类的处理类来自动生成实际代理类,它区别于静态代理于此.
我们把之前的接口和类搬下来:
package com.ndkj;
/**极度抽象的接口*/
public interface GoOut{
public void lookMovie();
}
Class AGril:
package com.ndkj;
/**小女孩类*/
public class AGril implements GoOut {
@Override
public void lookMovie() {
System.out.println("我出去看电影~");
}
}
在使用动态代理时,我们的代理类需要实现InvocationHandler类,通过这个类来动态生成实际的代理类.上代码:
package com.ndkj;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//我们会使用这个类自动生成代理类,代替AGril类来完成小女孩看电影的全部操作
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private AGril aGril;
public void setRent(AGril aGril) {
this.aGril = aGril;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), aGril.getClass().getInterfaces(),this );
}
/**此方法为目标类的代理类,处理代理实例,并返回结果*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是使用反射机制实现
takeMoney();
buyFood();
Object result = method.invoke(aGril, args);
backHome();
return null;
}
//带上钱~
public void takeMoney(){
System.out.println("先带上钱~");
}
//买爆米花
public void buyFood(){
System.out.println("买了一些爆米花!");
}
//回家
public void backHome(){
System.out.println("回家!!");
}
}
测试again:
package com.ndkj;
public class Client {
public static void main(String[] args) {
//真实角色
AGril aGril = new AGril();
//代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(aGril);
GoOut proxy = (GoOut) pih.getProxy(); //这里的proxy就是动态生成的,我们并没有写他
proxy.lookMovie();
}
}
no Bug~
二.通过实现Spring提供的AOP接口
Spring框架为我们提供了多种实现AOP的方式,这是Spring提供的方式之一.
放上源码:
interface UserService:
package com.ndkj.service;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
class UserServiceImpl接口实现类:
package com.ndkj.service;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户!");
}
@Override
public void delete() {
System.out.println("删除了一个用户!");
}
@Override
public void update() {
System.out.println("修改了一个用户!");
}
@Override
public void query() {
System.out.println("查询了一个用户!");
}
}
要使用Spring提供的接口来实现AOP,我们需要继承相应的功能增强的Spring接口,以下以前置通知MethodBeforeAdvice接口,后置通知AfterReturningAdvice为例,来实现为业务方法提供日志功能:
Class BeforeLog前置通知类,实现MethodBeforeAdvice接口:
package com.ndkj.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
//method:要执行的目标对象的方法
//args:参数
//target:目标对象
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("[前置Advice]: "+target.getClass().getName()+"的"+method.getName()+"方法被执行了");
}
}
Class AfterLog后置通知类,实现AfterReturningAdvice接口:
package com.ndkj.log;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
//returnValue:切入目标方法的返回值,切入的后置方法可以对返回值进行操作
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("[后置Advice]: 执行了"+method.getName()+"方法,返回值为"+returnValue);
}
}
&emsp后置通知在;重写afterReturning时会传入Object returnValue参数,它可以提供目标业务方法的返回值.
至此我们需要准备的类和接口都已经准备完毕,接下来还需要配置Spring配置文件:
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userServiceImpl" class="com.ndkj.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.ndkj.log.BeforeLog"/>
<bean id="afterLog" class="com.ndkj.log.AfterLog"/>
<!--AOP实现方式之一:使用原生Spring API接口-->
<!--配置AOP,在xml文件中引用AOP功能,必须先导入AOP的头文件依赖-->
<!--xmlns:aop="http://www.springframework.org/schema/aop"-->
<aop:config>
<!--切入点:id切入点的id,expression:确定要切入的地方和其详细信息
expression()-->
<aop:pointcut id="pointcut" expression="execution(* com.ndkj.service.UserServiceImpl.*(..))"/>
<!--执行环绕增强-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
至此使用Spring原生API接口实现AOP已经全部配置完毕,我们继续写一个测试类:
package com.ndkj.service;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void AOPTest(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userServiceImpl");
userService.add();
}
}
注意:在getBean得到实例化对象的必须返回和强转该对象的接口类型,不能使用实现类的类型.假设返回实现类类型UserServiceImpl,那么这个返回的实现类对象userService就会出现两种不同的表现形式(一种是没有功能增强的,一种是具备功能增强的),对于一个固定的类来说,这显然是自相矛盾的.
三.使用Spring纯配置实现
使用纯配置实现比使用Spring原生API接口要简单,它不需要继承接口,仅仅是一个简单的类而已~
我们继续把接口和实现类拷下来:
interface UserService:
package com.ndkj.service;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
Class UserServiceImpl:
package com.ndkj.service;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户!");
}
@Override
public void delete() {
System.out.println("删除了一个用户!");
}
@Override
public void update() {
System.out.println("修改了一个用户!");
}
@Override
public void query() {
System.out.println("查询了一个用户!");
}
}
接下来我们创建一个切面类,注意以下切面类没有使用任何AOP注解和实现接口,这仅仅是一个普通类。
Class DiyAspect:
package com.ndkj.diy;
public class DiyAspect {
public void before(){
System.out.println("DIY-AOP前置方法执行");
}
public void after(){
System.out.println("DIY-AOP后置方法执行");
}
}
我们需要使用Spring纯配置方式来实现AOP,接下来是使用纯配置实现AOP最重要的一步,配置Spring:
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="diyAspect" class="com.ndkj.diy.DiyAspect"/>
<bean id="userServiceImpl" class="com.ndkj.service.UserServiceImpl"/>
<aop:config>
<aop:aspect ref="diyAspect">
<aop:pointcut id="point" expression="execution(* com.ndkj.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
至此我们完成了使用纯配置方式实现了AOP:
package com.ndkj.service;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTEst {
@Test
public void AOPTest(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userServiceImpl");
userService.add();
}
}
一切正常~
四.使用注解实现AOP
注解方式分为纯注解(完全不依赖配置文件bean的AOP)和依赖(或部分依赖)配置文件bean的注解方式AOP,需要注意两者都需要在配置文件中做设置,例如:
<?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:p="http://www.springframework.org/schema/p"
5 xmlns:context="http://www.springframework.org/schema/context"
6 xmlns:aop="http://www.springframework.org/schema/aop"
7 xsi:schemaLocation="
8 http://www.springframework.org/schema/beans
9 http://www.springframework.org/schema/beans/spring-beans.xsd
10 http://www.springframework.org/schema/context
11 http://www.springframework.org/schema/context/spring-context.xsd
12 http://www.springframework.org/schema/aop
13 http://www.springframework.org/schema/aop/spring-aop.xsd">
14
15 <!-- 开启注解扫描,此方式可以在类上使用@ComponentScan("com...")替代 -->
16 <context:component-scan base-package="com.bie.aop"></context:component-scan>
17
18 <!-- 开启aop注解方式,默认为false -->
19 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
20
21 </beans>
使用注解方式AOP,我们需要在进行横切的类和方法上写明注释,切面类上使用@Aspect,切面类的横切方法上使用@After(),@Before(),@AfterThrowing(),@AfterReturning(),@Around()注解进行标注该切面方法的切入时机。
通知类型介绍
(1)Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
(2)AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
(3)AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名
来访问目标方法中所抛出的异常对象
(4)After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式
(5)Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
例如:
package com.ndkj.diy;
/**@Aspect:放在类的上面,标注这个类是一个切面类,也就是给业务方法附加的类,
* 相当于使用纯配置方式中的<aop:aspect ref="diyAspect">*/
@Aspect
public class Log {
/**使用@Before来标记切入点,放在方法的上面*/
@Before("execution(* com.ndkj.service.UserServiceImpl.*(..))")
public void addBeforeLog(){
System.out.println("[@Before]By Annotation to AOP:" + new Date());
}
@After("execution(* com.ndkj.service.UserServiceImpl.*(..))")
public void addAfterLog(){
System.out.println("[@After]By Annotation to AOP:" + new Date());
}
/**在使用环绕注解时,可以传入ProceedingJoinPoint的对象来获取业务方法的返回值*/
@Around("execution(* com.ndkj.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint pj) throws Throwable {
System.out.println("[@Around]By Annotation to AOP:"+"@Around前");
UserService userService =(UserService) pj.proceed();
System.out.println("[@Around]By Annotation to AOP:"+"@Around后userServiceImpl返回值"+userService);
}
}
}
测试结果:
我们可以在需要切入的业务方法上加上@PointCut定义一个切入点,这样在切入方法中引入execution的包名中可以直接使用定义的pointcut点进行定位。