Action中属性的注入
首先,放一下上一篇链接:http://blog.youkuaiyun.com/klsstt/article/details/54583942
上一篇中给我们说已经实现了通过注解访问指定类以及相关方法的功能,那么对于mvc来说最重要基本上已经实现了最核心的功能了,视图层只需要通过servlet转发一下即可,这里就不说太多了,主要来讲讲bean层的注入,其实我们不仅仅要注入bean,当请求抵达我们指定的控制器中时,我们需要调用业务层的接口来请求数据,业务层还要调用数据层,数据层需要调用数据源,这些属性都可以用spring来注入。
那么从上一篇开始,当我们访问test.do?action=test的时候,我在action中写了一个server层,代码如下:
@Action("test")
public class TestAction {
@Resource
private DeviceService deviceService;//要注入的属性
public String query(HttpServletRequest request, HttpServletResponse response){
Map<Object,Object> model=new HashMap<Object,Object>();
System.out.println("发现默认方法");
return "klsstt";
}
public ModelAndView test(HttpServletRequest request, HttpServletResponse response){
Map<Object,Object> model=new HashMap<Object,Object>();
System.out.println("test");
deviceService.save(null);
model.put("test", "klsstt");
return new ModelAndView("test","model",model);
}
}
deviceService是一个接口,调用了save时会输出一个句话,如果seviceService对象没有实例化,就会抛出异常。所以我们需要利用代理来实现属性注入,这也是spring的实现原理.
Spring 动态代理中的基本概念
1.关注点(concern)
一个关注点可以是一个特定的问题,概念、或者应用程序的兴趣点。总而言之,应用程序必须达到一个目标
安全验证、日志记录、事务管理都是一个关注点
在oo应用程序中,关注点可能已经被代码模块化了还可能散落在整个对象模型中
2.横切关注点(crosscutting concern)
如何一个关注点的实现代码散落在多个类中或方法中
3.方面(aspect)
一个方面是对一个横切关注点模块化,它将那些原本散落在各处的,
用于实现这个关注点的代码规整在一处
4.建议(advice)通知
advice是point cut执行代码,是方面执行的具体实现
5.切入点(pointcut)
用于指定某个建议用到何处
6.织入(weaving)
将aspect(方面)运用到目标对象的过程
7.连接点(join point)
程序执行过程中的一个点
以上是spring的动态代理原理,如果看不明白可以先放着,来跟着我一起来试试,spring代理目前有俩种方式,分别是javaJDk中提供的动态代理和cglib动态代理,还记得我们第一篇引入的那个jar吗,就是用来实现cglib动态代理的。
JDK中提供的动态代理和CGLIB动态代理区别
JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGLIB动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
了解了这这俩种代理的区别就明白了为什么要用他们,在action中我们无法使用JDK动态代理,因为Action不是接口,所以我们需要用到CGLIB动态代理,当我们获取到一个请求,通过我们的收集的数据,检查到对应的action之后,我们需要调用默认或者指定的方法,那么如果在执行调用之时,注入我们的action中的属性呢,先来看看我的action:
@Action("test")
public class TestAction {
@Resource
private DeviceService deviceService;
@Resource
private DeviceService deviceService2;
@Resource
private DeviceService deviceService3;
public String query(HttpServletRequest request, HttpServletResponse response){
Map<Object,Object> model=new HashMap<Object,Object>();
System.out.println("发现默认方法");
return "klsstt";
}
public ModelAndView test(HttpServletRequest request, HttpServletResponse response){
Map<Object,Object> model=new HashMap<Object,Object>();
System.out.println("test");
deviceService.save(null);
model.put("test", "klsstt");
return new ModelAndView("test","model",model);
}
}
我在testaction中写了三个属性,并且使用了@Resource注解,接下来,我实现的思路是这样的:
第一:我们在获取到约定的名称(test.do中的test)时,我们通过init收集到的map中,取得test对应的Action类。
第二:我们对这个类实例化时,需要创建其代理类的对象。
第三:在代理方法中我们需要检测这个类中所有属性,只要属性有@Resource注解我们就为该属性实例化一个对象。
这样就完成了属性的注入。
第一步我们在上一篇中已经完成了,目前我们需要从第二步开始,先来看一段代码:
//使用代理创建实例,实现aop
Object obj=Enhancer.create(temp.getClass(), new MethodInterceptor(){
@Override
public Object intercept(Object proxy, Method method, Object[] arg2,
MethodProxy arg3) throws Throwable {
long time = System.nanoTime();//纳秒 1毫秒=1000纳秒
//给类中的属性注入对象
//1.获取所有属性信息
Field[] fields=temp.getClass().getDeclaredFields();
for(Field f : fields){
//获取字段约定的属性名称
String filedName = f.getName();
//获取属性名称,找到对应对象(接口的实现者)
//根据属性名称获取对应的实现类
final Class<?> serviceImplClass=(Class<?>)cf.getServices().get(filedName);
if(null==serviceImplClass){
continue;
}
final Object serviceImpl=serviceImplClass.newInstance();
if(null!=serviceImpl){
//1、获取属性上的指定类型的注释
Annotation[] annotation = f.getAnnotations();
if(null!=annotation&&annotation.length>0){
for(Annotation anno:annotation){
//System.out.println("属性"+filedName+"上发现注释:"+anno.annotationType().getName());
if(anno.annotationType().getName().equals("javax.annotation.Resource")){//发现resource注释,则注入对象
//实例化service层
Object service = AnimalFactory.getAnimal(serviceImpl, new AOPMethod() {
//这里写方法执行前的AOP切入方法
public void before(Object proxy, Method method, Object[] args) throws IllegalArgumentException, IllegalAccessException {
//切入service层注入。检查是否存在dao注释,存在注入dao对象
Field[] fields=serviceImplClass.getDeclaredFields();
for(Field f : fields){
//获取字段约定的属性名称
String filedName = f.getName();
//获取属性名称,找到对应对象(接口的实现者)
final Class<?> daoImpl=(Class<?>)cf.getDaos().get(filedName);
if(null!=daoImpl){
try {
final Object objDao=daoImpl.newInstance();
//1、获取属性上的指定类型的注释
Annotation[] annotation = f.getAnnotations();
if(null!=annotation&&annotation.length>0){
for(Annotation anno:annotation){
if(anno.annotationType().getName().equals("javax.annotation.Resource")){//发现resource注释,则注入对象
Object dao = AnimalFactory.getAnimal(objDao, new AOPMethod() {
//这里写方法执行前的AOP切入方法
public void before(Object proxy, Method method, Object[] args) {
//装载驱动
try {
Class.forName(dbconfig.getDriver());
dbconfig.setDataSource(dbconfig.getDataSource());
Field[] fields=daoImpl.getDeclaredFields();
for(Field f : fields){
//获取属性上的注释
Annotation[] annotation = f.getAnnotations();
if(null!=annotation&&annotation.length>0){
for(Annotation anno:annotation){
if(anno.annotationType().getName().equals("javax.annotation.Resource")){//发现resource注释,则注入对象
if(f.getType().getName().equals("com.myspringframework.jdbc.DbConfig")){//如果实现了jdbc,则注入该对象
f.setAccessible(true);//类中的成员变量为private,故必须进行此操作
f.set(objDao, dbconfig);
}
}
}
}
}
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
//注入jdbc对象
System.out.println("dbconfig:"+dbconfig.getDriver());
}
// 这里系方法执行后的AOP切入方法
public void after(Object proxy, Method method, Object[] args) {
try {
//关闭jdbc对象
Field[] fields=daoImpl.getDeclaredFields();
for(Field f : fields){
//获取属性上的注释
Annotation[] annotation = f.getAnnotations();
if(null!=annotation&&annotation.length>0){
for(Annotation anno:annotation){
if(anno.annotationType().getName().equals("javax.annotation.Resource")){//发现resource注释,则注入对象
if(f.getType().getName().equals("com.myspringframework.jdbc.DbConfig")){//如果实现了jdbc,则注入该对象
//找到jdbc的对象,进行销毁
dbconfig.shutdownDataSource();
/*f.setAccessible(true);
Object dbconfig=f.getType().newInstance();
Method jdbcMethod =f.getType().getMethod("shutdownDataSource");
jdbcMethod.invoke(dbconfig);*/
}
}
}
}
}
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
});
f.setAccessible(true);//类中的成员变量为private,故必须进行此操作
f.set(serviceImpl, dao);
}
}
}
} catch (InstantiationException e1) {
e1.printStackTrace();
}
}
}
}
// 这里系方法执行后的AOP切入方法
public void after(Object proxy, Method method, Object[] args) {
if (method.getName().equals("save"))
System.err.println("成功拦截" + method.getName() + "方法,结束");
}
});
f.setAccessible(true);//类中的成员变量为private,故必须进行此操作
f.set(temp, service);
}
}
}
}
}
System.out.println("方法"+method.getName()+"进入切面状态,运行耗时:"+(System.nanoTime()-time)+"纳秒");
Object value = method.invoke(temp, arg2);
return value;
}
});
这是我写好的一段代码,其中我们了解到创建的代理类就是原生类的子类,当我们调用子类任意方法时,都会先调用intercept代理方法,因此按照我们的思路,当我们调用query或者test时,首先会调用代理方法intercept,此时我们在代理方法里面就可以实现属性注入。
属性注入
注入属性就需要用到反射机制了,通过比较属性的注解来判断是否需要注入,我这里规定所有需要注入的属性都必须存在@Resource注解,然后只要拿到指定的属性,检查对应注解的值,即可找到对应的类,为其创建相对应的实例,即可实现属性注入,但是我这里引入的属性是一个server层的接口,当我们注入了server之后,serverimpl中也有需要注入的属性,代码如下:
@Service("deviceService")
public class DeviceServiceImpl implements DeviceService{
@Resource
private DeviceDao deviceDao;
这是我的业务层实现类中的属性,我这里的devicDao属于数据层的接口,当我们通过代理创建了server层的时候,我们也需要为server层的实现代理,在代理中实现dao层的属性注入,这里的dao层也需要代理,所以我的代理总共可以分为三次:action,server,dao,dao层最后代理主要是为了注入jdbc的对象,当然也可以注入其它类似mybatis这样的东西。
还有一点需要注意的就是server和dao层代理都是使用的jdk动态代理机制,跟action有所不同:
package com.myspringframework.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import com.myspringframework.proxy.impl.AOPMethod;
public class AOPHandle implements InvocationHandler{
//保存对象
private AOPMethod method;
private Object o;
public AOPHandle(Object o,AOPMethod method) {
this.o=o;
this.method=method;
}
/**
* 这个方法会自动调用,Java动态代理机制
* 会传入下面是个参数
* @param Object proxy 代理对象的接口,不同于对象
* @param Method method 被调用方法
* @param Object[] args 方法参数
* 不能使用invoke时使用proxy作为反射参数时,因为代理对象的接口,不同于对象
* 这种代理机制是面向接口,而不是面向类的
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret=null;
//修改的地方在这里哦
this.method.before(proxy, method, args);
ret=method.invoke(o, args);
//修改的地方在这里哦
this.method.after(proxy, method, args);
return ret;
}
}
来看看这个AOPHandle,当我们接口再实例化是,调用AOPHandle来创建代理,代理接口如下:
package com.myspringframework.proxy.impl;
import java.lang.reflect.Method;
public interface AOPMethod{
//实例方法执行前执行的方法
void after(Object proxy, Method method, Object[] args);
//实例方法执行后执行的方法
void before(Object proxy, Method method, Object[] args) throws IllegalArgumentException, IllegalAccessException;
}
实现代理之后,在方法执行前后都会进入我们的代理方法中,这样我们就可以为server注入dao,为dao层注入数据源,甚至可以做其它操作。
这样就实现了我们刚才说的第二部,第三步。在server层或者dao层中,跟action一样,也是通过注解,检查属性注解,根据注解值进行匹配创建实例,这里就不重复说了。
至此,注入环节就结束了,现在我们访问test.do?action=test,就可以看到server中的输入语句得到执行,
那么距离完成springMVC就剩下最后的工作了,这里我们可以定义个bean,ModelAndView,代码如下:
package com.myspringframework.web.servlet;
import java.util.Map;
public class ModelAndView {
private String view;
private String key;
private Map<Object,Object> model;
private int mavType;
public String getView() {
return view;
}
public void setView(String view) {
this.view = view+".jsp";
}
public Map<Object,Object> getModel() {
return model;
}
public void setModel(Map<Object, Object> model) {
this.model = model;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public int getMavType() {
return mavType;
}
public void setMavType(int mavType) {
this.mavType = mavType;
}
/**
* 返回带数据和视图结果
* @param view
* @param key
* @param model
*/
public ModelAndView(String view,String key,Map<Object,Object> model){
this.setMavType(3);
this.setView(view);
this.model=model;
this.key=key;
}
/**
* 返回视图结果
* @param view
*/
public ModelAndView(String view){
this.setMavType(1);
this.setView(view);
}
/**
* 自动转换model为json格式的字符串
* @param view
*/
public ModelAndView(Map<Object,Object> model){
this.setMavType(2);
this.model=model;
}
}
主要是用来处理返回结果,通过不同参数来进行控制,是不是跟springmvc里面的很像?再来看看此时我的servlet中的结果处理代码:
Method method = obj.getClass().getMethod(methodName,HttpServletRequest.class, HttpServletResponse.class);
Object omv=method.invoke(obj, new Object[] {request ,response});
//返回结果只支持俩种类型,ModelAndView和String
if(omv instanceof ModelAndView){
if(((ModelAndView) omv).getMavType()==3){//返回结果带视图和数据
request.setAttribute(((ModelAndView) omv).getKey(), ((ModelAndView) omv).getModel());
request.getRequestDispatcher(((ModelAndView) omv).getView()).forward(request, response);
}else if(((ModelAndView) omv).getMavType()==2){//返回json
//将model转换成json,返回
response.getWriter().println(omv.toString());
}else{//返回不带数据的视图
request.getRequestDispatcher(((ModelAndView) omv).getView()).forward(request, response);
}
}else{
response.getWriter().println(omv.toString());
}
当我们通反射机制调用了目标类的方法之后,会返回我们设置好的ModelAndView,然后通过业务解析把视图或者要返回的字符串(JSON)通过reuqer返回即可,是不是很简单。
这样一个基本的springmvc框架就有了雏形了!
写这些东西包括myspring这个项目也花了我将近一周时间,但是也解决了我之前的一些疑惑,下一步我可能会去研究下spring的源代码,相信会收货更多东西!