传统Web开发存在硬编码所造成的过度程序耦合(例如:Service中作为属性Dao对象)。
部分Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)。
侵入性强,移植性差(例如:DAO实现的更换,从Connection到SqlSession)。
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器(框架)。
Spring是一个项目管理框架,同时也是一套Java EE解决方案。
Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。
Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称"胶水框架"。
Spring架构由诸多模块组成,可分类为
核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP。
测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
数据访问:事务,DAO支持,JDBC,ORM,封送XML。
Spring MVC和 Spring WebFlux Web框架。
集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
语言:Kotlin,Groovy,动态语言。
GroupId | ArtifactId | 说明 |
---|---|---|
org.springframework | spring-beans | Beans 支持,包含 Groovy |
org.springframework | spring-aop | 基于代理的AOP支持 |
org.springframework | spring-aspects | 基于AspectJ 的切面 |
org.springframework | spring-context | 应用上下文运行时,包括调度和远程抽象 |
org.springframework | spring-context-support | 支持将常见的第三方类库集成到 Spring 应用上下文 |
org.springframework | spring-core | 其他模块所依赖的核心模块 |
org.springframework | spring-expression | Spring 表达式语言,SpEL |
org.springframework | spring-instrument | JVM 引导的仪表(监测器)代理 |
org.springframework | spring-instrument-tomcat | Tomcat 的仪表(监测器)代理 |
org.springframework | spring-jdbc | 支持包括数据源设置和 JDBC 访问支持 |
org.springframework | spring-jms | 支持包括发送/接收JMS消息的助手类 |
org.springframework | spring-messaging | 对消息架构和协议的支持 |
org.springframework | spring-orm | 对象/关系映射,包括对 JPA 和 Hibernate 的支持 |
org.springframework | spring-oxm | 对象/XML 映射(Object/XML Mapping,OXM) |
org.springframework | spring-test | 单元测试和集成测试支持组件 |
org.springframework | spring-tx | 事务基础组件,包括对 DAO 的支持及 JCA 的集成 |
org.springframework | spring-web | web支持包,包括客户端及web远程调用 |
org.springframework | spring-webmvc | REST web 服务及 web 应用的 MVC 实现 |
org.springframework | spring-webmvc-portlet | 用于 Portlet 环境的MVC实现 |
org.springframework | spring-websocket | WebSocket 和 SockJS 实现,包括对 STOMP 的支持 |
org.springframework | spring-jcl | Jakarta Commons Logging 日志系统 |
使用IDEA打开已创建的文件夹目录 |
选择Maven项目 |
GAV坐标 |
org.springframework
spring-context
5.2.14.RELEASE
junit
junit
4.13.2
test
org.projectlombok
lombok
1.18.22
位置: resources下
格式: xml
名称: 一般叫做applicationContext.xml、beans.xml、spring-context.xml、app.xml
定义Bean类型。
public class MyClass{
public void show(){
System.out.println("HelloWorld");
}
}
applicationContext.xml中的< beans >内部配置bean标签。
- id:唯一标识
- class:需要被创建的目标对象全限定名
调用Spring工厂API(ApplicationContext接口)
- 程序中的对象都交由Spring的ApplicationContext工厂进行创建。
public class TestFactory{
/**
* 程序中的对象都交由Spring的ApplicationContext工厂进行创建。
*/
public static void main(String[] args){
//1. 读取配置文件中所需创建的bean对象,并获得工厂对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 通过id获取bean对象
MyClass myClass = (MyClass) context .getBean("mc");
//3. 使用对象
myClass .show();
}
}
Spring框架包含多个模块,每个模块各司其职,可结合需求引入相关依赖Jar包实现功能。
Spring常用功能的Jar包依赖关系 |
注意:Jar包彼此存在依赖,只需引入最外层Jar即可由Maven自动将相关依赖Jar引入到项目中。
配置文件中的顶级标签中包含了语义化标签的相关信息
xmlns:语义化标签所在的命名空间。
xmlns:xsi:XMLSchema-instance 标签遵循Schema标签标准。
xsi:schemaLocation:xsd文件位置,用以描述标签语义、属性、取值范围等。
IOC控制反转,负责创建对象,管理对象,依赖注入的方式将创建的对象和对象之间的维护交由Spring管理。
Spring:Spring容器使用工厂模式创建对象,不用自己创建,直接调用即可。
- 反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)
public interface UserDao {
void findUserById();
}
public class UserDaoImpl implements UserDao {
@Override
public void findUserById() {
System.out.println("UserDaoImpl执行了");
}
}
public interface UserService {
void findUserById();
}
public class UserServiceImpl implements UserService {
// 强耦合了UserDAOImp!,使得UserServiceImpl变得不稳健!
private UserDao userDao= new UserDaoImpl();
@Override
public void queryUser() {
userDao.findUserById();
System.out.println("UserServiceImpl执行了");
}
....
}
public interface UserService {
void findUserById();
}
// 不引用任何一个具体的组件(实现类),在需要其他组件的位置预留存取值入口(set/get)
public class UserServiceImpl implements UserService {
// 不再耦合任何Dao实现!消除不稳健因素!
private UserDao userDao;
// 为userDao定义set/get方法,允许userDao属性接受spring赋值
// Getters And Setters
@Override
public void findUserById() {
System.out.println("UserServiceImpl.findUserById()执行" );
}
....
}
- 一个bean标签代表一个类,会创建该类对象
- class:需要被创建的目标对象全限定名
- id:创建完成后的对象名
此时,如果需要更换其他UserDao实现类,则UserServiceImpl不用任何改动!
则此时的UserServiceImpl组件变得更加稳健!
public class TsetIOC {
@Test
public void test(){
String path = "applicationContext.xml";
// 根据配置文件创建出spring容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(path);
// 从容器中取出对象(根据对象名获得对象,配置文件中bean的id)
UserService userService = (UserService) context.getBean("userService");
userService.findUserById();
}
}
依赖注入:在Spring创建对象的同时,为其属性赋值,称之为依赖注入。
创建对象时,Spring工厂会通过Set方法为对象的属性赋值。
创建实体类。
public class User {
private int id;
private String name;
private Date birthday;
private double score;
// get/set
}
创建实体类。
public class Info {
private String[] phone;
private List list;
private Set set;
private Map map;
// get/set
}
110
119
120
沃尔沃
魏派
魏派
大众
华为
小米
苹果
苹果
public class Object {
/**
* 对象类型
* 在Object类里给User赋值属性
*/
// 将上面User类当做Object类的属性
private User user;
// set/get
}
创建对象时,Spring工厂会通过构造方法为对象的属性赋值。
创建实体类。
public class Student {
private Integer id;
private String name;
private String sex;
private Integer age;
// 创建有参构造方法 Constructors
public Student(Integer id , String name , String sex , Integer age){
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
}
不用在配置中指定为哪个属性赋值,及赋什么值.
由spring自动根据某个 "原则" ,在工厂中查找一个bean,为属性注入属性值。
public interface UserDao {
void findUserById();
}
public class UserDaoImpl implements UserDao {
@Override
public void findUserById() {
System.out.println("UserDaoImpl执行了");
}
}
public interface UserService {
void findUserById();
}
public class UserServiceImpl implements UserService {
private UserDao userDao;
// get/set方法
@Override
public void findUserById() {
userDao.findUserById();
System.out.println("UserServiceImpl执行了");
}
}
applicationContext.xml文件配置。
applicationContext.xml文件配置。
总结:
- byName: 通过属性名和容器中对象名一致,即可自动注入
- byType: 属性类型和容器中对象的类型一致,即可自动注入
上面我们都是使用xml配置实现IOC和DI,以后将使用注解实现IOC和DI,即XML中不再配置很多标签了。
注解 | 作用 | 被替代标签 | 位置 |
---|---|---|---|
@Component | 创建对象 | 类上 | |
@Controller | 创建对象 | 控制层的类上 | |
@Service | 创建对象 | 业务层的类上 | |
@Repository | 创建对象 | 持久层的类上 | |
@Value | 给基本类型属性赋值 | 属性上 | |
@Autowired | 给引用类型属性赋值 | autowired的属性 | 属性上 |
注意:@Component、@Controller、@Service、@Repository都是用来创建对象,只不过建议是在相应的位置使用相应的注解。
@Component:默认对象名是类名小写
可以通过@Component(value="user")来指定对象名,但在测试中getBean("user")两个对象名要一致。
@Component // 替代,默认对象名就是类名小写
public class User {
@Value("109")
private int id;
@Value("YMK")
private String name;
@Value("1999/12/28")
private Date birthday;
@Value("95.2")
private double score;
// get、set、toString
}
applicationContext.xml开启注解,让注解生效。
@Test
public void test() {
// 根据配置文件创建出spring容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml");
// 从容器中取出对象(默认是类名小写)
User user = (User)context.getBean("user");
System.out.println(user);
}
@Scope:用户控制bean的创建模式,默认是单例模式。
@Scope("singleton"):设置单例模式
@Scope("prototype"):设置多例模式
public interface UserDao {
void findUserById();
}
@Repository // 创建对象,取代
public class UserDaoImpl implements UserDao {
@Override
public void findUserById() {
System.out.println("UserDaoImpl执行了");
}
}
public interface UserService {
void findUserById();
}
@Service // 创建对象,取代
@Scope("singleton") // 声明创建模式,默认为单例模式 @Scope("prototype")可设置多例模式
public class UserServiceImpl implements UserService {
@Autowired // 自动注入,按照类型注入byType
private UserDao userDao;
@Override
public void findUserById() {
userDao.findUserById();
System.out.println("UserServiceImpl执行了");
}
}
applicationContext.xml开启注解,让注解生效。
@autowired:自动注入,按照类型注入
按类型注入多个同类型,注入失败怎么解决?
- 当同类型的对象有多个时,无法自动注入,需要手动指定注入那个对象
- @Qualifier("userDaoImpl2"):限定要自动注入的bean的id,一般和@Autowired联用
@Service
public class UserServiceImpl implements UserService {
@Autowired // 自动注入,按照类型注入
// 当同类型的对象有多个时,需要使用@Qualifier指定注入的对象名
@Qualifier("userDaoImpl2")
private UserDao userDao;
// ...
}
配置< bean scope="singleton | prototype"/>
- singleton(默认):每次调用工厂,得到的都是同一对象
- prototype:每次调用工厂,都会创建新的对象
注意:需要根据场景决定对象的单例、多例模式。
- 可以共用:Service、Dao、SQLSessionFactory(或者是所有的工厂)
- 不可共用:Connection、SqlSession、ShoppingCart
作用:让Spring可以创建复杂对象,或者无法直接通过反射创建的对象。
FactoryBean解决复杂对象创建 |
接口方法描述 |
注意:isSingleton方法的返回值,需根据所创建对象的特点决定返回true/false。
- 例:Connection 不应该被多个用户共享,返回false。
- 例:SqlSessionFactory 重量级资源,不该过多创建,返回true。
配置与获取方式 |
获取FactoryBean接口的实现类对象,而非getObject()所生产的对象 |
- 工厂创建之后,会将Spring配置文件中的所有对象都创建完成(饿汉式)。
- 提高程序运行效率。避免多次IO,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)
自定义初始化方法:添加“init-method”属性,Spring则会在创建对象之后,调用此方法。
自定义销毁方法:添加“destroy-method”属性,Spring则会在销毁对象之前,调用此方法。
销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。
分类:Singleton对象由Spring容器销毁、Prototype对象由JVM销毁。
初始化注解、销毁注解。
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@PostConstruct //初始化
public void init(){
System.out.println("init method executed");
}
@PreDestroy //销毁
public void destroy(){
System.out.println("destroy method executed");
}
- 单例bean:singleton
随工厂启动创建——> 构造方法——> set方法(注入值) ——> init(初始化)——> 构建完成 ——> 随工厂关闭销毁
- 多例bean:prototype
被使用时创建——> 构造方法 ——> set方法(注入值) ——> init(初始化) ——> 构建完成 ——> JVM垃圾回收销毁
将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、服务业务功能可复用。
- 作用: 实现对目标对象原有的功能增强,即扩展目标对象的功能方法,并且不会修改原有代码。
- 分类:
- (1) 静态代理:在运行前,通过编写代码的方式生成代理类
- (2) 动态代理:在运行后,通过反射机制生成代理类
通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。
- 注意:一个代理只能做一件事,做其他事情,需要再创建新代理.。
代理类 = 实现原始类相同接口 + 添加辅助功能 + 调用原始类的业务方法
静态代理的问题:
- 代理类数量过多,不利于项目的管理
- 多个代理类的辅助功能冗余,修改时,维护性差
需求:实现房东出租
// 创建一个接口
public interface FangDong {
void zufang();
}
被代理者和代理者实现同一个接口。
/**
* 被代理类实现接口
*/
public class FangdongImpl implements FangDong {
@Override
public void zufang() {
System.out.println("房东出租房..." );
}
}
/**
* 代理者实现接口
*/
public class FangdongProxy {
// 目标对象
private FangDong fangDong;
// 有参构造方法,只要new FandDongProxy就要给被代理对象
public FangdongProxy(FangDong fangDong){
this.fangDong = fangDong;
}
/**
* 代理类
*/
void zufang(){
// 前置增强
System.out.println("前:发传单,找客源" );
// 房东出租
fangDong.zufang();
// 后置增强
System.out.println("后:签合同" );
}
}
测试
@Test
public void test1() {
// 找代理
FangDongProxy proxy = new FangDongProxy(new FangDongImpl());
//真正租房
proxy.zuFang();
}
动态代理会动态的为目标类产生代理对象,动态代理有两种实现方案:
- JDK动态代理,只能代理接口,即目标类要有接口
- CGLIB动态代理,可以代理接口,也可以代理类,目录类可以有接口,也可以没有接
JDK代理,是Java自有技术无需导包
需求:使用动态代理技术,动态的产生房东代理对象,汽车厂商的代理。
JDK代理需要实现InvocationHandler接口。
/**
* 这是代理的模板,可以创建出任何一个类的代理对象
*/
public class JDKProxy implements InvocationHandler {
// 目标类,这里给Object因为可以是任何类
private Object target;
// 有参构造方法,只要new JDKProxy就要给被代理对象
public JDKProxy(Object target){
this.target=target;
}
/**
* @param proxy 被代理对象
* @param method 目标方法
* @param args 目标方法的参数
* @return 目标方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 目标方法前
System.out.println("前置增强");
// 目标方法执行
Object obj = method.invoke(target, args);
//目标方法后
System.out.println("后置增强");
return obj;
}
}
1.房东出租接口和被代理类。
/**
* 接口
*/
public interface FangDong {
void zufang();
}
/**
* 被代理类
*/
public class FangDongImpl implements FangDong {
@Override
public void zuFang() {
System.out.println("房东出租房...");
}
}
测试。
@Test
public void test2() {
// 目标类的类加载器
ClassLoader classLoader = FangDongImpl.class.getClassLoader();
// 目标类实现的所有接口
Class>[] interfaces = FangDongImpl.class.getInterfaces();
// 目标方法执行处理器
JDKProxy proxy = new JDKProxy(new FangDongImpl());
/**
* 参数1:目标类的类加载器
* 参数2:目标类所实现的所有接口
* 参数3:JDKProxy的对象
*/
FangDong fangDong = (FangDong) Proxy.newProxyInstance(classLoader, interfaces, proxy);
fangDong.zuFang();
}
2.汽车厂卖车接口和被代理类。
/**
* 接口
*/
public interface CarFactory {
void seleCar();
}
/**
* 被代理类
*/
public class CarFactoryImpl implements CarFactory{
@Override
public void seleCar() {
System.out.println("汽车厂卖车...");
}
}
测试。
@Test
public void test3() {
ClassLoader classLoader = CarFactoryImpl.class.getClassLoader();
Class>[] interfaces = CarFactoryImpl.class.getInterfaces();
JDKProxy proxy = new JDKProxy(new CarFactoryImpl());
CarFactory carFactory = (CarFactory) Proxy.newProxyInstance(classLoader, interfaces, proxy);
carFactory.seleCar();
}
总结:动态代理,会动态产生目标类的代理对象,JDK的动态代理,目标必须有接口。
CGLIB动态代理技术,又叫字节码增强。动态产生代理对象,可以代理接口也可以代理实现类。CGLIB是第三方技术,spring框架中已经整合了cglib的技术,所以只需导入spring-aop的依赖即可。
需求:使用动态代理技术,动态的产生房东代理对象,汽车厂商的代理。
CGLIB代理需要实现MethodInterceptor接口
public class MyCglibInterceptor implements MethodInterceptor {
// cglib提供的增强工具类
private Enhancer enhancer = new Enhancer();
// 创建代理拦截器时,指定目标类的字节码
public MyCglibInterceptor(Class clazz){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
}
// 提供一个获得代理对象的方法
public Object createProxyBean(){
return enhancer.create();
}
/**
* @param target 目标对象
* @param method 目标方法
* @param args 目标方法的参数
* @param methodProxy 目标方法的代理对象
* @return 目标方法的返回值
* @throws Throwable
*/
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 目标方法执行前
System.out.println("前置增强:权限校验、事务开启");
// 目标方法执行
Object ret = methodProxy.invokeSuper(target, args);
// 目标方法执行前
System.out.println("后置增强:记录日志、事务提交");
return ret;
}
}
测试。
/**
* CGLIB动态代理
*/
@Test
public void test4() {
// 此处,只需要将其他类字节码文件传入此处就可得到其他类的代理对象
MyCglibInterceptor interceptor = new MyCglibInterceptor(FangDongImpl.class);
FangDong fangDong = (FangDong) interceptor.createProxyBean();
fangDong.zuFang();
}
总结:CGLIB动态代理,会动态的给类产生代理对象,目标类可以没有接口,也可以有接口,都可以代理。
- 代理的目的: 将目标方法功能增强
- 代理的方式: 静态代理和动态代理
- 区别:
(1)静态代理: 每个类都需要自己创建代理对象,一个代理只能代理一个类
(2)动态代理: 给每个类动态产生代理对象(按需产生)
- 动态代理如何实现:
(1)jdk的动态代理: jdk动态代理只能代理接口(目标类必须有接口)
(2)cglib的动态代理:可以代理接口,也可以代理类(目标类可以有接口,也可以没接口)
AOP即切面编程,利用一种称为“横切”的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为“Aspect”,即切面。
- AOP切面编程,在原有的基础上通过Aop添加新的功能,而原有的功能并不知道新添加的功能,简单来说就是:在某个方法或类执行前打个标记声明该干什么,之后执行什么,插入的新的方法。
面向切面编程的作用:
- 将项目中与核心逻辑无关的代码横向抽取成切面类,通过织入作用到目标方法,以使目标方法执行前后达到增强的效果。
优点:
- 可以对业务逻辑的各个部分进行隔离
- 抽取代码,复用,提供效率
- 减少耦合
- 利于代码扩展
- 目标类(Target): 被代理的类
- 连接点(JoinPoint): 目标类中准备被切入的方法
- 切入点(Pointcut): 真正执行的目标方法
- 切面(Aspect): 切面中定义中增强的方法
- 通知、增强(Advice): 在切入点中具体要做的事情,分为:前置通知、后置通知、异常通知、环绕通知等
- 织入(Weaving): 将增强的方法作用到目标方法的过程
- 代理(Proxy):被AOP织入通知后产生的结果类
5种通知的分类:
前置通知(Before Advice):在目标方法被调用前调用通知功能,场景:一般用来做权限校验
后置通知(After Advice):在目标方法被调用之后调用通知功能,场景: 释放资源,或者记录日志
返回通知(After-returning):在目标方法成功执行之后调用通知功能,场景:数据库事务
异常通知(After-throwing):在目标方法抛出异常之后调用通知功能,场景:得到目标方法返回值再处理
环绕通知(Around):把整个目标方法包裹起来,在被调用前和调用之后分别调用通知功能。 场景:可以获得目标方法的异常信息,用于记录日志,或者进行异常拦截
org.springframework
spring-context
5.1.6.RELEASE
org.springframework
spring-aspects
5.1.6.RELEASE
public interface UserService {
int addUser();
}
public class UserServiceImpl implements UserService {
@Override
public int addUser() {
System.out.println("UserServiceImpl的addUser()方法执行了");
// 返回值用来测试后置返回通知
return 999;
}
}
/**
* 切面类中定义各种增强的方法
*/
public class MyAspect {
/**
* 前置增强
* @param joinPoint 目标类对象
* @return
* @throws Throwable
*/
public void before(JoinPoint joinPoint) throws Throwable {
//目标方法前
Object target = joinPoint.getTarget();
System.out.println("前置增强,获得目标类对象:"+target);
Signature signature = joinPoint.getSignature();
System.out.println("前置增强,获得目标方法:"+signature);
String name = signature.getName();
System.out.println("前置增强,获得目标方法名:"+name);
System.out.println("前置增强:权限校验");
System.out.println("-----------前置通知执行完毕----------");
}
/**
* 环绕通知
* @param joinPoint 目标方法
* @return
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 目标方法前
System.out.println("前置增强:开启事务");
// 目标方法执行
Object ret = joinPoint.proceed();
// 目标方法后
System.out.println("后置增强:提交事务");
System.out.println("-----------环绕通知执行完毕----------");
return ret;
}
/**
* 后置通知
* @param joinPoint
*/
public void after(JoinPoint joinPoint){
// 应用场景:还可以做一些关流、释放资源
System.out.println("后置通知:记录执行的日志");
System.out.println("-----------后置通知执行完毕----------");
}
/**
* 后置返回通知
* 只能得到目标方法的返回值
* @param ret
* @return
*/
public Object afterReturn(Object ret){
System.out.println("后置返回通知:"+ret);
System.out.println("-----------后置返回通知执行完毕----------");
return ret;
}
/**
* 异常处理通知
* @param e
*/
public void MyThrow(Exception e){
// 可以接受到目标方法执行的异常信息
// 可以做全局异常处理,记录异常信息到日志
System.out.println("异常通知,获得异常信息:"+e.getMessage());
}
}
applicationContext.xml引入AOP命名空间。
配置通知时:
- method:切面类中的方法名
- pointcut:切入点,内写切入点表达式
- execution(* com.ymk.service.*.*(..))
- 第一个*:返回值的意思,返回值任意
- com.ymk.service.*:通过路径确定切入点所在类,*的意思是service包下所有类
- .*:方法名,*指所有方法
- ():方法的参数列表
- .. :方法的任意参数
@Test
public void testAop() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.addUser();
}
- @Service:让Spring创建对象,将该类交给Spring托管
- @Component:让Spring创建对象,将该类交给Spring托管,不需要写
标签了 - @Aspect:声明这是个切面
- @Pointcut("execution(* com.ymk.service.*.*(..))"):将表达式抽取出来,方便复用
- @Before("pointcut()"):前置通知
- @After("pointcut()"):后置通知
- @Around("pointcut()"):环绕通知
HouseService接口和HosueServiceImpl实现类。
public interface HouseService {
int updateHouse();
}
@Service // 交由sprng容器创建对象
public class HouseServiceImpl implements HouseService {
@Override
public int updateHouse() {
System.out.println("HouseServiceImpl.addHouse()执行");
return 8686;
}
}
创建切面类。
@Component // 交由spring创建bean对象
@Aspect //声明这是一个切面
public class MyAspect {
/**
* 抽取表达式,方便复用
*/
@Pointcut("execution(* com.ymk.service.*.*(..))")
public void pointCut(){}
/**
* 前置通知,里面写的是切入点表达式方法名,注意不要忘了带括号()
* @param joinPoint
*/
@Before("pointCut()")
public void before(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("获取目标方法名:"+name);
System.out.println("前置通知:权限校验");
System.out.println("-------前置执行完毕-------");
}
/**
* 环绕通知
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("前置增强:开启事务");
Object ret = joinPoint.proceed();
System.out.println("后置增强:提交事务");
System.out.println("-------环绕置执行完毕-------");
return ret;
}
/**
* 后置通知
* @param joinPoint
*/
@After("pointCut()")
public void after(JoinPoint joinPoint){
System.out.println("后置通知:记录执行的日志");
System.out.println("-------后置执行完毕-------");
}
/**
* 后置返回通知
* @param ret
*/
@AfterReturning(returning = "ret",pointcut = "pointCut()")
public void afterReturn(Object ret){
System.out.println("后置返回通知:"+ret);
System.out.println("-------后置返回执行完毕-------");
}
/**
* 异常处理通知
* @param e
*/
@AfterThrowing(throwing = "e",pointcut = "pointCut()")
public void myThrow(Exception e){
System.out.println("获取异常信息:"+e.getMessage());
System.out.println("-------异常处理执行完毕-------");
}
}
applicationContext.xml文件配置
测试
@Test
public void test() {
// 获得容器对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
// 从容器获得对象,对象名一定是类名首字母小写
HouseService houseService = context.getBean("houseServiceImpl", HouseService.class);
// 执行目标方法,目标方法前后都会执行增强方法
houseService.updateHouse();
}
注意:AOP的动态代理,底层使用了两种代理方案:
- 默认情况下使用JDK动态代理,及目标类必须有接口,JDK代理接口
- 当目标类没有实现接口时,自动切换使用CGLIB实现动态代理
mysql
mysql-connector-java
5.1.48
org.mybatis
mybatis
3.5.6
org.slf4j
slf4j-log4j12
1.7.25
javax.servlet
javax.servlet-api
4.0.1
javax.servlet.jsp
javax.servlet.jsp-api
2.3.1
junit
junit
4.13.2
test
org.mybatis.generator
mybatis-generator-core
1.3.5
org.springframework
spring-context
5.1.6.RELEASE
org.mybatis
mybatis-spring
1.3.2
org.springframework
spring-aop
5.1.6.RELEASE
org.springframework
spring-aspects
5.1.6.RELEASE
org.springframework
spring-tx
5.2.14.RELEASE
org.springframework
spring-jdbc
5.2.14.RELEASE
com.alibaba
druid
1.2.8
com.github.pagehelper
pagehelper
5.3.0
src/main/java
**/*.xml
false
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bank?useSSL=false
jdbc.username=root
jdbc.password=123456
# 配置初始化大小、最小、最大
jdbc.initialSize=5
jdbc.minIdle=3
jdbc.maxActive=20
#配置获取连接等待超时的时间
jdbc.maxWait=0
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
jdbc.timeBetweenEvictionRunsMillis=600000
#配置一个连接在池中最小生存的时间,单位是毫秒
jdbc.minEvictableIdleTimeMillis=300000
UserService接口和UserServiceImpl实现类。
public interface UserService {
User findUserById(int id);
}
@Service
public class UserServiceImpl implements UserService {
// 设置Dao属性
@Autowired // 自动注入Mapper
private UserMapper userMapper;
@Override
public User findUserById(int id) {
User user = userMapper.findUserById(id);
return user;
}
}
UserMapper.java和UserMapper.xml。
public interface UserMapper {
User findUserById(int id);
}
测试。
public class TestSpringMybatis {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = context.getBean("userServiceImpl", UserService.class);
User user = service.findUserById(51);
System.out.println(user);
}
}
解决方法:
将log4j依赖更新为log4j12依赖
org.slf4j
slf4j-log4j12
1.7.25
分页插件作用: 可以自动完成分页功能。
使用分页插件,只需要正常查询全部数据,不用考虑关于分页的limit拼接问题,和count的数据条数等问题,插件全部搞定。
加入依赖.
com.github.pagehelper
pagehelper
5.3.0
将分页插件的配置放在applicationContext.xml的SqlSessionFactory中
mysql
创建UserService接口和UserServiceImpl实现类
public interface UserService {
List findAll();
}
@Service
public class UserServiceImpl implements UserService {
// 设置Dao属性
@Autowired
private UserMapper userMapper;
@Override
public List findAll() {
return userMapper.findAll();
}
}
创建UserMapper和UserMapper.xml。
public interface UserMapper {
List findAll();
}
测试,在代码中使用分页功能。
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = context.getBean("userServiceImpl", UserService.class);
// 每页为5条
int pageSize = 5;
// 页码
int pageNum = 1;
PageHelper.startPage(pageNum ,pageSize);
// 查询全部
List all = service.findAll( );
for (User user : all) {
System.out.println(user );
}
// 将查询返回的集合,交给分页插件,返回给所有分页数据
PageInfo pageInfo = new PageInfo<>(all);
System.out.println(pageInfo );
}
底层原理:
拦截Mybatis发出SQL语句,自动拼接Limit 语句,完成分页语句,另外还会发出SELECT count(0) FROM user 计算数据条数。
事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback等)。
注意:DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource的Bean,否则事务控制失败!!!
基于事务管理器,进一步定制,生成一个额外功能:Advice。
此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。
isolation
隔离级别.
名称 | 描述 |
---|---|
default | (默认值)(采用数据库的默认的设置) (建议) |
read-uncommited | 读未提交 |
read-commited | 读提交 (Oracle数据库默认的隔离级别) |
repeatable-read | 可重复读 (MySQL数据库默认的隔离级别) |
serialized-read | 序列化读 |
隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read
安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。
并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。
事务并发时的安全问题。
问题 | 描述 |
---|---|
脏读 | 一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止 |
不可重复读 | 一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止 |
幻影读 | 一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止 |
propagation
传播行为.
当涉及到事务嵌套(Service调用Service)时,可以设置:
SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)
readonly
读写性
true:只读,可提高查询效率。(适合查询)
false:可读可写。 (默认值)(适合增删改)
timeout
事务超时时间
当前事务所需操作的数据被其他事务占用,则等待。
100:自定义等待时间100(秒)。
-1:由数据库指定等待时间,默认值。(建议)
rollback-for
回滚属性
如果事务中抛出 RuntimeException,则自动回滚
如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务
处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for="Exception"
将事务管理的Advice 切入需要事务的业务方法中
org.springframework
spring-test
4.3.6.RELEASE
junit
junit
4.12
- 可以免去工厂的创建过程
- 可以直接将要测试的组件注入到测试类
//由SpringJUnit4ClassRunner启动测试
@RunWith(SpringJUnit4ClassRunner.class)
//spring的配置文件位置
@ContextConfiguration("classpath:applicationContext.xml")
//当前测试类也会被纳入工厂中,所以其中属性可以注入
public class SpringTest{
@Autowired // 注入要测试的组件
@Qualifier("userDAO")
private UserDAO userDAO;
@Test
public void test(){
// 测试使用userDAO
userDAO.queryUser();
....
}
}