1,什么是注解?
注解是一种元数据,就是描述java代码的数据。用于给java的类型、方法、属性、参数、代码等添加修饰。注意:注解本身仅仅只是元数据,不含业务逻辑。实际解析元模型的代码负责根据注解来赋予业务逻辑。(所谓元数据就是:描述数据的数据。)
2,注解有什么用?
注解的用处就是描述java代码元素,Java程序中通过注解,可以简洁、优雅的给代码添加修饰。java提供了4种元注解(就是对注解的描述,用于描述注解的注解),这4中元注解,确定了注解对java代码。如下:
@Documented //注解是否将包含在JavaDoc中;描述注解是否具有
@Retention //定义该注解的生命周期;包括:RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME;明确注解作用在源码阶段、编译阶段、运行期。
@Target //注解用于什么地方;参见枚举ElementType;不设定,则默认所有java元素都可以使用;
/**
*ElementType.TYPE(可用于类、接口、注解类型、枚举)
*ElementType.FIELD(可用于成员变量、枚举常量)
*ElementType.METHOD(用于方法,静态方法、非静态方法,不能用于构造方法)
*ElementType.PARAMETER(用于参数(方法形参))
*ElementType.CONSTRUCTOR(用于构造器)
*ElementType.LOCAL_VARIABLE(用于方法局部变量)
*ElementType.ANNOTATION_TYPE(用于注解类型)
*ElementType.PACKAGE(用于包类型,只能用在package-info.java文件中)
*ElementType.TYPE_PARAMETER(用于类型参数,即泛型方法、泛型类、泛型接口)
*/
@Inherited //是否允许子类继承该注解
其中,生命期为:RetentionPolicy.RUNTIME的注解,能够在代码实际运行时,动态添加功能。而动态添加功能本身是复杂的,由注解在实际解析时,通过反射判断是否存在对应注解,如果存在,根据注解的属性参数,完成逻辑处理。实际使用注解,则非常简洁、易读,屏蔽复杂性,将注意力集中当前的业务逻辑实现上。
3,怎么使用注解?
注解一旦定义,包括注解本身、注解解析机制等完成后,注解的实际使用就非常简单,只需要了解注解的@Target,在合适的java代码元素上标注就完成注解的使用。如下:
@Component
@MailEventProcessor("mailone")
public class ConcreteMailSender extends BaseMailSender {
@Override
public void sendMail(MailSenderContext mailCxt) throws Exception {
//处理邮件发送
}
}
4,利用spring框架自定义类注解
自定义注解:
/**
* 该注解描述邮件处理器处理的具体邮件类型;value为邮件标识;
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MailEventProcessor {
String value() default "";
}
注解修饰类生效方式一:
通过spring的扩展点实现,本例采用BeanPostProcessor实现,根据需要可以采用其它spring扩展点实现,比如:BeanFactoryPostProcessor的扩展类的扩展点等处理注解;
MailEventConfigProcessor:
@Component
class MailEventConfigProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean,
String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean,
String beanName) throws BeansException {
final Class<? extends Object> clazz = bean.getClass();
final MailEventProcessor mailEventProcessor = (MailEventProcessor) clazz.getAnnotation(MailEventProcessor.class);
if (null != mailEventProcessor) {
final String mailType = mailEventProcessor.value();
if (!StringUtils.isNullOrEmpty(mailType)) {
if (bean instanceof BaseMailSender)
MailProcessorUtils.map.put(mailType, (BaseMailSender)bean);
}
}
return bean;
}
}
MailProcessorUtils:
public final class MailProcessorUtils {
public static Map<String, BaseMailSender> map = new ConcurrentHashMap<String, BaseMailSender>();
// 通过邮件类型,发送邮件
public static void sendMail(final String strMailType, final MailSenderContext mailCxt) throws Exception {
if (map.containsKey(strMailType)) {
BaseMailSender sender = map.get(strMailType);
sender.sendMail(mailCxt);
}
}
}
事件接受处理时,直接如下调用:
MailProcessorUtils.sendMail("mailone", theContext);
注解修饰生效方式二:
自定义扫描,并获取注解信息进行处理;
1,loader来扫描;
@Component
public class MailProcessorScanLoader implements ResourceLoaderAware {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
private static Map<String, BaseMailSender> map = null;
public Map<String, BaseMailSender> getProcessorMap () {
if (null == map) {
initSenderMap();
}
return map;
}
private void initSenderMap() {
map = new ConcurrentHashMap<String, BaseMailSender>();
Set<Class<?>> classList = this.scan("com.gj.springboot.restful.email");
for (Class<?> clazz : classList) {
MailEventProcessor anno = (MailEventProcessor) clazz.getAnnotation(MailEventProcessor.class);
if (null != anno) {
String strMailType = anno.value();
// 注解值为key存放bean
if (BaseMailSender.class.isAssignableFrom(clazz)) {
map.put(strMailType, (BaseMailSender) SpringBeanUtils.getBean(clazz));
}
}
}
}
private Set<Class<?>> scan(final String basePackage) {
Set<Class<?>> classes = new HashSet<Class<?>>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ org.springframework.util.ClassUtils.convertClassNameToResourcePath(
SystemPropertyUtils.resolvePlaceholders(basePackage))
+ "/**/*.class";
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
if (resource.isReadable()) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
try {
classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
return classes;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
}
}
2,调用扫描,并提供发送方法:
MailScanProcessorUtils:
@Component
public class MailScanProcessorUtils {
@Autowired
private MailProcessorScanLoader mailProcessorScanLoader;
// 通过邮件类型,发送邮件
public void sendMail(final String strMailType, final MailSenderContext mailCxt) throws Exception {
Map<String, BaseMailSender> map = mailProcessorScanLoader.getProcessorMap();
System.out.println("map daxiao:" + map.size());
if (map.containsKey(strMailType)) {
BaseMailSender sender = map.get(strMailType);
sender.sendMail(mailCxt);
}
}
}
事件接受处理时,直接如下调用:
mailScanProcessorUtils.sendMail("mailone", theContext);
比较起来,第一种方法更简单直接,一般没有必要把被注解修饰的类放在spring启动扫描外的路径。
上面两种方式,都可以扩展,解析ElementType.METHOD 注解,修饰方法功能;但是一般情况下,有更简单、更好的方式。参见下节。
5,利用spring框架自定义method注解;
采用spring 提供的AOP扩展来实现;
1,定义注解
/**
* 安全权限验证;
*
* @author gaojun
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Security {
String value() default "";
}
2,添加切面,并绑定注解;
@Component
@Aspect
public class Authorizer {
@Pointcut("@annotation(com.gj.springboot.restful.annotation.Security)")
private void cut() { }
/**
* 定制一个环绕通知
* @param joinPoint
*/
@Around("cut()&&@annotation(security)")
public Object around(ProceedingJoinPoint joinPoint, Security security){
System.out.println("around通知之开始");
Object result = null;
String strOperater = security.value();
// TODO 获取当前人员是否具有对应配置的权限;
if ("operaterone".equals(strOperater)) {
try {
result = joinPoint.proceed();
} catch (Throwable e) {
//异常处理类似,只是需要定义返回类型,包装信息;
e.printStackTrace();
}
}
System.out.println("around通知之结束");
return result;
}
}
3,使用直接在需要的方法上面添加注解即可:
@Security("operaterone")
public String list(Model model) throws Exception{