不改老代码
日常 POJO 只增不改没问题的。字段命名不规范也不要改。
方法只有一处调用,或者是你开发的历史版本,改掉问题不大,多测测。
但其他情况,不要改老代码,考虑在代码上做扩展,原因是:
1. 看不惯想整合?改错了就是事故
2. 改东西要花时间的,开发要时间,测试要时间,验收也要时间
3. 底层框架里的类你改不了
类扩展的方式
Java8接口默认方法
Java API 都告诉你不要改老代码。他创建接口默认方法来实现老接口兼容。
public interface Collection<E> extends Iterable<E> {
// 接口默认方法
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
}
Collcetion是一个顶层接口,在Java8 加入一个 stream(),用于创建迭代流,所有实现类都可以用。
但假设没有接口默认方法,我们有个老代码,创建了一个 BizCollection implements Collection,代码升级的Java8就运行不起来了,必须实现 接口的 stream()。而且功能是 获取一个 Stream,这是通用的,不该在实现类里实现。也不能把接口改成抽象类,因为抽象类是一个类,单继承。
代理模式
看一个代理模式的典型实现,Spring的核心概念 AOP(Aspect Oriented Programming 面向切面编程)
Aspect Oriented Programming with Spring :: Spring Framework
以类为单位组织代码 是最常用的方式,但面向切面 提出,以切面 Aspect 为单位组织代码,实现对前者的扩展。
Spring AOP 使用方式
1. @Aspect 声明为切面类,可以重写 多个类的 方法
2. @Pointcut 声明 目标方法,即要重写哪个类的方法
3. @Around /@Before/@After 扩展方法执行时间。在目标方法执行之前之后都执行 / 先于目标方法实现 / 后于目标方法执行
@Before/@After,入参 JoinPoint jp,可以获取到目标方法声明和参数
如果是 @Around,入参 ProceedingJoinPoint pjp,含方法 pjp.proceed(); 用于调用 目标方法,获取调用结果。来实现目标方法调用时间的灵活性,在目标方法调用前后都添加扩展代码
AOP在 Spring frameWork中的应用
1. 事件(Events)
2. 事务(Transactions)
3. 缓存抽象(Caching Abstract)
4. 本地调度(Scheduling)
5. @RestControllerAdvice
看实现-AOP使用
spring-tx-5.3.22.jar
// org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
/**
* General delegate for around-advice-based subclasses, delegating to several other template
* methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager}
* as well as regular {@link PlatformTransactionManager} implementations and
* {@link ReactiveTransactionManager} implementations for reactive return types.
* @param method the Method being invoked 目标方法
* @param targetClass the target class that we're invoking the method on 目标类
* @param invocation the callback to use for proceeding with the target invocation 目标调用
* @return the return value of the method, if any 目标调用结果
* @throws Throwable propagated from the target invocation
*/
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 创建事务
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 反射获取目标对象结果
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception 异常抛出
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
// 提交事务
commitTransactionAfterReturning(txInfo);
}
AOP实现
从使用上得知,AOP的功能是 扩展 目标方法,实现方式是 重写 目标方法。
什么方法才能重写呢
1. 语法支持 父子类 结构
2. 子类实现父类同名方法
3. 可通过父类访问 子类实现的 父类同名方法
Java 重写的实现方式 是 1. 父子类 重写方法 2. 接口实现类 实现目标方法
那AOP实现的是对一个普通类的目标方法重写,在不改变普通类下,用哪种方式重写呢,只有一个方法,给这个 普通类 创建一个 子类,使用反射。
看实现- AOP结构
spring-aop-5.3.22.jar
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 开启AOP 代理
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
}
/**
* Registers an {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
* AnnotationAwareAspectJAutoProxyCreator} against the current {@link BeanDefinitionRegistry}
* as appropriate based on a given @{@link EnableAspectJAutoProxy} annotation.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
* @see EnableAspectJAutoProxy
*/
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
//
}
// doCreateBean 创建Bean -> initializeBean 初始化Bean -> 生成代理Bean
// wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
// org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 真正生成代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
/**
* Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
* @param bean the raw bean instance
* @param beanName the name of the bean
* @param cacheKey the cache key for metadata access
* @return a proxy wrapping the bean, or the raw bean instance as-is
*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 这个Bean的方法是否被声明在 Ponitcut
// Create proxy if we have advice.
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
if (specificInterceptors != DO_NOT_PROXY) {
// 标识 这个Bean 有切面
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建代理对象
Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
}
// 切面 匹配 from getAdvicesAndAdvisorsForBean
// org.springframework.aop.support.AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class<?>, boolean)
/**
* Can the given pointcut apply at all on the given class?
* <p>This is an important test as it can be used to optimize
* out a pointcut for a class.
* @param pc the static or dynamic pointcut to check
* @param targetClass the class to test
* @param hasIntroductions whether the advisor chain
* for this bean includes any introductions
* @return whether the pointcut can apply on any method
*/
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
// 类匹配
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
// 方法匹配
if (introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
// org.springframework.aop.framework.ProxyCreatorSupport#createAopProxy
/**
* Subclasses should call this to get a new AOP proxy. They should <b>not</b>
* create an AOP proxy with {@code this} as an argument.
*/
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
// 代理工厂生成代理 this=ProxyCreatorSupport
return getAopProxyFactory().createAopProxy(this);
}
// org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
// 代理类是 目标类实现 接口 的另一个实现类
return new JdkDynamicAopProxy(config);
}
// 代理类 是 目标类的 子类
return new ObjenesisCglibAopProxy(config);
}
// org.springframework.aop.framework.JdkDynamicAopProxy#invoke 调用代理方法
/**
* Implementation of {@code InvocationHandler.invoke}.
* <p>Callers will see exactly the exception thrown by the target,
* unless a hook method throws an exception.
*/
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TargetSource targetSource = this.advised.targetSource;
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method. 获取方法拦截链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// We need to create a method invocation...
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
// 调用代理方法
retVal = invocation.proceed();
}