Spring
Spring
1. 介绍
Spring:春天 绿色的
赞美者 胶水语言
Spring框架不属于任何一层,Spring框架的思想旨在让其他框架专注于自身专业的问题解决,而不需要关注自身无关的问题处理。
- Spring框架做了哪些事(目前代码中存在的问题)?
- 回顾之前我们使用Mybatis框架,MyBatis框架是一个持久层框架,主要负责数据库的CRUD,但是目前我们还需要让mybatis处理事务以及日志的操作,这样不是很合理,所以以后 这样的"杂活"可以交给"专人"来处理,就是Spring框架
以上问题,
Spring
框架可以解决~在实际开发中,将很少见到new关键字
2. Spring家族
3. 架构
- Core核心部分
- Bean 负责创建对象 IOC容器
- Context Spring应用程序上下文
- SpEL Spring提供的表达式
- AOP Aspectes(Aspect Oriented Programming)面向切面编程,是指在不影响原业务逻辑代码的情况,实现业务逻辑的增强
- Transaction 事务的处理
- JDBC 连接数据库
- ORM 关系映射
- Web
- Servlet SpringMVC对Servlet的封装
- Web SpringMVC对web项目支持
- Test Spring提供有对junit测试的支持
4. 特点
- 轻量级 是指Spring框架将每一个功能都单独划分为一个小组件 可以按需取用
- IOC inversion of control 控制反转
- AOP Aspect Oriented Programming 面向切面编程
5. IOC
- IOC称之为控制反转
- 1.控制了什么?控制了创建对象的主权,以前new对象,但是会增加耦合度,现在使用spring框架不需要new对象了
- 2.反转了什么?反转了创建对象这个事,以前自己new,现在由Spring框架创建
- 3.如何实现控制反转?通过IOC容器,帮我们实现控制反转
- DI dependicy injection 依赖注入,依赖IOC容器给创建的对象属性赋值,这个操作就是DI
6. 创建项目
6.1 添加依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.18.RELEASE</version>
</dependency>
6.2 编写配置文件
- 编写文件头 添加到idea模板
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beansn>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bHornDate" class="java.util.Date">
<property name="year" value="120"/>
<property name="month" value="10"/>
<property name="hours" value="10"/>
</bean>
<bean id="hero1" class="com.qfedu.entity.Hero">
<property name="hId" value="1"/>
<property name="hName" value="张辽"/>
<property name="hStory" value="关羽好兄弟"/>
<property name="imgPath" value="abc"/>
<property name="countryId" value="1"/>
<property name="hBornDate" ref="bHornDate"/>
<property name="country" ref="country"></property>
</bean>
<bean id="hero2" class="com.qfedu.entity.Hero">
<property name="hId" value="2"/>
<property name="hName" value="曹植"/>
<property name="hStory" value="七步成诗"/>
<property name="imgPath" value="abc"/>
<property name="countryId" value="1"/>
<property name="hBornDate" ref="bHornDate"/>
<property name="country">
<bean class="com.qfedu.entity.Country">
<property name="cId" value="1"/>
<property name="cName" value="魏国"/>
</bean>
</property>
</bean>
<bean id="country" class="com.qfedu.entity.Country">
<property name="cId" value="1"/>
<property name="cName" value="魏国"/>
</bean>
</beans>
6.3 编写测试
getBean() 方法
可以传入bean标签中id属性值来获取对象
也可以再传入一个Class对象类指定创建对象的类型
import com.qfedu.entity.Country;
import com.qfedu.entity.Hero;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author WHD
* @date 2021/11/5 11:11
*/
public class TestHero {
public static void main(String[] args) {
// Hero hero = new Hero();
// 创建context对象 用于加载配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 通过context对象 根据bean标签 id属性来获取对象
Object obj = context.getBean("hero1");
if(obj instanceof Hero){
Hero hero = (Hero) obj;
System.out.println(hero);
}
Country country = context.getBean("country", Country.class);
System.out.println(country);
Hero hero2 = context.getBean("hero2", Hero.class);
System.out.println(hero2);
}
}
6.4 注意
当前我们使用的方式是setter注入,那么必须给需要注入值的属性添加setter方法,否则注入失败,报错
7 .依赖(DI)注入方式
-
setter方法注入 必须给属性添加setter方法
-
构造器注入
-
<!--约定大于配置 是spring一大特点 不写index 和 name 将默认按照构造器的属性顺序赋值--> <bean id="country2" class="com.qfedu.entity.Country"> <constructor-arg value="蜀国" index="1" name="cName"/> <constructor-arg value="2" index="0" name="cId"/> </bean>
-
-
p命名空间注入
-
xmlns:p="http://www.springframework.org/schema/p" <!--p命名空间 也是根据setter方法注入的 如果不写setter方法 报错--> <bean id="country3" class="com.qfedu.entity.Country" p:cId="3" p:cName="吴国"></bean> <bean id="hero3" class="com.qfedu.entity.Hero" p:country-ref="country3" ></bean>
-
-
注解方式注入
- @Component 任何类上都可以使用 通常用在工具类,或者实体类 因为其他的有特殊含义的类有固定的注解来描述
- @Service注解 表示用于在Service实现类上
- @Repository注解 表示用于在DAO实现类上 可以实现生成bean
- @Controller注解 表示用于控制器上 实现生成bean
- @Value注解 表示给属性注入值 引用数据类型 使用 value = "#{bean id}" 这种方式类实现注入
- @Autowired 表示自动装配 按照类型找到一个对应的javabean进行注入
- @Qualifier 表示可以指定具体beanid 通常结合Autowired来使用
-
使用注解的方式不要忘记在主配置文件中加上对应的文件头 以及扫描注解
-
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
-
<context:component-scan base-package="com.qfedu.entity"></context:component-scan>
-
7.1 复杂参数注入
各种集合类型的数据
package com.qfedu.entity;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author WHD
* @date 2021/11/5 16:26
*/
public class TestParam {
private List<String> list1;
private List<Country> list2;
private Set<Country> set;
private Map<String,String> map;
public List<String> getList1() {
return list1;
}
public List<Country> getList2() {
return list2;
}
public Set<Country> getSet() {
return set;
}
public Map<String, String> getMap() {
return map;
}
public void setList1(List<String> list1) {
this.list1 = list1;
}
public void setList2(List<Country> list2) {
this.list2 = list2;
}
public void setSet(Set<Country> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="testParam" class="com.qfedu.entity.TestParam">
<property name="list1" value="a,b,c,d,e" />
<property name="list2">
<list>
<bean class="com.qfedu.entity.Country">
<property name="cId" value="1"/>
<property name="cName" value="魏国"/>
</bean>
<bean class="com.qfedu.entity.Country">
<property name="cId" value="2"/>
<property name="cName" value="蜀国"/>
</bean>
<bean class="com.qfedu.entity.Country">
<property name="cId" value="3"/>
<property name="cName" value="吴国"/>
</bean>
<bean class="com.qfedu.entity.Country">
<property name="cId" value="4"/>
<property name="cName" value="中立"/>
</bean>
</list>
</property>
<property name="set">
<set>
<bean class="com.qfedu.entity.Country">
<property name="cId" value="1"/>
<property name="cName" value="魏国"/>
</bean>
<bean class="com.qfedu.entity.Country">
<property name="cId" value="1"/>
<property name="cName" value="魏国"/>
</bean>
<bean class="com.qfedu.entity.Country">
<property name="cId" value="1"/>
<property name="cName" value="魏国"/>
</bean>
</set>
</property>
<property name="map">
<map>
<entry>
<key><value>"1"</value></key>
<value>"赵云"</value>
</entry>
<entry>
<key><value>"2"</value></key>
<value>"赵四"</value>
</entry>
<entry>
<key><value>"3"</value></key>
<value>"赵信"</value>
</entry>
</map>
</property>
</bean>
</beans>
8. IOC补充
8.1 主配置文件autowire属性
常用值为
byName:即根据名字来装配bean,会装配属性名和id名相同的b,并且类型相同bean,如果属性名和id不一致,则不能装配,属性值为null,不会报错
此时可以使用byType来装配,即根据类型
byType:即根据类型来装配,不再关注bean id和属性名是否一致,只会 找类型相同的bean,如果找到多个,将报错
expected single matching bean but found 2: c,country1
表示根据类型应该匹配一个,但是找到两个ioc容器不知道使用哪个
<bean id="hero" class="com.qfedu.entity.Hero" autowire="byName | byType">
</bean>
8.2 init-method和destory-method方法
这两个属性分别可以指定bean在生成之前,和bean在销毁之前执行的操作
只需要指定对应的方法名即可
<bean id="c" class="com.qfedu.entity.Country" init-method="init" destroy-method="destory">
<property name="cId" value="1"/>
<property name="cName" value="魏国"/>
</bean>
package com.qfedu.entity;
/**
* @author WHD
* @date 2021/11/8 9:12
*/
public class Country {
private int cId;
private String cName;
public void init(){
System.out.println("country init方法开始执行");
}
public void destory(){
System.out.println("country destory方法开始执行");
}
}
8.3 @PostConstruct和@PreDestory
这两个注解可以实现与8.2配置文件相同的效果
package com.qfedu.entity;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* @author WHD
* @date 2021/11/8 9:12
*/
public class Country {
private int cId;
private String cName;
@PostConstruct
public void init(){
System.out.println("country init方法开始执行");
}
@PreDestroy
public void destory(){
System.out.println("country destory方法开始执行");
}
}
8.3 @AutoWired注解
- 默认先根据类型装配,如果找到对应的bean ,则完成装配
- 如果找不到与属性名一致的beanid,且找到多个同类型的,则报错
- 如果没有 找到与属性名一致的beanid,但是只有一个类型相同的bean,也能完成装配
- required属性默认为true,表示必须装配,改为false,则表示不是必须装配
- @Autowired 如果存在多个相同类型,则优先匹配beanid和属性名一致的bean
- 通常结合@Qulifier注解结合使用
8.4 @Resource注解
-
默认按照与属性名对应的beanid来装配,也可以通过name属性来指定其他的beanid
-
如果只有一个类型相同,且名字不一致,也能实现自动装配
@Resource(name="country2") private Country country;
8.5 @Resource和@Autowired的区别
1.@Resources是Java提供的注解 @AutoWired是Spring提供的注解
2.@Resource默认是按照名字装配,即属性名和beanid一致则完成装配
@AutoWired默认按照类型来装配,即只要类型符合则完成装配
3.@Resource可以指定name属性和type属性,如果都指定表示寻找一个唯匹配的类型装配
只指定name,则按照名字查找
只指定type,则按照类型并且加上默认属性名为id查找
4.@Autowired 如果存在多个相同类型,则优先匹配beanid和属性名一致的bean
9. Bean的作用域
作用域 |
---|
单例模式,默认为饿汉单例,可以通过@Lazy注解实现懒汉单例 |
@Scope("prototype")注解设置为prototype表示为非单例模式 |
@Scope("request") 表示在同一个请求中使用同一个对象 |
@Scope("session") 表示 在同一个session回话中使用同一个对象 |
@Scope("global session") 全局session |
10. 代理模式
10.1 静态代理
静态代理只能代理固定的类,不易于程序的扩展
package com.qfedu.proxy;
/**
* @author WHD
* @date 2021/11/8 11:44
*/
public interface GeneralDao {
void add();
void del();
void modify();
}
package com.qfedu.proxy;
/**
* @author WHD
* @date 2021/11/8 11:43
*/
public class CountryDaoImpl implements GeneralDao{
public void add(){
System.out.println("添加country成功");
}
public void del(){
System.out.println("删除country成功");
}
public void modify(){
System.out.println("修改country成功");
}
}
package com.qfedu.proxy;
/**
* @author WHD
* @date 2021/11/8 11:36
*/
public class HeroDaoImpl implements GeneralDao{
public void add(){
System.out.println("添加hero成功");
}
public void del(){
System.out.println("删除hero成功");
}
public void modify(){
System.out.println("修改hero成功");
}
}
package com.qfedu.proxy;
/**
* @author WHD
* @date 2021/11/8 11:36
*/
public class StaticProxy {
private GeneralDao generalDao;
public StaticProxy(GeneralDao generalDao) {
this.generalDao = generalDao;
}
void add(){
begin();
generalDao.add();
commit();
}
void del(){
begin();
generalDao.del();
commit();
}
void modify(){
begin();
generalDao.modify();
commit();
}
public void begin(){
System.out.println("-----开启事务-----");
}
public void commit(){
System.out.println("-----提交事务-----");
}
}
package com.qfedu.proxy;
import com.qfedu.entity.Country;
/**
* @author WHD
* @date 2021/11/8 11:41
*/
public class Test {
public static void main(String[] args) {
CountryDaoImpl countryDaoImpl = new CountryDaoImpl();
StaticProxy staticProxy = new StaticProxy(countryDaoImpl);
staticProxy.add();
staticProxy.del();
staticProxy.modify();
}
}
10.2 动态代理
JDK动态代理
- 要求:
- 1.被代理对象必须实现接口
- 好处:
- 灵活性,任何接口实现类都可以被代理,解决静态代理的局限性
package com.qfedu.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author WHD
* @date 2021/11/8 15:17
*/
public class DynamicProxy implements InvocationHandler {
// 被代理对象
private Object objProxy;
public DynamicProxy(Object objProxy) {
this.objProxy = objProxy;
}
// 获取代理对象
// JDK动态代理
public Object getAgent(){
// 第一个参数 类加载器
// 第二个参数 被代理的类实现的接口
// 第三个参数 invocationHandler 拦截器
ClassLoader classLoader = objProxy.getClass().getClassLoader();
Class<?>[] interfaces = objProxy.getClass().getInterfaces();
Object agent = Proxy.newProxyInstance(classLoader, interfaces, this);
return agent;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
begin();
// System.out.println("hello dynamic");
Object returnValue = method.invoke(objProxy,args);
commit();
return returnValue;
}
public void begin(){
System.out.println("---开启事务---");
}
public void commit(){
System.out.println("---提交事务---");
}
}
package com.qfedu.proxy.test;
import com.qfedu.entity.Country;
import com.qfedu.proxy.CountryDaoImpl;
import com.qfedu.proxy.DynamicProxy;
import com.qfedu.proxy.GeneralDao;
import com.qfedu.proxy.HeroDaoImpl;
import org.junit.Test;
/**
* @author WHD
* @date 2021/11/8 15:26
*/
public class TestDynamic {
@Test
public void test1(){
CountryDaoImpl countryDaoImpl = new CountryDaoImpl();
DynamicProxy dynamicProxy = new DynamicProxy(countryDaoImpl);
Object agent = dynamicProxy.getAgent();
if(agent instanceof GeneralDao) {
GeneralDao generalDao = (GeneralDao) agent;
generalDao.modify();
}else{
System.out.println("类型不匹配,不能转换");
}
}
}
CGLIb 动态代理
Code Generation Library 代码生成库
不要求被代理类实现接口,因为CGLIb是将被代理对象作为父类,生成一个代理对象子类
要求父类不能被final修饰
被final修饰的方法不能被代理
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
package com.qfedu.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author WHD
* @date 2021/11/8 16:24
* CGLib动态代理 不要求代理类必须实现接口
* CGLib的实现思想是将被代理对象作为父类
* 如果被代理类是final修饰的 将 不能代理
*/
public class CGLibDynamicProxy implements MethodInterceptor {
// 被代理对象
private Object objProxy;
public CGLibDynamicProxy(Object objProxy) {
this.objProxy = objProxy;
}
public Object getAgent(){
// 创建一个增强对象
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(objProxy.getClass());
// callBack表示拦截父类的方法 由本类执行
enhancer.setCallback(this);
// 创建一个子类对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
begin();
Object returnValue = method.invoke(objProxy, objects);
commit();
return returnValue;
}
public void begin(){
System.out.println("---开启事务---");
}
public void commit(){
System.out.println("---提交事务---");
}
}
package com.qfedu.proxy.test;
import com.qfedu.proxy.CGLibDynamicProxy;
import com.qfedu.proxy.CountryDaoImpl;
import org.junit.Test;
/**
* @author WHD
* @date 2021/11/8 16:33
*/
public class TestCGLib {
@Test
public void test1(){
CountryDaoImpl countryDaoImpl = new CountryDaoImpl();
CGLibDynamicProxy cgLibDynamicProxy = new CGLibDynamicProxy(countryDaoImpl);
Object agent = cgLibDynamicProxy.getAgent();
if(agent instanceof CountryDaoImpl){
CountryDaoImpl countryDao = (CountryDaoImpl) agent;
countryDao.del();
}
}
}
11. AOP
11.1 添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
11.2 编写Java类
package com.qfedu.dao;
/**
* @author WHD
* @date 2021/11/8 17:44
*/
public interface GeneralDao {
void add();
void del();
void modify();
}
package com.qfedu.dao.impl;
import com.qfedu.dao.GeneralDao;
/**
* @author WHD
* @date 2021/11/8 17:45
*/
public class HeroDaoImpl implements GeneralDao {
@Override
public void add() {
System.out.println("添加hero");
}
@Override
public void del() {
System.out.println("删除hero");
}
@Override
public void modify() {
System.out.println("修改hero");
}
}
package com.qfedu.util;
/**
* @author WHD
* @date 2021/11/8 17:44
*/
public class AOPUtil {
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
}
import com.qfedu.dao.GeneralDao;
import com.qfedu.dao.impl.HeroDaoImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author WHD
* @date 2021/11/8 17:54
*/
public class TestAOP {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
HeroDaoImpl heroDaoImpl = context.getBean("heroDaoImpl",HeroDaoImpl.class);
heroDaoImpl.add();
}
}
11.3 编写配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<bean id="heroDaoImpl" class="com.qfedu.dao.impl.HeroDaoImpl"></bean>
<bean id="aopUtil" class="com.qfedu.util.AOPUtil"></bean>
<!--
proxy-target-class="true" 不写或者false都默认使用JDK动态代理
写为true表示使用CGLib动态代理
-->
<aop:config proxy-target-class="true">
<!--
第一个* 表示所有的返回值
包名.类名
(..) 表示所有参数个数、类型的参数
-->
<aop:pointcut id="pointCut" expression="execution(* com.qfedu.dao.impl.*.*(..))"/>
<aop:aspect ref="aopUtil">
<aop:before method="begin" pointcut-ref="pointCut"></aop:before>
<aop:after method="commit" pointcut-ref="pointCut"></aop:after>
</aop:aspect>
</aop:config>
</beans>
11.4 测试
import com.qfedu.dao.GeneralDao;
import com.qfedu.dao.impl.HeroDaoImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author WHD
* @date 2021/11/8 17:54
*/
public class TestAOP {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
HeroDaoImpl heroDaoImpl = context.getBean("heroDaoImpl",HeroDaoImpl.class);
heroDaoImpl.add();
}
}
12. AOP的切入点
切入点可以灵活的配置
<!--表示所有impl包下的所有类中的所有方法都将作为切入点-->
<aop:pointcut id="pointCut" expression="execution(* com.qfedu.dao.impl.*.*(..))"/>
<!--表示只在HeroDaoImpl类中的返回值类型为void的add方法作为切入点-->
<aop:pointcut id="pointCut1" expression="execution(void com.qfedu.dao.impl.HeroDaoImpl.add(..))"/>
<!--表示只在HeroDaoImpl类中的返回值类型为int的无参del作为去切入点-->
<aop:pointcut id="pointCut2" expression="execution(int com.qfedu.dao.impl.HeroDaoImpl.del())"/>
13. AOP的增强
- 前置增强
- 后置增强
- 返回值以后增强
- 抛出异常以后增强
- 环绕增强
13.1 AsceptJ
AsceptJ是一个单独的框架,Spring对其进行了集成,可以用于来实现AOP
接下来我们使用AsceptJ框架来实现日志的增强
- 第一步 导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
-
第二步 编写日志工具类
package com.qfedu.util; import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; /** * @author WHD * @date 2021/11/9 9:50 */ public class LogUtil { // 这行代码表示创建一个日志对象 用于人为的控制日志打印 Logger logger = Logger.getLogger(LogUtil.class); public void before(JoinPoint joinPoint){ logger.info("前置增强:"); logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "准备执行……"); } public void after(JoinPoint joinPoint){ logger.info("后置增强:"); logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕"); } public Object afterReturning(JoinPoint joinPoint,Object returnValue){ logger.info("返回值以后增强:"); logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕"); logger.info("返回值是" + returnValue); return returnValue; } public void afterThrowing(JoinPoint joinPoint,Exception ex){ logger.info("抛出异常以后增强:"); logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕"); logger.info(ex.getMessage()); } public Object around(ProceedingJoinPoint proceedingJoinPoint){ logger.info("环绕增强开始:"); Object returnValue = null; try { returnValue = proceedingJoinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); logger.info("环绕增强异常信息:" + throwable.getMessage()); } logger.info("环绕增强的返回值:" + returnValue); return returnValue; } }
-
第三步 编写主配置文件
<bean id="logUtil" class="com.qfedu.util.LogUtil"></bean> <aop:config> <aop:pointcut id="pointCut" expression="execution(* com.qfedu.dao.impl.*.*(..))"/> <aop:aspect ref="logUtil"> <aop:before method="before" pointcut-ref="pointCut"/> <aop:after method="after" pointcut-ref="pointCut"/> <!--returning属性值和 对应方法形参名称保持一致--> <aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="returnValue"/> <!--throwing属性值与对应方法形参名保持一致--> <aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="ex"/> <aop:around method="around" pointcut-ref="pointCut"/> </aop:aspect> </aop:config>
-
不要忘记添加log4j.properties文件
log4j.rootLogger=DEBUG,Console log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n log4j.logger.org.apache=INFO log4j.logger.org.springframework=ON log4j.logger.org.apache.struts2=OFF log4j.logger.com.opensymphony.xwork2=OFF log4j.logger.com.ibatis=ON log4j.logger.org.hibernate=ON
13.2 注解实现日志增强
-
第一步将原配置文件注释
-
第二步修改LogUtil工具类
package com.qfedu.util; import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * @author WHD * @date 2021/11/9 9:50 */ @Component @Aspect // 表示当前类为一个切面 public class LogUtil { // 这行代码表示创建一个日志对象 用于人为的控制日志打印 Logger logger = Logger.getLogger(LogUtil.class); @Pointcut("execution(* com.qfedu.dao.impl.*.*(..))") public void pointCut(){} @Before("pointCut()") public void before(JoinPoint joinPoint){ logger.info("前置增强:"); logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "准备执行……"); } @After("pointCut()") public void after(JoinPoint joinPoint){ logger.info("后置增强:"); logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕"); } @AfterReturning(pointcut = "pointCut()",returning = "returnValue") public Object afterReturning(JoinPoint joinPoint,Object returnValue){ logger.info("返回值以后增强:"); logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕"); logger.info("返回值是" + returnValue); return returnValue; } @AfterThrowing(pointcut = "pointCut()",throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex){ logger.info("抛出异常以后增强:"); logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕"); logger.info(ex.getMessage()); } // 这里是value属性 直接写值 表示 切入点 @Around("pointCut()") public Object around(ProceedingJoinPoint proceedingJoinPoint){ logger.info("环绕增强开始:"); Object returnValue = null; try { returnValue = proceedingJoinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); logger.info("环绕增强异常信息:" + throwable.getMessage()); } logger.info("环绕增强的返回值:" + returnValue); return returnValue; } }
-
第三步 在主配置文件中开启扫描注解(添加contex文件头)
<!--这表示扫描com.qfedu包下的所有的注解--> <context:component-scan base-package="com.qfedu"/>
-
第四步 开启aspectj自动代理
<!--表示开启aspectj自动代理 true--> <aop:aspectj-autoproxy proxy-target-class="true"/>
14. MyBatis-Spring整合
14.1 分析
- 这两个框架分别实现了什么操作?
- MyBatis:
- CRUD
- 获取数据源DataSource
- 创建SqlSessionFactory
- 生成SqlSession,通过openSession()方法
- 加载Mapper文件,通过代理生成实现类
- 起别名,自动(全局、局部)映射,配置数据源
- 日志、事务的处理
- Spring框架不属于任何一层,Spring框架的思想旨在让其他框架专注于自身专业的问题解决,而不需要关注自身无关的问题处理。所以,当我们Spring框架和Mybatis整合完了以后,MyBatis就真正能实现只专注于自身的功能实现,只做CRUD
- Spring:
- IOC:负责Bean的管理
- AOP:负责日志以及事务的处理
- 经过分析对比,我们发现除了CRUD这项工作Spring无法实现,其他的都可以实现
- MyBatis:
14.2 (1)需要哪些依赖?
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--spring相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.12</version>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!--aspectj aop框架-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
<scope>runtime</scope>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
除了以上我们之前使用的依赖之外,我们还需要引入一个新的依赖
就是Mybaits和Spring联合的依赖
这个依赖是Mybatis出的
mybatis-spring
<!-- mybatis和Spring联合的依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.0</version>
</dependency>
以上依赖是不支持web开发的mybatis结合spring
如果要添加web支持需要导入
servlet
jsp
jstl
spring-web 等依赖
14.3 (2) 编写配置文件
两个框架原来都需要一些配置文件,所以现在依然将两个框架的配置文件添加···
14.4 (3) 编写数据库连接信息文件
druid.url=jdbc:mysql://localhost:3306/tkm
druid.driver=com.mysql.jdbc.Driver
druid.username=root
druid.password=9999
druid.initSize=5
druid.maxActive=20
druid.minIdel=5
druid.maxWait=3000
14.5(4) 编写spring主配置文件
- 1.加载druid.properties文件,获取数据源
- 2.创建SqlSessionFactory
- 3.编写实体类,DAO,以及Mapper文件,引入lombok依赖,以及spring-jdbc依赖
- 4.配置MapperScannerConfigure
- 1.将SqlSessionFactoryBeanName作为属性赋值
- 将需要生成实现类的接口所在包作为属性赋值,以帮我们生成实现类
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--
加载druid.properties文件
classpath是Spring家族产品中经常使用的一个固定的标识
表示类路径 即classes文件夹下
-->
<context:property-placeholder location="classpath:druid.properties"/>
<!--第一步 配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${druid.username}"/>
<property name="password" value="${druid.password}"/>
<property name="url" value="${druid.url}"/>
<!--driver属于是对象类型 这里应该引入一个javabean-->
<!--所以我们如果使用druid.properties文件中的driver信息 则使用driverName属性-->
<property name="driverClassName" value="${druid.driver}"/>
<!--数据库连接池信息-->
<property name="initialSize" value="${druid.initSize}"/>
<property name="maxActive" value="${druid.maxActive}"/>
<property name="minIdle" value="${druid.minIdel}"/>
<property name="maxWait" value="${druid.maxWait}"/>
</bean>
<!--第二步 -->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.qfedu.entity"/>
<property name="mapperLocations" value="classpath:mappers/*Mapper.xml"/>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<!--第三步 编写entity实体类 dao层接口和Mapper文件 注意 换战场了 -->
<!--使用以上步骤可以已经可以实现CRUD了,但是如果这样每次都是自己获取SqlSession
那么意味着事务依然需要自己处理 所以 我们将SqlSession也交给Spring来管理-->
<!--第四步 将SqlSession创建 和 DAO实现类创建 交给Spring-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
<!--此包下所有的接口都会帮我们生成实现类 注册为bean 且bean id是?????-->
<property name="basePackage" value="com.qfedu.dao"/>
</bean>
</beans>
package com.qfedu.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author WHD
* @date 2021/11/9 14:32
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Country {
private int cId;
private String cName;
}
package com.qfedu.dao;
import com.qfedu.entity.Country;
import java.util.List;
/**
* @author WHD
* @date 2021/11/9 14:34
*/
public interface CountryDao {
List<Country> getAllCountries();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qfedu.dao.CountryDao">
<select id="getAllCountries" resultType="Country">
select * from country
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--全局映射-->
<settings>
<setting name="autoMappingBehavior" value="FULL"/>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
package com.qfedu.dao;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import static org.junit.Assert.*;
/**
* @author WHD
* @date 2021/11/9 14:37
*/
public class CountryDaoTest {
@Test
public void getAllCountries() {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
// SqlSessionFactory sessionFactory = context.getBean("sessionFactory", SqlSessionFactory.class);
//
// SqlSession sqlSession = sessionFactory.openSession(true);
//
// CountryDao countryMapper = sqlSession.getMapper(CountryDao.class);
//
// System.out.println(countryMapper.getAllCountries());
CountryDao countryDao = context.getBean("countryDao", CountryDao.class);
System.out.println(countryDao.getAllCountries());
}
}
14.6(5) 将事务交给Spring管理
隔离级别 | 幻读 | 不可重复读 | 脏读 |
---|---|---|---|
SERIALIZABLE | NO | NO | NO |
REPEATABLE_READ | YES | NO | NO |
READ_COMMITTED | YES | YES | NO |
READ_UNCOMMITTED | YES | YES | YES |
- 事务的传播机制
事务传播机制 | 描述 |
---|---|
REQUIRED | 必须使用事务,如果没有则新开启事务 |
SUPPORTS | 支持事务,如果当前有事务则继续使用,没有则使用数据库默认事务 |
NOT_SUPPORTED | 不支持,如果当前有事务,则挂起 |
<!--第五步 将事务交给Spring管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 处理事务的配置 需要使用到新的标签 tx 在上方文件头加入 xmlns:tx-->
<!--transaction-manager="transactionManager" 不需要单独写 因为默认值就是这个值-->
<tx:advice id="advice" >
<tx:attributes>
<!--
rollback-for="java.lang.Exception" 表示遇到这种异常将会回滚
isolation 事务的隔离级别
propagation 事务的传播机制
-->
<tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="add*" isolation="REPEATABLE_READ" propagation="SUPPORTS" rollback-for="java.lang.Exception"/>
<tx:method name="modify*" isolation="REPEATABLE_READ" propagation="NOT_SUPPORTED" rollback-for="java.lang.Exception"/>
<tx:method name="del*" isolation="REPEATABLE_READ" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.qfedu.service.impl.*.*(..))"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointCut"></aop:advisor>
</aop:config>
14.7(6) 补充
- 将service层使用注解实现
package com.qfedu.service;
import com.qfedu.entity.Country;
import java.util.List;
/**
* @author WHD
* @date 2021/11/9 16:35
*/
public interface CountryService {
List<Country> getAllCountries();
}
package com.qfedu.service.impl;
import com.qfedu.dao.CountryDao;
import com.qfedu.entity.Country;
import com.qfedu.service.CountryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author WHD
* @date 2021/11/9 16:36
*/
@Service
public class CountryServiceImpl implements CountryService {
@Autowired
private CountryDao countryDao;
@Override
public List<Country> getAllCountries() {
return countryDao.getAllCountries();
}
}
15. 后续内容
- 使用注解完成事务的配置
- 使用注解的方式实现AOP日志增强
- 将锋迷网整合为Spring + Mybatis + Servlet + JSP + Tomcat
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)