前言
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
相信Java开发者对AOP都不陌生,尤其是使用Spring框架的开发者,几乎是面试必问。 网上给出的答案都是使用代理来实现的,那么大家有没有很好的去理解,使用代理模式到底该如何实现呢?
今天就记录一下笔者手写AOP的过程,顺便和大家分享一下AOP底层的实现。
手写AOP
先不着急写代码,理清了思路才好开干。
实现思路
自定义注解,为后续定义AOP、切点、各种通知。解析切面类,获取切入点,解析表达式,明确哪些方法需要执行哪些通知。为Bean生成代理对象返回IOC容器,在代理对象中执行通知。解析方法的入参和出参,封装后分别传递给对应的通知方法。
实战
1、自定义必要的注解
@Target({ElementType
.TYPE
})
@Retention(RetentionPolicy
.RUNTIME
)
@Documented
public @
interface Aop {
}
@Target({ElementType
.FIELD
})
@Retention(RetentionPolicy
.RUNTIME
)
@Documented
public @
interface MyPointcut {
String
value() default "";
}
@Target({ElementType
.METHOD
})
@Retention(RetentionPolicy
.RUNTIME
)
@Documented
public @
interface Before {
}
@Target({ElementType
.METHOD
})
@Retention(RetentionPolicy
.RUNTIME
)
@Documented
public @
interface After {
}
2、定义方法增强描述
public class Advice {
private String classMethodName
;
private Object aopBean
;
private Method before
;
private Method after
;
public Advice(String classMethodName
, Object aopBean
, Method before
, Method after
) {
this.classMethodName
= classMethodName
;
this.aopBean
= aopBean
;
this.before
= before
;
this.after
= after
;
}
}
3、编写AOP代理类
public class AopProxy implements InvocationHandler {
private ParameterNameDiscoverer parameterNameDiscoverer
= new LocalVariableTableParameterNameDiscoverer();
private final Object origin
;
private final Advice advice
;
public AopProxy(Object origin
, Advice advice
) {
this.origin
= origin
;
this.advice
= advice
;
}
@Override
public Object
invoke(Object proxy
, Method method
, Object
[] args
) throws Throwable
{
String
[] parameterNames
= parameterNameDiscoverer
.getParameterNames(origin
.getClass().getMethod(method
.getName(), method
.getParameterTypes()));
Map
<String, Object> params
= new HashMap<>();
for (int i
= 0; i
< args
.length
; i
++) {
params
.put(parameterNames
[i
], args
[i
]);
}
advice
.getBefore().invoke(advice
.getAopBean(), origin
.getClass(), method
, params
);
Object result
= method
.invoke(origin
, args
);
advice
.getAfter().invoke(advice
.getAopBean(), origin
.getClass(), method
, result
);
return result
;
}
}
4、实现BeanPostProcessor,解析切面类,给需要增强的类生成代理对象。
@Component
public class AopBeanPostProcessor extends ApplicationObjectSupport implements BeanPostProcessor {
private Collection
<Object> AOP_BEANS
;
private Map
<String, Advice> adviceMap
= new ConcurrentHashMap<>();
@PostConstruct
public void init() throws Exception
{
AOP_BEANS
= getApplicationContext().getBeansWithAnnotation(Aop
.class).values();
for (Object bean
: AOP_BEANS
) {
Class
<?> beanClass
= bean
.getClass();
for (Field field
: beanClass
.getDeclaredFields()) {
if (field
.getDeclaredAnnotation(MyPointcut
.class) != null
) {
field
.setAccessible(true);
String pointcut
= (String
) field
.get(bean
);
Method before
= null
;
Method after
= null
;
for (Method method
: beanClass
.getMethods()) {
if (method
.getDeclaredAnnotation(Before
.class) != null
) {
before
= method
;
}
if (method
.getDeclaredAnnotation(After
.class) != null
) {
after
= method
;
}
}
adviceMap
.put(pointcut
, new Advice(pointcut
, bean
, before
, after
));
}
}
}
}
public Object
postProcessAfterInitialization(Object bean
, String beanName
) throws BeansException
{
Class
<?> beanClass
= bean
.getClass();
Advice advice
= adviceMap
.values().iterator().next();
if (advice
.getClassMethodName().startsWith(beanClass
.getName())) {
for (Method method
: beanClass
.getDeclaredMethods()) {
if (advice
.getClassMethodName().equalsIgnoreCase(beanClass
.getName() + "@" + method
.getName())) {
return Proxy
.newProxyInstance(beanClass
.getClassLoader(), beanClass
.getInterfaces(), new AopProxy(bean
, advice
));
}
}
}
return bean
;
}
}
至此,一个只支持【前置、后置】通知功能的简单的AOP就实现了。
功能测试
1、编写Controller、Service。
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService
;
@GetMapping("get")
public User
getById(Long userId
) {
return userService
.getById(userId
);
}
}
@Service
public class UserServiceImpl implements UserService {
@Override
public User
getById(Long userId
) {
ThreadUtil
.sleep(ThreadLocalRandom
.current().nextInt(1000));
return new User(userId
, "张三", 18);
}
}
2、编写切面类。
@Aop
@Component
public class LogInterceptor {
protected Logger log
= LoggerFactory
.getLogger(LogInterceptor
.class);
private ThreadLocal
<Long> execTime
= new ThreadLocal<>();
@MyPointcut
private final String pointcut
= "top.javap.service.UserServiceImpl@getById";
@Before
public void before(Class
<?> clazz
, Method method
, Map
<String,Object> args
) {
log
.info("{}()开始执行,参数:{}",
clazz
.getName() + "@" + method
.getName(),
JSONObject
.toJSONString(args
));
execTime
.set(System
.currentTimeMillis());
}
@After
public Object
after(Class
<?> clazz
, Method method
, Object result
) {
long time
= System
.currentTimeMillis() - execTime
.get();
log
.info("{}()执行结束,结果:{},耗时:{}ms",
clazz
.getName() + "@" + method
.getName(),
JSONObject
.toJSONString(result
), time
);
return result
;
}
}
3、请求接口,测试日志AOP是否正常工作。
尾巴
一句话总结:AOP就是为需要增强的类生成代理对象,在代理对象中可以选择性的去触发原生方法,以及在原方法的前、后、环绕、异常的地方执行特定的通知方法。