1.简单的IOC和AOP实现
1.1.简单的IOC
最简单的IOC容器只需4步即可实现,如下:
1.加载xml配置文件,遍历其中的标签
2.获取标签中的id和class属性,加载class属性对应的类,并创建bean
3.遍历标签中的标签,获取属性值,并将属性值填充到bean中
4.将bean注册到bean容器中
代码结构
SimpleIOC // IOC的实现类,实现了上面所说的4个步骤
SimpleIOCTest // IOC的测试类
Car // IOC 测试使用的bean
Wheel // 同上
ioc.xml // bean配置文件
容器实现类SimpleIOC的代码:
public class SimpleIOC {
private Map<String, Object> beanMap = new HashMap<>();
public SimpleIOC(String location) throws Exception {
loadBeans(location);
}
public Object getBean(String name) {
Object bean = beanMap.get(name);
if (bean == null) {
throw new IllegalArgumentException("there is no bean with name " + name);
}
return bean;
}
private void loadBeans(String location) throws Exception {
// 加载xml配置文件
FileInputStream inputStream = new FileInputStream(location);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document doc = docBuilder.parse(inputStream);
Element root = doc.getDocumentElement();
NodeList nodes = root.getChildNodes();
// 遍历<bean>标签
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
Element ele = (Element)node;
String id = ele.getAttribute("id");
String className = ele.getAttribute("class");
// 加载beanClass
Class beanClass = null;
try {
beanClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
// 创建bean
Object bean = beanClass.newInstance();
// 遍历<property>标签
NodeList propertyNodes = ele.getElementsByTagName("property");
for (int j = 0; j < propertyNodes.getLength(); j++) {
Node propertyNode = propertyNodes.item(j);
if (propertyNode instanceof Element) {
Element propertyElement = (Element)propertyNode;
String name = propertyElement.getAttribute("name");
String value = propertyElement.getAttribute("value");
// 利用反射将bean相关字段访问权限设为可访问
Field declaredField = bean.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
if (value != null && value.length() > 0) {
// 将属性值填充到相关字段中
declaredField.set(bean, value);
} else {
String ref = propertyElement.getAttribute("ref");
if (ref == null || ref.length() == 0) {
throw new IllegalArgumentException("ref config error");
}
// 将引用填充到相关字段中
// 这里如果ref没有初始化在此对象之前,是否会报错???
declaredField.set(bean, getBean(ref));
}
// 将bean注册到bean容器中
// 如果说,一个bean含有多个property,是初始化多个吗?后面的会覆盖前面的,为什么不放到外面一层?
registerBean(id, bean);
}
}
}
}
}
private void registerBean(String id, Object bean) {
beanMap.put(id, bean);
}
}
容器测试使用的bean代码:
@Data
public class Car {
private String name;
private String length;
private String width;
private String height;
private Wheel wheel;
}
@Data
public class Wheel {
private String brand;
private String specification;
}
bean配置文件ioc.xml内容:
<beans>
<bean id = "wheel" class = "com.axuan.toyspring.Wheel">
<property name = "brand" value = "Micheline"/>
<property name = "specification" value = "256/60 R18"/>
</bean>
<bean id = "car" class = "com.axuan.toyspring.Car">
<property name = "name" value = "Mercedes Benz G 500"/>
<property name = "length" value = "4717mm"/>
<property name = "width" value = "1855mm"/>
<property name = "height" value = "1949mm"/>
<property name = "wheel" ref = "wheel"/>
</bean>
</beans>
IOC测试类SimpleIOCTest:
public class SimpleIOCTest {
@Test
public void getBean() throws Exception {
String location = SimpleIOC.class.getClassLoader().getResource("toy-spring-ioc.xml").getFile();
SimpleIOC bf = new SimpleIOC(location);
Wheel wheel = (Wheel) bf.getBean("wheel");
System.out.println(wheel);
Car car = (Car)bf.getBean("car");
System.out.println(car);
}
}
测试结果:
1.2.简单的AOP实现
一些概念
通知(Advice)
通知定义了要织入目标对象的逻辑,以及执行时机。
Spring中对应了5种不同类型的通知:
- 前置通知(Before):在目标方法执行前,执行通知
- 后置通知(After):在目标方法执行后,执行通知,此时不关系目标方法返回的结果是什么
- 返回通知(After-returning):在目标方法执行后,执行通知
- 异常通知(After-throwing):在目标方法抛出异常后执行通知
- 环绕通知(Around):目标方法被通知包裹,通知在目标方法执行前和执行后都被会调用
切点(Pointcut)
如果说通知定义了在何时执行通知,那么切点就定义了在何处执行通知。所以切点的作用就是
通过匹配规则查找何时的连接点(Jointpoint),AOP会在这些连接点织入通知。
切面(Aspect)
切面包含了通知和切点,通知和切点共同定义了切面是什么,在何时,何处执行切面逻辑。
AOP是基于JDK动态代理实现的,只需3步即可完成:
1.定义一个包含切面逻辑的对象,这里假设叫logMethodInvocation
2.定义一个Advice对象(实现了InvocationHandler接口),并将上面的logMethodInvocation和目标对象传入
3.将上面的Advice对象和目标对象传给JDK动态代理方法,为目标对象生成代理
AOP的代码结构:
MethodInvocation 接口 // 实现类包含了切面逻辑,如上面的 logMethodInvocation
Advice 接口 // 继承了 InvocationHandler 接口
BeforeAdvice 类 // 实现了Advice接口,是一个前置通知
SimpleAOP类 // 生成代理类
SimpleAOPTest // SimpleAOP 从测试类
HelloService // 目标对象接口
HelloServiceImpl // 目标对象
MethodInvocation 接口代码:
public interface MethodInvocation {
void invoke();
}
Advice接口代码:
public interface Advice extends InvocationHandler {
}
BeforeAdvice实现代码:
public class BeforeAdvice implements Advice{
private Object bean;
private MethodInvocation methodInvocation;
public BeforeAdvice(Object bean, MethodInvocation methodInvocation) {
this.bean = bean;
this.methodInvocation = methodInvocation;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在目标方法执行前调用通知
methodInvocation.invoke();
return method.invoke(bean, args);
}
}
SimpleAOP实现代码:
public class SimpleAOP {
public static Object getProxy(Object bean, Advice advice) {
return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader(),
bean.getClass().getInterfaces(), advice);
}
}
HelloService接口,及其实现类代码:
public interface HelloService {
void sayHelloWorld();
}
public class HelloServiceImpl implements HelloService{
@Override
public void sayHelloWorld() {
System.out.println("hello world");
}
}
SimpleAOPTest代码:
public class SimpleAOPTest {
@Test
public void getProxy() {
// 1.创建一个 MethodInvocation 实现类
MethodInvocation logTask = () -> System.out.println("log task start");
HelloService helloServiceImpl = new HelloServiceImpl();
// 2.创建一个Advice
Advice beforeAdvice = new BeforeAdvice(helloServiceImpl, logTask);
// 3.为目标对象生成代理
HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloServiceImpl, beforeAdvice);
helloServiceImplProxy.sayHelloWorld();
}
}
结果:
以上实现了简单的IOC和AOP,下一篇有一个较为复杂的IOC和AOP。