SpringAOP使得代码关注点分离,不同的问题交给不同的部分去解决。将业务功能代码与切面代码分开,架构也变得高内聚,低耦合。
一.AOP的三种织入方式
- 编译时织入:需要特殊的Java编译器,如AspectJ
- 类加载时织入:需要特殊的Java编译器,如AspectJ和AspectWerkz
- 运行时织入:Spring采用的方式,通过动态代理方式,实现简单
二.理解三个W
- 理解如下代码
@Aspect
@Component
public class RequestLogAspect {
private ThreadLocal<Long> startTime = new ThreadLocal<>();
private static final Logger logger =
LoggerFactory.getLogger(RequestLogAspect.class);
@Pointcut("execution(public * com.whz.framework.web..*.*(..))")
public void webLog(){}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint){
startTime.set(System.currentTimeMillis());
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
logger.info("请求URL:" + request.getRequestURL().toString());
logger.info("请求HTTP_METHOD:" + request.getMethod());
logger.info("请求IP:" + request.getRemoteAddr());
logger.info("请求CLASS_METHOD:" + joinPoint.getSignature().getDeclaringTypeName()
+"."+joinPoint.getSignature());
logger.info("请求参数值:"+ Arrays.toString(joinPoint.getArgs()));
}
public void doAfterReturning(Object ret){
logger.info("响应RESPONSE:"+ret);
logger.info("响应时间SPEND TIME" + (System.currentTimeMillis()
- startTime.get()));
}
}
-
以上代码实现的功能是,当访问
com.whz.framework.web
下的controller
时,打印日志。试想,假如没有AOP进行统一配置,而是将该代码复制到每个方法中,那么方法将会变得庞大而冗余,不便于维护。 -
三个W,即what,where和when。
-
what既是切面要处理的内容,使用注解
@Aspect
标注 -
where既是想要增强的方法,可以是切点,使用
@Pointcut
标注 -
when既是指想要在方法执行前、后或环绕进行织入。使用
@Before
@After
@Around
@AfterReturning
等注解进行标注。
三.AOP主要名称概念
- Aspect:通用功能代码实现
- Target:被织入Aspect的对象
- Join Point:可以作为切入点的机会,所有方法都可以作为切入点
- PointCut:Aspect实际被应用在Join Point,支持正则
- Advice:类里的方法以及这个方法如何织入到目标方法的方式
- Weaving:AOP的实现过程
四.AOP实现
- 观察
DefaultAopProxyFactory
默认为AopProxyFactory
的实现,创建CGLIB
代理或JDK
动态代理。
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass()
|| hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
- 默认策略如果目标类是接口,则用JDKProxy来实现,否则用后者来实现
- Cglib以继承的方式动态生成目标类的代理,借助ASM实现。JDKProxy通过Java内部反射机制实现。
- 反射机制在生成类的过程中比较高效。ASM在生成类之后的执行过程中比较高效。若类使用final修饰,则无法进行Cglib代理。
- JDKProxy实现动态代理可以参考动态代理模式。以下代码为实际代理实现过程。
// 每个代理实例都有一个关联的调用处理程序。
// 在代理实例上调用方法时
//该方法调用被编码并分派到{@code invoke}其调用处理程序的方法。
public class OrderServiceDynamicProxy
implements InvocationHandler {
private Object target;
public OrderServiceDynamicProxy(Object target) {
this.target = target;
}
public Object bind(){
Class targetClass = target.getClass();
return Proxy.newProxyInstance(targetClass.getClassLoader()
,targetClass.getInterfaces(),this);
}
/*
* @Description //TODO 处理代理实例上的方法调用并返回
* @Date 2019/4/4
* @Param [proxy 调用该方法的代理实例
* method 在代理实例上调用的接口方法
* args 包含值的对象数组
* ]
* @return java.lang.Object 从方法调用返回的值
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object argObject = args[0];
beforeMethod(argObject);
// target 从中调用基础方法的对象 是OrderServiceImpl对象
// args 用于方法调用的参数 是Order对象
// return 调度表示的方法的结果
Object object = method.invoke(target, args);
afterMethod();
return object;
}
private void beforeMethod(Object object){
int userId = 0;
System.out.println("动态代理 before code ");
if(object instanceof Order){
Order order = (Order) object;
userId = order.getUserId();
}
int dbRouter = userId % 2;
System.out.println("动态代理分配到【db"+dbRouter+"】处理数据");
//todo 设置dataSource
DataSourceContextHolder.setDBType("db"+String.valueOf(dbRouter));
}
private void afterMethod(){
System.out.println("动态代理 after code");
}
}
public static void main(String[] args) {
Order order = new Order();
order.setUserId(1);
//创建代理类,并注入目标类
//注意这里需要将被代理对象注入代理类,并对代理类进行强制类型转换
IOrderService orderServiceDynamicProxy =
(IOrderService) new OrderServiceDynamicProxy(new OrderServiceImpl()).bind();
orderServiceDynamicProxy.saveOrder(order);
}
五.Cglib实现
- Cglib针对类来代理实现,它的原理是对制定目标类生成一个子类,并覆盖其中方法实现增强。使用cglib实现动态代理,完全不受代理类必须实现接口的限制,而且Cglib底层采用了ASM字节码生成框架,使用字节码。
- 需要引入两个jar包:
cglib.jar
asm.jar
,若是已有Sping环境,则无需另外引入jar包。 - 请看以下实例
public class UserDaoImpl {
public void save(){
System.out.println("已保存...");
}
}
public class CglibProxyFactory {
private Object obj;
public CglibProxyFactory(Object obj) {
super();
this.obj = obj;
}
public Object getProxyFactory(){
//生成动态子类以启用方法拦截
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
//钩子回调用户定义的拦截器
enhancer.setCallback(new MethodInterceptor() {
//通过实现MethodInterceptor实现方法回调
@Override
public Object intercept(Object o, Method method, Object[] objects
, MethodProxy methodProxy) throws Throwable {
System.out.println("代理开始...");
method.invoke(obj,objects);
System.out.println("代理结束...");
return o;
}
});
//生成目标对象并返回
return enhancer.create();
}
}
public class TestCglibProxy {
public static void main(String[] args) {
UserDaoImpl userDao = new UserDaoImpl();
UserDaoImpl userDaoProxy = (UserDaoImpl) new CglibProxyFactory(userDao).getProxyFactory();
System.out.println("目标对象类型:" +userDao.getClass());
System.out.println("代理对象类型:" + userDaoProxy.getClass());
}
}