前言
文章内容输出来源:拉勾教育Java高薪训练营。P7课程
本篇文章是学习课程中的一部分课后笔记。
一、作业
自定义@Service、@Autowired、@Transactional注解类, 完成基于注解的IOC容器(Bean对象创建及依赖注入维护)和声明式事务控制,写到转账工程中,并且可以实现转账成功和转账异常时事务回滚。
注意考虑以下情况:
1.注解有无value属性值【@service(value="") @Repository(value="")】
2.service层是否实现接口的情况【如果实现接口则用jdk代理,如果没有实现接口则用cglib代理】
这里因为自定义的功能很简单,在自定义里面service注解和repository注解要起到的作用一个样,所有这里我就只建了一个service注解,统一都用service注解去做处理。
二、做题思路,实现步骤。
1.扫描包下所有的@service,通过反射完成对象实例化
2.根据@Autowired完成注解的依赖关系
3.针对@Transactional,修改对象为对应的代理对象
4.对外提供获取实例对象的接口(根据id获取)
三、代码
1.首先对注解类进行开发
package com.lagou.edu.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 聂建明
*/
/*
关键字@interface,在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
元注解(专门修饰注解的注解):
@Target注解,用来限定某个自定义注解能够被应用在哪些Java元素上面的。
@Retention注解,用来修饰自定义注解的生命周期,使用了RetentionPolicy枚举类型定义了三个阶段
注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。
*/
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
/*
注解类型元素:
1.访问修饰符必须为public,不写默认为public;
2.只能是基本数据类型(不包含对应的封装类型)、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及这些类型的数组类型;
3.该元素的名称一般定义为名词,如果注解中只有一个元素,尽量把名字起为value(后面使用会带来便利操作);
4.()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
5.default代表默认值,值必须和定义的类型一致;
6.如果没有默认值,代表后续使用注解时必须给该类型元素赋值;
注解类型元素的语法非常奇怪,即有属性的特征(可以赋值),又有方法的特征(打上了一对括号),
但是这么设计是有道理的,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。
*/
String value() default "";
}
2.编写初始化容器功能
package com.lagou.edu.factory;
import com.alibaba.druid.util.StringUtils;
import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.annotation.Transactional;
import org.reflections.Reflections;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* @author 聂建明
* <p>
* 工厂类,生产对象(使用反射技术)
*/
public class BeanFactory {
/**
* 1.扫描包下所有的@service,通过反射完成对象实例化
* 2.根据@Autowired完成注解的依赖关系
* 3.针对@Transactional,修改对象为对应的代理对象
* 4.对外提供获取实例对象的接口(根据id获取)
*/
private static Map<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); // 存储对象
//声明静态方法块,一开始就加载
//扫描包通过反射技术实例化对象并且存储待用(map集合)
static {
try {
//扫描包获取反射对象集合
/*
Reflections通过扫描classpath,索引元数据,允许在运行时查询这些元数据,也可以保存收集项目中多个模块的元数据信息。
使用 Reflections 可以查询以下元数据信息:
1.获取某个类型的全部子类;
2.只要类型、构造器、方法,字段上带有特定注解,便能获取带有这个注解的全部信息(类型、构造器、方法,字段);
3.获取所有能匹配某个正则表达式的资源;
4.获取所有带有特定签名的方法,包括参数,参数注解,返回类型;
5.获取所有方法的名字;
6.获取代码里所有字段、方法名、构造器的使用;
*/
Reflections reflections = new Reflections("com.lagou.edu");
//获取带有Service注解类型的类
Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(Service.class);
for (Class<?> resourceAsStream : typesAnnotatedWith) {
//通过反射技术实例化对象
Object beanObejct = resourceAsStream.getDeclaredConstructor().newInstance();
//获取注解信息
Service annotation = resourceAsStream.getAnnotation(Service.class);
//该实例service注解有用value时用value的值,没有时用类名
if (StringUtils.isEmpty(annotation.value())) {
//由于getName获取的是全限定类名,所以要分割去掉前面包名部分
Class<?>[] interfaces = resourceAsStream.getInterfaces();
for (Class<?> interfaceClass : interfaces) {
String[] names = interfaceClass.getName().split("\\.");
map.put(names[names.length - 1], beanObejct);
}
} else {
map.put(annotation.value(), beanObejct);
}
}
//实例化对象后,开始维护对象的依赖关系Autowired,检查哪些对象需要传值进入
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object bean = entry.getValue(); //获取bean
Class<?> beanClass = bean.getClass(); //获取对应的class
//获取所有变量
Field[] declaredFields = beanClass.getDeclaredFields();
//遍历变量,如果持有Autowired注解则注入
for (Field field : declaredFields) {
//判断这个变量是否使用了注解
if (field.isAnnotationPresent(Autowired.class)) {
//获取变量的类型名(这里基本等于获取类名)
String[] names = field.getType().getName().split("\\.");
String name = names[names.length - 1];
//用到Autowired地方得有set方法,方便beanClass.getDeclaredMethods()获取
Method[] declaredMethods = beanClass.getDeclaredMethods();
for (int i = 0; i < declaredMethods.length; i++) {
Method declaredMethod = declaredMethods[i];
if (declaredMethod.getName().equalsIgnoreCase("set" + name)) {
declaredMethod.invoke(bean, map.get(name));
}
}
}
}
}
//维护事务,进行aop切面增强
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object bean = entry.getValue(); //获取bean
Class<?> beanClass = bean.getClass(); //获取对应的class
//判断对象类是否有Transactional注解,如果有则修改对象为代理对象
if (beanClass.isAnnotationPresent(Transactional.class)) {
//获取代理工厂
ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
Class<?>[] interfaces = beanClass.getInterfaces(); //获取bean实现的所有接口
//判断对象是否实现接口
if (null == interfaces || interfaces.length > 0) {
//没实现使用CGLIB代理
bean = proxyFactory.getCglibProxy(bean);
} else {
//实现使用JDK代理
bean = proxyFactory.getJdkProxy(bean);
}
//把处理好的bean重新放入map中
map.put(entry.getKey(), bean);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 对外提供获取实例对象的接口(根据id获取)
public static Object getBean(String id) {
return map.get(id);
}
}
三、解说视频和完整的代码地址
代码地址(里面有视频地址):spring自定义注解实现IOC容器
四、IOC/AOP介绍与源码刨析
笔记地址:IOC介绍与源码剖析
笔记地址:AOP介绍与源码剖析