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);
}
}
总结:
- 如果使用JDK的动态代理对目标对象进行增强,那么目标对象必须实现接口
- 生成的代理对象,必须用该增强的方法所在的接口引用
基于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>