1.@WebServlet("/")不会拦截指向jsp的转发,而@WebServlet("/*")会拦截指向jsp的转发。拦截之后不断重复执行@WebServlet("/")里面的内容,不断拦截里面的jsp,导致异常
反射依赖于Class和
java.lang.reflect包
- Field:表示类中的成员变量
- Method:类中的方法
- Constructor:表示类的构造方法
- Array:提供动态创建数组和访问数组元素的静态方法
Class用来表示运行时类型信息的对应类
- 每个类都有一个唯一与之对应的Class对象
- Class类为类类型
- 是类的一种
- 只有一个私有的构造函数,只有JVM能创建Class类的实例
获取Class对象的方式
- getClass() 方法
- Class.forName(全路径) 方法
- .class
反射用法
-
获取类的构造方法并使用(返回类型Constructor)
-
获取所有共有的构造方法:getConstructors()
-
获取所有的构造方法(公有、私有、受保护、默认):getDeclaredConstructors()
-
获取单个带参数的公有构造方法:getConstructor(String.class,int.class)
-
获取某个任意修饰符的构造方法(公有、私有、受保护、默认):getDeclaredConstructor(String.class)
-
创建实例:
Constructor con = clazz.getDeclaredConstructors(String.class); RelectTarget refl = con.newInstance();
-
创建私有构造方法的实例:
Constructor con = clazz.getDeclaredConstructors(String.class); con.setAccessible(true); RelectTarget refl = con.newInstance();
-
-
-
获取成员变量
-
取所有共有的字段:getFields()
-
获取所有的字段(公有、私有、受保护、默认):getDeclaredFields()
-
获取某个公有字段:getField(String fieldName)
-
获取某个任意修饰符的字段(公有、私有、受保护、默认):getDeclaredField(String fieldName)
-
设置值:
要给字段赋值需要两步: 1.获取字段: Class reflectTar = Class.forname("demo.reflect.ReflectTarget"); //获取某个特定值 Field f = reflectTar.getDeclaredFields("name"); //获取对象实例 Reflact ref = reflectTar.getConstructor().newInstance(); //设置值 f.set(ref,"Tom");
若成员变量为私有:
1.获取字段: Class reflectTar = Class.forname("demo.reflect.ReflectTarget"); //获取某个特定值 Field f = reflectTar.getDeclaredFields("name"); //获取对象实例 Reflact ref = reflectTar.getConstructor().newInstance(); //设置为可访问 f.setAccessible(true); //设置值 f.set(ref,"Tom");
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ybeepKvB-1599660561967)(D:\JavaProject\SpringFramework\1598010924434.png)]
-
-
-
获取成员方法
-
取所有共有的字段:getMethods()
-
获取所有的字段(公有、私有、受保护、默认):getDeclaredMethods()
-
获取某个公有字段:getMethod(String name,Class<?>…parameterTypes)
-
获取某个任意修饰符的字段(公有、私有、受保护、默认):getDeclaredMethod(String name,Class<?>…parameterTypes)
- 方法执行:
Class reflectTar = Class.forname("demo.reflect.ReflectTarget"); //获取单个公有方法(方法名,参数类类型) Method m = reflectTar.getMethod("show1",String.class); //执行方法要求实例对象 Reflact ref = reflectTat.getConstructor().newInstance(); //执行方法 invoke(实例对象,参数值) m.invoke(ref,"good");
-
元注解:修饰注解的注解
-
@Target:描述所修饰的注解的适用范围(类、方法、变量)
-
@Rentention:标注注解被保留时间的长短(注解的生命周期)
-
@Documented:注解是否应当被包含在JavaDoc文档中
-
@Inherited:是否允许子类继承该注解
自研究框架实现IOC步骤
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EKlacK5l-1599660561969)(D:\JavaProject\SpringFramework\1598065808301.png)]
IOC思想
Spring核心思想就是IOC控制翻转,IOC可以让我们从耦合的创建对象方式中解脱出来,通过依赖注入的方式建立对象之间的联系。在IOC思想出现之前(当然IOC并不是Spring提出来的,Spring只是实现了基于IOC思想的IOC容器,但这个也已经很了不起了)。传统的设计思路是上层建筑依赖于下层建筑,就好比说我要造一个行李箱,按照传统思想,我就需要先造轮子,根据轮子的尺寸造底盘,根据底盘造行李箱。但是这样有一个问题就是,如果我修改了轮子的尺寸,那么轮子上层所有的建筑都需要进行修改。在代码中也是一样,上层的类中需要自己去new一个轮子,并且传入轮子的尺寸参数,当参数改变时,整个代码都得改,不利于维护。但是如果我们按照IOC思想,通过依赖注入的方式来进行设计,就不存在这样的问题。依赖注入的原理和之前传统方式截然相反,依赖注入是一种自上而下的设计理念,我上层 需要什么,那么你下层传入就行了。控制权由下层转移到了上层。而IOC容器在这种思想的基础上进行完善,他讲控制权掌握在自己手里,他负责托管所有的上下层的类,当上层需要下层的组件的时候,IOC容器就在自己托管的类中创建对象传递给上层,减少了代码的耦合,维护性很高。
创建注解
Spring中实现IOC的方法主要是xml配置文件和注解,此处选择注解的方式
既然要通过注解的方式,那么就需要自定义注解。因此创建
- @Service 标记Service层的类
- @Controller 标记Controller层的类
- @Repository 标记Repository层的类
- @Conponent 被标记的类为通用的被Spring管理的类,可以代表上面三个标签。上前三个标签是他的拓展,有自己特定的功能
提取注解标记对象
思路:通过遍历的方式,获得被标记的对象
而遍历则需要确定遍历的范围,而范围则是由使用者决定,因此可以通过使用者传入的包路径来确定范围。
1.获取范围里所有的类
可以通过一个工具类中的方法得到所有类的对象。
例如:用户输入的是“com.imooc.service”,我们需要查找包下所有的类。
需要注意的是,光通过包名是找不到项目所在的位置的。我们需要的是绝对路径。但是对于用户来说传入绝对地址是很不友好的,因为不同机器之间路径可能不同。因此最好的办法是通过类加载器来获取。因此步骤如下:
-
获取类加载器;
-
获取到类加载器之后,通过类加载器中的方法getResource,传入包路径获取到相应的URL绝对定位符;
-
得到定位符之后进行判断,因为我们需要的是file类型的文件,因此调用url.getProtocol()方法判断是否为file类型
-
确定为file类型之后将用户输入的“com.imooc.service”转换为“com\imooc\service”,因为URL获取绝对路径方法getPath()要求分隔符为 “” ;
-
因为得到的绝对路径下可能还存在文件夹,我们要获取其中所有的Class,只能通过递归遍历所有文件夹。
-
创建一个用于递归的方法
extractClassFile(classSet,packageDirectory,packageName),参数为存放Class结果的classSet、文件packageDirectory、要找的包名packageName
-
进入方法,先写出递归结束的条件即packageDirectory不为文件。
-
列出packageDirectory所有的文件,使用过滤器进行判断:
-
-
如果为文件夹则先存入数组,用于待会儿的递归;
-
如果是.Class文件的话则进入获取Class对象的方法中。方法通过截取字符串得到Class的路径,再使用Class.forName()方法得到Class对象,存入classSet中。
到此,获取所有Class对象过程结束。
3.创建容器
创建容器思路:
- 首先容器只有一个,需要使用单例模式创建;
通过饿汉单例模式得到对象。但是普通的单例模式是不安全的,可以通过反射机制创建新的对象,因此可以通过枚举类型类进行保护:
public class EnumStarvingSigleton {
private EnumStarvingSigleton(){}
public static EnumStarvingSigleton getInstance(){
return ContainerHolder.HOLDER.instance;
}
private enum ContainerHolder{
HOLDER;
private EnumStarvingSigleton instance;
ContainerHolder(){
instance = new EnumStarvingSigleton();
}
}
}
- 容器的目的是管理bean,因此需要一个Map<Class,Object>存储管理的bean和他的类;
private final Map<Class,Object> beanMap = new ConcurrentHashMap<Class, Object>();
- 容器要对bean进行管理,需要通过遍历的方式获得bean,而遍历方式使用上述方法;
//扫描加载所有bean
public synchronized void loadBeans(String packageName){
if (isLoaded()){
return;
}
Set<Class> classSet = ClassUtil.extractPackageClass(packageName);
if (classSet == null || classSet.isEmpty()){
return;
}
for (Class clazz : classSet) {
for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
//如果类上标记了注解
if (clazz.isAnnotationPresent(annotation)){
beanMap.put(clazz,ClassUtil.newInstance(clazz,true));
}
}
}
loaded = true;
}
- 容器要对bean进行筛选,查找到使用注解标注的bean,因此需要引入存储有所有注解的集合
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION
= Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);
- 判断容器是否被加载过
private boolean loaded = false;
public boolean isLoaded(){
return loaded;
}
4.提供容器对外操作的方法
对bean的增删改查,没啥好说的
5.实现容器的依赖注入
到这里为止,容器虽然可以接管bean实例,但其仍是不完备的,因为此时并没有进行依赖注入,其成员变量的值为null
实现依赖注入的思路:
-
定义相关注解标签
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoWired { String value() default ""; }
-
实现创建被注解标记的成员变量实例,并将其注入到成员变量中
- 扫描容器中所有的Class
- 扫描每个Class下的成员变量
- 找出被注解AutoWired标记的成员变量
- 获取成员变量的类型
- 通过成员变量的类型获取其在容器中的实例
- 因为成员变量的类型有可能是接口类型,因此还需要进行判断:
- 若不是接口,说明存在实现类,直接返回
- 若为接口,则要获取其实现类
- 因为接口可能有多个实现类,因此我们需要选出正确的那一个。可以通过在注解中设置用户修改的Value变量,通过读取来判断;
- 因为成员变量的类型有可能是接口类型,因此还需要进行判断:
- 将实例赋给成员变量
-
依赖注入的使用
自研框架实现AOP
1.知识储备
Advice种类
2. 正式开发
1. 解决标记问题
使用AOP需要先准备好两个注解:
- @Aspect:定义切面,切面里包含了各种横切逻辑;
- @Order:用于多个Aspect切入时定义执行的顺序,Spring中按照Order的升序执行Aspect;
2.定义横切逻辑模板
在Spring中,横切逻辑Advice使用注解开发,但是本框架中为了简单起见,定义了横切模板类。类中给定了三种类型的Advice,对应不同场景:
- before
- afterReturning
- afterThrowing
public abstract class DefaultAspect {
/**
* 定义BeforeAdvice,设置为钩子函数。当需要设置beforeAdvice时实现即可
* @param targetClass
* @param method
* @param args
* @return void
* @creed: Talk is cheap,show me the code
* @date 2020/8/24 14:56
*/
public void before(Class targetClass, Method method,Object[] args)throws Throwable{}
/**
* 定义AfterAdvice,也设置为钩子函数。当需要设置时实现即可
* @param targetClass
* @param method
* @param args
* @return void
* @creed: Talk is cheap,show me the code
* @date 2020/8/24 14:56
*/
public Object afterReturning(Class targetClass, Method method,Object[] args,Object returnValue)throws Throwable{
return returnValue;
}
/**
* 定义afterThrowing,设置为钩子函数。当需要设置时实现即可
* @param targetClass
* @param method
* @param args
* @return void
* @creed: Talk is cheap,show me the code
* @date 2020/8/24 14:56
*/
public void afterThrowing(Class targetClass, Method method,Object[] args,Throwable e)throws Throwable{
}
}
当需要使用哪种横切逻辑时,继承该类的子类重写即可。没有进行重写的钩子函数不会影响程序的执行。
3.对执行多个Aspect进行管理
通过模拟CGLIB动态代理来对逻辑进行切入。在切入之前需要解决的问题是:
如何解决多个Aspect执行顺序的问题?
在知识储备里,我们知道对于多个Aspect,AOP通过Order使其顺序执行。而我们如果普通的使用CGLIB实现类来完成,那么Method会被执行多次,且多个Aspect之间的横切逻辑执行顺序将不复存在。此时我们就需要通过排序队列来确定执行的顺序。
即:按照队列中Aspect的Order顺序排好序,再依次读取Aspect,顺序执行before,再只执行依次Method,之后逆序执行after
而List需要依赖各个Aspect的信息,因此创建类AspectInfo,立面存储Order以及Aspect,对其进行排序。
@AllArgsConstructor
@Getter
public class AspectInfo {
private int orderIndex;
private DefaultAspect aspectObject;
public int getOrderIndex() {
return orderIndex;
}
public DefaultAspect getAspectObject() {
return aspectObject;
}
}
Aspect横切逻辑管理类的代码如下:
/**
* 实现Aspect横切逻辑
* AspectListExecutor用来管理各个Aspect。其中需要解决的问题:
* 1.实现Spring中多个Aspect中的Advice之间的执行顺序
* 如:执行Aspect1.beforeAdvice --> Aspect2.beforeAdvice --> Aspect3.beforeAdvice
* Aspect1.afterAdvice <-- Aspect2.afterAdvice <-- Aspect3.afterAdvice
* 我们可以通过有序队列完成
*
* @creed: Talk is cheap,show me the code
* @date 2020/8/24 15:07
*/
public class AspectListExecutor implements MethodInterceptor {
//通过有序队列来实现按顺序读取Aspect
//排序的依据是每个Aspect的信息,因此创建AspectInfo类进行储存
private List<AspectInfo> sortedAspectInfoList;
//被代理的类
private Class<?> targetClass;
public List<AspectInfo> getAspectInfoList() {
return sortedAspectInfoList;
}
//在构造函数中完成List的排序
public AspectListExecutor(List<AspectInfo> aspectInfoList, Class<?> targetClass) {
this.sortedAspectInfoList = sortAspectInfoList(aspectInfoList);
this.targetClass = targetClass;
}
private List<AspectInfo> sortAspectInfoList(List<AspectInfo> aspectInfoList){
Collections.sort(aspectInfoList, new Comparator<AspectInfo>() {
public int compare(AspectInfo o1, AspectInfo o2) {
return o1.getOrderIndex() - o2.getOrderIndex();
}
});
return aspectInfoList;
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
//进行判空
if (sortedAspectInfoList == null || sortedAspectInfoList.size() == 0){
return null;
}
//1.按照升序执行beforeAdvice
invokeBeforeAdvices(method,args);
try{
methodProxy.invokeSuper(proxy,args);
//2.按照降序执行afterReturningAdvice
result = invokeAferReturningAdvice(method,args,result);
}catch (Exception e){
//3.如果代理方法抛出异常,执行afterThrowingAdvice
invokeAfterThrowingAdvice(method,args,e);
}
return result;
}
private void invokeAfterThrowingAdvice(Method method, Object[] args,Throwable e) throws Throwable {
for (int i = sortedAspectInfoList.size()-1;i >= 0;i--){
sortedAspectInfoList.get(i).getAspectObject().afterThrowing(targetClass,method,args,e);
}
}
private Object invokeAferReturningAdvice(Method method, Object[] args, Object returnValue) throws Throwable {
Object result = null;
for (int i = sortedAspectInfoList.size()-1;i >= 0;i--){
result = sortedAspectInfoList.get(i).getAspectObject().afterReturning(targetClass,method,args,returnValue);
}
return result;
}
private void invokeBeforeAdvices(Method method, Object[] args) throws Throwable {
for (AspectInfo aspectInfo : sortedAspectInfoList) {
aspectInfo.getAspectObject().before(targetClass,method,args);
}
}
}
创建动态代理对象
public class ProxyCreator {
/**
* 创建动态代理对象并返回
* @param targetClass 被代理的Class对象
* @param methodInterceptor 方法拦截器
* @return void
* @creed: Talk is cheap,show me the code
* @date 2020/8/24 16:31
*/
public static Object creatProxy(Class targetClass, MethodInterceptor methodInterceptor){
return Enhancer.create(targetClass,methodInterceptor);
}
}
4.将带有织入逻辑的代理放入容器中
本步骤是AOP的关键步骤。在之前的步骤里,我们完成了基础设施的建设:创建注解和Aspect,织入Aspect的方法(解决多个Aspect织入的顺序问题),代理类的创建。
到这一步,我们就需要将之前的步骤整合起来,完成对于整个容器的AOP操作:即织入有织入逻辑的代理类替换掉容器中所有注解标记的AOP对象。具体步骤如下:
- 获取容器中所有被@Aspect标记的对象;
//1.获取容器中所有的Aspect
Set<Class<?>> aspectSet = beanContainer.getClassByAnnotation(Aspect.class);
- 根据@Aspect修饰的类对Aspect进行分类
HashMap<Class<? extends Annotation>, List<AspectInfo>> categorizedMap = new HashMap<Class<? extends Annotation>, List<AspectInfo>>();
if (aspectSet == null || aspectSet.size() == 0){return;}
for (Class<?> aspectClass : aspectSet) {
//对Aspect合法性进行验证
if (verifyAspect(aspectClass)){
//对Aspect按照Class进行分类
categorizedAspect(categorizedMap,aspectClass);
}else {
throw new RuntimeException("...");
}
}
其中涉及到两个具体点:
- Aspect合法性验证:要求验证Aspect是否带有@Order、@Aspect标签以及是否继承自Aspect父类DefaultAspect、@Aspect修饰的是否是@Aspect本身:
private boolean verifyAspect(Class<?> aspectClass) {
return aspectClass.isAnnotationPresent(Aspect.class)&&
aspectClass.isAnnotationPresent(Order.class)&&
DefaultAspect.class.isAssignableFrom(aspectClass)&&
aspectClass.getAnnotation(Aspect.class).value()!= Aspect.class;
}
- Aspect分类: 将Aspect按照其修饰的类进行分组,便于后续创建代理类使用
private void categorizedAspect(HashMap<Class<? extends Annotation>, List<AspectInfo>> categorizedMap, Class<?> aspectClass) {
Order orderTag = aspectClass.getAnnotation(Order.class);
Aspect aspectTag = aspectClass.getAnnotation(Aspect.class);
DefaultAspect defaultAspect = (DefaultAspect) beanContainer.getBean(aspectClass);
AspectInfo aspectInfo = new AspectInfo(orderTag.value(), defaultAspect);
//如果植入的joinpoint第一次出现,则创建对应的list
if (!categorizedMap.containsKey(aspectTag.value())){
ArrayList<AspectInfo> aspectInfoList= new ArrayList<AspectInfo>();
aspectInfoList.add(aspectInfo);
categorizedMap.put(aspectTag.value(),aspectInfoList);
}else {
//如果不是第一次出现,那么直接添加即可
List<AspectInfo> aspectInfoList = categorizedMap.get(aspectTag.value());
aspectInfoList.add(aspectInfo);
}
- 创建含有横切逻辑的代理类将容器中原本的类全部代理
if (categorizedMap == null || categorizedMap.size() == 0){return;}
for (Class<? extends Annotation> category : categorizedMap.keySet()) {
weaveByCategory(category,categorizedMap.get(category));
}
private void weaveByCategory(Class<? extends Annotation> category, List<AspectInfo> aspectInfos) {
Set<Class<?>> classSet = beanContainer.getClassByAnnotation(category);
if (classSet.size() == 0 || classSet == null){return;}
for (Class<?> targetClass : classSet) {
AspectListExecutor aspectListExecutor = new AspectListExecutor(aspectInfos, targetClass);
Object proxyBean = ProxyCreator.creatProxy(targetClass, aspectListExecutor);
beanContainer.addBean(targetClass,proxyBean);
}
}