Spring框架

IOC(控制反转),以前我们自己new对象,现在创建的对象的控制权交给了Spring容器(Map),将要创建类配置到Spring中,然后通过反射实例化对象。可以大大的降低程序的耦合度。

AOP(面向切面编程),就是在不改变代码的前提下,动态的添加一些功能。

1.classpath下的配置文件application.xml中配置bean

<bean id="userDao" class="com.doit.spring.dao.MybatisUserDaoImpl"></bean>

com.doit.spring.dao.MybatisUserDaoImpl是实现了UserDao接口的实现类的全路径

或者com.doit.spring.dao.MybatisUserDaoImpl是用component/Controller/Srevice/Repository注解修饰的即可,注解的作用:告诉Spring将该类管理起来

@Component("dBUtilsUserDaoImpl")
public class DBUtilsUserDaoImpl implements UserDao{

	@Override
	public User getById(Long id) {
		System.out.println("使用DBUtils查找用户");
		return null;
	}

	@Override
	public User getByNameAndPassword(User user) {
		// TODO Auto-generated method stub
		return null;
	}
}

2.在主程序中获取对象

//读取配置文件中的class的名字创建对象

//1.new ApplicationContext对象,参数:配置文件名
//该对象相当于是容器的引用
ApplicationContext application = new ClassPathXmlApplicationContext("application.xml");
		
//2.根据bean的id或name在spring的容器中查找(Map),赋值给接口类,实现多态
UserDao userDao1 = (UserDao) application.getBean(name);

//3.根据类型在Spring容器中查找
UserDao userDao2 = application.getBean(UserDao.class);

//4.如果该类型在容器中不止一个,可根据id+类名查找
UserDao userDao3 = application.getBean(name,UserDao.class);

3.通过setter方法设置对象(数据库连接池)属性值,为了让属性值不写死,从配置文件中读取设置

<bean id="dataPool" class="com.doit.spring.utils.DataPool">
		<!-- 调用set方法将对应的属性设置值 -->
		<property name="driverName" value="com.mysql.jdbc.Driver" />
		<property name="driverUrl" value="jdbc:mysql://localhost:3306/mybaits" />
		<property name="username" value="root" />
		<property name="password" value="root" />
		<property name="initSize" value="20" />	
</bean>

 DataPool类必须实现setter方法

public class DataPool {

	private String driverName;
	
	private String driverUrl;
	
	private String username;
	
	private String password;
	
	private int initSize;

	public String getDriverName() {
		return driverName;
	}

	public void setDriverName(String driverName) {
		this.driverName = driverName;
	}

	public String getDriverUrl() {
		return driverUrl;
	}

	public void setDriverUrl(String driverUrl) {
		this.driverUrl = driverUrl;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public int getInitSize() {
		return initSize;
	}

	public void setInitSize(int initSize) {
		this.initSize = initSize;
	}
}

4.通过构造器设置属性值

public class DataPool2 {

	private String driverName;
	
	private String driverUrl;
	
	private String username;
	
	private String password;
	
	private int initSize;
	
	
	//如果类的创建交给spring,我们添加了一个有参数的构造器,那么必须添加一个无参的构造器
	public DataPool2() {
		
	}

	public DataPool2(String driverName, String driverUrl, String username, String password, int initSize) {
		super();
		this.driverName = driverName;
		this.driverUrl = driverUrl;
		this.username = username;
		this.password = password;
		this.initSize = initSize;
	}
}

5.web三层框架介绍

Controller:接受请求,响应结果

Service:处理业务逻辑

DAO:数据的增删改查

6.注入引用类型

DI(Dependency Injection)依赖注入,一个类(Service)的实例引用了另一个类(DAO),如果Service在实例化时,DAO没有值(null),Service就不能调用DAO了。可以通过Spring将DAO注入到Service中,在Service中添加一个DAO的成员变量和相应的set方法,在Spring配置文件中添加<proprety name='',ref=''>,注入该引用类bean。

<!-- 在UserServiceImpl中有userDao成员属性,需要注入引用的bean -->
<bean id="userService" class="com.doit.spring.service.UserServiceImpl">
	<property name="userDao" ref="userDao"></property>
</bean>
public class UserServiceImpl implements UserService{

	private UserDao userDao;
	
	//实现登录功能
	@Override
	public User login(User user) {
		//查找数据库
		
		return userDao.getByNameAndPassword(user);
	}
	public UserDao getUserDao() {
		return userDao;
	}
	
	//要在配置文件中注入userDao引用,必须实现setter方法
	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}
}

实现自己的Spring-IOC

1.定义接口及其实现类

dao层

public interface UserDao {

}

 

public class UserDaoImpl implements UserDao {
	
}

service层

public interface UserService {
	
}
import com.doit.dao.UserDao;

public class UserServiceImpl implements UserService {
	private UserDao userDao;

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}
}

2.保存beans的容器:

在configs源码包下新建beans.xml文件,在该文件中注册bean

<?xml version="1.0" encoding="UTF-8"?>
<beans>
	<!-- scope="singleton":单例(全局一份)-->
	<!-- scope="prototype":多例(每次调用都创建新的) -->
	<bean id="userDao" class="com.doit.dao.UserDaoImpl" scope="singleton">
	</bean>
	
	<bean id="userService" class="com.doit.service.UserServiceImpl" scope="singleton">
		<property name="userDao" ref="userDao"></property>
	</bean>
	
	<bean id="userAction" class="com.doit.action.UserAction" scope="prototype">
		<property name="userService" ref="userService"></property>
	</bean>
</beans>

3.实现类似Spring中ClassPathXmlApplicationContext的接口及其实现类MyClassPathXmlApplicationContext

功能:解析配置文件,getBean()可以通过反射将类的全路径名实例化对象

public interface MyApplicationContext {
	
	public Object getBean(String name);
}
public class MyClassPathXmlApplicationContext implements MyApplicationContext {

	private HashMap<String,String> id2FullName = new HashMap<String,String>();
	
	//HashMap<String, Object>保存该配置文件中所有bean对应的类的对象的容器
	private HashMap<String, Object> id2Instance = new HashMap<String, Object>();

	public MyClassPathXmlApplicationContext(String fileName) {
		// 读取配置文件,并解析XML
		SAXReader reader = new SAXReader();
		try {
			// 使用SAXReader读取xml配置文件返回DOM对象
			Document document = reader.read(this.getClass().getClassLoader().getResourceAsStream(fileName));
			
			// 解析配置文件
			List<Element> eles = document.selectNodes("/beans/bean");
			
			// 迭代查找到的节点,每个节点就是一个bean
			for (Element e : eles) {
				//bean的id
				String id = e.attributeValue("id");
				
				//获取全路径名
				String className = e.attributeValue("class");
				String scope = e.attributeValue("scope");
				// System.out.println("id:" + id + " className:" + className + "
				// scope:" + scope);
				
				// 判断是否是单例
				if ("singleton".equals(scope)) {
					try {
						// 获取该类的Class对象
						Class<?> clazz = Class.forName(className);
						
						//新建该类的实例对象
						Object instance = clazz.newInstance();
						
						//将该对象放到大容器中,即HashMap,key为bean的id
						id2Instance.put(id, instance);

						// 获取该Element的子节点
						List<Element> proEles = e.selectNodes("property");
						for (Element pele : proEles) {
							//引用类对象名
							String name = pele.attributeValue("name");
							//引用类类名
							String ref = pele.attributeValue("ref");
							// System.out.println("name:" + name + " ref:" +
							// ref);

							// set+U+serDao,根据属性拼接方法名
							String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
							
							//获取引用对象,该对象已经被放到容器id2Instance中,从容器中获取
							Object arg = id2Instance.get(ref);
							
							//获取成员变量(引用类)
							Field declaredField = clazz.getDeclaredField(name);
							//获取成员变量(引用类)的类型
							Class<?> type = declaredField.getType();
							
							//获取成员方法(set方法,用来设置引用类对象)
							Method method = clazz.getMethod(methodName, type);
							
							//调用set方法给成员变量赋值
							//根据ref到map中查找已经创建好的引用实例
							method.invoke(instance, arg);
							System.out.println(instance);
						}
					} catch (Exception e1) {
						// 类找不到
						e1.printStackTrace();
					}
				}
			}
		} catch (DocumentException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
		}
	}

存在的问题:

配置文件要严格按照对象创建的顺序,被依赖的对象必须先实例化。。。

改进:先把遍历到的bean的各种信息保存起来,即封装为一个对象(beanProp)

public class MyClassPathXmlApplicationContext implements MyApplicationContext {

	// 保存xml配置文件中的的属性信息
	private Map<String, BeanProp> id2BeanProp = new HashMap<String, BeanProp>();
	private Map<String, Object> id2Instance = new HashMap<String, Object>();

	public MyClassPathXmlApplicationContext(String fileName) {
		try {
			// 1. 读取配置文件,并解析XML,然后将数据保存的BeanProp,再放入到map中
			SAXReader reader = new SAXReader();
			// 使用SAXReader读取xml配置文件返回DOM对象
			Document document = reader.read(this.getClass().getClassLoader().getResourceAsStream(fileName));
			// 解析配置文件
			List<Element> eles = document.selectNodes("/beans/bean");
			// 迭代查找到的节点
			for (Element e : eles) {
				String id = e.attributeValue("id");
				String className = e.attributeValue("class");
				String scope = e.attributeValue("scope");
				
				//封装bean标签中的属性值,封装成BeanProp对象
				BeanProp beanProp = new BeanProp(id,className,scope);
				
				//先获取beanProp中的存放多个引用类对象的map
				Map<String, String> refMap = beanProp.getProperties();
				
				//遍历该Element的子节点,引用类对象,存放到上述map容器中key-value=name-ref
				List<Element> proEles = e.selectNodes("property");
				for (Element pele : proEles) {
					String name = pele.attributeValue("name");
					String ref = pele.attributeValue("ref");
					refMap.put(name, ref);
				}
				
				// 将bean的所有属性保存到一个map中key-value=id-beanProp
				id2BeanProp.put(id, beanProp);
			}
			
			// 2.遍历id2BeanProp,然后将单例的bean实例化
			//new一个保存key-value的集合
			Set<Entry<String,BeanProp>> entrySet = id2BeanProp.entrySet();
			//2.1遍历每一个beanProp
			for (Entry<String, BeanProp> entry : entrySet) {
				String id = entry.getKey();
				BeanProp bp = entry.getValue();
				
				//2.2通过反射实例化单例对象,scope属性为空也是单例
				if(bp.getScope()==null||"singleton".equals(bp.getScope())){
					//获取该bean的全路径名
					//通过反射实例化对象
					String className = bp.getFullName();
					Class<?> clazz = Class.forName(className);
					Object instance = clazz.newInstance();
					
					//2.3注入依赖
					Map<String, String> properties = bp.getProperties();
					// 获取到依赖的keys
					Set<String> keySet = properties.keySet();
					//遍历每个依赖类
					for (String propName : keySet) {
						// 获取引用名
						String ref = properties.get(propName);
						
						// 通过get方法获取引用的实例
						Object refInstance = id2Instance.get(ref);
						
						// 如果为null,说明依赖还没创建
						if(refInstance==null){
							BeanProp refbeanProp = id2BeanProp.get(ref);
							
							// 得到依赖的全类名
							String refBeanName = refbeanProp.getFullName();
							
							// 通过反射实例化依赖
							Class<?> refClazz = Class.forName(refBeanName);
							
							// 创建实例
							refInstance = refClazz.newInstance();
							
							//判断依赖是不是单例,如果是单例,就把它保存起来
							//多例则不需要保存,每次都创建新的
							if(refbeanProp.getScope()==null||"singleton".equals(refbeanProp.getScope())){
								id2Instance.put(ref, refInstance);
							}
						}
						
						// 通过反射给成员变量赋值
						Field declaredField = clazz.getDeclaredField(propName);
						
						// 暴力反射
						declaredField.setAccessible(true);
						declaredField.set(instance, refInstance);
					}
					id2Instance.put(id, instance);
				}
			}
		} catch (Exception e2) {
			e2.printStackTrace();
		}
		// 创建一个大容器(Map)

		// 将配置文件中的全类通过反射实例化

		// 将实例化好的对象放入到容器中(Map)

	}

	@Override
	public Object getBean(String name) {
		// 根据名字到容器中取
		Object instance = null;
		try{
			instance = id2Instance.get(name);
			//如果没有找到,说明该bean是多例的
			if(instance==null&&id2BeanProp.containsKey(name)){
				//通过反射实例化该类对象
				BeanProp beanProp = id2BeanProp.get(name);
				String className = beanProp.getFullName();
				Class<?> clazz = Class.forName(className);
				instance = clazz.newInstance();
				
				// 获取依赖信息并注入依赖
				Map<String, String> properties = beanProp.getProperties();
				
				// 获取到依赖keys
				Set<String> keySet = properties.keySet();
				for (String propName : keySet) {
					// 获取引用名
					String ref = properties.get(propName);
					// 获取引用的实例
					Object refInstance = id2Instance.get(ref);
					// 如果为null,说明依赖还没创建
					if (refInstance == null) {
						// 实例化依赖
						BeanProp refBeanProp = id2BeanProp.get(ref);
						// 得到依赖的全类名
						String refBeanName = refBeanProp.getFullName();
						// 通过反射实例化依赖
						Class<?> refClazz = Class.forName(refBeanName);
						// 创建实例
						refInstance = refClazz.newInstance();
					}
					// 通过反射给成语变量赋值
					Field declaredField = clazz.getDeclaredField(propName);
					// 暴力反射,注入依赖
					declaredField.setAccessible(true);
					declaredField.set(instance, refInstance);
				}
			}
			
		}catch (Exception e) {
			e.printStackTrace();
		}
		return instance;
	}

}

7.使用注解方式实现IOC和DI

1.在Spring的配置文件application.xml中使用<context:component-scan>指定注解扫描的根包名(base-package)

<context:component-scan base-package="com.doit"></context:component-scan>

2.可以使用SpringMVC注解向Spring注册bean

Controller层一般使用:Controller

Service层一般使用:Service

DAO层一般使用:Repository

使用component亦可。。。

在依赖的成员对象上使用Autowire注解,按类别引入依赖对象

在依赖的成员对象上使用Resource注解,按ID引入依赖对象

@Repository
public class UserDaoImpl implements UserDao {
	
}
@Service
public class UserServiceImpl implements UserService {
	
	//Autowired是按类型注入
	@Autowired
	private UserDao userDao;
}
/**
 * 使用的是SpringMVC,其Controller是单例的
 * @author gefeng
 *
 */
@Controller("userAction")
public class UserAction {
	
	@Resource
	private UserService userService;
}

Spring-AOP(Aspect Oriented Programing)

在程序执行的某一个时机,把程序切一刀,添加一些功能,实现在不改变原来代码的前提下,对一些方法进行增强

原理:动态代理

装饰模式:

代理对象和目标对象实现同一个接口,代理对象(proxy)持有对目标对象(target)的引用,可在代理对象中对目标对象的方法进行增强。

接口

public interface IFace {

	public String invoke(String name);
}

目标类

public class Target implements IFace {

	@Override
	public String invoke(String name) {
		return "Hello " + name;
	}

}

代理类

public class Proxy implements IFace{

	private Target target;
	
	public Proxy(Target target){
		this.target=target;
	}
	
	@Override
	public String invoke(String name) {
		 String uname = name.toUpperCase();
		 String rname = target.invoke(uname);
		 return rname+"很帅!";
	}
}

 

JDK动态代理

接口

public interface UserService {

	public void update(String name);
	
	public void delete(String name);
	
	public Long getIdByName(String name);
}

实现了接口的目标类

public class UserServiceImpl implements UserService {

	@Override
	public void update(String name) {
		System.out.println("调用数据库更新!");
	}

	@Override
	public void delete(String name) {
		System.out.println("调用数据库删除!");

	}

	@Override
	public Long getIdByName(String name) {
		System.out.println("调用数据库查找!");
		return 1000L;
	}

}

通过反射机制实现对目标对象增强的增强类

public class EnhanceHandler implements InvocationHandler{

	private Object target;
	
	public EnhanceHandler(Object target) {
		this.target = target;
	}
 	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//增强功能
		preDo();
		
		//通过反射调用目标类target的method方法
		Object obj = method.invoke(target, args);
		
		//增强功能
		postDo();
		return obj;
	}
	

	private void preDo() {
		System.out.println("预处理,开启事务!");
	}
	
	private void postDo() {
		System.out.println("后处理,提交事务!");
	}

	public Object getProxy() {
		//第一个参数是要增强对象的类(目标类)加载器
		//第二个参数是目标类的所有接口
		//第三个参数是实现了InvocationHandler的一个实现类的实例
		
		//newProxyInstance创建目标对象的代理对象
		return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}
	
}

总结:

  1. 如果使用JDK的动态代理对目标对象进行增强,那么目标对象必须实现接口
  2. 生成的代理对象,必须用该增强的方法所在的接口引用

基于Spring框架的AOP

1.在Spring的配置文件applicationContext.xml中添加aop相关

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
xmlns:aop="http://www.springframework.org/schema/aop"

2.接口

public interface UserService {

	public void save(User user);
	
	public void update(User user);
}

3.目标实现类

public class UserServiceImpl implements UserService {

	@Override
	public void save(User user) {
		
		System.out.println("调用DAO将数据保存的数据库");
		
	}

	@Override
	public void update(User user) {
		
		System.out.println("调用DAO将数据更新");
		
	}
}

4.增强类

public class EnhanceAdvice {

	public void preDo(){
		System.out.println("预处理,开启事务!");
	}
	
	public void postDo(){
		System.out.println("后处理,提交事务!");
	}
}

5.通过配置文件将目标类和增强类交给Spring控制

<!-- 目标类要交给Spring容器管理,如果使用的是JDK动态代理就必须实现接口
		 到底这个类的方法会不会被增强,取决于aop的表达式
-->
<bean id="userSerivce" class="com.doit.service.UserServiceImpl" />
	
<!-- advice是 功能增强方法所在的类 -->
<bean id="enhanceAdvice" class="com.doit.advice.EnhanceAdvice" />

6.在配置文件中配置AOP的切面(aspect)和切入点(pointcut)

<!-- 配置AOP功能,实现动态目标类进行方法增强 -->
	<aop:config>
	
	<!-- 切面要引用advice -->
		<aop:aspect ref="enhanceAdvice">
		<!-- aop:pointcut-切入点,就是确定在哪个类的哪个方法上增强功能 -->
			<!-- 表达式决定了在哪里切,execution(* cn.edu360.service..*.s*(..)) -->
			<!-- 上面的表达式:cn.edu360.service这个包及其子包下面的所有类,以s开头的方法,方法的参数不限,返回值不限 -->
			<aop:pointcut expression="execution(* com.doit.service.*.*(..))" id="pc"/>
			
			<!-- 在切入点pc,增加增强功能的时机 -->
			<aop:before method="preDo" pointcut-ref="pc"/>
			<aop:after method="postDo" pointcut-ref="pc"/>
		</aop:aspect>
		
	</aop:config>

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值