学习总结(2022.06.15-2022.06.24)
动态代理
JDK动态代理
JDK动态代理是基于Java的反射机制实现的,使用jdk中的接口和类实现代理对象的动态创建,但是要求代理对象必须实现接口。
JDK动态代理的实现步骤如下。
- 新建一个接口,作为目标接口。
1 2 3 4
| public interface UserService {
public void sayHello(); }
|
- 为接口创建实现类。
1 2 3 4 5 6 7 8
| public class UserServiceImpl implements UserService{
@Override public void sayHello() { System.out.println("hello spring"); } }
|
- 创建一个类实现InvocationHandler接口及其invoke方法,在
method.invoke()前后执行对方法的增强。
1 2 3 4 5 6
| public class CustomInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }
|
- 在main方法中调用
Proxy.newProxyInstance()创建JDK动态代理,使用代理对象执行方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Test public void mytest4() { UserService instance = new UserServiceImpl(); UserService userServiceProxy = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("jdsfkhjasdjyr"); Object invoke = method.invoke(instance, args); System.out.println("dfjkasdhjfisdya"); return invoke; } }); userServiceProxy.sayHello(); }
|
Cglib动态代理
Cglib动态代理只针对类实现代理,不需要实现接口。
Cglib动态代理的实现步骤如下。
- 创建一个类实现MethodInterceptor接口及其
intercept方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class MyMethodInterceptor implements MethodInterceptor {
@Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("before advice..."); Object object = methodProxy.invokeSuper(o, objects); System.out.println("after advice..."); return object; } }
|
- 在main方法中,通过Enhancer获取代理对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(HelloService.class); enhancer.setCallback(new MyMethodInterceptor()); HelloService proxy = (HelloService) enhancer.create(); proxy.sayHello(); } }
|
IOC/DI
IOC,控制反转,将生成实例的控制权交由Spring容器,容器只控制实例的生成,而开发人员只需要往容器中注册需要生成实例的类,即组件,并在需要时取出该组件的对象即可。
DI,依赖注入,即具体的IOC实现方式。一般通过配置文件和注解来完成依赖注入。
XML
在Spring的XML文件中注册组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
<bean id="userDao" class="com.cskaoyan.dao.UserDaoImpl"/>
<bean id="userService" class="com.cskaoyan.service.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean>
<bean id="orderService" class="com.cskaoyan.service.OrderServiceImpl"> <property name="userDao" ref="userDao"/> </bean>
|
取出组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Test public void mytest1() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao"); UserService userService1 = applicationContext.getBean(UserService.class); UserService userService2 = applicationContext.getBean(UserServiceImpl.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class); }
|
注解
也可以使用注解注册组件。可以在注解中指定组件id,也可以使用默认的组件id,默认id一般为类名的首字母小写。
1 2 3 4 5 6 7 8 9 10 11
| @Component
@Service
@Repository
@Controller
|
配置类
在一个类上加上@Configuration注解,表示该类被注册为一个组件,且该类为一个配置类。类中的方法可以被@Bean注解修饰,表示该方法的返回值的实例会被注册为一个组件。
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
| @Configuration
@ComponentScan("com.cskaoyan") public class SpringConfiguration {
@Bean("druidDatasource") public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/cskaoyan_db"); dataSource.setUsername("root"); dataSource.setPassword("123456");
return dataSource; }
@Bean public UserService userService(@Qualifier("userDaoImpl") UserDao userDao) { UserServiceImpl userService = new UserServiceImpl(); userService.setUserDao(userDao); return userService; } }
|
@Autowired
类中的成员变量可以通过@Autowired注解注册为组件,此时该成员变量就不需要开发人员手动初始化。@Autowired的本质是利用容器提供的set方法。
1 2 3 4
| @Autowired
@Qualifier("userDaoImpl2") UserDao userDao;
|
组件的生命周期
组件可以使用@Scope注解来确定自己的作用域。如果作用域为singleton,即单例模式,那么每次从容器中取出的组件都是同一个组件;如果使用的是prototype,即原型模式,那么每次取出的都是新的实例。Spring默认使用单例模式。
在单例模式下,容器初始化时就会开始组件的生命周期;如果是原型模式,则只有在获得组件时才开始生命周期。
组件的生命周期如下。
实例化。
给成员变量赋值。一般通过@Autowired等注解实现。
觉醒(Aware),如果组件实现了Aware相关的接口,在这时组件就可以通过该接口的set方法设置一些数据。比如,如果组件实现了BeanNameAware接口,那么就可以调用setBeanName(String beanName)方法。
BeanPostProcessor,提供了两个实现方法:postProcessBeforeInitialization和postProcessAfterInitialization,其作用范围为所有注册的组件。此时触发的是before方法。
InitializingBean的afterPropertiesSet方法 ,或是自定义的init方法。
1 2 3 4 5 6 7 8 9 10
| @Override public void afterPropertiesSet() throws Exception { System.out.println("InitializingBean提供的init方法"); }
@PostConstruct public void init() { System.out.println("自定义init"); }
|
BeanPostProcessor的after方法。
使用组件。
DisposableBean的destroy方法,或是自定义的destroy方法。
1 2 3 4 5 6 7 8 9
| @Override public void destroy() throws Exception { System.out.println("DisposableBean提供的destroy"); }
@PreDestroy public void customDestroy() { System.out.println("自定义destroy"); }
|
AOP
AOP,面向切面编程,可以将一些与业务无关,但是所有业务都需要调用的逻辑封装起来(如日志,权限控制等),将其作为一种增强。AOP基于动态代理实现,但是相比动态代理,AOP可以更灵活的筛选需要增强的方法以及增强所要做的业务。
现在一般使用AspectJ框架实现AOP。
切入点Pointcut
使用execution指定需要增强的方法。
1 2 3 4 5 6 7
| <aop:comfig> <aop:pointcut id="mypointcut1" expression="execution(public void com.cskaoyan.service.UserServiceImpl.sayHello(String))"/> <aop:pointcut id="mypointcut2" expression="execution(* co*..say*(*))"/> <aop:pointcut id="mypointcut3" expression="execution(* co*.cskaoyan..UserServiceImpl.sayHello(..))"/> </aop:config>
|
使用@annotation指定特殊的增强方法。
1 2 3 4
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CountTime { }
|
1 2 3 4 5
| <aop:config>
<aop:pointcut id="mypointcut" expression="@annotation(com.cskaoyan.CountTime)"/> </aop:config>
|
通知器Advisor
1 2 3 4
| <aop:config> <aop:advisor advice-ref="countTimeInterceptor" pointcut="execution(public void com.cskaoyan.service.UserServiceImpl.sayHello(String))"/> </aop:config>
|
切面Aspect
切面是上面两种的结合,即在指定的切入点写入自定义的通知。切面有五个通知方法,分别是Before、After、Around、AfterReturning、AfterThrowing。Before和After表示在该方法的执行前后的通知;Around类似于InvocationHandler的invoke()和MethodInterceptor的invoke(),可以在执行proceed方法前后进行操作,Around也是使用的最多的通知;AfterReturning是在方法返回值后使用的通知,可以接收方法的返回值;AfterThrowing是在方法抛出异常后使用的通知,可以接收方法的异常。
五个通知的先后顺序是:Before->Around->After/AfterThrowing->AfterReturning
可以使用@Aspect注解指定一个组件为切面组件,且在类中的方法要使用@Before、@After、@Around、@AfterReturning、@AfterThrowing注解修饰方法。
@Aspect
@Component
public class CustomAspect {
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
| @Aspect @Component public class CustomAspect {
@Pointcut("execution(* com.cskaoyan.service..*(..))") public void servicePointcut() { }
@Before("servicePointcut()") public void beforex() { System.out.println("before方法"); }
@After("servicePointcut()") public void after() { System.out.println("after通知方法"); }
@Around("servicePointcut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); System.out.println("开始时间:" + start);
Object proceed = joinPoint.proceed();
long end = System.currentTimeMillis(); System.out.println("方法执行时间:" + (end - start)); return proceed; }
@AfterReturning(value = "servicePointcut()",returning = "result") public void afterReturning(Object result) { System.out.println("AfterReturning接收到的结果:" + result); }
@AfterThrowing(value = "servicePointcut()",throwing = "exception") public void afterThrowing(Throwable exception) { System.out.println("afterThrowing通知:" + exception.getMessage()); } }
|
连接点JoinPoint
在Before和Around通知执行过程中可以获得JoinPoint,通过它拿到一些信息,这意味着在通知中可以有更多操作空间。
joinPoint可以直接写在通知方法的形参中,通过调用其方法获取信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Before("servicePointcut()") public void beforex(JoinPoint joinPoint) { Object proxy = joinPoint.getThis(); Object target = joinPoint.getTarget(); Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature(); String name = signature.getName(); System.out.println("正在执行的方法:" + name);
System.out.println("before方法"); }
|
Spring事务
Spring事务有三种事务传播行为,分别是REQUIRED、REQUIRES_NEW、NESTED。
REQUIRED:Spring默认。如果外围的方法不包含事务,那么被修饰的内部方法就添加一个新的事务;如果外围方法包含事务,则内部方法加入该事务,要么一起提交要么一起回滚。
REQUIRES_NEW:如果外围的方法不包含事务,那么内部方法就添加一个新的事务;如果外围方法包含事务,内部方法也添加一个事务,且该事务与外围的事务独立。
NESTED:如果外围的方法不包含事务,那么内部方法就添加一个新的事务;如果外围方法包含事务,内部方法也添加一个事务,且该事务与外围的事务成嵌套关系。
Spring事务的开启方式有:
PlatFormTransactionManager,平台事务管理器
1 2 3 4 5 6 7 8
| public interface PlatformTransactionManager extends TransactionManager { TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException; void commit(TransactionStatus var1) throws TransactionException; void rollback(TransactionStatus var1) throws TransactionException; }
|
TransactionTemplate
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
| public class TransferTransactionCallBack implements TransactionCallback {
AccountMapper accountMapper; Integer fromId; Integer destId; Integer fromMoney; Integer destMoney;
public TransferTransactionCallBack(AccountMapper accountMapper, Integer fromId, Integer destId, Integer fromMoney, Integer destMoney) { this.accountMapper = accountMapper; this.fromId = fromId; this.destId = destId; this.fromMoney = fromMoney; this.destMoney = destMoney; }
@Override public Object doInTransaction(TransactionStatus transactionStatus) { int update1 = accountMapper.update(fromId, fromMoney); int i = 1 / 0; int update2 = accountMapper.update(destId, destMoney); return null; } }
|
在配置类使用@EnableTransactionManagement注解开启事务,在需要事务的类或方法中使用注解@Transactional。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Transactional @Override public void transfer(int fromId, int destId, Integer money) { Integer fromMoney = accountMapper.select(fromId); Integer destMoney = accountMapper.select(destId);
fromMoney -= money; destMoney += money;
int update1 = accountMapper.update(fromId, fromMoney); int i = 1 / 0; int update2 = accountMapper.update(destId, destMoney);
}
|
SpringMVC
传统的Servlet映射过程繁琐,需要我们自己处理url和Servlet的映射关系,并根据url判断、分发接口至不同的方法中,且请求参数、响应结果都要自己手动拆封json。在SpringMVC中,则只由一个Servlet:DispatcherServlet来解决上述问题。
在SpringMVC中的流程如下。
- 客户端(浏览器)发送请求,直接请求到
DispatcherServlet。
DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
- 解析到对应的
Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
HandlerAdapter 会根据 Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
- 处理器处理完业务后,会返回一个
ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
ViewResolver 会根据逻辑 View 查找实际的 View。
DispaterServlet 把返回的 Model 传给 View(视图渲染)。
- 把
View 返回给请求者(浏览器)
在SpringMVC中,可以完全使用JavaConfig代替配置文件,主要有:
AACDSI(AbstractAnnotationConfigDispatcherServletInitializer)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfiguration.class}; }
@Override protected Class<?>[] getServletConfigClasses() { return new Class[]{MvcConfiguration.class}; }
@Override protected String[] getServletMappings() { return new String[]{"/"}; } }
|
Spring配置类,用@Configuration修饰
1 2 3 4 5
| @ComponentScan(value = "com.cskaoyan", excludeFilters = @ComponentScan.Filter({Controller.class, EnableWebMvc.class})) @Configuration public class SpringConfiguration { }
|
SpringMVC配置类,用@EnableWebMvc修饰
1 2 3 4
| @ComponentScan("com.cskaoyan.controller") @EnableWebMvc public class MvcConfiguration implements WebMvcConfigurer { }
|
@RequestMapping
用于url的路径映射,可以写在类或方法上。
同一个RequestMapping可以映射多个路径,通过数组表示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @RequestMapping({"hello","hello2","hello3"}) @ResponseBody public String hello() { return "hello world"; }
@RequestMapping({"goodbye/*", "goodbye*"}) @ResponseBody public String goodbye() { return "byebye"; }
|
如果请求url包含共同的前缀,可以提取至修饰类的注解中,这称为窄化请求。
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
| @Controller @RequestMapping("user") public class UserController {
@RequestMapping("login") @ResponseBody public String login() { return "login"; } @RequestMapping("register") @ResponseBody public String register() { return "register"; } @RequestMapping("modify") @ResponseBody public String modify() { return "modify"; } @RequestMapping("remove") @ResponseBody public String remove() { return "remove"; } }
|
@RequestMapping还有多种属性:
@ResponseBody
在Controller上添加注解@ResponseBody,即可将返回值自动包装为json。
1 2 3 4 5 6 7 8 9
| @Controller public class JsonController {
@RequestMapping("json") @ResponseBody public User json() { return new User("123","456"); } }
|
可以使用@RestController来代替@Controller和 @ResponseBody。
请求参数的接收
如果是普通的get请求,可以使用方法形参来接收参数,但要求形参名与参数名必须一致。类型支持:
- 字符串String
- 基本类型以及对应包装类
- 数组
- Date(需要使用@DateTimeFormat来标准化日期格式)
- 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @RequestMapping("register5") public BaseRespVo register5(String username, String password, Integer age, String[] hobbies, Integer[] ids, @DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday) { return BaseRespVo.ok(); }
@RequestMapping("file") public BaseRespVo uploadFile(MultipartFile file) throws IOException { String name = file.getName(); String originalFilename = file.getOriginalFilename(); long size = file.getSize(); String contentType = file.getContentType(); File saveFile = new File("D:\\WorkSpace\\j40_workspace", originalFilename); file.transferTo(saveFile); return BaseRespVo.ok(); }
|
除了形参,还可以使用自定义的引用类型对象,如果使用引用类型的对象时,Servlet会去创建一个新的实例。看你这个类中有哪些成员变量,如果成员变量名和请求参数名相同,就会使用成员变量来接收请求参数,使用set方法来进行封装。所以要求请求参数名与引用类中的成员变量名一致。
1 2 3 4 5 6 7 8 9 10
| @Data public class User { String username; String password; Integer age; String[] hobbies; Integer[] ids; @DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday; }
|
1 2 3 4
| @RequestMapping("register6") public BaseRespVo register6(User user) { return BaseRespVo.ok(); }
|
如果是post请求,那么发送的请求参数一般是json,需要对json转换成对应的实例。此时需要增加@RequestBody注解来修饰json对应的bean类。
1 2 3 4
| @RequestMapping("user/login") public BaseRespVo login(@RequestBody JsonUser user) { return BaseRespVo.ok(user); }
|
如果需要获得Cookie,则不能直接获得,需要通过Request获得。
1 2 3 4 5 6 7 8 9 10
| @RequestMapping("cookies") public BaseRespVo cookies(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { System.out.println(cookie.getName() + " --> " + cookie.getValue()); }
return BaseRespVo.ok(); }
|
而session可以直接获得。
1 2 3 4 5
| @RequestMapping("session2") public BaseRespVo session2(HttpSession session) { Object username = session.getAttribute("username"); return BaseRespVo.ok(username); }
|
@PathVariable
获取请求url中指定占位符的值,并将值赋给对应的形参。
1 2 3 4
| @RequestMapping("note/{id}") public BaseRespVo note(@PathVariable("id") Integer id) { return BaseRespVo.ok(); }
|
Converter
Converter,类型转换器,使用主要是在请求参数封装的过程中,也就是Handler方法的形参。在形参并不是String的情况下会用到Converter。
一般情况下需要自己定义Converter。如从String转为User。
1 2 3 4 5 6 7 8 9 10
| public class String2UserConverter implements Converter<String, User> { @Override public User convert(String s) { User user = new User(); user.setUsername(s); user.setPassword(s); return user; } }
|
1 2 3 4 5
| @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new String2UserConverter()); }
|
ResourceHandler
因为在SpringMVC中,DispatcherServlet的映射范围是/,即除了jsp外的所有请求,之前JavaEE的访问静态资源的方式就无法使用了,所以需要使用ResourceHandler来进行静态资源映射。
1 2 3 4 5 6
| @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/pic/**").addResourceLocations("classpath:/"); registry.addResourceHandler("/jpg/**").addResourceLocations("/"); registry.addResourceHandler("/png/**").addResourceLocations("file:D:\\spring/"); }
|
HandlerInterceptor
在Handler前执行,起到了拦截的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
|
异常处理
有两种异常处理方式:统一的全局异常处理 HandlerExceptionResolver和自定义异常处理 ExceptionHandler。
HandlerExceptionResolver是一个接口,接口里提供了resolveException方法,重写该方法来处理全局异常。
1 2 3 4 5 6 7 8 9 10 11 12
| @Component public class CustomHandlerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { if (e instanceof ArithmeticException) { System.out.println(e.getMessage()); } return new ModelAndView("/exception.jsp"); } }
|
ExceptionHandler是一个注解,value是对应的异常,旨在用对应的方法处理对应的异常。一般放在ontrollerAdvice组件中的方法上。
1 2 3 4 5 6 7 8 9 10 11
| @ControllerAdvice public class CustomExceptionControllerAdvice {
@ExceptionHandler(ArithmeticException.class) @ResponseBody public BaseRespVo method1(ArithmeticException exception) { return BaseRespVo.fail(exception.getMessage()); } }
|
SpringBoot
SpringBoot本质上就是一个Spring框架,但是它简化了Spring的配置。在没有给具体配置前,SpringBoot会使用默认的配置,只有给到具体的配置,才采用具体的配置。
启动类:
1 2 3 4 5 6 7 8 9
| @SpringBootApplication public class Demo1FirstSbApplication { public static void main(String[] args) { SpringApplication.run(Demo1FirstSbApplication.class, args); }
}
|
SpringBoot对web的支持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| server: port: 8083 servlet: context-path: /demo1
spring: mvc: static-path-pattern: /pic/**
resources: static-locations: file:d:/spring/
|
SpringBoot对Mybatis的支持:
1 2 3 4 5 6
| spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf-8 username: root password: 123456
|
1 2 3 4 5 6 7 8 9 10
| @SpringBootApplication
@MapperScan("com.cskaoyan.mapper") public class Demo1FirstSbApplication {
public static void main(String[] args) { SpringApplication.run(Demo1FirstSbApplication.class, args); }
}
|
逆向工程
逆向工程可以根据数据库中的表自动生成接口、方法、映射文件,只需要操作对应的Example类就可以操作数据库。
接口方法示例。
1 2 3 4 5 6
| protected String orderByClause;
protected boolean distinct;
protected List<Criteria> oredCriteria;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Test public void testFindUserByName(){
UserExample userExample = new UserExample(); userExample.setOrderByClause("username asc"); userExample.setDistinct(false); UserExample.Criteria criteria = userExample.createCriteria(); criteria.andUsernameEqualTo("张三");
List<User> users = userMapper.selectByExample(userExample);
System.out.println(users); }
|
逆向工程生成的内容会有一定问题,如果表中的字段存在sql语句中的关键字,需要自己手动在mapper.xml文件中添加转义字符。
TypeHandler
TypeHandler是Mybatis输入输出映射过程中的类型处理器,用于处理jdbcType和JavaType间的转换。
主要方式是实现TypeHandler接口。
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 51 52 53 54
| @MappedTypes(Integer[].class) @MappedJdbcTypes(JdbcType.VARCHAR) public class IntegerArrayTypeHandler implements TypeHandler<Integer[]> {
ObjectMapper objectMapper = new ObjectMapper(); @Override public void setParameter(PreparedStatement preparedStatement, int index, Integer[] integers, JdbcType jdbcType) throws SQLException { String value = null; try { value = objectMapper.writeValueAsString(integers); } catch (JsonProcessingException e) { e.printStackTrace(); } preparedStatement.setString(index,value); }
@Override public Integer[] getResult(ResultSet resultSet, String columName) throws SQLException { String result = resultSet.getString(columName); return transfer(result); }
@Override public Integer[] getResult(ResultSet resultSet, int index) throws SQLException { String result = resultSet.getString(index); return transfer(result); }
@Override public Integer[] getResult(CallableStatement callableStatement, int i) throws SQLException { String result = callableStatement.getString(i); return transfer(result); }
private Integer[] transfer(String result) { Integer[] integers = new Integer[0]; try { integers = objectMapper.readValue(result, Integer[].class); } catch (JsonProcessingException e) { e.printStackTrace(); } return integers; } }
|
配置自定义的TypeHandler
1 2
| mybatis: type-handlers-package: com.cskaoyan.typehandler
|
Shiro框架
Shiro安全框架,Apache Shiro是一个开源安全框架,提供身份认证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。
Shiro在SpringMVC应用程序中以Filter的形式存在。
Shiro的一些核心术语如下。
- Authentication,认证是身份验证的过程 -您正在尝试证明用户是他们所说的人。为此,用户需要提供系统理解和信任的某种身份证明。认证其实就是我们通常所说的登录。
- Authorization,授权或访问控制是指定对资源的访问权限的功能。换句话说,谁可以访问什么。比如是否允许用户编辑此数据,这些都是决定用户有权访问的内容的决定。我们当前主要针对的是URL级别的权限访问控制。
- Subject,主体,在Shiro中所做的几乎所有操作都基于当前正在执行的用户,也就是基本上Shiro的操作都是使用Subject操作的,Subject指的就是当前操作的用户。在代码中的任何位置都可以轻松获得Subject,通过Subject可以方便的操作Shiro。比如我们可以使用Subject提供的方法来执行认证、判断是否认证等操作。
- Principals,主体鉴定后的参数,也就是认证后的用户信息,可以是姓名、用户id、用户对象等形式。
- Credentials,用来验证身份的秘密数据,通常指密码,生物数据比如指纹、面部、瞳孔等。
- Realms,域或领域,安全的特殊数据存储对象(DAO),Shiro中的Realm主要是让你能够获得对应的认证信息和授权信息。
- Token,令牌,Shiro中的Token是作为登录操作的参数。
Shiro的核心组件如下:
- SecurityManager,安全管理器
- Authenticator,认证器
- SessionManager,会话管理器
- Realm,域
- CacheManager,缓存管理器
- Cryptography,密码学
Shiro提供的Filter类型如下。
| Filter名称 |
Filter类 |
说明 |
| anon |
org.apache.shiro.web.filter.authc.AnonymousFilter |
匿名Filter,作用范围内的请求不需要认证和授权 |
| authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
认证Filter,作用范围内的请求需要完成认证 |
| perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
权限Filter,作用范围内的请求需要完成认证和授权 |
我们可以设置不同类型的Filter分别映射一些不同的URL范围,当请求发送到应用程序时,根据请求URL分别判断使用哪一些Filter,在Filter中决定是否继续访问流程。在SpringBoot中,主要在配置文件中配置。
Realm
Shiro中的Realm主要是让你能够获得对应的认证信息和授权信息。
一般通过自定义Realm来使用,需要继承一个抽象类AuthorizingRealm,并实现两个抽象方法:
- doGetAuthenticationInfo :获得认证信息,即根据token中的用户名查询该用户在系统中的Credentials,并且构造AuthenInfo。
- doGetAuthorizationInfo :获得授权信息,即根据Principal(放入AuthenInfo中的第一个参数)查询该用户在系统中的权限信息。
在这两个方法中去写获得用户认证信息和授权信息的业务代码。
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
| @Component public class CustomRealm extends AuthorizingRealm {
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername();
String password = selectPasswordByUsername(username);
return new SimpleAuthenticationInfo(username,password,getName()); }
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal(); List<String> permissions = getPermissionsByUsername(primaryPrincipal);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addStringPermissions(permissions); return simpleAuthorizationInfo; }
private String selectPasswordByUsername(String username) {
return "123456"; }
private List<String> getPermissionsByUsername(String username) { return Arrays.asList("admin:user:list", "admin:admin:list"); }
}
|
认证
在对应请求中获得subject,并执行subject的login方法。
- 因为登录请求不需要权限,需要将其权限设为anon
- subject对象可以在所有组件中获得,方式为
Subject subject = SecurityUtils.getSubject()
- 在绝大多数场景下,login方法的参数都为AuthenticationToken接口,可以直接使用其实现类UsernamePasswordToken:
subject.login(new UsernamePasswordToken(username,password))
- 如果需要获得sessionId,可以使用subject获得:
subject.getSession().getId()
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
| @PostMapping("login") public BaseRespVo<LoginUserData> login(@RequestBody Map map) { String username = (String)map.get("username"); String password = (String)map.get("password");
Subject subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(username,password));
if (subject.isAuthenticated()) { System.out.println("认证成功"); }
LoginUserData loginUserData = new LoginUserData(); AdminInfoBean adminInfo = new AdminInfoBean(); adminInfo.setAvatar("https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"); adminInfo.setNickName("admin123"); loginUserData.setAdminInfo(adminInfo); loginUserData.setToken((String) subject.getSession().getId()); return BaseRespVo.ok(loginUserData); }
|
- 认证后可以通过
subject.getPrincipals().getPrimaryPrincipal()来获取用户在数据库中的其他信息。该方法的返回值为Object,可以直接转换为对应的用户信息PO类。
- 可以通过
subject.logout()登出用户。
HibernateValidation
在需要对一些参数进行校验时,可以在Handler方法的形参上增加注解@Valid或@Validated,或在引用类型中的成员变量上增加校验功能的注解。
常见注解如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 常见的注解 (Bean Validation 中内置的 constraint) @Null 被注释的元素必须为 null @NotNull 被注释的元素必须不为 null @Size(max=, min=) 被注释的元素的大小必须在指定的范围内 @AssertTrue 被注释的元素必须为 true @AssertFalse 被注释的元素必须为 false @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内 @Past 被注释的元素必须是一个过去的日期 Date @Future 被注释的元素必须是一个将来的日期 @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式 Hibernate Validator 附加的 constraint @NotBlank(message =) 验证字符串非null,且长度必须大于0 @Email 被注释的元素必须是电子邮箱地址 @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内 @NotEmpty 被注释的字符串的必须非空 @Range(min=,max=,message=) 被注释的元素必须在合适的范围内
|