【Java】模拟Sping,实现其IOC和AOP核心(二)
接着上一篇,在上一篇完成了有关IOC的注解实现,这一篇用XML的方式实现IOC,并且完成AOP。
简易的IOC框图
注解的方式实现了左边的分支,那么就剩下右边的XML分支:
XmlContext:
这个类是也是AbstractApplicationContext的子类,和AnnotationContext相似,只不过这里是要解析XML文件而不是注解:
(关于XML文件的解析之前给过一篇博客:【Java】XML文件的解析
对于XML文件的处理,是不太容易的,会产生很多问题,后面只是实现核心步骤,很多属性就不考虑了!
首先给出XmlBean,和AnnotationBean一样,都是继承自BeanElement
public class XmlBean implements BeanElement {
private boolean DI;
private Object object;
private Object proxy;
private Map<Field, String> wiredMap;
// key:object的为注入成员 value:依赖的className
// 将不能注入的成员先保存起来 protected XmlBean() {
this(true, null, null);
} protected XmlBean(Object object, Object proxy) {
this(true, object, proxy);
} protected XmlBean(boolean dI, Object object, Object proxy) {
DI = dI;
this.object = object;
this.proxy = proxy;
} protected void addWiredElement(Field field, String ref) throws RepeatProperty {
if (wiredMap == null) {
wiredMap = new HashMap<>();
}
if (wiredMap.containsKey(field)) {
throw new RepeatProperty(object.getClass() + "成员:" + field.getName() + "已定义!");
}
wiredMap.put(field, ref);
} protected void setDI(boolean DI) {
this.DI = DI;
} protected Map<Field, String> getWiredMap() {
return wiredMap;
} @Override
@SuppressWarnings("unchecked")
public <E> E getProxy() {
return (E) proxy;
} @Override
public Object getObject() {
return object;
} @Override
public boolean isDI() {
return DI;
} }
XmlContext
public class XmlContext extends AbstractApplicationContext {
protected XmlContext() {
} protected XmlContext(String xmlPath) {
innerParseXml(xmlPath);
} // 和注解方式中的做法一样,只不过产生的是XML方式的BeanElement
private XmlBean addXmlBean(Class<?> klass, Object object, String classId, String className) throws BeansException {
Object proxy = aopFactory.creatCGLibProxy(klass, object);
XmlBean bean = new XmlBean(object, proxy);
add(classId, className, bean);
return bean;
} protected void innerParseXml(String xmlPath) {
// 找到根标签
new XMLReader() {
@Override
public void dealElment(Element element, int index) {
// 处理bean标签
new XMLReader() {
@Override
public void dealElment(Element element, int index) {
// 得到id属性和class属性的值
String classId = element.getAttribute("id");
String className = element.getAttribute("class");
try {
// 由class得到类
Class<?> klass = Class.forName(className);
// 处理constructor标签
new XMLReader() {
@Override
public void dealElment(Element element, int index) {
// TODO 处理有参数的构造方法,这里就会遇到许多问题,在这里我就不处理了,后面会给出解决思路
}
}.parse(element, "constructor-arg");
// 由于上面没有处理带参数的构造方法,这里直接通过反射机制调用无参构造产生对象
// 并且利用产生的对象生成代理对象,最后得到Bean放入beanMap中
Object object = klass.newInstance();
XmlBean bean = addXmlBean(klass, object, classId, className); // 处理property标签
new XMLReader() {
@Override
public void dealElment(Element element, int index) {
try {
dealProperty(element, klass, bean);
} catch (XmlPropertyMustNeedNameException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}.parse(element, "property");
} catch (Exception e1) {
e1.printStackTrace();
}
}
}.parse(element, "bean");
}
}.parse(XMLReader.openDocument(xmlPath), "SimpleSpring");
} private void dealProperty(Element element, Class<?> klass, XmlBean bean)
throws XmlPropertyMustNeedNameException, Exception {
// 得到property标签name属性的值
String fieldName = element.getAttribute("name");
if (fieldName.length() <= 0) {
throw new XmlPropertyMustNeedNameException("Bean" + klass.getName() + "的Property标签必须声明name属性!");
}
// 通过反射机制得到成员
Field field = klass.getDeclaredField(fieldName);
// 得到该成员的类型
Class<?> fieldType = field.getType();
// 得到value属性
String value = element.getAttribute("value");
// 得到ref属性
String ref = element.getAttribute("ref"); // 判断ref和value是否同时存在
if (value.length() > 0 && ref.length() > 0) {
throw new CanNotJudgeParameterException("value:" + value + " ref:" + ref + "只能存在一个!");
}
Object arg = null;
// value存在,则直接通过类型转换给成员赋值
if (value.length() > 0) {
if (!fieldType.isPrimitive() && !fieldType.equals(String.class)) {
throw new ValueOnlyPrimitiveType("Value只能用于八大基本类型!");
}
// TypeConversion是我自己写的,将字符串转换为基本类型的工具
arg = TypeConversion.getValue(value, fieldType.getSimpleName());
field.setAccessible(true);
field.set(bean.getObject(), arg);
}
if (ref.length() > 0) {
// ref属性存在,由于存在相互依赖关系,所以现在不做处理,只是将其保存起来
// 设置该bean的状态为尚未注入
bean.setDI(false);
bean.addWiredElement(field, ref);
}
} }
XmlContext能做的工作也十分有限,只能完成简单的注入,剩下的注入工作留给下一级处理!
在这里之所以没有处理constructor标签,是因为对与构造方法的处理存在许多因素:
比如:
public class Test {
public Test(String one, int two) {
......
}
public Test(int two, String one) {
......
}
}
通过XML文件读取出来的都是字符串,如何区分它是字符串“123”,而不是int类型123?这两个构造方法到底执行哪个?
再比如说:
public Test(int one, int two, Student student) {
......
} public Test(String one, int two, Student student) {
......
} public Test(int two, String one, Student student) {
......
}
通过反射机制,我们就需要得到构造方法的集合getConstructors();然后筛选出参数个数符合要求的子集,再遍历这个子集的每一个构造方法,然后遍历当前构造方法的所有参数,一个一个比对参数类型是否符合要求,直到找到符合要求的那一个为止,但是,如果说我们是想执行第三个构造方法,它却找到的是第一个,完全就出问题了!
所以Spring的解决办法是给出一个type属性
<bean id="xxx" class="xxx.xxx.Test">
<constructor-arg idnex="0" value="1" type="int.class">
<constructor-arg idnex="1" value="2" type="java.lang.String">
<constructor-arg idnex="2" ref="student">
</bean>
只有这样做才能真真区分,所以以后在使用Spring的constructor标签时,当构造方法有歧义时,一定要给出type属性,避免出错,也减少了查找时的遍历!
接下来就是最后一个类,xml分支的最高容器:
ClassPathXmlApplicationContext
上面的XmlContext只是完成了基本的注入问题,还有后续有关于注入之间的依赖关系,甚至是依赖循环(关于依赖循环在我的上一篇中有专门介绍,这里就不再介绍了)
public class ClassPathXmlApplicationContext extends XmlContext {
public ClassPathXmlApplicationContext() {
} public ClassPathXmlApplicationContext(String xmlPath) {
super(xmlPath);
} public ClassPathXmlApplicationContext parseXml(String xmlPath) {
innerParseXml(xmlPath);
return this;
} @Override
public <T> T getBean(Class<T> klass) throws BeansException {
String className = klass.getName();
BeanElement bean = beanMap.get(className); if (bean == null) {
throw new BeansException("Bean :" + klass + "不存在!");
}
// 在这里还是只考虑XmlBean的注入,不考虑AnnotationBlean注解的完成情况
if (!bean.isDI() && bean instanceof XmlBean) {
autowired(className, (XmlBean)bean);
} return bean.getProxy();
} private void autowired(String klassName, XmlBean bean) throws BeansException {
// 和AnnotationBean的解决思路一样,先设置状态为已注入,防止循环依赖的无限递归
bean.setDI(true);
// 得到尚未注入的成员map
Map<Field, String> wiredMap = bean.getWiredMap();
if (wiredMap == null || wiredMap.isEmpty()) return;
// 遍历map
for (Field field : wiredMap.keySet()) {
String ref = wiredMap.get(field);
String tagClassName = beanNameMap.get(ref);
// ref如果是id则在beanNameMap中找,如果是className就在beanMap中找
BeanElement wiredBean = tagClassName == null ? beanMap.get(ref) : beanMap.get(tagClassName);
if (bean == null) {
return;
}
if (!wiredBean.isDI() && wiredBean instanceof XmlBean) {
autowired(ref, (XmlBean)wiredBean);
}
field.setAccessible(true);
try {
field.set(bean.getObject(), wiredBean.getObject());
} catch (Exception e) {
throw new BeansException(klassName + "依赖关系不正确!");
}
}
wiredMap.clear();
} }
看过注解方式的话再看XML就会发现两者其实是一回事,都是通过两者提供的映射关系,利用反射机制完成注入!
只不过两者提供的映射关系在解析起来时各有各的特点!
Xml方式的实现这里就简单实现了,来看看使用情况:
public class StudentA {
String name;
private StudentB B; public StudentA() {
} @Override
public String toString() {
return "A:" + name + "->" + B;
} } @Component
public class StudentB {
private String name;
private StudentC C; public StudentB() {
} @Override
public String toString() {
return "B:" + name + "->" + C;
} } @Component
public class StudentC {
private String name;
private StudentA A; public StudentC() {
} @Override
public String toString() {
return "C:" + name;
} }
xml的配置:
<SimpleSpring>
<bean id="haha" class="com.zc.ioc.demo.StudentA">
<property name="name" value="我是A"></property>
<property name="B" ref="com.zc.ioc.demo.StudentB"></property>
</bean>
<bean class="com.zc.ioc.demo.StudentB">
<property name="name" value="我是B"></property>
<property name="C" ref="com.zc.ioc.demo.StudentC"></property>
</bean>
<bean class="com.zc.ioc.demo.StudentC">
<property name="name" value="我是C"></property>
<property name="A" ref="haha"></property>
</bean>
</SimpleSpring>
主函数:
public static void main(String[] args) throws BeansException {
// 或者是使用BeanFactory beanFactory = new ClassPathXmlApplicationContext("/test_simple_spring.xml");
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/test_simple_spring.xml");
StudentA bean = applicationContext.getBean(StudentA.class);
System.out.println(bean);
}
输出:
那么试一试注解和Xml方式的混合使用:
@Component
public class StudentA {
@Value(value="我是A")
String name;
@Autowired
private StudentB B; public StudentA() {
} @Override
public String toString() {
return "A:" + name + "->" + B;
} } @Component
public class StudentB {
@Value(value="我是B")
private String name;
@Autowired
private StudentC C; public StudentB() {
} @Override
public String toString() {
return "B:" + name + "->" + C;
} }
@Component
public class StudentC {
@Value(value="我是C")
private String name;
@Autowired
private StudentD D; @Autowired
private StudentA A; public StudentC() {
} @Override
public String toString() {
return "C:" + name + "->" + D;
} } public class StudentD {
private String name; public StudentD() {
} @Override
public String toString() {
return "D:" + name;
} }
Xml配置:
<SimpleSpring>
<bean class="com.zc.ioc.demo.StudentD">
<property name="name" value="我是D"></property>
</bean>
</SimpleSpring>
主函数:
public static void main(String[] args) throws BeansException {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/test_simple_spring.xml");
StudentD studentD = applicationContext.getBean(StudentD.class);
System.out.println(studentD); applicationContext= new AnnotationConfigApplicationContext("com.zc.moedl");
StudentA studentA = applicationContext.getBean(StudentA.class);
System.out.println(studentA);
}
输出结果:
看起来是没有问题了,但是如果Xml和注解之间的出现顺序不同,结果也会不一样,还得仔细考虑,而且我做的这个是延迟注入,只有在getBean的时候才会完成最后的注入,并且若是注解中需要一个Xml的bean注入,而xml的这个bean又依赖于注解中的一个bean,那么这套方法是不可行的!
AOP
前面多次谈到AOP,以及我们的Bean是通过原始对象+代理对象,这里来看看AOP部分的实现:
AOP说到底主要目的不是产生代理对象,而是要通过代理对象执行方法,并对方法进行有效的拦截!
简单起见,将拦截分为置前,置后,以及出现异常时的拦截。
而拦截又是何时产生的?
还是为了简单实现,后面都只使用CGLibProxy,有关CGLib的代理我在上一篇有介绍,这里也就不累赘了。
关于拦截器的产生,我之前的实现方式是给要拦截的方法添加注解,给出拦截Id,然后提供一套方法,给指定Id号的方法创建拦截器,但是,在知道Spring的处理后,这种方式很快被否定了!在工程中,往往很多需要拦截的方法是不允许侵入式修改的,又或者是被打成了jar包,那么就更不可能对其添加注解,所以给出新的解决思路:
由用户自己写一个方法,然后给这个方法添加注解,使其和要拦截的方法产生对映射关系,这样我们实际执行的拦截器方法完全是由用户提供,并不会干预源代码!
前面说过只是处理置前,置后,以及出现异常时的拦截,所以会给出三种不同的注解,用于区分!
由于是要使用注解,那么就要用到包扫描【Java】包、jar包的扫描
包扫描就需要对类进行区分,只处理带有标识的类,所以还缺少一个对类的注解:
@Aspect
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {
}
这个注解只是为了表明这个类存放着用户编写的拦截器方法!
主要的是下面三个注解:
@Before
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {
Class<?> klass();
String method();
}
置前拦截方法的注解,klass表明对哪个类进行置前拦截,method表明对哪个方法进行拦截,但发现仅仅通过这好像不能找到具体的方法,但仔细想一想,置前拦截是对要拦截的方法参数进行判断,用户在编写拦截时必然知道拦截的方法是什么,参数个数和类型当然也知道,那我们只要让用户写的方法的参数和要拦截的方法参数保持一致就行了,如果不一致,就异常处理!这样就能通过用户编写的方法,知道被拦截的方法参数,进而定位到具体要拦截的方法!
@After
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {
Class<?> klass();
String method();
Class<?>[] parameterTypes() default {};
}
置后拦截方法的注解,同置前拦截一样,klass表明对哪个类进行置前拦截,method表明对哪个方法进行拦截。由于之后拦截是对方法执行结果的操作,用户写的方法的参数有且只有一个,且参数类型要与原方法的返回值类型匹配,这样,我们就不能像处理@Before时一样,必须申明被拦截的方法的参数类型,只有这样才能定位到具体的被拦截方法!
@Throwable
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Throwable {
Class<?> klass();
String method();
Class<?>[] parameterTypes();
}
出现异常时拦截的注解,和@After一样,它只是处理异常,那么用户所提供的方法的参数有且只有一个,类型是执行被拦截方法产生的异常类型,它也必须传递被拦截方法的参数,使其定位到具体的被拦截方法!
其实在Spring里面使用了更为犀利的手段,它并没有使用Class<?> 只是使用了一个字符串就解决了类、方法、参数的定位,只不过它就需要对字符串解析,利用了正则表达式,虽然比我的方法繁琐,但面向用户的使用是十分友好的!
AOP框图
看起来我把它做的很复杂,事实上这里用到的全是接口,是非常灵活的,如果说不想使用这套方式,那么可以自己实现Advice接口;如果说拦截器链用的时list存储,以后想更换为链表也是可以的;拦截器的产生不想使用上面说的注解方式,那么自己去实现IntercepterFactory接口!
AopFactory
public class AopFactory {
private Advice advice; public AopFactory() {
} public AopFactory setAdvice(Advice advice) {
this.advice = advice;
return this;
} public <E> E creatCGLibProxy(Class<?> klass) throws Exception {
return creatCGLibProxy(klass, klass.newInstance());
} public <E> E creatCGLibProxy(Object object) {
return creatCGLibProxy(object.getClass(), object);
} @SuppressWarnings("unchecked")
public <E> E creatCGLibProxy(Class<?> klass, Object object) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(klass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxyObject, Method method,
Object[] args, MethodProxy methodProxy) throws Throwable {
return doInvoke(object, method, args);
}
}); return (E) enhancer.create();
} private Object doInvoke(Object object, Method method,
Object[] args)
throws Throwable{
Object result = null;
// AdviceAdapter是Advice的适配,什么都不做
advice = advice == null ? new AdviceAdapter() : advice;
if (!advice.dealBefore(method, args)) {
return result;
}
try {
result = method.invoke(object, args);
result = advice.dealAfter(method, result);
} catch (Throwable e) {
advice.delaThrowable(method, e);
throw e;
} return result;
} }
AopFactory只负责产生代理对象,而代理的拦截就下发给Advice
Advice
public interface Advice {
boolean dealBefore(Method method, Object[] args);
Object dealAfter(Method method, Object result);
void delaThrowable(Method method, Throwable e);
}
暂时只处理置前、置后、以及异常,以后需要再添加,而不是修改!这就是使用接口的好处!
IntercepterLink
public interface IntercepterLink {
boolean add(IntercepterMethod tagMethod);
public boolean doBefore(Object[] args);
Object doAfter(Object result);
void doThrowable(Throwable e);
}
拦截器链,一个方法可以由多个拦截器,拦截器链是拦截器方法的真正执行者!提供了添加拦截器,处理置前、置后、异常,也可以给个remove,这里就不写了。
IntercepterFactory
public interface IntercepterFactory {
void addBeforeIntercepter(Method tagMethod, IntercepterMethod imd);
void addAfterIntercepter(Method tagMethod, IntercepterMethod imd);
void addThrowableIntercepter(Method tagMethod, IntercepterMethod imd); IntercepterLink getBeforeIntercepterLink(Method tagMethod);
IntercepterLink getAfterIntercepterLink(Method tagMethod);
IntercepterLink getThrowableIntercepterLink(Method tagMethod);
}
拦截器链的创建者和拥有着,其中的IntercepterMethod就是拦截器接口:
IntercepterMethod
public interface IntercepterMethod {
Object getIntercepterObject();
Method getIntercepterMethod();
}
我们的拦截器的执行是通过反射机制,那么就必须知道方法和对象,至于参实是通过CGLib代理机制传递过来的,就不用考虑!
准备工作完成,接下来就是真真的处理部分:
IntercepterFactory在上面说是拦截器的创建者和持有者,所以我把它的是实现类进行了分级:
IntercepterLoader
public class IntercepterLoader implements IntercepterFactory {
// 可以看到每个方法都有自己的置前、置后、异常拦截器链
private static final Map<Method, IntercepterLink> beforeMap;
private static final Map<Method, IntercepterLink> afterMap;
private static final Map<Method, IntercepterLink> exceptionMap; static {
beforeMap = new HashMap<>();
afterMap = new HashMap<>();
exceptionMap = new HashMap<>();
} public IntercepterLoader() {
} @Override
public IntercepterLink getBeforeIntercepterLink(Method tagMethod) {
return beforeMap.get(tagMethod);
} @Override
public IntercepterLink getAfterIntercepterLink(Method tagMethod) {
return afterMap.get(tagMethod);
} @Override
public IntercepterLink getThrowableIntercepterLink(Method tagMethod) {
return exceptionMap.get(tagMethod);
} private void add(Map<Method, IntercepterLink> map,
Method tagMethod, IntercepterMethod imd) {
IntercepterLink link = map.get(tagMethod);
// 防止多线程的访问而创建不同的拦截器链
if (link == null) {
synchronized (map) {
if (link == null) {
// IntercepterNodeList是我这套机制默认的IntercepterLink实现类
link = new IntercepterNodeList(imd);
}
}
// 该方法还未创建拦截器链
map.put(tagMethod, link);
} else {
// 方法相同,则在拦截器链上追加
link.add(imd);
}
} @Override
public void addBeforeIntercepter(Method tagMethod, IntercepterMethod imd) {
add(beforeMap, tagMethod, imd);
} @Override
public void addAfterIntercepter(Method tagMethod, IntercepterMethod imd) {
add(afterMap, tagMethod, imd);
} @Override
public void addThrowableIntercepter(Method tagMethod, IntercepterMethod imd) {
add(exceptionMap, tagMethod, imd);
} }
真正意义上的拦截器持有者,它要完成的功能非常简单!
我们是要通过注解的方式产生拦截器,所以就有更高级来处理:
IntercepterLoaderFactory
/**
* 使用包扫描,找到带有@Before、@After、@Throwable的方法,将其添加至拦截器map中
*/
public class IntercepterLoaderFactory extends IntercepterLoader {
public IntercepterLoaderFactory() {
} public IntercepterLoaderFactory parseMethodForPackage(String packageName) {
new PackageScanner() {
@Override
public void dealClass(Class<?> klass) {
// 判断类是否满足我们定义的@Aspect
if (!klass.isAnnotationPresent(Aspect.class)) return;
try {
// 产生方法执行的对象
Object object = klass.newInstance();
Method[] methods = klass.getDeclaredMethods();
// 遍历所有方法,处理带有注解的方法
for (Method method : methods) {
if (method.isAnnotationPresent(Before.class)) {
parseBeforeIntercepter(klass, object, method, method.getAnnotation(Before.class));
} else if (method.isAnnotationPresent(After.class)) {
parseAfterIntercepter(klass, object, method, method.getAnnotation(After.class));
} else if (method.isAnnotationPresent(Throwable.class)) {
parseExceptionIntercepter(klass, object, method, method.getAnnotation(Throwable.class));
}
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}.scanPackage(packageName);
return this;
} /**
* 处理带@Before注解的方法
*/
private void parseBeforeIntercepter(Class<?> klass, Object object, Method method, Before before) {
if (!method.getReturnType().equals(boolean.class)) {
try {
throw new ReturnTypeNotMatch(method + "返回值类型必须是boolean!");
} catch (ReturnTypeNotMatch e) {
e.printStackTrace();
}
}
// 从@Before注解中获取被拦截方法的信息
Class<?> targetClass = before.klass();
String targetMethodName = before.method();
Class<?>[] methodTypes = method.getParameterTypes(); try {
Method targetMethod = targetClass.getDeclaredMethod(targetMethodName, methodTypes);
// 父类的方法调用,其中的IntercepterMethodDefination是IntercepterMethod的实现类
addBeforeIntercepter(targetMethod, new IntercepterMethodDefination(object, method));
} catch (NoSuchMethodException e) {
try {
throw new IntercepterMethodParaNotMatch(method + "参数不匹配!");
} catch (IntercepterMethodParaNotMatch e1) {
e1.printStackTrace();
}
}
} /**
* 处理带@After注解的方法
*/
private void parseAfterIntercepter(Class<?> klass, Object object, Method method, After after) {
// 从@After注解中获取被拦截方法的信息
Class<?> targetClass = after.klass();
String targetMethodName = after.method();
Class<?>[] targetMethodPara = after.parameterTypes();
try {
// 通过上述参数得到被拦截方法,如果得不到,异常处理
Method targetMethod = targetClass.getDeclaredMethod(targetMethodName, targetMethodPara);
Class<?> targetMethodReturnType = targetMethod.getReturnType();
Class<?> methodReturnType = method.getReturnType();
Class<?>[] methodParameters = method.getParameterTypes();
// 判断是否满足置后拦截方法的条件:
// 置后拦截的方法返回值类型必须和被拦截方法相同
// 置后拦截的方法的参数有且只有一个,且是被拦截的方法返回值类型
if (methodParameters.length != 1
|| !targetMethodReturnType.equals(methodReturnType)
|| !methodReturnType.equals(methodParameters[0])) {
try {
throw new IntercepterMethodParaNotMatch("拦截器方法:" + method +
" 与被拦截方法" + targetMethod + "不匹配!");
} catch (IntercepterMethodParaNotMatch e) {
e.printStackTrace();
}
} addAfterIntercepter(targetMethod, new IntercepterMethodDefination(object, method));
} catch (NoSuchMethodException | SecurityException e) {
try {
throw new IntercepterMethodParaNotMatch("被拦截方法[" + targetMethodName + "]不存在!");
} catch (IntercepterMethodParaNotMatch e1) {
e1.printStackTrace();
}
}
} private void parseExceptionIntercepter(Class<?> klass, Object object, Method method, Throwable throwable) {
// 从@Throwable 注解中获取被拦截方法的信息
Class<?> targetClass = throwable.klass();
String targetMethodName = throwable.method();
Class<?>[] targetMethodPara = throwable.parameterTypes();
try {
// 通过上述参数得到被拦截方法,如果得不到,异常处理
Method targetMethod = targetClass.getDeclaredMethod(targetMethodName, targetMethodPara);
Class<?>[] methodParameters = method.getParameterTypes();
// 判断是否满足异常拦截方法的条件:
// 异常拦截的方法的参数有且只有一个,且是java.lang.Throwable
if (methodParameters.length != 1
|| methodParameters[0].equals(java.lang.Throwable.class)) {
try {
throw new IntercepterMethodParaNotMatch("拦截器方法:" + method +
" 与被拦截方法" + targetMethod + "不匹配!");
} catch (IntercepterMethodParaNotMatch e) {
e.printStackTrace();
}
}
addAfterIntercepter(targetMethod, new IntercepterMethodDefination(object, method));
} catch (NoSuchMethodException | SecurityException e) {
try {
throw new IntercepterMethodParaNotMatch("被拦截方法[" + targetMethodName + "]不存在!");
} catch (IntercepterMethodParaNotMatch e1) {
e1.printStackTrace();
}
}
} }
通过这套机制,我们就能通过注解+包扫描,十分方便地给指定方法添加拦截了!
IntercepterMethodDefination
public class IntercepterMethodDefination implements IntercepterMethod {
private Object intercepterObject;
private Method intercepterMethod; protected IntercepterMethodDefination() {
} protected IntercepterMethodDefination(Object intercepterObject, Method intercepterMethod) {
this.intercepterObject = intercepterObject;
this.intercepterMethod = intercepterMethod;
} @Override
public Object getIntercepterObject() {
return intercepterObject;
} @Override
public Method getIntercepterMethod() {
return intercepterMethod;
} }
拦截器方法执行所需的封装
拦截器我们也有了,就剩下拦截器链了:
我的拦截器链使用了链表,为了能够方法地链式调用,也就是设计模式之一的职责链模式,当然也可以使用List,只不过使用链表相比于List,在处理时都需要遍历,没有什么差别,但是链表比List占的空间小,List在内部是数组,且数组大小是大于有效元素个数的!
IntercepterNodeList
public class IntercepterNodeList implements IntercepterLink {
private IntercepterMethod imd; // 拦截器
private IntercepterNodeList next; // 下一结点
private IntercepterNodeList last; // 尾结点 protected IntercepterNodeList() {
this(null);
} protected IntercepterNodeList(IntercepterMethod imd) {
this.imd = imd;
this.next = null;
this.last = this;
} /**
* 尾插法追加结点
*/
@Override
public boolean add(IntercepterMethod imd) {
if (next == null) {
next = new IntercepterNodeList(imd);
last = next;
} else {
last = last.next = new IntercepterNodeList(imd);
last.next = null;
}
return true;
}
/**
* 链式调用处理置前拦截
*/
@Override
public boolean doBefore(Object[] args) {
boolean isContinue = this.innerInvoke(imd.getIntercepterObject(), imd.getIntercepterMethod(), args);
if (this.next != null && isContinue) {
isContinue = this.next.doBefore(args);
}
return true;
} @SuppressWarnings("unchecked")
private <T> T innerInvoke(Object object, Method method, Object[] args) {
T result = null;
try {
result = (T) method.invoke(object, args);
} catch (Exception e) {
e.printStackTrace();
}
return result;
} /**
* 链式调用处理置后拦截
*/
@Override
public Object doAfter(Object result) {
result = innerInvoke(imd.getIntercepterObject(), imd.getIntercepterMethod(), new Object[] {result});
if (this.next != null) {
result = this.next.doAfter(result);
}
return result;
} /**
* 链式调用处理异常拦截
*/
@Override
public void doThrowable(Throwable e) {
innerInvoke(imd.getIntercepterObject(), imd.getIntercepterMethod(), new Object[] {e});
if (this.next != null) {
this.next.doThrowable(e);
}
} }
AOP到这里就已经结束了,来看看它的使用吧:
被拦截类及其方法:
public class Test {
public Test() {
} public String fun(int arg) {
System.out.println("Test的fun方法执行 arg = " + arg);
return "fun";
}
}
拦截器所在类:
@Aspect
public class Action {
public Action() {
} @Before(klass=Test.class, method="fun")
public boolean beforeFun(int arg) {
System.out.println("置前拦截beforeFun:arg = " + arg);
return true;
} @Before(klass=Test.class, method="fun")
public boolean beforeFunOther(int arg) {
System.out.println("置前拦截beforeFunOther:arg = " + arg);
return true;
} @After(klass=Test.class, method="fun", parameterTypes= {int.class})
public String AfterFun(String arg) {
System.out.println("置后拦截:arg = " + arg); return "AfterFun";
}
}
主函数:
public static void main(String[] args) throws Exception {
IntercepterLoaderFactory intercepterLoaderFactory =
new IntercepterLoaderFactory().parseMethodForPackage("com.zc.action"); AopFactory aopFactory = new AopFactory();
aopFactory.setAdvice(new AdviceHander()
.setIntercepterFactory(intercepterLoaderFactory)); Test testProxy = aopFactory.creatCGLibProxy(Test.class);
System.out.println(testProxy.fun(10));
}
执行结果:
这样的用法是有些恶心了,但是,别忘了,AOP配合IOC才是使用的精华:
注解方式的注入:
@Component
public class StudentA {
@Value(value="我是A")
String name;
@Autowired
private StudentB B; public String fun(int arg) {
System.out.println("StudentA的fun方法执行 arg = " + arg);
return "fun";
} @Override
public String toString() {
return "A:" + name + "->" + B;
} } @Component
public class StudentB {
@Value(value="我是B")
private String name;
@Autowired
private StudentC C; public StudentB() {
} @Override
public String toString() {
return "B:" + name + "->" + C;
} } @Component
public class StudentC {
@Value(value="我是C")
private String name;
@Autowired
private StudentD D; @Autowired
private StudentA A; public StudentC() {
} @Override
public String toString() {
return "C:" + name + "->" + D;
} }
Xml方式的注入:
<SimpleSpring>
<bean class="com.zc.ioc.demo.StudentD">
<property name="name" value="我是D"></property>
</bean>
</SimpleSpring>
拦截器:
@Aspect
public class Action {
public Action() {
} @Before(klass=StudentA.class, method="fun")
public boolean beforeFun(int arg) {
System.out.println("置前拦截beforeFun:arg = " + arg);
return true;
} @Before(klass=StudentA.class, method="fun")
public boolean beforeFunOther(int arg) {
System.out.println("置前拦截beforeFunOther:arg = " + arg);
return true;
} @After(klass=StudentA.class, method="fun", parameterTypes= {int.class})
public String AfterFun(String arg) {
System.out.println("置后拦截:arg = " + arg);
return "AfterFun";
} }
主函数:
public static void main(String[] args) throws Exception {
new IntercepterLoaderFactory().parseMethodForPackage("com.zc.action");
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/test_simple_spring.xml");
StudentD studentD = applicationContext.getBean(StudentD.class);
System.out.println(studentD); applicationContext = new AnnotationConfigApplicationContext("com.zc.model");
StudentA studentA = applicationContext.getBean(StudentA.class);
studentA.fun(10);
System.out.println(studentA);
}
执行结果:
Spring的IOC和AOP就是先到这里了,有兴趣的可以交流一下
感谢您的阅读(*^_^*)
【Java】模拟Sping,实现其IOC和AOP核心(二)的更多相关文章
- 【Java】模拟Sping,实现其IOC和AOP核心(一)
在这里我要实现的是Spring的IOC和AOP的核心,而且有关IOC的实现,注解+XML能混合使用! 参考资料: IOC:控制反转(Inversion of Control,缩写为IoC),是面向对象 ...
- 请简要介绍Sping MVC、IoC和AOP
Sping MVC是在Spring框架上发展起来的框架,它提供了构建Web应用程序的全功能MVC模块,使用了Spring可插入的MVC架构,可以自由的选择各个模块所使用的架构,非常灵活.Spring ...
- 模拟Java-Sping,实现其IOC和AOP核心
简易的IOC框图 注解的方式实现了左边的分支,那么就剩下右边的XML分支: XmlContext:这个类是也是AbstractApplicationContext的子类,和AnnotationCont ...
- J2EE进阶(十四)超详细的Java后台开发面试题之Spring IOC与AOP
J2EE进阶(十四)超详细的Java后台开发面试题之Spring IOC与AOP 前言 搜狐畅游笔试题中有一道问答题涉及到回答谈谈对Spring IOC与AOP的理解.特将相关内容进行整理. ...
- Sping框架的IOC特性 悲观锁、乐观锁 Spring的AOP特性
Sping框架的IOC特性 IOC(Inversion of Control):控制反转 以下以课程与老师的安排来介绍控制反转. 一个合理的课程编排系统应该围绕培训的内容为核心,而不应该以具体的培训老 ...
- java简单例子介绍IOC和AOP
IOC和AOP的一些基本概念 介绍 IOC 一.什么是IOC IoC就是Inversion of Control,控制反转.在Java开发中,IoC意味着将你设计好的类交给系统去控制,而不是在你的类内 ...
- 反射应用--IOC和AOP
反射最大的价值就是用来写框架,下面贴出自己的3篇代码,模拟实现SPING框架的bean工厂,IOC,AOP.当然这里重点是在利用反射实现功能,为了图方便,我用的是Properties文件,关于XML后 ...
- Spring中IOC和AOP的详细解释(转)
原文链接:Spring中IOC和AOP的详细解释 我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入,和AOP,面向切面编程,这两个是Spring的灵魂. 主要用到的设计模式有工厂 ...
- spring IOC 和AOP 方面
spring 的2大核心 是Ioc 和 aop spring的依赖注入:在程序运行期间,由外部容器动态的将依赖对象注入到组件中 IOC: 实例化spring容器的二种方法 第一种:在类路径下寻找配 ...
随机推荐
- CSS Grid
效果图如上所示 <!DOCTYPE html> <html> <head> <title>练习</title> </head> ...
- 32 bit 与 64 bit 程序(2)比较
32 bit 与 64 bit 程序(2)区别 由于操作系统内存分配的不同,导致软件开发过程中,需要编译不同版本的软件. 几个重要概念: (1)这里所说的的32位与64位程序,是指经过编译器编译后 ...
- js- DOM事件之按钮绑定函数注意事项
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8&qu ...
- Hessian 使用例子
一.协议包(数据对象需要实现序列化接口,可以用于服务端接口.客户端调用服务之用) /** * */ package com.junge.demo.protocol.model; import java ...
- Scrum 冲刺博客集合
Day1 博客链接:http://www.cnblogs.com/coolgirls/p/8869839.html Day2 博客链接:http://www.cnblogs.com/coolgirls ...
- mysql数据库的常用知识
问题一:如果使用可视化工具链接mysql? mysql默认情况下是不支持远程连接的!只需要几个步骤就能轻松搞定了. 1.vi /etc/mysql/my.cnf 修改里面bind-address项,将 ...
- 三种方法在当前目录下打开cmd命令窗口
概述 运行npm的时候,每次都要cd到目录,很麻烦,所以总结了三种在当前目录下直接打开cmd窗口的方法,供以后开发时参考,相信对其他人也有用. 方法一 在当前目录按住shift再右键. 会看到右键菜单 ...
- C语言中volatile的作用和使用方法
在程序设计中,尤其是在C语言.C++.C#和Java语言中,使用volatile关键字声明的变量或对象通常具有与优化.多线程相关的特殊属性. 通常,volatile关键字用来阻止(伪)编译器认为的无法 ...
- 51Node 1051---最大子矩阵和
题目链接 一个M*N的矩阵,找到此矩阵的一个子矩阵,并且这个子矩阵的元素的和是最大的,输出这个最大的值. 例如:3*3的矩阵: -1 3 -1 2 -1 3 -3 1 2 和最大的子矩阵是 ...
- python之线程(threading)
线程是属于进程的,一个进程可能包含多个线程 至于线程和进程在使用时哪个更好,只能看使用的场景了 话不多说,看下线程模块(threading)的使用方法: #导入模块 import threading, ...