框架系列——Spring
Spring 框架
1.核心概念
1.1 AOP 面向切面编程(Aspect Oriented Programming)
将一些通用的逻辑集中实现,然后通过 AOP 进行逻辑的切入,减少了零散的碎片化代码,提高了系统的可维护性。
具体的含义可以理解为:通过代理的方式,在调用想要的对象方法时候,进行拦截处理,执行切入的逻辑,然后再调用真正的方法实现。
1)代理模式
静态代理
在编译时或者类加载时进行切面的织入,典型的 Aspect就是静态代理。
package com.dao;
public interface UserDao {
void login();
void add();
void update();
void search();
}
package com.dao.impl;
import com.dao.UserDao;
public class UserDaoImpl implements UserDao {
public void login() {
System.out.println("login 执行了");
}
public void add() {
System.out.println("add 执行了");
}
public void update() {
System.out.println("update 执行了");
}
public void search() {
System.out.println("search 执行了");
}
}
package com.aop;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.time.LocalDateTime;
import com.dao.UserDao;
import com.dao.impl.UserDaoImpl;
public class UserDaoProxy implements UserDao {
//被代理的地象(委托对象)
private UserDaoImpl impl=new UserDaoImpl();
private String logPath="log.txt";
public void add() {
impl.add();
try {
BufferedWriter bw=new BufferedWriter(new FileWriter(logPath,true));
bw.write(LocalDateTime.now()+"add 方法调用了 ");
bw.newLine();
bw.close();
}
catch(Exception ex) {
ex.printStackTrace();
}
}
public void update() {
impl.update();
try {
BufferedWriter bw=new BufferedWriter(new FileWriter(logPath,true));
bw.write(LocalDateTime.now()+"update 方法调用了 ");
bw.newLine();
bw.close();
}
catch(Exception ex) {
ex.printStackTrace();
}
}
public void search() {
long begin=System.currentTimeMillis();
impl.search();
long end=System.currentTimeMillis();
System.out.println("search 这个查询执行了"+(end-begin) +" ms ");
}
public void login() {
long begin=System.currentTimeMillis();
impl.login();
long end=System.currentTimeMillis();
System.out.println("login 执行了"+(end-begin) +" ms ");
}
}
package com.aop;
import com.dao.UserDao;
public class UserDaoProxyFactory {
public static UserDao getUserDaoProxy() {
return new UserDaoProxy();
}
}
package com.test;
import com.aop.UserDaoProxyFactory;
import com.dao.UserDao;
import com.dao.impl.UserDaoImpl;
public class Test1 {
public static void main(String[] args) {
//UserDao dao=new UserDaoImpl();
UserDao dao=UserDaoProxyFactory.getUserDaoProxy();
dao.add();
dao.update();
dao.login();
dao.search();
}
}
上面的程序,最关键的:
1) 代理类和委托类实现的是同一个接口
2) 代理类中,要有一个委托类的对象
静态代理的缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
动态代理
需要手写代码,JDK动态代理和CGLIB,区别等
1.2 IOC 控制反转 (Inversion of Control)
应用不负责依赖对象的创建,而是把它们的创建的控制权,交给外部容器,控制权的转移就是控制反转
class UserServlet{
@Resource/@Autowired
private UserDao dao;
}
1)Bean 的注入方式
setter方式
package com.dao;
public interface UserDao {
void login();
void add();
void update();
void search();
}
package com.dao.impl;
import com.dao.UserDao;
public class UserDaoImpl implements UserDao{
public void login() {
System.out.println("UserDao login方法普通实现");
}
public void add() {
System.out.println("UserDao add方法普通实现");
}
public void update() {
System.out.println("UserDao update方法普通实现");
}
public void search() {
System.out.println("UserDao search方法普通实现");
}
}
package com.servlet;
import com.dao.UserDao;
public class UserServlet {
private UserDao dao;
//这个就是setter方法,由spring容器调用,将依赖对象注入进来
public void setDao(UserDao dao) {
System.out.println("setDao方法被容器调用了");
this.dao = dao;
}
public void service() {
dao.add();
dao.login();
dao.update();
dao.search();
}
}
配置文件
<bean name="userServlet" class="com.servlet.UserServlet">
<property name="dao" ref="userDao" />
</bean>
<bean name="userDao" class="com.dao.impl.UserDaoImpl" />
构造器方式
Spring 倡导构造函数注入,因为构造器注入返回给客户端使用的时候一定是完整的。
2)Bean 的作用域
- singleton:默认是单例,含义不用解释了吧,IOC 容器内部仅此
- prototype:原型,多实例
- request:每个请求都会新建一个属于自己的 Bean 实例,这种作用域仅存在 Spring Web 应用中
- session:一个 http session 中有一个 bean 的实例,这种作用域仅存在 Spring Web 应用中-
- application:整个 ServetContext 生命周期里,只有一个 bean,这种作用域仅存在 Spring Web 应用中
- websocket:一个 WebSocket 生命周期内一个 bean 实例,这种作用域仅存在 Spring Web 应用中
2. 使用方式
2.1 名称空间
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
</beans>
第一行 <?xml version="1.0" encoding="UTF-8"?> 这叫xml文档声明
要注意,它的格式非常固定,而且它必须位于第一行,前面不能任何内容 (包扩空格和注释)
第二行 xmlns="http://www.springframework.org/schema/beans"
xmlns 是 xml namespace 的简写,称为名称空间,后面跟的"http://www.springframework.org/schema/beans" 是名称空间的名称,每个名称空间名称,都对应着一个约束文件(Schema 语法) , 可以在后面找到:
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ... "
第四行 xmlns:aop="http://www.springframework.org/schema/aop" 后面的:aop 是指明名称空间前缀,如果有前缀,则引用
该名称空间中的内容的时候,要加上前缀,比如 <aop:config></aop:config>
如果没有前缀的,称为默认名称空间
2.2 注解装配
- 引入context名称空间
- 开启<context:annotation-config />
- 注解声明依赖关系 例如:
@Resource(name = "userSuperDao") //如果 UserDao 接口的实现类只有一个,则 name 属性可以省去
private UserDao dao;
补充说明:
@Resource 注解:
① 它是来源于 import javax.annotation.Resource; 它不属于spring ,它是jdk中的
② 默认它是按名称装配,然后再按类型装配置,如果指明了名称,则严格按名称进行装配置
③ 它也可以写在 set方法上
④ 从jdk8以后,使用 @Resource 注解要额外导包
@Autowired 注解
① 它是 import org.springframework.beans.factory.annotation.Autowired; 它属于spring
② 默认它是按类型进行装配置
③ 如果想按名称进行装配,则要和@Qualifier注解配合使用 @Autowired @Qualifier("userSuperDao")
④ 默认情况下,它不注入null值 如果允许,可以 @Autowired(required = false)
2.3 自动扫描
- 引入 context 名称空间
- 开启自动扫描
<context:component-scan base-package="com.servlet" />
<context:component-scan base-package="com.dao" />
<context:annotation-config /> //这个可以去掉了,因为开启自动扫描以后,相当于自动加入了这个配置
它会自动扫描指定的包和子包下的带有以下注解的类:
@Controller //控制层
@Service //用于业务层
@Repository //持久层
@Component //其他
3.事务管理
3.1 注解方式
①引入名称tx空间
<?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:tx="http://www.springframework.org/schema/tx"
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-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
....
②配置一个事务管理器
<context:component-scan base-package="com" />
<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/cms?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean name="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
③开启注解管理事务
<tx:annotation-driven transaction-manager="txManager"/>
④在方法上加相关的注解
@Transactional
public int addUser(UserInfo user) {
String sql="insert into userInfo (userName,password,note) values (?,?,?)";
int r= t.update(sql,user.getUserName(),user.getPassword(),user.getNote());
int a=9/0;
return r;
}
然后再运行程序,发现在程序出错的情况下,数据库中是不会添加数据的
@Transactional 注解可以写在方法上,也可以写在类体上
事务的传播行为: (其实讨论的就是 propagation 的取值情况)
-
----REQUIRED:业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务。
-
----NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。
如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。 -
----REQUIRESNEW:(requiresnew) 属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。
如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行 -
----MANDATORY (mandatory,强制的,命令的,受委托的):该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。
-
----SUPPORTS:这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。
-
----Never:指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
-
----NESTED:nested (窝,嵌套)如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效
3.2 xml方式
<bean name="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<aop:config>
<aop:pointcut expression="execution(* com.dao.UserDaoImpl.*(..))" id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" propagation="NOT_SUPPORTED" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>