Spring IoC容器
-是什么是IoC
IoC是Inversion of Control的缩写,即控制反转。也就是借助于与第三方进行对象之间的解耦,这是一种设计原则。
- 为什么要Ioc
说道IoC就要提依赖倒置原则,满足了依赖倒置原则就可以一定程度上满足IoC。依赖倒置:要依赖于抽象不应该依赖于具体实现。依赖倒置原则是为了最大程度的实现软件的复用,然而这就存在一个问题:不依赖于具体的实现要如何获取对象?
举个例子,在访问数据库时我们需要获取数据库连接也就是Connection对象,假设不同的数据库厂商(mysql,Oracle)实现不同的Connection对象,在我们的应用更换数据库时我们需要重修改代码重新编译这不仅十分麻烦而且违反了开放封闭原则。自然想让程序依赖于Connection抽象接口(或者类),但抽象的接口或类不能实列化。静态工厂就可以很好的解决这一问题,我们将实列化对象的代码封装在工厂方法中,通过静态工厂来获取对应的对象。
public class Procedure {
Connection connection;
public void process(){
ApplicationContex context = new ClassPahtXmlApplicationContex("/applicationContex.xml");
connection = (Connection) context.getBean("connection");
connection.connect();
}
}
public interface Connection {
public void connect();
}
public class ConnectionFactory{
public static Connection getConnection(String type){
if(type.equals("mysql"))
return new MysqlConnection();
else if(type.equals("oracle))
return new OracleConnection();
else ....
}
}
public class MysqlConnection implements Connection {
public MysqlConnection(){
System.out.println("mysql数据库");
}
public void connect(){
System.out.println("连接到mysql数据库");
}
}
public class OracleConnection implements Connection {
public OracleConnection(){
System.out.println("这是oracle数据库");
}
public void connect() {
System.out.println("连接oracle数据库");
}
}
这里的ConnectionFactory其实就充当了第三方实现了对象之间的解耦。但是我们发现,当有新的数据库厂商出现时,我们不得不更改ConnectionFactory的代码。 那IoC是如何实现对象之间解耦的呢?答案是DL或者DI。
- 如何实现:DL(依赖查找)和DI(依赖注入)
在上面,当新的数据库厂商出现时我们需要修改ConnectionFactory的代码。在Spring中使用了XML文件配置对象并且利用反射实列化对象的方法,这样可以有效的避免更改原来的代码,让我们来看看如何实现。
首先我们引入dom4j和jaxen来进行XML文件的解析,在maven依赖中添加
</dependencies>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
然后编写XML文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<benas>
<bean id = "connection" class = "com.zyf.entity.MysqlConnection"></bean>
</benas>
编写ApplicationContextt
public interface ApplicationContext {
public Object getBean(String beanId);
}
和ClassPathXmlApplicationContext
public class ClassPahtXmlApplicationContext implements ApplicationContex {
private Map iocContainer = new HashMap();
private String filePath;
public ClassPahtXmlApplicationContext(String path){
try {
String filePath = this.getClass().getResource(path).getPath();
filePath = URLDecoder.decode(filePath, "utf-8");
SAXReader reader = new SAXReader();
Document document = reader.read(new File(filePath));
//获取以bean节点
List<Node> beans = document.getRootElement().selectNodes("bean");
for (Node node : beans){
Element ele = (Element) node;
String id = ele.attributeValue("id");
String className = ele.attributeValue("class");
//使用反射创建对象
Class c = Class.forName(className);
Object obj = c.newInstance();
//将对象放在Map中
iocContainer.put(id, obj);
}
}catch (Exception e){
e.printStackTrace();
}
}
public Object getBean(String beanId) {
return iocContainer.get(beanId);
}
}
修改Procedure
public class Procedure {
Connection connection;
public void process(){
ApplicationContex context = new ClassPahtXmlApplicationContex("/applicationContex.xml");
connection = (Connection) context.getBean("connection");
connection.connect();
}
}
当需要使用新的数据库时我们只需要修改XML文件。回顾上面的代码我们发现,最核心的部分就是解析XML文件然后利用反射创建对象并且将创建的对象放入一个map中,这与工厂模式有异曲同工之妙。上面的方法对应着IoC的第一种实现方法DL(依赖查找),我们发现这种方法需要依赖于ApplicationContex,那有没有不需要依赖ApplicationContex的方法呢?答案是有的,实现方法是DI(依赖注入)。
我们修改applicationContext.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<benas>
<bean id = "connection" class = "com.zyf.entity.MysqlConnection"></bean>
<bean id = "procedure" class="com.zyf.entity.Procedure">
<property name = "connection" ref = "connection" >
</property>
</bean>
</benas>
在修改Procedure代码
public class Procedure {
Connection connection;
public void process(){
//ApplicationContext context = new ClassPahtXmlApplicationContext("/applicationContex.xml");
//connection = (Connection) context.getBean("connection");
connection.connect();
}
public Connection getConnection() {
return connection;
}
public void setConnection(Connection connection) {
this.connection = connection;
}
}
修改ClassPathXmlApplicationContext
public class ClassPahtXmlApplicationContex implements ApplicationContex {
private Map iocContainer = new HashMap();
private String filePath;
public ClassPahtXmlApplicationContex(String path){
try {
String filePath = this.getClass().getResource(path).getPath();
filePath = URLDecoder.decode(filePath, "utf-8");
SAXReader reader = new SAXReader();
Document document = reader.read(new File(filePath));
List<Node> beans = document.getRootElement().selectNodes("bean");
for (Node node : beans){
Element ele = (Element) node;
String id = ele.attributeValue("id");
String className = ele.attributeValue("class");
Class c = Class.forName(className);
Object obj = c.newInstance();
iocContainer.put(id, obj);
List<Node> pros = node.selectNodes("property");
for(Node pro : pros){
Element element = (Element)pro;
String propName = element.attributeValue("name");
String propVal = element.attributeValue("ref");
String methodName = "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
Method method = c.getMethod(methodName, Connection.class);
method.invoke(obj, iocContainer.get(propVal));
}
}
}catch (Exception e){
e.printStackTrace();
}
}
public Object getBean(String beanId) {
return iocContainer.get(beanId);
}
}
这样就不需由Procedure主动获取Connection对象,而是由容器主动注入。上面用到了set方法进行注入,实际上还可以使用构造方法或者直接访问字段的方法进行构造。
- spring中的IoC容器
<1>容器的类型
BeanFactory是一个抽象接口,XmlBeanFactory 是BeanFactory的实现。ApplicationContext继承与BeanFactory ,实现有FileSystemXmlApplicationContext(需要提供完整的XML路径),ClassPathXmlApplicationContext(从CLASSPATH 搜索XML配置文件)和WebXmlApplicationContext(在一个 web 应用程序的范围内加载 XML文件)。
<2>bean的作用范围
可以为scope设置singleton和prototype,其中singleton是单例,在初始化IoC容器是就好创建对象,prototype是对例,只有需要使用这个对象时才会创建。
<3>bean的生命周期:创建,属性设置,初始化,销毁。
可以在bean创建时指定初始化程序,销毁指定销毁程序
<?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:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"
default-init-method="init" default-destroy-method="destroy"> <!-- 设则默认的初始化或者销毁方法 -->
<context:property-placeholder location="classpath:config.properties" />
<bean id="user" class="com.zyf.entity.User">
</bean>
<!-- 在ben中设则 -->
<bean id="user" class="com.zyf.entity.User" init-method="init" destroy-method="destroy">
</bean>
</beans>
<4>依赖注入的几种方式
通过XML文件实现注入:XML下又分为三种:set方法,构造方法,工厂方法(静态工厂,实列工厂)
<?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:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<context:property-placeholder location="classpath:config.properties" />
<!--使用set方法实现注入-->
<bean id="user1" class="com.zyf.entity.User">
<property name="username" value="张三" />
<property name="password" value="123" />
<property name="apple" ref="apple1" />
</bean>
<bean id="apple1" class="com.zyf.entity.Apple">
<property name="type" value="红富士" />
</bean>
<!--使用构造方法实现注入-->
<bean id="user2" class="com.zyf.entity.User">
<constructor-arg name="username" value="李四" />
<constructor-arg name="password" value="123" />
<constructor-arg name="apple" ref="apple2" />
</bean>
<bean id="apple2" class="com.zyf.entity.Apple">
<property name="type" value="青苹果" />
</bean>
<!--使用内部bean-->
<bean id="user3" class="com.zyf.entity.User">
<constructor-arg name="username" value="王五" />
<constructor-arg name="password" value="123" />
<constructor-arg name="apple">
<bean class="com.zyf.entity.Apple">
<property name="type" value="黄金龙" />
</bean>
</constructor-arg>
</bean>
<!--使用工厂方法-->
<bean id="user4" class="com.zyf.factory.UserStaticFactory" factory-method="creatUser" />
<!--使用工厂实例-->
<bean id="factoryBean" class="com.zyf.factory.UserFactoryInstance" />
<bean id="user5" factory-bean="factoryBean" factory-method="creatUser"/>
<!--注入List-->
<bean id="user6" class="com.zyf.entity.User">
<property name="friends">
<list>
<value type="java.lang.String">赵已峰</value>
<ref bean="user5" />
</list>
</property>
</bean>
<!--注入Set-->
<bean id="user7" class="com.zyf.entity.User">
<property name="friends">
<set>
<value>赵已峰</value>
<ref bean="user5" />
</set>
</property>
</bean>
<bean id="user8" class="com.zyf.entity.User" scope="singleton">
<property name="friends">
<map>
<entry key="赵已峰" value="赵已峰" />
<entry key="张三" value-ref="user1" />
</map>
</property>
</bean>
<bean id="user9" class="com.zyf.entity.User" scope="prototype">
<property name="friends">
<props>
<prop key="张三">张三</prop>
<prop key="李四"></prop>
</props>
</property>
</bean>
</beans>
自动装配:
<?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:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!--更加类型注入-->
<bean id="user1" class="com.zyf.entity.User" autowire="byType">
</bean>
<!--根据名称注入-->
<bean id="user1" class="com.zyf.entity.User" autowire="byName">
</bean>
<!--使用构造函数注入-->
<bean id="user1" class="com.zyf.entity.User" autowire="constructor">
<constructor-arg name="username" value="张三" />
<constructor-arg name="password" value="123" />
</bean>
<bean id="apple" class="com.zyf.entity.Apple">
<property name="type" value="红富士" />
</bean>
</beans>
<2>使用注解:
三种各类型的注解
(1)组件类型声明:
@Component,组件类型注解,说明当前对象需要被IoC容器管理,@Service,说明当前注解的对象是Service层中的对象,@Controller,说明当前对象是Controller层中的对象,@Repository标记当前对象属于持久层。可以使用value=beanName指定bean的名称
(2)注入对象
按照类型注入:@Autowired,@Inject 按照名称装配:@Named,@Resource。使用@Resource时,如果找不到对应bean则按照类型注入,可以指定注入哪个bean使用name = beanName ,如果不指定则注入与变量名相同的bean.
(3)元注解
@Primary,当按照类型注入时,可能会有多个同类型的bean @Primary指定先注入哪个bean
在使用注解时需要开启包扫描
@PostConstruct:指定初始化方法
@PreDestroy:指定销毁方法
@Scope:指定Scope属性
@Value:注入属性值,可以编写config.properties文件
value=****
在applicationContext.xml中添加<context:property-placeholder location="classpath:config.properties" />
。
然后用@Value(${value})访问值。
<?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:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"
default-init-method="init" default-destroy-method="destroy">
<context:property-placeholder location="classpath:config.properties" />
<context:component-scan base-package="com.zyf" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
</beans>
use-default-filters
默认值为true,会对所有的标签签进行扫描,设则为false则对说有的标签都不会扫描。include-filter
指定需要扫描那些标签,type
设则为annotation时,指定需要对那些种类的标签进行扫描,若type
设为regex,则按照正则表达式进行过滤,若type
设则为assignable则对指定类名进行过滤。相对的,exclude-filter
不对指定包进行扫描。
<3>使用JavaConfig:使用java类来替代xml文件
@Configuration:声明当前类是一个配置类
@Ben:声明当前方法返回一个bean
@ComponentScan:作用类似与<context:component-scan />
@ImporResource:导入配置文件
import com.zyf.entity.Apple;
import com.zyf.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Component;
@Configuration
@ComponentScan(basePackages = "com.zyf")
@ImportResource(locations = "beanFactory.xml")
public class Config {
@Bean
public Apple apple(){
return new Apple();
}
//注入对象
@Bean
public User user(Apple apple){
User user = new User();
user.setApple(apple);
return user;
}
}
获取bean:
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
spring中集合测试框架
引入spring-test和junit4依赖,在测试类上添加
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:beanFactory.xml")
Spring AOP
AOP(Aspect Oriented Programming)即面向切面编程,将与业务无关通用的功能抽象封装为切面类,在需要使用时实现即插即用。
Spring AOP的实现方式有两种:基于JDK动态代理,基于Cglib。
JDK动态代理:JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法
先举个列子,对于User接口的实现类添加前置和后置通知。
User类
public interface User {
public void say();
}
Student类
public class Student implements User{
public void say(){
System.out.println("I am a student");
}
}
UserProxy代理类
public class UserProxy implements InvocationHandler {
Object target;
public UserProxy(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置处理");
Object object = method.invoke(target, args);
System.out.println("后置处理");
return object;
}
}
在主函数中创建代理对象
public static void main(String[] args) {
User user = new Student();
Class<?> clazz = user.getClass();
UserProxy userProxy = new UserProxy(user);
User user1 = (User) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), userProxy);
user1.say();
}
实现原理:使用了反射动态的创建了类,这个类继承了UserProxy并且实现了User(这也是为什么动态代理要求必须实现接口)。这个类实现了insert方法,在insert方法里面调用了UserProxy的invok方法。看一下这个类的具体实现:
参考:
https://blog.csdn.net/weixin_36759405/article/details/82770422
https://blog.csdn.net/mocas_wang/article/details/109921778
public final class $Proxy0 extends Proxy implements User {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void say() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.zyf.proxy.User").getMethod("say");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
CGLIB则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口
AOP的重要概念:
Aspect:具体的可插拔组件功能类
Target class/method:目标类,目标方法(真正执行与业务相关的方法)
PointCut:切入点,说明切面要作用在那些类上
JointPoint:连接点,包含了目标类方法的元数据对象
Advice:切面作用的时机
AOP配置过程:
依赖AspectJ,AspectJ是由Eclipse提供的面向切面编程的工具包,Spring AOP在底层需要依赖AspectJ。
实现切面方法类,切面方法类不需要基础任何对象或者实现任何接口,但里面的切面方法必须传入JoinPoint(注意不是joinpoint)
public class MyAspect {
public void before(JoinPoint joinPoint){
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
System.out.println("在" + className + "." + methodName + "前执行");
}
public void after(JoinPoint joinPoint){
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
System.out.println("在" + className + "." + methodName + "后执行");
}
//after returning 通知对应的切面方法需要有传入返回值
public void afterReturning(JoinPoint joinPoint, Object ret){
System.out.println("返回" + ret);
}
//after throwing 通知对应的切面方法需要传入Throwable对象
public void afterThrowing(JoinPoint joinPoint, Throwable th){
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
System.out.println("在" + className + "." + methodName + "抛出异常");
}
//around 通知传入的是ProceedingJoinPoint,并且需要返回值
public Object surround(ProceedingJoinPoint joinPoint) throws Throwable {
try {
long startDate = new Date().getTime();
Object obj = joinPoint.proceed();
long endDate = new Date().getTime();
System.out.println("执行时长:" + (endDate - startDate));
return obj;
} catch (Throwable throwable) {
throw throwable;
}
}
}
配置切面类的bean(与正常的bean的定义相同)
public class MyAspect{
public void before(){
System.out.println("before");
}
public void after(){
System.out.println("after");
}
public void afterThrowing(){
System.out.println("afterThrowing");
}
public void afterThrowing(){
System.out.println("afterThrowing");
}
public void surround(){
System.out.println("surround");
}
}
定义切点
配置通知,通知有before,after(方法结束后执行,不论有没有异常都会执行),after-returning方法返回或执行,after-throwing抛出异常时执行,around在目标方法周围执行
<?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:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="apple" class="com.zyf.entity.Apple">
<constructor-arg name="type" value="红富士" />
</bean>
<bean id="user" class="com.zyf.entity.User">
<constructor-arg name="username" value="张三"/>
<constructor-arg name="password" value="123" />
<constructor-arg name="apple" ref="apple" />
</bean>
<bean id="myAspect" class="com.zyf.aspect.MyAspect" />
<!--配置aop-->
<aop:config>
<!--定义切点-->
<aop:pointcut id="pointcut" expression="execution(public * com.zyf..*.eat(..))" />
<aop:pointcut id="exceptionPointcut" expression="execution(public * com.zyf..*.throwException(..))" />
<aop:pointcut id="allPointcut" expression="execution(public * com.zyf..*.*(..))" />
<!--定义切面-->
<aop:aspect ref="myAspect">
<!--切面的作用时机和作用地点-->
<aop:before method="before" pointcut-ref="pointcut" />
<aop:after method="after" pointcut-ref="pointcut" />
<!--returning说明返回值由哪个参数接收-->
<aop:after-returning method="afterReturning" returning="ret" pointcut-ref="pointcut" />
<aop:after-throwing method="afterThrowing" throwing="th" pointcut-ref="exceptionPointcut" />
<aop:around method="surround" pointcut-ref="pointcut" />
</aop:aspect>
</aop:config>
</beans>
使用注解配置AOP
在XML文件中配置开启扫描,启用AOP
<context:component-scan base-package="com.zyf" />
<aop:aspectj-autoproxy />
在切面类上添加@Aspect注解
在切面方法上添加@Before,@After,@AfterReturning,@AfterThrowing,@Around
Spring JDBC
1、配置步骤:
(1)引入spring-jdbc和mysql-connector-java依赖
(2)配置数据源并将数据源注入JabcTemplate
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/example?userUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<!-- 将数据源注入JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<context:component-scan base-package="com.zyf" />
(3)编写CRUD
@Repository
public class UserDao {
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private DataSourceTransactionManager transactionManager;
public User findById(Integer id){
String sql = "select * from user where id = ?";
User user = jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<User>(User.class));
return user;
}
public List<User> findByName(String name){
String sql = "select * from user where name = ?";
List<User> list = jdbcTemplate.query(sql, new Object[]{name}, new BeanPropertyRowMapper<User>(User.class));
return list;
}
public List<Map<String, Object>> findMapByName(String name){
String sql = "select name as studentName from user where name = ?";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, new Object[]{name});
return list;
}
public int insert(User user){
String sql = "insert into user(name) values(?)";
return jdbcTemplate.update(sql, new Object[]{user.getName()});
}
public int update(String originalName, String currentName){
String sql = "update user set name = ? where name = ?";
return jdbcTemplate.update(sql, new Object[]{currentName, originalName});
}
public void batchInsert(){
String sql = "insert into user(name) values(?)";
TransactionDefinition definition = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(definition);
try {
for(int i = 0; i < 10; i++){
if(i == 5)
throw new RuntimeException("运行时异常");
jdbcTemplate.update(sql, new Object[]{"user" + i});
}
transactionManager.commit(status);
}catch (Exception e){
transactionManager.rollback(status);
}
}
public void batchUpdate(){
String sql = "insert into user(name) values(?)";
for(int i = 0; i < 10; i++){
if(i == 5)
//throw new RuntimeException("运行时异常");
jdbcTemplate.update(sql, new Object[]{"user" + i});
}
}
}
2、事务管理
编程式事务:
<1>创建TransactionManager管理事务,并交由IoC容器管理
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<2>定义一个事务TransactionDefinition definition = new DefaultTransactionDefinition();
,这里定义事务指定是对事务进行配置,也就是设置事务的隔离级别,传播方式,超时时间等,这里使用的是默认配置。
<3>获取事务转态TransactionStatus status = ((DataSourceTransactionManager) transactionManager).getTransaction(definition);
,这里的事务状态才真正对应着一个事务。
<4>提交事务transactionManager.commit(status)
,
<5>回滚事务transactionManager.rollback(status)
。
申明式事务:使用aop来实现事务管理
XML形式:
<!-- 配置事务 -->
<tx:advice id = "txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--作用在哪个方法上,事务传播方式-->
<tx:method name="batchUpdate" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 指明事务的作用范围 -->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(public * com.zyf.dao.UserDao.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
</aop:config>
注解形式,开启事务的注解配置
<tx:annotation-driven transaction-manager="transactionManager" />
在方法或者类上添加@Transactional注解,若在类上添加则作用与这个类的所有方法,若在方法上添加则作用与这个方法(优先级更高)。
事务传播类(多个拥有事务的方法在嵌套调用时事务的控制方式)型的七种行为
<5>事务的隔离级别
spring的事务隔离级别:读未提交、读已提交、可重复读、串行化
设置事务的隔离级别:@Transactional(isolation = Isolation.DEFAULT)
3、事务失效的八种情况
- 数据库不支持事务:Spring使用的是数据的事务,如果数据库不支持事务那Spring也没办法支持事务。
- 没有被Spring关联:添加事务的方法所在的类必须要成为Spring容器中的bean,将@Service注释掉将不支持事务。
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
}
- 方法不是public的:@Transactional注解只能作用于public方法上,如果不是public方法可以开启AspectJ 代理模式。
- 自身体调用问题:下面的例子中,即使被调用的函数insert()的事务隔离级别设置的是ServiceB ,二者用的还是同一个事务,因为调doSomething()时处在同一个类中,并没有调用代理类的方法。
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void doSomething(){
serviceB.insert();
调用其他系统;
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insert(){
向数据库中添加数据;
}
}
解决方法:
<1>将insert()单独写在一个类中,调用这个类的对象的insert()方法
<2>在spring配置文件中添加如下配置:
<aop:aspectj-autoproxy expose-proxy="true"/>
//或者下面配置
<aop:config expose-proxy="true">
代码中使用,调用代理类的方法
((ServiceA ) AopContext.currentProxy()).insert();
https://editor.csdn.net/md/?articleId=124519927
- 数据源没有配置事务管理器
- 不支持事务:设置事务的传播方式为@Transactional(propagation = Propagation.NOT_SUPPORTED)。
- 异常被吃:Spring只能处理抛出的异常,如果用户自己捕捉了异常则Spring没法感知到。下面的方法在发生异常后Spring并不会进行回滚操作。
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
}
}
}
- 异常类型错误:Spring默认只能回滚RuntimeException,如果需要回滚其他类型的异常则需要配置一下。
@Transactional(rollbackFor = Exception.class)
参考链接
https://blog.csdn.net/weixin_43564627/article/details/121354260
Spring MVC
Spring MVC就是Spring对Servlet进行了封装,在使用Servlet时所有的页面访问都是由web容器控制的,WEB容器通过加载web.xml实现对页面的控制访问,所有的页面访问的控制权都在web容器,如何将控制权交由spring呢?一个简单的做法就是由spring实现一个servlet,并且这个servlet拦截所有的请求,这个servlet就是DispatcherServlet。理解了这点,就对spring mvc有了整体上的认识,下面来看看具体实现。
引入spring mvc的依赖(spring-webmvc)。
在web.xml配置servlet,让DispatcherServlet处理所有请求。使用spring我们需要初始化Spring容器所以在创建DispatcherServlet,我们需要加载配置文件,并且需要初始化容器。
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
通过以上配置,控制权就完全交给spring了,接下来只要学习spring封装了servlet的哪些操作就行了。
在servlet中,对于controller,我们需要继承HttpServlet并且重写doGet和doPost方法,并且通过HttpServletRequest对象获取从客户端传入的数据,然后通过HttpServletResponse 写入数据返回给客户端。
获取数据时我们需要通过getParameter("name")
方法,spring替我们封装了这个操作。
首先在applicationContext中开启注解模式,需要过滤掉静态资源,因为我们拦截了所有请求,如果不过滤掉静态资源需要编写大量的controller来处理这些请求。
<context:component-scan base-package="com.zyf.controller" />
<mvc:annotation-driven />
<!-- 过滤掉静态资源 -->
<mvc:default-servlet-handler />
编写controller,我们不需要再重写servlet,@Controller将控制器交由容器管理。@GetMapping,@PostMapping,@RequestMapping指定要处理的请求,其中@GetMapping只能处理get请求,@PostMapping只能处理post请求,@RequestMapping可以处理get和post请求。@ResponseBody指定返回值作为类容发送给客户端,若不加@ResponseBody则表示返回的值指向某个资源。
@Controller
public class MyController {
@GetMapping("/g")
@ResponseBody
public String doGet(){
return "success";
}
}
获取请求的类容不需要再显示编程,spring mvc会自动将值传入方法内规则如下:
假设请求的中的类容为key = value
,spring mvc 会将value注入controller方法中变量名为key的变量中(如果变量是一个自定义对象,则会注入这个对象的key 属性中),如果类型不同则会自动进行类型转换。可以使用@RequestParam(value = "username", defaultValue = "123") String username1
指定将变量名为username的变量注入String username1,defaultValue = "123"设置如果username不存在则将"123"注入username1。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="get" action="/g">
<input type="text" name="username"/>
<input type="password" name="password" />
<input type="submit" name="提交" />
</form>
</body>
</html>
@Controller
public class MyController {
@GetMapping("/g")
@ResponseBody
public String doGet(@RequestParam(value = "username", defaultValue = "123") String username1, String password){
System.out.println(username1 + ":" + password);
return "success";
}
}
也可以用bean来接收数据
public class User {
private String username;
private String password;
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;
}
}
@Controller
public class MyController {
@GetMapping("/g")
@ResponseBody
public String doGet(User user){
System.out.println(user.getUsername() + ":" + user.getPassword());
return "success";
}
}
请求中传入的可能是一个数组(如checkbox),需要使用数组来接收,如果用List来接收需要再变量前面添加@RequestParam注解。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="get" action="/g">
<input type="text" name="username"/>
<input type="password" name="password" />
<div>
<input type="checkbox" name="hobby" value="singing" />singing
<input type="checkbox" name="hobby" value="dancing" />dancing
</div>
<input type="submit" name="提交" />
</form>
</body>
</html>
@Controller
public class MyController {
@GetMapping("/g")
@ResponseBody
public String doGet(User user, @RequestParam List<String> hobby){
System.out.println(user.getUsername() + ":" + user.getPassword());
for (String s : hobby)
System.out.println(s);
return "success";
}
}
对象关联,可以使用一个对象来接收数据并且这个对象中的变量含有自定义的变量
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="get" action="/g">
<input type="text" name="username"/>
<input type="password" name="password" />
<div>
<input type="checkbox" name="hobby" value="singing" />singing
<input type="checkbox" name="hobby" value="dancing" />dancing
</div>
<div>
<h1>住址</h1>
<input type="text" name="address.country" />country
<input type="text" name="address.city" />city
</div>
<input type="submit" name="提交" />
</form>
</body>
</html>
package com.zyf.entity;
public class Address {
private String country;
private String city;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"country='" + country + '\'' +
", city='" + city + '\'' +
'}';
}
}
public class User {
private String username;
private String password;
private List<String> hobby;
private Address address;
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 List<String> getHobby() {
return hobby;
}
public void setHobby(List<String> hobby) {
this.hobby = hobby;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
@Controller
public class MyController {
@GetMapping("/g")
@ResponseBody
public String doGet(User user){
System.out.println(user.getAddress());
return "success";
}
}
日期转换,如果接收的日期需要按照指定的格式去解析日期,再参数前添加@DateTimeFormat(pattern = “yyyy-MM-dd”)注解
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="get" action="/g">
<input type="text" name="date" />
<input type="submit">
</form>
</body>
</html>
@Controller
public class MyController {
@GetMapping("/g")
@ResponseBody
public String doGet(@DateTimeFormat(pattern = "yyyy-MM-dd")Date date){
System.out.println(date);
return "success";
}
}
通过编写converter类并且进行配置可以对所有日期类型数据进行自动转换。
@Component
public class MyDateConverter implements Converter<String, Date> {
public Date convert(String source) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
return sdf.parse(source);
}catch (Exception e){
return null;
}
}
}
配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mv="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.zyf" />
<mvc:annotation-driven conversion-service="converters"/>
<mvc:default-servlet-handler />
<bean id="converters" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="myDateConverter" />
</set>
</property>
</bean>
<bean id="myDateConverter" class="com.zyf.converter.MyDateConverter" />
</beans>
解决中文乱码:
默认情况下客户端以utf-8发送数据服务器端以ISO8859-1接收数据,服务器端以ISO8859-1发送数据而客户端以GB2312接收数据。
接收数据:
如果是get请求则修改tomcat的配置文件,设置再Connector中增加URIEncoding=utf-8,再tomcat8.0以后不需要设置,已经解决了get请求乱码问题。
如果是post请求则需要添加过滤器(web.xml),对编码进行处理
<filter>
<filter-name>characterFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
响应解决乱码问题,再applicationContext中添加
<mvc:annotation-driven conversion-service="converters">
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=utf-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<mvc:default-servlet-handler />
在使用servlet时我们需要在controller中编写页面代码或者手动实现页面和逻辑的解耦,spring mvc封装这一部分的操作。
在方法上添加@ResponseBody注解表明当前的返回值就是页面类容,不添加这个注解则表示返回的时转发或者重定向的目标资源。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>I'm a view page</h1>
<h2>username:${username}</h2>
</body>
</html>
@Controller
public class MyController {
@PostMapping("/g")
public ModelAndView doGet(){
ModelAndView modelAndView = new ModelAndView("/view.jsp");
modelAndView.addObject("username", "张三");
return modelAndView;
}
}
或者
@PostMapping("/g")
public String doGet(ModelMap modelMap){
String url = "/view.jsp";
modelMap.addAttribute("username", "张三");
return url;
}
上面的两种方式都是转发,可以使用"redirect:/view.jsp"替换"/view.jsp"实现重定向。
servlet中还有一个重要的功能:过滤器(filter)。spring mvc实现了类似过滤器功能:拦截器(interceptor)。拦截器与过滤器的功能类似,但实现方式不同,拦截器是基于spring aop实现的。
实现HandlerInterceptor 类
public class MyInterceptor implements HandlerInterceptor {
//前置拦截处理,如果返回false则不会对目标资源进行访问
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("前置处理");
return true;
}
//目标资源访问完后执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("后置处理");
}
//处理完视图和模型数据,渲染视图完毕之后执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("返回处理");
}
}
配置xml文件,在applicationContext.xml中添加。
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截对哪些资源的访问 -->
<mvc:mapping path="/**"/>
<!-- 不对哪些资源的访问进行拦截 -->
<mvc:exclude-mapping path="/**.html" />
<mvc:exclude-mapping path="/**.css" />
<bean class="com.zyf.interceptor.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
Restful开发规范:
使用URL作为用户交互入口
明确的语义规范,(GET|POST|PUT|DELETE)分别对应与(查询|新增|修改|删除)
只返回数据(JSON|XML)不包含展现
restful命名要求
spring mvc中除了支持get(@GetMapping)和post(@PostMapping)请求外还支持put(@PutMapping)和delete(@DeleteMapping)
@RestController会设置当前类的每个方法的返回值作为响应体,相当于在每个方法上都添加@ResponseBody。
如果发送的URI中存在变量,比如/article/1,其中1作为变量。在controller中的对应方法上可以添加如@GetMapping(“/article/{rid}”),然后再参数上添加@PathVariable(“rid”)来接收数据。
GET和POST是简单请求,其余请求为非简单请求,非简单请求需要预检。spring一开始只支持GET和POST简单请求,对于非简单请求需要添加过滤器才能正确接收数据。
<filter>
<filter-name>formContentFilter</filter-name>
<filter-class>org.springframework.web.filter.FormContentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>formContentFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
jackson序列化
添加jackson-databind,jackson-core,ackson-annotations的依赖,当返回的实体对象时,spring mvc会自动对对象进行序列化。
Ajax的同源策略与跨域访问。
同源策略:阻止从一个域上加载的脚本去获取另一个域的资源。只要协议,域名,端口号有一个不同就是不同源。但,
<mvc:cors>
<mvc:mapping path="/t" allowed-origins="http://localhost" max-age="100" />
</mvc:cors>
Spring面试题
一、Spring基础
- Spring是什么
是一个分层的、一站式轻量级开源框架,为java应用提供全面的基础架构支持。 - Spring的优点
<1>降低了组件之间的耦合
<2>对主流框架提供了支持
<3>轻量级并且侵入性小(使用了AOP,无需修改代码) - Spring中的设计模式
<1>工厂模式:Spring的IOC容器也就是BeanFactory的实现类,FactoryBean的实现类
<2>单例模式:Spring中作用范围为Singleton的Bean
<3>代理模式:Spring的AOP
<4>观察者模式:Spring中的事件模型(可以看作观察者模式的扩展)
<5>模板方法模式:如JDBCTemplate,执行sql的过程都是类似的,JDBCTemplate为我们封装了算法的流程,我们只需要提供要执行的sql语句即可。这里JDBCTemplate的实现与经典的模板方法不太相同,经典的模板方法是有子类实现,而这里我们只需要传入sql语句。
<6>适配器模式:Spring MVC中的HandlerAdapter就是用了适配器模式,它将handler包装成一个同意的
<7>策略模式:JDBCTemplate中使用了策略模式,通过更换DataSource来实现对不同数据库的访问。 - Spring中的容器:
<1>BeanFactory:一个接口,所有的容器继承自BeanFactory
<2>XmlBeanFacotry:实现了BeanFactory
<3>ApplicationContext:基础自BeanFactory,有FileSystemXmlApplicationContext(需要提供文章的路径),ClassPathXmlApplicationContext(从CLASSPATH下搜索),WebXmlApplicationContext(从web程序范围内搜索)。 - BeanFacotry和FactoryBean的区别
<1>BeanFacotry:Spring容器最底层的接口,本质上是一个管理对象的工厂(对象的装配工厂),最主要的任务是管理对象的依赖。BeanFacotry的实现是一个IOC容器。
<2>FacotryBean:本质上也是一个工厂,最主要用来生产对象(对象的生产车间),比如ProxyFactoryBean,他极大简化了对象的产生,但是他并不负责管理对象,也不是IOC容器。
二、Spring中的Bean - bean的作用范围
<1>singleton:单例,只会创建一个,在容器初始化时就会创建。适用于无状态对象。
<2>Prototype:每次获取bean都会创建一个。适用于有状态对象。
<3>Request:每次Http请求都会创建一个。
<4>Session:同一个session共享一个Bean。
<5>Global Session:一个全局的Session中有一个对象。