文章目录
前言
SSM 框架:是 Spring + Spring MVC + MyBatis 的缩写,这个是SSH之后目前比较主流的 Java EE企业级框架,适用于搭建各种大型的企业级应用系统。做框架和使用框架的目的,就是为了提高开发效率,执行效率次之。
1、Spring
Spring 的本质核心是创建和管理应用程序的对象,因为代码中是抽象耦合,那么把 new 具体类对象放在 Spring 框架中管理,通过 Spring 框架根据用户的需求,把对象注入到需要的地方,相当于在代码中没有使用任何具体子类的实现。
耦合的分类
- 无 / 零耦合:类和类之间没有任何关系。
- 抽象耦合:依赖的抽象,在本类中耦合另一个类的抽象(UserDao userDao)。
- 具体耦合:依赖的具体类,在本类中耦合的是另一个具体的类(UserDaoImpl userDao)。
1.1、Spring IOC 控制反转
就是把 new 对象不放在具体类中去 new,把 new 对象的控制权反转给第三方 Spring 容器去 new 对象。
1.1.1、Spring 容器初始化
- 本地文件系统的方式
ApplicationContext context = new FileSystemXmlApplicationContext(“c:/spring.xml”); - 类路径的方式
AbstractApplicationContext context = new ClassPathXmlApplicationContext(new String[] {“conf/spring.xml”})
注意
AbstractApplicationContext context = new ClassPathXmlApplicationContext()会从类路径根目录自动寻找applicationContext.xml文件。
1.1.2、Spring 容器中获取对象
- getBean(String)根据 id 获取容器中的对象,需要强制转换。
- getBean(Class)根据类的类型获取容器中的对象,Class类型在容器中唯一,不需要强制转换。
- getBean(String,Class)根据 id 和类同时获取具备获取容器中的对象,id在容器中必须唯一。
1.1.3、Spring IOC 原理
- 启动容器 Spring 正确加载和解析 Spring 的 xml文件。
- .把 Spring 的 xml 中的 bean 节点的内容解析出来储存给 map 集合。
- 循环遍历map集合中所有的数据 ,取出 class 属性的值,通过反射实例化对象。
Object obj=Class.forName(“包名.类名”).newInstance() - 把创建完的对象储存到另一个map集合中,用bean的 id 作为 key,对象做为value。
- 如果属性需要注入,spring框架就会帮程序员注入数据。
1.1.4、Spring 容器中的 bean 生命周期
XML 文件 bean 节点内容
<bean id="yang" init-method="myInit" destroy-method="myDestroy" class="com.Yang">
<property name="number">
<value>1212</value>
</property>
</bean>
Yang类
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class Yang implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
private String number;
public Yang() {}
public void setNumber(String number) {
this.number = number;
}
public void myInit() {
}
public void myDestroy() {
}
@Override
public void destroy() throws Exception {
// TODO Auto-generated method stub
}
@Override
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// TODO Auto-generated method stubapplicationContext
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// TODO Auto-generated method stub
}
@Override
public void setBeanName(String beanName) {
// TODO Auto-generated method stub
}
}
验证生命周期流程
System.out.println("初始化容器");
AbstractApplicationContext test = new ClassPathXmlApplicationContext(new String[]{"config/spring_factory.xml"});
System.out.println("关闭容器");
test.close();
结论
- 调用 Bean 构造方法实例化 Bean。
- 利用依赖注入完成 Bean 中所有属性值的注入。
- 如果 Bean 实现了 BeanNameAware 接口,则调用 setBeanName() 方法传入当前 Bean 的 id。
- 如果 Bean 实现了 BeanFactoryAware 接口,则调用 setBeanFactory() 方法传入当前工厂实例的引用。
- 如果 Bean 实现了 ApplicationContextAware 接口,则 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
- 如果 BeanPostProcessor 和 Bean 关联,则调用 postProcessBeforeInitialzation() 方法对 Bean 进行加工操作,Spring 的 AOP 就是利用它实现的。
- 如果 Bean 实现了 InitializingBean 接口,则调用 afterPropertiesSet() 方法。
- 如果 在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
- 如果 BeanPostProcessor 和 Bean 关联,则调用 postProcessAfterInitialization() 方法。此时,Bean 已经可以被应用系统使用了。
- 如果在XML中指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入缓存池中,将触发 Spring 对该 Bean 的生命周期管理,如果指定了该 Bean 的作用范围为 scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
- 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁。
- 或者在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁(同11一样效果)。
1.1.5、Spring 对象单例和多例
单例: 所有请求用同一个对象来处理,通过单例模式,可以保证系统中一个类只有一个实例。
多例:每一个请求用一个新的对象来处理。
Spring ioc 容器的 bean 都是默认单例的,即 Spring 依赖注入Bean 实例默认都是单例的。
单例和多例优缺点
单例优点降低了实例创建和销毁所占用的资源,缺点线程共享一个实体,会发生线程安全问题。
多例线程之间数据隔离,所以不会有线程安全问题,但是频繁的实例创建和销毁会带来资源的大量浪费。
单例样例
<bean id="hello" class="com.ioc.Hello" ></bean>
等同于
<bean id="hello" class="com.ioc.Hello" scope="singleton"></bean>
多例样例
<bean id="hello" class="com.ioc.Hello" scope="prototype"></bean>
1.1.6、Spring 容器对象的懒加载(控制对象的创建的时机)
如果Spring容器创建,对象立即创建,则称为立即加载。
如果 Spring 容器创建,对象在被使用的时候创建,则称为懒加载。
注解:@Lazy 表示为懒加载
懒加载XML写法
<!-- 懒加载的局部的写法 -->
<bean id="hello" class="com.ioc.Hello" scope="singleton" lazy-init="true"></bean>
<!-- 懒加载的全局的写法 -->
<beans default-lazy-init="true"></beans>
lazy-init 是否懒加载 | scope 单例多例 | 对象的创建结果 |
---|---|---|
true | singleton | 懒加载 |
true | prototype | 懒加载 |
default/flase | singleton | 立即加载 |
default/flase | prototype | 懒加载 |
只要对象是多例模式 则都是懒加载! 在单例模式中控制懒加载才有效。
1.1.7、Spring 容器实例化和管理对象的四种方式
Hello 类
public class Hello {
public Hello() {
System.out.println("Hello()");
}
}
- 通过无参构造函数实例化对象
直接通过 Hello 类无参构造创建。
<bean id="hello" class="com.ioc.Hello"></bean>
特点:把创建的对象放在 Spring 容器中,Spring 帮你创建和管理对象。
- 静态工厂
所谓的静态工厂,通过类的名字调用类中的静态方法,由静态方法生产一个对象,把生产出来的对象交给 Spring 容器来管理,class 是一个类,可以是抽象类,可以是普通类,factory-method 一定是类中静态方法。
public class StaticFactory_Hello {
/**
* 这是一个静态工厂
*/
public static Hello getObject(){
return new Hello();
}
}
<bean id="hello" class="com.StaticFactory_Hello" factory-method="getObject"></bean>
特点:对象是被动渠道创建的,但是交给了 Spring 来管理。
- 实例工厂
所谓的实例工厂,生产对象的方法一定是非静态的,首先要创建一个实例工厂的对象,class类不能是抽象类,通过对象来调用非静态的方法。
public class Instance_Factory {
/**
* 这是一个实例工厂
*/
public Hello getObject() {
return new Hello();
}
}
特点:对象是被动渠道创建的,但是交给了 Spring 来管理。
<bean id="if" class="com.Instance_Factory"></bean>
<bean id="hello" class="com.Instance_Factory" factory-bean="if" factory-method="getObject"></bean>
- Spring 工厂
所谓的 Spring 工厂要求必须实现FactoryBean接口,自动调用重写getObject方法,此方法返回一个对象并且一定要返回一个对象,然后把对象放置到 Spring 容器中。
<bean id="hello" class="com.SpringFactory"></bean>
特点:对象是被动渠道创建的,但是交给了 Spring 来管理。
1.2、Spring DI 依赖注入
从spring容器中取出容器中的对象,然后把对象注入到需要的地方
1.2.1、注入方式
1.2.1.1、setter方式
bean 节点用来告知 Spring 实例化或管理对象,property 节点用来告知 Spring 有对象注入关系,相当于告知spring要做对象的关系管理。每一个property 节点都必须对应一个 setter 方法 和 name 属性,把 name 属性的值的第一个字母大写,前面加上set构建出字符串,拿这个字符串去 类中寻找是否有此名称的方法 ,如果有setter方法,就反射调用这个setter方法。
例如:name = “userDao” 则为 userDao -> UserDao -> setUserDao
- 对象注入
ref 是要引入对象,ref 引用的对象,一定是引自 Spring 容器且是容器中的唯一的id。
<bean id="userDao" class="userdao.UserDaoImpl"></bean>
<!-- 对象注入 -->
<bean id="service" class="service.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
- 单值注入
<bean id="value" class="entity.Value">
<property name="age" value="25"></property>
<property name="address" value="天津"></property>
</bean>
- 集合注入(直接结合注入和间接集合注入)
直接结合注入
<bean id="hello" class="entity.Hello"></bean>
<bean id="message" class="entity.Collection">
<property name="list">
<list>
<value>北京</value>
<value>上海</value>
<value>广州</value>
<ref bean="hello"></ref>
</list>
</property>
<property name="set">
<set>
<value>北京</value>
<value>上海</value>
<value>广州</value>
<ref bean="hello"></ref>
</set>
</property>
<property name="map">
<map>
<entry key="bj" value="北京"></entry>
<entry key="sh" value="上海"></entry>
<entry key="gz" value="广州"></entry>
<entry key="h" value-ref="hello"></entry>
</map>
</property>
<property name="props">
<props>
<prop key="bj">北京</prop>
<prop key="sh">上海</prop>
<prop key="gz">广州</prop>
</props>
</property>
</bean>
间接集合注入,集合的对象要交给spring容器来管理
<bean id="hello" class="entity.Hello"></bean>
<util:list id="uList">
<value>北京</value>
<value>上海</value>
<value>广州</value>
</util:list>
<util:set id="uSet">
<value>北京</value>
<value>上海</value>
<value>广州</value>
</util:set>
<util:map id="uMap">
<entry key="bj" value="北京"></entry>
<entry key="sh" value="上海"></entry>
<entry key="gz" value="广州"></entry>
<entry key="h" value-ref="hello"></entry>
</util:map>
<util:properties id="uProps">
<prop key="bj">北京</prop>
<prop key="sh">上海</prop>
<prop key="gz">广州</prop>
</util:properties>
<bean id="message" class="entity.Collection">
<property name="list" ref="uList"></property>
<property name="set" ref="uSet"></property>
<property name="map" ref="uMap"></property>
<property name="props" ref="uProps"></property>
</bean>
- 表达式注入
必须有属性文件,表达式注入就是把属性文件通过 Spring 注入给某个对象中。
mysql.properties
jdbc_driverClass=com.mysql.jdbc.Driver
jdbc_url=jdbc:mysql://localhost:3306/tesdb
jdbc_userName=root
jdbc_userPassword=root
mysql.propertses
属性注入的方式有两种
(1)用 ${} 方式 location属性可以放置多个属性文,用,间隔,建议用类路径classpath取,仅针对spring框架。
<!-- 把属性文件的数据加载到spring容器中 -->
<context:property-placeholder location="classpath:config/mysql.properties,classpath:config/page.properties" />
<bean id="jdbcUtil" class="expression.JDBCUtil">
<property name="driverClass" value="${jdbc_driverClass}"></property>
<property name="url" value="${jdbc_url}"></property>
<property name="username" value="${jdbc_userName}"></property>
<property name="userpassword" value="${jdbc_userPassword}"></property>
</bean>
(2)用 #{} 方式,逗号间隔,必须给一个id 把属性文件的数据存储给spring容器,容器以manyProperty,值是若干属性键值对。
<util:properties id="manyProperty" location="classpath:config/mysql.properties,classpath:config/page.properties" />
<bean id="jdbcUtil" class="expression.JDBCUtil">
<property name="driverClass"
value="#{manyProperty.jdbc_driverClass}"></property>
<property name="url" value="#{manyProperty.jdbc_url}"></property>
<property name="username"
value="#{manyProperty.jdbc_userName}"></property>
<property name="userpassword"
value="#{manyProperty.jdbc_userPassword}"></property>
</bean>
- 空值注入
<bean id="kong" class="kong.Kong">
<property name="str1" value=""></property>
<property name="str2">
<null></null>
</property>
</bean>
1.2.1.2、构造函数的方式
public class Constructor {
private Hello hello;
private String name;
public Constructor(Hello hello, String name) {
super();
this.hello = hello;
this.name = name;
}
@Override
public String toString() {
return "Constructor [hello=" + hello + ", name=" + name + "]";
}
}
<bean id="hello" class="entity.Hello"></bean>
<bean id="ci" class="constructor.Constructor">
<constructor-arg index="0" ref="hello"></constructor-arg>
<constructor-arg index="1" value="杨"></constructor-arg>
</bean>
1.3、Spring AOP 面向切面的编程
有一个需求:原有的业务没做任何的修改,添加额外的新功能。
实现需求方式:
- 在原有的代码的前面添加新的功能,在原有的代码的后面添加新的功能,这是直接修改源代码,破坏开闭原则(对扩展开放,对修改关闭),单一职责。
- 用代理设计模式来实现,原有的业务代码能够正常的执行,也没有修改源代码,通过代理设计模式能够实现在原有的功能前面或后面添加新的功能。
1.3.1、代理的分类
1.3.1.1、静态代理
1.3.1.1.1、静态代代码实现
import other.TransactionManager;
import service.UserService;
// 静态代理类组合新老功能
public class StaticProxy implements UserService {
// 业务原有的功能
private UserService userService;
// 新的业务功能
private TransactionManager tm;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void setTm(TransactionManager tm) {
this.tm = tm;
}
@Override
public Boolean addUser(User user) {
try {
tm.begin();
// 调用老的业务功能
userService.addUser(user);
tm.commit();
} catch (Exception e) {
tm.rollback();
e.printStackTrace();
}
return null;
}
}
1.3.1.1.2、静态代理的总结
- 新建了一个静态的代理类,把原有的业务和新的业务耦合在一起。
- 静态代理类要实现业务的接口,因为要保证业务的完整性。
- StaticProxy是静态代理类,此类的对象是代理对象。
- 静态代理类在编译期间就已经确认原有的业务和新的业务的耦合模型。
- 优点是执行效率高,缺点是开发效率低(有多少个业务类就有多少个静态代理类)。
1.3.1.1.2、衍生出来的若干名词
(1)旧的业务模型:目标类UserServiceImpl 和 目标对象 new UserServiceImpl()。
(2)新的业务模型:切面类(新功能类)和 切面对象(用新的功能类实例化的对象)。
(3)横切:把新的业务横切到老的业务方法上。
(4)耦合:老的业务和新的业务的结合。
(5)代理类:在代理类中体现出老业务和新业务的耦合(Procy)。
(6)代理对象:使用代理类实例化的对象(proxyObject)。
1.3.1.2、动态代理(jdk动态代理 和 cglib动态代理)
动态代理简单来说就是在程序执行过程中,创建代理对象,通过代理对象执行方法,给目标类的方法增加额外的功能,也叫做功能增强。
1.3.1.2.1、jdk 动态代理
jdk 动态代理要求目标类必须有接口,jdk 代理类是目标类的兄弟类。
1.3.1.2.1.1、JDK 动态代理实现步骤
- 首先需要有一个目标类,在目标类的基础上通过动态代理实现功能增强。
- 创建InvocationHandler接口的实现类,在这个类中实现invoke方法,在invoke方法中实现给目标类的方法增强功能。
- 通过JDK中的Proxy创建代理对象,通过代理对象调用目标类中的方法,实现功能增强。
1.3.1.2.1.2、JDK 动态代理代码实现
1.创建一个接口
public interface UserDao {
public Integer addUser(User user);
}
2.创建接口的实现类也就是目标类
public class UserDaoImpl implements UserDao {
@Override
public Integer addUser(User user) {
System.out.println("UserDaoImpl.addUser()");
return 1;
}
}
3.创建需要耦合的新功能类
public class TransactionManager {
public void begin() {
System.out.println("开始业务");
}
public void commit() {
System.out.println("提交业务");
}
public void rollback() {
System.out.println("回滚业务");
}
}
4.创建InvocationHandler接口的实现类,在此方法中耦合老的业务和新的业务功能
public class TransactionHandler implements InvocationHandler {
//目标对象
private Object targetObject;
//有参构造
public TransactionHandler(Object targetObject) {
this.targetObject = targetObject;
}
/**
* @param proxy 代理对象
* @param method 目标方法 老的业务方法
* @param args 老的业务方法的参数
* @return
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
// 实例化新的业务功能对象
TransactionManager tm = new TransactionManager();
try {
//执行新的业务功能
tm.begin();
//执行老的业务功能,用反射的方式
returnValue = method.invoke(targetObject, args);
//执行新的业务功能
tm.commit();
} catch (Exception e) {
//执行新的业务功能
tm.rollback();
e.printStackTrace();
}
return returnValue;
}
}
5.通过Proxy创建代理对象调用目标方法
@Test
public void testMethod3() {
// 实例化老的业务对象(目标类对象)
UserDao targetObject = new UserDaoImpl();
// 创建InvocationHandler对象
InvocationHandler handler = new TransactionHandler(targetObject);
/**
* jdk类库中有一个类Proxy,其中有一个静态方法newProxyInstance,此方法反回一个代理对象
* 参数一:类加载器,为了定位类路径
* 参数二:目标对象的所有接口数组
* 参数三:是一个类的对象,此类必须实现自InvocationHandler接口,在InvocationHandler接口的接口方法中耦合老业务和新业务功能
*/
// 创建Proxy代理对象
Object proxyObject = Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), handler);
// 代理对象强制转换成接口类型
UserDao UserDao = (UserDao)proxyObject;
// 用代理对象调用目标方法,用代理对象调用目标方法事实上执行的是InvocationHandler接口方法
UserDao.addUser(new User());
}
6.结果
开始业务
UserDaoImpl.addUser()
提交业务
1.3.1.2.2、cglib 动态代理
cglib是第三方的工具 jar 包提供的用来生成cglib代理对象,cglib要求目标类有无接口皆可,但必须要求目标类不能是 final 类,因为cglib的代理类是目标类的子类。
1.3.1.2.2.1、cglib 动态代理实现步骤
- 首先需要有一个目标类,在目标类的基础上通过动态代理实现功能增强。
- 创建MethodInterceptor接口的实现类,在这个类中实现intercept方法,在intercept方法中实现给目标类的方法增强功能。
- 通过Enhancer的create创建代理对象,通过代理对象调用目标类中的方法,实现功能增强。
1.3.1.2.2.2、cglib 动态代理代码实现
1.创建一个接口
public interface UserService {
public Boolean addUser(User user);
}
2.创建接口的实现类也就是目标类
public class UserServiceImpl implements UserService {
@Override
public Boolean addUser(User user) {
System.out.println("UserServiceImpl.addUser()");
return true;
}
}
3.创建需要耦合的新功能类
public class TransactionManager {
public void begin() {
System.out.println("开始业务");
}
public void commit() {
System.out.println("提交业务");
}
public void rollback() {
System.out.println("回滚业务");
}
}
4.创建MethodInterceptor接口的实现类,在此方法中耦合老的业务和新的业务功能
public class TransactionHandler implements MethodInterceptor {
//目标对象
private Object targetObject;
//有参构造
public TransactionHandler(Object targetObject) {
this.targetObject = targetObject;
}
/**
* @param proxy cglib的代理对象
* @param method 代理对象调用的目标方法,Method是java原生反射的类型
* @param args 目标方法的参数
* @param methodProxy 代理对象调用的目标方法,MethodProxy是cglib封装过
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodproxy) throws Throwable {
Object returnValue = null;
TransactionManager tm = new TransactionManager();
try {
// 执行新的业务功能
tm.begin();
// 用原生的反射调用老的业务方法
// returnValue = method.invoke(targetObject, args);
// 用cglib封装类来调用老的业务方法,其底层还是原生的反射api
returnValue = methodproxy.invoke(targetObject, args);
// 执行新的业务功能
tm.commit();
} catch (Exception e) {
// 执行新的业务功能
tm.rollback();
e.printStackTrace();
}
return returnValue;
}
5.通过CGLIBProxy创建代理对象调用目标方法
@Test
public void testMethod3() {
// 实例化老的业务对象(目标类对象)
UserService targetObject = new UserServiceImpl();
// 创建MethodInterceptor对象
MethodInterceptor handler = new TransactionInterceptor(targetObject);
// Enhancer
Enhancer enhancer = new Enhancer();
// 设置代理类的父类是谁,目标类是代理类的父类
enhancer.setSuperclass(targetObject.getClass());
// 必须重写MethodInterceptor接口中的intercept方法,耦合老和新的业务
enhancer.setCallback(handler);
// 创建CGLIBProxy代理对象
Object proxyObject = enhancer.create();
// 代理对象强制转换成接口类型
UserService UserService = (UserService)proxyObject;
// 用代理对象调用目标方法,用代理对象调用目标方法事实上执行的是InvocationHandler接口方法
UserService.addUser(new User());
}
6.结果
开始业务
UserServiceImpl.addUser()
提交业务
1.3.1.3、jdk动态代理 和 cglib动态代理的区别
- jdk动态代理目标业务类必须有接口,cglib动态代理业务类有无接口皆可。
- jdk动态代理必须实现InvocationHandler接口,cglib动态代理必须实现MethodInterceptor接口。
- jdk动态代理代理类和目标业务类是兄弟关系,因为隶属于同一个接口,cglib动态代理代理类和目标业务类是父子关系,业务类是父类,业务类不能是final类,代理类是子类。
- jdk动态代理创建代理类快,执行代理类慢,cglib动态代理创建代理类慢,执行代理类快。
1.3.2、面向切面编程
在 Spring 框架中有一个aop子框架,自己创建了一套独有的表达式语言,通过这些表达式语言,可以轻松控制业务类中某些方法横切或植入新功能,Spring AOP 底层用的就是动态代理,不用写动态代理代码。
目标类有实现业务接口就默认用jdk动态代理,目标类没有实现业务接口就用cglib,有业务接口也可以通过指定配置项来使用cglib。
1.3.2.1、Spring AOP 面向切面编程代码实现
1.创建一个接口
public interface UserService {
public Boolean addUser(User user);
}
2.创建接口的实现类也就是目标类
public class UserServiceImpl implements UserService {
@Override
public Boolean addUser(User user) {
System.out.println("UserServiceImpl.addUser()");
return true;
}
}
3.创建需要耦合的新功能类,切面类
public class TransactionManager {
// 前置通知
public void begin(JoinPoint jp) {
System.out.println("开始业务/前置通知 目标对象" + jp.getTarget());
System.out.println("开始业务/前置通知 目标方法" + jp.getSignature().getName());
System.out.println("开始业务/前置通知 目标方法的参数" + jp.getArgs()[0]);
}
// 后置通知
public void commit(JoinPoint jp, Object returnValue) {
System.out.println("提交业务/后置通知 目标对象" + jp.getTarget());
System.out.println("提交业务/后置通知 目标方法" + jp.getSignature().getName());
System.out.println("提交业务/后置通知 目标方法的参数" + jp.getArgs()[0]);
System.out.println("提交业务/后置通知 目标方法的返回值" + returnValue);
}
// 异常通知
public void rollback(JoinPoint jp, Throwable ex) {
System.out.println("回滚业务/异常通知 目标对象" + jp.getTarget());
System.out.println("回滚业务/异常通知 目标方法" + jp.getSignature().getName());
System.out.println("回滚业务/异常通知 目标方法的参数" + jp.getArgs()[0]);
System.out.println("回滚业务/异常通知 产生的异常信息" + ex.getMessage());
}
// 最终通知
public void finallyMethod(JoinPoint jp) {
System.out.println("最终通知 目标对象" + jp.getTarget());
System.out.println("最终通知 目标方法" + jp.getSignature().getName());
System.out.println("最终通知 目标方法的参数" + jp.getArgs()[0]);
}
// 环绕通知,环绕通知可以控制目标方法的执行,能用上面4种通知的可以用环绕替换,用环绕通知不一定能用4种替换
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object retVal = null;
try {
System.out.println("around:前置通知 TransactionManager");
retVal = pjp.proceed();
System.out.println("around:后置通知 TransactionManager");
} catch (Exception e) {
System.out.println("around:异常通知 TransactionManager");
e.printStackTrace();
} finally {
System.out.println("around:最终通知 TransactionManager");
}
return retVal;
}
}
4.XML 中 AOP 配置
<!--原有的业务对象 -->
<bean id="userService" class="service.UserServiceImpl"></bean>
<!-- 新的业务对象,切面类,切面对象 -->
<bean id="tm" class="other.TransactionManager"></bean>
<!-- spring独有的配置,启用spring aop,其底层是动态代理 -->
<aop:config proxy-target-class="false"> <!--true 强制使用cglib动态代理 -->
<aop:aspect id="myAspect" ref="tm">
<!-- 切入点-->
<aop:pointcut expression="execution(* service..*.*(..))" id="myPointcut"/>
<<!-- 前置通知 -->
<!-- <aop:before method="begin" pointcut-ref="myPointcut"/>-->
<!-- 后置通知 -->
<!-- <aop:after-returning method="commit" pointcut-ref="myPointcut"/>-->
<!-- 异常通知 -->
<!-- <aop:after-throwing method="rollback" pointcut-ref="myPointcut"/>-->
<!-- 最终通知 -->
<!-- <aop:after method="finallyMethod" pointcut-ref="myPointcut"/>-->
<!-- 环绕通知 环绕通知具备上面四种通知的功能,环绕通知可以控制目标方法的执行-->
<aop:around method="around" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
5.执行测试
@Test
public void testBefore() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("config/spring.xml");
// 不能是UserServiceImpl.class 因为 userService 可能是目标对象也有可能是代理对象
UserService userService = context.getBean("userService", UserService.class);
// 用代理对象调用目标方法的那句话的位置就叫连接点,但前提一定用代理对象调用才是连接点
userService.addUser(new User());// 连接点
context.close();
}
6.结果
around:前置通知 TransactionManager
UserServiceImpl.addUser()
around:后置通知 TransactionManager
around:最终通知 TransactionManager
1.4、Spring JDBC
Spring JDBC 是对原生的 JDBC 的封装,好处是可以用 Spring 容器来管理 Spring JDBC 的对象。
连接池:用户从连接池中获得数据库的连接,然后基于连接做相应的数据库的操作,用户关闭连接相当于把连接还给数据库的连接池以备其他的用户从连接池获取连接。
<!-- 加载属性文件的数据到spring容器中 -->
<context:property-placeholder location="classpath:conf/mysql.properties" />
<!-- spring管理jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="alibabaDataSource"></property>
</bean>
<!-- 数据库连接池 commons-dbcp,c3p0,proxool,阿里巴巴druid -->
<bean id="alibabaDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!-- 数据库连接的4项 -->
<property name="driverClassName">
<value>${jdbc_driverClass}</value>
</property>
<property name="url">
<value>${jdbc_url}</value>
</property>
<property name="username">
<value>${jdbc_userName}</value>
</property>
<property name="password">
<value>${jdbc_userPassword}</value>
</property>
</bean>
<!-- 原有的业务对象创建和管理 -->
<bean id="userDao" class="dao.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
1.5、Spring的声明式事务管理
该事务是建立在AOP之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完目标方法之后根据执行情况提交或回滚事务。
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- 以add开头,必须添加事务,Exception异常时回滚 -->
<tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<!-- 以delete开头,必须添加事务,Exception异常时回滚 -->
<tx:method name="delete*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<!-- 以update开头,必须添加事务,Exception异常时回滚 -->
<tx:method name="update*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<!-- 以create开头,必须添加事务,Exception异常时回滚 -->
<tx:method name="create*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<!-- 以find开头的方法是不需要添加事务的 -->
<tx:method name="find*" read-only="true" />
<!-- 以get开头的方法是不需要添加事务的 -->
<tx:method name="get*" read-only="true" />
</tx:attributes>
</tx:advice>
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--进行事务操作需要通过数据库连接来完成,而数据库连接现在是由数据源管理的,所以需要给事务管理器配置数据源连接池-->
<property name="dataSource" ref="alibabaDataSource" />
</bean>
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="userServiceOperation" expression="execution(* cn.tedu.service..*.*(..))" />
<!--建立切入点表达式和事务通知之间的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="userServiceOperation" />
</aop:config>
2、Spring MVC
2.1、什么是 MVC
MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。
- Model(模型 dao service层):负责代码的逻辑。
- View(视图 html jsp css javascript):显示交互的页面和收集页面的数据。
- Controller(控制器 servlet):Model 和 View 之间的桥梁,用于控制流程。
View 的页面数据提交给 Controller,Controller 调用 Model 处理数据,把 Model 的处理完的结果通过Controller 响应给 View,View 显示处理完的数据结果。
项目的三层
-
表示层:专门控制显示数据的,把数据显示给用户,起到交互的作用。
-
业务逻辑层:专门用来控制项目的具体的业务取决于需求的复杂程度,业务层获取表示层的数据,基于数据业务处理,一个业务可能多次调用数据访问层,把业务的结果返回给controller,一般情况下用servlet 或 biz 代表业务逻辑层。
-
数据访问层:专门用来控制数据库的操作,数据访问层接收业务逻辑的数据,把数据库返回的结果返回给业务逻辑层
2.2、Spring MVC 执行原理
- 用户发起 HTTP Request 请求,该请求会被提交到 DispatcherServlet(前端控制器)。
- 由 DispatcherServlet 把请求的 URL 路径给到 HandlerMapping(处理器映射器)。
- HandlerMapping(处理器映射器)据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息,构造执行链并返回。
- DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器)。
- HandlerAdapter (处理器适配器)根据 Handler 信息找到并执行相应的 Handler(常称为 Controller)。
- Handler 执行完毕后会返回给 HandlerAdapter (处理器适配器)一个 ModelAndView 对象(Spring MVC 的底层对象,包括 Model 数据模型和 View 视图信息)。
- HandlerAdapter(处理器适配器) 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet(前端控制器)。
- DispatcherServlet(前端控制器) 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析。
- ViewResolver (视图解析器)根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet(前端控制器)。
- DispatcherServlet (前端控制器)接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图)。
- 视图负责将结果显示到浏览器(客户端)。
2.3、Spring MVC 拦截器
Spring 的 HandlerMapping 处理器支持拦截器的应用,当需要为某些请求提供特殊功能是时候,添加拦截器。(身份验证:在所有的请求的前面都要判断是否已经登录,已经登录就直接访问资源,没有登录就跳转到登录界面,拦截器的概念,就是横切了一个身份验证的切面)。
拦截器必须实现自 HandlerInterceptor 接口,此接口有三个方法
-
preHandle()
该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。 -
postHandle()
该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。 -
afterCompletion()
该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。
<!-- spring mvc的拦截器的用法 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 需要拦截的地址,一般是所有的请求都拦截,把不需要的拦截的地址放出去 -->
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login.jsp"/>
<mvc:exclude-mapping path="/js/**"/>
<mvc:exclude-mapping path="/user/**"/>
<bean class="interceptor.SecurityInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
2.4、浏览器给服务器提供数据的方式
<input name="userName">
- form 表单提交
客户端的数据提交给服务端
- login (String userName)
userName 对应的是表单中的名字。 - login (@RequestParam(“userName”) String name)
@RequestParam(“userName”) 对应的是表单中的名字。 - login (User user)
User类中有setUserName方法。 - login (HttpServletRequest request,HttpServletResponse response) request.getparameter(“userName”) 来取出值。
服务端的数据响应给客户端
- ModelAndView login() Model 存储数据 View存储viewName。
- String login(Model model) Model 存储数据 String 返回视图名字。
- String login(ModelMap modelmap) modelMap 存储数据 String 返回视图名字。
- String login(Map map) Model 存储数据 String 返回视图的名字。
- ajax提交
客户端的数据提交给服务端
同上
服务端的数据响应给客户端
- 返回值就是一个字符串,无法处理中文。
- 返回值是对象,可以处理中文。
- 人为的构建 json 并通过 response 的流响应 dataType:“json” 可以处理中文。
- restful提交
@RequestMapping(value=“login/name/{uname}”,method=RequestMethod.GET)
@PathVariable(“upassword”) String pwd
3、MyBatis
3.1、MyBatis 的增删改查
- 原生的 MyBatis 的 API(增删改查语句对应的mapper子清单xml文件写法)
// MybatisUtil 工具类 MybatisUtil.getSqlSession() 取出 SqlSession 对象调用增删改查方法
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static{
try {
String resource = "conf/configuration.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
//openSession()开启事务
SqlSession session=sqlSessionFactory.openSession();
return session;
}
增 (insert)
value参数 #{name} name -> Name -> getName,去User中找是否有get方法。
<insert id="" parameterType="">插入sql语句</insert>
当主键id是自增的情况下,添加一条记录的同时,我们要获取到主键id。
useGeneratedKeys = true:这个表示插入数据之后返回一个自增的主键id给你对应实体类中的某个属性。必须设置为true,默认为false。
keyProperty:设置为POJO对象的主键id属性名称。
keyColumn:设置为数据库记录的主键id字段名称。
<insert id="" parameterType="" useGeneratedKeys="true" keyProperty="id" keyColumn"id"> </insert>
删 (delete)
<delete id="" parameterType="">删除sql语句</delete>
改 (update)
<update id="" parameterType="">修改sql语句</update>
查 (selectOne,selectList,selectMap,select)
<select id="" parameterType="" resultType="">查询sql语句</select>
- Mapper 接口 + JDK 动态代理方式(其底层用的是 MyBatis 的原生 API)
用此种方法可以大大提高开发效率,但是创建代理类需要时间,动态代理本身就比静态代理慢,一定降低了执行的效率。
1.根据接口创建接口中的代理对象
2.用代理对象调用代理类中的目标方法
3.在目标方法中调用InvocationHandler接口中的invoke方法
4.在invoke方法中根参数类型和结果类型去选择调用的具体的原生的api方法
2.userMapper对象是代理对象,用代理类对象调用目标方法,方法就是代理类中的方法
在代理类的方法中调用InvocationHandler接口中的invoke方法
根据resultType和parameterType的类型决定调用哪一个原生的mybatis的api方法
SqlSession session=null;
try {
session=MybatisUtil.getSqlSession();
UserMapper userMapper=session.getMapper(UserMapper.class);
User user=userMapper.findUserById(9);
System.out.println(user);
session.commit();
}catch(Exception e) {
session.rollback();
e.printStackTrace();
}finally {
session.close();
}
- 注解用法(不推荐)
3.2、MyBatis SQL标签
3.2.1、MyBatis 的动态 SQL
利用 MyBatis 动态 SQL解决了sql语句拼装的问题。
<!-- 查询用户信息 -->
<sql id="byid" >
select id,username name,password,age,address,head,point from t_user
</sql>
<!-- 根据id查询用户信息 -->
<select id="findUserById" resultType="User"
parameterType="java.lang.Integer">
<include refid="byid"></include>
where id=#{id}
</select>
3.2.2、if 标签
<select id="findUsersByIf" parameterType="User" resultType="User">
<include refid="select"></include> where age=23
<if test="name!=null"> and username like #{name}</if>
<if test="address!=null"> and address like #{address}</if>
</select>
name->Name->getName 用getName去parameterType指定的类中寻找是否有此方法,如果有就反射调用,调用完反射结果不为null就拼装sql语句 null就不拼装,address同理。
如果两个条件都不为null
select * from user where age=20 username like ? and address like ?
3.2.3、choose when otherwise 标签
<select id="findUsersByChoose" parameterType="java.util.Map" resultType="User">
<include refid="select"></include> where age=23
<choose>
<when test="uname !=null">and username like #{uname}</when>
<when test="uaddress !=null">and address like #{uaddress}</when>
<otherwise>
and username like '%a%'
and address like '%b%'
</otherwise>
</choose>
</select>
choose when otherwise 标签 多个when条件同时成立,就取第一个条件成立的when。
3.2.4、where 标签
<select id="findUsersByWhere" parameterType="java.util.Map" resultType="User">
<include refid="select"></include>
<where>
<if test="uname != null">username like #{uname}</if>
<if test="uaddress !=null">and address like #{uaddress}</if>
</where>
</select>
where标签是为了给sql语句添加where关键字,where标签中的条件都不成立,where关键字就不添加了,如果两个条件都成立
select * from t_user where username like ? and address like ?
如果第一个不成立,第二个条件成立
select * from t_user where and address like ?
他会自动去掉and关键字
3.2.5、set 标签
<update id="updateUserBySet" parameterType="java.util.Map"> update user
<set>
<if test="uname != null">username=#{uname},</if>
<if test="uaddress !=null">address=#{uaddress}</if>
</set>
where id=#{uid}
</update>
set 标签只能用于更新语句,第一个条件成立,第二条件不成立,则自动取消逗号。
3.2.6、trim 标签
trim 替换where标签
<select id="findUsersByTrim" parameterType="java.util.Map" resultType="User">
<include refid="select"></include>
<trim prefix="where" prefixOverrides="and|or">
<if test="uname != null">username like #{uname}</if>
<if test="uaddress !=null">and address like #{uaddress}</if>
</trim>
</select>
trim标签替换 set标签
<update id="updateUserByTrim" parameterType="java.util.Map"> update t_user
<trim prefix="set" suffixOverrides=",">
<if test="uname != null">username=#{uname},</if>
<if test="uaddress !=null">address=#{uaddress}</if>
</trim>
where id=#{uid}
</update>
可以替换where标签和set标签。
3.2.7、foreach 标签
<select id="findUsersByForeach" parameterType="list" resultType="User">
<include refid="select"></include>where id in
<foreach collection="list"
item="id"
index="index"
open="("
close=")"
separator=",">
#{id}
</foreach>
</select>
3.2.8、resultMap 标签
resultMap节点的用法,目标是解决结果集列名字跟实体类中的属性名(setter getter)不匹配问题。
id是唯一的标识符,type是java的类型。
添加autoMapping="true"不需要些那么多的result节点和id节点,前提是属性名称和结果集的列名字,忽略大小写比较为真值。
<resultMap id="user" type="User">
<id property="id" column="id"/>
<result property="name" column="username"/>
<result property="password" column="userpassword"/>
<result property="age" column="age"/>
<result property="address" column="address"/>
<result property="head" column="head"/>
<result property="point" column="point"/>
</resultMap>
id节点对应的是主键,result节点对应的非主键,property对应java类中的属性(setter 和 getter),column对应结果集的列名字。
3.3、MyBatis 中的关联关系
<!-- 定义resultMap,用户的resultMap -->
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="name" column="username"/>
<result property="password" column="password"/>
<result property="age" column="age"/>
<result property="address" column="address"/>
<result property="head" column="head"/>
<result property="point" column="point"/>
<association property="group" javaType="Group">
<id property="id" column="gid"/>
<result property="name" column="name"/>
<result property="loc" column="loc"/>
</association>
</resultMap>
<!-- 定义resultMap,组的resultMap -->
<resultMap id="groupMap" type="Group">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="loc" column="loc"/>
<collection property="users" ofType="User" javaType="java.util.List">
<id property="id" column="uid"/>
<result property="name" column="username"/>
<result property="password" column="password"/>
<result property="age" column="age"/>
<result property="address" column="address"/>
<result property="head" column="head"/>
<result property="point" column="point"/>
</collection>
</resultMap>
对一关联
<!-- mybatis的两种关联关系,对一关联 ,查询用户信息,用户信息中有一个group-->
<select id="findUserBy_Association" resultMap="userMap">
select u.id,u.username,u.password,u.address,u.age,u.address,
u.head,u.point,g.id,g.name,g.loc from t_user u left outer join t_group g on u.group_id=g.id
</select>
对多关联
<!-- mybatis的两种关联关系,对多关联,查询组的信息,一个组中包含多个user信息 -->
<select id="findGroupBy_Collection" resultMap="groupMap">
select g.id,g.name,g.loc,u.id,u.username,u.password,u.address,u.age,u.address,u.head,u.point
from (select * from t_group where id=1) g left outer join t_user u on u.group_id=g.id
</select>
3.4、MyBatis 中的缓存机制
3.4.1、一级缓存:SqlSession级别的缓存
// SqlSession 对象获取类
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static{
try {
String resource = "conf/configuration.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
//openSession()开启事务
SqlSession session=sqlSessionFactory.openSession();
return session;
}
}
测试方法
@Test
public void testOneLevelCache() {
SqlSession session = null;
try {
session = MybatisUtil.getSqlSession();
// 执行此语句的时候,先从sqlsession中获取id=3的数据,如果没有就发出sql语句,去数据库查询
User user = session.selectOne("findUserById", 3);
// 如果有就不发出sql语句,从sqlsession中获取id=3的数据
User user1 = session.selectOne("findUserById", 3);
System.out.println(user);
System.out.println(user1);
System.out.println(user == user1);
session.commit();
} catch (Exception e) {
e.printStackTrace();
session.rollback();
} finally {
session.close();
}
}
结果
User [id=3, area_name=华南, create_op=yang_tc, create_time=null, is_enable=1]
User [id=3, area_name=华南, create_op=yang_tc, create_time=null, is_enable=1]
true
把查询出来的数据存储给SqlSession对象,如果SqlSession对象不关闭,只需要查询一次数据库,其余的都是从缓存中获取的,如果SqlSession对象关闭,则缓存数据丢失。
3.4.2、二级缓存:SqlSessionFactory级别的缓存
MyBatis 自带了一个缓存的实现,也可以利用第三方的缓存的产品设置给 MyBatis 要求实体必须实例化,MyBatis 二级缓存默认是打开的,是否能够使用二级缓存,还需要设置子清单文件。
<!-- 使用mybatis自带的二级缓存产品-->
<cache></cache>
<!-- 使用第三方的缓存的缓存产品ehcache -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
MyBatis 查询数据的时候,是先从二级缓存查找是否有缓存的数据,如果没有,就从一级缓存查找,一级缓存中也没有,就会数据库中找,就会发出sql语句查询数据。