Spring基础
Spring核心是解耦,而不是简化代码,就像一个工具库一样
IoC容器
创建Spring项目
maven导入
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.9</version> </dependency>
|
Spring配置文件
name属性是别名,class属性是文件路径
1
| <bean name="student" class="com.test.bean.Student"/>
|
scope属性切换模式,默认singleton单例模式,调用时始终为一个对象;prototype原型模式,可以创建多个对象
1
| <bean name="student" class="com.test.bean.Student" scope="prototype"/>
|
调用对象时自动运行初始化与销毁方法,两个属性的参数为方法名
1
| <bean name="student" class="com.test.bean.Student" init-method="init" destroy-method="destroy"/>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Student { String name; int age;
private void init(){ System.out.println("我是初始化方法!"); }
private void destroy(){ System.out.println("我是销毁方法!"); }
public Student(){ System.out.println("我被构造了!"); } }
|
默认执行顺序是配置文件的bean顺序,depends-on
属性可以改变执行顺序,指定某个bean在它之前先加载
1 2
| <bean name="student" class="com.test.bean.Student" depends-on="card"/> <bean name="card" class="com.test.bean.Card" />
|
Main方法调用类
获取Spring配置文件;个人倾向使用指定某个类,写起来简单一点;单例模式下调用的是同一个对象
1 2 3 4
| ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); Student student = context.getBean(Student.class); Student student2 = (Student) context.getBean("student"); System.out.println(student == student2);
|
依赖注入DI
property标签,能给Bean成员属性赋值,前提是有set方法
1 2 3 4 5 6 7 8 9 10
| @Data public class Student { String name; int age; }
<bean name="student" class="com.test.bean.Student"> <property name="name" value="幽蓝"/> <property name="age" value="9"/> </bean>
|
ref属性引用另一个bean,再将对应的类型注册为bean
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Card { }
@Data public class Student { Card newCard; }
<bean name="student" class="com.test.bean.Student"> <property name="newCard" ref="card"/> </bean>
<bean name="card" class="com.test.bean.Card"/>
|
String类型不需要type属性,其他类型需要type属性指定相同类型
1 2 3 4 5 6 7 8 9 10 11 12 13
| List<String>list; List<Double>list;
<bean name="student" class="com.test.bean.Student"> <property name="list"> <list> <value type="double">100.0</value> <value type="double">90.0</value> <value type="double">80.0</value> </list> </property> </bean>
|
map集合
1 2 3 4 5 6 7 8 9 10
| Map<String, Integer> score;
<bean name="student" class="com.test.bean.Student"> <property name="score"> <map> <entry key="语文" value="100"/> <entry key="数学" value="90"/> </map> </property> </bean>
|
自动装配,查找是否存在对应名称、类型、构造方法的Bean,其余两个暂不了解
1 2 3 4 5
| <bean name="student" class="com.test.bean.Student" autowire="byName"/> byType constructor default no
|
constructor-arg标签指定构造函数,建议用name属性指向形参,看起来更直观一点
1 2 3 4 5 6 7 8 9 10 11
| public Student(String name, int age) { this.name = name; this.age = age; }
<bean name="student" class="com.test.bean.Student"> <constructor-arg name="name" value="幽蓝"/> <constructor-arg name="age" value="9"/> type="java.lang.String" value="幽蓝" index="0" value="幽蓝" </bean>
|
工厂模式
默认情况下,反射机制通过构造方法创建Bean对象;工厂模式下,反射机制先找到工厂类,再从工厂方法创建Bean对象
1 2 3 4 5 6 7 8 9 10 11 12
| public class Student { Student() { System.out.println("我被构造了"); } }
public class StudentFactory { public static Student getStudent(){ System.out.println("欢迎光临电子厂"); return new Student(); } }
|
class指定工厂类,通过它找到工厂方法;factory-method指定工厂方法,缺点是无法通过配置文件对Bean依赖注入
1
| <bean class="com.test.bean.StudentFactory" factory-method="getStudent"/>
|
第二种方式,可以进行依赖注入,首先去掉static关键字
1 2 3 4 5 6
| public class StudentFactory { public Student getStudent(){ System.out.println("欢迎光临电子厂"); return new Student(); } }
|
普普通通注册成bean,再使用factory-bean绕一圈,就可以依赖注入了
1 2
| <bean name="studentFactory" class="com.test.bean.StudentFactory"/> <bean factory-bean="studentFactory" factory-method="getStudent"/>
|
获取工厂bean生产出的bean的实例
1
| Student bean = (Student) context.getBean("studentFactory");
|
获取工厂类的实例
1
| StudentFactory bean = (StudentFactory) context.getBean("&studentFactory");
|
面向切面AOP
SpringAOP
maven导入
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.9</version> </dependency>
|
引入aop相关标签,就能使用aop操作
1 2 3 4 5 6
| <?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
|
我想say方法执行后,调用after方法,写好类放一边
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Log public class AopTest { public void after(){ log.info("我是方法执行之后的日志!"); } }
@Data public class Student { String name; String age;
public Student(String name, String age) { this.name = name; this.age = age; }
public void say(String text) { System.out.println("我叫" + name + "今年" + age + "岁了,我说:" + text); } }
public class Main { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); Student student = context.getBean(Student.class); student.say("我想睡觉!!!!!!"); } }
|
aop:config
标签添加AOP配置,expression
属性切入方式,execution
链式切入
- 修饰符:*代表任意修饰符
- 包名:如com.test(*代表全部,比如com.*代表com包下的全部包)
- 类名:*代表包下的所有类
- 方法名称:*代表全部方法
- 方法参数:填写对应的参数,比如(String, String),*代表任意一个参数,使用..代表所有参数
1 2 3 4 5 6 7 8 9 10 11 12 13
| <bean name="student" class="com.test.bean.Student"> <constructor-arg name="name" value="幽蓝"/> <constructor-arg name="age" value="9"/> </bean>
<bean name="aopTest" class="com.test.aop.AopTest"/>
<aop:config> <aop:aspect ref="aopTest"> <aop:pointcut id="stu" expression="execution(* com.test.bean.Student.say(String))"/> <aop:after-returning method="after" pointcut-ref="stu"/> </aop:aspect> </aop:config>
|
@annotation
注解切入,参数是注解名
1 2 3 4 5 6
| <aop:config> <aop:aspect ref="aopTest"> <aop:pointcut id="stu2" expression="@annotation(Deprecated)"/> <aop:before method="before" pointcut-ref="stu2"/> </aop:aspect> </aop:config>
|
环绕方法相当于完全代理say方法,joinPoint.proceed()
执行say方法,类似重写的super
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public String say(String text) { System.out.println("我叫" + name + "今年" + age + "岁了,我说:" + text); return text; }
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ Object res = joinPoint.proceed(); System.out.println("我是环绕方法"); return res; }
<aop:config> <aop:aspect ref="aopTest"> <aop:pointcut id="stu" expression="execution(* com.test.bean.Student.say(String))"/> <aop:around method="around" pointcut-ref="stu"/> </aop:aspect> </aop:config>
|
接口实现AOP
MethodBeforeAdvice方法执行前的动作,args方法执行前的实参列表,target执行此方法的实例对象
1 2 3 4 5 6 7 8 9 10 11
| public class AopTest implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("通过Advice实现AOP"); } }
<aop:config> <aop:pointcut id="stu" expression="execution(* com.test.bean.Student.say(String))"/> <aop:advisor advice-ref="before" pointcut-ref="stu"/> </aop:config>
|
AfterReturningAdvice方法执行后的动作,returnValue返回形参,其余一样
1 2 3 4 5 6
| public class AopAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("方法的返回值:" + returnValue); } }
|
注解开发
注解配置文件
批量扫描是个数组;@Configuration
注册配置;@Component
注册成bean;@Scope
切换成原型模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @ComponentScans({ @ComponentScan("com.test.bean"), @ComponentScan("com.test.xxxx"), ... })
@ComponentScan("com.test.bean") @Configuration public class MainConfiguration {}
@Component @Scope("prototype") @Data public class Student { String name; int age; }
|
Main方法调用类
获取配置文件的对象变了,其他跟IoC一样;原型模式下是两个不同的对象
1 2 3 4 5 6 7 8
| public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfiguration.class); Student student = context.getBean(Student.class); Student student2 = context.getBean(Student.class); System.out.println(student == student2); } }
|
自动注入
@Resource
自动装配,类似new对象;默认ByName,如果找不到则ByType,能添加到set方法、字段上
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Component public class Student { String name; int age; @Resource Card card; @Resource public void setCard(Card card) { System.out.println("通过方法"); this.card = card; } }
|
JDK17没有这个注解,maven导入
1 2 3 4 5
| <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
|
@PostConstruct
添加构造后执行的方法;Constructor(构造方法) -> @Resource(自动注入) -> @PostConstruct
1 2 3 4
| @PostConstruct public void init(){ System.out.println("我是初始化方法!"); }
|
如果是其他框架提供的类想要注册为bean,且无法修改源码,用最原始的@Bean
来注册
1 2 3 4 5 6 7
| @Configuration public class MainConfiguration { @Bean public Test test(){ return new Test(); } }
|
注解AOP操作
定义say方法,以及main方法调用say方法,跟之前aop一样╮(╯▽╰)╭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Component public class Student { String name; int age;
public String say(String text) { System.out.println("我叫" + name + ",今年" + age + "岁了"); return text; } }
public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfiguration.class); Student student = context.getBean(Student.class); System.out.println(student.say("想摆烂")); } }
|
@EnableAspectJAutoProxy
开启AOP注解支持
1 2 3 4 5 6 7
| @ComponentScans({ @ComponentScan("com.test.aop"), @ComponentScan("com.test.bean") }) @EnableAspectJAutoProxy @Configuration public class MainConfiguration {}
|
@Aspect
开启切面,@Before
和@AfterReturning
选择切点,returning属性作为方法某个参数的实参
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Component @Aspect public class AopTest { @Before("execution(* com.test.bean.Student.say(..))") public void before(){ System.out.println("我是方法执行前的事情"); }
@AfterReturning(value = "execution(* com.test.bean.Student.say(..))", returning = "returnVal") public void after(Object returnVal){ System.out.println("我是方法执行后,结果为:" + returnVal); } }
|
环绕方法
1 2 3 4 5 6 7
| @Around("execution(* com.test.bean.Student.say(..))") public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("我是方法执行前的事情"); Object val = point.proceed(); System.out.println("我是方法执行后"); return val; }
|
其他注解配置
@Import
将某个类当作Bean来注册,用来解决多个配置文件问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @ComponentScans({ @ComponentScan("com.test.aop"), @ComponentScan("com.test.bean") }) @Import(TestConfiguration.class) @Configuration public class MainConfiguration {}
public class TestConfiguration { @Bean public Teacher teacher(){ return new Teacher(); } }
public class Teacher { }
|
深入Mybatis框架
spring整合数据源
maven导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>3.0.2</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.9</version> </dependency>
|
创建一个数据源的实现类,再给SqlSessionFactoryBean实例,相当于通过IoC容器配置了SqlSessionFactory,再传入一个DataSource的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @ComponentScans({ @ComponentScan("com.test.aop"), @ComponentScan("com.test.bean") }) @Configuration @EnableAspectJAutoProxy @MapperScan("com.test.mapper") public class MainConfiguration { @Bean public DataSource dataSource(){ PooledDataSource dataSource = new PooledDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/study"); dataSource.setDriver("com.mysql.cj.jdbc.Driver"); dataSource.setUsername("root"); dataSource.setPassword("d6NFV8SYsEWPgTvq"); return dataSource; }
@Bean public SqlSessionFactoryBean sqlSessionFactoryBean(@Autowired DataSource source){ SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(source); return bean; } }
|
用spring开发将不再需要mybatis配置文件
HikariCP连接池
maven导入
1 2 3 4 5
| <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency>
|
新版本提示NOP什么什么的,暂时无法解决,用旧版本吧
1 2 3 4 5
| <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.25</version> </dependency>
|
不用PooledDataSource数据源了,改成HikariDataSource数据源,url和driver修改下,其他一样
1 2 3
| HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/study"); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
|
Mybatis事务管理
事务原则:原子性、一致性、隔离性、持久性
ISOLATION_REPEATABLE_READ(可重复读):其他事务会读取当前事务已经提交的数据并且其他事务执行过程中不允许再进行数据修改

- 脏读:读取到了被回滚的数据,它毫无意义。
- 虚读(不可重复读):由于其他事务更新数据,两次读取的数据不一致。
- 幻读:由于其他事务执行插入删除操作,而又无法感知到表中记录条数发生变化,当下次再读取时会莫名其妙多出或缺失数据,就像产生幻觉一样
Spring事务管理
@EnableTransactionManagement
开启Spring的事务支持;以下为事务管理器
1 2 3 4
| @Bean public TransactionManager transactionManager(@Autowired DataSource dataSource){ return new DataSourceTransactionManager(dataSource); }
|

@Transactional
执行事务操作,参数propagation传播等级
1
| @Transactional(propagation = Propagation.SUPPORTS)
|
集成JUnit测试
maven导入
1 2 3 4 5 6 7 8 9 10 11
| <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.12</version> </dependency>
|
@ExtendWith
JUnit提供的spring扩展;@ContextConfiguration
加载配置文件,支持XMl和类
1 2 3 4 5 6 7 8 9 10 11 12
| @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = MainConfiguration.class) public class TestMain {
@Resource TestService service; @Test public void test(){ service.test(); } }
|
Spring高级
Bean Aware
实现Aware接口的bean,初始化过程中可以获得一些资源,比如:BeanNameAware获取资源
1 2 3 4 5 6 7 8
| @Component public class Student implements BeanNameAware {
@Override public void setBeanName(String name) { System.out.println("我在加载阶段获得了Bean名字:"+name); } }
|
BeanClassLoaderAware获取类加载器
任务调度
异步任务
配置类添加@EnableAsync
表示开启异步
1 2 3 4 5
| @EnableAsync @Configuration @ComponentScan("com.test.bean") public class MainConfiguration { }
|
@Async
标记为异步,当方法被调用时,会新开一个线程执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Component public class Student { public void syncTest() throws InterruptedException { System.out.println(Thread.currentThread().getName()+"我是同步执行的方法,开始..."); Thread.sleep(3000); System.out.println("我是同步执行的方法,结束!"); }
@Async public void asyncTest() throws InterruptedException { System.out.println(Thread.currentThread().getName()+"我是异步执行的方法,开始..."); Thread.sleep(3000); System.out.println("我是异步执行的方法,结束!"); } }
|
试着调用吧
1 2 3 4 5 6
| public static void main(String[] args) throws InterruptedException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfiguration.class); Student student = context.getBean(Student.class); student.asyncTest(); student.syncTest(); }
|
定时任务
配置类添加@EnableScheduling
表示开启定时
1 2 3 4 5
| @EnableScheduling @Configuration @ComponentScan("com.test.bean") public class MainConfiguration { }
|
@Scheduled
编写定时任务,
1 2 3 4
| @Scheduled(fixedRate = 2000) public void task(){ System.out.println("我是定时任务!"+new Date()); }
|
- fixedDelay:在上一次定时任务执行完之后,间隔多久继续执行
- fixedRate:无论上一次定时任务有没有执行完成,两次任务之间的时间间隔
- cron:如果嫌上面两个不够灵活,你还可以使用cron表达式来指定任务计划
监听器
Bean继承ApplicationListener
,类型指定为对应的Event事件,当某个事件触发时会收到通知;ContextRefreshedEvent
Spring容器初始化完成时会触发一次
1 2 3 4 5 6 7
| @Component public class TestListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println(event.getApplicationContext()); } }
|
SpringEL表达式
暂时跳过
SpringMVC
配置文件
maven导入
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>6.0.9</version> </dependency>
|
注册配置
1 2 3 4 5
| @Configuration public class MainConfiguration {}
@Configuration public class WebConfiguration {}
|
Tomcat会在类路径中查找实现ServletContainerInitializer 接口的类,如果发现的话,就用它来配置Servlet容器,Spring提供了这个接口的实现类 SpringServletContainerInitializer , 通过@HandlesTypes(WebApplicationInitializer.class)设置,这个类反过来会查找实现WebApplicationInitializer 的类,并将配置的任务交给他们来完成,因此直接实现接口即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{MainConfiguration.class}; }
@Override protected Class<?>[] getServletConfigClasses() { return new Class[]{WebConfiguration.class}; }
@Override protected String[] getServletMappings() { return new String[]{"/"}; } }
|
Controller控制器
thymeleaf后端渲染引擎,过时了
1 2 3 4 5
| <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.12.RELEASE</version> </dependency>
|
@EnableWebMvc
启用WebMvc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @ComponentScan("com.example.controller") @Configuration @EnableWebMvc public class WebConfiguration { @Bean public ThymeleafViewResolver thymeleafViewResolver(@Autowired SpringTemplateEngine springTemplateEngine){ ThymeleafViewResolver resolver = new ThymeleafViewResolver(); resolver.setOrder(1); resolver.setCharacterEncoding("UTF-8"); resolver.setTemplateEngine(springTemplateEngine); return resolver; }
@Bean public SpringResourceTemplateResolver templateResolver(){ SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver(); resolver.setSuffix(".html"); resolver.setPrefix("/"); return resolver; }
@Bean public SpringTemplateEngine springTemplateEngine(@Autowired ITemplateResolver resolver){ SpringTemplateEngine engine = new SpringTemplateEngine(); engine.setTemplateResolver(resolver); return engine; } }
|
@Controller
自动注册为Controller类型的Bean;getModel().put()
向Model提供数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Controller public class MainController { @RequestMapping("/index") public ModelAndView index(){ return new ModelAndView("index"); } }
|
添加Model作为形参,SpringMVC自动传递实例对象
1 2 3 4 5
| @RequestMapping(value = "/index") public String index(Model model){ model.addAttribute("name", "yyds"); return "index"; }
|
静态资源通过Tomcat提供的默认Servlet进行解析
1 2 3 4 5 6 7 8 9 10
| @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); }
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/WEB-INF/static/"); }
|
@RequestMapping
收到请求时,根据映射关系调用对应的请求处理方法;path、value一样的,决定当前方法处理的请求路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Mapping public @interface RequestMapping { String name() default "";
@AliasFor("path") String[] value() default {};
@AliasFor("value") String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {}; }
|
处理多个请求路径
1 2 3 4
| @RequestMapping({"/index", "/test"}) public ModelAndView index(){ return new ModelAndView("index"); }
|
@RequestMapping
表示所有请求映射时,添加一个路径前缀;访问/yyds/index或是/yyds/test才可以得到此页面
1 2 3 4 5 6 7 8 9
| @Controller @RequestMapping("/yyds") public class MainController {
@RequestMapping({"/index", "/test"}) public ModelAndView index(){ return new ModelAndView("index"); } }
|
路径支持通配符
?
:表示任意一个字符,比如@RequestMapping(“/index/x?”)可以匹配/index/xa、/index/xb
*
:表示任意0-n个字符,比如@RequestMapping(“/index/*”)可以匹配/index/hi、/index/yyds
**
:表示当前目录或基于当前目录的多级目录,比如@RequestMapping(“/index/**”)可以匹配/index、/index/xxx
method属性表示请求方法的类型,用来限定请求方式;@PostMapping
Post请求类型的请求映射,还有@GetMapping
指定为GET请求方式
1 2 3 4 5 6 7 8 9 10
| @RequestMapping(value = "/index", method = RequestMethod.POST) public ModelAndView index(){ return new ModelAndView("index"); }
@PostMapping(value = "/index") public ModelAndView index(){ return new ModelAndView("index"); }
|
params属性指定携带哪些参数;!
不允许携带,!=
不允许为test
1 2 3 4 5 6
| @RequestMapping(value = "/index", params = {"username", "password"}) params = {"!username", "password"} params = {"username!=test", "password=123"} public ModelAndView index(){ return new ModelAndView("index"); }
|
header属性请求头需要携带什么;其他属性,consumes处理请求的提交内容类型,produces返回的内容类型
1 2 3 4
| @RequestMapping(value = "/index", headers = "!Connection") public ModelAndView index(){ return new ModelAndView("index"); }
|
@RequestParam
@RequestParam
获取请求中的参数,value会传递给形参,required是否必需传参,defaultValue参数丢失时的默认参数
1 2 3 4 5
| @RequestMapping(value = "/index") public ModelAndView index(@RequestParam(value = "username", required = false, defaultValue = "我是默认值") String username){ System.out.println("接受到请求参数:"+username); return new ModelAndView("index"); }
|
请求参数传递给一个实体类;必须携带set方法或是构造方法中包含所有参数,请求参数会自动根据类中的字段名称进行匹配
1 2 3 4 5 6 7 8 9 10 11
| @Data public class User { String username; String password; }
@RequestMapping(value = "/index") public ModelAndView index(User user){ System.out.println("获取到cookie值为:"+user); return new ModelAndView("index"); }
|
@RequestHeader与@RequestParam用法一致,不过它是用于获取请求头参数的
@CookieValue
获取请求携带的Cookie信息
1 2 3 4 5 6 7
| @RequestMapping(value = "/index") public ModelAndView index(HttpServletResponse response, @CookieValue(value = "test", required = false) String test){ System.out.println("获取到cookie值为:"+test); response.addCookie(new Cookie("test", "幽蓝")); return new ModelAndView("index"); }
|
@SessionAttribute
获取请求携带的Session信息
1 2 3 4 5 6 7
| @RequestMapping(value = "/index") public ModelAndView index(@SessionAttribute(value = "test", required = false) String test, HttpSession session){ session.setAttribute("test", "xxxx"); System.out.println(test); return new ModelAndView("index"); }
|
重定向和请求转发
redirect:
实现重定向,forward:
转发给其他请求映射
1 2 3 4 5 6 7 8 9
| @RequestMapping("/index") public String index(){ return "redirect:home"; }
@RequestMapping("/index") public String index(){ return "forward:home"; }
|
Bean的Web作用域
Bean分别会以单例和多例模式进行创建,而在SpringMVC中,它的作用域被继续细分:
- request:每次HTTP请求,使用request作用域定义的Bean都将产生一个新实例,请求结束后Bean也消失
- session:每一个会话,使用session作用域定义的Bean都将产生一个新实例,会话过期后Bean也消失
@RequestScope
或@SessionScope
表示此Bean的Web作用域
RestFul风格
@PathVariable
占位符位置的所有内容都会被作为请求参数
1 2 3 4 5
| @RequestMapping("/index/{str}") public String index(@PathVariable("str") String text){ System.out.println(text); return "index"; }
|
按照不同功能划分:
编写四个请求映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Controller public class MainController {
@RequestMapping(value = "/index/{id}", method = RequestMethod.GET) public String get(@PathVariable("id") String text){ System.out.println("获取用户:"+text); return "index"; }
@RequestMapping(value = "/index", method = RequestMethod.POST) public String post(String username){ System.out.println("添加用户:"+username); return "index"; }
@RequestMapping(value = "/index/{id}", method = RequestMethod.DELETE) public String delete(@PathVariable("id") String text){ System.out.println("删除用户:"+text); return "index"; }
@RequestMapping(value = "/index", method = RequestMethod.PUT) public String put(String username){ System.out.println("修改用户:"+username); return "index"; } }
|
Interceptor拦截器
实现HandlerInterceptor接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class MainInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("我是处理之前!"); return true; }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("我是处理之后!"); }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("我是完成之后!"); } }
|
配置类注册
1 2 3 4 5 6
| @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MainInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/home"); }
|
异常处理
自定义异常处理控制器,出现指定异常转接给控制器
1 2 3 4 5 6 7 8 9
| @ControllerAdvice public class ErrorController { @ExceptionHandler(Exception.class) public String error(Exception e, Model model){ e.printStackTrace(); model.addAttribute("e", e); return "500"; } }
|
th:text
是thymeleaf语法,用来显示错误信息
1 2 3 4 5 6 7 8 9 10 11
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 500 - 服务器出现了一个内部错误QAQ <div th:text="${e}"></div> </body> </html>
|
JSON数据格式
前端转换方式
JSON字符串转换JS对象
1 2 3
| let obj = JSON.parse('{"studentList": [{"name": "幽蓝", "age": 9}, {"name": "椰奶", "age": 18}], "count": 2}')
obj.studentList[0].name
|
JS对象转换JSON字符串
后端转换方式
常用JSON解析框架:Jackson和FastJSON
1 2 3 4 5
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.78</version> </dependency>
|
JSONObject存放数据
1 2 3 4 5 6 7 8 9 10 11
| @RequestMapping(value = "/index") public String index(){ JSONObject object = new JSONObject(); object.put("name", "幽蓝"); object.put("age", 9); System.out.println(object.toJSONString()); return "index"; }
|
JSONArray嵌套JSONObject
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RequestMapping(value = "/index") public String index(){ JSONObject object = new JSONObject(); object.put("name", "幽蓝"); object.put("age", 9); JSONArray array = new JSONArray(); array.add(object); System.out.println(array.toJSONString()); return "index"; }
|
实体类转换为JSON格式;produces = "application/json"
表示服务器端返回了一个JSON格式的数据;@ResponseBody
表示方法返回;@RestController
表示返回字符串数据
1 2 3 4 5 6 7 8 9
| @RequestMapping(value = "/index", produces = "application/json") @ResponseBody
public String data(){ Student student = new Student(); student.setName("幽蓝"); student.setAge(9); return JSON.toJSONString(student); }
|
AJAX请求
html导入jquery框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 你好, <span id="username"></span> 您的年龄是: <span id="age"></span> <button onclick="updateData()">点我更新页面数据</button> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> <script th:src="@{/static/test.js}"></script> </body> </html>
|
点击按钮,更新用户名和年龄
1 2 3 4 5 6 7 8
| function updateData() { $.get("/mvc/data", function (data) { window.alert('接受到异步请求数据:'+JSON.stringify(data)) $("#username").text(data.name) $("#age").text(data.age) }) }
|
js对象转换为JSON字符串的形式进行传输
1 2 3 4 5 6 7 8 9 10 11
| function submitData() { $.ajax({ type: 'POST', url: "/mvc/submit", data: JSON.stringify({name: "测试", age: 18}), success: function (data) { window.alert(JSON.stringify(data)) }, contentType: "application/json" }) }
|
@RequestBody
读取JSON格式数据
1 2 3 4 5 6
| @RequestMapping("/submit") @ResponseBody public String submit(@RequestBody JSONObject object){ System.out.println("接收到前端数据:"+object); return "{\"success\": true}"; }
|
文件上传和下载
maven导入,接收用户上传的文件
1 2 3 4 5
| <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
|
配置Resolver
1 2 3 4 5 6 7
| @Bean("multipartResolver") public CommonsMultipartResolver commonsMultipartResolver(){ CommonsMultipartResolver resolver = new CommonsMultipartResolver(); resolver.setMaxUploadSize(1024 * 1024 * 10); resolver.setDefaultEncoding("UTF-8"); return resolver; }
|
上传功能
1 2 3 4 5 6 7 8
| @RequestMapping(value = "/upload", method = RequestMethod.POST) @ResponseBody public String upload(@RequestParam CommonsMultipartFile file) throws IOException { File fileObj = new File("test.html"); file.transferTo(fileObj); System.out.println("用户上传的文件已保存到:"+fileObj.getAbsolutePath()); return "文件上传成功!"; }
|
上传页面
1 2 3 4 5 6
| <div> <form action="upload" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit"> </form> </div>
|
下载功能
1 2 3 4 5 6 7 8 9 10 11
| @RequestMapping(value = "/download", method = RequestMethod.GET) @ResponseBody public void download(HttpServletResponse response){ response.setContentType("multipart/form-data"); try(OutputStream stream = response.getOutputStream(); InputStream inputStream = new FileInputStream("test.html")){ IOUtils.copy(inputStream, stream); }catch (IOException e){ e.printStackTrace(); } }
|
下载页面
1
| <a href="download" download="test.html">下载最新资源</a>
|
SpringSecurity
项目需准备各种配置以及前端页面,为了省事只记录语法格式,最好实战了解
用户认证
配置用户验证
1 2 3 4 5 6 7 8 9 10
| @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); auth .inMemoryAuthentication() .passwordEncoder(encoder) .withUser("test") .password(encoder.encode("123456")) .roles("user"); }
|
数据库认证
创建一个Service实现,实现UserDetailsService,返回数据库中的用户名、密码等信息的UserDetails,SpringSecurity会自动进行比对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Service public class UserAuthService implements UserDetailsService { @Resource UserMapper mapper;
@Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { String password = mapper.getPasswordByUsername(s); if(password == null) throw new UsernameNotFoundException("登录失败,用户名或密码错误!"); return User .withUsername(s) .password(password) .roles("user") .build(); } }
|
数据库交互
1 2 3 4 5
| @Mapper public interface UserMapper { @Select("select password from users where username = #{username}") String getPasswordByUsername(String username); }
|
自定义登录界面
配置拦截规则,用户未登录时,哪些路径可以访问,哪些路径不可以访问
1 2 3 4 5 6 7
| @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/static/**").permitAll() .antMatchers("/**").hasRole("user") }
|
如果不可以访问,那么会被自动重定向到登陆页面
1 2 3 4 5
| .formLogin() .loginPage("/login") .loginProcessingUrl("/doLogin") .defaultSuccessUrl("/index") .permitAll()
|
登出操作
1 2 3 4
| .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login");
|
学习过程,关闭CSRF防护
1 2
| .and() .csrf().disable();
|
授权方式
SpringSecurity两种授权方式:
- 基于权限的授权:只要拥有某权限的用户,就可以访问某个路径
- 基于角色的授权:根据用户属于哪个角色来决定是否可以访问某个路径
基于角色的授权
数据库添加用户字段,user是普通用户,admin是管理员用户
1 2 3 4 5
| http .authorizeRequests() .antMatchers("/static/**").permitAll() .antMatchers("/index").hasAnyRole("user", "admin") .anyRequest().hasRole("admin")
|
基于权限的授权
与角色类似,需要以hasAnyAuthority或hasAuthority进行判断
1
| .anyRequest().hasAnyAuthority("page:index")
|
注解判断权限
Mvc的配置类上添加,这里只针对Controller进行过滤
1 2
| @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
@PreAuthorize
执行之前判断权限,如果没有对应的权限或是对应的角色,将无法访问页面
1 2 3 4 5
| @PreAuthorize("hasRole('user')") @RequestMapping("/index") public String index(){ return "index"; }
|
@PostAuthorize
方法执行之后再进行拦截
1 2 3 4 5 6
| @PostAuthorize("hasRole('user')") @RequestMapping("/index") public String index(){ System.out.println("执行了"); return "index"; }
|
Spring管理的Bean也同样起作用;注意Service是由根容器进行注册,在Security配置类上需添加@EnableGlobalMethodSecurity
注解才生效
1 2 3 4 5 6 7
| @Service public class UserService { @PreAuthorize("hasAnyRole('user')") public void test(){ System.out.println("成功执行"); } }
|
@PreFilter
和@PostFilter
对集合类型的参数或返回值进行过滤
1 2 3 4 5 6 7 8 9 10 11 12 13
| @PreFilter("filterObject.equals('yyds')") public void test(List<String> list){ System.out.println("成功执行"+list); }
@RequestMapping("/index") public String index(){ List<String> list = new LinkedList<>(); list.add("hello"); list.add("yyds"); service.test(list); return "index"; }
|
多个集合时,需要使用filterTarget
指定
1 2 3 4
| @PreFilter(value = "filterObject.equals('yyds')", filterTarget = "list2") public void test(List<String> list, List<String> list2){ System.out.println("成功执行"+list); }
|
记住我
已登录的浏览器分配一个携带Token的Cookie,默认14天,携带此Cookie访问服务器将无需登录
1 2 3 4 5 6
| .and() .rememberMe() .rememberMeParameter("remember") .tokenRepository(new InMemoryTokenRepositoryImpl())
|
Token信息存放到数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Resource PersistentTokenRepository repository;
@Bean public PersistentTokenRepository jdbcRepository(@Autowired DataSource dataSource){ JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl(); repository.setDataSource(dataSource); repository.setCreateTableOnStartup(true); return repository; }
.and() .rememberMe() .rememberMeParameter("remember") .tokenRepository(repository) .tokenValiditySeconds(60 * 60 * 24 * 7)
|
SecurityContext
SecurityContext对象,获取当前用户的名称和授权信息
1 2 3 4 5 6 7 8 9
| @RequestMapping("/index") public String index(){ SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); User user = (User) authentication.getPrincipal(); System.out.println(user.getUsername()); System.out.println(user.getAuthorities()); return "index"; }
|