切点匹配原理
代理技术
代理技术中的几个术语
切点PointCut
切面Advisor
织入wear
spring中的底层切面
底层切面
编写一个切面类
JDK动态代理
三个参数的作用
参数作用
invocationHandler
总结下
cglib
切面脑图
spring代理源码
一、代理技术
开闭原则:
在面向对象编程领域中, 规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”。
这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。
在Java语言中可以通过多种方式实现
- 动态代理:代理哪个实现对象不知到(JDK),那个方法不知到(JDK or CGLIB)。代理对象通用性比较强,一个代理对象可以为每个方法都做代理,调用那个方法就增强那个方法
- 静态代理:明确知道要代理那个类、类的那个方法。对比动态代理静态代理会为类的每个方法都生成一个代理对象(和程序本身的设计有关)可能会导致”类爆炸“
- 编译阶段改变字节码(三方编译插件)
- 类加载阶段改变字节码(agent技术)
使用spring框架开发时使用的@Aspect
切面很容易实现代理,原理还是借助JDK动态代理或CGLIB动态代理
二、代理技术中的几个术语
切点PointCut
需要做增强的代码通常指的是某个方法
切面Advisor
切面由两部分组成:通知、切点表达式(能够匹配到切点)。通知也可以看作是一个方法,通知是用来增强切点的代码
织入wear
织入是一段”描述“,描述了通知的生效时机
是在切点调用之前
生效还是切点调用之后
生效或是在切点抛异常
后生效等,本质还是一段代码
三、spring中的底层切面
一个很简单的@Aspect
切面也许需要包括两部分,既切点表达式
和通知
。一个@Aspect
类可以包含多个切点表达式
和多个通知
。
spring框架会将Aspect切面类
转化为多个底层切切面
。一个底层切面类
包含一个切点表达式
和一个通知
。
底层切面
与aspect切面
相比好处是功能单一(单一职责)、粒度更加精细,缺点是不方便开发(一个通知一个类很不方便维护)
编写一个底层切面类
/**
* 使用底层切面创建代理对象
*/
public class DeepAspect {
/**
* 创建切面
*/
private static DefaultPointcutAdvisor creatAdvisor() throws NoSuchMethodException {
// 1、=== 准备切点表达式(这里使用aspect表达式、也可以使用基于注解的AnnotationMatchingPointcut)
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution( * aa.slk.slkIE.spring.aop.Target.init(..))");
// 使用表达式,是否支持给某个方法做增强
MethodMatcher methodMatcher = pointcut.getMethodMatcher();
boolean isSupport = methodMatcher.matches(Target.class.getDeclaredMethod("init"), Target.class);
System.out.println("该切面是否支持这个方法: " + isSupport);
// 2、=== 准备通知
MethodInterceptor interceptor = (methodInvocation) -> {
System.out.println("before...");
Object proceed = methodInvocation.proceed();
System.out.println("after...");
return proceed;
};
// 3、=== 创建切面类
return new DefaultPointcutAdvisor(pointcut, interceptor);
}
/**
* 测试
*/
public static void main(String[] args) throws IOException, NoSuchMethodException {
// 切面
DefaultPointcutAdvisor advisor = creatAdvisor();
// 创建代理对象
Target target = new Target();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target); // 目标对象
proxyFactory.addAdvisor(advisor); // 切面
proxyFactory.setInterfaces(target.getClass().getInterfaces()); // 目标对象的接口(JDK动态代理,不设置使用cglib)
TargetInterface targetProxy = (TargetInterface) proxyFactory.getProxy();
System.out.println(targetProxy.getClass());
targetProxy.init(); // 增强
targetProxy.destroy(); // 没有被增强
}
}
---
四、JDK动态代理
三个参数的作用
三个参数的作用
// newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
TheJdkInterface proxyInstance = (TheJdkInterface) Proxy.newProxyInstance(TheMain.class.getClassLoader(),
new Class[]{TheJdkInterface.class}, new TheAspectAndWeaver());
TheMain.class.getClassLoader()
:类加载器,加载代理对象的字节码到JVMTheJdkInterface
:代理对象也要实现这个接口,主要是弄清代理对象调用的是那个方法(Method method)TheAspectAndWeaver
:这个类可以持有被代理对象的引用,而且包含织入的逻辑。也可以把这个类当成一个持有被代理对象引用的【切面】而且通知类型是环绕通知
参数作用
解释一下
从三个参数可以看出:
- 代理对象有一个成员变量为
invocationHandler
的实现类 - 代理对象实现了接口的方法,方法逻辑大概就是
invocationHandler.invoke()
invoke
方法参数为this(该代理对象)
,当前方法Method对象
,方法参数
;
invocationHandler
invocationHandler实现类
面向接口编程。调用接口方法=调用代理对象方法=调用InvacationHandler.invoke
方法
public class TheAspectAndWeaver implements InvocationHandler {
@Override
public Object c(Object proxy, Method method, Object[] args) throws Throwable {
// 自定义增强逻辑
return method.invoke("被代理对象", args); // 就是反射调用方法嘛
}
}
总结下
实现InvocationHandler
接口的意义就是用户可以自定义增强逻辑。其实对于代理对象来说InvocationHandler.invoke()
就是一个callback
:构建代理对象的时候把InvocationHandler
接口实现类传进去,调用代理对象方法的时候再调用这个callback
,而callback
中有个两个很重要的参数Method
和... 参数
,callback
肯定有反射调用Method
的逻辑。所以说这个callback
是不是类似环绕通知
,InvocationHandler
实现类相当于切面。
五、cglib
cglib动态代理和jdk动态代理类似,cglib代理对象需要组合MethodInterceptor
对象,对象的interceptor
方法实现增强逻辑。因为cglib创建的代理对象和Target
是子父关系,cglib可以避免使用反射的方式调用target
的方法(super.xx)
6、切面脑图
spring代理源码
AnnotationAwareAspectJAutoProxyCreator
类是一个负责为bean
创建代理对象的BeanPostProcessor
,此类有两个非常重要的方法FindEligbleAdvisor
和wrapIfNecessary
。
PointCut
:切点,指定了为某个类某个方法做增强Advisor
:切面(类比Aspect
)包含切点
和通知
findEligibleAdvisor
:寻找支持给当前bean
做增强的切面(迭代切面,判断切面中的切点是否可以作用到bean的某个方法上)wrapIfNecessary
:是否有必要为当前bean
创建代理
Aspect
切面包含多组切点
和通知
,spring会将Aspect
切面转化为Advisor
切面,一个Advisor
切面类仅包含组合切点和通知,粒度更细。
基础的东西
1、解耦合
耦合性(Coupling),是对模块间关联程度的度量
1、耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少
2、模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系
3、模块间联系越多,其耦合性越强,同时表明其独立性越差
2、几种耦合关系
# 内容、公共、外部、控制、标记、数据、非直接 | 耦合
# 如果模块间必须存在耦合,尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合
3、内聚
1、内聚表示一个模块内各个元素彼此结合的紧密程度
2、它是信息隐蔽和局部化概念的自然扩展
3、内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事
4、同其他模块存在高耦合的模块意味着低内聚
5、高内聚的模块意味着该模块同其他模块之间是低耦合
6、在进行软件设计时,应力争做到高内聚,低耦合
spring
1、IOC、DI
// Inverse Of Control
// 注意事项
@Autowired // 类型
@Qualifier // id,通常和Autowired联合使用,可独立使用
1.1、基于xml
<!--可以完全使用xml配置,开发效率底下-->
<!--一般都是混合使用(如不考虑SpringBoot)-->
<!--开启注解扫描: -->
<context:component-scan base-package="com.xx"/>
<!--添加Bean注意事项:-->
<!--工厂静态方法返回值-->
<!--工厂默认方法返回值-->
<!--ref注入已经在容器中的-->
1.2、纯注解配置
@Configuration // 配置类
@ComponentScan(basePackages = "com.xx") // 注解扫描
@Import({ JdbcConfig.class}) // 加载其他配置类(如:连接池)
public class SpringConfiguration {
// @Bean:在方法上面使用,把方法的返回值添加到spring容器
// return dataSource;
}
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建一个数据源,并存入 spring 容器中
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
2、AOP
Aspect Oriented Programming
:面向切面编程
在程序运行期间,不修改源码对已有方法进行增强,无侵入性
代码复用、维护方便,提高效率
2.1、动态代理技术
// 基于接口
// 基于实现类
2.2、增强
// 切面、通知(注解配置:环绕通知的bug)
// 接入点、切入点
// 切入点表达式
// 声明式事务管理:
// 线程conn
// 前置通知:conn.xxx:开启手动提交事务。默认完成
// 后置通知:conn.submit
// 异常通知:conn.rollback
// 最终通知:释放conn
Bean容器
// todo