SSM学习小结
WuCheng

Spring

Spring的核心组件包含BeanCoreContextExpression

而Spring的核心功能主要有两个:IOC(控制反转)、AOP(面向切面编程)

IOC

IOC的作用

IOC也叫做控制反转,是一种设计原则,可以很好的降低代码之间的耦合,常见的使用方式称作依赖注入(DI)。Spring提供了一个IOC容器,将创建的Java Bean对象交给Spring管理,可以自动所需依赖进行注入。

IOC容器的引入极大的方便了开发时Bean的管理,注册在IOC容器里的Bean可以很方便的拿到Bean对象,只需要使用@Autowrite注解即可把所依赖的对象注入。

包括第三方的jar包的对象也可以使用IOC容器进行管理,只需要在配置文件,或者配置类中配置即可。

IOC容器的使用

我们在以往写DAO层以及Service层到Web层通常的写法是这样的:

1
2
3
4
5
6
7
8
public class Service{

Dao dao = new DaoImpl();

public void service(){
dao.method();
}
}

以上这种通过创建Dao对象的方式叫做正转,这会使得代码之间的耦合性很高,而通过IOC容器管理Bean对象则能很好的松耦合

配置

我们可以通过xml配置文件来创建Bean(个人并不是很喜欢xml的方式)

bean.xml
1
2
3
4
5
6
7
8
9
10
11
<!--开启注解的扫描-->
<context:component-scan base-package="work.wucheng.spring5.aopanno"></context:component-scan>

<!--id为在IOC容器中注册的Bean的别名,class则标识Bean对象在哪个包中-->
<bean id="dao" class="work.wucheng.spring.dao.Dao"/>

<bean calss="work.wucheng.spring.service.Service">
<!--property标识依赖,name是Service类中所需要注入的属性名
ref则可以从配置中引入外部的Bean进行注入-->
<property name="dao" ref="dao"/>
</bean>
@Configuration

通过注解来进行配置IOC容器则方便的多,不过我们需要将xml以对象的形式来配置

首先需要创建config类来进行Bean管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration //标识为配置类
@ComponentScan(basePackages = {"work.wucheng.spring"}) //开启注解扫描,里面的值为需要扫描的包名
public class SpringConfig {

//如果有需要第三方Jar包中的类可以在配置类中使用@Bean进行注册
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//如果这个类也需要其他Bean的依赖,那么只需要在参数中写好,IOC容器会自动的在容器内找到依赖
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
//返回对象即可在IOC容器完成注册
return jdbcTemplate;
}

}

使用

Spring提供了四个注解进行标识Bean对象以在IOC容器中进行注册,四个注解除了名字不一样实际并没有任何区别,只是为了方便阅读。这四个注解分别为:

  • @Compent:一般使用在普通类中
  • @Repository:一般使用在持久层Dao
  • @Service:一般用在Service层
  • @Controller:一般使用在控制器层中,在MVC前也可以说是web层
1
2
3
4
5
@Repository
public class Dao{
@Autowired //自动装配
JdbcTemplate jdbcTemplate;
}

@Autowired是根据类型进行自动装配,当我们有多个实现同一接口的Bean对象的时候Spring则不知道我们需要哪个对象,这个时候我们则需要另一个注解@Qualifier来指定对象了

1
2
3
4
5
6
@Service
public class Service{
@Autowired //@Qualifier需要同@Autowired一起使用
@Qualifier("daoImpl") //值为指定的类名开头小写
Dao dao;
}

还有Java官方提供的一个注解@Resource,当没有值的时候用法同@Autowired,当有值的时候等同于@Autowired+@Qualifier,但是Spring更推荐我们使用Spring提供的注解

除了可以对所需的依赖进行注入,同样对于普通属性也可以使用@Value(数据)进行赋值

获取IOC容器的Bean对象

在使用Spring的时候如果我们需要直接获取到IOC容器中的对象可以使用如下方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestSpring {
//使用xml方式进行配置的方式
@Test
public void testService(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Service service = context.getBean("service", Service.class);
}
//使用配置类代替xml配置的文件的方式
@Test
public void testService2(){
//使用配置类代替xml配置文件
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Service service = context.getBean("service", Service.class);
}

简单的IOC的原理

IOC容器的原理本质上是工厂模式+反射进行实现的

将需要需要进行控制反转的对象和需要注入的Bean对象放入一个工厂类中

通过反射获取到类对象,再将Bean对象赋值给类并创建对象,此时就完成了控制反转

以下为粗略的的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Factory{
public <T> T factory(Class<T> clazz,Object object,String fieldName) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
//创建对象
T t = clazz.newInstance();
//获取属性
Field declaredField = clazz.getDeclaredField(fieldName);
//解锁私有属性
declaredField.setAccessible(true);
//注入依赖
declaredField.set(t,object);
//返回对象
return t;
}
}

Spring中有一个BeanFactory类是IOC容器的基本实现接口,而上面获取IOC容器中Bean对象的ApplicationContext是BeanFactory的子接口

以下是通过BeanFactory接口的简单实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyBean implements FactoryBean<Course> {
//定义返回的bean
@Override
public Bean getObject() throws Exception {
Bean bean = new Bean();
bean.setCname("abc");
return bean;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}

AOP

AOP的作用

AOP称作面向切面编程,和面向对象一样是一种编程思想,它可以不改变原有代码的情况下进行方法增强。Spring提供了注解@AspectJ进行AOP的实现,被增强的类和增强类需要IOC容器中进行注册。

AOP的最大好处就是,不用修改原有的代码也不用担心其修改会导致奇奇怪怪的错误,它只作增强,其效果相当于继承,如下:

1
2
3
4
5
6
7
8
9
10
public class Object2 extends Object1{
//被增强的类
@Override
public String method(String str){
StringBuffer buffer = new StringBuffer("before");
String origin = super(str);
buffer.append("after");
return buffer.toString();
}
}

如上演示,我们此时就获得了一个Object1的增强方法Object2,但是如此来进行方法的增强那么意味着依赖Object1的方法全部都要替换成Object2。

但是AOP则更为强大,它并不需要改变Object1被依赖的代码就能达到这样的效果。

AOP的一些概念

  1. 连接点:指哪些类可以被增强
  2. 切入点:被增强的方法
  3. 通知:实际被增强的部分被称作通知,而通知也分为多种类型
    • 前置通知
    • 后置通知
    • 环绕通知
    • 异常通知
    • 最终通知
  4. 切面:把通知应用到切入点的过程

AspectJ的使用

AspectJ并不是Spring核心的一部分,它是独立于AOP框架 的,但是一般都会一起使用

它的配置同样分为配置类和配置文件两种方式

xml

1
2
3
4
5
<!--开启注解的扫描-->
<context:component-scan base-package="work.wucheng.spring"/>

<!--开启Aspect生成代理对象-->
<aop:aspectj-autoproxy/>

@Configuration

1
2
3
4
5
@Configuration
@ComponentScan(basePackages = {"work.wucheng.spring"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}

当配置完成后我们就可以在增强类中使用@AspectJ注解了

但是使用前需要了解切入点表达式

execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名] ([参数列表]))

权限修饰符可以省略

可以使用【*】通配符表示任意返回值类型

也可以使用【*..】表示当前包以及子包

参数列表可以使用【..】表示任意类型参数

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
32
@Component
@Aspect //生成代理对象
public class UserProxy {
// 前置通知
@Before("execution(* work.wucheng.spring.aopanno.User.add(..))")
public void befor(){
System.out.println("before......");
}
//后置通知(最终通知)
@After("execution(* work.wucheng.spring.aopanno.User.add(..))")
public void after(){
System.out.println("after......");
}
//返回通知
@AfterReturning("execution(* work.wucheng.spring.aopanno.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning......");
}
//异常通知
@AfterThrowing("execution(* work.wucheng.spring.aopanno.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing......");
}
//环绕通知
@Around("execution(* work.wucheng.spring.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前");
//被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后");
}
}

显然如果我们需要对一个方法进行增强多次,那么这样写就会显得非常麻烦了

我们可以抽取相同切入点进行简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
@Aspect //生成代理对象
public class UserProxy {

//相同的切入点抽取
@Pointcut(value = "execution(* work.wucheng.spring5.aopanno.User.add(..))")
public void pointdemo(){
}

// 前置通知
// @Before表示注解 作为前置通知
@Before(value = "pointdemo()")
public void befor(){
System.out.println("before......");
}
}

但是如果我们需要多次增强,不止一个增强对象并且对运行顺序有要求,则需要用到@Order注解了

@Order注解标识在类的上方,数值为数字来表示优先级,数字越小优先级越高,@Before越先执行,@After越后执行

简单的AOP原理

AOP的本质其实是动态代理,而代理分为有接口代理和无接口的代理

以下是有接口的动态代理实现:

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
public class JDKProxy {
public static void main(String[] args) {
//创建实现类的代理对象Proxy
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1, 2);
System.out.println("result:"+result);
}
}

//创建代理对象代码
class UserDaoProxy implements InvocationHandler{

private Object object;

public UserDaoProxy(Object obj){
this.object = obj;
}
//增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
System.out.println("方法之前执行。。。"+method.getName()+":传递的参数"+ Arrays.toString(args));
//被增强的方法
Object res = method.invoke(object, args);
//方法之后
System.out.println("方法之后执行。。。"+object);
return res;
}
}

Spring MVC

@RequestMapping

作用

RequestMapping也叫做请求映射,当类使用注解@Controller标记为控制器方法后,SpringMVC扫描到该类会将方法使用了注解@RequestMapping的方法作为请求映射,当在游览器访问此方法时会调用此方法

@RequestMapping的使用

@RequsetMapping可以使用在类上,也可以使用在方法上

当使用在类上的时候,请求该类中方法的路径前缀都需要带上该类上@RequestMapping的值

例如:

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/test")
public class RequestMappingController {

//此时请求映射所映射的请求的请求路径为:/test/testRequestMapping
@RequestMapping("/testRequestMapping")
public String testRequestMapping(){
return "success";
}

}

在访问该方法需要在游览器中访问本机地址/工程名/test/testRequestMapping才能调用此方法

在该注解中同样拥有其他参数

例如在参数中指定method即可限定访问的方法

1
2
3
4
5
6
7
@RequestMapping(
value = {"/testRequestMapping", "/test"},
method = {RequestMethod.GET, RequestMethod.POST}
)
public String testRequestMapping(){
return "success";
}

例如上方的method限定了只有GET请求和POST的请求才能够调用此方法,当然SpringMVC也提供了例如@GetMapping@PostMapping注解来省略掉method参数来让我们快速的使用

其他参数

params参数

当在RequestMapping中使用了params参数和method类似,也是限定了请求,但是这个参数要求的是请求中必须携带参数或者要求请求携带的参数必须为指定的值

例如:

1
2
3
4
5
6
7
@RequestMapping
value = "/testRequestMapping"
,param = {"username","password!=123456"}
)
public String testRequestMapping(){
return "success";
}

这个方法就要求了参数中必须携带username参数且password参数不能为123456

其他参数一般更为少见,需要时可以查阅资料

在SpringMVC中的参数获取

在以往的Servlet中,我们获取参数往往都需要调用方法request.getParameter("")来获取携带的请求参数,而SpringMVC则帮助我们做了简化,我们只需要在方法中传入同名字的参数即可

例如:

1
2
3
4
5
@RequestMapping("/testParam")
public String testParam(String username,String password){
System.out.println("username:"+username+",password:"+password);
return "success";
}

通过以上方法即可方便的获取到游览器传过来的参数,当然,我们也可以将传入的参数放入集合中

例如:

1
2
3
4
5
@RequestMapping("/testParam")
public String testParam(@RequestParam Map<String,String> map){
String usernem = map.get("username");
return "success";
}

在参数中我们不仅可以获取Parameter,同样也可以直接获取到Servlet的request以及response以及session,但是一般并不会去这样使用。

1
2
3
4
5
@RequestMapping("/testParam")
public String testParam(HttpServletRequest reqeust){
String usernem = reqeust.getParameter("username");
return "success";
}

当然如果我们获取到的参数刚好是要放入Java Bean对象中,我们也可以直接在传入的参数中直接将对象作为参数,SpringMVC会自动装配值(当然名字得一样)

其他参数的获取

在方法的传参中我们能够获取Request对象,那么同样的我们也可以在里面获取其他的参数,以下是常用的获取参数

  • @PathVariable 路径变量(Resful风格)
  • @RequestHeader 获取请求头
  • @RequestParam 获取请求参数
  • @CookieValue 获取Cookie值
  • @RequestAttribute 获取request域属性

使用Cookie举例:

1
2
3
4
5
@RequestMapping("/testParam")
public String testParam(@CookieValue("GUID") Cookie cookie){
String value = cookie.getValue();
return "success";
}

向域对象中存放数据

在SpringMVC中可以轻易的获取值,那么除了先获取Request对象再放入值这种原始的方法之外,SpringMVC也提供了几种方法让我们能够轻易的向域对象中存放数据,不过这些方法之间差别不大。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//使用ServletAPI向request域对象共享数据
@RequestMapping("/testRequestByServletAPI")
public String testRequestByServletAPI(HttpServletRequest request){
request.setAttribute("testRequestScope","hello,servletAPI");
return "success";
}

@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
ModelAndView mav = new ModelAndView();
//处理模型数据,即向请求域request共享数据
mav.addObject("testRequestScope","hello,ModelAndView");
//设置视图名称
mav.setViewName("success");
return mav;
}

@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testRequestScope","hello,Model");
System.out.println(model.getClass().getName());
return "success";
}

@RequestMapping("/testMap")
public String testMap(Map<String,Object> map){
map.put("testRequestScope","hello,Map");
System.out.println(map.getClass().getName());
return "success";
}

@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope", "hello,ModelMap");
System.out.println(modelMap.getClass().getName());
return "success";
}

@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope","hello,session");
return "success";
}

@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
ServletContext context = session.getServletContext();
context.setAttribute("testApplicationScope","hello,application");
return "success";
}

视图的转发与重定向

在视图的配置中,我们通常都会配置视图前缀和视图后缀,因此只需要在方法中返回字符串SpringMVC会自动将拼接成完整的路径,在这里MVC做的是视图转发。

而如果我们想要把请求转发到其他方法,而不是让SpringMVC给我们做视图的转发,那么我们就可以这样写

代码如下:

1
2
3
4
5
6
7
8
9
10
@RequestMapping("/test")
public String test(){
//转发请求到地址为 http://地址/工程路径/success 中调用success方法处理请求
return "forward:/success";
}

@RequestMapping("/success")
public String success(){
return "success";
}

那么如果我们不希望SpringMVC给我们做转发到视图而是重定向,那么只需要将forward改成redirect即可

1
2
3
4
@RequestMapping("/test")
public String test(){
return "redirect:/success";
}

RESTful风格

SpringMVC同样支持Restful风格的写法,即为将参数包含在路径中发送请求

请求地址/book/page/1

如上所示的分页请求,就是一个Restful风格的请求

那么在SpringMVC中的请求处理中如下所示

1
2
3
@RequestMapping("/book/page/{num}")
public String test(@PathVariable("num")String page){
return "book";

page的页数加载请求地址中传入,在SpringMVC使用方法参数注解@PathVariable指定参数的名称即可获取到参数

而Result也规定了四种请求

操作 传统方式 REST风格
saveUser user—>POST
deleteUser?id=1 user/1—>DELETE
updateUser user—>PUT
getUserById?id=1 user/1—>GET

在游览器中实际上是无法直接发出四种请求的,我们可以借助ajax发送请求,或者使用SpringMVC提供的亲求转换过滤器HiddenHttpMethodFilter

可以在前面文章的SSM配置整合中找到配置方法,这里不再复述

在游览器中使用属性为hiddeninput标签,HiddenHttpMethodFilter会自动将请求转化为对应的请求

1
2
3
<form action="user/1" method="post">
<input type="hidden" name="_method" value="DELETE"/>
</form>

需要注意的是,游览器发送的请求必须是POST请求过滤器才能够进行转换

@ResponseBody

如果我们要向游览器返回数据,那么我们需要在方法上加上此注解,当然通常使用此注解的时候返回的都为JSON数据,那么此时我们需要依赖Jackson进行对象的转换

  • 本文标题:SSM学习小结
  • 本文作者:WuCheng
  • 创建时间:2022-11-13 16:33:48
  • 本文链接:https://www.wucheng.work/2022/11/13/SSM学习总结/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!